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
|
name: Budibase CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Trigger the workflow on push or pull request,
|
# Trigger the workflow on push or pull request,
|
||||||
# but only for the master branch
|
# but only for the master branch
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
pull_request:
|
workflow_dispatch:
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
BASE_BRANCH: ${{ github.event.pull_request.base.ref}}
|
BASE_BRANCH: ${{ github.event.pull_request.base.ref}}
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro $BRANCH $BASE_BRANCH
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
|
@ -49,17 +48,16 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro $BRANCH $BASE_BRANCH
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn test --ignore=@budibase/pro
|
- run: yarn test
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||||
|
@ -70,28 +68,26 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro $BRANCH $BASE_BRANCH
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn test --scope=@budibase/pro
|
- run: yarn test:pro
|
||||||
|
|
||||||
integration-test:
|
integration-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
- name: Use Node.js 14.x
|
- name: Use Node.js 14.x
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro $BRANCH $BASE_BRANCH
|
||||||
- run: yarn && yarn bootstrap && yarn build
|
- run: yarn && yarn bootstrap && yarn build
|
||||||
- run: |
|
- run: |
|
||||||
cd qa-core
|
cd qa-core
|
||||||
|
@ -100,24 +96,3 @@ jobs:
|
||||||
env:
|
env:
|
||||||
BB_ADMIN_USER_EMAIL: admin
|
BB_ADMIN_USER_EMAIL: admin
|
||||||
BB_ADMIN_USER_PASSWORD: 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
|
name: Budibase Prerelease
|
||||||
concurrency: release-prerelease
|
concurrency: release-prerelease
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- ".aws/**"
|
- '.aws/**'
|
||||||
- ".github/**"
|
- '.github/**'
|
||||||
- "charts/**"
|
- 'charts/**'
|
||||||
- "packages/**"
|
- 'packages/**'
|
||||||
- "scripts/**"
|
- 'scripts/**'
|
||||||
- "package.json"
|
- 'package.json'
|
||||||
- "yarn.lock"
|
- 'yarn.lock'
|
||||||
- "package.json"
|
- 'package.json'
|
||||||
- "yarn.lock"
|
- 'yarn.lock'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Posthog token used by ui at build time
|
# Posthog token used by ui at build time
|
||||||
|
@ -24,35 +24,35 @@ env:
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
FEATURE_PREVIEW_URL: https://budirelease.live
|
FEATURE_PREVIEW_URL: https://budirelease.live
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-images:
|
release-images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Fail if branch is not develop
|
- name: Fail if branch is not develop
|
||||||
if: github.ref != 'refs/heads/develop'
|
if: github.ref != 'refs/heads/develop'
|
||||||
run: |
|
run: |
|
||||||
echo "Ref is not develop, you must run this job from develop."
|
echo "Ref is not develop, you must run this job from develop."
|
||||||
exit 1
|
exit 1
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
- run: yarn
|
- name: Install Pro
|
||||||
|
run: yarn install:pro develop
|
||||||
|
|
||||||
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn build
|
- run: yarn build
|
||||||
- run: yarn build:sdk
|
- run: yarn build:sdk
|
||||||
# - run: yarn test
|
# - run: yarn test
|
||||||
|
|
||||||
- name: Publish budibase packages to NPM
|
- name: Publish budibase packages to NPM
|
||||||
env:
|
env:
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
# setup the username and email.
|
# setup the username and email.
|
||||||
git config --global user.name "Budibase Staging Release Bot"
|
git config --global user.name "Budibase Staging Release Bot"
|
||||||
git config --global user.email "<>"
|
git config --global user.email "<>"
|
||||||
|
@ -60,7 +60,7 @@ jobs:
|
||||||
yarn release:develop
|
yarn release:develop
|
||||||
|
|
||||||
- name: Build/release Docker images
|
- name: Build/release Docker images
|
||||||
run: |
|
run: |
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
||||||
yarn build:docker:develop
|
yarn build:docker:develop
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -2,55 +2,55 @@ name: Budibase Release
|
||||||
concurrency: release
|
concurrency: release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- ".aws/**"
|
- '.aws/**'
|
||||||
- ".github/**"
|
- '.github/**'
|
||||||
- "charts/**"
|
- 'charts/**'
|
||||||
- "packages/**"
|
- 'packages/**'
|
||||||
- "scripts/**"
|
- 'scripts/**'
|
||||||
- "package.json"
|
- 'package.json'
|
||||||
- "yarn.lock"
|
- 'yarn.lock'
|
||||||
- "package.json"
|
- 'package.json'
|
||||||
- "yarn.lock"
|
- 'yarn.lock'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
versioning:
|
versioning:
|
||||||
type: choice
|
type: choice
|
||||||
description: "Versioning type: patch, minor, major"
|
description: "Versioning type: patch, minor, major"
|
||||||
default: patch
|
default: patch
|
||||||
options:
|
options:
|
||||||
- patch
|
- patch
|
||||||
- minor
|
- minor
|
||||||
- major
|
- major
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Posthog token used by ui at build time
|
# Posthog token used by ui at build time
|
||||||
POSTHOG_TOKEN: phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
POSTHOG_TOKEN: phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN : ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release-images:
|
release-images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Fail if branch is not master
|
- name: Fail if branch is not master
|
||||||
if: github.ref != 'refs/heads/master'
|
if: github.ref != 'refs/heads/master'
|
||||||
run: |
|
run: |
|
||||||
echo "Ref is not master, you must run this job from master."
|
echo "Ref is not master, you must run this job from master."
|
||||||
exit 1
|
exit 1
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 14.x
|
||||||
|
|
||||||
|
- name: Install Pro
|
||||||
|
run: yarn install:pro master
|
||||||
|
|
||||||
- run: yarn
|
- run: yarn
|
||||||
- run: yarn bootstrap
|
- run: yarn bootstrap
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
@ -68,14 +68,13 @@ jobs:
|
||||||
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
||||||
yarn release
|
yarn release
|
||||||
|
|
||||||
- name: "Get Previous tag"
|
- name: 'Get Previous tag'
|
||||||
id: previoustag
|
id: previoustag
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
|
|
||||||
- name: Build/release Docker images
|
- name: Build/release Docker images
|
||||||
run: |
|
run: |
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
||||||
yarn build
|
|
||||||
yarn build:docker
|
yarn build:docker
|
||||||
env:
|
env:
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
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)):
|
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 -->
|
<a href="https://github.com/Budibase/budibase/graphs/contributors">
|
||||||
<!-- prettier-ignore-end -->
|
<img src="https://contrib.rocks/image?repo=Budibase/budibase" />
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
Made with [contrib.rocks](https://contrib.rocks).
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ metadata:
|
||||||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
|
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
|
||||||
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
|
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.sslPolicy }}
|
||||||
|
alb.ingress.kubernetes.io/actions.ssl-policy: {{ .Values.ingress.sslPolicy }}
|
||||||
|
{{- end }}
|
||||||
{{- if .Values.ingress.securityGroups }}
|
{{- if .Values.ingress.securityGroups }}
|
||||||
alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }}
|
alb.ingress.kubernetes.io/security-groups: {{ .Values.ingress.securityGroups }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
## Dev Environment on Debian 11
|
## Dev Environment on Debian 11
|
||||||
|
|
||||||
### Install NVM & Node 14
|
### Install NVM & Node 14
|
||||||
|
|
||||||
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
|
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
|
||||||
|
|
||||||
Install NVM
|
Install NVM
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
Install Node 14
|
Install Node 14
|
||||||
|
|
||||||
```
|
```
|
||||||
nvm install 14
|
nvm install 14
|
||||||
```
|
```
|
||||||
|
@ -21,16 +17,13 @@ nvm install 14
|
||||||
```
|
```
|
||||||
npm install -g yarn jest lerna
|
npm install -g yarn jest lerna
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Docker and Docker Compose
|
### Install Docker and Docker Compose
|
||||||
|
|
||||||
```
|
```
|
||||||
apt install docker.io
|
apt install docker.io
|
||||||
pip3 install docker-compose
|
pip3 install docker-compose
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clone the repo
|
### Clone the repo
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/Budibase/budibase.git
|
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
|
cd budibase
|
||||||
yarn setup
|
yarn setup
|
||||||
```
|
```
|
||||||
|
|
||||||
The yarn setup command runs several build steps i.e.
|
The yarn setup command runs several build steps i.e.
|
||||||
|
|
||||||
```
|
```
|
||||||
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
|
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.
|
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.
|
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
|
http://127.0.0.1:10000/builder/admin
|
||||||
|
|
||||||
### File descriptor issues with Vite and Chrome in Linux
|
### 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.
|
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.
|
To fix this, apply the following tweaks.
|
||||||
|
|
||||||
|
@ -73,4 +62,4 @@ Debian based distros:
|
||||||
Add `* - nofile 65536` to `/etc/security/limits.conf`.
|
Add `* - nofile 65536` to `/etc/security/limits.conf`.
|
||||||
|
|
||||||
Arch:
|
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/)
|
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
|
| **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
|
`eval $(/opt/homebrew/bin/brew shellenv)` line to your `.zshrc`. This will make your zsh to find the apps you install
|
||||||
through brew.
|
through brew.
|
||||||
|
|
||||||
|
|
||||||
### Install Node
|
### Install Node
|
||||||
|
|
||||||
Budibase requires a recent version of node 14:
|
Budibase requires a recent version of node 14:
|
||||||
|
|
||||||
```
|
```
|
||||||
brew install node npm
|
brew install node npm
|
||||||
node -v
|
node -v
|
||||||
|
@ -22,15 +22,12 @@ node -v
|
||||||
```
|
```
|
||||||
npm install -g yarn jest lerna
|
npm install -g yarn jest lerna
|
||||||
```
|
```
|
||||||
|
|
||||||
### Install Docker and Docker Compose
|
### Install Docker and Docker Compose
|
||||||
|
|
||||||
```
|
```
|
||||||
brew install docker docker-compose
|
brew install docker docker-compose
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clone the repo
|
### Clone the repo
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/Budibase/budibase.git
|
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
|
cd budibase
|
||||||
yarn setup
|
yarn setup
|
||||||
```
|
```
|
||||||
|
|
||||||
The yarn setup command runs several build steps i.e.
|
The yarn setup command runs several build steps i.e.
|
||||||
|
|
||||||
```
|
```
|
||||||
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
|
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.
|
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.
|
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
|
| **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)
|
[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.
|
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)
|
## Dev Environment on Windows 10/11 (WSL2)
|
||||||
|
|
||||||
|
|
||||||
### Install WSL with Ubuntu LTS
|
### Install WSL with Ubuntu LTS
|
||||||
|
|
||||||
Enable WSL 2 on Windows 10/11 for docker support.
|
Enable WSL 2 on Windows 10/11 for docker support.
|
||||||
|
|
||||||
```
|
```
|
||||||
wsl --set-default-version 2
|
wsl --set-default-version 2
|
||||||
```
|
```
|
||||||
|
|
||||||
Install Ubuntu LTS.
|
Install Ubuntu LTS.
|
||||||
|
|
||||||
```
|
```
|
||||||
wsl --install Ubuntu
|
wsl --install Ubuntu
|
||||||
```
|
```
|
||||||
|
@ -18,7 +16,6 @@ Or follow the instruction here:
|
||||||
https://learn.microsoft.com/en-us/windows/wsl/install
|
https://learn.microsoft.com/en-us/windows/wsl/install
|
||||||
|
|
||||||
### Install Docker in windows
|
### Install Docker in windows
|
||||||
|
|
||||||
Download the installer from docker and install it.
|
Download the installer from docker and install it.
|
||||||
|
|
||||||
Check this url for more detailed instructions:
|
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.
|
You should follow the next steps from within the Ubuntu terminal.
|
||||||
|
|
||||||
### Install NVM & Node 14
|
### Install NVM & Node 14
|
||||||
|
|
||||||
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
|
NVM documentation: https://github.com/nvm-sh/nvm#installing-and-updating
|
||||||
|
|
||||||
Install NVM
|
Install NVM
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
Install Node 14
|
Install Node 14
|
||||||
|
|
||||||
```
|
```
|
||||||
nvm install 14
|
nvm install 14
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Install npm requirements
|
### Install npm requirements
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -49,7 +43,6 @@ npm install -g yarn jest lerna
|
||||||
```
|
```
|
||||||
|
|
||||||
### Clone the repo
|
### Clone the repo
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/Budibase/budibase.git
|
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
|
cd budibase
|
||||||
yarn setup
|
yarn setup
|
||||||
```
|
```
|
||||||
|
|
||||||
The yarn setup command runs several build steps i.e.
|
The yarn setup command runs several build steps i.e.
|
||||||
|
|
||||||
```
|
```
|
||||||
node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev
|
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.
|
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.
|
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
|
http://127.0.0.1:10000/builder/admin
|
||||||
|
|
||||||
### Working with the code
|
### 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.
|
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
|
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",
|
"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,
|
"useWorkspaces": true,
|
||||||
|
"packages": ["packages/*"],
|
||||||
"command": {
|
"command": {
|
||||||
"publish": {
|
"publish": {
|
||||||
"ignoreChanges": [
|
"ignoreChanges": [
|
||||||
|
|
32
package.json
32
package.json
|
@ -11,7 +11,7 @@
|
||||||
"eslint": "^7.28.0",
|
"eslint": "^7.28.0",
|
||||||
"eslint-plugin-cypress": "^2.11.3",
|
"eslint-plugin-cypress": "^2.11.3",
|
||||||
"eslint-plugin-svelte3": "^3.2.0",
|
"eslint-plugin-svelte3": "^3.2.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^7.0.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"kill-port": "^1.6.1",
|
"kill-port": "^1.6.1",
|
||||||
"lerna": "^6.6.1",
|
"lerna": "^6.6.1",
|
||||||
|
@ -24,16 +24,18 @@
|
||||||
"typescript": "4.7.3"
|
"typescript": "4.7.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"setup": "git config submodule.recurse true && git submodule update && node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
|
"setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev",
|
||||||
"bootstrap": "./scripts/bootstrap.sh && lerna link && ./scripts/link-dependencies.sh",
|
"bootstrap": "lerna link && ./scripts/link-dependencies.sh",
|
||||||
"build": "lerna run --stream build",
|
"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:bootstrap": "./scripts/scopeBackend.sh && yarn run bootstrap",
|
||||||
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
|
"backend:build": "./scripts/scopeBackend.sh 'lerna run --stream build'",
|
||||||
"build:sdk": "lerna run --stream build:sdk",
|
"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",
|
"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": "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: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",
|
"restore": "yarn run clean && yarn run bootstrap && yarn run build",
|
||||||
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
"nuke": "yarn run nuke:packages && yarn run nuke:docker",
|
||||||
"nuke:packages": "yarn run restore",
|
"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": "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: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: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": "lerna run --stream test --stream",
|
||||||
|
"test:pro": "bash scripts/pro/test.sh",
|
||||||
"lint:eslint": "eslint packages && eslint qa-core",
|
"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: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",
|
"lint": "yarn run lint:eslint && yarn run lint:prettier",
|
||||||
|
@ -82,25 +85,12 @@
|
||||||
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
"mode:account": "yarn mode:cloud && yarn env:account:enable",
|
||||||
"security:audit": "node scripts/audit.js",
|
"security:audit": "node scripts/audit.js",
|
||||||
"postinstall": "husky install",
|
"postinstall": "husky install",
|
||||||
"dep:clean": "yarn clean -y && yarn bootstrap",
|
"install:pro": "bash scripts/pro/install.sh",
|
||||||
"submodules:load": "git submodule init && git submodule update && yarn && yarn bootstrap",
|
"dep:clean": "yarn clean && yarn bootstrap"
|
||||||
"submodules:unload": "git submodule deinit --all && yarn && yarn bootstrap"
|
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/backend-core",
|
"packages/*"
|
||||||
"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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"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",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -15,6 +15,8 @@
|
||||||
"prebuild": "rimraf dist/",
|
"prebuild": "rimraf dist/",
|
||||||
"prepack": "cp package.json dist",
|
"prepack": "cp package.json dist",
|
||||||
"build": "tsc -p tsconfig.build.json",
|
"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",
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||||
"test": "bash scripts/test.sh",
|
"test": "bash scripts/test.sh",
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
|
@ -22,7 +24,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.2",
|
"@budibase/nano": "10.1.2",
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.10",
|
"@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",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-cloudfront-sign": "2.2.0",
|
"aws-cloudfront-sign": "2.2.0",
|
||||||
|
|
|
@ -14,6 +14,7 @@ export enum ViewName {
|
||||||
USER_BY_APP = "by_app",
|
USER_BY_APP = "by_app",
|
||||||
USER_BY_EMAIL = "by_email2",
|
USER_BY_EMAIL = "by_email2",
|
||||||
BY_API_KEY = "by_api_key",
|
BY_API_KEY = "by_api_key",
|
||||||
|
/** @deprecated - could be deleted */
|
||||||
USER_BY_BUILDERS = "by_builders",
|
USER_BY_BUILDERS = "by_builders",
|
||||||
LINK = "by_link",
|
LINK = "by_link",
|
||||||
ROUTING = "screen_routes",
|
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,
|
tenantId: string | null,
|
||||||
task: any
|
task: () => T
|
||||||
): Promise<any> {
|
): Promise<T> {
|
||||||
// make sure default always selected in single tenancy
|
// make sure default always selected in single tenancy
|
||||||
if (!env.MULTI_TENANCY) {
|
if (!env.MULTI_TENANCY) {
|
||||||
tenantId = tenantId || DEFAULT_TENANT_ID
|
tenantId = tenantId || DEFAULT_TENANT_ID
|
||||||
|
|
|
@ -243,7 +243,7 @@ export class QueryBuilder<T> {
|
||||||
}
|
}
|
||||||
// Escape characters
|
// Escape characters
|
||||||
if (!this.#noEscaping && escape && originalType === "string") {
|
if (!this.#noEscaping && escape && originalType === "string") {
|
||||||
value = `${value}`.replace(/[ #+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
|
value = `${value}`.replace(/[ \/#+\-&|!(){}\]^"~*?:\\]/g, "\\$&")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap in quotes
|
// Wrap in quotes
|
||||||
|
@ -320,6 +320,18 @@ export class QueryBuilder<T> {
|
||||||
return `${key}:(${statement})`
|
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 notContains = (key: string, value: any) => {
|
||||||
const allPrefix = allOr ? "*:* AND " : ""
|
const allPrefix = allOr ? "*:* AND " : ""
|
||||||
const mode = allOr ? "AND" : undefined
|
const mode = allOr ? "AND" : undefined
|
||||||
|
@ -408,17 +420,7 @@ export class QueryBuilder<T> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (this.#query.fuzzy) {
|
if (this.#query.fuzzy) {
|
||||||
build(this.#query.fuzzy, (key: string, value: any) => {
|
build(this.#query.fuzzy, fuzzy)
|
||||||
if (!value) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
value = builder.preprocess(value, {
|
|
||||||
escape: true,
|
|
||||||
lowercase: true,
|
|
||||||
type: "fuzzy",
|
|
||||||
})
|
|
||||||
return `${key}:${value}~`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if (this.#query.equal) {
|
if (this.#query.equal) {
|
||||||
build(this.#query.equal, equal)
|
build(this.#query.equal, equal)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
} from "../constants"
|
} from "../constants"
|
||||||
import { getGlobalDB } from "../context"
|
import { getGlobalDB } from "../context"
|
||||||
import { doWithDB } from "./"
|
import { doWithDB } from "./"
|
||||||
import { Database, DatabaseQueryOpts } from "@budibase/types"
|
import { AllDocsResponse, Database, DatabaseQueryOpts } from "@budibase/types"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
|
||||||
const DESIGN_DB = "_design/database"
|
const DESIGN_DB = "_design/database"
|
||||||
|
@ -119,6 +119,34 @@ export interface QueryViewOptions {
|
||||||
arrayResponse?: boolean
|
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>(
|
export const queryView = async <T>(
|
||||||
viewName: ViewName,
|
viewName: ViewName,
|
||||||
params: DatabaseQueryOpts,
|
params: DatabaseQueryOpts,
|
||||||
|
@ -126,34 +154,18 @@ export const queryView = async <T>(
|
||||||
createFunc: any,
|
createFunc: any,
|
||||||
opts?: QueryViewOptions
|
opts?: QueryViewOptions
|
||||||
): Promise<T[] | T | undefined> => {
|
): Promise<T[] | T | undefined> => {
|
||||||
try {
|
const response = await queryViewRaw<T>(viewName, params, db, createFunc, opts)
|
||||||
let response = await db.query<T>(`database/${viewName}`, params)
|
const rows = response.rows
|
||||||
const rows = response.rows
|
const docs = rows.map((row: any) =>
|
||||||
const docs = rows.map((row: any) =>
|
params.include_docs ? row.doc : row.value
|
||||||
params.include_docs ? row.doc : row.value
|
)
|
||||||
)
|
|
||||||
|
|
||||||
// if arrayResponse has been requested, always return array regardless of length
|
// if arrayResponse has been requested, always return array regardless of length
|
||||||
if (opts?.arrayResponse) {
|
if (opts?.arrayResponse) {
|
||||||
return docs as T[]
|
return docs as T[]
|
||||||
} else {
|
} else {
|
||||||
// return the single document if there is only one
|
// return the single document if there is only one
|
||||||
return docs.length <= 1 ? (docs[0] as T) : (docs as T[])
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>(
|
export const queryGlobalView = async <T>(
|
||||||
viewName: ViewName,
|
viewName: ViewName,
|
||||||
params: DatabaseQueryOpts,
|
params: DatabaseQueryOpts,
|
||||||
db?: Database,
|
db?: Database,
|
||||||
opts?: QueryViewOptions
|
opts?: QueryViewOptions
|
||||||
): Promise<T[] | T | undefined> => {
|
): 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
|
// can pass DB in if working with something specific
|
||||||
if (!db) {
|
if (!db) {
|
||||||
db = getGlobalDB()
|
db = getGlobalDB()
|
||||||
|
@ -227,3 +240,13 @@ export const queryGlobalView = async <T>(
|
||||||
const createFn = CreateFuncByName[viewName]
|
const createFn = CreateFuncByName[viewName]
|
||||||
return queryView(viewName, params, db!, createFn, opts)
|
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() {
|
function isTest() {
|
||||||
return isCypress() || isJest()
|
return isCypress() || isJest()
|
||||||
}
|
}
|
||||||
|
@ -45,6 +47,35 @@ function httpLogging() {
|
||||||
return process.env.HTTP_LOGGING
|
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 = {
|
const environment = {
|
||||||
isTest,
|
isTest,
|
||||||
isJest,
|
isJest,
|
||||||
|
@ -122,6 +153,7 @@ const environment = {
|
||||||
ENABLE_SSO_MAINTENANCE_MODE: selfHosted
|
ENABLE_SSO_MAINTENANCE_MODE: selfHosted
|
||||||
? process.env.ENABLE_SSO_MAINTENANCE_MODE
|
? process.env.ENABLE_SSO_MAINTENANCE_MODE
|
||||||
: false,
|
: false,
|
||||||
|
VERSION: findVersion(),
|
||||||
_set(key: any, value: any) {
|
_set(key: any, value: any) {
|
||||||
process.env[key] = value
|
process.env[key] = value
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -23,8 +23,6 @@ import * as installation from "../installation"
|
||||||
import * as configs from "../configs"
|
import * as configs from "../configs"
|
||||||
import { withCache, TTL, CacheKey } from "../cache/generic"
|
import { withCache, TTL, CacheKey } from "../cache/generic"
|
||||||
|
|
||||||
const pkg = require("../../package.json")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An identity can be:
|
* An identity can be:
|
||||||
* - account user (Self host)
|
* - account user (Self host)
|
||||||
|
@ -102,7 +100,7 @@ const identifyInstallationGroup = async (
|
||||||
const id = installId
|
const id = installId
|
||||||
const type = IdentityType.INSTALLATION
|
const type = IdentityType.INSTALLATION
|
||||||
const hosting = getHostingFromEnv()
|
const hosting = getHostingFromEnv()
|
||||||
const version = pkg.version
|
const version = env.VERSION
|
||||||
const environment = getDeploymentEnvironment()
|
const environment = getDeploymentEnvironment()
|
||||||
|
|
||||||
const group: InstallationGroup = {
|
const group: InstallationGroup = {
|
||||||
|
@ -306,4 +304,5 @@ export default {
|
||||||
identify,
|
identify,
|
||||||
identifyGroup,
|
identifyGroup,
|
||||||
getInstallationId,
|
getInstallationId,
|
||||||
|
getUniqueTenantId,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { EventProcessor } from "../types"
|
||||||
import env from "../../../environment"
|
import env from "../../../environment"
|
||||||
import * as context from "../../../context"
|
import * as context from "../../../context"
|
||||||
import * as rateLimiting from "./rateLimiting"
|
import * as rateLimiting from "./rateLimiting"
|
||||||
const pkg = require("../../../../package.json")
|
|
||||||
|
|
||||||
const EXCLUDED_EVENTS: Event[] = [
|
const EXCLUDED_EVENTS: Event[] = [
|
||||||
Event.USER_UPDATED,
|
Event.USER_UPDATED,
|
||||||
|
@ -49,7 +48,7 @@ export default class PosthogProcessor implements EventProcessor {
|
||||||
|
|
||||||
properties = this.clearPIIProperties(properties)
|
properties = this.clearPIIProperties(properties)
|
||||||
|
|
||||||
properties.version = pkg.version
|
properties.version = env.VERSION
|
||||||
properties.service = env.SERVICE
|
properties.service = env.SERVICE
|
||||||
properties.environment = identity.environment
|
properties.environment = identity.environment
|
||||||
properties.hosting = identity.hosting
|
properties.hosting = identity.hosting
|
||||||
|
|
|
@ -6,8 +6,7 @@ import { Installation, IdentityType, Database } from "@budibase/types"
|
||||||
import * as context from "./context"
|
import * as context from "./context"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
import { bustCache, withCache, TTL, CacheKey } from "./cache/generic"
|
import { bustCache, withCache, TTL, CacheKey } from "./cache/generic"
|
||||||
|
import environment from "./environment"
|
||||||
const pkg = require("../package.json")
|
|
||||||
|
|
||||||
export const getInstall = async (): Promise<Installation> => {
|
export const getInstall = async (): Promise<Installation> => {
|
||||||
return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, {
|
return withCache(CacheKey.INSTALLATION, TTL.ONE_DAY, getInstallFromDB, {
|
||||||
|
@ -18,7 +17,7 @@ async function createInstallDoc(platformDb: Database) {
|
||||||
const install: Installation = {
|
const install: Installation = {
|
||||||
_id: StaticDatabases.PLATFORM_INFO.docs.install,
|
_id: StaticDatabases.PLATFORM_INFO.docs.install,
|
||||||
installId: newid(),
|
installId: newid(),
|
||||||
version: pkg.version,
|
version: environment.VERSION,
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resp = await platformDb.put(install)
|
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(
|
return doWithDB(
|
||||||
StaticDatabases.PLATFORM_INFO.name,
|
StaticDatabases.PLATFORM_INFO.name,
|
||||||
async (platformDb: any) => {
|
async (platformDb: any) => {
|
||||||
|
@ -80,7 +79,7 @@ export const checkInstallVersion = async (): Promise<void> => {
|
||||||
const install = await getInstall()
|
const install = await getInstall()
|
||||||
|
|
||||||
const currentVersion = install.version
|
const currentVersion = install.version
|
||||||
const newVersion = pkg.version
|
const newVersion = environment.VERSION
|
||||||
|
|
||||||
if (currentVersion !== newVersion) {
|
if (currentVersion !== newVersion) {
|
||||||
const isUpgrade = semver.gt(newVersion, currentVersion)
|
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
|
// check both the primary and the fallback internal api keys
|
||||||
// this allows for rotation
|
// this allows for rotation
|
||||||
if (isValidInternalAPIKey(apiKey)) {
|
if (isValidInternalAPIKey(apiKey)) {
|
||||||
return { valid: true }
|
return { valid: true, user: undefined }
|
||||||
}
|
}
|
||||||
const decrypted = decrypt(apiKey)
|
const decrypted = decrypt(apiKey)
|
||||||
const tenantId = decrypted.split(SEPARATOR)[0]
|
const tenantId = decrypted.split(SEPARATOR)[0]
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
import Redlock, { Options } from "redlock"
|
import Redlock from "redlock"
|
||||||
import { getLockClient } from "./init"
|
import { getLockClient } from "./init"
|
||||||
import { LockOptions, LockType } from "@budibase/types"
|
import { LockOptions, LockType } from "@budibase/types"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import env from "../environment"
|
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) {
|
if (env.isTest() && type !== LockType.TRY_ONCE) {
|
||||||
return newRedlock(OPTIONS.TEST)
|
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 }
|
let options = { ...OPTIONS.DEFAULT, ...opts }
|
||||||
const redisWrapper = await getLockClient()
|
const redisWrapper = await getLockClient()
|
||||||
const client = redisWrapper.getClient()
|
const client = redisWrapper.getClient()
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import {
|
import {
|
||||||
ViewName,
|
|
||||||
getUsersByAppParams,
|
|
||||||
getProdAppID,
|
|
||||||
generateAppUserID,
|
|
||||||
queryGlobalView,
|
|
||||||
UNICODE_MAX,
|
|
||||||
DocumentType,
|
|
||||||
SEPARATOR,
|
|
||||||
directCouchFind,
|
directCouchFind,
|
||||||
|
DocumentType,
|
||||||
|
generateAppUserID,
|
||||||
getGlobalUserParams,
|
getGlobalUserParams,
|
||||||
|
getProdAppID,
|
||||||
|
getUsersByAppParams,
|
||||||
pagination,
|
pagination,
|
||||||
|
queryGlobalView,
|
||||||
|
queryGlobalViewRaw,
|
||||||
|
SEPARATOR,
|
||||||
|
UNICODE_MAX,
|
||||||
|
ViewName,
|
||||||
} from "./db"
|
} from "./db"
|
||||||
import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types"
|
import { BulkDocsResponse, SearchUsersRequest, User } from "@budibase/types"
|
||||||
import { getGlobalDB } from "./context"
|
import { getGlobalDB } from "./context"
|
||||||
|
@ -239,3 +240,11 @@ export const paginatedUsers = async ({
|
||||||
getKey,
|
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
|
// search prod apps for a url that matches
|
||||||
const apps: App[] = await context.doInTenant(tenantId, () =>
|
const apps: App[] = await context.doInTenant(
|
||||||
getAllApps({ dev: false })
|
tenantId,
|
||||||
|
() => getAllApps({ dev: false }) as Promise<App[]>
|
||||||
)
|
)
|
||||||
const app = apps.filter(
|
const app = apps.filter(
|
||||||
a => a.url && a.url.toLowerCase() === possibleAppUrl
|
a => a.url && a.url.toLowerCase() === possibleAppUrl
|
||||||
|
@ -221,27 +222,6 @@ export function isClient(ctx: Ctx) {
|
||||||
return ctx.headers[Header.TYPE] === "client"
|
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) {
|
export function timeout(timeMs: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, timeMs))
|
return new Promise(resolve => setTimeout(resolve, timeMs))
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,5 +2,5 @@ export * as mocks from "./mocks"
|
||||||
export * as structures from "./structures"
|
export * as structures from "./structures"
|
||||||
export { generator } from "./structures"
|
export { generator } from "./structures"
|
||||||
export * as testContainerUtils from "./testContainerUtils"
|
export * as testContainerUtils from "./testContainerUtils"
|
||||||
|
export * as utils from "./utils"
|
||||||
export * from "./jestUtils"
|
export * from "./jestUtils"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import * as events from "../../../../src/events"
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const processors = await import("../../../../src/events/processors")
|
const processors = await import("../../../../src/events/processors")
|
||||||
const events = await import("../../../../src/events")
|
const events = await import("../../../../src/events")
|
||||||
|
@ -120,4 +122,13 @@ beforeAll(async () => {
|
||||||
jest.spyOn(events.plugin, "init")
|
jest.spyOn(events.plugin, "init")
|
||||||
jest.spyOn(events.plugin, "imported")
|
jest.spyOn(events.plugin, "imported")
|
||||||
jest.spyOn(events.plugin, "deleted")
|
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 { generateGlobalUserID } from "../../../../src/docIds"
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
|
@ -28,6 +28,7 @@ export const account = (): Account => {
|
||||||
name: generator.name(),
|
name: generator.name(),
|
||||||
size: "10+",
|
size: "10+",
|
||||||
profession: "Software Engineer",
|
profession: "Software Engineer",
|
||||||
|
quotaUsage: quotas.usage(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
import Chance from "chance"
|
import Chance from "./Chance"
|
||||||
export const generator = new Chance()
|
export const generator = new Chance()
|
||||||
|
|
|
@ -11,3 +11,4 @@ export * as users from "./users"
|
||||||
export * as userGroups from "./userGroups"
|
export * as userGroups from "./userGroups"
|
||||||
export { generator } from "./generator"
|
export { generator } from "./generator"
|
||||||
export * as scim from "./scim"
|
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 {
|
return {
|
||||||
type,
|
type,
|
||||||
|
usesInvoicing: false,
|
||||||
|
minUsers: 1,
|
||||||
|
model: PlanModel.PER_USER,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const newLicense = (opts: {
|
export function quotas(): Quotas {
|
||||||
quotas: Quotas
|
|
||||||
planType?: PlanType
|
|
||||||
}): License => {
|
|
||||||
return {
|
return {
|
||||||
features: [],
|
usage: {
|
||||||
quotas: opts.quotas,
|
monthly: {
|
||||||
plan: newPlan(opts.planType),
|
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,
|
"incremental": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"types": [ "node", "jest" ],
|
"types": ["node", "jest"],
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"skipLibCheck": true
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["**/*.js", "**/*.ts"],
|
||||||
"**/*.js",
|
|
||||||
"**/*.ts",
|
|
||||||
"package.json"
|
|
||||||
],
|
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
"dist",
|
"dist",
|
||||||
|
@ -26,4 +22,4 @@
|
||||||
"**/*.spec.js",
|
"**/*.spec.js",
|
||||||
"__mocks__"
|
"__mocks__"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,11 @@
|
||||||
"@budibase/types": ["../types/src"]
|
"@budibase/types": ["../types/src"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"references": [
|
||||||
"exclude": ["node_modules", "dist"]
|
{ "path": "../types" }
|
||||||
}
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"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",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,8 +38,8 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "1.2.1",
|
||||||
"@budibase/shared-core": "2.5.6-alpha.6",
|
"@budibase/shared-core": "2.5.6-alpha.28",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.6",
|
"@budibase/string-templates": "2.5.6-alpha.28",
|
||||||
"@spectrum-css/accordion": "3.0.24",
|
"@spectrum-css/accordion": "3.0.24",
|
||||||
"@spectrum-css/actionbutton": "1.0.1",
|
"@spectrum-css/actionbutton": "1.0.1",
|
||||||
"@spectrum-css/actiongroup": "1.0.1",
|
"@spectrum-css/actiongroup": "1.0.1",
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
"@spectrum-css/vars": "3.0.1",
|
"@spectrum-css/vars": "3.0.1",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"easymde": "^2.16.1",
|
"easymde": "^2.16.1",
|
||||||
"svelte-flatpickr": "^3.2.3",
|
"svelte-flatpickr": "^3.3.2",
|
||||||
"svelte-portal": "^1.0.0"
|
"svelte-portal": "^1.0.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-Icon--size{size}"
|
class="spectrum-Icon spectrum-Icon--sizeS"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
aria-label={icon}
|
aria-label={icon}
|
||||||
|
|
|
@ -6,6 +6,9 @@ let clickHandlers = []
|
||||||
*/
|
*/
|
||||||
const handleClick = event => {
|
const handleClick = event => {
|
||||||
// Ignore click if this is an ignored class
|
// Ignore click if this is an ignored class
|
||||||
|
if (event.target.closest('[data-ignore-click-outside="true"]')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
for (let className of ignoredClasses) {
|
for (let className of ignoredClasses) {
|
||||||
if (event.target.closest(className)) {
|
if (event.target.closest(className)) {
|
||||||
return
|
return
|
||||||
|
@ -29,6 +32,7 @@ const handleClick = event => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
document.documentElement.addEventListener("click", handleClick, true)
|
document.documentElement.addEventListener("click", handleClick, true)
|
||||||
|
document.documentElement.addEventListener("contextmenu", handleClick, true)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds or updates a click handler
|
* Adds or updates a click handler
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container" class:compact>
|
||||||
{#if selectedImage}
|
{#if selectedImage}
|
||||||
{#if gallery}
|
{#if gallery}
|
||||||
<div class="gallery">
|
<div class="gallery">
|
||||||
|
@ -355,6 +355,9 @@
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.compact .spectrum-Dropzone {
|
||||||
|
padding: 6px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.gallery {
|
.gallery {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -379,6 +382,17 @@
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin: 20px 30px;
|
margin: 20px 30px;
|
||||||
}
|
}
|
||||||
|
.compact .placeholder,
|
||||||
|
.compact img {
|
||||||
|
margin: 10px 16px;
|
||||||
|
}
|
||||||
|
.compact img {
|
||||||
|
height: 90px;
|
||||||
|
}
|
||||||
|
.compact .gallery {
|
||||||
|
padding: 6px 10px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -447,6 +461,13 @@
|
||||||
.disabled .spectrum-Heading--sizeL {
|
.disabled .spectrum-Heading--sizeL {
|
||||||
color: var(--spectrum-alias-text-color-disabled);
|
color: var(--spectrum-alias-text-color-disabled);
|
||||||
}
|
}
|
||||||
|
.compact .spectrum-Dropzone {
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
.compact .spectrum-IllustratedMessage-description {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
|
@ -20,12 +20,13 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: selectedLookupMap = getSelectedLookupMap(value)
|
$: arrayValue = Array.isArray(value) ? value : [value].filter(x => !!x)
|
||||||
|
$: selectedLookupMap = getSelectedLookupMap(arrayValue)
|
||||||
$: optionLookupMap = getOptionLookupMap(options)
|
$: optionLookupMap = getOptionLookupMap(options)
|
||||||
|
|
||||||
$: fieldText = getFieldText(value, optionLookupMap, placeholder)
|
$: fieldText = getFieldText(arrayValue, optionLookupMap, placeholder)
|
||||||
$: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true
|
$: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true
|
||||||
$: toggleOption = makeToggleOption(selectedLookupMap, value)
|
$: toggleOption = makeToggleOption(selectedLookupMap, arrayValue)
|
||||||
|
|
||||||
const getFieldText = (value, map, placeholder) => {
|
const getFieldText = (value, map, placeholder) => {
|
||||||
if (Array.isArray(value) && value.length > 0) {
|
if (Array.isArray(value) && value.length > 0) {
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
{readonly}
|
{readonly}
|
||||||
{fieldText}
|
{fieldText}
|
||||||
{options}
|
{options}
|
||||||
isPlaceholder={!value?.length}
|
isPlaceholder={!arrayValue.length}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
bind:fetchTerm
|
bind:fetchTerm
|
||||||
{useFetch}
|
{useFetch}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let gallery = true
|
export let gallery = true
|
||||||
export let fileTags = []
|
export let fileTags = []
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
|
export let compact = false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
{gallery}
|
{gallery}
|
||||||
{fileTags}
|
{fileTags}
|
||||||
{maximum}
|
{maximum}
|
||||||
|
{compact}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
export let message = ""
|
export let message = ""
|
||||||
export let onConfirm = undefined
|
export let onConfirm = undefined
|
||||||
export let buttonText = ""
|
export let buttonText = ""
|
||||||
|
export let cta = false
|
||||||
$: icon = selectIcon(type)
|
$: icon = selectIcon(type)
|
||||||
// if newlines used, convert them to different elements
|
// if newlines used, convert them to different elements
|
||||||
$: split = message.split("\n")
|
$: split = message.split("\n")
|
||||||
|
@ -41,7 +41,9 @@
|
||||||
{/each}
|
{/each}
|
||||||
{#if onConfirm}
|
{#if onConfirm}
|
||||||
<div class="spectrum-InLineAlert-footer button">
|
<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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -57,7 +59,6 @@
|
||||||
--spectrum-semantic-negative-icon-color: #e34850;
|
--spectrum-semantic-negative-icon-color: #e34850;
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-color: var(--spectrum-global-color-gray-400);
|
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
export let wide = false
|
export let wide = false
|
||||||
export let narrow = false
|
export let narrow = false
|
||||||
|
export let narrower = false
|
||||||
export let noPadding = false
|
export let noPadding = false
|
||||||
|
|
||||||
let sidePanelVisble = false
|
let sidePanelVisble = false
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<div class="content" class:wide class:noPadding class:narrow>
|
<div class="content" class:wide class:noPadding class:narrow class:narrower>
|
||||||
<slot />
|
<slot />
|
||||||
<div class="fix-scroll-padding" />
|
<div class="fix-scroll-padding" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,6 +71,9 @@
|
||||||
.content.narrow {
|
.content.narrow {
|
||||||
max-width: 840px;
|
max-width: 840px;
|
||||||
}
|
}
|
||||||
|
.content.narrower {
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
#side-panel {
|
#side-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
export let dismissible = true
|
export let dismissible = true
|
||||||
export let offset = 5
|
export let offset = 5
|
||||||
export let customHeight
|
export let customHeight
|
||||||
|
export let animate = true
|
||||||
|
|
||||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
class="spectrum-Popover is-open"
|
class="spectrum-Popover is-open"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
style="height: {customHeight}"
|
style="height: {customHeight}"
|
||||||
transition:fly|local={{ y: -20, duration: 200 }}
|
transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { writable } from "svelte/store"
|
||||||
export const BANNER_TYPES = {
|
export const BANNER_TYPES = {
|
||||||
INFO: "info",
|
INFO: "info",
|
||||||
NEGATIVE: "negative",
|
NEGATIVE: "negative",
|
||||||
|
WARNING: "warning",
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBannerStore() {
|
export function createBannerStore() {
|
||||||
|
@ -38,7 +39,8 @@ export function createBannerStore() {
|
||||||
const queue = async entries => {
|
const queue = async entries => {
|
||||||
const priority = {
|
const priority = {
|
||||||
[BANNER_TYPES.NEGATIVE]: 0,
|
[BANNER_TYPES.NEGATIVE]: 0,
|
||||||
[BANNER_TYPES.INFO]: 1,
|
[BANNER_TYPES.WARNING]: 1,
|
||||||
|
[BANNER_TYPES.INFO]: 2,
|
||||||
}
|
}
|
||||||
banner.update(store => {
|
banner.update(store => {
|
||||||
const sorted = [...store.messages, ...entries].sort((a, b) => {
|
const sorted = [...store.messages, ...entries].sort((a, b) => {
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
|
|
||||||
const displayLimit = 5
|
const displayLimit = 5
|
||||||
|
|
||||||
$: badges = Array.isArray(value) ? value.slice(0, displayLimit) : []
|
$: arrayValue = Array.isArray(value) ? value : [value].filter(x => !!x)
|
||||||
$: leftover = (value?.length ?? 0) - badges.length
|
$: badges = arrayValue.slice(0, displayLimit)
|
||||||
|
$: leftover = arrayValue.length - badges.length
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each badges as badge}
|
{#each badges as badge}
|
||||||
|
|
|
@ -143,7 +143,7 @@
|
||||||
}
|
}
|
||||||
fields?.forEach(field => {
|
fields?.forEach(field => {
|
||||||
const fieldSchema = schema[field]
|
const fieldSchema = schema[field]
|
||||||
if (fieldSchema.width) {
|
if (fieldSchema.width && typeof fieldSchema.width === "string") {
|
||||||
style += ` ${fieldSchema.width}`
|
style += ` ${fieldSchema.width}`
|
||||||
} else {
|
} else {
|
||||||
style += " minmax(auto, 1fr)"
|
style += " minmax(auto, 1fr)"
|
||||||
|
|
|
@ -3,9 +3,13 @@
|
||||||
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let serif = false
|
export let serif = false
|
||||||
|
export let weight = 600
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
|
style={`
|
||||||
|
${weight ? `font-weight:${weight};` : ""}
|
||||||
|
`}
|
||||||
class="spectrum-Detail spectrum-Detail--size{size}"
|
class="spectrum-Detail spectrum-Detail--size{size}"
|
||||||
class:spectrum-Detail--serif={serif}
|
class:spectrum-Detail--serif={serif}
|
||||||
>
|
>
|
||||||
|
@ -13,7 +17,4 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
p {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -97,4 +97,22 @@
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
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",
|
"name": "@budibase/builder",
|
||||||
"version": "2.5.6-alpha.6",
|
"version": "2.5.6-alpha.28",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -58,11 +58,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.5.6-alpha.6",
|
"@budibase/bbui": "2.5.6-alpha.28",
|
||||||
"@budibase/client": "2.5.6-alpha.6",
|
"@budibase/client": "2.5.6-alpha.28",
|
||||||
"@budibase/frontend-core": "2.5.6-alpha.6",
|
"@budibase/frontend-core": "2.5.6-alpha.28",
|
||||||
"@budibase/shared-core": "2.5.6-alpha.6",
|
"@budibase/shared-core": "2.5.6-alpha.28",
|
||||||
"@budibase/string-templates": "2.5.6-alpha.6",
|
"@budibase/string-templates": "2.5.6-alpha.28",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||||
"@fortawesome/free-solid-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({
|
export const API = createAPIClient({
|
||||||
attachHeaders: headers => {
|
attachHeaders: headers => {
|
||||||
// Attach app ID header from store
|
// 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
|
// Add csrf token if authenticated
|
||||||
const user = get(auth).user
|
const user = get(auth).user
|
||||||
|
|
|
@ -67,6 +67,15 @@
|
||||||
newInputData = cloneDeep(blockInputs)
|
newInputData = cloneDeep(blockInputs)
|
||||||
}
|
}
|
||||||
inputData = newInputData
|
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) => {
|
const onChange = Utils.sequential(async (e, key) => {
|
||||||
|
@ -243,6 +252,7 @@
|
||||||
<Select
|
<Select
|
||||||
on:change={e => onChange(e, key)}
|
on:change={e => onChange(e, key)}
|
||||||
value={inputData[key]}
|
value={inputData[key]}
|
||||||
|
placeholder={false}
|
||||||
options={value.enum}
|
options={value.enum}
|
||||||
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
|
getOptionLabel={(x, idx) => (value.pretty ? value.pretty[idx] : x)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,286 +1,75 @@
|
||||||
<script>
|
<script>
|
||||||
import { fade } from "svelte/transition"
|
|
||||||
import { tables } from "stores/backend"
|
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 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 { TableNames } from "constants"
|
||||||
import CreateEditRow from "./modals/CreateEditRow.svelte"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
import {
|
|
||||||
Pagination,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
Layout,
|
|
||||||
notifications,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { fetchData } from "@budibase/frontend-core"
|
|
||||||
import { API } from "api"
|
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
|
const userSchemaOverrides = {
|
||||||
let filters
|
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
|
$: id = $tables.selected?._id
|
||||||
$: fetch = createFetch(id)
|
$: isUsersTable = id === TableNames.USERS
|
||||||
$: hasCols = checkHasCols(schema)
|
$: isInternal = $tables.selected?.type !== "external"
|
||||||
$: 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()
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="wrapper">
|
||||||
<Table
|
<Grid
|
||||||
title={$tables.selected?.name}
|
{API}
|
||||||
schema={enrichedSchema}
|
|
||||||
{type}
|
|
||||||
tableId={id}
|
tableId={id}
|
||||||
data={$fetch.rows}
|
allowAddRows={!isUsersTable}
|
||||||
bind:hideAutocolumns
|
allowDeleteRows={!isUsersTable}
|
||||||
loading={!$fetch.loaded}
|
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||||
on:sort={onSort}
|
on:updatetable={e => tables.updateTable(e.detail)}
|
||||||
allowEditing
|
|
||||||
disableSorting
|
|
||||||
on:updatecolumns={onUpdateColumns}
|
|
||||||
on:updaterows={onUpdateRows}
|
|
||||||
on:selectionUpdated={e => {
|
|
||||||
selectedRows = e.detail
|
|
||||||
}}
|
|
||||||
customPlaceholder
|
|
||||||
>
|
>
|
||||||
<div class="buttons">
|
<svelte:fragment slot="controls">
|
||||||
<div class="left-buttons">
|
{#if isInternal}
|
||||||
<CreateColumnButton
|
<GridCreateViewButton />
|
||||||
highlighted={$fetch.loaded && (!hasCols || !hasRows)}
|
{/if}
|
||||||
on:updatecolumns={onUpdateColumns}
|
<GridManageAccessButton />
|
||||||
/>
|
{#if !isInternal}
|
||||||
{#if !isUsersTable}
|
<GridRelationshipButton />
|
||||||
<CreateRowButton
|
{/if}
|
||||||
on:updaterows={onUpdateRows}
|
{#if isUsersTable}
|
||||||
title={"Create row"}
|
<EditRolesButton />
|
||||||
modalContentComponent={CreateEditRow}
|
{:else}
|
||||||
disabled={!hasCols}
|
<GridImportButton />
|
||||||
highlighted={$fetch.loaded && hasCols && !hasRows}
|
{/if}
|
||||||
/>
|
<GridExportButton />
|
||||||
{/if}
|
<GridFilterButton />
|
||||||
{#if isInternal}
|
<GridAddColumnModal />
|
||||||
<CreateViewButton disabled={!hasCols || !hasRows} />
|
<GridEditColumnModal />
|
||||||
{/if}
|
{#if isUsersTable}
|
||||||
</div>
|
<GridEditUserModal />
|
||||||
<div class="right-buttons">
|
{:else}
|
||||||
<ManageAccessButton resourceId={$tables.selected?._id} />
|
<GridCreateEditRowModal />
|
||||||
{#if isUsersTable}
|
{/if}
|
||||||
<EditRolesButton />
|
</svelte:fragment>
|
||||||
{/if}
|
</Grid>
|
||||||
{#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}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.pagination {
|
.wrapper {
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
|
margin: -28px -40px -40px -40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
background: var(--background);
|
||||||
align-items: center;
|
overflow: hidden;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.left-buttons,
|
|
||||||
.right-buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-m);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { goto, params } from "@roxi/routify"
|
import { goto, params } from "@roxi/routify"
|
||||||
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
|
import { Table, Heading, Layout } from "@budibase/bbui"
|
||||||
import { API } from "api"
|
|
||||||
import Spinner from "components/common/Spinner.svelte"
|
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 CreateEditRow from "./modals/CreateEditRow.svelte"
|
||||||
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
import CreateEditUser from "./modals/CreateEditUser.svelte"
|
||||||
import CreateEditColumn from "./modals/CreateEditColumn.svelte"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
import {
|
import {
|
||||||
TableNames,
|
TableNames,
|
||||||
UNEDITABLE_USER_FIELDS,
|
UNEDITABLE_USER_FIELDS,
|
||||||
|
@ -22,7 +17,6 @@
|
||||||
export let data = []
|
export let data = []
|
||||||
export let tableId
|
export let tableId
|
||||||
export let title
|
export let title
|
||||||
export let allowEditing = false
|
|
||||||
export let loading = false
|
export let loading = false
|
||||||
export let hideAutocolumns
|
export let hideAutocolumns
|
||||||
export let rowCount
|
export let rowCount
|
||||||
|
@ -32,12 +26,7 @@
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let selectedRows = []
|
let selectedRows = []
|
||||||
let editableColumn
|
|
||||||
let editableRow
|
|
||||||
let editRowModal
|
|
||||||
let editColumnModal
|
|
||||||
let customRenderers = []
|
let customRenderers = []
|
||||||
let confirmDelete
|
|
||||||
|
|
||||||
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
$: selectedRows, dispatch("selectionUpdated", selectedRows)
|
||||||
$: isUsersTable = tableId === TableNames.USERS
|
$: isUsersTable = tableId === TableNames.USERS
|
||||||
|
@ -92,36 +81,6 @@
|
||||||
`/builder/app/${$params.application}/data/table/${tableId}/relationship/${rowId}/${fieldName}`
|
`/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>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding gap="S">
|
<Layout noPadding gap="S">
|
||||||
|
@ -138,16 +97,6 @@
|
||||||
{/if}
|
{/if}
|
||||||
<div class="popovers">
|
<div class="popovers">
|
||||||
<slot />
|
<slot />
|
||||||
{#if !isUsersTable && selectedRows.length > 0}
|
|
||||||
<DeleteRowsButton
|
|
||||||
on:updaterows
|
|
||||||
{selectedRows}
|
|
||||||
deleteRows={async rows => {
|
|
||||||
await deleteRows(rows)
|
|
||||||
resetSelectedRows()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
{#key tableId}
|
{#key tableId}
|
||||||
|
@ -160,13 +109,7 @@
|
||||||
{rowCount}
|
{rowCount}
|
||||||
{disableSorting}
|
{disableSorting}
|
||||||
{customPlaceholder}
|
{customPlaceholder}
|
||||||
bind:selectedRows
|
|
||||||
allowSelectRows={allowEditing && !isUsersTable}
|
|
||||||
allowEditRows={allowEditing}
|
|
||||||
allowEditColumns={allowEditing}
|
|
||||||
showAutoColumns={!hideAutocolumns}
|
showAutoColumns={!hideAutocolumns}
|
||||||
on:editcolumn={e => editColumn(e.detail)}
|
|
||||||
on:editrow={e => editRow(e.detail)}
|
|
||||||
on:clickrelationship={e => selectRelationship(e.detail)}
|
on:clickrelationship={e => selectRelationship(e.detail)}
|
||||||
on:sort
|
on:sort
|
||||||
>
|
>
|
||||||
|
@ -176,42 +119,6 @@
|
||||||
{/key}
|
{/key}
|
||||||
</Layout>
|
</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>
|
<style>
|
||||||
.table-title {
|
.table-title {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
|
@ -57,7 +57,6 @@
|
||||||
{data}
|
{data}
|
||||||
{loading}
|
{loading}
|
||||||
{type}
|
{type}
|
||||||
allowEditing={false}
|
|
||||||
rowCount={10}
|
rowCount={10}
|
||||||
bind:hideAutocolumns
|
bind:hideAutocolumns
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon="Calculator"
|
icon="Calculator"
|
||||||
size="S"
|
|
||||||
quiet
|
quiet
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
active={view.field && view.calculation}
|
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"}`
|
$: text = `${item}${selectedRows?.length === 1 ? "" : "s"}`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button icon="Delete" size="s" warning quiet on:click={modal.show}>
|
<Button icon="Delete" warning quiet on:click={modal.show}>
|
||||||
Delete
|
Delete
|
||||||
{selectedRows.length}
|
{selectedRows.length}
|
||||||
{text}
|
{text}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import { Button, Modal } from "@budibase/bbui"
|
import { ActionButton, Modal } from "@budibase/bbui"
|
||||||
import EditRolesModal from "../modals/EditRoles.svelte"
|
import EditRolesModal from "../modals/EditRoles.svelte"
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<ActionButton icon="UsersLock" quiet on:click={modal.show}>
|
||||||
<Button icon="UsersLock" primary size="S" quiet on:click={modal.show}>
|
Edit roles
|
||||||
Edit roles
|
</ActionButton>
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<EditRolesModal />
|
<EditRolesModal />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -7,15 +7,23 @@
|
||||||
export let table
|
export let table
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: datasource = findDatasource(table?._id)
|
||||||
$: plusTables = datasource?.plus
|
$: plusTables = datasource?.plus
|
||||||
? Object.values(datasource?.entities || {})
|
? Object.values(datasource?.entities || {})
|
||||||
: []
|
: []
|
||||||
$: datasource = $datasources.list.find(
|
|
||||||
source => source._id === table?.sourceId
|
|
||||||
)
|
|
||||||
|
|
||||||
let modal
|
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() {
|
async function saveRelationship() {
|
||||||
try {
|
try {
|
||||||
// Create datasource
|
// Create datasource
|
||||||
|
@ -28,15 +36,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if table.sourceId}
|
{#if datasource}
|
||||||
<div>
|
<div>
|
||||||
<ActionButton
|
<ActionButton icon="DataCorrelated" primary quiet on:click={modal.show}>
|
||||||
icon="DataCorrelated"
|
|
||||||
primary
|
|
||||||
size="S"
|
|
||||||
quiet
|
|
||||||
on:click={modal.show}
|
|
||||||
>
|
|
||||||
Define existing relationship
|
Define existing relationship
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,13 +11,7 @@
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton {disabled} icon="DataDownload" quiet on:click={modal.show}>
|
||||||
{disabled}
|
|
||||||
icon="DataDownload"
|
|
||||||
size="S"
|
|
||||||
quiet
|
|
||||||
on:click={modal.show}
|
|
||||||
>
|
|
||||||
Export
|
Export
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
<Button
|
<Button
|
||||||
icon="Group"
|
icon="Group"
|
||||||
primary
|
primary
|
||||||
size="S"
|
|
||||||
quiet
|
quiet
|
||||||
active={!!view.groupBy}
|
active={!!view.groupBy}
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon={hideAutocolumns ? "VisibilityOff" : "Visibility"}
|
icon={hideAutocolumns ? "VisibilityOff" : "Visibility"}
|
||||||
primary
|
primary
|
||||||
size="S"
|
|
||||||
quiet
|
quiet
|
||||||
on:click={hideOrUnhide}
|
on:click={hideOrUnhide}
|
||||||
>
|
>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show} {disabled}>
|
<ActionButton icon="DataUpload" quiet on:click={modal.show} {disabled}>
|
||||||
Import
|
Import
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import ManageAccessModal from "../modals/ManageAccessModal.svelte"
|
import ManageAccessModal from "../modals/ManageAccessModal.svelte"
|
||||||
|
|
||||||
export let resourceId
|
export let resourceId
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
let resourcePermissions
|
let resourcePermissions
|
||||||
|
@ -14,8 +15,8 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="LockClosed" size="S" quiet on:click={openDropdown}>
|
<ActionButton icon="LockClosed" quiet on:click={openDropdown} {disabled}>
|
||||||
Manage access
|
Access
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
<ManageAccessModal
|
<ManageAccessModal
|
||||||
|
|
|
@ -18,11 +18,10 @@
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon="Filter"
|
icon="Filter"
|
||||||
size="S"
|
|
||||||
quiet
|
quiet
|
||||||
{disabled}
|
{disabled}
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
active={tempValue?.length > 0}
|
selected={tempValue?.length > 0}
|
||||||
>
|
>
|
||||||
Filter
|
Filter
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
icon="Filter"
|
icon="Filter"
|
||||||
size="S"
|
|
||||||
quiet
|
quiet
|
||||||
on:click={modal.show}
|
on:click={modal.show}
|
||||||
active={view.filters?.length}
|
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,
|
indexes,
|
||||||
})
|
})
|
||||||
dispatch("updatecolumns")
|
dispatch("updatecolumns")
|
||||||
|
if (originalName) {
|
||||||
|
notifications.success("Column updated successfully")
|
||||||
|
} else {
|
||||||
|
notifications.success("Column created successfully")
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err)
|
|
||||||
notifications.error(`Error saving column: ${err.message}`)
|
notifications.error(`Error saving column: ${err.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -199,7 +203,7 @@
|
||||||
notifications.error("You cannot delete the display column")
|
notifications.error("You cannot delete the display column")
|
||||||
} else {
|
} else {
|
||||||
await tables.deleteField(editableColumn)
|
await tables.deleteField(editableColumn)
|
||||||
notifications.success(`Column ${editableColumn.name} deleted.`)
|
notifications.success(`Column ${editableColumn.name} deleted`)
|
||||||
confirmDeleteDialog.hide()
|
confirmDeleteDialog.hide()
|
||||||
hide()
|
hide()
|
||||||
deletion = false
|
deletion = false
|
||||||
|
|
|
@ -23,9 +23,9 @@
|
||||||
async function saveRow() {
|
async function saveRow() {
|
||||||
errors = []
|
errors = []
|
||||||
try {
|
try {
|
||||||
await API.saveRow({ ...row, tableId: table._id })
|
const res = await API.saveRow({ ...row, tableId: table._id })
|
||||||
notifications.success("Row saved successfully")
|
notifications.success("Row saved successfully")
|
||||||
dispatch("updaterows")
|
dispatch("updaterows", res._id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const response = error.json
|
const response = error.json
|
||||||
if (error.handled && response?.errors) {
|
if (error.handled && response?.errors) {
|
||||||
|
|
|
@ -55,9 +55,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await API.saveRow({ ...row, tableId: table._id })
|
const res = await API.saveRow({ ...row, tableId: table._id })
|
||||||
notifications.success("User saved successfully")
|
notifications.success("User saved successfully")
|
||||||
dispatch("updaterows")
|
dispatch("updaterows", res.id)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.handled) {
|
if (error.handled) {
|
||||||
const response = error.json
|
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
|
$: sortedTables = $tables.list
|
||||||
.filter(table => table.sourceId === sourceId)
|
.filter(table => table.sourceId === sourceId)
|
||||||
.sort(alphabetical)
|
.sort(alphabetical)
|
||||||
|
|
||||||
|
const selectTable = tableId => {
|
||||||
|
tables.select(tableId)
|
||||||
|
if (!$isActive("./table/:tableId")) {
|
||||||
|
$goto(`./table/${tableId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $database?._id}
|
{#if $database?._id}
|
||||||
|
@ -25,7 +32,7 @@
|
||||||
text={table.name}
|
text={table.name}
|
||||||
selected={$isActive("./table/:tableId") &&
|
selected={$isActive("./table/:tableId") &&
|
||||||
$tables.selected?._id === table._id}
|
$tables.selected?._id === table._id}
|
||||||
on:click={() => $goto(`./table/${table._id}`)}
|
on:click={() => selectTable(table._id)}
|
||||||
>
|
>
|
||||||
{#if table._id !== TableNames.USERS}
|
{#if table._id !== TableNames.USERS}
|
||||||
<EditTablePopover {table} />
|
<EditTablePopover {table} />
|
||||||
|
|
|
@ -325,9 +325,4 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
</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 ColumnEditor from "./controls/ColumnEditor/ColumnEditor.svelte"
|
||||||
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
|
import BasicColumnEditor from "./controls/ColumnEditor/BasicColumnEditor.svelte"
|
||||||
import BarButtonList from "./controls/BarButtonList.svelte"
|
import BarButtonList from "./controls/BarButtonList.svelte"
|
||||||
|
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: DrawerBindableCombobox,
|
text: DrawerBindableCombobox,
|
||||||
|
@ -43,6 +44,7 @@ const componentMap = {
|
||||||
section: SectionSelect,
|
section: SectionSelect,
|
||||||
filter: FilterEditor,
|
filter: FilterEditor,
|
||||||
url: URLSelect,
|
url: URLSelect,
|
||||||
|
fieldConfiguration: FieldConfiguration,
|
||||||
columns: ColumnEditor,
|
columns: ColumnEditor,
|
||||||
"columns/basic": BasicColumnEditor,
|
"columns/basic": BasicColumnEditor,
|
||||||
"field/sortable": SortableFieldSelect,
|
"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 DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
export let fieldName = null
|
||||||
export let rules = []
|
export let rules = []
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
export let type
|
export let type
|
||||||
|
@ -124,7 +125,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
|
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
|
||||||
$: field = $selectedComponent?.field
|
$: field = fieldName || $selectedComponent?.field
|
||||||
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
|
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
|
||||||
$: fieldType = type?.split("/")[1] || "string"
|
$: fieldType = type?.split("/")[1] || "string"
|
||||||
$: constraintOptions = getConstraintsForType(fieldType)
|
$: constraintOptions = getConstraintsForType(fieldType)
|
||||||
|
@ -140,8 +141,12 @@
|
||||||
const formParent = findClosestMatchingComponent(
|
const formParent = findClosestMatchingComponent(
|
||||||
asset.props,
|
asset.props,
|
||||||
component._id,
|
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)
|
return getSchemaForDatasource(asset, formParent?.dataSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ export const ExpiringKeys = {
|
||||||
LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner",
|
LICENSING_ROWS_WARNING_BANNER: "licensing_rows_warning_banner",
|
||||||
LICENSING_AUTOMATIONS_WARNING_BANNER: "licensing_automations_warning_banner",
|
LICENSING_AUTOMATIONS_WARNING_BANNER: "licensing_automations_warning_banner",
|
||||||
LICENSING_QUERIES_WARNING_BANNER: "licensing_queries_warning_banner",
|
LICENSING_QUERIES_WARNING_BANNER: "licensing_queries_warning_banner",
|
||||||
|
LICENSING_USERS_ABOVE_LIMIT_BANNER: "licensing_users_above_limit_banner",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StripeStatus = {
|
export const StripeStatus = {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { temporalStore } from "builderStore"
|
||||||
import { admin, auth, licensing } from "stores/portal"
|
import { admin, auth, licensing } from "stores/portal"
|
||||||
import { get } from "svelte/store"
|
import { get } from "svelte/store"
|
||||||
import { BANNER_TYPES } from "@budibase/bbui"
|
import { BANNER_TYPES } from "@budibase/bbui"
|
||||||
|
import { capitalise } from "helpers"
|
||||||
|
|
||||||
const oneDayInSeconds = 86400
|
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 = () => {
|
export const getBanners = () => {
|
||||||
return [
|
return [
|
||||||
buildPaymentFailedBanner(),
|
buildPaymentFailedBanner(),
|
||||||
|
@ -163,6 +188,7 @@ export const getBanners = () => {
|
||||||
ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER,
|
ExpiringKeys.LICENSING_QUERIES_WARNING_BANNER,
|
||||||
90
|
90
|
||||||
),
|
),
|
||||||
|
buildUsersAboveLimitBanner(ExpiringKeys.LICENSING_USERS_ABOVE_LIMIT_BANNER),
|
||||||
].filter(licensingBanner => {
|
].filter(licensingBanner => {
|
||||||
return (
|
return (
|
||||||
!temporalStore.actions.getExpiring(licensingBanner.key) &&
|
!temporalStore.actions.getExpiring(licensingBanner.key) &&
|
||||||
|
|
|
@ -59,9 +59,7 @@
|
||||||
<div class="app-row-actions">
|
<div class="app-row-actions">
|
||||||
<AppLockModal {app} buttonSize="M" />
|
<AppLockModal {app} buttonSize="M" />
|
||||||
<Button size="S" secondary on:click={goToOverview}>Manage</Button>
|
<Button size="S" secondary on:click={goToOverview}>Manage</Button>
|
||||||
<Button size="S" primary disabled={app.lockedOther} on:click={goToBuilder}>
|
<Button size="S" primary on:click={goToBuilder}>Edit</Button>
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -67,3 +67,8 @@ export const OnboardingType = {
|
||||||
EMAIL: "email",
|
EMAIL: "email",
|
||||||
PASSWORD: "password",
|
PASSWORD: "password",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PlanModel = {
|
||||||
|
PER_USER: "perUser",
|
||||||
|
DAY_PASS: "dayPass",
|
||||||
|
}
|
||||||
|
|
|
@ -70,7 +70,6 @@ a {
|
||||||
background: var(--spectrum-alias-background-color-default);
|
background: var(--spectrum-alias-background-color-default);
|
||||||
}
|
}
|
||||||
html * {
|
html * {
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: var(--spectrum-global-color-gray-400)
|
scrollbar-color: var(--spectrum-global-color-gray-400)
|
||||||
var(--spectrum-alias-background-color-default);
|
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>
|
<script>
|
||||||
import {
|
import { Button, Heading, notifications, Layout, Body } from "@budibase/bbui"
|
||||||
Button,
|
|
||||||
Heading,
|
|
||||||
notifications,
|
|
||||||
Layout,
|
|
||||||
Body,
|
|
||||||
Modal,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { goto } from "@roxi/routify"
|
import { goto } from "@roxi/routify"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import { admin, auth } from "stores/portal"
|
import { admin, auth } from "stores/portal"
|
||||||
import ImportAppsModal from "./_components/ImportAppsModal.svelte"
|
|
||||||
import Logo from "assets/bb-emblem.svg"
|
import Logo from "assets/bb-emblem.svg"
|
||||||
import { onMount } from "svelte"
|
import { FancyForm, FancyInput } from "@budibase/bbui"
|
||||||
import { FancyForm, FancyInput, ActionButton } from "@budibase/bbui"
|
|
||||||
import { TestimonialPage } from "@budibase/frontend-core/src/components"
|
import { TestimonialPage } from "@budibase/frontend-core/src/components"
|
||||||
import { passwordsMatch, handleError } from "../auth/_components/utils"
|
import { passwordsMatch, handleError } from "../auth/_components/utils"
|
||||||
|
|
||||||
let modal
|
|
||||||
let form
|
let form
|
||||||
let errors = {}
|
let errors = {}
|
||||||
let formData = {}
|
let formData = {}
|
||||||
let submitted = false
|
let submitted = false
|
||||||
|
|
||||||
$: tenantId = $auth.tenantId
|
$: tenantId = $auth.tenantId
|
||||||
$: cloud = $admin.cloud
|
|
||||||
$: imported = $admin.importComplete
|
|
||||||
|
|
||||||
async function save() {
|
async function save() {
|
||||||
form.validate()
|
form.validate()
|
||||||
|
@ -46,22 +34,8 @@
|
||||||
notifications.error("Failed to create admin user")
|
notifications.error("Failed to create admin user")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if (!cloud) {
|
|
||||||
try {
|
|
||||||
await admin.checkImportComplete()
|
|
||||||
} catch (error) {
|
|
||||||
notifications.error("Error checking import status")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal} padding={false} width="600px">
|
|
||||||
<ImportAppsModal />
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<TestimonialPage>
|
<TestimonialPage>
|
||||||
<Layout gap="M" noPadding>
|
<Layout gap="M" noPadding>
|
||||||
<Layout justifyItems="center" noPadding>
|
<Layout justifyItems="center" noPadding>
|
||||||
|
@ -156,20 +130,6 @@
|
||||||
Create super admin user
|
Create super admin user
|
||||||
</Button>
|
</Button>
|
||||||
</Layout>
|
</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>
|
</Layout>
|
||||||
</TestimonialPage>
|
</TestimonialPage>
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue