Merge branch 'develop' of github.com:Budibase/budibase into chore/esbuild
This commit is contained in:
commit
49b45f5e67
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "packages/pro"]
|
||||
path = packages/pro
|
||||
url = git@github.com:Budibase/budibase-pro.git
|
|
@ -1,4 +0,0 @@
|
|||
# .husky/post-checkout
|
||||
# ...
|
||||
|
||||
git config submodule.recurse true
|
34
README.md
34
README.md
|
@ -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).
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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`.
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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.
|
18
lerna.json
18
lerna.json
|
@ -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": [
|
||||
|
|
32
package.json
32
package.json
|
@ -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/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
import Chance from "chance"
|
||||
import Chance from "./Chance"
|
||||
export const generator = new Chance()
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: {},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * as time from "./time"
|
|
@ -0,0 +1,3 @@
|
|||
export function addDaysToDate(date: Date, days: number) {
|
||||
return new Date(date.getTime() + days * 24 * 60 * 60 * 1000)
|
||||
}
|
|
@ -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__"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
"@budibase/types": ["../types/src"]
|
||||
}
|
||||
},
|
||||
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
"references": [
|
||||
{ "path": "../types" }
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
]
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
{data}
|
||||
{loading}
|
||||
{type}
|
||||
allowEditing={false}
|
||||
rowCount={10}
|
||||
bind:hideAutocolumns
|
||||
>
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
<ActionButton
|
||||
icon="Calculator"
|
||||
size="S"
|
||||
quiet
|
||||
on:click={modal.show}
|
||||
active={view.field && view.calculation}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<Button
|
||||
icon="Group"
|
||||
primary
|
||||
size="S"
|
||||
quiet
|
||||
active={!!view.groupBy}
|
||||
on:click={modal.show}
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
<ActionButton
|
||||
icon={hideAutocolumns ? "VisibilityOff" : "Visibility"}
|
||||
primary
|
||||
size="S"
|
||||
quiet
|
||||
on:click={hideOrUnhide}
|
||||
>
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
<ActionButton
|
||||
icon="Filter"
|
||||
size="S"
|
||||
quiet
|
||||
on:click={modal.show}
|
||||
active={view.filters?.length}
|
||||
|
|
|
@ -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>
|
|
@ -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}
|
||||
/>
|
|
@ -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}
|
|
@ -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}
|
||||
/>
|
|
@ -0,0 +1,8 @@
|
|||
<script>
|
||||
import ManageAccessButton from "../ManageAccessButton.svelte"
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const { config } = getContext("grid")
|
||||
</script>
|
||||
|
||||
<ManageAccessButton resourceId={$config.tableId} />
|
|
@ -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}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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} />
|
||||
|
|
|
@ -325,9 +325,4 @@
|
|||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -67,3 +67,8 @@ export const OnboardingType = {
|
|||
EMAIL: "email",
|
||||
PASSWORD: "password",
|
||||
}
|
||||
|
||||
export const PlanModel = {
|
||||
PER_USER: "perUser",
|
||||
DAY_PASS: "dayPass",
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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
Loading…
Reference in New Issue