Merge branch 'master' into BUDI-7580/account_portal_submodule
This commit is contained in:
commit
e7bcabf1d2
|
@ -19,6 +19,7 @@
|
||||||
"bundle.js"
|
"bundle.js"
|
||||||
],
|
],
|
||||||
"extends": ["eslint:recommended"],
|
"extends": ["eslint:recommended"],
|
||||||
|
"plugins": ["import", "eslint-plugin-local-rules"],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["**/*.svelte"],
|
"files": ["**/*.svelte"],
|
||||||
|
@ -49,7 +50,8 @@
|
||||||
"no-case-declarations": "off",
|
"no-case-declarations": "off",
|
||||||
"no-useless-escape": "off",
|
"no-useless-escape": "off",
|
||||||
"no-undef": "off",
|
"no-undef": "off",
|
||||||
"no-prototype-builtins": "off"
|
"no-prototype-builtins": "off",
|
||||||
|
"local-rules/no-budibase-imports": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -62,7 +64,11 @@
|
||||||
"argsIgnorePattern": "^_",
|
"argsIgnorePattern": "^_",
|
||||||
"destructuredArrayIgnorePattern": "^_"
|
"destructuredArrayIgnorePattern": "^_"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"import/no-relative-packages": "error",
|
||||||
|
"import/export": "error",
|
||||||
|
"import/no-duplicates": "error",
|
||||||
|
"import/newline-after-import": "error"
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"GeolocationPositionError": true
|
"GeolocationPositionError": true
|
||||||
|
|
|
@ -12,6 +12,13 @@ on:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
run_as_oss:
|
||||||
|
type: boolean
|
||||||
|
required: false
|
||||||
|
description: Force running checks as if it was an OSS contributor
|
||||||
|
default: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
@ -19,50 +26,41 @@ env:
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
||||||
NX_BASE_BRANCH: origin/${{ github.base_ref }}
|
NX_BASE_BRANCH: origin/${{ github.base_ref }}
|
||||||
USE_NX_AFFECTED: ${{ github.event_name == 'pull_request' }}
|
USE_NX_AFFECTED: ${{ github.event_name == 'pull_request' }}
|
||||||
|
IS_OSS_CONTRIBUTOR: ${{ inputs.run_as_oss == true || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase') }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
- name: Checkout repo only
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase'
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- run: yarn lint
|
- run: yarn lint
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Checkout repo only
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase'
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
|
|
||||||
# Run build all the projects
|
# Run build all the projects
|
||||||
|
@ -81,24 +79,18 @@ jobs:
|
||||||
test-libraries:
|
test-libraries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Checkout repo only
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase'
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
|
@ -116,24 +108,18 @@ jobs:
|
||||||
test-worker:
|
test-worker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Checkout repo only
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase'
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test worker
|
- name: Test worker
|
||||||
run: |
|
run: |
|
||||||
|
@ -152,24 +138,18 @@ jobs:
|
||||||
test-server:
|
test-server:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Checkout repo only
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase'
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test server
|
- name: Test server
|
||||||
run: |
|
run: |
|
||||||
|
@ -200,7 +180,7 @@ jobs:
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
|
@ -213,24 +193,23 @@ jobs:
|
||||||
integration-test:
|
integration-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo and submodules
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: ${{ env.IS_OSS_CONTRIBUTOR == 'false' }}
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }}
|
||||||
- name: Checkout repo only
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != 'Budibase/budibase'
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
- name: Use Node.js 18.x
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
cache: "yarn"
|
cache: yarn
|
||||||
- run: yarn --frozen-lockfile
|
- run: yarn --frozen-lockfile
|
||||||
- name: Build packages
|
- name: Build packages
|
||||||
run: yarn build --scope @budibase/server --scope @budibase/worker
|
run: yarn build --scope @budibase/server --scope @budibase/worker
|
||||||
|
- name: Build backend-core for OSS contributor (required for pro)
|
||||||
|
if: ${{ env.IS_OSS_CONTRIBUTOR == 'true' }}
|
||||||
|
run: yarn build --scope @budibase/backend-core
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: |
|
run: |
|
||||||
cd qa-core
|
cd qa-core
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: OSS contributor checks
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 8,16 * * 1-5" # on weekdays at 8am and 4pm
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-checks:
|
||||||
|
name: Publish server and worker docker images
|
||||||
|
uses: ./.github/workflows/budibase_ci.yml
|
||||||
|
with:
|
||||||
|
run_as_oss: true
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
notify-error:
|
||||||
|
needs: ["run-checks"]
|
||||||
|
if: ${{ failure() }}
|
||||||
|
name: Notify error
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set commit SHA
|
||||||
|
id: set_sha
|
||||||
|
run: echo "::set-output name=sha::$(git rev-parse --short ${{ github.sha }})"
|
||||||
|
|
||||||
|
- name: Notify error
|
||||||
|
uses: tsickert/discord-webhook@v5.3.0
|
||||||
|
with:
|
||||||
|
webhook-url: ${{ secrets.OSS_CHECKS_WEBHOOK_URL }}
|
||||||
|
embed-title: 🚨 OSS checks failed in master
|
||||||
|
embed-url: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
embed-description: |
|
||||||
|
Git sha: `${{ steps.set_sha.outputs.sha }}`
|
|
@ -1,48 +0,0 @@
|
||||||
name: Budibase Deploy Production
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version:
|
|
||||||
description: Budibase release version. For example - 1.0.0
|
|
||||||
required: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Fail if not a tag
|
|
||||||
run: |
|
|
||||||
if [[ $GITHUB_REF != refs/tags/* ]]; then
|
|
||||||
echo "Workflow Dispatch can only be run on tags"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Fail if tag is not in master
|
|
||||||
run: |
|
|
||||||
if ! git merge-base --is-ancestor ${{ github.sha }} origin/master; then
|
|
||||||
echo "Tag is not in master. This pipeline can only execute tags that are present on the master branch"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Get the latest budibase release version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
if [ -z "${{ github.event.inputs.version }}" ]; then
|
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
|
||||||
else
|
|
||||||
release_version=${{ github.event.inputs.version }}
|
|
||||||
fi
|
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- uses: passeidireto/trigger-external-workflow-action@main
|
|
||||||
env:
|
|
||||||
PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }}
|
|
||||||
with:
|
|
||||||
repository: budibase/budibase-deploys
|
|
||||||
event: budicloud-prod-deploy
|
|
||||||
github_pat: ${{ secrets.GH_ACCESS_TOKEN }}
|
|
|
@ -7,6 +7,7 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == 'Budibase/budibase'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
|
@ -1,178 +0,0 @@
|
||||||
name: Budibase Release
|
|
||||||
concurrency:
|
|
||||||
group: release
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+"
|
|
||||||
# Exclude all pre-releases
|
|
||||||
- "!*[0-9]+.[0-9]+.[0-9]+-*"
|
|
||||||
|
|
||||||
env:
|
|
||||||
# Posthog token used by ui at build time
|
|
||||||
POSTHOG_TOKEN: phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
|
||||||
INTERCOM_TOKEN: ${{ secrets.INTERCOM_TOKEN }}
|
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release-images:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Fail if tag is not in master
|
|
||||||
run: |
|
|
||||||
if ! git merge-base --is-ancestor ${{ github.sha }} origin/master; then
|
|
||||||
echo "Tag is not in master. This pipeline can only execute tags that are present on the master branch"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
cache: yarn
|
|
||||||
|
|
||||||
- run: yarn install --frozen-lockfile
|
|
||||||
- name: Update versions
|
|
||||||
run: ./scripts/updateVersions.sh
|
|
||||||
- run: yarn lint
|
|
||||||
- run: yarn build
|
|
||||||
- run: yarn build:sdk
|
|
||||||
|
|
||||||
- name: Publish budibase packages to NPM
|
|
||||||
env:
|
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
run: |
|
|
||||||
# setup the username and email. I tend to use 'GitHub Actions Bot' with no email by default
|
|
||||||
git config --global user.name "Budibase Release Bot"
|
|
||||||
git config --global user.email "<>"
|
|
||||||
git submodule foreach git commit -a -m 'Release process'
|
|
||||||
git commit -a -m 'Release process'
|
|
||||||
echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} >> .npmrc
|
|
||||||
yarn release
|
|
||||||
|
|
||||||
- name: "Get Current tag"
|
|
||||||
id: currenttag
|
|
||||||
run: |
|
|
||||||
version=$(./scripts/getCurrentVersion.sh)
|
|
||||||
echo "Using tag $version"
|
|
||||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Setup Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- name: Docker login
|
|
||||||
run: |
|
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
|
|
||||||
- name: Build worker docker
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
build-args: |
|
|
||||||
BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
|
|
||||||
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
|
||||||
file: ./packages/worker/Dockerfile.v2
|
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:latest
|
|
||||||
cache-to: type=inline
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: budibase/worker
|
|
||||||
IMAGE_TAG: ${{ steps.currenttag.outputs.version }}
|
|
||||||
BUDIBASE_VERSION: ${{ steps.currenttag.outputs.version }}
|
|
||||||
|
|
||||||
- name: Build server docker
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
build-args: |
|
|
||||||
BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
|
|
||||||
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
|
||||||
file: ./packages/server/Dockerfile.v2
|
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:latest
|
|
||||||
cache-to: type=inline
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: budibase/apps
|
|
||||||
IMAGE_TAG: ${{ steps.currenttag.outputs.version }}
|
|
||||||
BUDIBASE_VERSION: ${{ steps.currenttag.outputs.version }}
|
|
||||||
|
|
||||||
- name: Build proxy docker
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
|
||||||
context: ./hosting/proxy
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
|
||||||
file: ./hosting/proxy/Dockerfile
|
|
||||||
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:latest
|
|
||||||
cache-to: type=inline
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: budibase/proxy
|
|
||||||
IMAGE_TAG: ${{ steps.currenttag.outputs.version }}
|
|
||||||
|
|
||||||
release-helm-chart:
|
|
||||||
needs: [release-images]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Setup Helm
|
|
||||||
uses: azure/setup-helm@v1
|
|
||||||
id: helm-install
|
|
||||||
|
|
||||||
- name: Get the latest budibase release version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
# due to helm repo index issue: https://github.com/helm/helm/issues/7363
|
|
||||||
# we need to create new package in a different dir, merge the index and move the package back
|
|
||||||
- name: Build and release helm chart
|
|
||||||
run: |
|
|
||||||
git config user.name "Budibase Helm Bot"
|
|
||||||
git config user.email "<>"
|
|
||||||
git reset --hard
|
|
||||||
git fetch
|
|
||||||
mkdir sync
|
|
||||||
echo "Packaging chart to sync dir"
|
|
||||||
helm package charts/budibase --version 0.0.0-master --app-version "$RELEASE_VERSION" --destination sync
|
|
||||||
echo "Packaging successful"
|
|
||||||
git checkout gh-pages
|
|
||||||
echo "Indexing helm repo"
|
|
||||||
helm repo index --merge docs/index.yaml sync
|
|
||||||
mv -f sync/* docs
|
|
||||||
rm -rf sync
|
|
||||||
echo "Pushing new helm release"
|
|
||||||
git add -A
|
|
||||||
git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}"
|
|
||||||
git push
|
|
||||||
|
|
||||||
trigger-deploy-to-qa-env:
|
|
||||||
needs: [release-helm-chart]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: peter-evans/repository-dispatch@v2
|
|
||||||
with:
|
|
||||||
repository: budibase/budibase-deploys
|
|
||||||
event-type: budicloud-qa-deploy
|
|
||||||
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
|
||||||
client-payload: |-
|
|
||||||
{
|
|
||||||
"VERSION": "${{ github.ref_name }}",
|
|
||||||
"REF_NAME": "${{ github.ref_name}}"
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
name: Budibase Release Selfhost
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Fail if not a tag
|
|
||||||
run: |
|
|
||||||
if [[ $GITHUB_REF != refs/tags/* ]]; then
|
|
||||||
echo "Workflow Dispatch can only be run on tags"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Fail if tag is not in master
|
|
||||||
run: |
|
|
||||||
if ! git merge-base --is-ancestor ${{ github.sha }} origin/master; then
|
|
||||||
echo "Tag is not in master. This pipeline can only execute tags that are present on the master branch"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Use Node.js 18.x
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: 18.x
|
|
||||||
|
|
||||||
- name: Get the latest budibase release version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Tag and release Docker images (Self Host)
|
|
||||||
run: |
|
|
||||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
|
||||||
|
|
||||||
release_tag=${{ env.RELEASE_VERSION }}
|
|
||||||
|
|
||||||
# Pull apps and worker images
|
|
||||||
docker pull budibase/apps:$release_tag
|
|
||||||
docker pull budibase/worker:$release_tag
|
|
||||||
docker pull budibase/proxy:$release_tag
|
|
||||||
|
|
||||||
# Tag apps and worker images
|
|
||||||
docker tag budibase/apps:$release_tag budibase/apps:$SELFHOST_TAG
|
|
||||||
docker tag budibase/worker:$release_tag budibase/worker:$SELFHOST_TAG
|
|
||||||
docker tag budibase/proxy:$release_tag budibase/proxy:$SELFHOST_TAG
|
|
||||||
|
|
||||||
# Push images
|
|
||||||
docker push budibase/apps:$SELFHOST_TAG
|
|
||||||
docker push budibase/worker:$SELFHOST_TAG
|
|
||||||
docker push budibase/proxy:$SELFHOST_TAG
|
|
||||||
env:
|
|
||||||
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
SELFHOST_TAG: latest
|
|
||||||
|
|
||||||
- name: Bootstrap and build (CLI)
|
|
||||||
run: |
|
|
||||||
yarn
|
|
||||||
yarn build
|
|
||||||
|
|
||||||
- name: Build OpenAPI spec
|
|
||||||
run: |
|
|
||||||
pushd packages/server
|
|
||||||
yarn
|
|
||||||
yarn specs
|
|
||||||
popd
|
|
||||||
|
|
||||||
- name: Setup Helm
|
|
||||||
uses: azure/setup-helm@v1
|
|
||||||
id: helm-install
|
|
||||||
|
|
||||||
# due to helm repo index issue: https://github.com/helm/helm/issues/7363
|
|
||||||
# we need to create new package in a different dir, merge the index and move the package back
|
|
||||||
- name: Build and release helm chart
|
|
||||||
run: |
|
|
||||||
git config user.name "Budibase Helm Bot"
|
|
||||||
git config user.email "<>"
|
|
||||||
git reset --hard
|
|
||||||
git fetch
|
|
||||||
mkdir sync
|
|
||||||
echo "Packaging chart to sync dir"
|
|
||||||
helm package charts/budibase --version "$RELEASE_VERSION" --app-version "$RELEASE_VERSION" --destination sync
|
|
||||||
echo "Packaging successful"
|
|
||||||
git checkout gh-pages
|
|
||||||
echo "Indexing helm repo"
|
|
||||||
helm repo index --merge docs/index.yaml sync
|
|
||||||
mv -f sync/* docs
|
|
||||||
rm -rf sync
|
|
||||||
echo "Pushing new helm release"
|
|
||||||
git add -A
|
|
||||||
git commit -m "Helm Release: ${{ env.RELEASE_VERSION }}"
|
|
||||||
git push
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Perform Github Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
name: ${{ env.RELEASE_VERSION }}
|
|
||||||
tag_name: ${{ env.RELEASE_VERSION }}
|
|
||||||
generate_release_notes: true
|
|
||||||
files: |
|
|
||||||
packages/cli/build/cli-win.exe
|
|
||||||
packages/cli/build/cli-linux
|
|
||||||
packages/cli/build/cli-macos
|
|
||||||
packages/server/specs/openapi.yaml
|
|
||||||
packages/server/specs/openapi.json
|
|
||||||
|
|
||||||
- name: Discord Webhook Action
|
|
||||||
uses: tsickert/discord-webhook@v4.0.0
|
|
||||||
with:
|
|
||||||
webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }}
|
|
||||||
content: "Self Host Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Self Host."
|
|
||||||
embed-title: ${{ env.RELEASE_VERSION }}
|
|
|
@ -1,85 +0,0 @@
|
||||||
name: Deploy Budibase Single Container Image to DockerHub
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
REGISTRY_URL: registry.hub.docker.com
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: "build"
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [18.x]
|
|
||||||
steps:
|
|
||||||
- name: Maximize build space
|
|
||||||
uses: easimon/maximize-build-space@master
|
|
||||||
with:
|
|
||||||
root-reserve-mb: 30000
|
|
||||||
swap-size-mb: 1024
|
|
||||||
remove-android: "true"
|
|
||||||
remove-dotnet: "true"
|
|
||||||
- name: Fail if not a tag
|
|
||||||
run: |
|
|
||||||
if [[ $GITHUB_REF != refs/tags/* ]]; then
|
|
||||||
echo "Workflow Dispatch can only be run on tags"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- name: "Checkout"
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: actions/setup-node@v1
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
- name: Setup QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Setup Docker Buildx
|
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Run Yarn
|
|
||||||
run: yarn
|
|
||||||
- name: Update versions
|
|
||||||
run: ./scripts/updateVersions.sh
|
|
||||||
- name: Run Yarn Build
|
|
||||||
run: yarn build
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_API_KEY }}
|
|
||||||
- name: Get the latest release version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
release_version=$(cat lerna.json | jq -r '.version')
|
|
||||||
echo $release_version
|
|
||||||
echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV
|
|
||||||
- name: Tag and release Budibase service docker image
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
build-args: BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
|
|
||||||
tags: budibase/budibase,budibase/budibase:${{ env.RELEASE_VERSION }}
|
|
||||||
file: ./hosting/single/Dockerfile.v2
|
|
||||||
env:
|
|
||||||
BUDIBASE_VERSION: ${{ env.RELEASE_VERSION }}
|
|
||||||
- name: Tag and release Budibase Azure App Service docker image
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
platforms: linux/amd64
|
|
||||||
build-args: |
|
|
||||||
TARGETBUILD=aas
|
|
||||||
BUDIBASE_VERSION=${{ env.BUDIBASE_VERSION }}
|
|
||||||
tags: budibase/budibase-aas,budibase/budibase-aas:${{ env.RELEASE_VERSION }}
|
|
||||||
file: ./hosting/single/Dockerfile.v2
|
|
||||||
env:
|
|
||||||
BUDIBASE_VERSION: ${{ env.RELEASE_VERSION }}
|
|
|
@ -1,13 +1,11 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
*.spec.js
|
|
||||||
packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
|
|
||||||
packages/server/builder
|
packages/server/builder
|
||||||
packages/server/coverage
|
packages/server/coverage
|
||||||
packages/worker/coverage
|
|
||||||
packages/backend-core/coverage
|
|
||||||
packages/server/client
|
packages/server/client
|
||||||
packages/server/src/definitions/openapi.ts
|
packages/server/src/definitions/openapi.ts
|
||||||
|
packages/worker/coverage
|
||||||
|
packages/backend-core/coverage
|
||||||
packages/builder/.routify
|
packages/builder/.routify
|
||||||
packages/sdk/sdk
|
packages/sdk/sdk
|
||||||
packages/pro/coverage
|
packages/pro/coverage
|
|
@ -46,11 +46,9 @@ spec:
|
||||||
image: minio/minio
|
image: minio/minio
|
||||||
imagePullPolicy: ""
|
imagePullPolicy: ""
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
exec:
|
httpGet:
|
||||||
command:
|
path: /minio/health/live
|
||||||
- curl
|
port: 9000
|
||||||
- -f
|
|
||||||
- http://localhost:9000/minio/health/live
|
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
timeoutSeconds: 20
|
timeoutSeconds: 20
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
module.exports = {
|
||||||
|
"no-budibase-imports": {
|
||||||
|
create: function (context) {
|
||||||
|
return {
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
const importPath = node.source.value
|
||||||
|
|
||||||
|
if (
|
||||||
|
/^@budibase\/[^/]+\/.*$/.test(importPath) &&
|
||||||
|
importPath !== "@budibase/backend-core/tests"
|
||||||
|
) {
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message: `Importing from @budibase is not allowed, except for @budibase/backend-core/tests.`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -29,7 +29,6 @@ WORKDIR /opt/couchdb
|
||||||
ADD couch/vm.args couch/local.ini ./etc/
|
ADD couch/vm.args couch/local.ini ./etc/
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
ADD build-target-paths.sh .
|
|
||||||
ADD runner.sh ./bbcouch-runner.sh
|
ADD runner.sh ./bbcouch-runner.sh
|
||||||
RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau ./build-target-paths.sh
|
RUN chmod +x ./bbcouch-runner.sh /opt/clouseau/bin/clouseau
|
||||||
CMD ["./bbcouch-runner.sh"]
|
CMD ["./bbcouch-runner.sh"]
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo ${TARGETBUILD} > /buildtarget.txt
|
|
||||||
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
|
||||||
# Azure AppService uses /home for persistent data & SSH on port 2222
|
|
||||||
DATA_DIR="${DATA_DIR:-/home}"
|
|
||||||
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
|
||||||
mkdir -p $DATA_DIR/{search,minio,couch}
|
|
||||||
mkdir -p $DATA_DIR/couch/{dbs,views}
|
|
||||||
chown -R couchdb:couchdb $DATA_DIR/couch/
|
|
||||||
apt update
|
|
||||||
apt-get install -y openssh-server
|
|
||||||
echo "root:Docker!" | chpasswd
|
|
||||||
mkdir -p /tmp
|
|
||||||
chmod +x /tmp/ssh_setup.sh \
|
|
||||||
&& (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
|
|
||||||
cp /etc/sshd_config /etc/ssh/sshd_config
|
|
||||||
/etc/init.d/ssh restart
|
|
||||||
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
|
|
||||||
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
|
|
||||||
else
|
|
||||||
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
|
||||||
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
|
||||||
fi
|
|
|
@ -1,14 +1,52 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DATA_DIR=${DATA_DIR:-/data}
|
DATA_DIR=${DATA_DIR:-/data}
|
||||||
|
|
||||||
mkdir -p ${DATA_DIR}
|
mkdir -p ${DATA_DIR}
|
||||||
mkdir -p ${DATA_DIR}/couch/{dbs,views}
|
mkdir -p ${DATA_DIR}/couch/{dbs,views}
|
||||||
mkdir -p ${DATA_DIR}/search
|
mkdir -p ${DATA_DIR}/search
|
||||||
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
/build-target-paths.sh
|
|
||||||
|
echo ${TARGETBUILD} > /buildtarget.txt
|
||||||
|
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||||
|
# Azure AppService uses /home for persistent data & SSH on port 2222
|
||||||
|
DATA_DIR="${DATA_DIR:-/home}"
|
||||||
|
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
||||||
|
mkdir -p $DATA_DIR/{search,minio,couch}
|
||||||
|
mkdir -p $DATA_DIR/couch/{dbs,views}
|
||||||
|
chown -R couchdb:couchdb $DATA_DIR/couch/
|
||||||
|
apt update
|
||||||
|
apt-get install -y openssh-server
|
||||||
|
echo "root:Docker!" | chpasswd
|
||||||
|
mkdir -p /tmp
|
||||||
|
chmod +x /tmp/ssh_setup.sh \
|
||||||
|
&& (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
|
||||||
|
cp /etc/sshd_config /etc/ssh/sshd_config
|
||||||
|
/etc/init.d/ssh restart
|
||||||
|
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
|
||||||
|
elif [[ "${TARGETBUILD}" = "single" ]]; then
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
||||||
|
elif [[ -n $KUBERNETES_SERVICE_HOST ]]; then
|
||||||
|
# In Kubernetes the directory /opt/couchdb/data has a persistent volume
|
||||||
|
# mount for storing database data.
|
||||||
|
sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/opt/couchdb/data#g" /opt/couchdb/etc/local.ini
|
||||||
|
sed -i "s/^-name .*$//g" /opt/couchdb/etc/vm.args
|
||||||
|
else
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
||||||
|
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
||||||
|
fi
|
||||||
|
|
||||||
/opt/clouseau/bin/clouseau > /dev/stdout 2>&1 &
|
/opt/clouseau/bin/clouseau > /dev/stdout 2>&1 &
|
||||||
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
||||||
sleep 10
|
|
||||||
|
while [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/_up -o /dev/null) -ne 200 ]]; do
|
||||||
|
echo 'Waiting for CouchDB to start...';
|
||||||
|
sleep 5;
|
||||||
|
done
|
||||||
|
|
||||||
curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_users
|
curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_users
|
||||||
curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_replicator
|
curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_replicator
|
||||||
sleep infinity
|
sleep infinity
|
|
@ -6,7 +6,7 @@ services:
|
||||||
app-service:
|
app-service:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: packages/server/Dockerfile.v2
|
dockerfile: packages/server/Dockerfile
|
||||||
args:
|
args:
|
||||||
- BUDIBASE_VERSION=0.0.0+dev-docker
|
- BUDIBASE_VERSION=0.0.0+dev-docker
|
||||||
container_name: build-bbapps
|
container_name: build-bbapps
|
||||||
|
@ -36,7 +36,7 @@ services:
|
||||||
worker-service:
|
worker-service:
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: packages/worker/Dockerfile.v2
|
dockerfile: packages/worker/Dockerfile
|
||||||
args:
|
args:
|
||||||
- BUDIBASE_VERSION=0.0.0+dev-docker
|
- BUDIBASE_VERSION=0.0.0+dev-docker
|
||||||
container_name: build-bbworker
|
container_name: build-bbworker
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo ${TARGETBUILD} > /buildtarget.txt
|
|
||||||
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
|
||||||
# Azure AppService uses /home for persistent data & SSH on port 2222
|
|
||||||
DATA_DIR="${DATA_DIR:-/home}"
|
|
||||||
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
|
||||||
mkdir -p $DATA_DIR/{search,minio,couch}
|
|
||||||
mkdir -p $DATA_DIR/couch/{dbs,views}
|
|
||||||
chown -R couchdb:couchdb $DATA_DIR/couch/
|
|
||||||
apt update
|
|
||||||
apt-get install -y openssh-server
|
|
||||||
echo "root:Docker!" | chpasswd
|
|
||||||
mkdir -p /tmp
|
|
||||||
chmod +x /tmp/ssh_setup.sh \
|
|
||||||
&& (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
|
|
||||||
cp /etc/sshd_config /etc/ssh/sshd_config
|
|
||||||
/etc/init.d/ssh restart
|
|
||||||
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
|
|
||||||
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
|
|
||||||
else
|
|
||||||
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
|
||||||
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
|
||||||
fi
|
|
|
@ -1,44 +1,59 @@
|
||||||
FROM node:18-slim as build
|
FROM node:18-slim as build
|
||||||
|
|
||||||
# install node-gyp dependencies
|
# install node-gyp dependencies
|
||||||
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends apt-utils cron g++ make python3
|
RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq
|
||||||
|
|
||||||
# add pin script
|
|
||||||
WORKDIR /
|
|
||||||
ADD scripts/cleanup.sh ./
|
|
||||||
RUN chmod +x /cleanup.sh
|
|
||||||
|
|
||||||
# build server
|
# copy and install dependencies
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD packages/server .
|
COPY package.json .
|
||||||
COPY yarn.lock .
|
COPY yarn.lock .
|
||||||
RUN yarn install --production=true --network-timeout 1000000
|
COPY lerna.json .
|
||||||
RUN /cleanup.sh
|
COPY .yarnrc .
|
||||||
|
|
||||||
# build worker
|
COPY packages/server/package.json packages/server/package.json
|
||||||
WORKDIR /worker
|
COPY packages/worker/package.json packages/worker/package.json
|
||||||
ADD packages/worker .
|
# string-templates does not get bundled during the esbuild process, so we want to use the local version
|
||||||
COPY yarn.lock .
|
COPY packages/string-templates/package.json packages/string-templates/package.json
|
||||||
RUN yarn install --production=true --network-timeout 1000000
|
|
||||||
RUN /cleanup.sh
|
|
||||||
|
|
||||||
FROM budibase/couchdb
|
|
||||||
|
COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh
|
||||||
|
RUN chmod +x ./scripts/removeWorkspaceDependencies.sh
|
||||||
|
RUN ./scripts/removeWorkspaceDependencies.sh packages/server/package.json
|
||||||
|
RUN ./scripts/removeWorkspaceDependencies.sh packages/worker/package.json
|
||||||
|
|
||||||
|
|
||||||
|
# We will never want to sync pro, but the script is still required
|
||||||
|
RUN echo '' > scripts/syncProPackage.js
|
||||||
|
RUN jq 'del(.scripts.postinstall)' package.json > temp.json && mv temp.json package.json
|
||||||
|
RUN ./scripts/removeWorkspaceDependencies.sh package.json
|
||||||
|
RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production
|
||||||
|
|
||||||
|
# copy the actual code
|
||||||
|
COPY packages/server/dist packages/server/dist
|
||||||
|
COPY packages/server/pm2.config.js packages/server/pm2.config.js
|
||||||
|
COPY packages/server/client packages/server/client
|
||||||
|
COPY packages/server/builder packages/server/builder
|
||||||
|
COPY packages/worker/dist packages/worker/dist
|
||||||
|
COPY packages/worker/pm2.config.js packages/worker/pm2.config.js
|
||||||
|
COPY packages/string-templates packages/string-templates
|
||||||
|
|
||||||
|
|
||||||
|
FROM budibase/couchdb as runner
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV TARGETARCH $TARGETARCH
|
ENV TARGETARCH $TARGETARCH
|
||||||
|
ENV NODE_MAJOR 18
|
||||||
#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
|
||||||
ENV TARGETBUILD $TARGETBUILD
|
ENV TARGETBUILD $TARGETBUILD
|
||||||
|
|
||||||
COPY --from=build /app /app
|
|
||||||
COPY --from=build /worker /worker
|
|
||||||
|
|
||||||
# install base dependencies
|
# install base dependencies
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server
|
apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server libaio1
|
||||||
|
|
||||||
# Install postgres client for pg_dump utils
|
# Install postgres client for pg_dump utils
|
||||||
RUN apt install software-properties-common apt-transport-https gpg -y \
|
RUN apt install -y software-properties-common apt-transport-https ca-certificates gnupg \
|
||||||
&& curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \
|
&& curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \
|
||||||
&& echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \
|
&& echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \
|
||||||
&& apt update -y \
|
&& apt update -y \
|
||||||
|
@ -47,14 +62,12 @@ RUN apt install software-properties-common apt-transport-https gpg -y \
|
||||||
|
|
||||||
# install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx
|
# install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx
|
||||||
WORKDIR /nodejs
|
WORKDIR /nodejs
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \
|
COPY scripts/install-node.sh ./install.sh
|
||||||
bash /tmp/nodesource_setup.sh && \
|
RUN chmod +x install.sh && ./install.sh
|
||||||
apt-get install -y --no-install-recommends libaio1 nodejs && \
|
|
||||||
npm install --global yarn pm2
|
|
||||||
|
|
||||||
# setup nginx
|
# setup nginx
|
||||||
ADD hosting/single/nginx/nginx.conf /etc/nginx
|
COPY hosting/single/nginx/nginx.conf /etc/nginx
|
||||||
ADD hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default
|
COPY hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default
|
||||||
RUN mkdir -p /var/log/nginx && \
|
RUN mkdir -p /var/log/nginx && \
|
||||||
touch /var/log/nginx/error.log && \
|
touch /var/log/nginx/error.log && \
|
||||||
touch /var/run/nginx.pid && \
|
touch /var/run/nginx.pid && \
|
||||||
|
@ -62,29 +75,39 @@ RUN mkdir -p /var/log/nginx && \
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN mkdir -p scripts/integrations/oracle
|
RUN mkdir -p scripts/integrations/oracle
|
||||||
ADD packages/server/scripts/integrations/oracle scripts/integrations/oracle
|
COPY packages/server/scripts/integrations/oracle scripts/integrations/oracle
|
||||||
RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh
|
RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh
|
||||||
|
|
||||||
# setup minio
|
# setup minio
|
||||||
WORKDIR /minio
|
WORKDIR /minio
|
||||||
ADD scripts/install-minio.sh ./install.sh
|
COPY scripts/install-minio.sh ./install.sh
|
||||||
RUN chmod +x install.sh && ./install.sh
|
RUN chmod +x install.sh && ./install.sh
|
||||||
|
|
||||||
# setup runner file
|
# setup runner file
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
ADD hosting/single/runner.sh .
|
COPY hosting/single/runner.sh .
|
||||||
RUN chmod +x ./runner.sh
|
RUN chmod +x ./runner.sh
|
||||||
ADD hosting/single/healthcheck.sh .
|
COPY hosting/single/healthcheck.sh .
|
||||||
RUN chmod +x ./healthcheck.sh
|
RUN chmod +x ./healthcheck.sh
|
||||||
|
|
||||||
# Script below sets the path for storing data based on $DATA_DIR
|
# Script below sets the path for storing data based on $DATA_DIR
|
||||||
# For Azure App Service install SSH & point data locations to /home
|
# For Azure App Service install SSH & point data locations to /home
|
||||||
ADD hosting/single/ssh/sshd_config /etc/
|
COPY hosting/single/ssh/sshd_config /etc/
|
||||||
ADD hosting/single/ssh/ssh_setup.sh /tmp
|
COPY hosting/single/ssh/ssh_setup.sh /tmp
|
||||||
RUN /build-target-paths.sh
|
|
||||||
|
# setup letsencrypt certificate
|
||||||
|
RUN apt-get install -y certbot python3-certbot-nginx
|
||||||
|
COPY hosting/letsencrypt /app/letsencrypt
|
||||||
|
RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh
|
||||||
|
|
||||||
|
COPY --from=build /app/node_modules /node_modules
|
||||||
|
COPY --from=build /app/package.json /package.json
|
||||||
|
COPY --from=build /app/packages/server /app
|
||||||
|
COPY --from=build /app/packages/worker /worker
|
||||||
|
COPY --from=build /app/packages/string-templates /string-templates
|
||||||
|
|
||||||
|
RUN cd /string-templates && yarn link && cd ../app && yarn link @budibase/string-templates && cd ../worker && yarn link @budibase/string-templates
|
||||||
|
|
||||||
# cleanup cache
|
|
||||||
RUN yarn cache clean -f
|
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
EXPOSE 443
|
EXPOSE 443
|
||||||
|
@ -92,20 +115,10 @@ EXPOSE 443
|
||||||
EXPOSE 2222
|
EXPOSE 2222
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
# setup letsencrypt certificate
|
ARG BUDIBASE_VERSION
|
||||||
RUN apt-get install -y certbot python3-certbot-nginx
|
# Ensuring the version argument is sent
|
||||||
ADD hosting/letsencrypt /app/letsencrypt
|
RUN test -n "$BUDIBASE_VERSION"
|
||||||
RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh
|
ENV BUDIBASE_VERSION=$BUDIBASE_VERSION
|
||||||
# Remove cached files
|
|
||||||
RUN rm -rf \
|
|
||||||
/root/.cache \
|
|
||||||
/root/.npm \
|
|
||||||
/root/.pip \
|
|
||||||
/usr/local/share/doc \
|
|
||||||
/usr/share/doc \
|
|
||||||
/usr/share/man \
|
|
||||||
/var/lib/apt/lists/* \
|
|
||||||
/tmp/*
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh"
|
HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh"
|
||||||
|
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
FROM node:18-slim as build
|
|
||||||
|
|
||||||
# install node-gyp dependencies
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends g++ make python3 jq
|
|
||||||
|
|
||||||
|
|
||||||
# copy and install dependencies
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package.json .
|
|
||||||
COPY yarn.lock .
|
|
||||||
COPY lerna.json .
|
|
||||||
COPY .yarnrc .
|
|
||||||
|
|
||||||
COPY packages/server/package.json packages/server/package.json
|
|
||||||
COPY packages/worker/package.json packages/worker/package.json
|
|
||||||
# string-templates does not get bundled during the esbuild process, so we want to use the local version
|
|
||||||
COPY packages/string-templates/package.json packages/string-templates/package.json
|
|
||||||
|
|
||||||
|
|
||||||
COPY scripts/removeWorkspaceDependencies.sh scripts/removeWorkspaceDependencies.sh
|
|
||||||
RUN chmod +x ./scripts/removeWorkspaceDependencies.sh
|
|
||||||
RUN ./scripts/removeWorkspaceDependencies.sh packages/server/package.json
|
|
||||||
RUN ./scripts/removeWorkspaceDependencies.sh packages/worker/package.json
|
|
||||||
|
|
||||||
|
|
||||||
# We will never want to sync pro, but the script is still required
|
|
||||||
RUN echo '' > scripts/syncProPackage.js
|
|
||||||
RUN jq 'del(.scripts.postinstall)' package.json > temp.json && mv temp.json package.json
|
|
||||||
RUN ./scripts/removeWorkspaceDependencies.sh package.json
|
|
||||||
RUN --mount=type=cache,target=/root/.yarn YARN_CACHE_FOLDER=/root/.yarn yarn install --production
|
|
||||||
|
|
||||||
# copy the actual code
|
|
||||||
COPY packages/server/dist packages/server/dist
|
|
||||||
COPY packages/server/pm2.config.js packages/server/pm2.config.js
|
|
||||||
COPY packages/server/client packages/server/client
|
|
||||||
COPY packages/server/builder packages/server/builder
|
|
||||||
COPY packages/worker/dist packages/worker/dist
|
|
||||||
COPY packages/worker/pm2.config.js packages/worker/pm2.config.js
|
|
||||||
COPY packages/string-templates packages/string-templates
|
|
||||||
|
|
||||||
|
|
||||||
FROM budibase/couchdb as runner
|
|
||||||
ARG TARGETARCH
|
|
||||||
ENV TARGETARCH $TARGETARCH
|
|
||||||
ENV NODE_MAJOR 18
|
|
||||||
#TARGETBUILD can be set to single (for single docker image) or aas (for azure app service)
|
|
||||||
# e.g. docker build --build-arg TARGETBUILD=aas ....
|
|
||||||
ARG TARGETBUILD=single
|
|
||||||
ENV TARGETBUILD $TARGETBUILD
|
|
||||||
|
|
||||||
# install base dependencies
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server libaio1
|
|
||||||
|
|
||||||
# Install postgres client for pg_dump utils
|
|
||||||
RUN apt install -y software-properties-common apt-transport-https ca-certificates gnupg \
|
|
||||||
&& curl -fsSl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null \
|
|
||||||
&& echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main | tee /etc/apt/sources.list.d/postgresql.list \
|
|
||||||
&& apt update -y \
|
|
||||||
&& apt install postgresql-client-15 -y \
|
|
||||||
&& apt remove software-properties-common apt-transport-https gpg -y
|
|
||||||
|
|
||||||
# install other dependencies, nodejs, oracle requirements, jdk8, redis, nginx
|
|
||||||
WORKDIR /nodejs
|
|
||||||
COPY scripts/install-node.sh ./install.sh
|
|
||||||
RUN chmod +x install.sh && ./install.sh
|
|
||||||
|
|
||||||
# setup nginx
|
|
||||||
COPY hosting/single/nginx/nginx.conf /etc/nginx
|
|
||||||
COPY hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default
|
|
||||||
RUN mkdir -p /var/log/nginx && \
|
|
||||||
touch /var/log/nginx/error.log && \
|
|
||||||
touch /var/run/nginx.pid && \
|
|
||||||
usermod -a -G tty www-data
|
|
||||||
|
|
||||||
WORKDIR /
|
|
||||||
RUN mkdir -p scripts/integrations/oracle
|
|
||||||
COPY packages/server/scripts/integrations/oracle scripts/integrations/oracle
|
|
||||||
RUN /bin/bash -e ./scripts/integrations/oracle/instantclient/linux/install.sh
|
|
||||||
|
|
||||||
# setup minio
|
|
||||||
WORKDIR /minio
|
|
||||||
COPY scripts/install-minio.sh ./install.sh
|
|
||||||
RUN chmod +x install.sh && ./install.sh
|
|
||||||
|
|
||||||
# setup runner file
|
|
||||||
WORKDIR /
|
|
||||||
COPY hosting/single/runner.sh .
|
|
||||||
RUN chmod +x ./runner.sh
|
|
||||||
COPY hosting/single/healthcheck.sh .
|
|
||||||
RUN chmod +x ./healthcheck.sh
|
|
||||||
|
|
||||||
# Script below sets the path for storing data based on $DATA_DIR
|
|
||||||
# For Azure App Service install SSH & point data locations to /home
|
|
||||||
COPY hosting/single/ssh/sshd_config /etc/
|
|
||||||
COPY hosting/single/ssh/ssh_setup.sh /tmp
|
|
||||||
RUN /build-target-paths.sh
|
|
||||||
|
|
||||||
|
|
||||||
# setup letsencrypt certificate
|
|
||||||
RUN apt-get install -y certbot python3-certbot-nginx
|
|
||||||
COPY hosting/letsencrypt /app/letsencrypt
|
|
||||||
RUN chmod +x /app/letsencrypt/certificate-request.sh /app/letsencrypt/certificate-renew.sh
|
|
||||||
|
|
||||||
COPY --from=build /app/node_modules /node_modules
|
|
||||||
COPY --from=build /app/package.json /package.json
|
|
||||||
COPY --from=build /app/packages/server /app
|
|
||||||
COPY --from=build /app/packages/worker /worker
|
|
||||||
COPY --from=build /app/packages/string-templates /string-templates
|
|
||||||
|
|
||||||
RUN cd /string-templates && yarn link && cd ../app && yarn link @budibase/string-templates && cd ../worker && yarn link @budibase/string-templates
|
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
EXPOSE 443
|
|
||||||
# Expose port 2222 for SSH on Azure App Service build
|
|
||||||
EXPOSE 2222
|
|
||||||
VOLUME /data
|
|
||||||
|
|
||||||
ARG BUDIBASE_VERSION
|
|
||||||
# Ensuring the version argument is sent
|
|
||||||
RUN test -n "$BUDIBASE_VERSION"
|
|
||||||
ENV BUDIBASE_VERSION=$BUDIBASE_VERSION
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh"
|
|
||||||
|
|
||||||
# must set this just before running
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
WORKDIR /
|
|
||||||
|
|
||||||
CMD ["./runner.sh"]
|
|
|
@ -25,7 +25,7 @@ if [[ $(curl -s -w "%{http_code}\n" http://localhost:4002/health -o /dev/null) -
|
||||||
healthy=false
|
healthy=false
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/ -o /dev/null) -ne 200 ]]; then
|
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/_up -o /dev/null) -ne 200 ]]; then
|
||||||
echo 'ERROR: CouchDB is not running';
|
echo 'ERROR: CouchDB is not running';
|
||||||
healthy=false
|
healthy=false
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -22,11 +22,11 @@ declare -a DOCKER_VARS=("APP_PORT" "APPS_URL" "ARCHITECTURE" "BUDIBASE_ENVIRONME
|
||||||
|
|
||||||
# Azure App Service customisations
|
# Azure App Service customisations
|
||||||
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||||
DATA_DIR="${DATA_DIR:-/home}"
|
export DATA_DIR="${DATA_DIR:-/home}"
|
||||||
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
||||||
/etc/init.d/ssh start
|
/etc/init.d/ssh start
|
||||||
else
|
else
|
||||||
DATA_DIR=${DATA_DIR:-/data}
|
export DATA_DIR=${DATA_DIR:-/data}
|
||||||
fi
|
fi
|
||||||
mkdir -p ${DATA_DIR}
|
mkdir -p ${DATA_DIR}
|
||||||
# Mount NFS or GCP Filestore if env vars exist for it
|
# Mount NFS or GCP Filestore if env vars exist for it
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.13.5",
|
"version": "2.13.17",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.5",
|
"@babel/core": "^7.22.5",
|
||||||
"@babel/eslint-parser": "^7.22.15",
|
"@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.9.0",
|
"@typescript-eslint/parser": "6.9.0",
|
||||||
|
@ -11,6 +11,8 @@
|
||||||
"esbuild-node-externals": "^1.8.0",
|
"esbuild-node-externals": "^1.8.0",
|
||||||
"eslint": "^8.52.0",
|
"eslint": "^8.52.0",
|
||||||
"eslint-plugin-svelte": "^2.34.0",
|
"eslint-plugin-svelte": "^2.34.0",
|
||||||
|
"eslint-plugin-import": "^2.29.0",
|
||||||
|
"eslint-plugin-local-rules": "^2.0.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",
|
||||||
|
@ -21,6 +23,7 @@
|
||||||
"prettier": "2.8.8",
|
"prettier": "2.8.8",
|
||||||
"prettier-plugin-svelte": "^2.3.0",
|
"prettier-plugin-svelte": "^2.3.0",
|
||||||
"svelte-eslint-parser": "^0.33.1",
|
"svelte-eslint-parser": "^0.33.1",
|
||||||
|
"svelte": "3.49.0",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const _passport = require("koa-passport")
|
const _passport = require("koa-passport")
|
||||||
const LocalStrategy = require("passport-local").Strategy
|
const LocalStrategy = require("passport-local").Strategy
|
||||||
|
|
||||||
import { getGlobalDB } from "../context"
|
import { getGlobalDB } from "../context"
|
||||||
import { Cookie } from "../constants"
|
import { Cookie } from "../constants"
|
||||||
import { getSessionsForUser, invalidateSessions } from "../security/sessions"
|
import { getSessionsForUser, invalidateSessions } from "../security/sessions"
|
||||||
|
@ -26,6 +27,7 @@ import { clearCookie, getCookie } from "../utils"
|
||||||
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
|
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
|
||||||
|
|
||||||
const refresh = require("passport-oauth2-refresh")
|
const refresh = require("passport-oauth2-refresh")
|
||||||
|
|
||||||
export {
|
export {
|
||||||
auditLog,
|
auditLog,
|
||||||
authError,
|
authError,
|
||||||
|
|
|
@ -19,7 +19,7 @@ async function populateFromDB(appId: string) {
|
||||||
return doWithDB(
|
return doWithDB(
|
||||||
appId,
|
appId,
|
||||||
(db: Database) => {
|
(db: Database) => {
|
||||||
return db.get(DocumentType.APP_METADATA)
|
return db.get<App>(DocumentType.APP_METADATA)
|
||||||
},
|
},
|
||||||
{ skip_setup: true }
|
{ skip_setup: true }
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const BaseCache = require("./base")
|
import BaseCache from "./base"
|
||||||
|
|
||||||
const GENERIC = new BaseCache.default()
|
const GENERIC = new BaseCache()
|
||||||
|
|
||||||
export enum CacheKey {
|
export enum CacheKey {
|
||||||
CHECKLIST = "checklist",
|
CHECKLIST = "checklist",
|
||||||
|
@ -19,6 +19,7 @@ export enum TTL {
|
||||||
}
|
}
|
||||||
|
|
||||||
function performExport(funcName: string) {
|
function performExport(funcName: string) {
|
||||||
|
// @ts-ignore
|
||||||
return (...args: any) => GENERIC[funcName](...args)
|
return (...args: any) => GENERIC[funcName](...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,6 @@ export * as generic from "./generic"
|
||||||
export * as user from "./user"
|
export * as user from "./user"
|
||||||
export * as app from "./appMetadata"
|
export * as app from "./appMetadata"
|
||||||
export * as writethrough from "./writethrough"
|
export * as writethrough from "./writethrough"
|
||||||
|
export * as invite from "./invite"
|
||||||
|
export * as passwordReset from "./passwordReset"
|
||||||
export * from "./generic"
|
export * from "./generic"
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
import * as utils from "../utils"
|
||||||
|
import { Duration, DurationType } from "../utils"
|
||||||
|
import env from "../environment"
|
||||||
|
import { getTenantId } from "../context"
|
||||||
|
import * as redis from "../redis/init"
|
||||||
|
|
||||||
|
const TTL_SECONDS = Duration.fromDays(7).toSeconds()
|
||||||
|
|
||||||
|
interface Invite {
|
||||||
|
email: string
|
||||||
|
info: any
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InviteWithCode extends Invite {
|
||||||
|
code: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an invite code and invite body, allow the update an existing/valid invite in redis
|
||||||
|
* @param code The invite code for an invite in redis
|
||||||
|
* @param value The body of the updated user invitation
|
||||||
|
*/
|
||||||
|
export async function updateCode(code: string, value: Invite) {
|
||||||
|
const client = await redis.getInviteClient()
|
||||||
|
await client.store(code, value, TTL_SECONDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an invitation code and writes it to redis - which can later be checked for user creation.
|
||||||
|
* @param email the email address which the code is being sent to (for use later).
|
||||||
|
* @param info Information to be carried along with the invitation.
|
||||||
|
* @return returns the code that was stored to redis.
|
||||||
|
*/
|
||||||
|
export async function createCode(email: string, info: any): Promise<string> {
|
||||||
|
const code = utils.newid()
|
||||||
|
const client = await redis.getInviteClient()
|
||||||
|
await client.store(code, { email, info }, TTL_SECONDS)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the provided invite code is valid - will return the email address of user that was invited.
|
||||||
|
* @param code the invite code that was provided as part of the link.
|
||||||
|
* @return If the code is valid then an email address will be returned.
|
||||||
|
*/
|
||||||
|
export async function getCode(code: string): Promise<Invite> {
|
||||||
|
const client = await redis.getInviteClient()
|
||||||
|
const value = (await client.get(code)) as Invite | undefined
|
||||||
|
if (!value) {
|
||||||
|
throw "Invitation is not valid or has expired, please request a new one."
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteCode(code: string) {
|
||||||
|
const client = await redis.getInviteClient()
|
||||||
|
await client.delete(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get all currently available user invitations for the current tenant.
|
||||||
|
**/
|
||||||
|
export async function getInviteCodes(): Promise<InviteWithCode[]> {
|
||||||
|
const client = await redis.getInviteClient()
|
||||||
|
const invites: { key: string; value: Invite }[] = await client.scan()
|
||||||
|
|
||||||
|
const results: InviteWithCode[] = invites.map(invite => {
|
||||||
|
return {
|
||||||
|
...invite.value,
|
||||||
|
code: invite.key,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!env.MULTI_TENANCY) {
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
const tenantId = getTenantId()
|
||||||
|
return results.filter(invite => tenantId === invite.info.tenantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getExistingInvites(
|
||||||
|
emails: string[]
|
||||||
|
): Promise<InviteWithCode[]> {
|
||||||
|
return (await getInviteCodes()).filter(invite =>
|
||||||
|
emails.includes(invite.email)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import * as redis from "../redis/init"
|
||||||
|
import * as utils from "../utils"
|
||||||
|
import { Duration, DurationType } from "../utils"
|
||||||
|
|
||||||
|
const TTL_SECONDS = Duration.fromHours(1).toSeconds()
|
||||||
|
|
||||||
|
interface PasswordReset {
|
||||||
|
userId: string
|
||||||
|
info: any
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a user ID this will store a code (that is returned) for an hour in redis.
|
||||||
|
* The user can then return this code for resetting their password (through their reset link).
|
||||||
|
* @param userId the ID of the user which is to be reset.
|
||||||
|
* @param info Info about the user/the reset process.
|
||||||
|
* @return returns the code that was stored to redis.
|
||||||
|
*/
|
||||||
|
export async function createCode(userId: string, info: any): Promise<string> {
|
||||||
|
const code = utils.newid()
|
||||||
|
const client = await redis.getPasswordResetClient()
|
||||||
|
await client.store(code, { userId, info }, TTL_SECONDS)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a reset code this will lookup to redis, check if the code is valid.
|
||||||
|
* @param code The code provided via the email link.
|
||||||
|
* @return returns the user ID if it is found
|
||||||
|
*/
|
||||||
|
export async function getCode(code: string): Promise<PasswordReset> {
|
||||||
|
const client = await redis.getPasswordResetClient()
|
||||||
|
const value = (await client.get(code)) as PasswordReset | undefined
|
||||||
|
if (!value) {
|
||||||
|
throw "Provided information is not valid, cannot reset password - please try again."
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ import { DocumentType, SEPARATOR } from "../constants"
|
||||||
import { CacheKey, TTL, withCache } from "../cache"
|
import { CacheKey, TTL, withCache } from "../cache"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import environment from "../environment"
|
|
||||||
|
|
||||||
// UTILS
|
// UTILS
|
||||||
|
|
||||||
|
@ -181,10 +180,10 @@ export async function getGoogleDatasourceConfig(): Promise<
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined {
|
export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined {
|
||||||
if (environment.GOOGLE_CLIENT_ID && environment.GOOGLE_CLIENT_SECRET) {
|
if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||||
return {
|
return {
|
||||||
clientID: environment.GOOGLE_CLIENT_ID!,
|
clientID: env.GOOGLE_CLIENT_ID!,
|
||||||
clientSecret: environment.GOOGLE_CLIENT_SECRET!,
|
clientSecret: env.GOOGLE_CLIENT_SECRET!,
|
||||||
activated: true,
|
activated: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { prefixed, DocumentType } from "@budibase/types"
|
import { prefixed, DocumentType } from "@budibase/types"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
UNICODE_MAX,
|
UNICODE_MAX,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ContextMap } from "./types"
|
||||||
export default class Context {
|
export default class Context {
|
||||||
static storage = new AsyncLocalStorage<ContextMap>()
|
static storage = new AsyncLocalStorage<ContextMap>()
|
||||||
|
|
||||||
static run(context: ContextMap, func: any) {
|
static run<T>(context: ContextMap, func: () => T) {
|
||||||
return Context.storage.run(context, () => func())
|
return Context.storage.run(context, () => func())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,17 +98,17 @@ function updateContext(updates: ContextMap): ContextMap {
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
async function newContext(updates: ContextMap, task: any) {
|
async function newContext<T>(updates: ContextMap, task: () => T) {
|
||||||
// see if there already is a context setup
|
// see if there already is a context setup
|
||||||
let context: ContextMap = updateContext(updates)
|
let context: ContextMap = updateContext(updates)
|
||||||
return Context.run(context, task)
|
return Context.run(context, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doInAutomationContext(params: {
|
export async function doInAutomationContext<T>(params: {
|
||||||
appId: string
|
appId: string
|
||||||
automationId: string
|
automationId: string
|
||||||
task: any
|
task: () => T
|
||||||
}): Promise<any> {
|
}): Promise<T> {
|
||||||
const tenantId = getTenantIDFromAppID(params.appId)
|
const tenantId = getTenantIDFromAppID(params.appId)
|
||||||
return newContext(
|
return newContext(
|
||||||
{
|
{
|
||||||
|
@ -144,10 +144,10 @@ export async function doInTenant<T>(
|
||||||
return newContext(updates, task)
|
return newContext(updates, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doInAppContext(
|
export async function doInAppContext<T>(
|
||||||
appId: string | null,
|
appId: string | null,
|
||||||
task: any
|
task: () => T
|
||||||
): Promise<any> {
|
): Promise<T> {
|
||||||
if (!appId && !env.isTest()) {
|
if (!appId && !env.isTest()) {
|
||||||
throw new Error("appId is required")
|
throw new Error("appId is required")
|
||||||
}
|
}
|
||||||
|
@ -165,10 +165,10 @@ export async function doInAppContext(
|
||||||
return newContext(updates, task)
|
return newContext(updates, task)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doInIdentityContext(
|
export async function doInIdentityContext<T>(
|
||||||
identity: IdentityContext,
|
identity: IdentityContext,
|
||||||
task: any
|
task: () => T
|
||||||
): Promise<any> {
|
): Promise<T> {
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
throw new Error("identity is required")
|
throw new Error("identity is required")
|
||||||
}
|
}
|
||||||
|
@ -276,6 +276,9 @@ export function getAuditLogsDB(): Database {
|
||||||
*/
|
*/
|
||||||
export function getAppDB(opts?: any): Database {
|
export function getAppDB(opts?: any): Database {
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
|
if (!appId) {
|
||||||
|
throw new Error("Unable to retrieve app DB - no app ID.")
|
||||||
|
}
|
||||||
return getDB(appId, opts)
|
return getDB(appId, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
DatabaseDeleteIndexOpts,
|
DatabaseDeleteIndexOpts,
|
||||||
Document,
|
Document,
|
||||||
isDocument,
|
isDocument,
|
||||||
|
RowResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { getCouchInfo } from "./connections"
|
import { getCouchInfo } from "./connections"
|
||||||
import { directCouchUrlCall } from "./utils"
|
import { directCouchUrlCall } from "./utils"
|
||||||
|
@ -48,10 +49,7 @@ export class DatabaseImpl implements Database {
|
||||||
|
|
||||||
private readonly couchInfo = getCouchInfo()
|
private readonly couchInfo = getCouchInfo()
|
||||||
|
|
||||||
constructor(dbName?: string, opts?: DatabaseOpts, connection?: string) {
|
constructor(dbName: string, opts?: DatabaseOpts, connection?: string) {
|
||||||
if (dbName == null) {
|
|
||||||
throw new Error("Database name cannot be undefined.")
|
|
||||||
}
|
|
||||||
this.name = dbName
|
this.name = dbName
|
||||||
this.pouchOpts = opts || {}
|
this.pouchOpts = opts || {}
|
||||||
if (connection) {
|
if (connection) {
|
||||||
|
@ -112,7 +110,7 @@ export class DatabaseImpl implements Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async get<T>(id?: string): Promise<T | any> {
|
async get<T extends Document>(id?: string): Promise<T> {
|
||||||
const db = await this.checkSetup()
|
const db = await this.checkSetup()
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error("Unable to get doc without a valid _id.")
|
throw new Error("Unable to get doc without a valid _id.")
|
||||||
|
@ -120,6 +118,35 @@ export class DatabaseImpl implements Database {
|
||||||
return this.updateOutput(() => db.get(id))
|
return this.updateOutput(() => db.get(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMultiple<T extends Document>(
|
||||||
|
ids: string[],
|
||||||
|
opts?: { allowMissing?: boolean }
|
||||||
|
): Promise<T[]> {
|
||||||
|
// get unique
|
||||||
|
ids = [...new Set(ids)]
|
||||||
|
const response = await this.allDocs<T>({
|
||||||
|
keys: ids,
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
const rowUnavailable = (row: RowResponse<T>) => {
|
||||||
|
// row is deleted - key lookup can return this
|
||||||
|
if (row.doc == null || ("deleted" in row.value && row.value.deleted)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return row.error === "not_found"
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = response.rows.filter(row => !rowUnavailable(row))
|
||||||
|
const someMissing = rows.length !== response.rows.length
|
||||||
|
// some were filtered out - means some missing
|
||||||
|
if (!opts?.allowMissing && someMissing) {
|
||||||
|
const missing = response.rows.filter(row => rowUnavailable(row))
|
||||||
|
const missingIds = missing.map(row => row.key).join(", ")
|
||||||
|
throw new Error(`Unable to get documents: ${missingIds}`)
|
||||||
|
}
|
||||||
|
return rows.map(row => row.doc!)
|
||||||
|
}
|
||||||
|
|
||||||
async remove(idOrDoc: string | Document, rev?: string) {
|
async remove(idOrDoc: string | Document, rev?: string) {
|
||||||
const db = await this.checkSetup()
|
const db = await this.checkSetup()
|
||||||
let _id: string
|
let _id: string
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import env from "../environment"
|
|
||||||
import { directCouchQuery, DatabaseImpl } from "./couch"
|
import { directCouchQuery, DatabaseImpl } from "./couch"
|
||||||
import { CouchFindOptions, Database } from "@budibase/types"
|
import { CouchFindOptions, Database, DatabaseOpts } from "@budibase/types"
|
||||||
|
|
||||||
const dbList = new Set()
|
export function getDB(dbName: string, opts?: DatabaseOpts): Database {
|
||||||
|
|
||||||
export function getDB(dbName?: string, opts?: any): Database {
|
|
||||||
return new DatabaseImpl(dbName, opts)
|
return new DatabaseImpl(dbName, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +11,7 @@ export function getDB(dbName?: string, opts?: any): Database {
|
||||||
export async function doWithDB<T>(
|
export async function doWithDB<T>(
|
||||||
dbName: string,
|
dbName: string,
|
||||||
cb: (db: Database) => Promise<T>,
|
cb: (db: Database) => Promise<T>,
|
||||||
opts = {}
|
opts?: DatabaseOpts
|
||||||
) {
|
) {
|
||||||
const db = getDB(dbName, opts)
|
const db = getDB(dbName, opts)
|
||||||
// need this to be async so that we can correctly close DB after all
|
// need this to be async so that we can correctly close DB after all
|
||||||
|
@ -22,13 +19,6 @@ export async function doWithDB<T>(
|
||||||
return await cb(db)
|
return await cb(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function allDbs() {
|
|
||||||
if (!env.isTest()) {
|
|
||||||
throw new Error("Cannot be used outside test environment.")
|
|
||||||
}
|
|
||||||
return [...dbList]
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function directCouchAllDbs(queryString?: string) {
|
export async function directCouchAllDbs(queryString?: string) {
|
||||||
let couchPath = "/_all_dbs"
|
let couchPath = "/_all_dbs"
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
|
|
|
@ -5,7 +5,6 @@ const { getDB } = require("../db")
|
||||||
describe("db", () => {
|
describe("db", () => {
|
||||||
describe("getDB", () => {
|
describe("getDB", () => {
|
||||||
it("returns a db", async () => {
|
it("returns a db", async () => {
|
||||||
|
|
||||||
const dbName = structures.db.id()
|
const dbName = structures.db.id()
|
||||||
const db = getDB(dbName)
|
const db = getDB(dbName)
|
||||||
expect(db).toBeDefined()
|
expect(db).toBeDefined()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { AppState, DeletedApp, getAppMetadata } from "../cache/appMetadata"
|
||||||
import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
|
import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
|
||||||
import { App, Database } from "@budibase/types"
|
import { App, Database } from "@budibase/types"
|
||||||
import { getStartEndKeyURL } from "../docIds"
|
import { getStartEndKeyURL } from "../docIds"
|
||||||
|
|
||||||
export * from "../docIds"
|
export * from "../docIds"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { APP_DEV_PREFIX, APP_PREFIX } from "../constants"
|
import { APP_DEV_PREFIX, APP_PREFIX } from "../constants"
|
||||||
import { App } from "@budibase/types"
|
import { App } from "@budibase/types"
|
||||||
|
|
||||||
const NO_APP_ERROR = "No app provided"
|
const NO_APP_ERROR = "No app provided"
|
||||||
|
|
||||||
export function isDevAppID(appId?: string) {
|
export function isDevAppID(appId?: string) {
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
import PosthogProcessor from "./PosthogProcessor"
|
import PosthogProcessor from "./PosthogProcessor"
|
||||||
|
|
||||||
export default PosthogProcessor
|
export default PosthogProcessor
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { testEnv } from "../../../../../tests/extra"
|
import { testEnv } from "../../../../../tests/extra"
|
||||||
import PosthogProcessor from "../PosthogProcessor"
|
import PosthogProcessor from "../PosthogProcessor"
|
||||||
import { Event, IdentityType, Hosting } from "@budibase/types"
|
import { Event, IdentityType, Hosting } from "@budibase/types"
|
||||||
|
|
||||||
const tk = require("timekeeper")
|
const tk = require("timekeeper")
|
||||||
|
|
||||||
import * as cache from "../../../../cache/generic"
|
import * as cache from "../../../../cache/generic"
|
||||||
import { CacheKey } from "../../../../cache/generic"
|
import { CacheKey } from "../../../../cache/generic"
|
||||||
import * as context from "../../../../context"
|
import * as context from "../../../../context"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
|
|
||||||
export * from "./installation"
|
export * from "./installation"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,11 +32,13 @@ export * as blacklist from "./blacklist"
|
||||||
export * as docUpdates from "./docUpdates"
|
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"
|
||||||
// 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
|
||||||
import * as context from "./context"
|
import * as context from "./context"
|
||||||
import * as _tenancy from "./tenancy"
|
import * as _tenancy from "./tenancy"
|
||||||
|
|
||||||
export const tenancy = {
|
export const tenancy = {
|
||||||
..._tenancy,
|
..._tenancy,
|
||||||
...context,
|
...context,
|
||||||
|
@ -50,6 +52,7 @@ export * from "./constants"
|
||||||
|
|
||||||
// expose package init function
|
// expose package init function
|
||||||
import * as db from "./db"
|
import * as db from "./db"
|
||||||
|
|
||||||
export const init = (opts: any = {}) => {
|
export const init = (opts: any = {}) => {
|
||||||
db.init(opts.db)
|
db.init(opts.db)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { newid } from "./utils"
|
import { newid } from "./utils"
|
||||||
import * as events from "./events"
|
import * as events from "./events"
|
||||||
import { StaticDatabases } from "./db"
|
import { StaticDatabases, doWithDB } from "./db"
|
||||||
import { doWithDB } from "./db"
|
|
||||||
import { Installation, IdentityType, Database } from "@budibase/types"
|
import { Installation, IdentityType, Database } from "@budibase/types"
|
||||||
import * as context from "./context"
|
import * as context from "./context"
|
||||||
import semver from "semver"
|
import semver from "semver"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Header } from "../../constants"
|
import { Header } from "../../constants"
|
||||||
|
|
||||||
const correlator = require("correlation-id")
|
const correlator = require("correlation-id")
|
||||||
|
|
||||||
export const setHeader = (headers: any) => {
|
export const setHeader = (headers: any) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Header } from "../../constants"
|
import { Header } from "../../constants"
|
||||||
import { v4 as uuid } from "uuid"
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
const correlator = require("correlation-id")
|
const correlator = require("correlation-id")
|
||||||
|
|
||||||
const correlation = (ctx: any, next: any) => {
|
const correlation = (ctx: any, next: any) => {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
import { logger } from "./logger"
|
import { logger } from "./logger"
|
||||||
import { IncomingMessage } from "http"
|
import { IncomingMessage } from "http"
|
||||||
|
|
||||||
const pino = require("koa-pino-logger")
|
const pino = require("koa-pino-logger")
|
||||||
|
|
||||||
import { Options } from "pino-http"
|
import { Options } from "pino-http"
|
||||||
import { Ctx } from "@budibase/types"
|
import { Ctx } from "@budibase/types"
|
||||||
|
|
||||||
const correlator = require("correlation-id")
|
const correlator = require("correlation-id")
|
||||||
|
|
||||||
export function pinoSettings(): Options {
|
export function pinoSettings(): Options {
|
||||||
|
|
|
@ -2,6 +2,7 @@ export * as local from "./passport/local"
|
||||||
export * as google from "./passport/sso/google"
|
export * as google from "./passport/sso/google"
|
||||||
export * as oidc from "./passport/sso/oidc"
|
export * as oidc from "./passport/sso/oidc"
|
||||||
import * as datasourceGoogle from "./passport/datasource/google"
|
import * as datasourceGoogle from "./passport/datasource/google"
|
||||||
|
|
||||||
export const datasource = {
|
export const datasource = {
|
||||||
google: datasourceGoogle,
|
google: datasourceGoogle,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
SaveSSOUserFunction,
|
SaveSSOUserFunction,
|
||||||
GoogleInnerConfig,
|
GoogleInnerConfig,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
|
|
||||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
|
|
||||||
export function buildVerifyFn(saveUserFn: SaveSSOUserFunction) {
|
export function buildVerifyFn(saveUserFn: SaveSSOUserFunction) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ const mockStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
|
|
||||||
jest.mock("../sso")
|
jest.mock("../sso")
|
||||||
import * as _sso from "../sso"
|
import * as _sso from "../sso"
|
||||||
|
|
||||||
const sso = jest.mocked(_sso)
|
const sso = jest.mocked(_sso)
|
||||||
|
|
||||||
const mockSaveUserFn = jest.fn()
|
const mockSaveUserFn = jest.fn()
|
||||||
|
|
|
@ -11,6 +11,7 @@ const mockSaveUser = jest.fn()
|
||||||
|
|
||||||
jest.mock("../../../../users")
|
jest.mock("../../../../users")
|
||||||
import * as _users from "../../../../users"
|
import * as _users from "../../../../users"
|
||||||
|
|
||||||
const users = jest.mocked(_users)
|
const users = jest.mocked(_users)
|
||||||
|
|
||||||
const getErrorMessage = () => {
|
const getErrorMessage = () => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { structures } from "../../../tests"
|
||||||
import { ContextUser, ServiceType } from "@budibase/types"
|
import { ContextUser, ServiceType } from "@budibase/types"
|
||||||
import { doInAppContext } from "../../context"
|
import { doInAppContext } from "../../context"
|
||||||
import env from "../../environment"
|
import env from "../../environment"
|
||||||
|
|
||||||
env._set("SERVICE_TYPE", ServiceType.APPS)
|
env._set("SERVICE_TYPE", ServiceType.APPS)
|
||||||
|
|
||||||
const appId = "app_aaa"
|
const appId = "app_aaa"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const sanitize = require("sanitize-s3-objectkey")
|
const sanitize = require("sanitize-s3-objectkey")
|
||||||
|
|
||||||
import AWS from "aws-sdk"
|
import AWS from "aws-sdk"
|
||||||
import stream, { Readable } from "stream"
|
import stream, { Readable } from "stream"
|
||||||
import fetch from "node-fetch"
|
import fetch from "node-fetch"
|
||||||
|
|
|
@ -7,15 +7,19 @@ let userClient: Client,
|
||||||
cacheClient: Client,
|
cacheClient: Client,
|
||||||
writethroughClient: Client,
|
writethroughClient: Client,
|
||||||
lockClient: Client,
|
lockClient: Client,
|
||||||
socketClient: Client
|
socketClient: Client,
|
||||||
|
inviteClient: Client,
|
||||||
|
passwordResetClient: Client
|
||||||
|
|
||||||
async function init() {
|
export async function init() {
|
||||||
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
userClient = await new Client(utils.Databases.USER_CACHE).init()
|
||||||
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
sessionClient = await new Client(utils.Databases.SESSIONS).init()
|
||||||
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
appClient = await new Client(utils.Databases.APP_METADATA).init()
|
||||||
cacheClient = await new Client(utils.Databases.GENERIC_CACHE).init()
|
cacheClient = await new Client(utils.Databases.GENERIC_CACHE).init()
|
||||||
lockClient = await new Client(utils.Databases.LOCKS).init()
|
lockClient = await new Client(utils.Databases.LOCKS).init()
|
||||||
writethroughClient = await new Client(utils.Databases.WRITE_THROUGH).init()
|
writethroughClient = await new Client(utils.Databases.WRITE_THROUGH).init()
|
||||||
|
inviteClient = await new Client(utils.Databases.INVITATIONS).init()
|
||||||
|
passwordResetClient = await new Client(utils.Databases.PW_RESETS).init()
|
||||||
socketClient = await new Client(
|
socketClient = await new Client(
|
||||||
utils.Databases.SOCKET_IO,
|
utils.Databases.SOCKET_IO,
|
||||||
utils.SelectableDatabase.SOCKET_IO
|
utils.SelectableDatabase.SOCKET_IO
|
||||||
|
@ -29,6 +33,8 @@ export async function shutdown() {
|
||||||
if (cacheClient) await cacheClient.finish()
|
if (cacheClient) await cacheClient.finish()
|
||||||
if (writethroughClient) await writethroughClient.finish()
|
if (writethroughClient) await writethroughClient.finish()
|
||||||
if (lockClient) await lockClient.finish()
|
if (lockClient) await lockClient.finish()
|
||||||
|
if (inviteClient) await inviteClient.finish()
|
||||||
|
if (passwordResetClient) await passwordResetClient.finish()
|
||||||
if (socketClient) await socketClient.finish()
|
if (socketClient) await socketClient.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,3 +90,17 @@ export async function getSocketClient() {
|
||||||
}
|
}
|
||||||
return socketClient
|
return socketClient
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getInviteClient() {
|
||||||
|
if (!inviteClient) {
|
||||||
|
await init()
|
||||||
|
}
|
||||||
|
return inviteClient
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPasswordResetClient() {
|
||||||
|
if (!passwordResetClient) {
|
||||||
|
await init()
|
||||||
|
}
|
||||||
|
return passwordResetClient
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ const DEFAULT_SELECT_DB = SelectableDatabase.DEFAULT
|
||||||
// for testing just generate the client once
|
// for testing just generate the client once
|
||||||
let CLOSED = false
|
let CLOSED = false
|
||||||
let CLIENTS: { [key: number]: any } = {}
|
let CLIENTS: { [key: number]: any } = {}
|
||||||
0
|
|
||||||
let CONNECTED = false
|
let CONNECTED = false
|
||||||
|
|
||||||
// mock redis always connected
|
// mock redis always connected
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { getLockClient } from "./init"
|
||||||
import { LockOptions, LockType } from "@budibase/types"
|
import { LockOptions, LockType } from "@budibase/types"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
import { logWarn } from "../logging"
|
||||||
|
|
||||||
async function getClient(
|
async function getClient(
|
||||||
type: LockType,
|
type: LockType,
|
||||||
|
@ -116,7 +117,7 @@ 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) {
|
||||||
console.warn("lock error")
|
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) {
|
||||||
|
@ -124,11 +125,9 @@ export async function doWithLock<T>(
|
||||||
// due to retry count (0) exceeded
|
// due to retry count (0) exceeded
|
||||||
return { executed: false }
|
return { executed: false }
|
||||||
} else {
|
} else {
|
||||||
console.error(e)
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(e)
|
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -75,10 +75,12 @@ export function getRedisConnectionDetails() {
|
||||||
}
|
}
|
||||||
const [host, port] = url.split(":")
|
const [host, port] = url.split(":")
|
||||||
|
|
||||||
|
const portNumber = parseInt(port)
|
||||||
return {
|
return {
|
||||||
host,
|
host,
|
||||||
password,
|
password,
|
||||||
port: parseInt(port),
|
// assume default port for redis if invalid found
|
||||||
|
port: isNaN(portNumber) ? 6379 : portNumber,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,4 +160,5 @@ export function isPermissionLevelHigherThanRead(level: PermissionLevel) {
|
||||||
|
|
||||||
// utility as a lot of things need simply the builder permission
|
// utility as a lot of things need simply the builder permission
|
||||||
export const BUILDER = PermissionType.BUILDER
|
export const BUILDER = PermissionType.BUILDER
|
||||||
|
export const CREATOR = PermissionType.CREATOR
|
||||||
export const GLOBAL_BUILDER = PermissionType.GLOBAL_BUILDER
|
export const GLOBAL_BUILDER = PermissionType.GLOBAL_BUILDER
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
||||||
import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
|
import {
|
||||||
|
prefixRoleID,
|
||||||
|
getRoleParams,
|
||||||
|
DocumentType,
|
||||||
|
SEPARATOR,
|
||||||
|
doWithDB,
|
||||||
|
} from "../db"
|
||||||
import { getAppDB } from "../context"
|
import { getAppDB } from "../context"
|
||||||
import { doWithDB } from "../db"
|
|
||||||
import { Screen, Role as RoleDoc } from "@budibase/types"
|
import { Screen, Role as RoleDoc } from "@budibase/types"
|
||||||
import cloneDeep from "lodash/fp/cloneDeep"
|
import cloneDeep from "lodash/fp/cloneDeep"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const redis = require("../redis/init")
|
const redis = require("../redis/init")
|
||||||
const { v4: uuidv4 } = require("uuid")
|
const { v4: uuidv4 } = require("uuid")
|
||||||
const { logWarn } = require("../logging")
|
const { logWarn } = require("../logging")
|
||||||
|
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import {
|
import {
|
||||||
Session,
|
Session,
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import * as eventHelpers from "./events"
|
import * as eventHelpers from "./events"
|
||||||
import * as accounts from "../accounts"
|
|
||||||
import * as accountSdk from "../accounts"
|
import * as accountSdk from "../accounts"
|
||||||
import * as cache from "../cache"
|
import * as cache from "../cache"
|
||||||
import { getGlobalDB, getIdentity, getTenantId } from "../context"
|
import { doInTenant, 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"
|
||||||
|
@ -11,12 +10,10 @@ import * as sessions from "../security/sessions"
|
||||||
import * as usersCore from "./users"
|
import * as usersCore from "./users"
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
AllDocsResponse,
|
|
||||||
BulkUserCreated,
|
BulkUserCreated,
|
||||||
BulkUserDeleted,
|
BulkUserDeleted,
|
||||||
isSSOAccount,
|
isSSOAccount,
|
||||||
isSSOUser,
|
isSSOUser,
|
||||||
RowResponse,
|
|
||||||
SaveUserOpts,
|
SaveUserOpts,
|
||||||
User,
|
User,
|
||||||
UserStatus,
|
UserStatus,
|
||||||
|
@ -149,12 +146,12 @@ export class UserDB {
|
||||||
|
|
||||||
static async allUsers() {
|
static async allUsers() {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs<User>(
|
||||||
dbUtils.getGlobalUserParams(null, {
|
dbUtils.getGlobalUserParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return response.rows.map((row: any) => row.doc)
|
return response.rows.map(row => row.doc!)
|
||||||
}
|
}
|
||||||
|
|
||||||
static async countUsersByApp(appId: string) {
|
static async countUsersByApp(appId: string) {
|
||||||
|
@ -212,13 +209,6 @@ export class UserDB {
|
||||||
throw new Error("_id or email is required")
|
throw new Error("_id or email is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
user.builder?.apps?.length &&
|
|
||||||
!(await UserDB.features.isAppBuildersEnabled())
|
|
||||||
) {
|
|
||||||
throw new Error("Unable to update app builders, please check license")
|
|
||||||
}
|
|
||||||
|
|
||||||
let dbUser: User | undefined
|
let dbUser: User | undefined
|
||||||
if (_id) {
|
if (_id) {
|
||||||
// try to get existing user from db
|
// try to get existing user from db
|
||||||
|
@ -303,7 +293,7 @@ export class UserDB {
|
||||||
|
|
||||||
static async bulkCreate(
|
static async bulkCreate(
|
||||||
newUsersRequested: User[],
|
newUsersRequested: User[],
|
||||||
groups: string[]
|
groups?: string[]
|
||||||
): Promise<BulkUserCreated> {
|
): Promise<BulkUserCreated> {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
|
|
||||||
|
@ -328,7 +318,7 @@ export class UserDB {
|
||||||
})
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newUser.userGroups = groups
|
newUser.userGroups = groups || []
|
||||||
newUsers.push(newUser)
|
newUsers.push(newUser)
|
||||||
if (isCreator(newUser)) {
|
if (isCreator(newUser)) {
|
||||||
newCreators.push(newUser)
|
newCreators.push(newUser)
|
||||||
|
@ -467,7 +457,7 @@ export class UserDB {
|
||||||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||||
// root account holder can't be deleted from inside budibase
|
// root account holder can't be deleted from inside budibase
|
||||||
const email = dbUser.email
|
const email = dbUser.email
|
||||||
const account = await accounts.getAccount(email)
|
const account = await accountSdk.getAccount(email)
|
||||||
if (account) {
|
if (account) {
|
||||||
if (dbUser.userId === getIdentity()!._id) {
|
if (dbUser.userId === getIdentity()!._id) {
|
||||||
throw new HTTPError('Please visit "Account" to delete this user', 400)
|
throw new HTTPError('Please visit "Account" to delete this user', 400)
|
||||||
|
@ -488,6 +478,37 @@ export class UserDB {
|
||||||
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async createAdminUser(
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
tenantId: string,
|
||||||
|
opts?: { ssoId?: string; hashPassword?: boolean; requirePassword?: boolean }
|
||||||
|
) {
|
||||||
|
const user: User = {
|
||||||
|
email: email,
|
||||||
|
password: password,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
roles: {},
|
||||||
|
builder: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
global: true,
|
||||||
|
},
|
||||||
|
tenantId,
|
||||||
|
}
|
||||||
|
if (opts?.ssoId) {
|
||||||
|
user.ssoId = opts.ssoId
|
||||||
|
}
|
||||||
|
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
||||||
|
// stuck in a cycle
|
||||||
|
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
||||||
|
return await UserDB.save(user, {
|
||||||
|
hashPassword: opts?.hashPassword,
|
||||||
|
requirePassword: opts?.requirePassword,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static async getGroups(groupIds: string[]) {
|
static async getGroups(groupIds: string[]) {
|
||||||
return await this.groups.getBulk(groupIds)
|
return await this.groups.getBulk(groupIds)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import * as dbUtils from "../db"
|
import * as dbUtils from "../db"
|
||||||
import { ViewName } from "../constants"
|
import { ViewName } from "../constants"
|
||||||
|
import { getExistingInvites } from "../cache/invite"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply a system-wide search on emails:
|
* Apply a system-wide search on emails:
|
||||||
|
@ -26,6 +27,9 @@ export async function searchExistingEmails(emails: string[]) {
|
||||||
const existingAccounts = await getExistingAccounts(emails)
|
const existingAccounts = await getExistingAccounts(emails)
|
||||||
matchedEmails.push(...existingAccounts.map(account => account.email))
|
matchedEmails.push(...existingAccounts.map(account => account.email))
|
||||||
|
|
||||||
|
const invitedEmails = await getExistingInvites(emails)
|
||||||
|
matchedEmails.push(...invitedEmails.map(invite => invite.email))
|
||||||
|
|
||||||
return [...new Set(matchedEmails.map(email => email.toLowerCase()))]
|
return [...new Set(matchedEmails.map(email => email.toLowerCase()))]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import {
|
||||||
import { getGlobalDB } from "../context"
|
import { getGlobalDB } from "../context"
|
||||||
import * as context from "../context"
|
import * as context from "../context"
|
||||||
import { isCreator } from "./utils"
|
import { isCreator } from "./utils"
|
||||||
|
import { UserDB } from "./db"
|
||||||
|
|
||||||
type GetOpts = { cleanup?: boolean }
|
type GetOpts = { cleanup?: boolean }
|
||||||
|
|
||||||
|
@ -43,7 +44,7 @@ function removeUserPassword(users: User | User[]) {
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isSupportedUserSearch = (query: SearchQuery) => {
|
export function isSupportedUserSearch(query: SearchQuery) {
|
||||||
const allowed = [
|
const allowed = [
|
||||||
{ op: SearchQueryOperators.STRING, key: "email" },
|
{ op: SearchQueryOperators.STRING, key: "email" },
|
||||||
{ op: SearchQueryOperators.EQUAL, key: "_id" },
|
{ op: SearchQueryOperators.EQUAL, key: "_id" },
|
||||||
|
@ -68,10 +69,10 @@ export const isSupportedUserSearch = (query: SearchQuery) => {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkGetGlobalUsersById = async (
|
export async function bulkGetGlobalUsersById(
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
opts?: GetOpts
|
opts?: GetOpts
|
||||||
) => {
|
) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
let users = (
|
let users = (
|
||||||
await db.allDocs({
|
await db.allDocs({
|
||||||
|
@ -85,7 +86,7 @@ export const bulkGetGlobalUsersById = async (
|
||||||
return users
|
return users
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAllUserIds = async () => {
|
export async function getAllUserIds() {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const startKey = `${DocumentType.USER}${SEPARATOR}`
|
const startKey = `${DocumentType.USER}${SEPARATOR}`
|
||||||
const response = await db.allDocs({
|
const response = await db.allDocs({
|
||||||
|
@ -95,7 +96,7 @@ export const getAllUserIds = async () => {
|
||||||
return response.rows.map(row => row.id)
|
return response.rows.map(row => row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkUpdateGlobalUsers = async (users: User[]) => {
|
export async function bulkUpdateGlobalUsers(users: User[]) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
return (await db.bulkDocs(users)) as BulkDocsResponse
|
return (await db.bulkDocs(users)) as BulkDocsResponse
|
||||||
}
|
}
|
||||||
|
@ -113,10 +114,10 @@ export async function getById(id: string, opts?: GetOpts): Promise<User> {
|
||||||
* Given an email address this will use a view to search through
|
* Given an email address this will use a view to search through
|
||||||
* all the users to find one with this email address.
|
* all the users to find one with this email address.
|
||||||
*/
|
*/
|
||||||
export const getGlobalUserByEmail = async (
|
export async function getGlobalUserByEmail(
|
||||||
email: String,
|
email: String,
|
||||||
opts?: GetOpts
|
opts?: GetOpts
|
||||||
): Promise<User | undefined> => {
|
): Promise<User | undefined> {
|
||||||
if (email == null) {
|
if (email == null) {
|
||||||
throw "Must supply an email address to view"
|
throw "Must supply an email address to view"
|
||||||
}
|
}
|
||||||
|
@ -139,11 +140,23 @@ export const getGlobalUserByEmail = async (
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
export const searchGlobalUsersByApp = async (
|
export async function doesUserExist(email: string) {
|
||||||
|
try {
|
||||||
|
const user = await getGlobalUserByEmail(email)
|
||||||
|
if (Array.isArray(user) || user != null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchGlobalUsersByApp(
|
||||||
appId: any,
|
appId: any,
|
||||||
opts: DatabaseQueryOpts,
|
opts: DatabaseQueryOpts,
|
||||||
getOpts?: GetOpts
|
getOpts?: GetOpts
|
||||||
) => {
|
) {
|
||||||
if (typeof appId !== "string") {
|
if (typeof appId !== "string") {
|
||||||
throw new Error("Must provide a string based app ID")
|
throw new Error("Must provide a string based app ID")
|
||||||
}
|
}
|
||||||
|
@ -167,10 +180,10 @@ export const searchGlobalUsersByApp = async (
|
||||||
Return any user who potentially has access to the application
|
Return any user who potentially has access to the application
|
||||||
Admins, developers and app users with the explicitly role.
|
Admins, developers and app users with the explicitly role.
|
||||||
*/
|
*/
|
||||||
export const searchGlobalUsersByAppAccess = async (
|
export async function searchGlobalUsersByAppAccess(
|
||||||
appId: any,
|
appId: any,
|
||||||
opts?: { limit?: number }
|
opts?: { limit?: number }
|
||||||
) => {
|
) {
|
||||||
const roleSelector = `roles.${appId}`
|
const roleSelector = `roles.${appId}`
|
||||||
|
|
||||||
let orQuery: any[] = [
|
let orQuery: any[] = [
|
||||||
|
@ -205,7 +218,7 @@ export const searchGlobalUsersByAppAccess = async (
|
||||||
return resp.rows
|
return resp.rows
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
export function getGlobalUserByAppPage(appId: string, user: User) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -215,11 +228,11 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
||||||
/**
|
/**
|
||||||
* Performs a starts with search on the global email view.
|
* Performs a starts with search on the global email view.
|
||||||
*/
|
*/
|
||||||
export const searchGlobalUsersByEmail = async (
|
export async function searchGlobalUsersByEmail(
|
||||||
email: string | unknown,
|
email: string | unknown,
|
||||||
opts: any,
|
opts: any,
|
||||||
getOpts?: GetOpts
|
getOpts?: GetOpts
|
||||||
) => {
|
) {
|
||||||
if (typeof email !== "string") {
|
if (typeof email !== "string") {
|
||||||
throw new Error("Must provide a string to search by")
|
throw new Error("Must provide a string to search by")
|
||||||
}
|
}
|
||||||
|
@ -242,12 +255,12 @@ export const searchGlobalUsersByEmail = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE_LIMIT = 8
|
const PAGE_LIMIT = 8
|
||||||
export const paginatedUsers = async ({
|
export async function paginatedUsers({
|
||||||
bookmark,
|
bookmark,
|
||||||
query,
|
query,
|
||||||
appId,
|
appId,
|
||||||
limit,
|
limit,
|
||||||
}: SearchUsersRequest = {}) => {
|
}: SearchUsersRequest = {}) {
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const pageSize = limit ?? PAGE_LIMIT
|
const pageSize = limit ?? PAGE_LIMIT
|
||||||
const pageLimit = pageSize + 1
|
const pageLimit = pageSize + 1
|
||||||
|
@ -324,3 +337,20 @@ export function cleanseUserObject(user: User | ContextUser, base?: User) {
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addAppBuilder(user: User, appId: string) {
|
||||||
|
const prodAppId = getProdAppID(appId)
|
||||||
|
user.builder ??= {}
|
||||||
|
user.builder.creator = true
|
||||||
|
user.builder.apps ??= []
|
||||||
|
user.builder.apps.push(prodAppId)
|
||||||
|
await UserDB.save(user, { hashPassword: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeAppBuilder(user: User, appId: string) {
|
||||||
|
const prodAppId = getProdAppID(appId)
|
||||||
|
if (user.builder && user.builder.apps?.includes(prodAppId)) {
|
||||||
|
user.builder.apps = user.builder.apps.filter(id => id !== prodAppId)
|
||||||
|
}
|
||||||
|
await UserDB.save(user, { hashPassword: false })
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,9 @@ export class Duration {
|
||||||
toMs: () => {
|
toMs: () => {
|
||||||
return Duration.convert(from, DurationType.MILLISECONDS, duration)
|
return Duration.convert(from, DurationType.MILLISECONDS, duration)
|
||||||
},
|
},
|
||||||
|
toSeconds: () => {
|
||||||
|
return Duration.convert(from, DurationType.SECONDS, duration)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
|
|
||||||
export * from "../docIds/newid"
|
export * from "../docIds/newid"
|
||||||
const bcrypt = env.JS_BCRYPT ? require("bcryptjs") : require("bcrypt")
|
const bcrypt = env.JS_BCRYPT ? require("bcryptjs") : require("bcrypt")
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
TenantResolutionStrategy,
|
TenantResolutionStrategy,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import type { SetOption } from "cookies"
|
import type { SetOption } from "cookies"
|
||||||
|
|
||||||
const jwt = require("jsonwebtoken")
|
const jwt = require("jsonwebtoken")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require("lodash/fp")
|
||||||
const {structures} = require("../../../tests")
|
const { structures } = require("../../../tests")
|
||||||
|
|
||||||
jest.mock("../../../src/context")
|
jest.mock("../../../src/context")
|
||||||
jest.mock("../../../src/db")
|
jest.mock("../../../src/db")
|
||||||
|
@ -7,10 +7,9 @@ jest.mock("../../../src/db")
|
||||||
const context = require("../../../src/context")
|
const context = require("../../../src/context")
|
||||||
const db = require("../../../src/db")
|
const db = require("../../../src/db")
|
||||||
|
|
||||||
const {getCreatorCount} = require('../../../src/users/users')
|
const { getCreatorCount } = require("../../../src/users/users")
|
||||||
|
|
||||||
describe("Users", () => {
|
describe("Users", () => {
|
||||||
|
|
||||||
let getGlobalDBMock
|
let getGlobalDBMock
|
||||||
let getGlobalUserParamsMock
|
let getGlobalUserParamsMock
|
||||||
let paginationMock
|
let paginationMock
|
||||||
|
@ -26,26 +25,26 @@ describe("Users", () => {
|
||||||
it("Retrieves the number of creators", async () => {
|
it("Retrieves the number of creators", async () => {
|
||||||
const getUsers = (offset, limit, creators = false) => {
|
const getUsers = (offset, limit, creators = false) => {
|
||||||
const range = _.range(offset, limit)
|
const range = _.range(offset, limit)
|
||||||
const opts = creators ? {builder: {global: true}} : undefined
|
const opts = creators ? { builder: { global: true } } : undefined
|
||||||
return range.map(() => structures.users.user(opts))
|
return range.map(() => structures.users.user(opts))
|
||||||
}
|
}
|
||||||
const page1Data = getUsers(0, 8)
|
const page1Data = getUsers(0, 8)
|
||||||
const page2Data = getUsers(8, 12, true)
|
const page2Data = getUsers(8, 12, true)
|
||||||
getGlobalDBMock.mockImplementation(() => ({
|
getGlobalDBMock.mockImplementation(() => ({
|
||||||
name : "fake-db",
|
name: "fake-db",
|
||||||
allDocs: () => ({
|
allDocs: () => ({
|
||||||
rows: [...page1Data, ...page2Data]
|
rows: [...page1Data, ...page2Data],
|
||||||
})
|
}),
|
||||||
}))
|
}))
|
||||||
paginationMock.mockImplementationOnce(() => ({
|
paginationMock.mockImplementationOnce(() => ({
|
||||||
data: page1Data,
|
data: page1Data,
|
||||||
hasNextPage: true,
|
hasNextPage: true,
|
||||||
nextPage: "1"
|
nextPage: "1",
|
||||||
}))
|
}))
|
||||||
paginationMock.mockImplementation(() => ({
|
paginationMock.mockImplementation(() => ({
|
||||||
data: page2Data,
|
data: page2Data,
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
nextPage: undefined
|
nextPage: undefined,
|
||||||
}))
|
}))
|
||||||
const creatorsCount = await getCreatorCount()
|
const creatorsCount = await getCreatorCount()
|
||||||
expect(creatorsCount).toBe(4)
|
expect(creatorsCount).toBe(4)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
jest.mock("../../../../src/logging/alerts")
|
jest.mock("../../../../src/logging/alerts")
|
||||||
import * as _alerts from "../../../../src/logging/alerts"
|
import * as _alerts from "../../../../src/logging/alerts"
|
||||||
|
|
||||||
export const alerts = jest.mocked(_alerts)
|
export const alerts = jest.mocked(_alerts)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
jest.mock("../../../../src/accounts")
|
jest.mock("../../../../src/accounts")
|
||||||
import * as _accounts from "../../../../src/accounts"
|
import * as _accounts from "../../../../src/accounts"
|
||||||
|
|
||||||
export const accounts = jest.mocked(_accounts)
|
export const accounts = jest.mocked(_accounts)
|
||||||
|
|
||||||
export * as date from "./date"
|
export * as date from "./date"
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
import Chance from "./Chance"
|
import Chance from "./Chance"
|
||||||
|
|
||||||
export const generator = new Chance()
|
export const generator = new Chance()
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { generator } from "./generator"
|
||||||
import { tenant } from "."
|
import { tenant } from "."
|
||||||
|
|
||||||
export const newEmail = () => {
|
export const newEmail = () => {
|
||||||
return `${uuid()}@test.com`
|
return `${uuid()}@example.com`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
|
export const user = (userProps?: Partial<Omit<User, "userId">>): User => {
|
||||||
|
|
|
@ -9,6 +9,7 @@ mocks.fetch.enable()
|
||||||
// mock all dates to 2020-01-01T00:00:00.000Z
|
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||||
// use tk.reset() to use real dates in individual tests
|
// use tk.reset() to use real dates in individual tests
|
||||||
import tk from "timekeeper"
|
import tk from "timekeeper"
|
||||||
|
|
||||||
tk.freeze(mocks.date.MOCK_DATE)
|
tk.freeze(mocks.date.MOCK_DATE)
|
||||||
|
|
||||||
if (!process.env.DEBUG) {
|
if (!process.env.DEBUG) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/actiongroup/dist/index-vars.css"
|
import "@spectrum-css/actiongroup/dist/index-vars.css"
|
||||||
|
|
||||||
export let vertical = false
|
export let vertical = false
|
||||||
export let justified = false
|
export let justified = false
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/avatar/dist/index-vars.css"
|
import "@spectrum-css/avatar/dist/index-vars.css"
|
||||||
|
|
||||||
let sizes = new Map([
|
let sizes = new Map([
|
||||||
["XXS", "--spectrum-alias-avatar-size-50"],
|
["XXS", "--spectrum-alias-avatar-size-50"],
|
||||||
["XS", "--spectrum-alias-avatar-size-75"],
|
["XS", "--spectrum-alias-avatar-size-75"],
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/buttongroup/dist/index-vars.css"
|
import "@spectrum-css/buttongroup/dist/index-vars.css"
|
||||||
|
|
||||||
export let vertical = false
|
export let vertical = false
|
||||||
export let gap = ""
|
export let gap = "M"
|
||||||
|
|
||||||
$: gapStyle =
|
$: gapStyle =
|
||||||
gap === "L"
|
gap === "L"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/divider/dist/index-vars.css"
|
import "@spectrum-css/divider/dist/index-vars.css"
|
||||||
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
|
|
||||||
export let vertical = false
|
export let vertical = false
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
import Button from "../Button/Button.svelte"
|
import Button from "../Button/Button.svelte"
|
||||||
import Body from "../Typography/Body.svelte"
|
import Body from "../Typography/Body.svelte"
|
||||||
import Heading from "../Typography/Heading.svelte"
|
import Heading from "../Typography/Heading.svelte"
|
||||||
import { setContext } from "svelte"
|
import { setContext, createEventDispatcher } from "svelte"
|
||||||
import { createEventDispatcher } from "svelte"
|
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
|
|
||||||
export let title
|
export let title
|
||||||
|
|
|
@ -12,11 +12,13 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let validate = null
|
export let validate = null
|
||||||
export let options = []
|
export let options = []
|
||||||
|
export let footer = null
|
||||||
export let isOptionEnabled = () => true
|
export let isOptionEnabled = () => true
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
export let getOptionSubtitle = option => extractProperty(option, "subtitle")
|
export let getOptionSubtitle = option => extractProperty(option, "subtitle")
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = () => null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let open = false
|
let open = false
|
||||||
|
@ -100,6 +102,7 @@
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
{options}
|
{options}
|
||||||
|
{footer}
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
{getOptionSubtitle}
|
{getOptionSubtitle}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
export let error = null
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -18,6 +19,6 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Checkbox {error} {disabled} {text} {value} {size} on:change={onChange} />
|
<Checkbox {error} {disabled} {text} {value} {size} on:change={onChange} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
export let error = null
|
export let error = null
|
||||||
export let placeholder = "Choose an option or type"
|
export let placeholder = "Choose an option or type"
|
||||||
export let options = []
|
export let options = []
|
||||||
|
export let helpText = null
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<Combobox
|
<Combobox
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
export let value = false
|
export let value = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let text = null
|
export let text = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
@ -22,7 +21,6 @@
|
||||||
|
|
||||||
<label
|
<label
|
||||||
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:checked={value}
|
class:checked={value}
|
||||||
class:is-indeterminate={indeterminate}
|
class:is-indeterminate={indeterminate}
|
||||||
class:readonly
|
class:readonly
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
export let value = []
|
export let value = []
|
||||||
export let options = []
|
export let options = []
|
||||||
export let error = null
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
|
@ -34,7 +33,6 @@
|
||||||
<div
|
<div
|
||||||
title={getOptionLabel(option)}
|
title={getOptionLabel(option)}
|
||||||
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:readonly
|
class:readonly
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
export let placeholder = "Choose an option or type"
|
export let placeholder = "Choose an option or type"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
|
@ -39,12 +38,10 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-InputGroup"
|
class="spectrum-InputGroup"
|
||||||
class:is-focused={open || focus}
|
class:is-focused={open || focus}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={open || focus}
|
class:is-focused={open || focus}
|
||||||
>
|
>
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let enableTime = true
|
export let enableTime = true
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
|
@ -188,7 +187,6 @@
|
||||||
<div
|
<div
|
||||||
id={flatpickrId}
|
id={flatpickrId}
|
||||||
class:is-disabled={disabled || readonly}
|
class:is-disabled={disabled || readonly}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
||||||
class:is-focused={open}
|
class:is-focused={open}
|
||||||
aria-readonly="false"
|
aria-readonly="false"
|
||||||
|
@ -199,17 +197,7 @@
|
||||||
on:click={flatpickr?.open}
|
on:click={flatpickr?.open}
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-invalid={!!error}
|
|
||||||
>
|
>
|
||||||
{#if !!error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<input
|
<input
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
|
@ -227,7 +215,6 @@
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-invalid={!!error}
|
|
||||||
on:click={flatpickr?.open}
|
on:click={flatpickr?.open}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
export let handleFileTooLarge = null
|
export let handleFileTooLarge = null
|
||||||
export let handleTooManyFiles = null
|
export let handleTooManyFiles = null
|
||||||
export let gallery = true
|
export let gallery = true
|
||||||
export let error = null
|
|
||||||
export let fileTags = []
|
export let fileTags = []
|
||||||
export let maximum = null
|
export let maximum = null
|
||||||
export let extensions = "*"
|
export let extensions = "*"
|
||||||
|
@ -222,7 +221,6 @@
|
||||||
{#if showDropzone}
|
{#if showDropzone}
|
||||||
<div
|
<div
|
||||||
class="spectrum-Dropzone"
|
class="spectrum-Dropzone"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:disabled
|
class:disabled
|
||||||
role="region"
|
role="region"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -351,9 +349,6 @@
|
||||||
.spectrum-Dropzone {
|
.spectrum-Dropzone {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
.spectrum-Dropzone.is-invalid {
|
|
||||||
border-color: var(--spectrum-global-color-red-400);
|
|
||||||
}
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,10 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => extractProperty(option, "label")
|
export let getOptionLabel = option => extractProperty(option, "label")
|
||||||
export let getOptionValue = option => extractProperty(option, "value")
|
export let getOptionValue = option => extractProperty(option, "value")
|
||||||
|
export let getOptionSubtitle = option => option?.subtitle
|
||||||
export let isOptionSelected = () => false
|
export let isOptionSelected = () => false
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -111,27 +110,12 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="spectrum-InputGroup" class:is-disabled={disabled}>
|
||||||
class="spectrum-InputGroup"
|
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
{id}
|
{id}
|
||||||
on:click
|
on:click
|
||||||
|
@ -151,7 +135,7 @@
|
||||||
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
class="spectrum-Textfield-input spectrum-InputGroup-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="width: 30%">
|
<div style="width: 40%">
|
||||||
<button
|
<button
|
||||||
{id}
|
{id}
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM override-borders"
|
class="spectrum-Picker spectrum-Picker--sizeM override-borders"
|
||||||
|
@ -173,38 +157,43 @@
|
||||||
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{#if open}
|
|
||||||
<div
|
|
||||||
use:clickOutside={handleOutsideClick}
|
|
||||||
transition:fly|local={{ y: -20, duration: 200 }}
|
|
||||||
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
|
||||||
>
|
|
||||||
<ul class="spectrum-Menu" role="listbox">
|
|
||||||
{#each options as option, idx}
|
|
||||||
<li
|
|
||||||
class="spectrum-Menu-item"
|
|
||||||
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
|
|
||||||
role="option"
|
|
||||||
aria-selected="true"
|
|
||||||
tabindex="0"
|
|
||||||
on:click={() => onPick(getOptionValue(option, idx))}
|
|
||||||
>
|
|
||||||
<span class="spectrum-Menu-itemLabel">
|
|
||||||
{getOptionLabel(option, idx)}
|
|
||||||
</span>
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
|
||||||
</svg>
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
{#if open}
|
||||||
|
<div
|
||||||
|
use:clickOutside={handleOutsideClick}
|
||||||
|
transition:fly|local={{ y: -20, duration: 200 }}
|
||||||
|
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
|
||||||
|
>
|
||||||
|
<ul class="spectrum-Menu" role="listbox">
|
||||||
|
{#each options as option, idx}
|
||||||
|
<li
|
||||||
|
class="spectrum-Menu-item"
|
||||||
|
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
|
||||||
|
role="option"
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => onPick(getOptionValue(option, idx))}
|
||||||
|
>
|
||||||
|
<span class="spectrum-Menu-itemLabel">
|
||||||
|
{getOptionLabel(option, idx)}
|
||||||
|
{#if getOptionSubtitle(option, idx)}
|
||||||
|
<span class="subtitle-text">
|
||||||
|
{getOptionSubtitle(option, idx)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
<svg
|
||||||
|
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||||
|
focusable="false"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<use xlink:href="#spectrum-css-icon-Checkmark100" />
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -212,7 +201,6 @@
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-InputGroup-input {
|
.spectrum-InputGroup-input {
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
}
|
}
|
||||||
|
@ -222,7 +210,6 @@
|
||||||
.spectrum-Textfield-input {
|
.spectrum-Textfield-input {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.override-borders {
|
.override-borders {
|
||||||
border-top-left-radius: 0px;
|
border-top-left-radius: 0px;
|
||||||
border-bottom-left-radius: 0px;
|
border-bottom-left-radius: 0px;
|
||||||
|
@ -231,5 +218,18 @@
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.subtitle-text {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--spectrum-global-color-gray-600);
|
||||||
|
display: block;
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.spectrum-Menu-checkmark {
|
||||||
|
align-self: center;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
|
@ -84,7 +83,6 @@
|
||||||
<Picker
|
<Picker
|
||||||
on:loadMore
|
on:loadMore
|
||||||
{id}
|
{id}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{fieldText}
|
{fieldText}
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let fieldText = ""
|
export let fieldText = ""
|
||||||
export let fieldIcon = ""
|
export let fieldIcon = ""
|
||||||
export let fieldColour = ""
|
export let fieldColour = ""
|
||||||
|
@ -113,7 +112,6 @@
|
||||||
class="spectrum-Picker spectrum-Picker--sizeM"
|
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||||
class:spectrum-Picker--quiet={quiet}
|
class:spectrum-Picker--quiet={quiet}
|
||||||
{disabled}
|
{disabled}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-open={open}
|
class:is-open={open}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
|
@ -142,16 +140,6 @@
|
||||||
>
|
>
|
||||||
{fieldText}
|
{fieldText}
|
||||||
</span>
|
</span>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
aria-label="Folder"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
|
@ -236,13 +224,12 @@
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="spectrum-Menu-itemLabel">
|
<span class="spectrum-Menu-itemLabel">
|
||||||
{#if getOptionSubtitle(option, idx)}
|
|
||||||
<span class="subtitle-text"
|
|
||||||
>{getOptionSubtitle(option, idx)}</span
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{getOptionLabel(option, idx)}
|
{getOptionLabel(option, idx)}
|
||||||
|
{#if getOptionSubtitle(option, idx)}
|
||||||
|
<span class="subtitle-text">
|
||||||
|
{getOptionSubtitle(option, idx)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if option.tag}
|
{#if option.tag}
|
||||||
<span class="option-tag">
|
<span class="option-tag">
|
||||||
|
@ -287,10 +274,9 @@
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
top: 10px;
|
|
||||||
color: var(--spectrum-global-color-gray-600);
|
color: var(--spectrum-global-color-gray-600);
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: var(--spacing-s);
|
margin-top: var(--spacing-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrum-Picker-label.auto-width {
|
.spectrum-Picker-label.auto-width {
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let placeholder = "Choose an option or type"
|
export let placeholder = "Choose an option or type"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let secondaryOptions = []
|
export let secondaryOptions = []
|
||||||
export let primaryOptions = []
|
export let primaryOptions = []
|
||||||
export let secondaryFieldText = ""
|
export let secondaryFieldText = ""
|
||||||
|
@ -105,14 +104,9 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="spectrum-InputGroup" class:is-disabled={disabled}>
|
||||||
class="spectrum-InputGroup"
|
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
class:is-full-width={!secondaryOptions.length}
|
class:is-full-width={!secondaryOptions.length}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let direction = "vertical"
|
export let direction = "vertical"
|
||||||
export let value = null
|
export let value = null
|
||||||
export let options = []
|
export let options = []
|
||||||
export let error = null
|
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
|
@ -40,7 +39,6 @@
|
||||||
<div
|
<div
|
||||||
title={getOptionTitle(option)}
|
title={getOptionTitle(option)}
|
||||||
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:readonly
|
class:readonly
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
|
|
@ -5,14 +5,13 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let height = null
|
export let height = null
|
||||||
export let id = null
|
export let id = null
|
||||||
export let fullScreenOffset = null
|
export let fullScreenOffset = null
|
||||||
export let easyMDEOptions = null
|
export let easyMDEOptions = null
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class:error>
|
<div>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
{value}
|
{value}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
|
@ -27,18 +26,4 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.error :global(.EasyMDEContainer .editor-toolbar) {
|
|
||||||
border-top-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-left-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
}
|
|
||||||
.error :global(.EasyMDEContainer .CodeMirror) {
|
|
||||||
border-bottom-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-left-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
}
|
|
||||||
.error :global(.EasyMDEContainer .editor-preview-side) {
|
|
||||||
border-bottom-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
export let id = null
|
export let id = null
|
||||||
export let placeholder = "Choose an option"
|
export let placeholder = "Choose an option"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let options = []
|
export let options = []
|
||||||
export let getOptionLabel = option => option
|
export let getOptionLabel = option => option
|
||||||
export let getOptionValue = option => option
|
export let getOptionValue = option => option
|
||||||
export let getOptionIcon = () => null
|
export let getOptionIcon = () => null
|
||||||
export let useOptionIconImage = false
|
|
||||||
export let getOptionColour = () => null
|
export let getOptionColour = () => null
|
||||||
|
export let getOptionSubtitle = () => null
|
||||||
|
export let useOptionIconImage = false
|
||||||
export let isOptionEnabled
|
export let isOptionEnabled
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
@ -71,7 +71,6 @@
|
||||||
on:loadMore
|
on:loadMore
|
||||||
{quiet}
|
{quiet}
|
||||||
{id}
|
{id}
|
||||||
{error}
|
|
||||||
{disabled}
|
{disabled}
|
||||||
{readonly}
|
{readonly}
|
||||||
{fieldText}
|
{fieldText}
|
||||||
|
@ -84,8 +83,9 @@
|
||||||
{getOptionLabel}
|
{getOptionLabel}
|
||||||
{getOptionValue}
|
{getOptionValue}
|
||||||
{getOptionIcon}
|
{getOptionIcon}
|
||||||
{useOptionIconImage}
|
|
||||||
{getOptionColour}
|
{getOptionColour}
|
||||||
|
{getOptionSubtitle}
|
||||||
|
{useOptionIconImage}
|
||||||
{isOptionEnabled}
|
{isOptionEnabled}
|
||||||
{autocomplete}
|
{autocomplete}
|
||||||
{sort}
|
{sort}
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
export let value = null
|
export let value = null
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
|
@ -98,20 +97,9 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-Stepper"
|
class="spectrum-Stepper"
|
||||||
class:spectrum-Stepper--quiet={quiet}
|
class:spectrum-Stepper--quiet={quiet}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="spectrum-Textfield spectrum-Stepper-textfield">
|
<div class="spectrum-Textfield spectrum-Stepper-textfield">
|
||||||
<input
|
<input
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let height = null
|
export let height = null
|
||||||
export let minHeight = null
|
export let minHeight = null
|
||||||
|
@ -41,20 +40,9 @@
|
||||||
<div
|
<div
|
||||||
style={`${heightString}${minHeightString}`}
|
style={`${heightString}${minHeightString}`}
|
||||||
class="spectrum-Textfield spectrum-Textfield--multiline"
|
class="spectrum-Textfield spectrum-Textfield--multiline"
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM
|
|
||||||
spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<textarea
|
<textarea
|
||||||
bind:this={textarea}
|
bind:this={textarea}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
export let placeholder = null
|
export let placeholder = null
|
||||||
export let type = "text"
|
export let type = "text"
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let error = null
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let readonly = false
|
export let readonly = false
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
|
@ -78,19 +77,9 @@
|
||||||
<div
|
<div
|
||||||
class="spectrum-Textfield"
|
class="spectrum-Textfield"
|
||||||
class:spectrum-Textfield--quiet={quiet}
|
class:spectrum-Textfield--quiet={quiet}
|
||||||
class:is-invalid={!!error}
|
|
||||||
class:is-disabled={disabled}
|
class:is-disabled={disabled}
|
||||||
class:is-focused={focus}
|
class:is-focused={focus}
|
||||||
>
|
>
|
||||||
{#if error}
|
|
||||||
<svg
|
|
||||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
|
||||||
focusable="false"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
|
||||||
</svg>
|
|
||||||
{/if}
|
|
||||||
<input
|
<input
|
||||||
bind:this={field}
|
bind:this={field}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let appendTo = undefined
|
export let appendTo = undefined
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
export let range = false
|
export let range = false
|
||||||
|
export let helpText = null
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let fileTags = []
|
export let fileTags = []
|
||||||
export let maximum = undefined
|
export let maximum = undefined
|
||||||
export let compact = false
|
export let compact = false
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<CoreDropzone
|
<CoreDropzone
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let variables
|
export let variables
|
||||||
export let showModal
|
export let showModal
|
||||||
|
export let helpText = null
|
||||||
export let environmentVariablesEnabled
|
export let environmentVariablesEnabled
|
||||||
export let handleUpgradePanel
|
export let handleUpgradePanel
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<EnvDropdown
|
<EnvDropdown
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
import FieldLabel from "./FieldLabel.svelte"
|
import FieldLabel from "./FieldLabel.svelte"
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
|
||||||
export let id = null
|
export let id = null
|
||||||
export let label = null
|
export let label = null
|
||||||
export let labelPosition = "above"
|
export let labelPosition = "above"
|
||||||
export let error = null
|
export let error = null
|
||||||
|
export let helpText = null
|
||||||
export let tooltip = ""
|
export let tooltip = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -17,6 +19,10 @@
|
||||||
<slot />
|
<slot />
|
||||||
{#if error}
|
{#if error}
|
||||||
<div class="error">{error}</div>
|
<div class="error">{error}</div>
|
||||||
|
{:else if helpText}
|
||||||
|
<div class="helpText">
|
||||||
|
<Icon name="HelpOutline" /> <span>{helpText}</span>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,4 +45,21 @@
|
||||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
margin-top: var(--spectrum-global-dimension-size-75);
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.helpText {
|
||||||
|
display: flex;
|
||||||
|
margin-top: var(--spectrum-global-dimension-size-75);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText :global(svg) {
|
||||||
|
width: 14px;
|
||||||
|
color: var(--grey-5);
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helpText span {
|
||||||
|
color: var(--grey-7);
|
||||||
|
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
export let title = null
|
export let title = null
|
||||||
export let value = null
|
export let value = null
|
||||||
export let tooltip = null
|
export let tooltip = null
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error} {tooltip}>
|
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||||
<CoreFile
|
<CoreFile
|
||||||
{error}
|
{error}
|
||||||
{disabled}
|
{disabled}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus
|
||||||
export let autocomplete
|
export let autocomplete
|
||||||
|
export let helpText = null
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<TextField
|
<TextField
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
export let updateOnChange = true
|
export let updateOnChange = true
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autofocus
|
export let autofocus
|
||||||
|
export let helpText = null
|
||||||
export let options = []
|
export let options = []
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
@ -29,7 +30,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {helpText} {label} {labelPosition} {error}>
|
||||||
<InputDropdown
|
<InputDropdown
|
||||||
{updateOnChange}
|
{updateOnChange}
|
||||||
{error}
|
{error}
|
||||||
|
@ -42,6 +43,7 @@
|
||||||
{quiet}
|
{quiet}
|
||||||
{autofocus}
|
{autofocus}
|
||||||
{options}
|
{options}
|
||||||
|
isOptionSelected={option => option === dropdownValue}
|
||||||
on:change={onChange}
|
on:change={onChange}
|
||||||
on:pick={onPick}
|
on:pick={onPick}
|
||||||
on:click
|
on:click
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue