Merge branch 'develop' of github.com:Budibase/budibase into chore/esbuild

This commit is contained in:
mike12345567 2023-04-25 17:37:56 +01:00
commit 49b45f5e67
262 changed files with 8555 additions and 1646 deletions

View File

@ -1,46 +1,45 @@
name: Budibase CI
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
workflow_dispatch:
workflow_dispatch:
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
BASE_BRANCH: ${{ github.event.pull_request.base.ref}}
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: yarn
- run: yarn lint
- uses: actions/checkout@v2
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- run: yarn
- run: yarn lint
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn
- run: yarn bootstrap
- run: yarn build
@ -49,17 +48,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn
- run: yarn bootstrap
- run: yarn build
- run: yarn test --ignore=@budibase/pro
- run: yarn test
- uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
@ -70,28 +68,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn
- run: yarn bootstrap
- run: yarn test --scope=@budibase/pro
- run: yarn test:pro
integration-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- name: Use Node.js 14.x
uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro $BRANCH $BASE_BRANCH
- run: yarn && yarn bootstrap && yarn build
- run: |
cd qa-core
@ -100,24 +96,3 @@ jobs:
env:
BB_ADMIN_USER_EMAIL: admin
BB_ADMIN_USER_PASSWORD: admin
check-pro-submodule:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
fetch-depth: 0
- name: Check submodule
run: |
cd packages/pro
git fetch
if ! git merge-base --is-ancestor $(git log -n 1 --pretty=format:%H) origin/develop; then
echo "Current commit has not been merged to develop"
echo "Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md"
exit 1
else
echo "All good, the submodule had been merged!"
fi

View File

@ -1,21 +1,21 @@
name: Budibase Prerelease
concurrency: release-prerelease
on:
push:
on:
push:
branches:
- develop
paths:
- ".aws/**"
- ".github/**"
- "charts/**"
- "packages/**"
- "scripts/**"
- "package.json"
- "yarn.lock"
- "package.json"
- "yarn.lock"
workflow_dispatch:
- '.aws/**'
- '.github/**'
- 'charts/**'
- 'packages/**'
- 'scripts/**'
- 'package.json'
- 'yarn.lock'
- 'package.json'
- 'yarn.lock'
workflow_dispatch:
env:
# Posthog token used by ui at build time
@ -24,35 +24,35 @@ env:
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
FEATURE_PREVIEW_URL: https://budirelease.live
jobs:
release-images:
runs-on: ubuntu-latest
runs-on: ubuntu-latest
steps:
- name: Fail if branch is not develop
if: github.ref != 'refs/heads/develop'
run: |
if: github.ref != 'refs/heads/develop'
run: |
echo "Ref is not develop, you must run this job from develop."
exit 1
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- uses: actions/setup-node@v1
with:
node-version: 14.x
- run: yarn
- name: Install Pro
run: yarn install:pro develop
- run: yarn
- run: yarn bootstrap
- run: yarn build
- run: yarn build:sdk
# - run: yarn test
# - run: yarn test
- name: Publish budibase packages to NPM
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
run: |
# setup the username and email.
git config --global user.name "Budibase Staging Release Bot"
git config --global user.email "<>"
@ -60,7 +60,7 @@ jobs:
yarn release:develop
- name: Build/release Docker images
run: |
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build:docker:develop
env:

View File

@ -2,55 +2,55 @@ name: Budibase Release
concurrency: release
on:
push:
push:
branches:
- master
paths:
- ".aws/**"
- ".github/**"
- "charts/**"
- "packages/**"
- "scripts/**"
- "package.json"
- "yarn.lock"
- "package.json"
- "yarn.lock"
workflow_dispatch:
inputs:
versioning:
type: choice
description: "Versioning type: patch, minor, major"
default: patch
options:
- patch
- minor
- major
required: true
- '.aws/**'
- '.github/**'
- 'charts/**'
- 'packages/**'
- 'scripts/**'
- 'package.json'
- 'yarn.lock'
- 'package.json'
- 'yarn.lock'
workflow_dispatch:
inputs:
versioning:
type: choice
description: "Versioning type: patch, minor, major"
default: patch
options:
- patch
- minor
- major
required: true
env:
# Posthog token used by ui at build time
# Posthog token used by ui at build time
POSTHOG_TOKEN: phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
jobs:
release-images:
runs-on: ubuntu-latest
steps:
- name: Fail if branch is not master
if: github.ref != 'refs/heads/master'
run: |
if: github.ref != 'refs/heads/master'
run: |
echo "Ref is not master, you must run this job from master."
exit 1
- uses: actions/checkout@v2
with:
submodules: true
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
- uses: actions/setup-node@v1
with:
node-version: 14.x
- name: Install Pro
run: yarn install:pro master
- run: yarn
- run: yarn bootstrap
- run: yarn lint
@ -68,14 +68,13 @@ jobs:
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
yarn release
- name: "Get Previous tag"
- name: 'Get Previous tag'
id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
- name: Build/release Docker images
run: |
run: |
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
yarn build
yarn build:docker
env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "packages/pro"]
path = packages/pro
url = git@github.com:Budibase/budibase-pro.git

View File

@ -1,4 +0,0 @@
# .husky/post-checkout
# ...
git config submodule.recurse true

View File

@ -216,35 +216,9 @@ If you are having issues between updates of the builder, please use the guide [h
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="http://martinmck.com"><img src="https://avatars1.githubusercontent.com/u/11256663?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Martin McKeaveney</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=shogunpurple" title="Tests">⚠️</a> <a href="#infra-shogunpurple" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="http://www.michaeldrury.co.uk/"><img src="https://avatars2.githubusercontent.com/u/4407001?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Drury</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=mike12345567" title="Tests">⚠️</a> <a href="#infra-mike12345567" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/aptkingston"><img src="https://avatars3.githubusercontent.com/u/9075550?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Kingston</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=aptkingston" title="Tests">⚠️</a> <a href="#design-aptkingston" title="Design">🎨</a></td>
<td align="center"><a href="https://budibase.com/"><img src="https://avatars3.githubusercontent.com/u/3524181?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Shanks</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=mjashanks" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/kevmodrome"><img src="https://avatars3.githubusercontent.com/u/534488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Åberg Kultalahti</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=kevmodrome" title="Tests">⚠️</a></td>
<td align="center"><a href="https://www.budibase.com/"><img src="https://avatars2.githubusercontent.com/u/49767913?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Joe</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=joebudi" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=joebudi" title="Code">💻</a> <a href="#content-joebudi" title="Content">🖋</a> <a href="#design-joebudi" title="Design">🎨</a></td>
<td align="center"><a href="https://github.com/Rory-Powell"><img src="https://avatars.githubusercontent.com/u/8755148?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rory Powell</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=Rory-Powell" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/PClmnt"><img src="https://avatars.githubusercontent.com/u/5665926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Peter Clement</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=PClmnt" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=PClmnt" title="Documentation">📖</a> <a href="https://github.com/Budibase/budibase/commits?author=PClmnt" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/Conor-Mack"><img src="https://avatars1.githubusercontent.com/u/36074859?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Conor_Mack</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=Conor-Mack" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=Conor-Mack" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/pngwn"><img src="https://avatars1.githubusercontent.com/u/12937446?v=4?s=100" width="100px;" alt=""/><br /><sub><b>pngwn</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Code">💻</a> <a href="https://github.com/Budibase/budibase/commits?author=pngwn" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/HugoLd"><img src="https://avatars0.githubusercontent.com/u/26521848?v=4?s=100" width="100px;" alt=""/><br /><sub><b>HugoLd</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=HugoLd" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/victoriasloan"><img src="https://avatars.githubusercontent.com/u/9913651?v=4?s=100" width="100px;" alt=""/><br /><sub><b>victoriasloan</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=victoriasloan" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/yashank09"><img src="https://avatars.githubusercontent.com/u/37672190?v=4?s=100" width="100px;" alt=""/><br /><sub><b>yashank09</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=yashank09" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/SOVLOOKUP"><img src="https://avatars.githubusercontent.com/u/53158137?v=4?s=100" width="100px;" alt=""/><br /><sub><b>SOVLOOKUP</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=SOVLOOKUP" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/seoulaja"><img src="https://avatars.githubusercontent.com/u/15101654?v=4?s=100" width="100px;" alt=""/><br /><sub><b>seoulaja</b></sub></a><br /><a href="#translation-seoulaja" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/mslourens"><img src="https://avatars.githubusercontent.com/u/1907152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maurits Lourens</b></sub></a><br /><a href="https://github.com/Budibase/budibase/commits?author=mslourens" title="Tests">⚠️</a> <a href="https://github.com/Budibase/budibase/commits?author=mslourens" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<a href="https://github.com/Budibase/budibase/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Budibase/budibase" />
</a>
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
Made with [contrib.rocks](https://contrib.rocks).

View File

@ -14,6 +14,9 @@ metadata:
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
{{- end }}
{{- if .Values.ingress.sslPolicy }}
alb.ingress.kubernetes.io/actions.ssl-policy: {{ .Values.ingress.sslPolicy }}
{{- end }}
{{- if .Values.ingress.securityGroups }}
alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }}
{{- end }}

View File

@ -1,17 +1,13 @@
## Dev Environment on Debian 11
### Install NVM & Node 14
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
Install NVM
```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
```
Install Node 14
```
nvm install 14
```
@ -21,16 +17,13 @@ nvm install 14
```
npm install -g yarn jest lerna
```
### Install Docker and Docker Compose
```
apt install docker.io
pip3 install docker-compose
```
### Clone the repo
```
git clone https://github.com/Budibase/budibase.git
```
@ -51,13 +44,10 @@ This setup process was tested on Debian 11 (bullseye) with version numbers show
cd budibase
yarn setup
```
The yarn setup command runs several build steps i.e.
```
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
```
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
The dev version will be available on port 10000 i.e.
@ -65,7 +55,6 @@ The dev version will be available on port 10000 i.e.
http://127.0.0.1:10000/builder/admin
### File descriptor issues with Vite and Chrome in Linux
If your dev environment stalls forever, with some network requests stuck in flight, it's likely that Chrome is trying to open more file descriptors than your system allows.
To fix this, apply the following tweaks.
@ -73,4 +62,4 @@ Debian based distros:
Add `* - nofile 65536` to `/etc/security/limits.conf`.
Arch:
Add `DefaultLimitNOFILE=65536` to `/etc/systemd/system.conf`.
Add `DefaultLimitNOFILE=65536` to `/etc/systemd/system.conf`.

View File

@ -4,14 +4,14 @@
Install instructions [here](https://brew.sh/)
| **NOTE**: If you are working on a M1 Apple Silicon which is running Z shell, you could need to add
`eval $(/opt/homebrew/bin/brew shellenv)` line to your `.zshrc`. This will make your zsh to find the apps you install
| **NOTE**: If you are working on a M1 Apple Silicon which is running Z shell, you could need to add
`eval $(/opt/homebrew/bin/brew shellenv)` line to your `.zshrc`. This will make your zsh to find the apps you install
through brew.
### Install Node
Budibase requires a recent version of node 14:
```
brew install node npm
node -v
@ -22,15 +22,12 @@ node -v
```
npm install -g yarn jest lerna
```
### Install Docker and Docker Compose
```
brew install docker docker-compose
```
### Clone the repo
```
git clone https://github.com/Budibase/budibase.git
```
@ -51,13 +48,10 @@ This setup process was tested on Mac OSX 12 (Monterey) with version numbers show
cd budibase
yarn setup
```
The yarn setup command runs several build steps i.e.
```
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
```
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
The dev version will be available on port 10000 i.e.
@ -67,6 +61,18 @@ http://127.0.0.1:10000/builder/admin
| **NOTE**: If you are working on a M1 Apple Silicon, you will need to uncomment `# platform: linux/amd64` line in
[hosting/docker-compose-dev.yaml](../hosting/docker-compose.dev.yaml)
### Troubleshooting
### Troubleshootings
#### Yarn setup errors
If there are errors with the `yarn setup` command, you can try installing nvm and node 14. This is the same as the instructions for Debian 11.
#### Node 14.20.1 not supported for arm64
If you are working with M1 or M2 Mac and trying the Node installation via `nvm`, probably you will find the error `curl: (22) The requested URL returned error: 404`.
Version `v14.20.1` is not supported for arm64; in order to use it, you can switch the CPU architecture for this by the following command:
```shell
arch -x86_64 zsh #Run this before nvm install
```

View File

@ -1,15 +1,13 @@
## Dev Environment on Windows 10/11 (WSL2)
### Install WSL with Ubuntu LTS
Enable WSL 2 on Windows 10/11 for docker support.
```
wsl --set-default-version 2
```
Install Ubuntu LTS.
```
wsl --install Ubuntu
```
@ -18,7 +16,6 @@ Or follow the instruction here:
https://learn.microsoft.com/en-us/windows/wsl/install
### Install Docker in windows
Download the installer from docker and install it.
Check this url for more detailed instructions:
@ -27,21 +24,18 @@ https://docs.docker.com/desktop/install/windows-install/
You should follow the next steps from within the Ubuntu terminal.
### Install NVM & Node 14
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
Install NVM
```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
```
Install Node 14
```
nvm install 14
```
### Install npm requirements
```
@ -49,7 +43,6 @@ npm install -g yarn jest lerna
```
### Clone the repo
```
git clone https://github.com/Budibase/budibase.git
```
@ -70,13 +63,10 @@ This setup process was tested on Windows 11 with version numbers show below. You
cd budibase
yarn setup
```
The yarn setup command runs several build steps i.e.
```
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
```
So this command will actually run the application in dev mode. It creates .env files under `./packages/server` and `./packages/worker` and runs docker containers for each service via docker-compose.
The dev version will be available on port 10000 i.e.
@ -84,9 +74,8 @@ The dev version will be available on port 10000 i.e.
http://127.0.0.1:10000/builder/admin
### Working with the code
Here are the instructions to work on the application from within Visual Studio Code (in Windows) through the WSL. All the commands and files are within the Ubuntu system and it should run as if you were working on a Linux machine.
https://code.visualstudio.com/docs/remote/wsl
Note you will be able to run the application from within the WSL terminal and you will be able to access the application from the a browser in Windows.
Note you will be able to run the application from within the WSL terminal and you will be able to access the application from the a browser in Windows.

View File

@ -1,22 +1,8 @@
{
"version": "2.5.6-alpha.6",
"version": "2.5.6-alpha.28",
"npmClient": "yarn",
"packages": [
"packages/backend-core",
"packages/bbui",
"packages/builder",
"packages/cli",
"packages/client",
"packages/frontend-core",
"packages/sdk",
"packages/server",
"packages/shared-core",
"packages/string-templates",
"packages/types",
"packages/worker",
"packages/pro/packages/pro"
],
"useWorkspaces": true,
"packages": ["packages/*"],
"command": {
"publish": {
"ignoreChanges": [

View File

@ -11,7 +11,7 @@
"eslint": "^7.28.0",
"eslint-plugin-cypress": "^2.11.3",
"eslint-plugin-svelte3": "^3.2.0",
"husky": "^8.0.3",
"husky": "^7.0.1",
"js-yaml": "^4.1.0",
"kill-port": "^1.6.1",
"lerna": "^6.6.1",
@ -24,16 +24,18 @@
"typescript": "4.7.3"
},
"scripts": {
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "./scripts/bootstrap.sh && lerna link && ./scripts/link-dependencies.sh",
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
"bootstrap": "lerna link && ./scripts/link-dependencies.sh",
"build": "lerna run --stream build",
"build:dev": "lerna run --stream prebuild && yarn nx run-many --target=build --output-style=dynamic --watch --preserveWatchOutput",
"build:dev": "lerna run --stream prebuild && tsc --build --watch --preserveWatchOutput",
"backend:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap",
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
"build:sdk": "lerna run --stream build:sdk",
"deps:circular": "madge packages/server/dist/index.js packages/worker/src/index.ts packages/backend-core/dist/src/index.js packages/cli/src/index.js --circular",
"release": "lerna publish ${RELEASE_VERSION_TYPE:-patch} --yes --force-publish && yarn release:pro",
"release:develop": "lerna publish prerelease --yes --force-publish --dist-tag develop --exact && yarn release:pro:develop",
"release:pro": "bash scripts/pro/release.sh",
"release:pro:develop": "bash scripts/pro/release.sh develop",
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
"nuke:packages": "yarn run restore",
@ -45,8 +47,9 @@
"dev": "yarn run kill-all && lerna link && lerna run --stream --parallel dev:builder --concurrency 1 --stream",
"dev:noserver": "yarn run kill-builder && lerna link && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
"dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server",
"dev:built": "cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built",
"test": "lerna run --stream test --stream",
"test:pro": "bash scripts/pro/test.sh",
"lint:eslint": "eslint packages && eslint qa-core",
"lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"",
"lint": "yarn run lint:eslint && yarn run lint:prettier",
@ -82,25 +85,12 @@
"mode:account": "yarn mode:cloud && yarn env:account:enable",
"security:audit": "node scripts/audit.js",
"postinstall": "husky install",
"dep:clean": "yarn clean -y && yarn bootstrap",
"submodules:load": "git submodule init && git submodule update && yarn && yarn bootstrap",
"submodules:unload": "git submodule deinit --all && yarn && yarn bootstrap"
"install:pro": "bash scripts/pro/install.sh",
"dep:clean": "yarn clean && yarn bootstrap"
},
"workspaces": {
"packages": [
"packages/backend-core",
"packages/bbui",
"packages/builder",
"packages/cli",
"packages/client",
"packages/frontend-core",
"packages/sdk",
"packages/server",
"packages/shared-core",
"packages/string-templates",
"packages/types",
"packages/worker",
"packages/pro/packages/pro"
"packages/*"
]
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "2.5.6-alpha.6",
"version": "2.5.6-alpha.28",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -15,6 +15,8 @@
"prebuild": "rimraf dist/",
"prepack": "cp package.json dist",
"build": "tsc -p tsconfig.build.json",
"build:pro": "../../scripts/pro/build.sh",
"postbuild": "yarn run build:pro",
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
"test": "bash scripts/test.sh",
"test:watch": "jest --watchAll"
@ -22,7 +24,7 @@
"dependencies": {
"@budibase/nano": "10.1.2",
"@budibase/pouchdb-replication-stream": "1.2.10",
"@budibase/types": "2.5.6-alpha.6",
"@budibase/types": "2.5.6-alpha.28",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-cloudfront-sign": "2.2.0",

View File

@ -14,6 +14,7 @@ export enum ViewName {
USER_BY_APP = "by_app",
USER_BY_EMAIL = "by_email2",
BY_API_KEY = "by_api_key",
/** @deprecated - could be deleted */
USER_BY_BUILDERS = "by_builders",
LINK = "by_link",
ROUTING = "screen_routes",

View File

@ -115,10 +115,10 @@ export async function doInContext(appId: string, task: any): Promise<any> {
)
}
export async function doInTenant(
export async function doInTenant<T>(
tenantId: string | null,
task: any
): Promise<any> {
task: () => T
): Promise<T> {
// make sure default always selected in single tenancy
if (!env.MULTI_TENANCY) {
tenantId = tenantId || DEFAULT_TENANT_ID

View File

@ -243,7 +243,7 @@ export class QueryBuilder<T> {
}
// Escape characters
if (!this.#noEscaping && escape && originalType === "string") {
value = `${value}`.replace(/[ #+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
value = `${value}`.replace(/[ \/#+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
}
// Wrap in quotes
@ -320,6 +320,18 @@ export class QueryBuilder<T> {
return `${key}:(${statement})`
}
const fuzzy = (key: string, value: any) => {
if (!value) {
return null
}
value = builder.preprocess(value, {
escape: true,
lowercase: true,
type: "fuzzy",
})
return `${key}:/.*${value}.*/`
}
const notContains = (key: string, value: any) => {
const allPrefix = allOr ? "*:* AND " : ""
const mode = allOr ? "AND" : undefined
@ -408,17 +420,7 @@ export class QueryBuilder<T> {
})
}
if (this.#query.fuzzy) {
build(this.#query.fuzzy, (key: string, value: any) => {
if (!value) {
return null
}
value = builder.preprocess(value, {
escape: true,
lowercase: true,
type: "fuzzy",
})
return `${key}:${value}~`
})
build(this.#query.fuzzy, fuzzy)
}
if (this.#query.equal) {
build(this.#query.equal, equal)

View File

@ -7,7 +7,7 @@ import {
} from "../constants"
import { getGlobalDB } from "../context"
import { doWithDB } from "./"
import { Database, DatabaseQueryOpts } from "@budibase/types"
import { AllDocsResponse, Database, DatabaseQueryOpts } from "@budibase/types"
import env from "../environment"
const DESIGN_DB = "_design/database"
@ -119,6 +119,34 @@ export interface QueryViewOptions {
arrayResponse?: boolean
}
export async function queryViewRaw<T>(
viewName: ViewName,
params: DatabaseQueryOpts,
db: Database,
createFunc: any,
opts?: QueryViewOptions
): Promise<AllDocsResponse<T>> {
try {
const response = await db.query<T>(`database/${viewName}`, params)
// await to catch error
return response
} catch (err: any) {
const pouchNotFound = err && err.name === "not_found"
const couchNotFound = err && err.status === 404
if (pouchNotFound || couchNotFound) {
await removeDeprecated(db, viewName)
await createFunc()
return queryViewRaw(viewName, params, db, createFunc, opts)
} else if (err.status === 409) {
// can happen when multiple queries occur at once, view couldn't be created
// other design docs being updated, re-run
return queryViewRaw(viewName, params, db, createFunc, opts)
} else {
throw err
}
}
}
export const queryView = async <T>(
viewName: ViewName,
params: DatabaseQueryOpts,
@ -126,34 +154,18 @@ export const queryView = async <T>(
createFunc: any,
opts?: QueryViewOptions
): Promise<T[] | T | undefined> => {
try {
let response = await db.query<T>(`database/${viewName}`, params)
const rows = response.rows
const docs = rows.map((row: any) =>
params.include_docs ? row.doc : row.value
)
const response = await queryViewRaw<T>(viewName, params, db, createFunc, opts)
const rows = response.rows
const docs = rows.map((row: any) =>
params.include_docs ? row.doc : row.value
)
// if arrayResponse has been requested, always return array regardless of length
if (opts?.arrayResponse) {
return docs as T[]
} else {
// return the single document if there is only one
return docs.length <= 1 ? (docs[0] as T) : (docs as T[])
}
} catch (err: any) {
const pouchNotFound = err && err.name === "not_found"
const couchNotFound = err && err.status === 404
if (pouchNotFound || couchNotFound) {
await removeDeprecated(db, viewName)
await createFunc()
return queryView(viewName, params, db, createFunc, opts)
} else if (err.status === 409) {
// can happen when multiple queries occur at once, view couldn't be created
// other design docs being updated, re-run
return queryView(viewName, params, db, createFunc, opts)
} else {
throw err
}
// if arrayResponse has been requested, always return array regardless of length
if (opts?.arrayResponse) {
return docs as T[]
} else {
// return the single document if there is only one
return docs.length <= 1 ? (docs[0] as T) : (docs as T[])
}
}
@ -208,18 +220,19 @@ export const queryPlatformView = async <T>(
})
}
const CreateFuncByName: any = {
[ViewName.USER_BY_EMAIL]: createNewUserEmailView,
[ViewName.BY_API_KEY]: createApiKeyView,
[ViewName.USER_BY_BUILDERS]: createUserBuildersView,
[ViewName.USER_BY_APP]: createUserAppView,
}
export const queryGlobalView = async <T>(
viewName: ViewName,
params: DatabaseQueryOpts,
db?: Database,
opts?: QueryViewOptions
): Promise<T[] | T | undefined> => {
const CreateFuncByName: any = {
[ViewName.USER_BY_EMAIL]: createNewUserEmailView,
[ViewName.BY_API_KEY]: createApiKeyView,
[ViewName.USER_BY_BUILDERS]: createUserBuildersView,
[ViewName.USER_BY_APP]: createUserAppView,
}
// can pass DB in if working with something specific
if (!db) {
db = getGlobalDB()
@ -227,3 +240,13 @@ export const queryGlobalView = async <T>(
const createFn = CreateFuncByName[viewName]
return queryView(viewName, params, db!, createFn, opts)
}
export async function queryGlobalViewRaw<T>(
viewName: ViewName,
params: DatabaseQueryOpts,
opts?: QueryViewOptions
) {
const db = getGlobalDB()
const createFn = CreateFuncByName[viewName]
return queryViewRaw<T>(viewName, params, db, createFn, opts)
}

View File

@ -1,3 +1,5 @@
import { existsSync, readFileSync } from "fs"
function isTest() {
return isCypress() || isJest()
}
@ -45,6 +47,35 @@ function httpLogging() {
return process.env.HTTP_LOGGING
}
function findVersion() {
function findFileInAncestors(
fileName: string,
currentDir: string
): string | null {
const filePath = `${currentDir}/${fileName}`
if (existsSync(filePath)) {
return filePath
}
const parentDir = `${currentDir}/..`
if (parentDir === currentDir) {
// reached root directory
return null
}
return findFileInAncestors(fileName, parentDir)
}
try {
const packageJsonFile = findFileInAncestors("package.json", process.cwd())
const content = readFileSync(packageJsonFile!, "utf-8")
const version = JSON.parse(content).version
return version
} catch {
throw new Error("Cannot find a valid version in its package.json")
}
}
const environment = {
isTest,
isJest,
@ -122,6 +153,7 @@ const environment = {
ENABLE_SSO_MAINTENANCE_MODE: selfHosted
? process.env.ENABLE_SSO_MAINTENANCE_MODE
: false,
VERSION: findVersion(),
_set(key: any, value: any) {
process.env[key] = value
// @ts-ignore

View File

@ -23,8 +23,6 @@ import * as installation from "../installation"
import * as configs from "../configs"
import { withCache, TTL, CacheKey } from "../cache/generic"
const pkg = require("../../package.json")
/**
* An identity can be:
* - account user (Self host)
@ -102,7 +100,7 @@ const identifyInstallationGroup = async (
const id = installId
const type = IdentityType.INSTALLATION
const hosting = getHostingFromEnv()
const version = pkg.version
const version = env.VERSION
const environment = getDeploymentEnvironment()
const group: InstallationGroup = {
@ -306,4 +304,5 @@ export default {
identify,
identifyGroup,
getInstallationId,
getUniqueTenantId,
}

View File

@ -4,7 +4,6 @@ import { EventProcessor } from "../types"
import env from "../../../environment"
import * as context from "../../../context"
import * as rateLimiting from "./rateLimiting"
const pkg = require("../../../../package.json")
const EXCLUDED_EVENTS: Event[] = [
Event.USER_UPDATED,
@ -49,7 +48,7 @@ export default class PosthogProcessor implements EventProcessor {
properties = this.clearPIIProperties(properties)
properties.version = pkg.version
properties.version = env.VERSION
properties.service = env.SERVICE
properties.environment = identity.environment
properties.hosting = identity.hosting

View File

@ -6,8 +6,7 @@ import { Installation, IdentityType, Database } from "@budibase/types"
import * as context from "./context"
import semver from "semver"
import { bustCache, withCache, TTL, CacheKey } from "./cache/generic"
const pkg = require("../package.json")
import environment from "./environment"
export const getInstall = async (): Promise<Installation> => {
return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, {
@ -18,7 +17,7 @@ async function createInstallDoc(platformDb: Database) {
const install: Installation = {
_id: StaticDatabases.PLATFORM_INFO.docs.install,
installId: newid(),
version: pkg.version,
version: environment.VERSION,
}
try {
const resp = await platformDb.put(install)
@ -33,7 +32,7 @@ async function createInstallDoc(platformDb: Database) {
}
}
const getInstallFromDB = async (): Promise<Installation> => {
export const getInstallFromDB = async (): Promise<Installation> => {
return doWithDB(
StaticDatabases.PLATFORM_INFO.name,
async (platformDb: any) => {
@ -80,7 +79,7 @@ export const checkInstallVersion = async (): Promise<void> => {
const install = await getInstall()
const currentVersion = install.version
const newVersion = pkg.version
const newVersion = environment.VERSION
if (currentVersion !== newVersion) {
const isUpgrade = semver.gt(newVersion, currentVersion)

View File

@ -44,7 +44,7 @@ async function checkApiKey(apiKey: string, populateUser?: Function) {
// check both the primary and the fallback internal api keys
// this allows for rotation
if (isValidInternalAPIKey(apiKey)) {
return { valid: true }
return { valid: true, user: undefined }
}
const decrypted = decrypt(apiKey)
const tenantId = decrypted.split(SEPARATOR)[0]

View File

@ -1,10 +1,16 @@
import Redlock, { Options } from "redlock"
import Redlock from "redlock"
import { getLockClient } from "./init"
import { LockOptions, LockType } from "@budibase/types"
import * as context from "../context"
import env from "../environment"
const getClient = async (type: LockType): Promise<Redlock> => {
const getClient = async (
type: LockType,
opts?: Redlock.Options
): Promise<Redlock> => {
if (type === LockType.CUSTOM) {
return newRedlock(opts)
}
if (env.isTest() && type !== LockType.TRY_ONCE) {
return newRedlock(OPTIONS.TEST)
}
@ -56,7 +62,7 @@ const OPTIONS = {
},
}
const newRedlock = async (opts: Options = {}) => {
const newRedlock = async (opts: Redlock.Options = {}) => {
let options = { ...OPTIONS.DEFAULT, ...opts }
const redisWrapper = await getLockClient()
const client = redisWrapper.getClient()

View File

@ -1,15 +1,16 @@
import {
ViewName,
getUsersByAppParams,
getProdAppID,
generateAppUserID,
queryGlobalView,
UNICODE_MAX,
DocumentType,
SEPARATOR,
directCouchFind,
DocumentType,
generateAppUserID,
getGlobalUserParams,
getProdAppID,
getUsersByAppParams,
pagination,
queryGlobalView,
queryGlobalViewRaw,
SEPARATOR,
UNICODE_MAX,
ViewName,
} from "./db"
import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types"
import { getGlobalDB } from "./context"
@ -239,3 +240,11 @@ export const paginatedUsers = async ({
getKey,
})
}
export async function getUserCount() {
const response = await queryGlobalViewRaw(ViewName.USER_BY_EMAIL, {
limit: 0, // to be as fast as possible - we just want the total rows count
include_docs: false,
})
return response.total_rows
}

View File

@ -46,8 +46,9 @@ export async function resolveAppUrl(ctx: Ctx) {
}
// search prod apps for a url that matches
const apps: App[] = await context.doInTenant(tenantId, () =>
getAllApps({ dev: false })
const apps: App[] = await context.doInTenant(
tenantId,
() => getAllApps({ dev: false }) as Promise<App[]>
)
const app = apps.filter(
a => a.url && a.url.toLowerCase() === possibleAppUrl
@ -221,27 +222,6 @@ export function isClient(ctx: Ctx) {
return ctx.headers[Header.TYPE] === "client"
}
async function getBuilders() {
const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, {
include_docs: false,
})
if (!builders) {
return []
}
if (Array.isArray(builders)) {
return builders
} else {
return [builders]
}
}
export async function getBuildersCount() {
const builders = await getBuilders()
return builders.length
}
export function timeout(timeMs: number) {
return new Promise(resolve => setTimeout(resolve, timeMs))
}

View File

@ -2,5 +2,5 @@ export * as mocks from "./mocks"
export * as structures from "./structures"
export { generator } from "./structures"
export * as testContainerUtils from "./testContainerUtils"
export * as utils from "./utils"
export * from "./jestUtils"

View File

@ -1,3 +1,5 @@
import * as events from "../../../../src/events"
beforeAll(async () => {
const processors = await import("../../../../src/events/processors")
const events = await import("../../../../src/events")
@ -120,4 +122,13 @@ beforeAll(async () => {
jest.spyOn(events.plugin, "init")
jest.spyOn(events.plugin, "imported")
jest.spyOn(events.plugin, "deleted")
jest.spyOn(events.license, "tierChanged")
jest.spyOn(events.license, "planChanged")
jest.spyOn(events.license, "activated")
jest.spyOn(events.license, "checkoutOpened")
jest.spyOn(events.license, "checkoutSuccess")
jest.spyOn(events.license, "portalOpened")
jest.spyOn(events.license, "paymentFailed")
jest.spyOn(events.license, "paymentRecovered")
})

View File

@ -0,0 +1,20 @@
import Chance from "chance"
export default class CustomChance extends Chance {
arrayOf<T>(
generateFn: () => T,
opts: { min?: number; max?: number } = {}
): T[] {
const itemCount = this.integer({
min: opts.min != null ? opts.min : 1,
max: opts.max != null ? opts.max : 50,
})
const items = []
for (let i = 0; i < itemCount; i++) {
items.push(generateFn())
}
return items
}
}

View File

@ -1,4 +1,4 @@
import { generator, uuid } from "."
import { generator, uuid, quotas } from "."
import { generateGlobalUserID } from "../../../../src/docIds"
import {
Account,
@ -28,6 +28,7 @@ export const account = (): Account => {
name: generator.name(),
size: "10+",
profession: "Software Engineer",
quotaUsage: quotas.usage(),
}
}

View File

@ -1,2 +1,2 @@
import Chance from "chance"
import Chance from "./Chance"
export const generator = new Chance()

View File

@ -11,3 +11,4 @@ export * as users from "./users"
export * as userGroups from "./userGroups"
export { generator } from "./generator"
export * as scim from "./scim"
export * as quotas from "./quotas"

View File

@ -1,18 +1,132 @@
import { AccountPlan, License, PlanType, Quotas } from "@budibase/types"
import {
Billing,
Customer,
Feature,
License,
PlanModel,
PlanType,
PriceDuration,
PurchasedPlan,
Quotas,
Subscription,
} from "@budibase/types"
const newPlan = (type: PlanType = PlanType.FREE): AccountPlan => {
export const plan = (type: PlanType = PlanType.FREE): PurchasedPlan => {
return {
type,
usesInvoicing: false,
minUsers: 1,
model: PlanModel.PER_USER,
}
}
export const newLicense = (opts: {
quotas: Quotas
planType?: PlanType
}): License => {
export function quotas(): Quotas {
return {
features: [],
quotas: opts.quotas,
plan: newPlan(opts.planType),
usage: {
monthly: {
queries: {
name: "Queries",
value: 1,
triggers: [],
},
automations: {
name: "Queries",
value: 1,
triggers: [],
},
dayPasses: {
name: "Queries",
value: 1,
triggers: [],
},
},
static: {
rows: {
name: "Rows",
value: 1,
triggers: [],
},
apps: {
name: "Apps",
value: 1,
triggers: [],
},
users: {
name: "Users",
value: 1,
triggers: [],
},
userGroups: {
name: "User Groups",
value: 1,
triggers: [],
},
plugins: {
name: "Plugins",
value: 1,
triggers: [],
},
},
},
constant: {
automationLogRetentionDays: {
name: "Automation Logs",
value: 1,
triggers: [],
},
appBackupRetentionDays: {
name: "Backups",
value: 1,
triggers: [],
},
},
}
}
export function billing(
opts: { customer?: Customer; subscription?: Subscription } = {}
): Billing {
return {
customer: opts.customer || customer(),
subscription: opts.subscription || subscription(),
}
}
export function customer(): Customer {
return {
balance: 0,
currency: "usd",
}
}
export function subscription(): Subscription {
return {
amount: 10000,
cancelAt: undefined,
currency: "usd",
currentPeriodEnd: 0,
currentPeriodStart: 0,
downgradeAt: 0,
duration: PriceDuration.MONTHLY,
pastDueAt: undefined,
quantity: 0,
status: "active",
}
}
export const license = (
opts: {
quotas?: Quotas
plan?: PurchasedPlan
planType?: PlanType
features?: Feature[]
billing?: Billing
} = {}
): License => {
return {
features: opts.features || [],
quotas: opts.quotas || quotas(),
plan: opts.plan || plan(opts.planType),
billing: opts.billing || billing(),
}
}

View File

@ -0,0 +1,67 @@
import { MonthlyQuotaName, QuotaUsage } from "@budibase/types"
export const usage = (): QuotaUsage => {
return {
_id: "usage_quota",
quotaReset: new Date().toISOString(),
apps: {
app_1: {
// @ts-ignore - the apps definition doesn't match up to actual usage
usageQuota: {
rows: 0,
},
},
},
monthly: {
"01-2023": {
automations: 0,
dayPasses: 0,
queries: 0,
triggers: {},
breakdown: {
rowQueries: {
parent: MonthlyQuotaName.QUERIES,
values: {
row_1: 0,
row_2: 0,
},
},
datasourceQueries: {
parent: MonthlyQuotaName.QUERIES,
values: {
ds_1: 0,
ds_2: 0,
},
},
automations: {
parent: MonthlyQuotaName.AUTOMATIONS,
values: {
auto_1: 0,
auto_2: 0,
},
},
},
},
"02-2023": {
automations: 0,
dayPasses: 0,
queries: 0,
triggers: {},
},
current: {
automations: 0,
dayPasses: 0,
queries: 0,
triggers: {},
},
},
usageQuota: {
apps: 0,
plugins: 0,
users: 0,
userGroups: 0,
rows: 0,
triggers: {},
},
}
}

View File

@ -0,0 +1 @@
export * as time from "./time"

View File

@ -0,0 +1,3 @@
export function addDaysToDate(date: Date, days: number) {
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000)
}

View File

@ -10,15 +10,11 @@
"incremental": true,
"sourceMap": true,
"declaration": true,
"types": [ "node", "jest" ],
"types": ["node", "jest"],
"outDir": "dist",
"skipLibCheck": true
},
"include": [
"**/*.js",
"**/*.ts",
"package.json"
],
"include": ["**/*.js", "**/*.ts"],
"exclude": [
"node_modules",
"dist",
@ -26,4 +22,4 @@
"**/*.spec.js",
"__mocks__"
]
}
}

View File

@ -7,6 +7,11 @@
"@budibase/types": ["../types/src"]
}
},
"exclude": ["node_modules", "dist"]
}
"references": [
{ "path": "../types" }
],
"exclude": [
"node_modules",
"dist",
]
}

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "2.5.6-alpha.6",
"version": "2.5.6-alpha.28",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@ -38,8 +38,8 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "1.2.1",
"@budibase/shared-core": "2.5.6-alpha.6",
"@budibase/string-templates": "2.5.6-alpha.6",
"@budibase/shared-core": "2.5.6-alpha.28",
"@budibase/string-templates": "2.5.6-alpha.28",
"@spectrum-css/accordion": "3.0.24",
"@spectrum-css/actionbutton": "1.0.1",
"@spectrum-css/actiongroup": "1.0.1",
@ -84,7 +84,7 @@
"@spectrum-css/vars": "3.0.1",
"dayjs": "^1.10.4",
"easymde": "^2.16.1",
"svelte-flatpickr": "^3.2.3",
"svelte-flatpickr": "^3.3.2",
"svelte-portal": "^1.0.0"
},
"resolutions": {

View File

@ -71,7 +71,7 @@
{/if}
{#if icon}
<svg
class="spectrum-Icon spectrum-Icon--size{size}"
class="spectrum-Icon spectrum-Icon--sizeS"
focusable="false"
aria-hidden="true"
aria-label={icon}

View File

@ -6,6 +6,9 @@ let clickHandlers = []
*/
const handleClick = event => {
// Ignore click if this is an ignored class
if (event.target.closest('[data-ignore-click-outside="true"]')) {
return
}
for (let className of ignoredClasses) {
if (event.target.closest(className)) {
return
@ -29,6 +32,7 @@ const handleClick = event => {
})
}
document.documentElement.addEventListener("click", handleClick, true)
document.documentElement.addEventListener("contextmenu", handleClick, true)
/**
* Adds or updates a click handler

View File

@ -138,7 +138,7 @@
}
</script>
<div class="container">
<div class="container" class:compact>
{#if selectedImage}
{#if gallery}
<div class="gallery">
@ -355,6 +355,9 @@
input[type="file"] {
display: none;
}
.compact .spectrum-Dropzone {
padding: 6px 0 !important;
}
.gallery {
display: flex;
@ -379,6 +382,17 @@
object-fit: contain;
margin: 20px 30px;
}
.compact .placeholder,
.compact img {
margin: 10px 16px;
}
.compact img {
height: 90px;
}
.compact .gallery {
padding: 6px 10px;
margin-bottom: 8px;
}
.title {
display: flex;
flex-direction: row;
@ -447,6 +461,13 @@
.disabled .spectrum-Heading--sizeL {
color: var(--spectrum-alias-text-color-disabled);
}
.compact .spectrum-Dropzone {
padding-top: 8px;
padding-bottom: 8px;
}
.compact .spectrum-IllustratedMessage-description {
margin: 0;
}
.tags {
margin-top: 20px;

View File

@ -20,12 +20,13 @@
const dispatch = createEventDispatcher()
$: selectedLookupMap = getSelectedLookupMap(value)
$: arrayValue = Array.isArray(value) ? value : [value].filter(x => !!x)
$: selectedLookupMap = getSelectedLookupMap(arrayValue)
$: optionLookupMap = getOptionLookupMap(options)
$: fieldText = getFieldText(value, optionLookupMap, placeholder)
$: fieldText = getFieldText(arrayValue, optionLookupMap, placeholder)
$: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true
$: toggleOption = makeToggleOption(selectedLookupMap, value)
$: toggleOption = makeToggleOption(selectedLookupMap, arrayValue)
const getFieldText = (value, map, placeholder) => {
if (Array.isArray(value) && value.length > 0) {
@ -84,7 +85,7 @@
{readonly}
{fieldText}
{options}
isPlaceholder={!value?.length}
isPlaceholder={!arrayValue.length}
{autocomplete}
bind:fetchTerm
{useFetch}

View File

@ -16,6 +16,7 @@
export let gallery = true
export let fileTags = []
export let maximum = undefined
export let compact = false
const dispatch = createEventDispatcher()
const onChange = e => {
@ -37,6 +38,7 @@
{gallery}
{fileTags}
{maximum}
{compact}
on:change={onChange}
/>
</Field>

View File

@ -7,7 +7,7 @@
export let message = ""
export let onConfirm = undefined
export let buttonText = ""
export let cta = false
$: icon = selectIcon(type)
// if newlines used, convert them to different elements
$: split = message.split("\n")
@ -41,7 +41,9 @@
{/each}
{#if onConfirm}
<div class="spectrum-InLineAlert-footer button">
<Button secondary on:click={onConfirm}>{buttonText || "OK"}</Button>
<Button {cta} secondary={cta ? false : true} on:click={onConfirm}
>{buttonText || "OK"}</Button
>
</div>
{/if}
</div>
@ -57,7 +59,6 @@
--spectrum-semantic-negative-icon-color: #e34850;
min-width: 100px;
margin: 0;
border-color: var(--spectrum-global-color-gray-400);
border-width: 1px;
}
</style>

View File

@ -4,6 +4,7 @@
export let wide = false
export let narrow = false
export let narrower = false
export let noPadding = false
let sidePanelVisble = false
@ -16,7 +17,7 @@
<div class="page">
<div class="main">
<div class="content" class:wide class:noPadding class:narrow>
<div class="content" class:wide class:noPadding class:narrow class:narrower>
<slot />
<div class="fix-scroll-padding" />
</div>
@ -70,6 +71,9 @@
.content.narrow {
max-width: 840px;
}
.content.narrower {
max-width: 700px;
}
#side-panel {
position: absolute;
right: 0;

View File

@ -20,6 +20,7 @@
export let dismissible = true
export let offset = 5
export let customHeight
export let animate = true
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
@ -78,7 +79,7 @@
class="spectrum-Popover is-open"
role="presentation"
style="height: {customHeight}"
transition:fly|local={{ y: -20, duration: 200 }}
transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }}
>
<slot />
</div>

View File

@ -3,6 +3,7 @@ import { writable } from "svelte/store"
export const BANNER_TYPES = {
INFO: "info",
NEGATIVE: "negative",
WARNING: "warning",
}
export function createBannerStore() {
@ -38,7 +39,8 @@ export function createBannerStore() {
const queue = async entries => {
const priority = {
[BANNER_TYPES.NEGATIVE]: 0,
[BANNER_TYPES.INFO]: 1,
[BANNER_TYPES.WARNING]: 1,
[BANNER_TYPES.INFO]: 2,
}
banner.update(store => {
const sorted = [...store.messages, ...entries].sort((a, b) => {

View File

@ -5,8 +5,9 @@
const displayLimit = 5
$: badges = Array.isArray(value) ? value.slice(0, displayLimit) : []
$: leftover = (value?.length ?? 0) - badges.length
$: arrayValue = Array.isArray(value) ? value : [value].filter(x => !!x)
$: badges = arrayValue.slice(0, displayLimit)
$: leftover = arrayValue.length - badges.length
</script>
{#each badges as badge}

View File

@ -143,7 +143,7 @@
}
fields?.forEach(field => {
const fieldSchema = schema[field]
if (fieldSchema.width) {
if (fieldSchema.width && typeof fieldSchema.width === "string") {
style += ` ${fieldSchema.width}`
} else {
style += " minmax(auto, 1fr)"

View File

@ -3,9 +3,13 @@
export let size = "M"
export let serif = false
export let weight = 600
</script>
<p
style={`
${weight ? `font-weight:${weight};` : ""}
`}
class="spectrum-Detail spectrum-Detail--size{size}"
class:spectrum-Detail--serif={serif}
>
@ -13,7 +17,4 @@
</p>
<style>
p {
font-weight: 600;
}
</style>

View File

@ -97,4 +97,22 @@
a {
text-decoration: none;
}
}
/* Custom theme additions */
.spectrum--darkest {
--drop-shadow: rgba(0, 0, 0, 0.6);
--spectrum-global-color-blue-100: rgb(28, 33, 43);
}
.spectrum--dark {
--drop-shadow: rgba(0, 0, 0, 0.3);
--spectrum-global-color-blue-100: rgb(42, 47, 57);
}
.spectrum--light {
--drop-shadow: rgba(0, 0, 0, 0.075);
--spectrum-global-color-blue-100: rgb(240, 245, 255);
}
.spectrum--lightest {
--drop-shadow: rgba(0, 0, 0, 0.05);
--spectrum-global-color-blue-100: rgb(240, 244, 255);
}

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/builder",
"version": "2.5.6-alpha.6",
"version": "2.5.6-alpha.28",
"license": "GPL-3.0",
"private": true,
"scripts": {
@ -58,11 +58,11 @@
}
},
"dependencies": {
"@budibase/bbui": "2.5.6-alpha.6",
"@budibase/client": "2.5.6-alpha.6",
"@budibase/frontend-core": "2.5.6-alpha.6",
"@budibase/shared-core": "2.5.6-alpha.6",
"@budibase/string-templates": "2.5.6-alpha.6",
"@budibase/bbui": "2.5.6-alpha.28",
"@budibase/client": "2.5.6-alpha.28",
"@budibase/frontend-core": "2.5.6-alpha.28",
"@budibase/shared-core": "2.5.6-alpha.28",
"@budibase/string-templates": "2.5.6-alpha.28",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",

View File

@ -10,7 +10,10 @@ import { auth } from "./stores/portal"
export const API = createAPIClient({
attachHeaders: headers => {
// Attach app ID header from store
headers["x-budibase-app-id"] = get(store).appId
let appId = get(store).appId
if (appId) {
headers["x-budibase-app-id"] = appId
}
// Add csrf token if authenticated
const user = get(auth).user

View File

@ -67,6 +67,15 @@
newInputData = cloneDeep(blockInputs)
}
inputData = newInputData
setDefaultEnumValues()
}
const setDefaultEnumValues = () => {
for (const [key, value] of schemaProperties) {
if (value.type === "string" && value.enum && inputData[key] == null) {
inputData[key] = value.enum[0]
}
}
}
const onChange = Utils.sequential(async (e, key) => {
@ -243,6 +252,7 @@
<Select
on:change={e => onChange(e, key)}
value={inputData[key]}
placeholder={false}
options={value.enum}
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
/>

View File

@ -1,286 +1,75 @@
<script>
import { fade } from "svelte/transition"
import { tables } from "stores/backend"
import CreateRowButton from "./buttons/CreateRowButton.svelte"
import CreateColumnButton from "./buttons/CreateColumnButton.svelte"
import CreateViewButton from "./buttons/CreateViewButton.svelte"
import ExistingRelationshipButton from "./buttons/ExistingRelationshipButton.svelte"
import ExportButton from "./buttons/ExportButton.svelte"
import ImportButton from "./buttons/ImportButton.svelte"
import EditRolesButton from "./buttons/EditRolesButton.svelte"
import ManageAccessButton from "./buttons/ManageAccessButton.svelte"
import HideAutocolumnButton from "./buttons/HideAutocolumnButton.svelte"
import TableFilterButton from "./buttons/TableFilterButton.svelte"
import Table from "./Table.svelte"
import { TableNames } from "constants"
import CreateEditRow from "./modals/CreateEditRow.svelte"
import {
Pagination,
Heading,
Body,
Layout,
notifications,
} from "@budibase/bbui"
import { fetchData } from "@budibase/frontend-core"
import { Grid } from "@budibase/frontend-core"
import { API } from "api"
import GridAddColumnModal from "components/backend/DataTable/modals/grid/GridCreateColumnModal.svelte"
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
import GridEditUserModal from "components/backend/DataTable/modals/grid/GridEditUserModal.svelte"
import GridCreateViewButton from "components/backend/DataTable/buttons/grid/GridCreateViewButton.svelte"
import GridImportButton from "components/backend/DataTable/buttons/grid/GridImportButton.svelte"
import GridExportButton from "components/backend/DataTable/buttons/grid/GridExportButton.svelte"
import GridFilterButton from "components/backend/DataTable/buttons/grid/GridFilterButton.svelte"
import GridManageAccessButton from "components/backend/DataTable/buttons/grid/GridManageAccessButton.svelte"
import GridRelationshipButton from "components/backend/DataTable/buttons/grid/GridRelationshipButton.svelte"
import GridEditColumnModal from "components/backend/DataTable/modals/grid/GridEditColumnModal.svelte"
let hideAutocolumns = true
let filters
const userSchemaOverrides = {
firstName: { name: "First name", disabled: true },
lastName: { name: "Last name", disabled: true },
email: { name: "Email", disabled: true },
roleId: { name: "Role", disabled: true },
status: { name: "Status", disabled: true },
}
$: isUsersTable = $tables.selected?._id === TableNames.USERS
$: type = $tables.selected?.type
$: isInternal = type !== "external"
$: schema = $tables.selected?.schema
$: enrichedSchema = enrichSchema($tables.selected?.schema)
$: id = $tables.selected?._id
$: fetch = createFetch(id)
$: hasCols = checkHasCols(schema)
$: hasRows = !!$fetch.rows?.length
$: showError($fetch.error)
$: id, (filters = null)
let appliedFilter
let rawFilter
let appliedSort
let selectedRows = []
$: enrichedSchema,
() => {
appliedFilter = null
rawFilter = null
appliedSort = null
selectedRows = []
}
$: if (Number.isInteger($fetch.pageNumber)) {
selectedRows = []
}
const showError = error => {
if (error) {
notifications.error(error?.message || "Unable to fetch data.")
}
}
const enrichSchema = schema => {
let tempSchema = { ...schema }
tempSchema._id = {
type: "internal",
editable: false,
displayName: "ID",
autocolumn: true,
}
if (isInternal) {
tempSchema._rev = {
type: "internal",
editable: false,
displayName: "Revision",
autocolumn: true,
}
}
return tempSchema
}
const checkHasCols = schema => {
if (!schema || Object.keys(schema).length === 0) {
return false
}
let fields = Object.values(schema)
for (let field of fields) {
if (!field.autocolumn) {
return true
}
}
return false
}
// Fetches new data whenever the table changes
const createFetch = tableId => {
return fetchData({
API,
datasource: {
tableId,
type: "table",
},
options: {
schema,
limit: 10,
paginate: true,
},
})
}
// Fetch data whenever sorting option changes
const onSort = async e => {
const sort = {
sortColumn: e.detail.column,
sortOrder: e.detail.order,
}
await fetch.update(sort)
appliedSort = { ...sort }
appliedSort.sortOrder = appliedSort.sortOrder.toLowerCase()
selectedRows = []
}
// Fetch data whenever filters change
const onFilter = e => {
filters = e.detail
fetch.update({
filter: filters,
})
appliedFilter = e.detail
}
// Fetch data whenever schema changes
const onUpdateColumns = () => {
selectedRows = []
fetch.refresh()
tables.fetchTable(id)
}
// Fetch data whenever rows are modified. Unfortunately we have to lose
// our pagination place, as our bookmarks will have shifted.
const onUpdateRows = () => {
selectedRows = []
fetch.refresh()
}
// When importing new rows it is better to reinitialise request/paging data.
// Not doing so causes inconsistency in paging behaviour and content.
const onImportData = () => {
fetch.getInitialData()
}
$: isUsersTable = id === TableNames.USERS
$: isInternal = $tables.selected?.type !== "external"
</script>
<div>
<Table
title={$tables.selected?.name}
schema={enrichedSchema}
{type}
<div class="wrapper">
<Grid
{API}
tableId={id}
data={$fetch.rows}
bind:hideAutocolumns
loading={!$fetch.loaded}
on:sort={onSort}
allowEditing
disableSorting
on:updatecolumns={onUpdateColumns}
on:updaterows={onUpdateRows}
on:selectionUpdated={e => {
selectedRows = e.detail
}}
customPlaceholder
allowAddRows={!isUsersTable}
allowDeleteRows={!isUsersTable}
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
on:updatetable={e => tables.updateTable(e.detail)}
>
<div class="buttons">
<div class="left-buttons">
<CreateColumnButton
highlighted={$fetch.loaded && (!hasCols || !hasRows)}
on:updatecolumns={onUpdateColumns}
/>
{#if !isUsersTable}
<CreateRowButton
on:updaterows={onUpdateRows}
title={"Create row"}
modalContentComponent={CreateEditRow}
disabled={!hasCols}
highlighted={$fetch.loaded && hasCols && !hasRows}
/>
{/if}
{#if isInternal}
<CreateViewButton disabled={!hasCols || !hasRows} />
{/if}
</div>
<div class="right-buttons">
<ManageAccessButton resourceId={$tables.selected?._id} />
{#if isUsersTable}
<EditRolesButton />
{/if}
{#if !isInternal}
<ExistingRelationshipButton
table={$tables.selected}
on:updatecolumns={onUpdateColumns}
/>
{/if}
<HideAutocolumnButton bind:hideAutocolumns />
<ImportButton
disabled={$tables.selected?._id === "ta_users"}
tableId={$tables.selected?._id}
on:importrows={onImportData}
/>
<ExportButton
disabled={!hasRows || !hasCols}
view={$tables.selected?._id}
filters={appliedFilter}
sorting={appliedSort}
{selectedRows}
/>
{#key id}
<TableFilterButton
{schema}
{filters}
on:change={onFilter}
disabled={!hasCols}
tableId={id}
/>
{/key}
</div>
</div>
<div slot="placeholder">
<Layout gap="S">
{#if !hasCols}
<Heading>Let's create some columns</Heading>
<Body>
Start building out your table structure<br />
by adding some columns
</Body>
{:else}
<Heading>Now let's add a row</Heading>
<Body>
Add some data to your table<br />
by adding some rows
</Body>
{/if}
</Layout>
</div>
</Table>
{#key id}
<div in:fade={{ delay: 200, duration: 100 }}>
<div class="pagination">
<Pagination
page={$fetch.pageNumber + 1}
hasPrevPage={$fetch.hasPrevPage}
hasNextPage={$fetch.hasNextPage}
goToPrevPage={$fetch.loading ? null : fetch.prevPage}
goToNextPage={$fetch.loading ? null : fetch.nextPage}
/>
</div>
</div>
{/key}
<svelte:fragment slot="controls">
{#if isInternal}
<GridCreateViewButton />
{/if}
<GridManageAccessButton />
{#if !isInternal}
<GridRelationshipButton />
{/if}
{#if isUsersTable}
<EditRolesButton />
{:else}
<GridImportButton />
{/if}
<GridExportButton />
<GridFilterButton />
<GridAddColumnModal />
<GridEditColumnModal />
{#if isUsersTable}
<GridEditUserModal />
{:else}
<GridCreateEditRowModal />
{/if}
</svelte:fragment>
</Grid>
</div>
<style>
.pagination {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
margin-top: var(--spacing-xl);
}
.buttons {
.wrapper {
flex: 1 1 auto;
margin: -28px -40px -40px -40px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
}
.left-buttons,
.right-buttons {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
flex-direction: column;
background: var(--background);
overflow: hidden;
}
</style>

View File

@ -1,15 +1,10 @@
<script>
import { fade } from "svelte/transition"
import { goto, params } from "@roxi/routify"
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
import { API } from "api"
import { Table, Heading, Layout } from "@budibase/bbui"
import Spinner from "components/common/Spinner.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
import CreateEditRow from "./modals/CreateEditRow.svelte"
import CreateEditUser from "./modals/CreateEditUser.svelte"
import CreateEditColumn from "./modals/CreateEditColumn.svelte"
import { cloneDeep } from "lodash/fp"
import {
TableNames,
UNEDITABLE_USER_FIELDS,
@ -22,7 +17,6 @@
export let data = []
export let tableId
export let title
export let allowEditing = false
export let loading = false
export let hideAutocolumns
export let rowCount
@ -32,12 +26,7 @@
const dispatch = createEventDispatcher()
let selectedRows = []
let editableColumn
let editableRow
let editRowModal
let editColumnModal
let customRenderers = []
let confirmDelete
$: selectedRows, dispatch("selectionUpdated", selectedRows)
$: isUsersTable = tableId === TableNames.USERS
@ -92,36 +81,6 @@
`/builder/app/${$params.application}/data/table/${tableId}/relationship/${rowId}/${fieldName}`
)
}
const deleteRows = async targetRows => {
try {
await API.deleteRows({
tableId,
rows: targetRows,
})
const deletedRowIds = targetRows.map(row => row._id)
data = data.filter(row => deletedRowIds.indexOf(row._id))
notifications.success(`Successfully deleted ${targetRows.length} rows`)
} catch (error) {
notifications.error("Error deleting rows")
}
}
const editRow = row => {
editableRow = row
if (row) {
editRowModal.show()
}
}
const editColumn = field => {
editableColumn = cloneDeep(schema?.[field])
if (editableColumn) {
editColumnModal.show()
}
}
</script>
<Layout noPadding gap="S">
@ -138,16 +97,6 @@
{/if}
<div class="popovers">
<slot />
{#if !isUsersTable && selectedRows.length > 0}
<DeleteRowsButton
on:updaterows
{selectedRows}
deleteRows={async rows => {
await deleteRows(rows)
resetSelectedRows()
}}
/>
{/if}
</div>
</Layout>
{#key tableId}
@ -160,13 +109,7 @@
{rowCount}
{disableSorting}
{customPlaceholder}
bind:selectedRows
allowSelectRows={allowEditing && !isUsersTable}
allowEditRows={allowEditing}
allowEditColumns={allowEditing}
showAutoColumns={!hideAutocolumns}
on:editcolumn={e => editColumn(e.detail)}
on:editrow={e => editRow(e.detail)}
on:clickrelationship={e => selectRelationship(e.detail)}
on:sort
>
@ -176,42 +119,6 @@
{/key}
</Layout>
<Modal bind:this={editRowModal}>
<svelte:component
this={editRowComponent}
on:updaterows
on:deleteRows={() => {
confirmDelete.show()
}}
row={editableRow}
/>
</Modal>
<ConfirmDialog
bind:this={confirmDelete}
okText="Delete"
onOk={async () => {
if (editableRow) {
await deleteRows([editableRow])
}
editableRow = undefined
}}
onCancel={async () => {
editRow(editableRow)
}}
title="Confirm Deletion"
>
Are you sure you want to delete this row?
</ConfirmDialog>
<Modal bind:this={editColumnModal}>
<CreateEditColumn
field={editableColumn}
on:updatecolumns
onClosed={editColumnModal.hide}
/>
</Modal>
<style>
.table-title {
height: 24px;

View File

@ -57,7 +57,6 @@
{data}
{loading}
{type}
allowEditing={false}
rowCount={10}
bind:hideAutocolumns
>

View File

@ -9,7 +9,6 @@
<ActionButton
icon="Calculator"
size="S"
quiet
on:click={modal.show}
active={view.field && view.calculation}

View File

@ -1,24 +0,0 @@
<script>
import { ActionButton, Modal } from "@budibase/bbui"
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
export let highlighted = false
export let disabled = false
let modal
</script>
<ActionButton
{disabled}
selected={highlighted}
emphasized={highlighted}
icon="TableColumnAddRight"
quiet
size="S"
on:click={modal.show}
>
Create column
</ActionButton>
<Modal bind:this={modal}>
<CreateEditColumn on:updatecolumns />
</Modal>

View File

@ -1,26 +0,0 @@
<script>
import { ActionButton, Modal } from "@budibase/bbui"
import CreateEditRow from "../modals/CreateEditRow.svelte"
export let modalContentComponent = CreateEditRow
export let title = "Create row"
export let disabled = false
export let highlighted = false
let modal
</script>
<ActionButton
{disabled}
emphasized={highlighted}
selected={highlighted}
icon="TableRowAddBottom"
size="S"
quiet
on:click={modal.show}
>
{title}
</ActionButton>
<Modal bind:this={modal}>
<svelte:component this={modalContentComponent} on:updaterows />
</Modal>

View File

@ -1,21 +0,0 @@
<script>
import { Modal, ActionButton } from "@budibase/bbui"
import CreateViewModal from "../modals/CreateViewModal.svelte"
export let disabled = false
let modal
</script>
<ActionButton
{disabled}
icon="CollectionAdd"
size="S"
quiet
on:click={modal.show}
>
Create view
</ActionButton>
<Modal bind:this={modal}>
<CreateViewModal />
</Modal>

View File

@ -19,7 +19,7 @@
$: text = `${item}${selectedRows?.length === 1 ? "" : "s"}`
</script>
<Button icon="Delete" size="s" warning quiet on:click={modal.show}>
<Button icon="Delete" warning quiet on:click={modal.show}>
Delete
{selectedRows.length}
{text}

View File

@ -1,15 +1,13 @@
<script>
import { Button, Modal } from "@budibase/bbui"
import { ActionButton, Modal } from "@budibase/bbui"
import EditRolesModal from "../modals/EditRoles.svelte"
let modal
</script>
<div>
<Button icon="UsersLock" primary size="S" quiet on:click={modal.show}>
Edit roles
</Button>
</div>
<ActionButton icon="UsersLock" quiet on:click={modal.show}>
Edit roles
</ActionButton>
<Modal bind:this={modal}>
<EditRolesModal />
</Modal>

View File

@ -7,15 +7,23 @@
export let table
const dispatch = createEventDispatcher()
$: datasource = findDatasource(table?._id)
$: plusTables = datasource?.plus
? Object.values(datasource?.entities || {})
: []
$: datasource = $datasources.list.find(
source => source._id === table?.sourceId
)
let modal
const findDatasource = tableId => {
return $datasources.list.find(datasource => {
return (
Object.values(datasource.entities || {}).find(entity => {
return entity._id === tableId
}) != null
)
})
}
async function saveRelationship() {
try {
// Create datasource
@ -28,15 +36,9 @@
}
</script>
{#if table.sourceId}
{#if datasource}
<div>
<ActionButton
icon="DataCorrelated"
primary
size="S"
quiet
on:click={modal.show}
>
<ActionButton icon="DataCorrelated" primary quiet on:click={modal.show}>
Define existing relationship
</ActionButton>
</div>

View File

@ -11,13 +11,7 @@
let modal
</script>
<ActionButton
{disabled}
icon="DataDownload"
size="S"
quiet
on:click={modal.show}
>
<ActionButton {disabled} icon="DataDownload" quiet on:click={modal.show}>
Export
</ActionButton>
<Modal bind:this={modal}>

View File

@ -10,7 +10,6 @@
<Button
icon="Group"
primary
size="S"
quiet
active={!!view.groupBy}
on:click={modal.show}

View File

@ -11,7 +11,6 @@
<ActionButton
icon={hideAutocolumns ? "VisibilityOff" : "Visibility"}
primary
size="S"
quiet
on:click={hideOrUnhide}
>

View File

@ -8,7 +8,7 @@
let modal
</script>
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show} {disabled}>
<ActionButton icon="DataUpload" quiet on:click={modal.show} {disabled}>
Import
</ActionButton>
<Modal bind:this={modal}>

View File

@ -4,6 +4,7 @@
import ManageAccessModal from "../modals/ManageAccessModal.svelte"
export let resourceId
export let disabled = false
let modal
let resourcePermissions
@ -14,8 +15,8 @@
}
</script>
<ActionButton icon="LockClosed" size="S" quiet on:click={openDropdown}>
Manage access
<ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}>
Access
</ActionButton>
<Modal bind:this={modal}>
<ManageAccessModal

View File

@ -18,11 +18,10 @@
<ActionButton
icon="Filter"
size="S"
quiet
{disabled}
on:click={modal.show}
active={tempValue?.length > 0}
selected={tempValue?.length > 0}
>
Filter
</ActionButton>

View File

@ -9,7 +9,6 @@
<ActionButton
icon="Filter"
size="S"
quiet
on:click={modal.show}
active={view.filters?.length}

View File

@ -0,0 +1,18 @@
<script>
import { getContext } from "svelte"
import { Modal, ActionButton } from "@budibase/bbui"
import CreateViewModal from "../../modals/CreateViewModal.svelte"
const { rows, columns } = getContext("grid")
let modal
$: disabled = !$columns.length || !$rows.length
</script>
<ActionButton {disabled} icon="CollectionAdd" quiet on:click={modal.show}>
Add view
</ActionButton>
<Modal bind:this={modal}>
<CreateViewModal />
</Modal>

View File

@ -0,0 +1,21 @@
<script>
import ExportButton from "../ExportButton.svelte"
import { getContext } from "svelte"
const { rows, columns, tableId, sort, selectedRows, filter } =
getContext("grid")
$: disabled = !$rows.length || !$columns.length
$: selectedRowArray = Object.keys($selectedRows).map(id => ({ _id: id }))
</script>
<ExportButton
{disabled}
view={$tableId}
filters={$filter}
sorting={{
sortColumn: $sort.column,
sortOrder: $sort.order,
}}
selectedRows={selectedRowArray}
/>

View File

@ -0,0 +1,20 @@
<script>
import TableFilterButton from "../TableFilterButton.svelte"
import { getContext } from "svelte"
const { columns, config, filter, table } = getContext("grid")
const onFilter = e => {
filter.set(e.detail || [])
}
</script>
{#key $config.tableId}
<TableFilterButton
schema={$table?.schema}
filters={$filter}
on:change={onFilter}
disabled={!$columns.length}
tableId={$config.tableId}
/>
{/key}

View File

@ -0,0 +1,14 @@
<script>
import ImportButton from "../ImportButton.svelte"
import { getContext } from "svelte"
export let disabled = false
const { rows, tableId } = getContext("grid")
</script>
<ImportButton
{disabled}
tableId={$tableId}
on:importrows={rows.actions.refreshData}
/>

View File

@ -0,0 +1,8 @@
<script>
import ManageAccessButton from "../ManageAccessButton.svelte"
import { getContext } from "svelte"
const { config } = getContext("grid")
</script>
<ManageAccessButton resourceId={$config.tableId} />

View File

@ -0,0 +1,13 @@
<script>
import ExistingRelationshipButton from "../ExistingRelationshipButton.svelte"
import { getContext } from "svelte"
const { table, rows } = getContext("grid")
</script>
{#if $table}
<ExistingRelationshipButton
table={$table}
on:updatecolumns={() => rows.actions.refreshData()}
/>
{/if}

View File

@ -182,8 +182,12 @@
indexes,
})
dispatch("updatecolumns")
if (originalName) {
notifications.success("Column updated successfully")
} else {
notifications.success("Column created successfully")
}
} catch (err) {
console.log(err)
notifications.error(`Error saving column: ${err.message}`)
}
}
@ -199,7 +203,7 @@
notifications.error("You cannot delete the display column")
} else {
await tables.deleteField(editableColumn)
notifications.success(`Column ${editableColumn.name} deleted.`)
notifications.success(`Column ${editableColumn.name} deleted`)
confirmDeleteDialog.hide()
hide()
deletion = false

View File

@ -23,9 +23,9 @@
async function saveRow() {
errors = []
try {
await API.saveRow({ ...row, tableId: table._id })
const res = await API.saveRow({ ...row, tableId: table._id })
notifications.success("Row saved successfully")
dispatch("updaterows")
dispatch("updaterows", res._id)
} catch (error) {
const response = error.json
if (error.handled && response?.errors) {

View File

@ -55,9 +55,9 @@
}
try {
await API.saveRow({ ...row, tableId: table._id })
const res = await API.saveRow({ ...row, tableId: table._id })
notifications.success("User saved successfully")
dispatch("updaterows")
dispatch("updaterows", res.id)
} catch (error) {
if (error.handled) {
const response = error.json

View File

@ -0,0 +1,15 @@
<script>
import { getContext, onMount } from "svelte"
import { Modal } from "@budibase/bbui"
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
const { rows, subscribe } = getContext("grid")
let modal
onMount(() => subscribe("add-column", modal.show))
</script>
<Modal bind:this={modal}>
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
</Modal>

View File

@ -0,0 +1,37 @@
<script>
import CreateEditRow from "../../modals/CreateEditRow.svelte"
import { getContext, onMount } from "svelte"
import { Modal, notifications } from "@budibase/bbui"
import { cloneDeep } from "lodash/fp"
const { subscribe, rows } = getContext("grid")
let modal
let row
const deleteRow = e => {
rows.actions.deleteRows([e.detail])
notifications.success("Deleted 1 row")
}
onMount(() =>
subscribe("add-row", () => {
row = {}
modal.show()
})
)
onMount(() =>
subscribe("edit-row", rowToEdit => {
row = cloneDeep(rowToEdit)
modal.show()
})
)
</script>
<Modal bind:this={modal}>
<CreateEditRow
{row}
on:updaterows={e => rows.actions.refreshRow(e.detail)}
on:deleteRows={deleteRow}
/>
</Modal>

View File

@ -0,0 +1,24 @@
<script>
import { getContext, onMount } from "svelte"
import { Modal } from "@budibase/bbui"
import CreateEditColumn from "../CreateEditColumn.svelte"
const { rows, subscribe } = getContext("grid")
let editableColumn
let editColumnModal
const editColumn = column => {
editableColumn = column
editColumnModal.show()
}
onMount(() => subscribe("edit-column", editColumn))
</script>
<Modal bind:this={editColumnModal}>
<CreateEditColumn
field={editableColumn}
on:updatecolumns={rows.actions.refreshData}
/>
</Modal>

View File

@ -0,0 +1,25 @@
<script>
import CreateEditUser from "../../modals/CreateEditUser.svelte"
import { getContext, onMount } from "svelte"
import { Modal } from "@budibase/bbui"
import { cloneDeep } from "lodash/fp"
const { subscribe, rows } = getContext("grid")
let modal
let row
onMount(() =>
subscribe("edit-row", rowToEdit => {
row = cloneDeep(rowToEdit)
modal.show()
})
)
</script>
<Modal bind:this={modal}>
<CreateEditUser
{row}
on:updaterows={e => rows.actions.refreshRow(e.detail)}
/>
</Modal>

View File

@ -13,6 +13,13 @@
$: sortedTables = $tables.list
.filter(table => table.sourceId === sourceId)
.sort(alphabetical)
const selectTable = tableId => {
tables.select(tableId)
if (!$isActive("./table/:tableId")) {
$goto(`./table/${tableId}`)
}
}
</script>
{#if $database?._id}
@ -25,7 +32,7 @@
text={table.name}
selected={$isActive("./table/:tableId") &&
$tables.selected?._id === table._id}
on:click={() => $goto(`./table/${table._id}`)}
on:click={() => selectTable(table._id)}
>
{#if table._id !== TableNames.USERS}
<EditTablePopover {table} />

View File

@ -325,9 +325,4 @@
text-overflow: ellipsis;
white-space: nowrap;
}
footer {
display: flex;
justify-content: center;
}
</style>

View File

@ -0,0 +1,23 @@
<script>
import { ModalContent } from "@budibase/bbui"
import { licensing } from "stores/portal"
export let isOwner
</script>
<ModalContent
onConfirm={() =>
isOwner
? $licensing.goToUpgradePage()
: window.open("https://budibase.com/pricing/", "_blank")}
confirmText={isOwner ? "Upgrade" : "View plans"}
title="Upgrade to add more users"
>
<div>
Free plan is limited to {$licensing.license.quotas.usage.static.users.value}
users. Upgrade your plan to add more users.
</div>
</ModalContent>
<style>
</style>

View File

@ -21,6 +21,7 @@ import DrawerBindableCombobox from "components/common/bindings/DrawerBindableCom
import ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte"
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
import BarButtonList from "./controls/BarButtonList.svelte"
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
const componentMap = {
text: DrawerBindableCombobox,
@ -43,6 +44,7 @@ const componentMap = {
section: SectionSelect,
filter: FilterEditor,
url: URLSelect,
fieldConfiguration: FieldConfiguration,
columns: ColumnEditor,
"columns/basic": BasicColumnEditor,
"field/sortable": SortableFieldSelect,

View File

@ -0,0 +1,91 @@
<script>
import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import ColumnDrawer from "./ColumnDrawer.svelte"
import { cloneDeep } from "lodash/fp"
import {
getDatasourceForProvider,
getSchemaForDatasource,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import { getFields } from "helpers/searchFields"
export let componentInstance
export let value = []
export let allowCellEditing = true
export let subject = "Table"
const dispatch = createEventDispatcher()
let drawer
let boundValue
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchema($currentAsset, datasource)
$: options = allowCellEditing
? Object.keys(schema || {})
: enrichedSchemaFields?.map(field => field.name)
$: sanitisedValue = getValidColumns(value, options)
$: updateBoundValue(sanitisedValue)
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
allowLinks: true,
})
const getSchema = (asset, datasource) => {
const schema = getSchemaForDatasource(asset, datasource).schema
// Don't show ID and rev in tables
if (schema) {
delete schema._id
delete schema._rev
}
return schema
}
const updateBoundValue = value => {
boundValue = cloneDeep(value)
}
const getValidColumns = (columns, options) => {
if (!Array.isArray(columns) || !columns.length) {
return []
}
// We need to account for legacy configs which would just be an array
// of strings
if (typeof columns[0] === "string") {
columns = columns.map(col => ({
name: col,
displayName: col,
}))
}
return columns.filter(column => {
return options.includes(column.name)
})
}
const open = () => {
updateBoundValue(sanitisedValue)
drawer.show()
}
const save = () => {
dispatch("change", getValidColumns(boundValue, options))
drawer.hide()
}
</script>
<ActionButton on:click={open}>Configure columns</ActionButton>
<Drawer bind:this={drawer} title="{subject} Columns">
<svelte:fragment slot="description">
Configure the columns in your {subject.toLowerCase()}.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<ColumnDrawer
slot="body"
bind:columns={boundValue}
{options}
{schema}
{allowCellEditing}
/>
</Drawer>

View File

@ -0,0 +1,26 @@
<script>
import { DrawerContent, Drawer, Button, Icon } from "@budibase/bbui"
import ValidationDrawer from "components/design/settings/controls/ValidationEditor/ValidationDrawer.svelte"
export let column
export let type
let drawer
</script>
<Icon name="Settings" hoverable size="S" on:click={drawer.show} />
<Drawer bind:this={drawer} title="Field Validation">
<svelte:fragment slot="description">
"{column.name}" field validation
</svelte:fragment>
<Button cta slot="buttons" on:click={drawer.hide}>Save</Button>
<DrawerContent slot="body">
<div class="container">
<ValidationDrawer
slot="body"
bind:rules={column.validation}
fieldName={column.name}
{type}
/>
</div>
</DrawerContent>
</Drawer>

View File

@ -0,0 +1,202 @@
<script>
import {
Button,
Icon,
DrawerContent,
Layout,
Select,
Label,
Body,
Input,
} from "@budibase/bbui"
import { flip } from "svelte/animate"
import { dndzone } from "svelte-dnd-action"
import { generate } from "shortid"
import CellEditor from "./CellEditor.svelte"
export let columns = []
export let options = []
export let schema = {}
const flipDurationMs = 150
let dragDisabled = true
$: unselectedColumns = getUnselectedColumns(options, columns)
$: columns.forEach(column => {
if (!column.id) {
column.id = generate()
}
})
const getUnselectedColumns = (allColumns, selectedColumns) => {
let optionsObj = {}
allColumns.forEach(option => {
optionsObj[option] = true
})
selectedColumns?.forEach(column => {
delete optionsObj[column.name]
})
return Object.keys(optionsObj)
}
const getRemainingColumnOptions = selectedColumn => {
if (!selectedColumn || unselectedColumns.includes(selectedColumn)) {
return unselectedColumns
}
return [selectedColumn, ...unselectedColumns]
}
const addColumn = () => {
columns = [...columns, {}]
}
const removeColumn = id => {
columns = columns.filter(column => column.id !== id)
}
const updateColumnOrder = e => {
columns = e.detail.items
}
const handleFinalize = e => {
updateColumnOrder(e)
dragDisabled = true
}
const addAllColumns = () => {
let newColumns = columns || []
options.forEach(field => {
const fieldSchema = schema[field]
const hasCol = columns && columns.findIndex(x => x.name === field) !== -1
if (!fieldSchema?.autocolumn && !hasCol) {
newColumns.push({
name: field,
displayName: field,
})
}
})
columns = newColumns
}
const reset = () => {
columns = []
}
const getFieldType = column => {
return `validation/${schema[column.name]?.type}`
}
</script>
<DrawerContent>
<div class="container">
<Layout noPadding gap="S">
{#if columns?.length}
<Layout noPadding gap="XS">
<div class="column">
<div />
<Label size="L">Column</Label>
<Label size="L">Label</Label>
<div />
<div />
</div>
<div
class="columns"
use:dndzone={{
items: columns,
flipDurationMs,
dropTargetStyle: { outline: "none" },
dragDisabled,
}}
on:finalize={handleFinalize}
on:consider={updateColumnOrder}
>
{#each columns as column (column.id)}
<div class="column" animate:flip={{ duration: flipDurationMs }}>
<div
class="handle"
aria-label="drag-handle"
style={dragDisabled ? "cursor: grab" : "cursor: grabbing"}
on:mousedown={() => (dragDisabled = false)}
>
<Icon name="DragHandle" size="XL" />
</div>
<Select
bind:value={column.name}
placeholder="Column"
options={getRemainingColumnOptions(column.name)}
on:change={e => (column.displayName = e.detail)}
/>
<Input bind:value={column.displayName} placeholder="Label" />
<CellEditor type={getFieldType(column)} bind:column />
<Icon
name="Close"
hoverable
size="S"
on:click={() => removeColumn(column.id)}
disabled={columns.length === 1}
/>
</div>
{/each}
</div>
</Layout>
{:else}
<div class="column">
<div class="wide">
<Body size="S">Add columns to be included in your form below.</Body>
</div>
</div>
{/if}
<div class="column">
<div class="buttons wide">
<Button secondary icon="Add" on:click={addColumn}>Add column</Button>
<Button secondary quiet on:click={addAllColumns}>
Add all columns
</Button>
{#if columns?.length}
<Button secondary quiet on:click={reset}>Reset columns</Button>
{/if}
</div>
</div>
</Layout>
</div>
</DrawerContent>
<style>
.container {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.columns {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-m);
}
.column {
gap: var(--spacing-l);
display: grid;
grid-template-columns: 20px 1fr 1fr 16px 16px;
align-items: center;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;
}
.column:hover {
background-color: var(--spectrum-global-color-gray-100);
}
.handle {
display: grid;
place-items: center;
}
.wide {
grid-column: 2 / -1;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-m);
}
</style>

View File

@ -0,0 +1,89 @@
<script>
import { Button, ActionButton, Drawer } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import ColumnDrawer from "./ColumnDrawer.svelte"
import { cloneDeep } from "lodash/fp"
import {
getDatasourceForProvider,
getSchemaForDatasource,
} from "builderStore/dataBinding"
import { currentAsset } from "builderStore"
import { getFields } from "helpers/searchFields"
export let componentInstance
export let value = []
const convertOldColumnFormat = oldColumns => {
if (typeof oldColumns?.[0] === "string") {
value = oldColumns.map(field => ({ name: field, displayName: field }))
}
}
$: convertOldColumnFormat(value)
const dispatch = createEventDispatcher()
let drawer
let boundValue
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchema($currentAsset, datasource)
$: options = Object.keys(schema || {})
$: sanitisedValue = getValidColumns(value, options)
$: updateBoundValue(sanitisedValue)
$: enrichedSchemaFields = getFields(Object.values(schema || {}), {
allowLinks: true,
})
const getSchema = (asset, datasource) => {
const schema = getSchemaForDatasource(asset, datasource).schema
// Don't show ID and rev in tables
if (schema) {
delete schema._id
delete schema._rev
}
return schema
}
const updateBoundValue = value => {
boundValue = cloneDeep(value)
}
const getValidColumns = (columns, options) => {
if (!Array.isArray(columns) || !columns.length) {
return []
}
// We need to account for legacy configs which would just be an array
// of strings
if (typeof columns[0] === "string") {
columns = columns.map(col => ({
name: col,
displayName: col,
}))
}
return columns.filter(column => {
return options.includes(column.name)
})
}
const open = () => {
updateBoundValue(sanitisedValue)
drawer.show()
}
const save = () => {
dispatch("change", getValidColumns(boundValue, options))
drawer.hide()
}
</script>
<ActionButton on:click={open}>Configure fields</ActionButton>
<Drawer bind:this={drawer} title="Form Fields">
<svelte:fragment slot="description">
Configure the fields in your form.
</svelte:fragment>
<Button cta slot="buttons" on:click={save}>Save</Button>
<ColumnDrawer slot="body" bind:columns={boundValue} {options} {schema} />
</Drawer>

View File

@ -16,6 +16,7 @@
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
import { generate } from "shortid"
export let fieldName = null
export let rules = []
export let bindings = []
export let type
@ -124,7 +125,7 @@
}
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
$: field = $selectedComponent?.field
$: field = fieldName || $selectedComponent?.field
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
$: fieldType = type?.split("/")[1] || "string"
$: constraintOptions = getConstraintsForType(fieldType)
@ -140,8 +141,12 @@
const formParent = findClosestMatchingComponent(
asset.props,
component._id,
component => component._component.endsWith("/form")
component =>
component._component.endsWith("/form") ||
component._component.endsWith("/formblock") ||
component._component.endsWith("/tableblock")
)
return getSchemaForDatasource(asset, formParent?.dataSource)
}

View File

@ -7,6 +7,7 @@ export const ExpiringKeys = {
LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner",
LICENSING_AUTOMATIONS_WARNING_BANNER: "licensing_automations_warning_banner",
LICENSING_QUERIES_WARNING_BANNER: "licensing_queries_warning_banner",
LICENSING_USERS_ABOVE_LIMIT_BANNER: "licensing_users_above_limit_banner",
}
export const StripeStatus = {

View File

@ -3,6 +3,7 @@ import { temporalStore } from "builderStore"
import { admin, auth, licensing } from "stores/portal"
import { get } from "svelte/store"
import { BANNER_TYPES } from "@budibase/bbui"
import { capitalise } from "helpers"
const oneDayInSeconds = 86400
@ -141,6 +142,30 @@ const buildPaymentFailedBanner = () => {
}
}
const buildUsersAboveLimitBanner = EXPIRY_KEY => {
const userLicensing = get(licensing)
return {
key: EXPIRY_KEY,
type: BANNER_TYPES.WARNING,
criteria: () => {
return userLicensing.warnUserLimit
},
message: `${capitalise(
userLicensing.license.plan.type
)} plan changes - Users will be limited to ${
userLicensing.userLimit
} users in ${userLicensing.userLimitDays}`,
...{
extraButtonText: "Find out more",
extraButtonAction: () => {
defaultCacheFn(ExpiringKeys.LICENSING_USERS_ABOVE_LIMIT_BANNER)
window.location.href = "/builder/portal/users/users"
},
},
showCloseButton: true,
}
}
export const getBanners = () => {
return [
buildPaymentFailedBanner(),
@ -163,6 +188,7 @@ export const getBanners = () => {
ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER,
90
),
buildUsersAboveLimitBanner(ExpiringKeys.LICENSING_USERS_ABOVE_LIMIT_BANNER),
].filter(licensingBanner => {
return (
!temporalStore.actions.getExpiring(licensingBanner.key) &&

View File

@ -59,9 +59,7 @@
<div class="app-row-actions">
<AppLockModal {app} buttonSize="M" />
<Button size="S" secondary on:click={goToOverview}>Manage</Button>
<Button size="S" primary disabled={app.lockedOther} on:click={goToBuilder}>
Edit
</Button>
<Button size="S" primary on:click={goToBuilder}>Edit</Button>
</div>
</div>

View File

@ -67,3 +67,8 @@ export const OnboardingType = {
EMAIL: "email",
PASSWORD: "password",
}
export const PlanModel = {
PER_USER: "perUser",
DAY_PASS: "dayPass",
}

View File

@ -70,7 +70,6 @@ a {
background: var(--spectrum-alias-background-color-default);
}
html * {
scrollbar-width: thin;
scrollbar-color: var(--spectrum-global-color-gray-400)
var(--spectrum-alias-background-color-default);
}

View File

@ -1,46 +0,0 @@
<script>
import { notifications, ModalContent, Dropzone, Body } from "@budibase/bbui"
import { API } from "api"
import { admin } from "stores/portal"
let submitting = false
$: value = { file: null }
async function importApps() {
submitting = true
try {
// Create form data to create app
let data = new FormData()
data.append("importFile", value.file)
// Create App
await API.importApps(data)
await admin.checkImportComplete()
notifications.success("Import complete, please finish registration!")
} catch (error) {
notifications.error("Failed to import apps")
}
submitting = false
}
</script>
<ModalContent
title="Import apps"
confirmText="Import apps"
onConfirm={importApps}
disabled={!value.file}
>
<Body>
Please upload the file that was exported from your Cloud environment to get
started
</Body>
<Dropzone
gallery={false}
label="File to import"
value={[value.file]}
on:change={e => {
value.file = e.detail?.[0]
}}
/>
</ModalContent>

View File

@ -1,31 +1,19 @@
<script>
import {
Button,
Heading,
notifications,
Layout,
Body,
Modal,
} from "@budibase/bbui"
import { Button, Heading, notifications, Layout, Body } from "@budibase/bbui"
import { goto } from "@roxi/routify"
import { API } from "api"
import { admin, auth } from "stores/portal"
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
import Logo from "assets/bb-emblem.svg"
import { onMount } from "svelte"
import { FancyForm, FancyInput, ActionButton } from "@budibase/bbui"
import { FancyForm, FancyInput } from "@budibase/bbui"
import { TestimonialPage } from "@budibase/frontend-core/src/components"
import { passwordsMatch, handleError } from "../auth/_components/utils"
let modal
let form
let errors = {}
let formData = {}
let submitted = false
$: tenantId = $auth.tenantId
$: cloud = $admin.cloud
$: imported = $admin.importComplete
async function save() {
form.validate()
@ -46,22 +34,8 @@
notifications.error("Failed to create admin user")
}
}
onMount(async () => {
if (!cloud) {
try {
await admin.checkImportComplete()
} catch (error) {
notifications.error("Error checking import status")
}
}
})
</script>
<Modal bind:this={modal} padding={false} width="600px">
<ImportAppsModal />
</Modal>
<TestimonialPage>
<Layout gap="M" noPadding>
<Layout justifyItems="center" noPadding>
@ -156,20 +130,6 @@
Create super admin user
</Button>
</Layout>
<Layout gap="XS" noPadding justifyItems="center">
<div class="user-actions">
{#if !cloud && !imported}
<ActionButton
quiet
on:click={() => {
modal.show()
}}
>
Import from cloud
</ActionButton>
{/if}
</div>
</Layout>
</Layout>
</TestimonialPage>

Some files were not shown because too many files have changed in this diff Show More