Merge remote-tracking branch 'origin/master' into global-bindings
This commit is contained in:
commit
5d6862b399
|
@ -1,194 +0,0 @@
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"imageSize": 100,
|
|
||||||
"commit": false,
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"login": "shogunpurple",
|
|
||||||
"name": "Martin McKeaveney",
|
|
||||||
"avatar_url": "https://avatars1.githubusercontent.com/u/11256663?v=4",
|
|
||||||
"profile": "http://martinmck.com",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc",
|
|
||||||
"test",
|
|
||||||
"infra"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mike12345567",
|
|
||||||
"name": "Michael Drury",
|
|
||||||
"avatar_url": "https://avatars2.githubusercontent.com/u/4407001?v=4",
|
|
||||||
"profile": "http://www.michaeldrury.co.uk/",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code",
|
|
||||||
"test",
|
|
||||||
"infra"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "aptkingston",
|
|
||||||
"name": "Andrew Kingston",
|
|
||||||
"avatar_url": "https://avatars3.githubusercontent.com/u/9075550?v=4",
|
|
||||||
"profile": "https://github.com/aptkingston",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code",
|
|
||||||
"test",
|
|
||||||
"design"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mjashanks",
|
|
||||||
"name": "Michael Shanks",
|
|
||||||
"avatar_url": "https://avatars3.githubusercontent.com/u/3524181?v=4",
|
|
||||||
"profile": "https://budibase.com/",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "kevmodrome",
|
|
||||||
"name": "Kevin Åberg Kultalahti",
|
|
||||||
"avatar_url": "https://avatars3.githubusercontent.com/u/534488?v=4",
|
|
||||||
"profile": "https://github.com/kevmodrome",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "joebudi",
|
|
||||||
"name": "Joe",
|
|
||||||
"avatar_url": "https://avatars2.githubusercontent.com/u/49767913?v=4",
|
|
||||||
"profile": "https://www.budibase.com/",
|
|
||||||
"contributions": [
|
|
||||||
"doc",
|
|
||||||
"code",
|
|
||||||
"content",
|
|
||||||
"design"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Rory-Powell",
|
|
||||||
"name": "Rory Powell",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/8755148?v=4",
|
|
||||||
"profile": "https://github.com/Rory-Powell",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "PClmnt",
|
|
||||||
"name": "Peter Clement",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/5665926?v=4",
|
|
||||||
"profile": "https://github.com/PClmnt",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"doc",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Conor-Mack",
|
|
||||||
"name": "Conor_Mack",
|
|
||||||
"avatar_url": "https://avatars1.githubusercontent.com/u/36074859?v=4",
|
|
||||||
"profile": "https://github.com/Conor-Mack",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "pngwn",
|
|
||||||
"name": "pngwn",
|
|
||||||
"avatar_url": "https://avatars1.githubusercontent.com/u/12937446?v=4",
|
|
||||||
"profile": "https://github.com/pngwn",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"test"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "HugoLd",
|
|
||||||
"name": "HugoLd",
|
|
||||||
"avatar_url": "https://avatars0.githubusercontent.com/u/26521848?v=4",
|
|
||||||
"profile": "https://github.com/HugoLd",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "victoriasloan",
|
|
||||||
"name": "victoriasloan",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/9913651?v=4",
|
|
||||||
"profile": "https://github.com/victoriasloan",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "yashank09",
|
|
||||||
"name": "yashank09",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/37672190?v=4",
|
|
||||||
"profile": "https://github.com/yashank09",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "SOVLOOKUP",
|
|
||||||
"name": "SOVLOOKUP",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/53158137?v=4",
|
|
||||||
"profile": "https://github.com/SOVLOOKUP",
|
|
||||||
"contributions": [
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "seoulaja",
|
|
||||||
"name": "seoulaja",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/15101654?v=4",
|
|
||||||
"profile": "https://github.com/seoulaja",
|
|
||||||
"contributions": [
|
|
||||||
"translation"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "mslourens",
|
|
||||||
"name": "Maurits Lourens",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/1907152?v=4",
|
|
||||||
"profile": "https://github.com/mslourens",
|
|
||||||
"contributions": [
|
|
||||||
"test",
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "Rory-Powell",
|
|
||||||
"name": "Rory Powell",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/8755148?v=4",
|
|
||||||
"profile": "https://github.com/Rory-Powell",
|
|
||||||
"contributions": [
|
|
||||||
"infra",
|
|
||||||
"test",
|
|
||||||
"code"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"contributorsPerLine": 7,
|
|
||||||
"projectName": "budibase",
|
|
||||||
"projectOwner": "Budibase",
|
|
||||||
"repoType": "github",
|
|
||||||
"repoHost": "https://github.com",
|
|
||||||
"skipCi": true,
|
|
||||||
"commitConvention": "none"
|
|
||||||
}
|
|
|
@ -8,3 +8,6 @@ packages/backend-core/coverage
|
||||||
packages/server/client
|
packages/server/client
|
||||||
packages/builder/.routify
|
packages/builder/.routify
|
||||||
packages/sdk/sdk
|
packages/sdk/sdk
|
||||||
|
packages/account-portal/packages/server/build
|
||||||
|
packages/account-portal/packages/ui/.routify
|
||||||
|
packages/account-portal/packages/ui/build
|
|
@ -1,139 +1,45 @@
|
||||||
# Budibase CI Pipelines
|
# Budibase CI Pipelines
|
||||||
|
|
||||||
Welcome to the budibase CI pipelines directory. This document details what each of the CI pipelines are for, and come common combinations.
|
Welcome to the Budibase CI pipelines directory. This document details what each of the CI pipelines are for, and come common combinations.
|
||||||
|
|
||||||
## All CI Pipelines
|
## All CI Pipelines
|
||||||
|
|
||||||
### Note
|
|
||||||
|
|
||||||
- When running workflow dispatch jobs, ensure you always run them off the `master` branch. It defaults to `develop`, so double check before running any jobs. The exception to this case is the `deploy-release` job which requires the develop branch.
|
|
||||||
|
|
||||||
### Standard CI Build Job (budibase_ci.yml)
|
### Standard CI Build Job (budibase_ci.yml)
|
||||||
|
|
||||||
Triggers:
|
Triggers:
|
||||||
|
|
||||||
- PR or push to develop
|
|
||||||
- PR or push to master
|
- PR or push to master
|
||||||
|
|
||||||
The standard CI Build job is what runs when you raise a PR to develop or master.
|
The standard CI Build job is what runs when you raise a PR to master.
|
||||||
|
|
||||||
- Installs all dependencies,
|
- Installs all dependencies,
|
||||||
- builds the project
|
- builds the project
|
||||||
- run the unit tests
|
- run the unit tests
|
||||||
- Generate test coverage metrics with codecov
|
- Generate test coverage metrics with codecov
|
||||||
- Run the integration tests
|
- Run the integration tests
|
||||||
|
- Check that the pro and account portal submodules are pointing to the lastest master head
|
||||||
|
|
||||||
### Release Develop Job (release-develop.yml)
|
### Release Job (tag-release.yml)
|
||||||
|
|
||||||
Triggers:
|
Triggers:
|
||||||
|
|
||||||
- Push to develop
|
- Manually triggered
|
||||||
|
|
||||||
The job responsible for building, tagging and pushing docker images out to the test and release environments.
|
This job is responsible for building and pushing all the production services, packages and images. This is done via [budibase-deploys](https://github.com/Budibase/budibase-deploys/actions/workflows/release.yml).
|
||||||
|
|
||||||
- Installs all dependencies
|
An input is required, indicating if the new version will be a `patch`, `minor` or `major` bump.
|
||||||
- builds the project
|
|
||||||
- run the unit tests
|
|
||||||
- publish the budibase JS packages under a prerelease tag to NPM
|
|
||||||
- build, tag and push docker images under the `develop` tag to docker hub
|
|
||||||
|
|
||||||
These images will then be pulled by the test and release environments, updating the latest automatically. Discord notifications are sent to the #infra channel when this occurs.
|
More documentation can be found in here: https://budibase.atlassian.net/wiki/spaces/DEVOPS/pages/347930625/Production+release
|
||||||
|
|
||||||
### Release Job (release.yml)
|
|
||||||
|
|
||||||
Triggers:
|
|
||||||
|
|
||||||
- Push to master
|
|
||||||
|
|
||||||
This job is responsible for building and pushing the latest code to NPM and docker hub, so that it can be deployed.
|
|
||||||
|
|
||||||
- Installs all dependencies
|
|
||||||
- builds the project
|
|
||||||
- run the unit tests
|
|
||||||
- publish the budibase JS packages under a release tag to NPM (always incremented by patch versions)
|
|
||||||
- build, tag and push docker images under the `v.x.x.x` (the tag of the NPM release) tag to docker hub
|
|
||||||
|
|
||||||
### Release Selfhost Job (release-selfhost.yml)
|
|
||||||
|
|
||||||
Triggers:
|
|
||||||
|
|
||||||
- Manual Workflow Dispatch Trigger
|
|
||||||
|
|
||||||
This job is responsible for delivering the latest version of budibase to those that are self-hosting.
|
|
||||||
|
|
||||||
This job relies on the release job to have run first, so the latest image is pushed to dockerhub. This job then will pull the latest version from `lerna.json` and try to find an image in dockerhub corresponding to that version. For example, if the version in `lerna.json` is `1.0.0`:
|
|
||||||
|
|
||||||
- Pull the images for all budibase services tagged `v1.0.0` from dockerhub
|
|
||||||
- Tag these images as `latest`
|
|
||||||
- Push them back to dockerhub. This now means anyone who pulls `latest` (self hosters using docker-compose) will get the latest version.
|
|
||||||
- Build and release the budibase helm chart for kubernetes users
|
|
||||||
- Perform a github release with the latest version. You can see previous releases here (https://github.com/Budibase/budibase/releases)
|
|
||||||
|
|
||||||
### Deploy Release (deploy-release.yml)
|
|
||||||
|
|
||||||
Triggers:
|
|
||||||
|
|
||||||
- Manual Workflow Dispatch Trigger
|
|
||||||
|
|
||||||
This job is responsible for deploying to our release, cloud kubernetes environment. You must run the release job first, to ensure that the latest images have been built and pushed to docker hub. After kicking off this job, the following will occur:
|
|
||||||
|
|
||||||
- Checks out the release branch
|
|
||||||
- Pulls the latest `values.yaml` from budibase infra, a private repo containing budibases infrastructure configuration
|
|
||||||
- Gets the latest budibase version from `lerna.json`, if it hasn't been specified in the workflow when you kicked it off
|
|
||||||
- Configures AWS Credentials
|
|
||||||
- Deploys the helm chart in the budibase repo to our preproduction EKS cluster, injecting the `values.yaml` we pulled from budibase-infra
|
|
||||||
- Fires off a discord webhook in the #infra channel to show that the deployment completely successfully.
|
|
||||||
|
|
||||||
### Deploy Preprod (deploy-preprod.yml)
|
|
||||||
|
|
||||||
Triggers:
|
|
||||||
|
|
||||||
- Manual Workflow Dispatch Trigger
|
|
||||||
|
|
||||||
This job is responsible for deploying to our preprod, cloud kubernetes environment. You must run the release job first, to ensure that the latest images have been built and pushed to docker hub. After kicking off this job, the following will occur:
|
|
||||||
|
|
||||||
- Checks out the master branch
|
|
||||||
- Pulls the latest `values.yaml` from budibase infra, a private repo containing budibases infrastructure configuration
|
|
||||||
- Gets the latest budibase version from `lerna.json`, if it hasn't been specified in the workflow when you kicked it off
|
|
||||||
- Configures AWS Credentials
|
|
||||||
- Deploys the helm chart in the budibase repo to our preprod EKS cluster, injecting the `values.yaml` we pulled from budibase-infra
|
|
||||||
- Fires off a discord webhook in the #infra channel to show that the deployment completely successfully.
|
|
||||||
|
|
||||||
### Deploy Production (deploy-cloud.yml)
|
|
||||||
|
|
||||||
Triggers:
|
|
||||||
|
|
||||||
- Manual Workflow Dispatch Trigger
|
|
||||||
|
|
||||||
This job is responsible for deploying to our production, cloud kubernetes environment. You must run the release job first, to ensure that the latest images have been built and pushed to docker hub. You can also manually enter a version number for this job, so you can perform rollbacks or upgrade to a specific version. After kicking off this job, the following will occur:
|
|
||||||
|
|
||||||
- Checks out the master branch
|
|
||||||
- Pulls the latest `values.yaml` from budibase infra, a private repo containing budibases infrastructure configuration
|
|
||||||
- Gets the latest budibase version from `lerna.json`, if it hasn't been specified in the workflow when you kicked it off
|
|
||||||
- Configures AWS Credentials
|
|
||||||
- Deploys the helm chart in the budibase repo to our production EKS cluster, injecting the `values.yaml` we pulled from budibase-infra
|
|
||||||
- Fires off a discord webhook in the #infra channel to show that the deployment completely successfully.
|
|
||||||
|
|
||||||
## Common Workflows
|
## Common Workflows
|
||||||
|
|
||||||
### Deploy Changes to Production (Release)
|
### Deploy Changes to Production (Release)
|
||||||
|
|
||||||
- Merge `develop` into `master`
|
- Merge your changes into `master`
|
||||||
- Wait for budibase CI job and release job to run
|
- Run `tag-release.yml`
|
||||||
- Run cloud deploy job
|
- Check the progress in [budibase-deploys](https://github.com/Budibase/budibase-deploys/actions/workflows/release.yml)
|
||||||
- Run release selfhost job
|
|
||||||
|
|
||||||
### Deploy Changes to Production (Hotfix)
|
|
||||||
|
|
||||||
- Branch off `master`
|
|
||||||
- Perform your hotfix
|
|
||||||
- Merge back into `master`
|
|
||||||
- Wait for budibase CI job and release job to run
|
|
||||||
- Run cloud deploy job
|
|
||||||
- Run release selfhost job
|
|
||||||
|
|
||||||
### Rollback A Bad Cloud Deployment
|
### Rollback A Bad Cloud Deployment
|
||||||
|
|
||||||
- Kick off cloud deploy job
|
Rollback documentation can be found in here.
|
||||||
- Ensure you are running off master
|
https://budibase.atlassian.net/wiki/spaces/DEVOPS/pages/347930625/Production+release#Rollback
|
||||||
- Enter the version number of the last known good version of budibase. For example `1.0.0`
|
|
||||||
|
|
|
@ -38,10 +38,10 @@ jobs:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
@ -56,10 +56,10 @@ jobs:
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
|
|
||||||
|
@ -76,6 +76,18 @@ jobs:
|
||||||
yarn check:types
|
yarn check:types
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
helm-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Use Node.js 20.x
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
- run: cd charts/budibase && helm lint .
|
||||||
|
|
||||||
test-libraries:
|
test-libraries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -86,10 +98,10 @@ jobs:
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test
|
- name: Test
|
||||||
|
@ -110,10 +122,10 @@ jobs:
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test worker
|
- name: Test worker
|
||||||
|
@ -134,10 +146,10 @@ jobs:
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test server
|
- name: Test server
|
||||||
|
@ -159,10 +171,10 @@ jobs:
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test
|
- name: Test
|
||||||
|
@ -182,10 +194,10 @@ jobs:
|
||||||
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 20.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Build packages
|
- name: Build packages
|
||||||
|
@ -204,7 +216,7 @@ jobs:
|
||||||
|
|
||||||
check-pro-submodule:
|
check-pro-submodule:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase')
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo and submodules
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
@ -246,7 +258,57 @@ jobs:
|
||||||
|
|
||||||
if (submoduleCommit !== baseCommit) {
|
if (submoduleCommit !== baseCommit) {
|
||||||
console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}" branch.');
|
console.error('Submodule commit does not match the latest commit on the "${{ steps.get_pro_commits.outputs.target_branch }}" branch.');
|
||||||
console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md')
|
console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/master/docs/getting_started.md')
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log('All good, the submodule had been merged and setup correctly!')
|
||||||
|
}
|
||||||
|
|
||||||
|
check-accountportal-submodule:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: inputs.run_as_oss != true && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase')
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo and submodules
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Check account portal commit
|
||||||
|
id: get_accountportal_commits
|
||||||
|
run: |
|
||||||
|
cd packages/account-portal
|
||||||
|
accountportal_commit=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
branch="${{ github.base_ref || github.ref_name }}"
|
||||||
|
echo "Running on branch '$branch' (base_ref=${{ github.base_ref }}, ref_name=${{ github.head_ref }})"
|
||||||
|
|
||||||
|
base_commit=$(git rev-parse origin/master)
|
||||||
|
|
||||||
|
if [[ ! -z $base_commit ]]; then
|
||||||
|
echo "target_branch=$branch"
|
||||||
|
echo "target_branch=$branch" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "accountportal_commit=$accountportal_commit"
|
||||||
|
echo "accountportal_commit=$accountportal_commit" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "base_commit=$base_commit"
|
||||||
|
echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "Nothing to do - branch to branch merge."
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check submodule merged to base branch
|
||||||
|
if: ${{ steps.get_accountportal_commits.outputs.base_commit != '' }}
|
||||||
|
uses: actions/github-script@v4
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
script: |
|
||||||
|
const submoduleCommit = '${{ steps.get_accountportal_commits.outputs.accountportal_commit }}';
|
||||||
|
const baseCommit = '${{ steps.get_accountportal_commits.outputs.base_commit }}';
|
||||||
|
|
||||||
|
if (submoduleCommit !== baseCommit) {
|
||||||
|
console.error('Submodule commit does not match the latest commit on the "${{ steps.get_accountportal_commits.outputs.target_branch }}" branch.');
|
||||||
|
console.error('Refer to the account portal repo to merge your changes: https://github.com/Budibase/account-portal/blob/master/docs/index.md')
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
} else {
|
} else {
|
||||||
console.log('All good, the submodule had been merged and setup correctly!')
|
console.log('All good, the submodule had been merged and setup correctly!')
|
||||||
|
|
|
@ -2,9 +2,7 @@ name: close-featurebranch
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types: [closed, unlabeled]
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
BRANCH:
|
BRANCH:
|
||||||
|
@ -14,6 +12,9 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
if: |
|
||||||
|
(github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'feature-branch')) ||
|
||||||
|
github.event.label.name == 'feature-branch'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -2,12 +2,19 @@ name: deploy-featurebranch
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
types: [
|
||||||
- master
|
labeled,
|
||||||
|
# default types below (https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request)
|
||||||
|
opened,
|
||||||
|
synchronize,
|
||||||
|
reopened,
|
||||||
|
]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
if: |
|
||||||
|
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase') &&
|
||||||
|
contains(github.event.pull_request.labels.*.name, 'feature-branch')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
name: Forced release
|
||||||
|
concurrency:
|
||||||
|
group: tag-release
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ensure-is-master-tag:
|
||||||
|
name: Ensure is a master tag
|
||||||
|
runs-on: qa-arc-runner-set
|
||||||
|
steps:
|
||||||
|
- name: Checkout monorepo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
|
fetch-tags: true
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Fail if ref is not a tag
|
||||||
|
run: |
|
||||||
|
if ! git show-ref -q --verify "refs/tags/${{ github.ref_name }}" 2>/dev/null; then
|
||||||
|
echo "'${{ github.ref_name }}' is not a valid tag."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Fail if tag is not in master
|
||||||
|
run: |
|
||||||
|
if ! git merge-base --is-ancestor ${{ github.ref_name }} origin/master; then
|
||||||
|
echo "Tag is not in master. Release can only execute tags that are present on the master branch"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
trigger-release:
|
||||||
|
needs: [ensure-is-master-tag]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: peter-evans/repository-dispatch@v2
|
||||||
|
with:
|
||||||
|
repository: budibase/budibase-deploys
|
||||||
|
event-type: release-prod
|
||||||
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
client-payload: |-
|
||||||
|
{
|
||||||
|
"TAG": "${{ github.ref_name }}"
|
||||||
|
}
|
|
@ -16,8 +16,8 @@ jobs:
|
||||||
days-before-pr-stale: 7
|
days-before-pr-stale: 7
|
||||||
stale-issue-label: stale
|
stale-issue-label: stale
|
||||||
exempt-pr-labels: pinned,security,roadmap
|
exempt-pr-labels: pinned,security,roadmap
|
||||||
|
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
|
days-before-issue-close: 30
|
||||||
|
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
|
@ -26,6 +26,7 @@ jobs:
|
||||||
days-before-stale: 30
|
days-before-stale: 30
|
||||||
only-issue-labels: bug,High priority
|
only-issue-labels: bug,High priority
|
||||||
stale-issue-label: warn
|
stale-issue-label: warn
|
||||||
|
days-before-close: 30
|
||||||
|
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
|
@ -34,6 +35,7 @@ jobs:
|
||||||
days-before-stale: 90
|
days-before-stale: 90
|
||||||
only-issue-labels: bug,Medium priority
|
only-issue-labels: bug,Medium priority
|
||||||
stale-issue-label: warn
|
stale-issue-label: warn
|
||||||
|
days-before-close: 30
|
||||||
|
|
||||||
- uses: actions/stale@v8
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
|
@ -43,5 +45,4 @@ jobs:
|
||||||
stale-issue-label: stale
|
stale-issue-label: stale
|
||||||
only-issue-labels: bug
|
only-issue-labels: bug
|
||||||
stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for six months."
|
stale-issue-message: "This issue has been automatically marked as stale because it has not had any activity for six months."
|
||||||
|
|
||||||
days-before-close: 30
|
days-before-close: 30
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
builder/*
|
|
||||||
.data/
|
.data/
|
||||||
.temp/
|
.temp/
|
||||||
packages/server/runtime_apps/
|
packages/server/runtime_apps/
|
||||||
|
@ -41,8 +40,11 @@ bower_components
|
||||||
build/Release
|
build/Release
|
||||||
|
|
||||||
# Dependency directories
|
# Dependency directories
|
||||||
/node_modules/
|
|
||||||
jspm_packages/
|
jspm_packages/
|
||||||
|
*.min.js
|
||||||
|
*.map
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
# TypeScript v1 declaration files
|
||||||
typings/
|
typings/
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
[submodule "packages/pro"]
|
[submodule "packages/pro"]
|
||||||
path = packages/pro
|
path = packages/pro
|
||||||
url = git@github.com:Budibase/budibase-pro.git
|
url = git@github.com:Budibase/budibase-pro.git
|
||||||
|
[submodule "packages/account-portal"]
|
||||||
|
path = packages/account-portal
|
||||||
|
url = git@github.com:Budibase/account-portal.git
|
||||||
|
|
|
@ -9,3 +9,6 @@ packages/backend-core/coverage
|
||||||
packages/builder/.routify
|
packages/builder/.routify
|
||||||
packages/sdk/sdk
|
packages/sdk/sdk
|
||||||
packages/pro/coverage
|
packages/pro/coverage
|
||||||
|
packages/account-portal/packages/ui/build
|
||||||
|
packages/account-portal/packages/ui/.routify
|
||||||
|
packages/account-portal/packages/server/build
|
|
@ -1,3 +1,3 @@
|
||||||
nodejs 18.17.0
|
nodejs 20.10.0
|
||||||
python 3.10.0
|
python 3.10.0
|
||||||
yarn 1.22.19
|
yarn 1.22.19
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": "explicit"
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[json]": {
|
"[json]": {
|
||||||
|
|
4
LICENSE
4
LICENSE
|
@ -1,7 +1,9 @@
|
||||||
Copyright 2019-2021, Budibase Ltd.
|
Copyright 2019-2023, Budibase Ltd.
|
||||||
|
|
||||||
Each Budibase package has its own license, please check the license file in each package.
|
Each Budibase package has its own license, please check the license file in each package.
|
||||||
|
|
||||||
You can consider Budibase to be GPLv3 licensed overall.
|
You can consider Budibase to be GPLv3 licensed overall.
|
||||||
|
|
||||||
The apps that you build with Budibase do not package any GPLv3 licensed code, thus do not fall under those restrictions.
|
The apps that you build with Budibase do not package any GPLv3 licensed code, thus do not fall under those restrictions.
|
||||||
|
|
||||||
|
Budibase ships with Structured Query Server, by The Neighbourhoodie Software GmbH. This license for this can be found at ./SQS_LICENSE
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
FORM OF CUSTOMER LICENCE
|
||||||
|
|
||||||
|
Budibase hereby grants the Customer a worldwide, royalty free, non-exclusive,
|
||||||
|
perpetual (for the lifetime of the intellectual property rights contained in the Product)
|
||||||
|
right and title to utilise the binary code of the The Neighbourhoodie Software GmbH
|
||||||
|
Structured Query Server software product (Product) for its own internal business
|
||||||
|
purposes (the Purpose) only (the Licence). The Product has the function of bringing a
|
||||||
|
CouchDB database (NoSQL database) into an SQL database form (SQLite) and thereby
|
||||||
|
making it usable for complex queries - which originally could only be displayed in an
|
||||||
|
SQL database. By indexing in SQLite and a server that is tailored to it, the Product
|
||||||
|
enables the use of CouchDB with SQL queries.
|
||||||
|
The Licence shall not permit sub-licensing, resale or transfer of the Product to third
|
||||||
|
parties, other than sub-licensing to the Customer’s direct contractors for the purposes
|
||||||
|
of utilizing the Product as contemplated above.
|
||||||
|
The Licence shall not permit the adaptation, modification, decompilation, reverse
|
||||||
|
engineering or similar activities with respect to the Product.
|
||||||
|
This licence is granted to the Customer only, although Customer and its Affiliates’
|
||||||
|
employees, servants and agents shall be entitled to utilize the Product within the scope
|
||||||
|
of the Licence for the Customer’s Purpose only.
|
||||||
|
Reproduction is not permitted to users, except for reproductions that are necessary for
|
||||||
|
the use of the product under the licence described above. These conditions apply to the
|
||||||
|
product regardless of the form in which we make the product available and on which
|
||||||
|
devices it is installed and/or with which devices it is ultimately used. Depending on the
|
||||||
|
product variant or intended use, certain technical requirements in the IT infrastructure
|
||||||
|
must be satisfied as a prerequisite for use.
|
||||||
|
The law of the Northern Ireland applies exclusively to this licence, and the courts of
|
||||||
|
Northern Ireland shall have exclusive jurisdiction, save that we reserve a right to sue
|
||||||
|
you in the jurisdiction in which you are based. The application of the UN Sales
|
||||||
|
Convention (CISG) is excluded.
|
||||||
|
The invalidity of any part of this licence does not affect the validity of the remaining
|
||||||
|
regulations.
|
|
@ -157,6 +157,17 @@ $ helm install --create-namespace --namespace budibase budibase . -f values.yaml
|
||||||
| services.apps.replicaCount | int | `1` | The number of apps replicas to run. |
|
| services.apps.replicaCount | int | `1` | The number of apps replicas to run. |
|
||||||
| services.apps.resources | object | `{}` | The resources to use for apps pods. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/> for more information on how to set these. |
|
| services.apps.resources | object | `{}` | The resources to use for apps pods. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/> for more information on how to set these. |
|
||||||
| services.apps.startupProbe | object | HTTP health checks. | Startup probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/> |
|
| services.apps.startupProbe | object | HTTP health checks. | Startup probe configuration for apps pods. You shouldn't need to change this, but if you want to you can find more information here: <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/> |
|
||||||
|
| services.automationWorkers.autoscaling.enabled | bool | `false` | Whether to enable horizontal pod autoscaling for the apps service. |
|
||||||
|
| services.automationWorkers.autoscaling.maxReplicas | int | `10` | |
|
||||||
|
| services.automationWorkers.autoscaling.minReplicas | int | `1` | |
|
||||||
|
| services.automationWorkers.autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage for the automation worker service. Note that for autoscaling to work, you will need to have metrics-server configured, and resources set for the automation worker pods. |
|
||||||
|
| services.automationWorkers.enabled | bool | `true` | Whether or not to enable the automation worker service. If you disable this, automations will be processed by the apps service. |
|
||||||
|
| services.automationWorkers.livenessProbe | object | HTTP health checks. | Liveness probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/> |
|
||||||
|
| services.automationWorkers.logLevel | string | `"info"` | The log level for the automation worker service. |
|
||||||
|
| services.automationWorkers.readinessProbe | object | HTTP health checks. | Readiness probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/> |
|
||||||
|
| services.automationWorkers.replicaCount | int | `1` | The number of automation worker replicas to run. |
|
||||||
|
| services.automationWorkers.resources | object | `{}` | The resources to use for automation worker pods. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/> for more information on how to set these. |
|
||||||
|
| services.automationWorkers.startupProbe | object | HTTP health checks. | Startup probe configuration for automation worker pods. You shouldn't need to change this, but if you want to you can find more information here: <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/> |
|
||||||
| services.couchdb.backup.enabled | bool | `false` | Whether or not to enable periodic CouchDB backups. This works by replicating to another CouchDB instance. |
|
| services.couchdb.backup.enabled | bool | `false` | Whether or not to enable periodic CouchDB backups. This works by replicating to another CouchDB instance. |
|
||||||
| services.couchdb.backup.interval | string | `""` | Backup interval in seconds |
|
| services.couchdb.backup.interval | string | `""` | Backup interval in seconds |
|
||||||
| services.couchdb.backup.resources | object | `{}` | The resources to use for CouchDB backup pods. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/> for more information on how to set these. |
|
| services.couchdb.backup.resources | object | `{}` | The resources to use for CouchDB backup pods. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/> for more information on how to set these. |
|
||||||
|
|
|
@ -192,7 +192,14 @@ spec:
|
||||||
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
||||||
value: {{ .Values.services.tlsRejectUnauthorized }}
|
value: {{ .Values.services.tlsRejectUnauthorized }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{- if .Values.services.automationWorkers.enabled }}
|
||||||
|
- name: APP_FEATURES
|
||||||
|
value: "api"
|
||||||
|
{{- end }}
|
||||||
|
{{- range .Values.services.apps.extraEnv }}
|
||||||
|
- name: {{ .name }}
|
||||||
|
value: {{ .value | quote }}
|
||||||
|
{{- end }}
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
{{- if .Values.services.apps.startupProbe }}
|
{{- if .Values.services.apps.startupProbe }}
|
||||||
|
@ -220,6 +227,14 @@ spec:
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml . | nindent 10 }}
|
{{- toYaml . | nindent 10 }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Values.services.apps.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.apps.command | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.apps.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.apps.args | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
{{- with .Values.affinity }}
|
{{- with .Values.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
@ -237,4 +252,10 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
|
{{ if .Values.services.apps.ndots }}
|
||||||
|
dnsConfig:
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: {{ .Values.services.apps.ndots | quote }}
|
||||||
|
{{ end }}
|
||||||
status: {}
|
status: {}
|
||||||
|
|
|
@ -0,0 +1,262 @@
|
||||||
|
{{- if .Values.services.automationWorkers.enabled }}
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
{{ if .Values.services.automationWorkers.deploymentAnnotations }}
|
||||||
|
{{- toYaml .Values.services.automationWorkers.deploymentAnnotations | indent 4 -}}
|
||||||
|
{{ end }}
|
||||||
|
labels:
|
||||||
|
io.kompose.service: automation-worker-service
|
||||||
|
{{ if .Values.services.automationWorkers.deploymentLabels }}
|
||||||
|
{{- toYaml .Values.services.automationWorkers.deploymentLabels | indent 4 -}}
|
||||||
|
{{ end }}
|
||||||
|
name: automation-worker-service
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.services.automationWorkers.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
io.kompose.service: automation-worker-service
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
{{ if .Values.services.automationWorkers.templateAnnotations }}
|
||||||
|
{{- toYaml .Values.services.automationWorkers.templateAnnotations | indent 8 -}}
|
||||||
|
{{ end }}
|
||||||
|
labels:
|
||||||
|
io.kompose.service: automation-worker-service
|
||||||
|
{{ if .Values.services.automationWorkers.templateLabels }}
|
||||||
|
{{- toYaml .Values.services.automationWorkers.templateLabels | indent 8 -}}
|
||||||
|
{{ end }}
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- name: BUDIBASE_ENVIRONMENT
|
||||||
|
value: {{ .Values.globals.budibaseEnv }}
|
||||||
|
- name: DEPLOYMENT_ENVIRONMENT
|
||||||
|
value: "kubernetes"
|
||||||
|
- name: COUCH_DB_URL
|
||||||
|
{{ if .Values.services.couchdb.url }}
|
||||||
|
value: {{ .Values.services.couchdb.url }}
|
||||||
|
{{ else }}
|
||||||
|
value: http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.couchdb.enabled }}
|
||||||
|
- name: COUCH_DB_USER
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "couchdb.fullname" . }}
|
||||||
|
key: adminUsername
|
||||||
|
- name: COUCH_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "couchdb.fullname" . }}
|
||||||
|
key: adminPassword
|
||||||
|
{{ end }}
|
||||||
|
- name: ENABLE_ANALYTICS
|
||||||
|
value: {{ .Values.globals.enableAnalytics | quote }}
|
||||||
|
- name: API_ENCRYPTION_KEY
|
||||||
|
value: {{ .Values.globals.apiEncryptionKey | quote }}
|
||||||
|
- name: HTTP_LOGGING
|
||||||
|
value: {{ .Values.services.automationWorkers.httpLogging | quote }}
|
||||||
|
- name: INTERNAL_API_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: internalApiKey
|
||||||
|
- name: INTERNAL_API_KEY_FALLBACK
|
||||||
|
value: {{ .Values.globals.internalApiKeyFallback | quote }}
|
||||||
|
- name: JWT_SECRET
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: jwtSecret
|
||||||
|
- name: JWT_SECRET_FALLBACK
|
||||||
|
value: {{ .Values.globals.jwtSecretFallback | quote }}
|
||||||
|
{{ if .Values.services.objectStore.region }}
|
||||||
|
- name: AWS_REGION
|
||||||
|
value: {{ .Values.services.objectStore.region }}
|
||||||
|
{{ end }}
|
||||||
|
- name: MINIO_ENABLED
|
||||||
|
value: {{ .Values.services.objectStore.minio | quote }}
|
||||||
|
- name: MINIO_ACCESS_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: objectStoreAccess
|
||||||
|
- name: MINIO_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ template "budibase.fullname" . }}
|
||||||
|
key: objectStoreSecret
|
||||||
|
- name: CLOUDFRONT_CDN
|
||||||
|
value: {{ .Values.services.objectStore.cloudfront.cdn | quote }}
|
||||||
|
- name: CLOUDFRONT_PUBLIC_KEY_ID
|
||||||
|
value: {{ .Values.services.objectStore.cloudfront.publicKeyId | quote }}
|
||||||
|
- name: CLOUDFRONT_PRIVATE_KEY_64
|
||||||
|
value: {{ .Values.services.objectStore.cloudfront.privateKey64 | quote }}
|
||||||
|
- name: MINIO_URL
|
||||||
|
value: {{ .Values.services.objectStore.url }}
|
||||||
|
- name: PLUGIN_BUCKET_NAME
|
||||||
|
value: {{ .Values.services.objectStore.pluginBucketName | quote }}
|
||||||
|
- name: APPS_BUCKET_NAME
|
||||||
|
value: {{ .Values.services.objectStore.appsBucketName | quote }}
|
||||||
|
- name: GLOBAL_BUCKET_NAME
|
||||||
|
value: {{ .Values.services.objectStore.globalBucketName | quote }}
|
||||||
|
- name: BACKUPS_BUCKET_NAME
|
||||||
|
value: {{ .Values.services.objectStore.backupsBucketName | quote }}
|
||||||
|
- name: PORT
|
||||||
|
value: {{ .Values.services.automationWorkers.port | quote }}
|
||||||
|
{{ if .Values.services.worker.publicApiRateLimitPerSecond }}
|
||||||
|
- name: API_REQ_LIMIT_PER_SEC
|
||||||
|
value: {{ .Values.globals.automationWorkers.publicApiRateLimitPerSecond | quote }}
|
||||||
|
{{ end }}
|
||||||
|
- name: MULTI_TENANCY
|
||||||
|
value: {{ .Values.globals.multiTenancy | quote }}
|
||||||
|
- name: OFFLINE_MODE
|
||||||
|
value: {{ .Values.globals.offlineMode | quote }}
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: {{ .Values.services.automationWorkers.logLevel | quote }}
|
||||||
|
- name: REDIS_PASSWORD
|
||||||
|
value: {{ .Values.services.redis.password }}
|
||||||
|
- name: REDIS_URL
|
||||||
|
{{ if .Values.services.redis.url }}
|
||||||
|
value: {{ .Values.services.redis.url }}
|
||||||
|
{{ else }}
|
||||||
|
value: redis-service:{{ .Values.services.redis.port }}
|
||||||
|
{{ end }}
|
||||||
|
- name: SELF_HOSTED
|
||||||
|
value: {{ .Values.globals.selfHosted | quote }}
|
||||||
|
- name: POSTHOG_TOKEN
|
||||||
|
value: {{ .Values.globals.posthogToken | quote }}
|
||||||
|
- name: WORKER_URL
|
||||||
|
value: http://worker-service:{{ .Values.services.worker.port }}
|
||||||
|
- name: PLATFORM_URL
|
||||||
|
value: {{ .Values.globals.platformUrl | quote }}
|
||||||
|
- name: ACCOUNT_PORTAL_URL
|
||||||
|
value: {{ .Values.globals.accountPortalUrl | quote }}
|
||||||
|
- name: ACCOUNT_PORTAL_API_KEY
|
||||||
|
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
||||||
|
- name: COOKIE_DOMAIN
|
||||||
|
value: {{ .Values.globals.cookieDomain | quote }}
|
||||||
|
- name: HTTP_MIGRATIONS
|
||||||
|
value: {{ .Values.globals.httpMigrations | quote }}
|
||||||
|
- name: GOOGLE_CLIENT_ID
|
||||||
|
value: {{ .Values.globals.google.clientId | quote }}
|
||||||
|
- name: GOOGLE_CLIENT_SECRET
|
||||||
|
value: {{ .Values.globals.google.secret | quote }}
|
||||||
|
- name: AUTOMATION_MAX_ITERATIONS
|
||||||
|
value: {{ .Values.globals.automationMaxIterations | quote }}
|
||||||
|
- name: TENANT_FEATURE_FLAGS
|
||||||
|
value: {{ .Values.globals.tenantFeatureFlags | quote }}
|
||||||
|
- name: ENCRYPTION_KEY
|
||||||
|
value: {{ .Values.globals.bbEncryptionKey | quote }}
|
||||||
|
{{ if .Values.globals.bbAdminUserEmail }}
|
||||||
|
- name: BB_ADMIN_USER_EMAIL
|
||||||
|
value: {{ .Values.globals.bbAdminUserEmail | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.bbAdminUserPassword }}
|
||||||
|
- name: BB_ADMIN_USER_PASSWORD
|
||||||
|
value: {{ .Values.globals.bbAdminUserPassword | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.pluginsDir }}
|
||||||
|
- name: PLUGINS_DIR
|
||||||
|
value: {{ .Values.globals.pluginsDir | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.automationWorkers.nodeDebug }}
|
||||||
|
- name: NODE_DEBUG
|
||||||
|
value: {{ .Values.services.automationWorkers.nodeDebug | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.datadogApmEnabled }}
|
||||||
|
- name: DD_LOGS_INJECTION
|
||||||
|
value: {{ .Values.globals.datadogApmEnabled | quote }}
|
||||||
|
- name: DD_APM_ENABLED
|
||||||
|
value: {{ .Values.globals.datadogApmEnabled | quote }}
|
||||||
|
- name: DD_APM_DD_URL
|
||||||
|
value: https://trace.agent.datadoghq.eu
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.globalAgentHttpProxy }}
|
||||||
|
- name: GLOBAL_AGENT_HTTP_PROXY
|
||||||
|
value: {{ .Values.globals.globalAgentHttpProxy | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.globalAgentHttpsProxy }}
|
||||||
|
- name: GLOBAL_AGENT_HTTPS_PROXY
|
||||||
|
value: {{ .Values.globals.globalAgentHttpsProxy | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.globals.globalAgentNoProxy }}
|
||||||
|
- name: GLOBAL_AGENT_NO_PROXY
|
||||||
|
value: {{ .Values.globals.globalAgentNoProxy | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.tlsRejectUnauthorized }}
|
||||||
|
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
||||||
|
value: {{ .Values.services.tlsRejectUnauthorized }}
|
||||||
|
{{ end }}
|
||||||
|
- name: APP_FEATURES
|
||||||
|
value: "automations"
|
||||||
|
{{- range .Values.services.automationWorkers.extraEnv }}
|
||||||
|
- name: {{ .name }}
|
||||||
|
value: {{ .value | quote }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
|
imagePullPolicy: Always
|
||||||
|
{{- if .Values.services.automationWorkers.startupProbe }}
|
||||||
|
{{- with .Values.services.automationWorkers.startupProbe }}
|
||||||
|
startupProbe:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.services.automationWorkers.livenessProbe }}
|
||||||
|
{{- with .Values.services.automationWorkers.livenessProbe }}
|
||||||
|
livenessProbe:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.services.automationWorkers.readinessProbe }}
|
||||||
|
{{- with .Values.services.automationWorkers.readinessProbe }}
|
||||||
|
readinessProbe:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
name: bbautomationworker
|
||||||
|
ports:
|
||||||
|
- containerPort: {{ .Values.services.automationWorkers.port }}
|
||||||
|
{{ with .Values.services.automationWorkers.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.automationWorkers.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.automationWorkers.command | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.automationWorkers.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.automationWorkers.args | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{ if .Values.schedulerName }}
|
||||||
|
schedulerName: {{ .Values.schedulerName | quote }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml .Values.imagePullSecrets | nindent 6 }}
|
||||||
|
{{ end }}
|
||||||
|
restartPolicy: Always
|
||||||
|
serviceAccountName: ""
|
||||||
|
{{ if .Values.services.automationWorkers.ndots }}
|
||||||
|
dnsConfig:
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: {{ .Values.services.automationWorkers.ndots | quote }}
|
||||||
|
{{ end }}
|
||||||
|
status: {}
|
||||||
|
{{- end }}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{{- if .Values.services.automationWorkers.autoscaling.enabled }}
|
||||||
|
apiVersion: {{ ternary "autoscaling/v2" "autoscaling/v2beta2" (.Capabilities.APIVersions.Has "autoscaling/v2") }}
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "budibase.fullname" . }}-apps
|
||||||
|
labels:
|
||||||
|
{{- include "budibase.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: automation-worker-service
|
||||||
|
minReplicas: {{ .Values.services.automationWorkers.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.services.automationWorkers.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
{{- if .Values.services.automationWorkers.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: {{ .Values.services.automationWorkers.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.services.automationWorkers.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: {{ .Values.services.automationWorkers.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
|
@ -100,5 +100,19 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
|
{{ if .Values.services.proxy.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.proxy.command | nindent 8 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.proxy.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.proxy.args | nindent 8 }}
|
||||||
|
{{ end }}
|
||||||
volumes:
|
volumes:
|
||||||
|
{{ if .Values.services.proxy.ndots }}
|
||||||
|
dnsConfig:
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: {{ .Values.services.proxy.ndots | quote }}
|
||||||
|
{{ end }}
|
||||||
status: {}
|
status: {}
|
||||||
|
|
|
@ -182,6 +182,10 @@ spec:
|
||||||
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
- name: NODE_TLS_REJECT_UNAUTHORIZED
|
||||||
value: {{ .Values.services.tlsRejectUnauthorized }}
|
value: {{ .Values.services.tlsRejectUnauthorized }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{- range .Values.services.worker.extraEnv }}
|
||||||
|
- name: {{ .name }}
|
||||||
|
value: {{ .value | quote }}
|
||||||
|
{{- end }}
|
||||||
image: budibase/worker:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
image: budibase/worker:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
{{- if .Values.services.worker.startupProbe }}
|
{{- if .Values.services.worker.startupProbe }}
|
||||||
|
@ -209,6 +213,14 @@ spec:
|
||||||
resources:
|
resources:
|
||||||
{{- toYaml . | nindent 10 }}
|
{{- toYaml . | nindent 10 }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if .Values.services.worker.command }}
|
||||||
|
command:
|
||||||
|
{{- toYaml .Values.services.worker.command | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Values.services.worker.args }}
|
||||||
|
args:
|
||||||
|
{{- toYaml .Values.services.worker.args | nindent 10 }}
|
||||||
|
{{ end }}
|
||||||
{{- with .Values.affinity }}
|
{{- with .Values.affinity }}
|
||||||
affinity:
|
affinity:
|
||||||
{{- toYaml . | nindent 8 }}
|
{{- toYaml . | nindent 8 }}
|
||||||
|
@ -226,4 +238,10 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
restartPolicy: Always
|
restartPolicy: Always
|
||||||
serviceAccountName: ""
|
serviceAccountName: ""
|
||||||
|
{{ if .Values.services.worker.ndots }}
|
||||||
|
dnsConfig:
|
||||||
|
options:
|
||||||
|
- name: ndots
|
||||||
|
value: {{ .Values.services.worker.ndots | quote }}
|
||||||
|
{{ end }}
|
||||||
status: {}
|
status: {}
|
||||||
|
|
|
@ -220,6 +220,9 @@ services:
|
||||||
# <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/>
|
# <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/>
|
||||||
# for more information on how to set these.
|
# for more information on how to set these.
|
||||||
resources: {}
|
resources: {}
|
||||||
|
# -- Extra environment variables to set for apps pods. Takes a list of
|
||||||
|
# name=value pairs.
|
||||||
|
extraEnv: []
|
||||||
# -- Startup probe configuration for apps pods. You shouldn't need to
|
# -- Startup probe configuration for apps pods. You shouldn't need to
|
||||||
# change this, but if you want to you can find more information here:
|
# change this, but if you want to you can find more information here:
|
||||||
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
||||||
|
@ -272,6 +275,78 @@ services:
|
||||||
# and resources set for the apps pods.
|
# and resources set for the apps pods.
|
||||||
targetCPUUtilizationPercentage: 80
|
targetCPUUtilizationPercentage: 80
|
||||||
|
|
||||||
|
automationWorkers:
|
||||||
|
# -- Whether or not to enable the automation worker service. If you disable this,
|
||||||
|
# automations will be processed by the apps service.
|
||||||
|
enabled: true
|
||||||
|
# @ignore (you shouldn't need to change this)
|
||||||
|
port: 4002
|
||||||
|
# -- The number of automation worker replicas to run.
|
||||||
|
replicaCount: 1
|
||||||
|
# -- The log level for the automation worker service.
|
||||||
|
logLevel: info
|
||||||
|
# -- The resources to use for automation worker pods. See
|
||||||
|
# <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/>
|
||||||
|
# for more information on how to set these.
|
||||||
|
resources: {}
|
||||||
|
# -- Extra environment variables to set for automation worker pods. Takes a list of
|
||||||
|
# name=value pairs.
|
||||||
|
extraEnv: []
|
||||||
|
# -- Startup probe configuration for automation worker pods. You shouldn't
|
||||||
|
# need to change this, but if you want to you can find more information
|
||||||
|
# here:
|
||||||
|
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
||||||
|
# @default -- HTTP health checks.
|
||||||
|
startupProbe:
|
||||||
|
# @ignore
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4002
|
||||||
|
scheme: HTTP
|
||||||
|
# @ignore
|
||||||
|
failureThreshold: 30
|
||||||
|
# @ignore
|
||||||
|
periodSeconds: 3
|
||||||
|
# -- Readiness probe configuration for automation worker pods. You shouldn't
|
||||||
|
# need to change this, but if you want to you can find more information
|
||||||
|
# here:
|
||||||
|
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
||||||
|
# @default -- HTTP health checks.
|
||||||
|
readinessProbe:
|
||||||
|
# @ignore
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4002
|
||||||
|
scheme: HTTP
|
||||||
|
# @ignore
|
||||||
|
periodSeconds: 3
|
||||||
|
# @ignore
|
||||||
|
failureThreshold: 1
|
||||||
|
# -- Liveness probe configuration for automation worker pods. You shouldn't
|
||||||
|
# need to change this, but if you want to you can find more information
|
||||||
|
# here:
|
||||||
|
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
||||||
|
# @default -- HTTP health checks.
|
||||||
|
livenessProbe:
|
||||||
|
# @ignore
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4002
|
||||||
|
scheme: HTTP
|
||||||
|
# @ignore
|
||||||
|
failureThreshold: 3
|
||||||
|
# @ignore
|
||||||
|
periodSeconds: 30
|
||||||
|
autoscaling:
|
||||||
|
# -- Whether to enable horizontal pod autoscaling for the apps service.
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 10
|
||||||
|
# -- Target CPU utilization percentage for the automation worker service.
|
||||||
|
# Note that for autoscaling to work, you will need to have metrics-server
|
||||||
|
# configured, and resources set for the automation worker pods.
|
||||||
|
targetCPUUtilizationPercentage: 80
|
||||||
|
|
||||||
worker:
|
worker:
|
||||||
# @ignore (you shouldn't need to change this)
|
# @ignore (you shouldn't need to change this)
|
||||||
port: 4003
|
port: 4003
|
||||||
|
@ -285,6 +360,9 @@ services:
|
||||||
# <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/>
|
# <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/>
|
||||||
# for more information on how to set these.
|
# for more information on how to set these.
|
||||||
resources: {}
|
resources: {}
|
||||||
|
# -- Extra environment variables to set for worker pods. Takes a list of
|
||||||
|
# name=value pairs.
|
||||||
|
extraEnv: []
|
||||||
# -- Startup probe configuration for worker pods. You shouldn't need to
|
# -- Startup probe configuration for worker pods. You shouldn't need to
|
||||||
# change this, but if you want to you can find more information here:
|
# change this, but if you want to you can find more information here:
|
||||||
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
# <https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/>
|
||||||
|
|
|
@ -84,13 +84,13 @@ Component libraries are collections of components as well as the definition of t
|
||||||
|
|
||||||
- If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
|
- If the project diverges from your branch, please rebase instead of merging. This makes the commit graph easier to read.
|
||||||
|
|
||||||
- Once your work is completed, please raise a PR against the `develop` branch with some information about what has changed and why.
|
- Once your work is completed, please raise a PR against the `master` branch with some information about what has changed and why.
|
||||||
|
|
||||||
### Getting Started For Contributors
|
### Getting Started For Contributors
|
||||||
|
|
||||||
#### 1. Prerequisites
|
#### 1. Prerequisites
|
||||||
|
|
||||||
- NodeJS version `18.x.x`
|
- NodeJS version `20.x.x`
|
||||||
- Python version `3.x`
|
- Python version `3.x`
|
||||||
|
|
||||||
### Using asdf (recommended)
|
### Using asdf (recommended)
|
||||||
|
@ -246,7 +246,7 @@ From here - to develop a change in pro, you can follow the below flow:
|
||||||
cd packages/pro
|
cd packages/pro
|
||||||
# get the base branch you are working from (same as monorepo)
|
# get the base branch you are working from (same as monorepo)
|
||||||
git fetch
|
git fetch
|
||||||
git checkout <develop | master>
|
git checkout master
|
||||||
# create a branch, named the same as the branch in your monorepo
|
# create a branch, named the same as the branch in your monorepo
|
||||||
git checkout -b <some branch>
|
git checkout -b <some branch>
|
||||||
... make changes
|
... make changes
|
||||||
|
|
|
@ -22,6 +22,6 @@
|
||||||
"@types/react": "17.0.39",
|
"@types/react": "17.0.39",
|
||||||
"eslint": "8.10.0",
|
"eslint": "8.10.0",
|
||||||
"eslint-config-next": "12.1.0",
|
"eslint-config-next": "12.1.0",
|
||||||
"typescript": "4.6.2"
|
"typescript": "5.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -30,10 +30,18 @@ elif [[ "${TARGETBUILD}" = "single" ]]; then
|
||||||
# mount, so we use that for all persistent data.
|
# mount, so we use that for all persistent data.
|
||||||
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
||||||
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
||||||
|
elif [[ "${TARGETBUILD}" = "docker-compose" ]]; then
|
||||||
|
# We remove the database_dir and view_index_dir settings from the local.ini
|
||||||
|
# in docker-compose because it will default to /opt/couchdb/data which is what
|
||||||
|
# our docker-compose was using prior to us switching to using our own CouchDB
|
||||||
|
# image.
|
||||||
|
sed -i "s#^database_dir.*\$##g" /opt/couchdb/etc/local.ini
|
||||||
|
sed -i "s#^view_index_dir.*\$##g" /opt/couchdb/etc/local.ini
|
||||||
|
sed -i "s#^dir=.*\$#dir=/opt/couchdb/data#g" /opt/clouseau/clouseau.ini
|
||||||
elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then
|
elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then
|
||||||
# In Kubernetes the directory /opt/couchdb/data has a persistent volume
|
# In Kubernetes the directory /opt/couchdb/data has a persistent volume
|
||||||
# mount for storing database data.
|
# mount for storing database data.
|
||||||
sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini
|
sed -i "s#^dir=.*\$#dir=/opt/couchdb/data#g" /opt/clouseau/clouseau.ini
|
||||||
|
|
||||||
# We remove the database_dir and view_index_dir settings from the local.ini
|
# We remove the database_dir and view_index_dir settings from the local.ini
|
||||||
# in Kubernetes because it will default to /opt/couchdb/data which is what
|
# in Kubernetes because it will default to /opt/couchdb/data which is what
|
||||||
|
|
|
@ -57,7 +57,6 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis-service
|
- redis-service
|
||||||
- minio-service
|
- minio-service
|
||||||
- couch-init
|
|
||||||
|
|
||||||
minio-service:
|
minio-service:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -70,7 +69,7 @@ services:
|
||||||
MINIO_BROWSER: "off"
|
MINIO_BROWSER: "off"
|
||||||
command: server /data --console-address ":9001"
|
command: server /data --console-address ":9001"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
test: "timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1"
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 20s
|
timeout: 20s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
@ -98,30 +97,19 @@ services:
|
||||||
|
|
||||||
couchdb-service:
|
couchdb-service:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: ibmcom/couchdb3
|
image: budibase/couchdb
|
||||||
|
pull_policy: always
|
||||||
environment:
|
environment:
|
||||||
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
|
||||||
- COUCHDB_USER=${COUCH_DB_USER}
|
- COUCHDB_USER=${COUCH_DB_USER}
|
||||||
|
- TARGETBUILD=docker-compose
|
||||||
volumes:
|
volumes:
|
||||||
- couchdb3_data:/opt/couchdb/data
|
- couchdb3_data:/opt/couchdb/data
|
||||||
|
|
||||||
couch-init:
|
|
||||||
image: curlimages/curl
|
|
||||||
environment:
|
|
||||||
PUT_CALL: "curl -u ${COUCH_DB_USER}:${COUCH_DB_PASSWORD} -X PUT couchdb-service:5984"
|
|
||||||
depends_on:
|
|
||||||
- couchdb-service
|
|
||||||
command:
|
|
||||||
[
|
|
||||||
"sh",
|
|
||||||
"-c",
|
|
||||||
"sleep 10 && $${PUT_CALL}/_users && $${PUT_CALL}/_replicator; fg;",
|
|
||||||
]
|
|
||||||
|
|
||||||
redis-service:
|
redis-service:
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
image: redis
|
image: redis
|
||||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
command: redis-server --requirepass "${REDIS_PASSWORD}"
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
|
|
||||||
|
|
|
@ -257,6 +257,7 @@ http {
|
||||||
|
|
||||||
access_log off;
|
access_log off;
|
||||||
allow 127.0.0.1;
|
allow 127.0.0.1;
|
||||||
|
allow 10.0.0.0/8;
|
||||||
deny all;
|
deny all;
|
||||||
|
|
||||||
location /nginx_status {
|
location /nginx_status {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:18-slim as build
|
FROM node:20-slim as build
|
||||||
|
|
||||||
# install node-gyp dependencies
|
# install node-gyp dependencies
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq
|
RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq
|
||||||
|
@ -42,7 +42,7 @@ COPY packages/string-templates packages/string-templates
|
||||||
FROM budibase/couchdb as runner
|
FROM budibase/couchdb as runner
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV TARGETARCH $TARGETARCH
|
ENV TARGETARCH $TARGETARCH
|
||||||
ENV NODE_MAJOR 18
|
ENV NODE_MAJOR 20
|
||||||
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
||||||
# e.g. docker build --build-arg TARGETBUILD=aas ....
|
# e.g. docker build --build-arg TARGETBUILD=aas ....
|
||||||
ARG TARGETBUILD=single
|
ARG TARGETBUILD=single
|
||||||
|
|
|
@ -7,7 +7,7 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
|
||||||
[[ -z "${BUDIBASE_ENVIRONMENT}" ]] && export BUDIBASE_ENVIRONMENT=PRODUCTION
|
[[ -z "${BUDIBASE_ENVIRONMENT}" ]] && export BUDIBASE_ENVIRONMENT=PRODUCTION
|
||||||
[[ -z "${CLUSTER_PORT}" ]] && export CLUSTER_PORT=80
|
[[ -z "${CLUSTER_PORT}" ]] && export CLUSTER_PORT=80
|
||||||
[[ -z "${DEPLOYMENT_ENVIRONMENT}" ]] && export DEPLOYMENT_ENVIRONMENT=docker
|
[[ -z "${DEPLOYMENT_ENVIRONMENT}" ]] && export DEPLOYMENT_ENVIRONMENT=docker
|
||||||
[[ -z "${MINIO_URL}" ]] && export MINIO_URL=http://127.0.0.1:9000
|
[[ -z "${MINIO_URL}" ]] && [[ -z "${USE_S3}" ]] && export MINIO_URL=http://127.0.0.1:9000
|
||||||
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
|
[[ -z "${NODE_ENV}" ]] && export NODE_ENV=production
|
||||||
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
[[ -z "${POSTHOG_TOKEN}" ]] && export POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||||
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
|
[[ -z "${TENANT_FEATURE_FLAGS}" ]] && export TENANT_FEATURE_FLAGS="*:LICENSING,*:USER_GROUPS,*:ONBOARDING_TOUR"
|
||||||
|
@ -77,7 +77,12 @@ mkdir -p ${DATA_DIR}/minio
|
||||||
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
||||||
/bbcouch-runner.sh &
|
/bbcouch-runner.sh &
|
||||||
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
|
||||||
|
# only start minio if use s3 isn't passed
|
||||||
|
if [[ -z "${USE_S3}" ]]; then
|
||||||
|
/minio/minio server --console-address ":9001" ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
||||||
|
fi
|
||||||
|
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
# Add monthly cron job to renew certbot certificate
|
# Add monthly cron job to renew certbot certificate
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
{
|
{
|
||||||
"version": "2.13.35",
|
"version": "2.14.2",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*",
|
||||||
|
"!packages/account-portal",
|
||||||
|
"packages/account-portal/packages/*"
|
||||||
],
|
],
|
||||||
"useNx": true,
|
"useNx": true,
|
||||||
|
"concurrency": 20,
|
||||||
"command": {
|
"command": {
|
||||||
"publish": {
|
"publish": {
|
||||||
"ignoreChanges": [
|
"ignoreChanges": [
|
||||||
|
|
35
package.json
35
package.json
|
@ -6,25 +6,26 @@
|
||||||
"@babel/eslint-parser": "^7.22.5",
|
"@babel/eslint-parser": "^7.22.5",
|
||||||
"@babel/preset-env": "^7.22.5",
|
"@babel/preset-env": "^7.22.5",
|
||||||
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
||||||
"@typescript-eslint/parser": "6.7.2",
|
"@types/node": "20.10.0",
|
||||||
|
"@typescript-eslint/parser": "6.9.0",
|
||||||
"esbuild": "^0.18.17",
|
"esbuild": "^0.18.17",
|
||||||
"esbuild-node-externals": "^1.8.0",
|
"esbuild-node-externals": "^1.8.0",
|
||||||
"eslint": "^8.44.0",
|
"eslint": "^8.52.0",
|
||||||
"eslint-plugin-import": "^2.29.0",
|
"eslint-plugin-import": "^2.29.0",
|
||||||
"eslint-plugin-local-rules": "^2.0.0",
|
"eslint-plugin-local-rules": "^2.0.0",
|
||||||
"eslint-plugin-svelte": "^2.32.2",
|
"eslint-plugin-svelte": "^2.34.0",
|
||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"kill-port": "^1.6.1",
|
"kill-port": "^1.6.1",
|
||||||
"lerna": "7.1.1",
|
"lerna": "7.1.1",
|
||||||
"madge": "^6.0.0",
|
"madge": "^6.0.0",
|
||||||
"minimist": "^1.2.8",
|
|
||||||
"nx": "16.4.3",
|
"nx": "16.4.3",
|
||||||
"nx-cloud": "16.0.5",
|
"nx-cloud": "16.0.5",
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"prettier-plugin-svelte": "^2.3.0",
|
"prettier-plugin-svelte": "^2.3.0",
|
||||||
"svelte": "3.49.0",
|
"svelte": "3.49.0",
|
||||||
"svelte-eslint-parser": "^0.32.0",
|
"svelte-eslint-parser": "^0.33.1",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node scripts/syncProPackage.js",
|
"preinstall": "node scripts/syncProPackage.js",
|
||||||
|
@ -39,13 +40,16 @@
|
||||||
"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",
|
||||||
"nuke:docker": "lerna run --stream dev:stack:nuke",
|
"nuke:docker": "lerna run --stream dev:stack:nuke",
|
||||||
"clean": "lerna clean -y",
|
"clean": "lerna clean -y && echo Cleaning top level node modules 🧹 && rm -rf ./node_modules && echo Done! 🚀",
|
||||||
"kill-builder": "kill-port 3000",
|
"kill-builder": "kill-port 3000",
|
||||||
"kill-server": "kill-port 4001 4002",
|
"kill-server": "kill-port 4001 4002",
|
||||||
"kill-all": "yarn run kill-builder && yarn run kill-server",
|
"kill-accountportal": "kill-port 3001 4003",
|
||||||
"dev": "yarn run kill-all && lerna run --parallel prebuild && lerna run --stream dev:builder",
|
"kill-all": "yarn run kill-builder && yarn run kill-server && yarn kill-accountportal",
|
||||||
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker",
|
"dev": "yarn run kill-all && lerna run --parallel prebuild && lerna run --stream dev --ignore=@budibase/account-portal-ui --ignore @budibase/account-portal-server",
|
||||||
"dev:server": "yarn run kill-server && lerna run --stream dev:builder --scope @budibase/worker --scope @budibase/server",
|
"dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream dev --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker --ignore=@budibase/account-portal-ui --ignore @budibase/account-portal-server",
|
||||||
|
"dev:server": "yarn run kill-server && lerna run --stream dev --scope @budibase/worker --scope @budibase/server",
|
||||||
|
"dev:accountportal": "yarn kill-accountportal && lerna run dev --stream --scope @budibase/account-portal-ui --scope @budibase/account-portal-server",
|
||||||
|
"dev:all": "yarn run kill-all && lerna run --stream dev",
|
||||||
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
"dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream dev:built",
|
||||||
"dev:docker": "yarn build --scope @budibase/server --scope @budibase/worker && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
"dev:docker": "yarn build --scope @budibase/server --scope @budibase/worker && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0",
|
||||||
"test": "lerna run --stream test --stream",
|
"test": "lerna run --stream test --stream",
|
||||||
|
@ -79,11 +83,14 @@
|
||||||
"security:audit": "node scripts/audit.js",
|
"security:audit": "node scripts/audit.js",
|
||||||
"postinstall": "husky install",
|
"postinstall": "husky install",
|
||||||
"submodules:load": "git submodule init && git submodule update && yarn",
|
"submodules:load": "git submodule init && git submodule update && yarn",
|
||||||
"submodules:unload": "git submodule deinit --all && yarn"
|
"submodules:unload": "git submodule deinit --all && yarn",
|
||||||
|
"add-app-migration": "node scripts/add-app-migration.js --title"
|
||||||
},
|
},
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*",
|
||||||
|
"!packages/account-portal",
|
||||||
|
"packages/account-portal/packages/*"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
@ -93,7 +100,7 @@
|
||||||
"@budibase/types": "0.0.0"
|
"@budibase/types": "0.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0 <19.0.0"
|
"node": ">=20.0.0 <21.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit b11e6b47370d9b77c63648b45929c86bfed6360c
|
|
@ -21,7 +21,7 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/nano": "10.1.3",
|
"@budibase/nano": "10.1.4",
|
||||||
"@budibase/pouchdb-replication-stream": "1.2.10",
|
"@budibase/pouchdb-replication-stream": "1.2.10",
|
||||||
"@budibase/shared-core": "0.0.0",
|
"@budibase/shared-core": "0.0.0",
|
||||||
"@budibase/types": "0.0.0",
|
"@budibase/types": "0.0.0",
|
||||||
|
@ -32,6 +32,7 @@
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bull": "4.10.1",
|
"bull": "4.10.1",
|
||||||
"correlation-id": "4.0.0",
|
"correlation-id": "4.0.0",
|
||||||
|
"dd-trace": "3.13.2",
|
||||||
"dotenv": "16.0.1",
|
"dotenv": "16.0.1",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
|
@ -64,7 +65,6 @@
|
||||||
"@types/cookies": "0.7.8",
|
"@types/cookies": "0.7.8",
|
||||||
"@types/jest": "29.5.5",
|
"@types/jest": "29.5.5",
|
||||||
"@types/lodash": "4.14.200",
|
"@types/lodash": "4.14.200",
|
||||||
"@types/node": "18.17.0",
|
|
||||||
"@types/node-fetch": "2.6.4",
|
"@types/node-fetch": "2.6.4",
|
||||||
"@types/pouchdb": "6.4.0",
|
"@types/pouchdb": "6.4.0",
|
||||||
"@types/redlock": "4.0.3",
|
"@types/redlock": "4.0.3",
|
||||||
|
@ -73,8 +73,8 @@
|
||||||
"@types/uuid": "8.3.4",
|
"@types/uuid": "8.3.4",
|
||||||
"chance": "1.1.8",
|
"chance": "1.1.8",
|
||||||
"ioredis-mock": "8.9.0",
|
"ioredis-mock": "8.9.0",
|
||||||
"jest": "29.6.2",
|
"jest": "29.7.0",
|
||||||
"jest-environment-node": "29.6.2",
|
"jest-environment-node": "29.7.0",
|
||||||
"jest-serial-runner": "1.2.1",
|
"jest-serial-runner": "1.2.1",
|
||||||
"pino-pretty": "10.0.0",
|
"pino-pretty": "10.0.0",
|
||||||
"pouchdb-adapter-memory": "7.2.2",
|
"pouchdb-adapter-memory": "7.2.2",
|
||||||
|
|
|
@ -18,14 +18,15 @@ export enum TTL {
|
||||||
ONE_DAY = 86400,
|
ONE_DAY = 86400,
|
||||||
}
|
}
|
||||||
|
|
||||||
function performExport(funcName: string) {
|
export const keys = (...args: Parameters<typeof GENERIC.keys>) =>
|
||||||
// @ts-ignore
|
GENERIC.keys(...args)
|
||||||
return (...args: any) => GENERIC[funcName](...args)
|
export const get = (...args: Parameters<typeof GENERIC.get>) =>
|
||||||
}
|
GENERIC.get(...args)
|
||||||
|
export const store = (...args: Parameters<typeof GENERIC.store>) =>
|
||||||
export const keys = performExport("keys")
|
GENERIC.store(...args)
|
||||||
export const get = performExport("get")
|
export const destroy = (...args: Parameters<typeof GENERIC.delete>) =>
|
||||||
export const store = performExport("store")
|
GENERIC.delete(...args)
|
||||||
export const destroy = performExport("delete")
|
export const withCache = (...args: Parameters<typeof GENERIC.withCache>) =>
|
||||||
export const withCache = performExport("withCache")
|
GENERIC.withCache(...args)
|
||||||
export const bustCache = performExport("bustCache")
|
export const bustCache = (...args: Parameters<typeof GENERIC.bustCache>) =>
|
||||||
|
GENERIC.bustCache(...args)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as redis from "../redis/init"
|
import * as redis from "../redis/init"
|
||||||
import * as utils from "../utils"
|
import * as utils from "../utils"
|
||||||
import { Duration, DurationType } from "../utils"
|
import { Duration } from "../utils"
|
||||||
|
|
||||||
const TTL_SECONDS = Duration.fromHours(1).toSeconds()
|
const TTL_SECONDS = Duration.fromHours(1).toSeconds()
|
||||||
|
|
||||||
|
@ -32,7 +32,18 @@ export async function getCode(code: string): Promise<PasswordReset> {
|
||||||
const client = await redis.getPasswordResetClient()
|
const client = await redis.getPasswordResetClient()
|
||||||
const value = (await client.get(code)) as PasswordReset | undefined
|
const value = (await client.get(code)) as PasswordReset | undefined
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw "Provided information is not valid, cannot reset password - please try again."
|
throw new Error(
|
||||||
|
"Provided information is not valid, cannot reset password - please try again."
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a reset code this will invalidate it.
|
||||||
|
* @param code The code provided via the email link.
|
||||||
|
*/
|
||||||
|
export async function invalidateCode(code: string): Promise<void> {
|
||||||
|
const client = await redis.getPasswordResetClient()
|
||||||
|
await client.delete(code)
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { DBTestConfiguration } from "../../../tests/extra"
|
import { DBTestConfiguration } from "../../../tests/extra"
|
||||||
import {
|
import { structures } from "../../../tests"
|
||||||
structures,
|
|
||||||
expectFunctionWasCalledTimesWith,
|
|
||||||
mocks,
|
|
||||||
} from "../../../tests"
|
|
||||||
import { Writethrough } from "../writethrough"
|
import { Writethrough } from "../writethrough"
|
||||||
import { getDB } from "../../db"
|
import { getDB } from "../../db"
|
||||||
|
import { Document } from "@budibase/types"
|
||||||
import tk from "timekeeper"
|
import tk from "timekeeper"
|
||||||
|
|
||||||
tk.freeze(Date.now())
|
tk.freeze(Date.now())
|
||||||
|
|
||||||
|
interface ValueDoc extends Document {
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
|
||||||
const DELAY = 5000
|
const DELAY = 5000
|
||||||
|
|
||||||
describe("writethrough", () => {
|
describe("writethrough", () => {
|
||||||
|
@ -117,7 +118,7 @@ describe("writethrough", () => {
|
||||||
describe("get", () => {
|
describe("get", () => {
|
||||||
it("should be able to retrieve", async () => {
|
it("should be able to retrieve", async () => {
|
||||||
await config.doInTenant(async () => {
|
await config.doInTenant(async () => {
|
||||||
const response = await writethrough.get(docId)
|
const response = await writethrough.get<ValueDoc>(docId)
|
||||||
expect(response.value).toBe(4)
|
expect(response.value).toBe(4)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,7 +7,7 @@ import * as locks from "../redis/redlockImpl"
|
||||||
const DEFAULT_WRITE_RATE_MS = 10000
|
const DEFAULT_WRITE_RATE_MS = 10000
|
||||||
let CACHE: BaseCache | null = null
|
let CACHE: BaseCache | null = null
|
||||||
|
|
||||||
interface CacheItem {
|
interface CacheItem<T extends Document> {
|
||||||
doc: any
|
doc: any
|
||||||
lastWrite: number
|
lastWrite: number
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,10 @@ function makeCacheKey(db: Database, key: string) {
|
||||||
return db.name + key
|
return db.name + key
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCacheItem(doc: any, lastWrite: number | null = null): CacheItem {
|
function makeCacheItem<T extends Document>(
|
||||||
|
doc: T,
|
||||||
|
lastWrite: number | null = null
|
||||||
|
): CacheItem<T> {
|
||||||
return { doc, lastWrite: lastWrite || Date.now() }
|
return { doc, lastWrite: lastWrite || Date.now() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +38,7 @@ async function put(
|
||||||
) {
|
) {
|
||||||
const cache = await getCache()
|
const cache = await getCache()
|
||||||
const key = doc._id
|
const key = doc._id
|
||||||
let cacheItem: CacheItem | undefined
|
let cacheItem: CacheItem<any> | undefined
|
||||||
if (key) {
|
if (key) {
|
||||||
cacheItem = await cache.get(makeCacheKey(db, key))
|
cacheItem = await cache.get(makeCacheKey(db, key))
|
||||||
}
|
}
|
||||||
|
@ -53,11 +56,8 @@ async function put(
|
||||||
const writeDb = async (toWrite: any) => {
|
const writeDb = async (toWrite: any) => {
|
||||||
// doc should contain the _id and _rev
|
// doc should contain the _id and _rev
|
||||||
const response = await db.put(toWrite, { force: true })
|
const response = await db.put(toWrite, { force: true })
|
||||||
output = {
|
output._id = response.id
|
||||||
...doc,
|
output._rev = response.rev
|
||||||
_id: response.id,
|
|
||||||
_rev: response.rev,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await writeDb(doc)
|
await writeDb(doc)
|
||||||
|
@ -84,12 +84,12 @@ async function put(
|
||||||
return { ok: true, id: output._id, rev: output._rev }
|
return { ok: true, id: output._id, rev: output._rev }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get(db: Database, id: string): Promise<any> {
|
async function get<T extends Document>(db: Database, id: string): Promise<T> {
|
||||||
const cache = await getCache()
|
const cache = await getCache()
|
||||||
const cacheKey = makeCacheKey(db, id)
|
const cacheKey = makeCacheKey(db, id)
|
||||||
let cacheItem: CacheItem = await cache.get(cacheKey)
|
let cacheItem: CacheItem<T> = await cache.get(cacheKey)
|
||||||
if (!cacheItem) {
|
if (!cacheItem) {
|
||||||
const doc = await db.get(id)
|
const doc = await db.get<T>(id)
|
||||||
cacheItem = makeCacheItem(doc)
|
cacheItem = makeCacheItem(doc)
|
||||||
await cache.store(cacheKey, cacheItem)
|
await cache.store(cacheKey, cacheItem)
|
||||||
}
|
}
|
||||||
|
@ -123,8 +123,8 @@ export class Writethrough {
|
||||||
return put(this.db, doc, writeRateMs)
|
return put(this.db, doc, writeRateMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: string) {
|
async get<T extends Document>(id: string) {
|
||||||
return get(this.db, id)
|
return get<T>(this.db, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove(docOrId: any, rev?: any) {
|
async remove(docOrId: any, rev?: any) {
|
||||||
|
|
|
@ -11,24 +11,7 @@ export enum Cookie {
|
||||||
OIDC_CONFIG = "budibase:oidc:config",
|
OIDC_CONFIG = "budibase:oidc:config",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Header {
|
export { Header } from "@budibase/shared-core"
|
||||||
API_KEY = "x-budibase-api-key",
|
|
||||||
LICENSE_KEY = "x-budibase-license-key",
|
|
||||||
API_VER = "x-budibase-api-version",
|
|
||||||
APP_ID = "x-budibase-app-id",
|
|
||||||
SESSION_ID = "x-budibase-session-id",
|
|
||||||
TYPE = "x-budibase-type",
|
|
||||||
PREVIEW_ROLE = "x-budibase-role",
|
|
||||||
TENANT_ID = "x-budibase-tenant-id",
|
|
||||||
VERIFICATION_CODE = "x-budibase-verification-code",
|
|
||||||
RETURN_VERIFICATION_CODE = "x-budibase-return-verification-code",
|
|
||||||
RESET_PASSWORD_CODE = "x-budibase-reset-password-code",
|
|
||||||
RETURN_RESET_PASSWORD_CODE = "x-budibase-return-reset-password-code",
|
|
||||||
TOKEN = "x-budibase-token",
|
|
||||||
CSRF_TOKEN = "x-csrf-token",
|
|
||||||
CORRELATION_ID = "x-budibase-correlation-id",
|
|
||||||
AUTHORIZATION = "authorization",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum GlobalRole {
|
export enum GlobalRole {
|
||||||
OWNER = "owner",
|
OWNER = "owner",
|
||||||
|
|
|
@ -134,7 +134,7 @@ export async function doInContext(appId: string, task: any): Promise<any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doInTenant<T>(
|
export async function doInTenant<T>(
|
||||||
tenantId: string | null,
|
tenantId: string | undefined,
|
||||||
task: () => T
|
task: () => T
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
// make sure default always selected in single tenancy
|
// make sure default always selected in single tenancy
|
||||||
|
@ -335,3 +335,11 @@ export function isScim(): boolean {
|
||||||
const scimCall = context?.isScim
|
const scimCall = context?.isScim
|
||||||
return !!scimCall
|
return !!scimCall
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCurrentContext(): ContextMap | undefined {
|
||||||
|
try {
|
||||||
|
return Context.get()
|
||||||
|
} catch (e) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { IdentityContext } from "@budibase/types"
|
import { IdentityContext } from "@budibase/types"
|
||||||
|
import { ExecutionTimeTracker } from "../timers"
|
||||||
|
|
||||||
// keep this out of Budibase types, don't want to expose context info
|
// keep this out of Budibase types, don't want to expose context info
|
||||||
export type ContextMap = {
|
export type ContextMap = {
|
||||||
|
@ -9,4 +10,5 @@ export type ContextMap = {
|
||||||
isScim?: boolean
|
isScim?: boolean
|
||||||
automationId?: string
|
automationId?: string
|
||||||
isMigrating?: boolean
|
isMigrating?: boolean
|
||||||
|
jsExecutionTracker?: ExecutionTimeTracker
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { directCouchUrlCall } from "./utils"
|
||||||
import { getPouchDB } from "./pouchDB"
|
import { getPouchDB } from "./pouchDB"
|
||||||
import { WriteStream, ReadStream } from "fs"
|
import { WriteStream, ReadStream } from "fs"
|
||||||
import { newid } from "../../docIds/newid"
|
import { newid } from "../../docIds/newid"
|
||||||
|
import { DDInstrumentedDatabase } from "../instrumentation"
|
||||||
|
|
||||||
function buildNano(couchInfo: { url: string; cookie: string }) {
|
function buildNano(couchInfo: { url: string; cookie: string }) {
|
||||||
return Nano({
|
return Nano({
|
||||||
|
@ -35,10 +36,8 @@ export function DatabaseWithConnection(
|
||||||
connection: string,
|
connection: string,
|
||||||
opts?: DatabaseOpts
|
opts?: DatabaseOpts
|
||||||
) {
|
) {
|
||||||
if (!connection) {
|
const db = new DatabaseImpl(dbName, opts, connection)
|
||||||
throw new Error("Must provide connection details")
|
return new DDInstrumentedDatabase(db)
|
||||||
}
|
|
||||||
return new DatabaseImpl(dbName, opts, connection)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DatabaseImpl implements Database {
|
export class DatabaseImpl implements Database {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { directCouchQuery, DatabaseImpl } from "./couch"
|
import { directCouchQuery, DatabaseImpl } from "./couch"
|
||||||
import { CouchFindOptions, Database, DatabaseOpts } from "@budibase/types"
|
import { CouchFindOptions, Database, DatabaseOpts } from "@budibase/types"
|
||||||
|
import { DDInstrumentedDatabase } from "./instrumentation"
|
||||||
|
|
||||||
export function getDB(dbName: string, opts?: DatabaseOpts): Database {
|
export function getDB(dbName: string, opts?: DatabaseOpts): Database {
|
||||||
return new DatabaseImpl(dbName, opts)
|
return new DDInstrumentedDatabase(new DatabaseImpl(dbName, opts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have to use a callback for this so that we can close
|
// we have to use a callback for this so that we can close
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
import {
|
||||||
|
DocumentScope,
|
||||||
|
DocumentDestroyResponse,
|
||||||
|
DocumentInsertResponse,
|
||||||
|
DocumentBulkResponse,
|
||||||
|
OkResponse,
|
||||||
|
} from "@budibase/nano"
|
||||||
|
import {
|
||||||
|
AllDocsResponse,
|
||||||
|
AnyDocument,
|
||||||
|
Database,
|
||||||
|
DatabaseDumpOpts,
|
||||||
|
DatabasePutOpts,
|
||||||
|
DatabaseQueryOpts,
|
||||||
|
Document,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import tracer from "dd-trace"
|
||||||
|
import { Writable } from "stream"
|
||||||
|
|
||||||
|
export class DDInstrumentedDatabase implements Database {
|
||||||
|
constructor(private readonly db: Database) {}
|
||||||
|
|
||||||
|
get name(): string {
|
||||||
|
return this.db.name
|
||||||
|
}
|
||||||
|
|
||||||
|
exists(): Promise<boolean> {
|
||||||
|
return tracer.trace("db.exists", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.exists()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSetup(): Promise<DocumentScope<any>> {
|
||||||
|
return tracer.trace("db.checkSetup", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.checkSetup()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get<T extends Document>(id?: string | undefined): Promise<T> {
|
||||||
|
return tracer.trace("db.get", span => {
|
||||||
|
span?.addTags({ db_name: this.name, doc_id: id })
|
||||||
|
return this.db.get(id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getMultiple<T extends Document>(
|
||||||
|
ids: string[],
|
||||||
|
opts?: { allowMissing?: boolean | undefined } | undefined
|
||||||
|
): Promise<T[]> {
|
||||||
|
return tracer.trace("db.getMultiple", span => {
|
||||||
|
span?.addTags({
|
||||||
|
db_name: this.name,
|
||||||
|
num_docs: ids.length,
|
||||||
|
allow_missing: opts?.allowMissing,
|
||||||
|
})
|
||||||
|
return this.db.getMultiple(ids, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(
|
||||||
|
id: string | Document,
|
||||||
|
rev?: string | undefined
|
||||||
|
): Promise<DocumentDestroyResponse> {
|
||||||
|
return tracer.trace("db.remove", span => {
|
||||||
|
span?.addTags({ db_name: this.name, doc_id: id })
|
||||||
|
return this.db.remove(id, rev)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
put(
|
||||||
|
document: AnyDocument,
|
||||||
|
opts?: DatabasePutOpts | undefined
|
||||||
|
): Promise<DocumentInsertResponse> {
|
||||||
|
return tracer.trace("db.put", span => {
|
||||||
|
span?.addTags({ db_name: this.name, doc_id: document._id })
|
||||||
|
return this.db.put(document, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkDocs(documents: AnyDocument[]): Promise<DocumentBulkResponse[]> {
|
||||||
|
return tracer.trace("db.bulkDocs", span => {
|
||||||
|
span?.addTags({ db_name: this.name, num_docs: documents.length })
|
||||||
|
return this.db.bulkDocs(documents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
allDocs<T extends Document>(
|
||||||
|
params: DatabaseQueryOpts
|
||||||
|
): Promise<AllDocsResponse<T>> {
|
||||||
|
return tracer.trace("db.allDocs", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.allDocs(params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
query<T extends Document>(
|
||||||
|
viewName: string,
|
||||||
|
params: DatabaseQueryOpts
|
||||||
|
): Promise<AllDocsResponse<T>> {
|
||||||
|
return tracer.trace("db.query", span => {
|
||||||
|
span?.addTags({ db_name: this.name, view_name: viewName })
|
||||||
|
return this.db.query(viewName, params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): Promise<void | OkResponse> {
|
||||||
|
return tracer.trace("db.destroy", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.destroy()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
compact(): Promise<void | OkResponse> {
|
||||||
|
return tracer.trace("db.compact", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.compact()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
dump(stream: Writable, opts?: DatabaseDumpOpts | undefined): Promise<any> {
|
||||||
|
return tracer.trace("db.dump", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.dump(stream, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
load(...args: any[]): Promise<any> {
|
||||||
|
return tracer.trace("db.load", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.load(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createIndex(...args: any[]): Promise<any> {
|
||||||
|
return tracer.trace("db.createIndex", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.createIndex(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteIndex(...args: any[]): Promise<any> {
|
||||||
|
return tracer.trace("db.deleteIndex", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.deleteIndex(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getIndexes(...args: any[]): Promise<any> {
|
||||||
|
return tracer.trace("db.getIndexes", span => {
|
||||||
|
span?.addTags({ db_name: this.name })
|
||||||
|
return this.db.getIndexes(...args)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -107,6 +107,7 @@ const environment = {
|
||||||
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
||||||
API_ENCRYPTION_KEY: getAPIEncryptionKey(),
|
API_ENCRYPTION_KEY: getAPIEncryptionKey(),
|
||||||
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
|
COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005",
|
||||||
|
COUCH_DB_SQL_URL: process.env.COUCH_DB_SQL_URL || "http://localhost:4984",
|
||||||
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
|
COUCH_DB_USERNAME: process.env.COUCH_DB_USER,
|
||||||
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD,
|
COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD,
|
||||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
||||||
|
|
|
@ -33,6 +33,7 @@ export * as docUpdates from "./docUpdates"
|
||||||
export * from "./utils/Duration"
|
export * from "./utils/Duration"
|
||||||
export { SearchParams } from "./db"
|
export { SearchParams } from "./db"
|
||||||
export * as docIds from "./docIds"
|
export * as docIds from "./docIds"
|
||||||
|
export * as security from "./security"
|
||||||
// Add context to tenancy for backwards compatibility
|
// Add context to tenancy for backwards compatibility
|
||||||
// only do this for external usages to prevent internal
|
// only do this for external usages to prevent internal
|
||||||
// circular dependencies
|
// circular dependencies
|
||||||
|
|
|
@ -5,6 +5,8 @@ import { IdentityType } from "@budibase/types"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import * as context from "../../context"
|
import * as context from "../../context"
|
||||||
import * as correlation from "../correlation"
|
import * as correlation from "../correlation"
|
||||||
|
import tracer from "dd-trace"
|
||||||
|
import { formats } from "dd-trace/ext"
|
||||||
|
|
||||||
import { localFileDestination } from "../system"
|
import { localFileDestination } from "../system"
|
||||||
|
|
||||||
|
@ -115,6 +117,11 @@ if (!env.DISABLE_PINO_LOGGER) {
|
||||||
correlationId: correlation.getId(),
|
correlationId: correlation.getId(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const span = tracer.scope().active()
|
||||||
|
if (span) {
|
||||||
|
tracer.inject(span.context(), formats.LOG, contextObject)
|
||||||
|
}
|
||||||
|
|
||||||
const mergingObject: any = {
|
const mergingObject: any = {
|
||||||
err: error,
|
err: error,
|
||||||
pid: process.pid,
|
pid: process.pid,
|
||||||
|
|
|
@ -260,12 +260,12 @@ export async function listAllObjects(bucketName: string, path: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a presigned url with a default TTL of 36 hours
|
* Generate a presigned url with a default TTL of 1 hour
|
||||||
*/
|
*/
|
||||||
export function getPresignedUrl(
|
export function getPresignedUrl(
|
||||||
bucketName: string,
|
bucketName: string,
|
||||||
key: string,
|
key: string,
|
||||||
durationSeconds: number = 129600
|
durationSeconds: number = 3600
|
||||||
) {
|
) {
|
||||||
const objectStore = ObjectStore(bucketName, { presigning: true })
|
const objectStore = ObjectStore(bucketName, { presigning: true })
|
||||||
const params = {
|
const params = {
|
||||||
|
|
|
@ -3,4 +3,5 @@ export enum JobQueue {
|
||||||
APP_BACKUP = "appBackupQueue",
|
APP_BACKUP = "appBackupQueue",
|
||||||
AUDIT_LOG = "auditLogQueue",
|
AUDIT_LOG = "auditLogQueue",
|
||||||
SYSTEM_EVENT_QUEUE = "systemEventQueue",
|
SYSTEM_EVENT_QUEUE = "systemEventQueue",
|
||||||
|
APP_MIGRATION = "appMigration",
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ function newJob(queue: string, message: any) {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
queue: queue,
|
queue: queue,
|
||||||
data: message,
|
data: message,
|
||||||
|
opts: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +69,10 @@ class InMemoryQueue {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isReady() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// simply puts a message to the queue and emits to the queue for processing
|
// simply puts a message to the queue and emits to the queue for processing
|
||||||
/**
|
/**
|
||||||
* Simple function to replicate the add message functionality of Bull, putting
|
* Simple function to replicate the add message functionality of Bull, putting
|
||||||
|
|
|
@ -87,6 +87,7 @@ enum QueueEventType {
|
||||||
APP_BACKUP_EVENT = "app-backup-event",
|
APP_BACKUP_EVENT = "app-backup-event",
|
||||||
AUDIT_LOG_EVENT = "audit-log-event",
|
AUDIT_LOG_EVENT = "audit-log-event",
|
||||||
SYSTEM_EVENT = "system-event",
|
SYSTEM_EVENT = "system-event",
|
||||||
|
APP_MIGRATION = "app-migration",
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventTypeMap: { [key in JobQueue]: QueueEventType } = {
|
const EventTypeMap: { [key in JobQueue]: QueueEventType } = {
|
||||||
|
@ -94,6 +95,7 @@ const EventTypeMap: { [key in JobQueue]: QueueEventType } = {
|
||||||
[JobQueue.APP_BACKUP]: QueueEventType.APP_BACKUP_EVENT,
|
[JobQueue.APP_BACKUP]: QueueEventType.APP_BACKUP_EVENT,
|
||||||
[JobQueue.AUDIT_LOG]: QueueEventType.AUDIT_LOG_EVENT,
|
[JobQueue.AUDIT_LOG]: QueueEventType.AUDIT_LOG_EVENT,
|
||||||
[JobQueue.SYSTEM_EVENT_QUEUE]: QueueEventType.SYSTEM_EVENT,
|
[JobQueue.SYSTEM_EVENT_QUEUE]: QueueEventType.SYSTEM_EVENT,
|
||||||
|
[JobQueue.APP_MIGRATION]: QueueEventType.APP_MIGRATION,
|
||||||
}
|
}
|
||||||
|
|
||||||
function logging(queue: Queue, jobQueue: JobQueue) {
|
function logging(queue: Queue, jobQueue: JobQueue) {
|
||||||
|
|
|
@ -47,7 +47,7 @@ export function createQueue<T>(
|
||||||
cleanupInterval = timers.set(cleanup, CLEANUP_PERIOD_MS)
|
cleanupInterval = timers.set(cleanup, CLEANUP_PERIOD_MS)
|
||||||
// fire off an initial cleanup
|
// fire off an initial cleanup
|
||||||
cleanup().catch(err => {
|
cleanup().catch(err => {
|
||||||
console.error(`Unable to cleanup automation queue initially - ${err}`)
|
console.error(`Unable to cleanup ${jobQueue} initially - ${err}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return queue
|
return queue
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
SelectableDatabase,
|
SelectableDatabase,
|
||||||
getRedisConnectionDetails,
|
getRedisConnectionDetails,
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
|
import { logAlert } from "../logging"
|
||||||
import * as timers from "../timers"
|
import * as timers from "../timers"
|
||||||
|
|
||||||
const RETRY_PERIOD_MS = 2000
|
const RETRY_PERIOD_MS = 2000
|
||||||
|
@ -39,21 +40,16 @@ function pickClient(selectDb: number): any {
|
||||||
return CLIENTS[selectDb]
|
return CLIENTS[selectDb]
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectionError(
|
function connectionError(timeout: NodeJS.Timeout, err: Error | string) {
|
||||||
selectDb: number,
|
|
||||||
timeout: NodeJS.Timeout,
|
|
||||||
err: Error | string
|
|
||||||
) {
|
|
||||||
// manually shut down, ignore errors
|
// manually shut down, ignore errors
|
||||||
if (CLOSED) {
|
if (CLOSED) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pickClient(selectDb).disconnect()
|
|
||||||
CLOSED = true
|
CLOSED = true
|
||||||
// always clear this on error
|
// always clear this on error
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
CONNECTED = false
|
CONNECTED = false
|
||||||
console.error("Redis connection failed - " + err)
|
logAlert("Redis connection failed", err)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
init()
|
init()
|
||||||
}, RETRY_PERIOD_MS)
|
}, RETRY_PERIOD_MS)
|
||||||
|
@ -79,11 +75,7 @@ function init(selectDb = DEFAULT_SELECT_DB) {
|
||||||
// start the timer - only allowed 5 seconds to connect
|
// start the timer - only allowed 5 seconds to connect
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
if (!CONNECTED) {
|
if (!CONNECTED) {
|
||||||
connectionError(
|
connectionError(timeout, "Did not successfully connect in timeout")
|
||||||
selectDb,
|
|
||||||
timeout,
|
|
||||||
"Did not successfully connect in timeout"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, STARTUP_TIMEOUT_MS)
|
}, STARTUP_TIMEOUT_MS)
|
||||||
|
|
||||||
|
@ -106,12 +98,13 @@ function init(selectDb = DEFAULT_SELECT_DB) {
|
||||||
// allow the process to exit
|
// allow the process to exit
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connectionError(selectDb, timeout, err)
|
connectionError(timeout, err)
|
||||||
})
|
})
|
||||||
client.on("error", (err: Error) => {
|
client.on("error", (err: Error) => {
|
||||||
connectionError(selectDb, timeout, err)
|
connectionError(timeout, err)
|
||||||
})
|
})
|
||||||
client.on("connect", () => {
|
client.on("connect", () => {
|
||||||
|
console.log(`Connected to Redis DB: ${selectDb}`)
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
CONNECTED = true
|
CONNECTED = true
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,6 @@ 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 { logWarn } from "../logging"
|
|
||||||
import { utils } from "@budibase/shared-core"
|
import { utils } from "@budibase/shared-core"
|
||||||
import { Duration } from "../utils"
|
import { Duration } from "../utils"
|
||||||
|
|
||||||
|
@ -137,7 +136,6 @@ export async function doWithLock<T>(
|
||||||
const result = await task()
|
const result = await task()
|
||||||
return { executed: true, result }
|
return { executed: true, result }
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logWarn(`lock type: ${opts.type} error`, e)
|
|
||||||
// lock limit exceeded
|
// lock limit exceeded
|
||||||
if (e.name === "LockError") {
|
if (e.name === "LockError") {
|
||||||
if (opts.type === LockType.TRY_ONCE) {
|
if (opts.type === LockType.TRY_ONCE) {
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { env } from ".."
|
||||||
|
|
||||||
|
export const PASSWORD_MIN_LENGTH = +(process.env.PASSWORD_MIN_LENGTH || 8)
|
||||||
|
export const PASSWORD_MAX_LENGTH = +(process.env.PASSWORD_MAX_LENGTH || 512)
|
||||||
|
|
||||||
|
export function validatePassword(
|
||||||
|
password: string
|
||||||
|
): { valid: true } | { valid: false; error: string } {
|
||||||
|
if (!password || password.length < PASSWORD_MIN_LENGTH) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: `Password invalid. Minimum ${PASSWORD_MIN_LENGTH} characters.`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length > PASSWORD_MAX_LENGTH) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
error: `Password invalid. Maximum ${PASSWORD_MAX_LENGTH} characters.`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true }
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./auth"
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { generator } from "../../../tests"
|
||||||
|
import { PASSWORD_MAX_LENGTH, validatePassword } from "../auth"
|
||||||
|
|
||||||
|
describe("auth", () => {
|
||||||
|
describe("validatePassword", () => {
|
||||||
|
it("a valid password returns successful", () => {
|
||||||
|
expect(validatePassword("password")).toEqual({ valid: true })
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["undefined", undefined],
|
||||||
|
["null", null],
|
||||||
|
["empty", ""],
|
||||||
|
])("%s returns unsuccessful", (_, password) => {
|
||||||
|
expect(validatePassword(password as string)).toEqual({
|
||||||
|
valid: false,
|
||||||
|
error: "Password invalid. Minimum 8 characters.",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
generator.word({ length: PASSWORD_MAX_LENGTH }),
|
||||||
|
generator.paragraph().substring(0, PASSWORD_MAX_LENGTH),
|
||||||
|
])(`can use passwords up to 512 characters in length`, password => {
|
||||||
|
expect(validatePassword(password)).toEqual({
|
||||||
|
valid: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
generator.word({ length: PASSWORD_MAX_LENGTH + 1 }),
|
||||||
|
generator
|
||||||
|
.paragraph({ sentences: 50 })
|
||||||
|
.substring(0, PASSWORD_MAX_LENGTH + 1),
|
||||||
|
])(
|
||||||
|
`passwords cannot have more than ${PASSWORD_MAX_LENGTH} characters`,
|
||||||
|
password => {
|
||||||
|
expect(validatePassword(password)).toEqual({
|
||||||
|
valid: false,
|
||||||
|
error: "Password invalid. Maximum 512 characters.",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -39,7 +39,7 @@ const ALL_STRATEGIES = Object.values(TenantResolutionStrategy)
|
||||||
export const getTenantIDFromCtx = (
|
export const getTenantIDFromCtx = (
|
||||||
ctx: BBContext,
|
ctx: BBContext,
|
||||||
opts: GetTenantIdOptions
|
opts: GetTenantIdOptions
|
||||||
): string | null => {
|
): string | undefined => {
|
||||||
// exit early if not multi-tenant
|
// exit early if not multi-tenant
|
||||||
if (!isMultiTenant()) {
|
if (!isMultiTenant()) {
|
||||||
return DEFAULT_TENANT_ID
|
return DEFAULT_TENANT_ID
|
||||||
|
@ -144,5 +144,5 @@ export const getTenantIDFromCtx = (
|
||||||
ctx.throw(403, "Tenant id not set")
|
ctx.throw(403, "Tenant id not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,12 +157,12 @@ describe("getTenantIDFromCtx", () => {
|
||||||
TenantResolutionStrategy.PATH,
|
TenantResolutionStrategy.PATH,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeNull()
|
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeUndefined()
|
||||||
expect(ctx.throw).toBeCalledTimes(1)
|
expect(ctx.throw).toBeCalledTimes(1)
|
||||||
expect(ctx.throw).toBeCalledWith(403, "Tenant id not set")
|
expect(ctx.throw).toBeCalledWith(403, "Tenant id not set")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns null if allowNoTenant is true", () => {
|
it("returns undefined if allowNoTenant is true", () => {
|
||||||
const ctx = createCtx({})
|
const ctx = createCtx({})
|
||||||
mockOpts = {
|
mockOpts = {
|
||||||
allowNoTenant: true,
|
allowNoTenant: true,
|
||||||
|
@ -172,7 +172,7 @@ describe("getTenantIDFromCtx", () => {
|
||||||
TenantResolutionStrategy.PATH,
|
TenantResolutionStrategy.PATH,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeNull()
|
expect(getTenantIDFromCtx(ctx, mockOpts)).toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -20,3 +20,41 @@ export function cleanup() {
|
||||||
}
|
}
|
||||||
intervals = []
|
intervals = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ExecutionTimeoutError extends Error {
|
||||||
|
public readonly name = "ExecutionTimeoutError"
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExecutionTimeTracker {
|
||||||
|
static withLimit(limitMs: number) {
|
||||||
|
return new ExecutionTimeTracker(limitMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(readonly limitMs: number) {}
|
||||||
|
|
||||||
|
private totalTimeMs = 0
|
||||||
|
|
||||||
|
track<T>(f: () => T): T {
|
||||||
|
this.checkLimit()
|
||||||
|
const start = process.hrtime.bigint()
|
||||||
|
try {
|
||||||
|
return f()
|
||||||
|
} finally {
|
||||||
|
const end = process.hrtime.bigint()
|
||||||
|
this.totalTimeMs += Number(end - start) / 1e6
|
||||||
|
this.checkLimit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get elapsedMS() {
|
||||||
|
return this.totalTimeMs
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLimit() {
|
||||||
|
if (this.totalTimeMs > this.limitMs) {
|
||||||
|
throw new ExecutionTimeoutError(
|
||||||
|
`Execution time limit of ${this.limitMs}ms exceeded: ${this.totalTimeMs}ms`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import env from "../environment"
|
||||||
import * as eventHelpers from "./events"
|
import * as eventHelpers from "./events"
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import * as cache from "../cache"
|
import * as cache from "../cache"
|
||||||
import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
|
import { getGlobalDB, getIdentity, getTenantId } from "../context"
|
||||||
import * as dbUtils from "../db"
|
import * as dbUtils from "../db"
|
||||||
import { EmailUnavailableError, HTTPError } from "../errors"
|
import { EmailUnavailableError, HTTPError } from "../errors"
|
||||||
import * as platform from "../platform"
|
import * as platform from "../platform"
|
||||||
|
@ -27,6 +27,7 @@ import {
|
||||||
} from "./utils"
|
} from "./utils"
|
||||||
import { searchExistingEmails } from "./lookup"
|
import { searchExistingEmails } from "./lookup"
|
||||||
import { hash } from "../utils"
|
import { hash } from "../utils"
|
||||||
|
import { validatePassword } from "../security"
|
||||||
|
|
||||||
type QuotaUpdateFn = (
|
type QuotaUpdateFn = (
|
||||||
change: number,
|
change: number,
|
||||||
|
@ -110,6 +111,12 @@ export class UserDB {
|
||||||
if (await UserDB.isPreventPasswordActions(user, account)) {
|
if (await UserDB.isPreventPasswordActions(user, account)) {
|
||||||
throw new HTTPError("Password change is disabled for this user", 400)
|
throw new HTTPError("Password change is disabled for this user", 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordValidation = validatePassword(password)
|
||||||
|
if (!passwordValidation.valid) {
|
||||||
|
throw new HTTPError(passwordValidation.error, 400)
|
||||||
|
}
|
||||||
|
|
||||||
hashedPassword = opts.hashPassword ? await hash(password) : password
|
hashedPassword = opts.hashPassword ? await hash(password) : password
|
||||||
} else if (dbUser) {
|
} else if (dbUser) {
|
||||||
hashedPassword = dbUser.password
|
hashedPassword = dbUser.password
|
||||||
|
|
|
@ -49,4 +49,8 @@ export class Duration {
|
||||||
static fromDays(duration: number) {
|
static fromDays(duration: number) {
|
||||||
return Duration.from(DurationType.DAYS, duration)
|
return Duration.from(DurationType.DAYS, duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromMilliseconds(duration: number) {
|
||||||
|
return Duration.from(DurationType.MILLISECONDS, duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,8 @@ export async function resolveAppUrl(ctx: Ctx) {
|
||||||
const appUrl = ctx.path.split("/")[2]
|
const appUrl = ctx.path.split("/")[2]
|
||||||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||||
|
|
||||||
let tenantId: string | null = context.getTenantId()
|
let tenantId: string | undefined = context.getTenantId()
|
||||||
if (env.MULTI_TENANCY) {
|
if (!env.isDev() && env.MULTI_TENANCY) {
|
||||||
// always use the tenant id from the subdomain in multi tenancy
|
// always use the tenant id from the subdomain in multi tenancy
|
||||||
// this ensures the logged-in user tenant id doesn't overwrite
|
// this ensures the logged-in user tenant id doesn't overwrite
|
||||||
// e.g. in the case of viewing a public app while already logged-in to another tenant
|
// e.g. in the case of viewing a public app while already logged-in to another tenant
|
||||||
|
@ -41,7 +41,7 @@ export async function resolveAppUrl(ctx: Ctx) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// search prod apps for a url that matches
|
// search prod apps for an url that matches
|
||||||
const apps: App[] = await context.doInTenant(
|
const apps: App[] = await context.doInTenant(
|
||||||
tenantId,
|
tenantId,
|
||||||
() => getAllApps({ dev: false }) as Promise<App[]>
|
() => getAllApps({ dev: false }) as Promise<App[]>
|
||||||
|
@ -96,7 +96,7 @@ export async function getAppIdFromCtx(ctx: Ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// look in the path
|
// look in the path
|
||||||
const pathId = parseAppIdFromUrl(ctx.path)
|
const pathId = parseAppIdFromUrlPath(ctx.path)
|
||||||
if (!appId && pathId) {
|
if (!appId && pathId) {
|
||||||
appId = confirmAppId(pathId)
|
appId = confirmAppId(pathId)
|
||||||
}
|
}
|
||||||
|
@ -116,18 +116,21 @@ export async function getAppIdFromCtx(ctx: Ctx) {
|
||||||
// referer header is present from a builder redirect
|
// referer header is present from a builder redirect
|
||||||
const referer = ctx.request.headers.referer
|
const referer = ctx.request.headers.referer
|
||||||
if (!appId && referer?.includes(BUILDER_APP_PREFIX)) {
|
if (!appId && referer?.includes(BUILDER_APP_PREFIX)) {
|
||||||
const refererId = parseAppIdFromUrl(ctx.request.headers.referer)
|
const refererId = parseAppIdFromUrlPath(ctx.request.headers.referer)
|
||||||
appId = confirmAppId(refererId)
|
appId = confirmAppId(refererId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return appId
|
return appId
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseAppIdFromUrl(url?: string) {
|
function parseAppIdFromUrlPath(url?: string) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return url.split("/").find(subPath => subPath.startsWith(APP_PREFIX))
|
return url
|
||||||
|
.split("?")[0] // Remove any possible query string
|
||||||
|
.split("/")
|
||||||
|
.find(subPath => subPath.startsWith(APP_PREFIX))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
|
||||||
_id: userId,
|
_id: userId,
|
||||||
userId,
|
userId,
|
||||||
email: newEmail(),
|
email: newEmail(),
|
||||||
password: "test",
|
password: "password",
|
||||||
roles: { app_test: "admin" },
|
roles: { app_test: "admin" },
|
||||||
firstName: generator.first(),
|
firstName: generator.first(),
|
||||||
lastName: generator.last(),
|
lastName: generator.last(),
|
||||||
|
|
|
@ -130,5 +130,6 @@
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
<script>
|
<script>
|
||||||
import Icon from "../Icon/Icon.svelte"
|
import Icon from "../Icon/Icon.svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
|
|
||||||
export let name
|
export let name
|
||||||
export let show = false
|
export let initiallyShow = false
|
||||||
export let collapsible = true
|
export let collapsible = true
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
let show = initiallyShow
|
||||||
|
|
||||||
const onHeaderClick = () => {
|
const onHeaderClick = () => {
|
||||||
if (!collapsible) {
|
if (!collapsible) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
show = !show
|
show = !show
|
||||||
if (show) {
|
|
||||||
dispatch("open")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -81,7 +78,7 @@
|
||||||
var(--spacing-xl);
|
var(--spacing-xl);
|
||||||
}
|
}
|
||||||
.property-panel.no-title {
|
.property-panel.no-title {
|
||||||
padding: var(--spacing-xl);
|
padding-top: var(--spacing-xl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
// Ensure the value is updated if the value prop changes outside the editor's
|
// Ensure the value is updated if the value prop changes outside the editor's
|
||||||
// control
|
// control
|
||||||
$: checkValue(value)
|
$: checkValue(value)
|
||||||
$: mde?.codemirror.on("change", debouncedUpdate)
|
$: mde?.codemirror.on("blur", update)
|
||||||
$: if (readonly || disabled) {
|
$: if (readonly || disabled) {
|
||||||
mde?.togglePreview()
|
mde?.togglePreview()
|
||||||
}
|
}
|
||||||
|
@ -30,21 +30,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const debounce = (fn, interval) => {
|
|
||||||
let timeout
|
|
||||||
return () => {
|
|
||||||
clearTimeout(timeout)
|
|
||||||
timeout = setTimeout(fn, interval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
latestValue = mde.value()
|
latestValue = mde.value()
|
||||||
dispatch("change", latestValue)
|
dispatch("change", latestValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debounce the update function to avoid spamming it constantly
|
|
||||||
const debouncedUpdate = debounce(update, 250)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key height}
|
{#key height}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "routify -b && vite build --emptyOutDir",
|
"build": "routify -b && vite build --emptyOutDir",
|
||||||
"start": "routify -c rollup",
|
"start": "routify -c rollup",
|
||||||
"dev:builder": "routify -c dev:vite",
|
"dev": "routify -c dev:vite",
|
||||||
"dev:vite": "vite --host 0.0.0.0",
|
"dev:vite": "vite --host 0.0.0.0",
|
||||||
"rollup": "rollup -c -w",
|
"rollup": "rollup -c -w",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
@ -61,9 +61,9 @@
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@codemirror/view": "^6.11.2",
|
"@codemirror/view": "^6.11.2",
|
||||||
"@fontsource/source-sans-pro": "^5.0.3",
|
"@fontsource/source-sans-pro": "^5.0.3",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
"@fortawesome/fontawesome-svg-core": "^6.4.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
"@fortawesome/free-brands-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
"codemirror": "^5.59.0",
|
"codemirror": "^5.59.0",
|
||||||
|
@ -78,25 +78,24 @@
|
||||||
"svelte-dnd-action": "^0.9.8",
|
"svelte-dnd-action": "^0.9.8",
|
||||||
"svelte-loading-spinners": "^0.1.1",
|
"svelte-loading-spinners": "^0.1.1",
|
||||||
"svelte-portal": "1.0.0",
|
"svelte-portal": "1.0.0",
|
||||||
"yup": "0.29.2"
|
"yup": "^0.32.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.14",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.13.10",
|
"@babel/plugin-transform-runtime": "^7.13.10",
|
||||||
"@babel/preset-env": "^7.13.12",
|
"@babel/preset-env": "^7.13.12",
|
||||||
"@rollup/plugin-replace": "^5.0.3",
|
"@rollup/plugin-replace": "^5.0.3",
|
||||||
"@roxi/routify": "2.18.12",
|
"@roxi/routify": "2.18.12",
|
||||||
"@sveltejs/vite-plugin-svelte": "1.0.1",
|
"@sveltejs/vite-plugin-svelte": "1.4.0",
|
||||||
"@testing-library/jest-dom": "5.17.0",
|
"@testing-library/jest-dom": "5.17.0",
|
||||||
"@testing-library/svelte": "^3.2.2",
|
"@testing-library/svelte": "^3.2.2",
|
||||||
"babel-jest": "29.6.2",
|
"babel-jest": "^29.6.2",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "29.6.2",
|
"jest": "29.7.0",
|
||||||
"jsdom": "^21.1.1",
|
"jsdom": "^21.1.1",
|
||||||
"ncp": "^2.0.0",
|
"ncp": "^2.0.0",
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.49.0",
|
||||||
"svelte-jester": "^1.3.2",
|
"svelte-jester": "^1.3.2",
|
||||||
"vite": "^4.4.11",
|
"vite": "^4.5.0",
|
||||||
"vite-plugin-static-copy": "^0.17.0",
|
"vite-plugin-static-copy": "^0.17.0",
|
||||||
"vitest": "^0.29.2"
|
"vitest": "^0.29.2"
|
||||||
},
|
},
|
||||||
|
@ -115,7 +114,7 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dev:builder": {
|
"dev": {
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
{
|
{
|
||||||
"projects": [
|
"projects": [
|
||||||
|
|
|
@ -510,9 +510,7 @@ const isContextCompatibleWithComponent = (context, component) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Enrich binding category information for certain components
|
||||||
* Determines the correct category for a given binding.
|
|
||||||
*/
|
|
||||||
const getComponentBindingCategory = (component, context, def) => {
|
const getComponentBindingCategory = (component, context, def) => {
|
||||||
// Default category to component name
|
// Default category to component name
|
||||||
let icon = def.icon
|
let icon = def.icon
|
||||||
|
@ -520,14 +518,13 @@ const getComponentBindingCategory = (component, context, def) => {
|
||||||
|
|
||||||
// Form block edge case
|
// Form block edge case
|
||||||
if (component._component.endsWith("formblock")) {
|
if (component._component.endsWith("formblock")) {
|
||||||
let contextCategorySuffix = {
|
if (context.type === "form") {
|
||||||
form: "Fields",
|
category = `${component._instanceName} - Fields`
|
||||||
schema: "Row",
|
icon = "Form"
|
||||||
|
} else if (context.type === "schema") {
|
||||||
|
category = `${component._instanceName} - Row`
|
||||||
|
icon = "Data"
|
||||||
}
|
}
|
||||||
category = `${component._instanceName} - ${
|
|
||||||
contextCategorySuffix[context.type]
|
|
||||||
}`
|
|
||||||
icon = context.type === "form" ? "Form" : "Data"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { derived, get } from "svelte/store"
|
||||||
import { findComponent, findComponentPath } from "./componentUtils"
|
import { findComponent, findComponentPath } from "./componentUtils"
|
||||||
import { RoleUtils } from "@budibase/frontend-core"
|
import { RoleUtils } from "@budibase/frontend-core"
|
||||||
import { createHistoryStore } from "builderStore/store/history"
|
import { createHistoryStore } from "builderStore/store/history"
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
|
||||||
export const store = getFrontendStore()
|
export const store = getFrontendStore()
|
||||||
export const automationStore = getAutomationStore()
|
export const automationStore = getAutomationStore()
|
||||||
|
@ -69,7 +70,14 @@ export const selectedComponent = derived(
|
||||||
if (!$selectedScreen || !$store.selectedComponentId) {
|
if (!$selectedScreen || !$store.selectedComponentId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return findComponent($selectedScreen?.props, $store.selectedComponentId)
|
const selected = findComponent(
|
||||||
|
$selectedScreen?.props,
|
||||||
|
$store.selectedComponentId
|
||||||
|
)
|
||||||
|
|
||||||
|
const clone = selected ? cloneDeep(selected) : selected
|
||||||
|
store.actions.components.migrateSettings(clone)
|
||||||
|
return clone
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,7 @@ const INITIAL_FRONTEND_STATE = {
|
||||||
selectedScreenId: null,
|
selectedScreenId: null,
|
||||||
selectedComponentId: null,
|
selectedComponentId: null,
|
||||||
selectedLayoutId: null,
|
selectedLayoutId: null,
|
||||||
|
hoverComponentId: null,
|
||||||
|
|
||||||
// Client state
|
// Client state
|
||||||
selectedComponentInstance: null,
|
selectedComponentInstance: null,
|
||||||
|
@ -113,7 +114,7 @@ export const getFrontendStore = () => {
|
||||||
}
|
}
|
||||||
let clone = cloneDeep(screen)
|
let clone = cloneDeep(screen)
|
||||||
const result = patchFn(clone)
|
const result = patchFn(clone)
|
||||||
|
// An explicit false result means skip this change
|
||||||
if (result === false) {
|
if (result === false) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -602,6 +603,36 @@ export const getFrontendStore = () => {
|
||||||
// Finally try an external table
|
// Finally try an external table
|
||||||
return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
|
return validTables.find(table => table.sourceType === DB_TYPE_EXTERNAL)
|
||||||
},
|
},
|
||||||
|
migrateSettings: enrichedComponent => {
|
||||||
|
const componentPrefix = "@budibase/standard-components"
|
||||||
|
let migrated = false
|
||||||
|
|
||||||
|
if (enrichedComponent?._component == `${componentPrefix}/formblock`) {
|
||||||
|
// Use default config if the 'buttons' prop has never been initialised
|
||||||
|
if (!("buttons" in enrichedComponent)) {
|
||||||
|
enrichedComponent["buttons"] =
|
||||||
|
Utils.buildFormBlockButtonConfig(enrichedComponent)
|
||||||
|
migrated = true
|
||||||
|
} else if (enrichedComponent["buttons"] == null) {
|
||||||
|
// Ignore legacy config if 'buttons' has been reset by 'resetOn'
|
||||||
|
const { _id, actionType, dataSource } = enrichedComponent
|
||||||
|
enrichedComponent["buttons"] = Utils.buildFormBlockButtonConfig({
|
||||||
|
_id,
|
||||||
|
actionType,
|
||||||
|
dataSource,
|
||||||
|
})
|
||||||
|
migrated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure existing Formblocks position their buttons at the top.
|
||||||
|
if (!("buttonPosition" in enrichedComponent)) {
|
||||||
|
enrichedComponent["buttonPosition"] = "top"
|
||||||
|
migrated = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return migrated
|
||||||
|
},
|
||||||
enrichEmptySettings: (component, opts) => {
|
enrichEmptySettings: (component, opts) => {
|
||||||
if (!component?._component) {
|
if (!component?._component) {
|
||||||
return
|
return
|
||||||
|
@ -673,7 +704,6 @@ export const getFrontendStore = () => {
|
||||||
component[setting.key] = setting.defaultValue
|
component[setting.key] = setting.defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate non-empty settings
|
// Validate non-empty settings
|
||||||
else {
|
else {
|
||||||
if (setting.type === "dataProvider") {
|
if (setting.type === "dataProvider") {
|
||||||
|
@ -732,6 +762,9 @@ export const getFrontendStore = () => {
|
||||||
useDefaultValues: true,
|
useDefaultValues: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Migrate nested component settings
|
||||||
|
store.actions.components.migrateSettings(instance)
|
||||||
|
|
||||||
// Add any extra properties the component needs
|
// Add any extra properties the component needs
|
||||||
let extras = {}
|
let extras = {}
|
||||||
if (definition.hasChildren) {
|
if (definition.hasChildren) {
|
||||||
|
@ -855,7 +888,16 @@ export const getFrontendStore = () => {
|
||||||
if (!component) {
|
if (!component) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return patchFn(component, screen)
|
|
||||||
|
// Mutates the fetched component with updates
|
||||||
|
const patchResult = patchFn(component, screen)
|
||||||
|
|
||||||
|
// Mutates the component with any required settings updates
|
||||||
|
const migrated = store.actions.components.migrateSettings(component)
|
||||||
|
|
||||||
|
// Returning an explicit false signifies that we should skip this
|
||||||
|
// update. If we migrated something, ensure we never skip.
|
||||||
|
return migrated ? null : patchResult
|
||||||
}
|
}
|
||||||
await store.actions.screens.patch(patchScreen, screenId)
|
await store.actions.screens.patch(patchScreen, screenId)
|
||||||
},
|
},
|
||||||
|
@ -1257,11 +1299,14 @@ export const getFrontendStore = () => {
|
||||||
const settings = getComponentSettings(component._component)
|
const settings = getComponentSettings(component._component)
|
||||||
const updatedSetting = settings.find(setting => setting.key === name)
|
const updatedSetting = settings.find(setting => setting.key === name)
|
||||||
|
|
||||||
const resetFields = settings.filter(
|
// Reset dependent fields
|
||||||
setting => name === setting.resetOn
|
settings.forEach(setting => {
|
||||||
)
|
const needsReset =
|
||||||
resetFields?.forEach(setting => {
|
name === setting.resetOn ||
|
||||||
component[setting.key] = null
|
(Array.isArray(setting.resetOn) && setting.resetOn.includes(name))
|
||||||
|
if (needsReset) {
|
||||||
|
component[setting.key] = setting.defaultValue || null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -1281,6 +1326,7 @@ export const getFrontendStore = () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
component[name] = value
|
component[name] = value
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
requestEjectBlock: componentId => {
|
requestEjectBlock: componentId => {
|
||||||
|
@ -1288,7 +1334,6 @@ export const getFrontendStore = () => {
|
||||||
},
|
},
|
||||||
handleEjectBlock: async (componentId, ejectedDefinition) => {
|
handleEjectBlock: async (componentId, ejectedDefinition) => {
|
||||||
let nextSelectedComponentId
|
let nextSelectedComponentId
|
||||||
|
|
||||||
await store.actions.screens.patch(screen => {
|
await store.actions.screens.patch(screen => {
|
||||||
const block = findComponent(screen.props, componentId)
|
const block = findComponent(screen.props, componentId)
|
||||||
const parent = findComponentParent(screen.props, componentId)
|
const parent = findComponentParent(screen.props, componentId)
|
||||||
|
|
|
@ -69,7 +69,15 @@
|
||||||
on:change={e => onChange(e, field)}
|
on:change={e => onChange(e, field)}
|
||||||
useLabel={false}
|
useLabel={false}
|
||||||
/>
|
/>
|
||||||
{:else if schema.type === "string" || schema.type === "number"}
|
{:else if schema.type === "bb_reference"}
|
||||||
|
<LinkedRowSelector
|
||||||
|
linkedRows={value[field]}
|
||||||
|
{schema}
|
||||||
|
linkedTableId={"ta_users"}
|
||||||
|
on:change={e => onChange(e, field)}
|
||||||
|
useLabel={false}
|
||||||
|
/>
|
||||||
|
{:else if ["string", "number", "bigint", "barcodeqr"].includes(schema.type)}
|
||||||
<svelte:component
|
<svelte:component
|
||||||
this={isTestModal ? ModalBindableInput : DrawerBindableInput}
|
this={isTestModal ? ModalBindableInput : DrawerBindableInput}
|
||||||
panel={AutomationBindingPanel}
|
panel={AutomationBindingPanel}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { ActionButton, notifications } from "@budibase/bbui"
|
import { ActionButton, notifications } from "@budibase/bbui"
|
||||||
import CreateEditRelationshipModal from "../../Datasources/CreateEditRelationshipModal.svelte"
|
import CreateEditRelationshipModal from "../../Datasources/CreateEditRelationshipModal.svelte"
|
||||||
import { datasources } from "../../../../stores/backend"
|
import {
|
||||||
|
datasources,
|
||||||
|
tables as tablesStore,
|
||||||
|
} from "../../../../stores/backend"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let table
|
export let table
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
$: datasource = findDatasource(table?._id)
|
$: datasource = findDatasource(table?._id)
|
||||||
$: tables = datasource?.plus ? Object.values(datasource?.entities || {}) : []
|
$: tables = datasource?.plus
|
||||||
|
? $tablesStore.list.filter(tbl => tbl.sourceId === datasource._id)
|
||||||
|
: []
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
|
@ -28,7 +33,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onError = err => {
|
const onError = err => {
|
||||||
notifications.error(`Error saving relationship info: ${err}`)
|
if (err.err) {
|
||||||
|
err = err.err
|
||||||
|
}
|
||||||
|
notifications.error(
|
||||||
|
`Error saving relationship info: ${err?.message || JSON.stringify(err)}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
let relationshipTableIdSecondary = null
|
let relationshipTableIdSecondary = null
|
||||||
|
|
||||||
let table = $tables.selected
|
let table = $tables.selected
|
||||||
|
|
||||||
let confirmDeleteDialog
|
let confirmDeleteDialog
|
||||||
let savingColumn
|
let savingColumn
|
||||||
let deleteColName
|
let deleteColName
|
||||||
|
@ -171,7 +172,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!savingColumn) {
|
if (!savingColumn && !originalName) {
|
||||||
let highestNumber = 0
|
let highestNumber = 0
|
||||||
Object.keys(table.schema).forEach(columnName => {
|
Object.keys(table.schema).forEach(columnName => {
|
||||||
const columnNumber = extractColumnNumber(columnName)
|
const columnNumber = extractColumnNumber(columnName)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { string, number } from "yup"
|
import { string, number, object } from "yup"
|
||||||
|
|
||||||
const propertyValidator = type => {
|
const propertyValidator = type => {
|
||||||
if (type === "number") {
|
if (type === "number") {
|
||||||
|
@ -9,6 +9,10 @@ const propertyValidator = type => {
|
||||||
return string().email().nullable()
|
return string().email().nullable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "object") {
|
||||||
|
return object().nullable()
|
||||||
|
}
|
||||||
|
|
||||||
return string().nullable()
|
return string().nullable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,7 @@
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let autocompleteEnabled = true
|
export let autocompleteEnabled = true
|
||||||
|
export let autofocus = false
|
||||||
|
|
||||||
// Export a function to expose caret position
|
// Export a function to expose caret position
|
||||||
export const getCaretPosition = () => {
|
export const getCaretPosition = () => {
|
||||||
|
@ -241,6 +242,12 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (autofocus && isEditorInitialised) {
|
||||||
|
editor.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: editorHeight = typeof height === "number" ? `${height}px` : height
|
$: editorHeight = typeof height === "number" ? `${height}px` : height
|
||||||
|
|
||||||
// Init when all elements are ready
|
// Init when all elements are ready
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let linkedRows = []
|
export let linkedRows = []
|
||||||
export let useLabel = true
|
export let useLabel = true
|
||||||
|
export let linkedTableId
|
||||||
|
export let label
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let rows = []
|
let rows = []
|
||||||
|
@ -16,8 +18,8 @@
|
||||||
$: linkedIds = (Array.isArray(linkedRows) ? linkedRows : [])?.map(
|
$: linkedIds = (Array.isArray(linkedRows) ? linkedRows : [])?.map(
|
||||||
row => row?._id || row
|
row => row?._id || row
|
||||||
)
|
)
|
||||||
$: label = capitalise(schema.name)
|
$: label = label || capitalise(schema.name)
|
||||||
$: linkedTableId = schema.tableId
|
$: linkedTableId = linkedTableId || schema.tableId
|
||||||
$: linkedTable = $tables.list.find(table => table._id === linkedTableId)
|
$: linkedTable = $tables.list.find(table => table._id === linkedTableId)
|
||||||
$: fetchRows(linkedTableId)
|
$: fetchRows(linkedTableId)
|
||||||
|
|
||||||
|
@ -57,7 +59,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<Multiselect
|
<Multiselect
|
||||||
value={linkedIds}
|
value={linkedIds}
|
||||||
{label}
|
label={useLabel ? label : null}
|
||||||
options={rows}
|
options={rows}
|
||||||
getOptionLabel={getPrettyName}
|
getOptionLabel={getPrettyName}
|
||||||
getOptionValue={row => row._id}
|
getOptionValue={row => row._id}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
export let showTooltip = false
|
export let showTooltip = false
|
||||||
export let selectedBy = null
|
export let selectedBy = null
|
||||||
export let compact = false
|
export let compact = false
|
||||||
|
export let hovering = false
|
||||||
|
|
||||||
const scrollApi = getContext("scroll")
|
const scrollApi = getContext("scroll")
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -61,6 +62,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
|
class:hovering
|
||||||
class:border
|
class:border
|
||||||
class:selected
|
class:selected
|
||||||
class:withActions
|
class:withActions
|
||||||
|
@ -71,6 +73,8 @@
|
||||||
on:dragstart
|
on:dragstart
|
||||||
on:dragover
|
on:dragover
|
||||||
on:drop
|
on:drop
|
||||||
|
on:mouseenter
|
||||||
|
on:mouseleave
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
ondragover="return false"
|
ondragover="return false"
|
||||||
ondragenter="return false"
|
ondragenter="return false"
|
||||||
|
@ -152,15 +156,17 @@
|
||||||
--avatars-background: var(--spectrum-global-color-gray-200);
|
--avatars-background: var(--spectrum-global-color-gray-200);
|
||||||
}
|
}
|
||||||
.nav-item.selected {
|
.nav-item.selected {
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
background-color: var(--spectrum-global-color-gray-300) !important;
|
||||||
--avatars-background: var(--spectrum-global-color-gray-300);
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
.nav-item:hover {
|
.nav-item:hover,
|
||||||
background-color: var(--spectrum-global-color-gray-300);
|
.hovering {
|
||||||
|
background-color: var(--spectrum-global-color-gray-200);
|
||||||
--avatars-background: var(--spectrum-global-color-gray-300);
|
--avatars-background: var(--spectrum-global-color-gray-300);
|
||||||
}
|
}
|
||||||
.nav-item:hover .actions {
|
.nav-item:hover .actions,
|
||||||
|
.hovering .actions {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
.nav-item-content {
|
.nav-item-content {
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
export let valid
|
export let valid
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
|
export let autofocusEditor = false
|
||||||
|
|
||||||
const drawerActions = getContext("drawer-actions")
|
const drawerActions = getContext("drawer-actions")
|
||||||
const bindingDrawerActions = getContext("binding-drawer-actions")
|
const bindingDrawerActions = getContext("binding-drawer-actions")
|
||||||
|
@ -199,6 +200,7 @@
|
||||||
]}
|
]}
|
||||||
placeholder=""
|
placeholder=""
|
||||||
height="100%"
|
height="100%"
|
||||||
|
autofocus={autofocusEditor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="binding-footer">
|
<div class="binding-footer">
|
||||||
|
@ -301,6 +303,7 @@
|
||||||
bind:getCaretPosition
|
bind:getCaretPosition
|
||||||
bind:insertAtPos
|
bind:insertAtPos
|
||||||
height="100%"
|
height="100%"
|
||||||
|
autofocus={autofocusEditor}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="binding-footer">
|
<div class="binding-footer">
|
||||||
|
|
|
@ -79,6 +79,7 @@
|
||||||
bind:this={popover}
|
bind:this={popover}
|
||||||
anchor={popoverAnchor}
|
anchor={popoverAnchor}
|
||||||
maxWidth={300}
|
maxWidth={300}
|
||||||
|
maxHeight={300}
|
||||||
dismissible={false}
|
dismissible={false}
|
||||||
>
|
>
|
||||||
<Layout gap="S">
|
<Layout gap="S">
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
export let value = ""
|
export let value = ""
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
|
export let autofocusEditor = false
|
||||||
|
|
||||||
$: enrichedBindings = enrichBindings(bindings)
|
$: enrichedBindings = enrichBindings(bindings)
|
||||||
|
|
||||||
|
@ -27,5 +28,6 @@
|
||||||
{value}
|
{value}
|
||||||
{allowJS}
|
{allowJS}
|
||||||
{allowHelpers}
|
{allowHelpers}
|
||||||
|
{autofocusEditor}
|
||||||
on:change
|
on:change
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -44,7 +44,11 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
const onChange = value => {
|
const onChange = value => {
|
||||||
if (type === "link" && value && hasValidLinks(value)) {
|
if (
|
||||||
|
(type === "link" || type === "bb_reference") &&
|
||||||
|
value &&
|
||||||
|
hasValidLinks(value)
|
||||||
|
) {
|
||||||
currentVal = value.split(",")
|
currentVal = value.split(",")
|
||||||
} else if (type === "array" && value && hasValidOptions(value)) {
|
} else if (type === "array" && value && hasValidOptions(value)) {
|
||||||
currentVal = value.split(",")
|
currentVal = value.split(",")
|
||||||
|
@ -95,6 +99,7 @@
|
||||||
date: isValidDate,
|
date: isValidDate,
|
||||||
datetime: isValidDate,
|
datetime: isValidDate,
|
||||||
link: hasValidLinks,
|
link: hasValidLinks,
|
||||||
|
bb_reference: hasValidLinks,
|
||||||
array: hasValidOptions,
|
array: hasValidOptions,
|
||||||
longform: value => !isJSBinding(value),
|
longform: value => !isJSBinding(value),
|
||||||
json: value => !isJSBinding(value),
|
json: value => !isJSBinding(value),
|
||||||
|
@ -113,7 +118,7 @@
|
||||||
if (type === "json" && !isJSBinding(value)) {
|
if (type === "json" && !isJSBinding(value)) {
|
||||||
return "json-slot-icon"
|
return "json-slot-icon"
|
||||||
}
|
}
|
||||||
if (type !== "string" && type !== "number") {
|
if (!["string", "number", "bigint", "barcodeqr"].includes(type)) {
|
||||||
return "slot-icon"
|
return "slot-icon"
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -25,6 +25,8 @@ import BarButtonList from "./controls/BarButtonList.svelte"
|
||||||
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
|
import FieldConfiguration from "./controls/FieldConfiguration/FieldConfiguration.svelte"
|
||||||
import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte"
|
import ButtonConfiguration from "./controls/ButtonConfiguration/ButtonConfiguration.svelte"
|
||||||
import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte"
|
import RelationshipFilterEditor from "./controls/RelationshipFilterEditor.svelte"
|
||||||
|
import FormStepConfiguration from "./controls/FormStepConfiguration.svelte"
|
||||||
|
import FormStepControls from "components/design/settings/controls/FormStepControls.svelte"
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
text: DrawerBindableInput,
|
text: DrawerBindableInput,
|
||||||
|
@ -51,6 +53,8 @@ const componentMap = {
|
||||||
url: URLSelect,
|
url: URLSelect,
|
||||||
fieldConfiguration: FieldConfiguration,
|
fieldConfiguration: FieldConfiguration,
|
||||||
buttonConfiguration: ButtonConfiguration,
|
buttonConfiguration: ButtonConfiguration,
|
||||||
|
stepConfiguration: FormStepConfiguration,
|
||||||
|
formStepControls: FormStepControls,
|
||||||
columns: ColumnEditor,
|
columns: ColumnEditor,
|
||||||
"columns/basic": BasicColumnEditor,
|
"columns/basic": BasicColumnEditor,
|
||||||
"columns/grid": GridColumnEditor,
|
"columns/grid": GridColumnEditor,
|
||||||
|
|
|
@ -34,6 +34,9 @@
|
||||||
$: canAddButtons = max == null || buttonList.length < max
|
$: canAddButtons = max == null || buttonList.length < max
|
||||||
|
|
||||||
const sanitizeValue = val => {
|
const sanitizeValue = val => {
|
||||||
|
if (!Array.isArray(val)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return val?.map(button => {
|
return val?.map(button => {
|
||||||
return button._component ? button : buildPseudoInstance(button)
|
return button._component ? button : buildPseudoInstance(button)
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
export let draggable = true
|
export let draggable = true
|
||||||
export let focus
|
export let focus
|
||||||
|
|
||||||
|
let zoneType = generate()
|
||||||
|
|
||||||
let store = writable({
|
let store = writable({
|
||||||
selected: null,
|
selected: null,
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -46,6 +48,7 @@
|
||||||
return {
|
return {
|
||||||
id: listItemKey ? item[listItemKey] : generate(),
|
id: listItemKey ? item[listItemKey] : generate(),
|
||||||
item,
|
item,
|
||||||
|
type: zoneType,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(item => item.id)
|
.filter(item => item.id)
|
||||||
|
@ -83,6 +86,8 @@
|
||||||
items: draggableItems,
|
items: draggableItems,
|
||||||
dropTargetStyle: { outline: "none" },
|
dropTargetStyle: { outline: "none" },
|
||||||
dragDisabled: !draggable || inactive,
|
dragDisabled: !draggable || inactive,
|
||||||
|
type: zoneType,
|
||||||
|
dropFromOthersDisabled: true,
|
||||||
}}
|
}}
|
||||||
on:finalize={handleFinalize}
|
on:finalize={handleFinalize}
|
||||||
on:consider={updateRowOrder}
|
on:consider={updateRowOrder}
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
import { convertOldFieldFormat, getComponentForField } from "./utils"
|
import { convertOldFieldFormat, getComponentForField } from "./utils"
|
||||||
|
|
||||||
export let componentInstance
|
export let componentInstance
|
||||||
|
export let bindings
|
||||||
export let value
|
export let value
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -28,7 +29,9 @@
|
||||||
|
|
||||||
let selectAll = true
|
let selectAll = true
|
||||||
|
|
||||||
$: bindings = getBindableProperties($selectedScreen, componentInstance._id)
|
$: resolvedBindings =
|
||||||
|
bindings || getBindableProperties($selectedScreen, componentInstance._id)
|
||||||
|
|
||||||
$: actionType = componentInstance.actionType
|
$: actionType = componentInstance.actionType
|
||||||
let componentBindings = []
|
let componentBindings = []
|
||||||
|
|
||||||
|
@ -39,7 +42,10 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: datasource = getDatasourceForProvider($currentAsset, componentInstance)
|
$: datasource =
|
||||||
|
componentInstance.dataSource ||
|
||||||
|
getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
|
|
||||||
$: resourceId = datasource?.resourceId || datasource?.tableId
|
$: resourceId = datasource?.resourceId || datasource?.tableId
|
||||||
|
|
||||||
$: if (!isEqual(value, cachedValue)) {
|
$: if (!isEqual(value, cachedValue)) {
|
||||||
|
@ -179,7 +185,7 @@
|
||||||
listType={FieldSetting}
|
listType={FieldSetting}
|
||||||
listTypeProps={{
|
listTypeProps={{
|
||||||
componentBindings,
|
componentBindings,
|
||||||
bindings,
|
bindings: resolvedBindings,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
<div class="filter-editor">
|
<div class="filter-editor">
|
||||||
<ActionButton on:click={drawer.show}>{text}</ActionButton>
|
<ActionButton on:click={drawer.show}>{text}</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<Drawer bind:this={drawer} title="Filtering">
|
<Drawer bind:this={drawer} title="Filtering" on:drawerHide on:drawerShow>
|
||||||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
|
||||||
<FilterDrawer
|
<FilterDrawer
|
||||||
slot="body"
|
slot="body"
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher, setContext } from "svelte"
|
||||||
|
import ComponentSettingsSection from "../../../../pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/ComponentSettingsSection.svelte"
|
||||||
|
import { getDatasourceForProvider } from "builderStore/dataBinding"
|
||||||
|
import { currentAsset, store } from "builderStore"
|
||||||
|
import { Helpers } from "@budibase/bbui"
|
||||||
|
import { derived, writable } from "svelte/store"
|
||||||
|
import { Utils } from "@budibase/frontend-core"
|
||||||
|
import { cloneDeep } from "lodash"
|
||||||
|
|
||||||
|
export let componentInstance
|
||||||
|
export let componentBindings
|
||||||
|
export let value
|
||||||
|
export let bindings
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const multiStepStore = writable({
|
||||||
|
stepCount: value?.length ?? 0,
|
||||||
|
currentStep: 0,
|
||||||
|
})
|
||||||
|
const currentStep = derived(multiStepStore, state => state.currentStep)
|
||||||
|
const componentType = "@budibase/standard-components/multistepformblockstep"
|
||||||
|
|
||||||
|
setContext("multi-step-form-block", multiStepStore)
|
||||||
|
|
||||||
|
$: stepCount = value?.length || 0
|
||||||
|
$: updateStore(stepCount)
|
||||||
|
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
|
||||||
|
$: emitCurrentStep($currentStep)
|
||||||
|
$: stepLabel = getStepLabel($multiStepStore)
|
||||||
|
$: stepDef = getDefinition(stepLabel)
|
||||||
|
$: stepSettings = value?.[$currentStep] || {}
|
||||||
|
$: defaults = Utils.buildMultiStepFormBlockDefaultProps({
|
||||||
|
_id: componentInstance._id,
|
||||||
|
stepCount: $multiStepStore.stepCount,
|
||||||
|
currentStep: $multiStepStore.currentStep,
|
||||||
|
actionType: componentInstance.actionType,
|
||||||
|
dataSource: componentInstance.dataSource,
|
||||||
|
})
|
||||||
|
$: stepInstance = {
|
||||||
|
_id: Helpers.uuid(),
|
||||||
|
_component: componentType,
|
||||||
|
_instanceName: `Step ${currentStep + 1}`,
|
||||||
|
title: stepSettings.title ?? defaults.title,
|
||||||
|
buttons: stepSettings.buttons || defaults.buttons,
|
||||||
|
fields: stepSettings.fields,
|
||||||
|
desc: stepSettings.desc,
|
||||||
|
|
||||||
|
// Needed for field configuration
|
||||||
|
dataSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefinition = stepLabel => {
|
||||||
|
let def = cloneDeep(store.actions.components.getDefinition(componentType))
|
||||||
|
def.settings.find(x => x.key === "steps").label = stepLabel
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStore = stepCount => {
|
||||||
|
multiStepStore.update(state => {
|
||||||
|
state.stepCount = stepCount
|
||||||
|
if (state.currentStep >= stepCount) {
|
||||||
|
state.currentStep = 0
|
||||||
|
}
|
||||||
|
return { ...state }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStepLabel = ({ stepCount, currentStep }) => {
|
||||||
|
if (stepCount <= 1) {
|
||||||
|
return "Steps"
|
||||||
|
}
|
||||||
|
return `Steps (${currentStep + 1}/${stepCount})`
|
||||||
|
}
|
||||||
|
|
||||||
|
const emitCurrentStep = step => {
|
||||||
|
store.actions.preview.sendEvent("builder-meta", {
|
||||||
|
componentId: componentInstance._id,
|
||||||
|
step: step,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addStep = () => {
|
||||||
|
value = value.toSpliced($currentStep + 1, 0, {})
|
||||||
|
dispatch("change", value)
|
||||||
|
multiStepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: $currentStep + 1,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeStep = () => {
|
||||||
|
value = value.toSpliced($currentStep, 1)
|
||||||
|
dispatch("change", value)
|
||||||
|
multiStepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: Math.min($currentStep, stepCount - 2),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousStep = () => {
|
||||||
|
multiStepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: Math.max($currentStep - 1, 0),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextStep = () => {
|
||||||
|
multiStepStore.update(state => ({
|
||||||
|
...state,
|
||||||
|
currentStep: Math.min($currentStep + 1, value.length - 1),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStep = (field, val) => {
|
||||||
|
const newStep = {
|
||||||
|
...value[$currentStep],
|
||||||
|
[field.key]: val,
|
||||||
|
}
|
||||||
|
value = value.toSpliced($currentStep, 1, newStep)
|
||||||
|
dispatch("change", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleStepAction = action => {
|
||||||
|
switch (action) {
|
||||||
|
case "addStep":
|
||||||
|
addStep()
|
||||||
|
break
|
||||||
|
case "removeStep":
|
||||||
|
removeStep()
|
||||||
|
break
|
||||||
|
case "nextStep":
|
||||||
|
nextStep()
|
||||||
|
break
|
||||||
|
case "previousStep":
|
||||||
|
previousStep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processUpdate = (field, val) => {
|
||||||
|
if (field.key === "steps") {
|
||||||
|
handleStepAction(val.action)
|
||||||
|
} else {
|
||||||
|
updateStep(field, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="nested-section">
|
||||||
|
<ComponentSettingsSection
|
||||||
|
includeHidden
|
||||||
|
componentInstance={stepInstance}
|
||||||
|
componentDefinition={stepDef}
|
||||||
|
onUpdateSetting={processUpdate}
|
||||||
|
showSectionTitle={false}
|
||||||
|
isScreen={false}
|
||||||
|
nested={true}
|
||||||
|
{bindings}
|
||||||
|
{componentBindings}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.nested-section {
|
||||||
|
margin: 0 calc(-1 * var(--spacing-xl)) calc(-1 * var(--spacing-xl))
|
||||||
|
calc(-1 * var(--spacing-xl));
|
||||||
|
}
|
||||||
|
.nested-section :global(.property-panel) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,84 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
|
import { ActionButton } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const multiStepStore = getContext("multi-step-form-block")
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
$: ({ stepCount, currentStep } = $multiStepStore)
|
||||||
|
|
||||||
|
const stepAction = action => {
|
||||||
|
dispatch("change", {
|
||||||
|
action,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if stepCount === 1}
|
||||||
|
<div class="stretch">
|
||||||
|
<ActionButton
|
||||||
|
icon="MultipleAdd"
|
||||||
|
secondary
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("addStep")
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add Step
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="step-actions">
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
icon="ChevronLeft"
|
||||||
|
disabled={currentStep === 0}
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("previousStep")
|
||||||
|
}}
|
||||||
|
tooltip={"Previous step"}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
disabled={currentStep === stepCount - 1}
|
||||||
|
icon="ChevronRight"
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("nextStep")
|
||||||
|
}}
|
||||||
|
tooltip={"Next step"}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
icon="Close"
|
||||||
|
disabled={stepCount === 1}
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("removeStep")
|
||||||
|
}}
|
||||||
|
tooltip={"Remove step"}
|
||||||
|
/>
|
||||||
|
<ActionButton
|
||||||
|
size="S"
|
||||||
|
secondary
|
||||||
|
icon="MultipleAdd"
|
||||||
|
on:click={() => {
|
||||||
|
stepAction("addStep")
|
||||||
|
}}
|
||||||
|
tooltip={"Add step"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.stretch :global(.spectrum-ActionButton) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.step-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.step-actions :global(.spectrum-ActionButton) {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -49,7 +49,15 @@
|
||||||
<div class="field-label">{item.label || item.field}</div>
|
<div class="field-label">{item.label || item.field}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-item-right">
|
<div class="list-item-right">
|
||||||
<Toggle on:change={onToggle(item)} text="" value={item.active} thin />
|
<Toggle
|
||||||
|
on:click={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
on:change={onToggle(item)}
|
||||||
|
text=""
|
||||||
|
value={item.active}
|
||||||
|
thin
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ const getColumns = ({
|
||||||
primary,
|
primary,
|
||||||
sortable,
|
sortable,
|
||||||
updateSortable: newDraggableList => {
|
updateSortable: newDraggableList => {
|
||||||
onChange(toGridFormat(newDraggableList.concat(primary)))
|
onChange(toGridFormat(newDraggableList.concat(primary || [])))
|
||||||
},
|
},
|
||||||
update: newEntry => {
|
update: newEntry => {
|
||||||
const newDraggableList = draggableList.map(entry => {
|
const newDraggableList = draggableList.map(entry => {
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="options-wrap">
|
<div class="options-wrap">
|
||||||
<div />
|
|
||||||
<div><ActionButton on:click={drawer.show}>Define Options</ActionButton></div>
|
<div><ActionButton on:click={drawer.show}>Define Options</ActionButton></div>
|
||||||
</div>
|
</div>
|
||||||
<Drawer bind:this={drawer} title="Options" on:drawerHide on:drawerShow>
|
<Drawer bind:this={drawer} title="Options" on:drawerHide on:drawerShow>
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
export let propertyFocus = false
|
export let propertyFocus = false
|
||||||
export let info = null
|
export let info = null
|
||||||
export let disableBindings = false
|
export let disableBindings = false
|
||||||
|
export let wide
|
||||||
|
|
||||||
$: nullishValue = value == null || value === ""
|
$: nullishValue = value == null || value === ""
|
||||||
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
$: allBindings = getAllBindings(bindings, componentBindings, nested)
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="property-control"
|
class="property-control"
|
||||||
class:wide={!label || labelHidden}
|
class:wide={!label || labelHidden || wide === true}
|
||||||
class:highlighted={highlighted && nullishValue}
|
class:highlighted={highlighted && nullishValue}
|
||||||
class:property-focus={propertyFocus}
|
class:property-focus={propertyFocus}
|
||||||
>
|
>
|
||||||
|
@ -104,6 +105,7 @@
|
||||||
{...props}
|
{...props}
|
||||||
on:drawerHide
|
on:drawerHide
|
||||||
on:drawerShow
|
on:drawerShow
|
||||||
|
on:meta
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if info}
|
{#if info}
|
||||||
|
@ -146,15 +148,28 @@
|
||||||
.control {
|
.control {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
.property-control.wide .control {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
.text {
|
.text {
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
color: var(--grey-6);
|
color: var(--grey-6);
|
||||||
grid-column: 2 / 2;
|
grid-column: 2 / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.property-control.wide .control {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.property-control.wide {
|
||||||
|
grid-template-columns: unset;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.property-control.wide > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.property-control.wide .text {
|
.property-control.wide .text {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
.property-control.wide .label {
|
||||||
|
margin-bottom: -8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -32,4 +32,4 @@
|
||||||
$: schema = linkedTable?.schema
|
$: schema = linkedTable?.schema
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FilterEditor on:change {...$$props} {schema} />
|
<FilterEditor on:change {...$$props} {schema} on:drawerHide on:drawerShow />
|
||||||
|
|
|
@ -12,7 +12,10 @@
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { currentAsset, selectedComponent } from "builderStore"
|
import { currentAsset, selectedComponent } from "builderStore"
|
||||||
import { findClosestMatchingComponent } from "builderStore/componentUtils"
|
import { findClosestMatchingComponent } from "builderStore/componentUtils"
|
||||||
import { getSchemaForDatasource } from "builderStore/dataBinding"
|
import {
|
||||||
|
getSchemaForDatasource,
|
||||||
|
getDatasourceForProvider,
|
||||||
|
} from "builderStore/dataBinding"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
|
@ -124,6 +127,12 @@
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveDatasource = (currentAsset, componentInstance, parent) => {
|
||||||
|
return (
|
||||||
|
getDatasourceForProvider(currentAsset, parent || componentInstance) || {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
|
$: dataSourceSchema = getDataSourceSchema($currentAsset, $selectedComponent)
|
||||||
$: field = fieldName || $selectedComponent?.field
|
$: field = fieldName || $selectedComponent?.field
|
||||||
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
|
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
|
||||||
|
@ -146,8 +155,8 @@
|
||||||
component._component.endsWith("/formblock") ||
|
component._component.endsWith("/formblock") ||
|
||||||
component._component.endsWith("/tableblock")
|
component._component.endsWith("/tableblock")
|
||||||
)
|
)
|
||||||
|
const dataSource = resolveDatasource(asset, component, formParent)
|
||||||
return getSchemaForDatasource(asset, formParent?.dataSource)
|
return getSchemaForDatasource(asset, dataSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseRulesFromSchema = (field, dataSourceSchema) => {
|
const parseRulesFromSchema = (field, dataSourceSchema) => {
|
||||||
|
@ -164,7 +173,8 @@
|
||||||
// Required constraint
|
// Required constraint
|
||||||
if (
|
if (
|
||||||
field === dataSourceSchema?.table?.primaryDisplay ||
|
field === dataSourceSchema?.table?.primaryDisplay ||
|
||||||
constraints.presence?.allowEmpty === false
|
constraints.presence?.allowEmpty === false ||
|
||||||
|
constraints.presence === true
|
||||||
) {
|
) {
|
||||||
rules.push({
|
rules.push({
|
||||||
constraint: "required",
|
constraint: "required",
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
$goto("../portal")
|
$goto("../portal")
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
submitted = false
|
submitted = false
|
||||||
notifications.error("Failed to create admin user")
|
notifications.error(error.message || "Failed to create admin user")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
|
|
||||||
$: tables = Object.values(datasource.entities)
|
$: tables =
|
||||||
|
$tablesStore.list.filter(tbl => tbl.sourceId === datasource._id) || []
|
||||||
$: relationships = getRelationships(tables)
|
$: relationships = getRelationships(tables)
|
||||||
|
|
||||||
function getRelationships(tables) {
|
function getRelationships(tables) {
|
||||||
|
@ -43,14 +44,16 @@
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return Object.values(relatedColumns).map(({ from, to, through }) => {
|
return Object.values(relatedColumns)
|
||||||
return {
|
.filter(({ from, to }) => from && to)
|
||||||
tables: `${from.tableName} ${through ? "↔" : "→"} ${to.tableName}`,
|
.map(({ from, to, through }) => {
|
||||||
columns: `${from.name} to ${to.name}`,
|
return {
|
||||||
from,
|
tables: `${from.tableName} ${through ? "↔" : "→"} ${to.tableName}`,
|
||||||
to,
|
columns: `${from.name} to ${to.name}`,
|
||||||
}
|
from,
|
||||||
})
|
to,
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRowClick = ({ detail }) => {
|
const handleRowClick = ({ detail }) => {
|
||||||
|
|
|
@ -35,17 +35,16 @@
|
||||||
const customSections = settings.filter(
|
const customSections = settings.filter(
|
||||||
setting => setting.section && setting.tag === tag
|
setting => setting.section && setting.tag === tag
|
||||||
)
|
)
|
||||||
let sections = [
|
let sections = []
|
||||||
...(generalSettings?.length
|
if (generalSettings.length) {
|
||||||
? [
|
sections.push({
|
||||||
{
|
name: "General",
|
||||||
name: "General",
|
settings: generalSettings,
|
||||||
settings: generalSettings,
|
})
|
||||||
},
|
}
|
||||||
]
|
if (customSections.length) {
|
||||||
: []),
|
sections = sections.concat(customSections)
|
||||||
...(customSections || []),
|
}
|
||||||
]
|
|
||||||
|
|
||||||
// Filter out settings which shouldn't be rendered
|
// Filter out settings which shouldn't be rendered
|
||||||
sections.forEach(section => {
|
sections.forEach(section => {
|
||||||
|
@ -151,7 +150,8 @@
|
||||||
{#if section.visible}
|
{#if section.visible}
|
||||||
<DetailSummary
|
<DetailSummary
|
||||||
name={showSectionTitle ? section.name : ""}
|
name={showSectionTitle ? section.name : ""}
|
||||||
show={section.collapsed !== true}
|
initiallyShow={section.collapsed !== true}
|
||||||
|
collapsible={section.name !== "General"}
|
||||||
>
|
>
|
||||||
{#if section.info}
|
{#if section.info}
|
||||||
<div class="section-info">
|
<div class="section-info">
|
||||||
|
@ -171,6 +171,7 @@
|
||||||
control={getComponentForSetting(setting)}
|
control={getComponentForSetting(setting)}
|
||||||
label={setting.label}
|
label={setting.label}
|
||||||
labelHidden={setting.labelHidden}
|
labelHidden={setting.labelHidden}
|
||||||
|
wide={setting.wide}
|
||||||
key={setting.key}
|
key={setting.key}
|
||||||
value={componentInstance[setting.key]}
|
value={componentInstance[setting.key]}
|
||||||
defaultValue={setting.defaultValue}
|
defaultValue={setting.defaultValue}
|
||||||
|
@ -207,7 +208,7 @@
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{#if componentDefinition?.block && !tag}
|
{#if componentDefinition?.block && !tag && componentDefinition.ejectable !== false}
|
||||||
<DetailSummary name="Eject" collapsible={false}>
|
<DetailSummary name="Eject" collapsible={false}>
|
||||||
<EjectBlockButton />
|
<EjectBlockButton />
|
||||||
</DetailSummary>
|
</DetailSummary>
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
on:change={event => (tempValue = event.detail)}
|
on:change={event => (tempValue = event.detail)}
|
||||||
allowJS
|
allowJS
|
||||||
{bindings}
|
{bindings}
|
||||||
|
autofocusEditor={true}
|
||||||
/>
|
/>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
{/key}
|
{/key}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"cardsblock",
|
"cardsblock",
|
||||||
"repeaterblock",
|
"repeaterblock",
|
||||||
"formblock",
|
"formblock",
|
||||||
|
"multistepformblock",
|
||||||
"chartblock",
|
"chartblock",
|
||||||
"rowexplorer"
|
"rowexplorer"
|
||||||
]
|
]
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue