diff --git a/.eslintignore b/.eslintignore index 1dac74b117..0d81de0ef9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,4 +10,4 @@ packages/builder/.routify packages/builder/cypress/support/queryLevelTransformerFunction.js packages/builder/cypress/support/queryLevelTransformerFunctionWithData.js packages/builder/cypress/reports -packages/sdk/sdk \ No newline at end of file +packages/sdk/sdk diff --git a/.eslintrc.json b/.eslintrc.json index 79e0c00abd..d94c749042 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -16,8 +16,7 @@ "dist", "public", "*.spec.js", - "bundle.js", - "packages/pro" + "bundle.js" ], "plugins": ["svelte3"], "extends": ["eslint:recommended"], @@ -30,9 +29,7 @@ "files": ["**/*.ts"], "parser": "@typescript-eslint/parser", "plugins": [], - "extends": [ - "eslint:recommended" - ], + "extends": ["eslint:recommended"], "rules": { "no-unused-vars": "off", "no-inner-declarations": "off", diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 6c875f2dfe..4ada96accc 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -1,5 +1,9 @@ name: Budibase CI +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + on: # Trigger the workflow on push or pull request, # but only for the master branch @@ -22,7 +26,16 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo and submodules + uses: actions/checkout@v3 + if: github.repository == github.event.pull_request.head.repo.full_name + with: + submodules: true + token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + - name: Checkout repo only + uses: actions/checkout@v3 + if: github.repository != github.event.pull_request.head.repo.full_name + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -34,10 +47,16 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo and submodules + uses: actions/checkout@v3 + if: github.repository == github.event.pull_request.head.repo.full_name with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + - name: Checkout repo only + uses: actions/checkout@v3 + if: github.repository != github.event.pull_request.head.repo.full_name + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -52,10 +71,16 @@ jobs: test-libraries: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo and submodules + uses: actions/checkout@v3 + if: github.repository == github.event.pull_request.head.repo.full_name with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + - name: Checkout repo only + uses: actions/checkout@v3 + if: github.repository != github.event.pull_request.head.repo.full_name + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -72,10 +97,16 @@ jobs: test-services: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo and submodules + uses: actions/checkout@v3 + if: github.repository == github.event.pull_request.head.repo.full_name with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + - name: Checkout repo only + uses: actions/checkout@v3 + if: github.repository != github.event.pull_request.head.repo.full_name + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -91,11 +122,14 @@ jobs: test-pro: runs-on: ubuntu-latest + if: github.repository == github.event.pull_request.head.repo.full_name steps: - - uses: actions/checkout@v3 + - name: Checkout repo and submodules + uses: actions/checkout@v3 with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -107,10 +141,16 @@ jobs: integration-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo and submodules + uses: actions/checkout@v3 + if: github.repository == github.event.pull_request.head.repo.full_name with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} + - name: Checkout repo only + uses: actions/checkout@v3 + if: github.repository != github.event.pull_request.head.repo.full_name + - name: Use Node.js 14.x uses: actions/setup-node@v3 with: @@ -129,21 +169,46 @@ jobs: check-pro-submodule: runs-on: ubuntu-latest + if: github.repository == github.event.pull_request.head.repo.full_name steps: - - name: Checkout code + - name: Checkout repo and submodules uses: actions/checkout@v3 with: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN || github.token }} - fetch-depth: 0 - - name: Check submodule + + - name: Check pro commit + id: get_pro_commits run: | cd packages/pro - git fetch - if ! git merge-base --is-ancestor $(git log -n 1 --pretty=format:%H) origin/develop; then - echo "Current commit has not been merged to develop" - echo "Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md" - exit 1 + pro_commit=$(git rev-parse HEAD) + + branch=${{ github.base_ref || github.ref_name }} + echo "Running on branch `$branch` (base_ref=${{ github.base_ref }}, ref_name=${{ github.head_ref }})" + + if [[ "$branch" == "master" ]]; then + base_commit=$(git rev-parse origin/master) else - echo "All good, the submodule had been merged!" + base_commit=$(git rev-parse origin/develop) fi + + echo "pro_commit=$pro_commit" + echo "pro_commit=$pro_commit" >> "$GITHUB_OUTPUT" + echo "base_commit=$base_commit" + echo "base_commit=$base_commit" >> "$GITHUB_OUTPUT" + + - name: Check submodule merged to develop + uses: actions/github-script@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const submoduleCommit = '${{ steps.get_pro_commits.outputs.pro_commit }}'; + const baseCommit = '${{ steps.get_pro_commits.outputs.base_commit }}'; + + if (submoduleCommit !== baseCommit) { + console.error('Submodule commit does not match the latest commit on the develop branch.'); + console.error('Refer to the pro repo to merge your changes: https://github.com/Budibase/budibase-pro/blob/develop/docs/getting_started.md') + process.exit(1); + } else { + console.log('All good, the submodule had been merged and setup correctly!') + } diff --git a/.github/workflows/tag-prerelease.yml b/.github/workflows/tag-prerelease.yml index 83660e409d..f6446c55f5 100644 --- a/.github/workflows/tag-prerelease.yml +++ b/.github/workflows/tag-prerelease.yml @@ -32,10 +32,11 @@ jobs: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - run: yarn + - run: cd scripts && yarn - name: Tag prerelease run: | + cd scripts # setup the username and email. git config --global user.name "Budibase Staging Release Bot" git config --global user.email "<>" - ./scripts/versionCommit.sh prerelease + ./versionCommit.sh prerelease diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml index f361c200a0..191c3ad9ef 100644 --- a/.github/workflows/tag-release.yml +++ b/.github/workflows/tag-release.yml @@ -42,12 +42,13 @@ jobs: submodules: true token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - - run: yarn + - run: cd scripts && yarn - name: Tag release run: | + cd scripts # setup the username and email. git config --global user.name "Budibase Staging Release Bot" git config --global user.email "<>" BUMP_TYPE_INPUT=${{ github.event.inputs.versioning }} BUMP_TYPE=${BUMP_TYPE_INPUT:-"patch"} - ./scripts/versionCommit.sh $BUMP_TYPE + ./versionCommit.sh $BUMP_TYPE diff --git a/hosting/nginx.dev.conf b/hosting/nginx.dev.conf index 1ecee422cd..915125cbce 100644 --- a/hosting/nginx.dev.conf +++ b/hosting/nginx.dev.conf @@ -126,6 +126,16 @@ http { proxy_pass http://app-service; } + location /embed { + rewrite /embed/(.*) /app/$1 break; + proxy_pass http://app-service; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header x-budibase-embed "true"; + add_header x-budibase-embed "true"; + add_header Content-Security-Policy "frame-ancestors *"; + } + location /builder { proxy_read_timeout 120s; proxy_connect_timeout 120s; diff --git a/hosting/proxy/nginx.prod.conf b/hosting/proxy/nginx.prod.conf index 001a08a9a6..9ce6b54053 100644 --- a/hosting/proxy/nginx.prod.conf +++ b/hosting/proxy/nginx.prod.conf @@ -92,6 +92,16 @@ http { proxy_pass $apps; } + location /embed { + rewrite /embed/(.*) /app/$1 break; + proxy_pass $apps; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header x-budibase-embed "true"; + add_header x-budibase-embed "true"; + add_header Content-Security-Policy "frame-ancestors *"; + } + location = / { proxy_pass $apps; } diff --git a/hosting/scripts/airgapped/airgappedDockerBuild.js b/hosting/scripts/airgapped/airgappedDockerBuild.js index cc0ea48eb3..58bc7c09a9 100755 --- a/hosting/scripts/airgapped/airgappedDockerBuild.js +++ b/hosting/scripts/airgapped/airgappedDockerBuild.js @@ -2,7 +2,9 @@ const fs = require("fs") const { execSync } = require("child_process") const path = require("path") -const IMAGES = { +const IS_SINGLE_IMAGE = process.env.SINGLE_IMAGE + +let IMAGES = { worker: "budibase/worker", apps: "budibase/apps", proxy: "budibase/proxy", @@ -10,7 +12,13 @@ const IMAGES = { couch: "ibmcom/couchdb3", curl: "curlimages/curl", redis: "redis", - watchtower: "containrrr/watchtower" + watchtower: "containrrr/watchtower", +} + +if (IS_SINGLE_IMAGE) { + IMAGES = { + budibase: "budibase/budibase" + } } const FILES = { @@ -39,11 +47,10 @@ for (let image in IMAGES) { } // copy config files -copyFile(FILES.COMPOSE) +if (!IS_SINGLE_IMAGE) { + copyFile(FILES.COMPOSE) +} copyFile(FILES.ENV) // compress -execSync(`tar -czf bb-airgapped.tar.gz hosting/scripts/bb-airgapped`) - -// clean up -fs.rmdirSync(OUTPUT_DIR, { recursive: true }) \ No newline at end of file +execSync(`tar -czf bb-airgapped.tar.gz hosting/scripts/bb-airgapped`) \ No newline at end of file diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index 56df8185a9..e43e5ad10c 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -37,6 +37,14 @@ COPY --from=build /worker /worker RUN apt-get update && \ apt-get install -y --no-install-recommends software-properties-common nginx uuid-runtime redis-server +# Install postgres client for pg_dump utils +RUN apt install software-properties-common apt-transport-https gpg -y \ + && 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 RUN curl -sL https://deb.nodesource.com/setup_16.x -o /tmp/nodesource_setup.sh && \ diff --git a/lerna.json b/lerna.json index 00e6aa61f9..2c240a88ad 100644 --- a/lerna.json +++ b/lerna.json @@ -1,21 +1,10 @@ { - "version": "2.7.20-alpha.2", + "version": "2.7.37-alpha.3", "npmClient": "yarn", "packages": [ - "packages/backend-core", - "packages/bbui", - "packages/builder", - "packages/cli", - "packages/client", - "packages/frontend-core", - "packages/sdk", - "packages/server", - "packages/shared-core", - "packages/string-templates", - "packages/types", - "packages/worker", - "packages/pro/packages/pro" + "packages/*" ], + "useNx": true, "command": { "publish": { "ignoreChanges": [ diff --git a/package.json b/package.json index 49ffc5fef7..42e528dfce 100644 --- a/package.json +++ b/package.json @@ -2,28 +2,26 @@ "name": "root", "private": true, "devDependencies": { - "@esbuild-plugins/node-resolve": "^0.2.2", "@esbuild-plugins/tsconfig-paths": "^0.1.2", "@nx/js": "16.2.1", "@rollup/plugin-json": "^4.0.2", "@typescript-eslint/parser": "5.45.0", "babel-eslint": "^10.0.3", "esbuild": "^0.17.18", + "esbuild-node-externals": "^1.7.0", "eslint": "^7.28.0", "eslint-plugin-cypress": "^2.11.3", "eslint-plugin-svelte3": "^3.2.0", "husky": "^8.0.3", "js-yaml": "^4.1.0", "kill-port": "^1.6.1", - "lerna": "^7.0.1", + "lerna": "7.0.2", "madge": "^6.0.0", "minimist": "^1.2.8", - "nx": "^16.3.2", - "prettier": "^2.3.1", + "prettier": "2.8.8", "prettier-plugin-svelte": "^2.3.0", "rimraf": "^3.0.2", "rollup-plugin-replace": "^2.2.0", - "semver": "^7.5.0", "svelte": "^3.38.2", "typescript": "4.7.3" }, @@ -48,9 +46,9 @@ "kill-builder": "kill-port 3000", "kill-server": "kill-port 4001 4002", "kill-all": "yarn run kill-builder && yarn run kill-server", - "dev": "yarn run kill-all && lerna run --stream --parallel dev:builder --stream", - "dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", - "dev:server": "yarn run kill-server && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server", + "dev": "yarn run kill-all && lerna run --stream --parallel dev:builder --stream", + "dev:noserver": "yarn run kill-builder && lerna run --stream dev:stack:up && lerna run --stream --parallel dev:builder --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", + "dev:server": "yarn run kill-server && yarn build --projects=@budibase/client && lerna run --stream --parallel dev:builder --scope @budibase/worker --scope @budibase/server", "dev:built": "yarn run kill-all && cd packages/server && yarn dev:stack:up && cd ../../ && lerna run --stream --parallel dev:built", "dev:docker": "yarn build:docker:pre && docker-compose -f hosting/docker-compose.build.yaml -f hosting/docker-compose.dev.yaml --env-file hosting/.env up --build --scale proxy-service=0", "test": "lerna run --stream test --stream", @@ -67,6 +65,7 @@ "build:docker:selfhost": "lerna run --stream build:docker && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh latest && cd -", "build:docker:develop": "node scripts/pinVersions && lerna run --stream build:docker && yarn build:docker:proxy && cd hosting/scripts/linux/ && ./release-to-docker-hub.sh develop && cd -", "build:docker:airgap": "node hosting/scripts/airgapped/airgappedDockerBuild", + "build:docker:airgap:single": "SINGLE_IMAGE=1 node hosting/scripts/airgapped/airgappedDockerBuild", "build:digitalocean": "cd hosting/digitalocean && ./build.sh && cd -", "build:docker:single:multiarch": "docker buildx build --platform linux/arm64,linux/amd64 -f hosting/single/Dockerfile -t budibase:latest .", "build:docker:single:image": "docker build -f hosting/single/Dockerfile -t budibase:latest .", @@ -95,19 +94,7 @@ }, "workspaces": { "packages": [ - "packages/backend-core", - "packages/bbui", - "packages/builder", - "packages/cli", - "packages/client", - "packages/frontend-core", - "packages/sdk", - "packages/server", - "packages/shared-core", - "packages/string-templates", - "packages/types", - "packages/worker", - "packages/pro/packages/pro" + "packages/*" ] }, "resolutions": { diff --git a/packages/backend-core/jest.config.ts b/packages/backend-core/jest.config.ts index 1e69797e71..8d64b24a2f 100644 --- a/packages/backend-core/jest.config.ts +++ b/packages/backend-core/jest.config.ts @@ -31,4 +31,6 @@ const config: Config.InitialOptions = { coverageReporters: ["lcov", "json", "clover"], } +process.env.DISABLE_PINO_LOGGER = "1" + export default config diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index f85687b007..4a1ed5c373 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -27,7 +27,7 @@ "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", "aws-sdk": "2.1030.0", - "bcrypt": "5.0.1", + "bcrypt": "5.1.0", "bcryptjs": "2.4.3", "bull": "4.10.1", "correlation-id": "4.0.0", diff --git a/packages/backend-core/src/configs/configs.ts b/packages/backend-core/src/configs/configs.ts index 701b5bb329..49ace84d52 100644 --- a/packages/backend-core/src/configs/configs.ts +++ b/packages/backend-core/src/configs/configs.ts @@ -5,6 +5,7 @@ import { GoogleInnerConfig, OIDCConfig, OIDCInnerConfig, + OIDCLogosConfig, SCIMConfig, SCIMInnerConfig, SettingsConfig, @@ -191,6 +192,10 @@ export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined { // OIDC +export async function getOIDCLogosDoc(): Promise { + return getConfig(ConfigType.OIDC_LOGOS) +} + async function getOIDCConfigDoc(): Promise { return getConfig(ConfigType.OIDC) } diff --git a/packages/backend-core/src/db/Replication.ts b/packages/backend-core/src/db/Replication.ts index eb9d613a58..e813722d98 100644 --- a/packages/backend-core/src/db/Replication.ts +++ b/packages/backend-core/src/db/Replication.ts @@ -57,6 +57,9 @@ class Replication { appReplicateOpts() { return { filter: (doc: any) => { + if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) { + return false + } return doc._id !== DocumentType.APP_METADATA }, } diff --git a/packages/backend-core/src/docIds/ids.ts b/packages/backend-core/src/docIds/ids.ts index 152977b3af..e0ac85b3df 100644 --- a/packages/backend-core/src/docIds/ids.ts +++ b/packages/backend-core/src/docIds/ids.ts @@ -81,8 +81,19 @@ export function generateAppUserID(prodAppId: string, userId: string) { * Generates a new role ID. * @returns {string} The new role ID which the role doc can be stored under. */ -export function generateRoleID(id?: any) { - return `${DocumentType.ROLE}${SEPARATOR}${id || newid()}` +export function generateRoleID(name: string) { + const prefix = `${DocumentType.ROLE}${SEPARATOR}` + if (name.startsWith(prefix)) { + return name + } + return `${prefix}${name}` +} + +/** + * Utility function to be more verbose. + */ +export function prefixRoleID(name: string) { + return generateRoleID(name) } /** diff --git a/packages/backend-core/src/events/publishers/serve.ts b/packages/backend-core/src/events/publishers/serve.ts index 64e24e20a7..ac6a23dfdb 100644 --- a/packages/backend-core/src/events/publishers/serve.ts +++ b/packages/backend-core/src/events/publishers/serve.ts @@ -14,10 +14,15 @@ async function servedBuilder(timezone: string) { await publishEvent(Event.SERVED_BUILDER, properties) } -async function servedApp(app: App, timezone: string) { +async function servedApp( + app: App, + timezone: string, + embed?: boolean | undefined +) { const properties: AppServedEvent = { appVersion: app.version, timezone, + embed: embed === true, } await publishEvent(Event.SERVED_APP, properties) } diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index e8a3c76c0a..cf5c6bc406 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -1,5 +1,5 @@ import { BuiltinPermissionID, PermissionLevel } from "./permissions" -import { generateRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db" +import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db" import { getAppDB } from "../context" import { doWithDB } from "../db" import { Screen, Role as RoleDoc } from "@budibase/types" @@ -25,18 +25,28 @@ const EXTERNAL_BUILTIN_ROLE_IDS = [ BUILTIN_IDS.PUBLIC, ] +export const RoleIDVersion = { + // original version, with a UUID based ID + UUID: undefined, + // new version - with name based ID + NAME: "name", +} + export class Role implements RoleDoc { _id: string _rev?: string name: string permissionId: string inherits?: string + version?: string permissions = {} constructor(id: string, name: string, permissionId: string) { this._id = id this.name = name this.permissionId = permissionId + // version for managing the ID - removing the role_ when responding + this.version = RoleIDVersion.NAME } addInheritance(inherits: string) { @@ -157,13 +167,16 @@ export async function getRole( role = cloneDeep( Object.values(BUILTIN_ROLES).find(role => role._id === roleId) ) + } else { + // make sure has the prefix (if it has it then it won't be added) + roleId = prefixRoleID(roleId) } try { const db = getAppDB() const dbRole = await db.get(getDBRoleID(roleId)) role = Object.assign(role, dbRole) // finalise the ID - role._id = getExternalRoleID(role._id) + role._id = getExternalRoleID(role._id, role.version) } catch (err) { if (!isBuiltin(roleId) && opts?.defaultPublic) { return cloneDeep(BUILTIN_ROLES.PUBLIC) @@ -261,6 +274,9 @@ export async function getAllRoles(appId?: string) { }) ) roles = body.rows.map((row: any) => row.doc) + roles.forEach( + role => (role._id = getExternalRoleID(role._id!, role.version)) + ) } const builtinRoles = getBuiltinRoles() @@ -268,14 +284,15 @@ export async function getAllRoles(appId?: string) { for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { const builtinRole = builtinRoles[builtinRoleId] const dbBuiltin = roles.filter( - dbRole => getExternalRoleID(dbRole._id) === builtinRoleId + dbRole => + getExternalRoleID(dbRole._id!, dbRole.version) === builtinRoleId )[0] if (dbBuiltin == null) { roles.push(builtinRole || builtinRoles.BASIC) } else { // remove role and all back after combining with the builtin roles = roles.filter(role => role._id !== dbBuiltin._id) - dbBuiltin._id = getExternalRoleID(dbBuiltin._id) + dbBuiltin._id = getExternalRoleID(dbBuiltin._id!, dbBuiltin.version) roles.push(Object.assign(builtinRole, dbBuiltin)) } } @@ -381,19 +398,22 @@ export class AccessController { /** * Adds the "role_" for builtin role IDs which are to be written to the DB (for permissions). */ -export function getDBRoleID(roleId?: string) { - if (roleId?.startsWith(DocumentType.ROLE)) { - return roleId +export function getDBRoleID(roleName: string) { + if (roleName?.startsWith(DocumentType.ROLE)) { + return roleName } - return generateRoleID(roleId) + return prefixRoleID(roleName) } /** * Remove the "role_" from builtin role IDs that have been written to the DB (for permissions). */ -export function getExternalRoleID(roleId?: string) { +export function getExternalRoleID(roleId: string, version?: string) { // for built-in roles we want to remove the DB role ID element (role_) - if (roleId?.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) { + if ( + (roleId.startsWith(DocumentType.ROLE) && isBuiltin(roleId)) || + version === RoleIDVersion.NAME + ) { return roleId.split(`${DocumentType.ROLE}${SEPARATOR}`)[1] } return roleId diff --git a/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte b/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte index aaea388c36..b2e412ac24 100644 --- a/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte +++ b/packages/bbui/src/FancyForm/FancyCheckboxGroup.svelte @@ -8,11 +8,10 @@ export let showSelectAll = true export let selectAllText = "Select all" - let selectedBooleans = reset() + let selectedBooleans = options.map(x => selected.indexOf(x) > -1) const dispatch = createEventDispatcher() $: updateSelected(selectedBooleans) - $: dispatch("change", selected) $: allSelected = selected?.length === options.length $: noneSelected = !selected?.length @@ -28,6 +27,7 @@ } } selected = array + dispatch("change", selected) } function toggleSelectAll() { @@ -36,6 +36,7 @@ } else { selectedBooleans = reset() } + dispatch("change", selected) } diff --git a/packages/bbui/src/Form/Core/DatePicker.svelte b/packages/bbui/src/Form/Core/DatePicker.svelte index ad39136142..7ce15292be 100644 --- a/packages/bbui/src/Form/Core/DatePicker.svelte +++ b/packages/bbui/src/Form/Core/DatePicker.svelte @@ -71,6 +71,7 @@ timeOnly, enableTime, time24hr, + disabled, } const handleChange = event => { diff --git a/packages/bbui/src/Form/Core/Picker.svelte b/packages/bbui/src/Form/Core/Picker.svelte index bd575600b1..aada17b318 100644 --- a/packages/bbui/src/Form/Core/Picker.svelte +++ b/packages/bbui/src/Form/Core/Picker.svelte @@ -99,9 +99,15 @@ bind:this={button} > {#if fieldIcon} - - - + {#if !useOptionIconImage} + + + + {:else} + + icon + + {/if} {/if} {#if fieldColour} @@ -311,4 +317,8 @@ max-width: 170px; font-size: 12px; } + + .option-extra.icon.field-icon { + display: flex; + } diff --git a/packages/bbui/src/Markdown/SpectrumMDE.svelte b/packages/bbui/src/Markdown/SpectrumMDE.svelte index 9b0832c91f..8e7b1bbdfd 100644 --- a/packages/bbui/src/Markdown/SpectrumMDE.svelte +++ b/packages/bbui/src/Markdown/SpectrumMDE.svelte @@ -87,7 +87,7 @@ border-color: var(--spectrum-global-color-gray-400); } /* Toolbar button color */ - :global(.EasyMDEContainer .editor-toolbar button i) { + :global(.EasyMDEContainer .editor-toolbar button) { color: var(--spectrum-global-color-gray-800); } /* Separator between toolbar buttons*/ diff --git a/packages/builder/package.json b/packages/builder/package.json index a2567dc638..646bb144df 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -9,7 +9,8 @@ "dev:builder": "routify -c dev:vite", "dev:vite": "vite --host 0.0.0.0", "rollup": "rollup -c -w", - "test": "vitest run" + "test": "vitest run", + "test:watch": "vitest" }, "jest": { "globals": { diff --git a/packages/builder/src/builderStore/datasource.js b/packages/builder/src/builderStore/datasource.js deleted file mode 100644 index 219ff7eb8f..0000000000 --- a/packages/builder/src/builderStore/datasource.js +++ /dev/null @@ -1,54 +0,0 @@ -import { datasources, tables } from "../stores/backend" -import { IntegrationNames } from "../constants/backend" -import { get } from "svelte/store" -import cloneDeep from "lodash/cloneDeepWith" -import { API } from "api" - -function prepareData(config) { - let datasource = {} - let existingTypeCount = get(datasources).list.filter( - ds => ds.source === config.type - ).length - - let baseName = IntegrationNames[config.type] || config.name - let name = - existingTypeCount === 0 ? baseName : `${baseName}-${existingTypeCount + 1}` - - datasource.type = "datasource" - datasource.source = config.type - datasource.config = config.config - datasource.name = name - datasource.plus = config.plus - - return datasource -} - -export async function saveDatasource(config, { skipFetch, tablesFilter } = {}) { - const datasource = prepareData(config) - // Create datasource - const fetchSchema = !skipFetch && datasource.plus - const resp = await datasources.save(datasource, { fetchSchema, tablesFilter }) - - // update the tables incase datasource plus - await tables.fetch() - await datasources.select(resp._id) - return resp -} - -export async function createRestDatasource(integration) { - const config = cloneDeep(integration) - return saveDatasource(config) -} - -export async function validateDatasourceConfig(config) { - const datasource = prepareData(config) - return await API.validateDatasource(datasource) -} - -export async function getDatasourceInfo(config) { - let datasource = config - if (!config._id) { - datasource = prepareData(config) - } - return await API.fetchInfoForDatasource(datasource) -} diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index d0414b5733..5de58f02e7 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -61,6 +61,9 @@ const INITIAL_FRONTEND_STATE = { showNotificationAction: false, sidePanel: false, }, + features: { + componentValidation: false, + }, errors: [], hasAppPackage: false, libraries: null, @@ -117,10 +120,13 @@ export const getFrontendStore = () => { reset: () => { store.set({ ...INITIAL_FRONTEND_STATE }) websocket?.disconnect() + websocket = null }, initialise: async pkg => { const { layouts, screens, application, clientLibPath, hasLock } = pkg - websocket = createBuilderWebsocket(application.appId) + if (!websocket) { + websocket = createBuilderWebsocket(application.appId) + } await store.actions.components.refreshDefinitions(application.appId) // Reset store state @@ -145,6 +151,11 @@ export const getFrontendStore = () => { navigation: application.navigation || {}, usedPlugins: application.usedPlugins || [], hasLock, + features: { + ...INITIAL_FRONTEND_STATE.features, + ...application.features, + }, + icon: application.icon || {}, initialised: true, })) screenHistoryStore.reset() @@ -225,6 +236,7 @@ export const getFrontendStore = () => { legalDirectChildren = [] ) => { const type = component._component + if (illegalChildren.includes(type)) { return type } @@ -238,10 +250,13 @@ export const getFrontendStore = () => { return } + if (type === "@budibase/standard-components/sidepanel") { + illegalChildren = [] + } + const definition = store.actions.components.getDefinition( component._component ) - // Reset whitelist for direct children legalDirectChildren = [] if (definition?.legalDirectChildren?.length) { @@ -280,9 +295,12 @@ export const getFrontendStore = () => { } }, save: async screen => { - // Validate screen structure - // Temporarily disabled to accommodate migration issues - // store.actions.screens.validate(screen) + const state = get(store) + + // Validate screen structure if the app supports it + if (state.features?.componentValidation) { + store.actions.screens.validate(screen) + } // Check screen definition for any component settings which need updated store.actions.screens.enrichEmptySettings(screen) @@ -293,7 +311,6 @@ export const getFrontendStore = () => { const routesResponse = await API.fetchAppRoutes() // If plugins changed we need to fetch the latest app metadata - const state = get(store) let usedPlugins = state.usedPlugins if (savedScreen.pluginAdded) { const { application } = await API.fetchAppPackage(state.appId) diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte index 092138170f..7a02433411 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/FlowItem.svelte @@ -78,9 +78,6 @@ } async function removeLooping() { - let loopBlock = $selectedAutomation?.definition.steps.find( - x => x.blockToLoop === block.id - ) try { await automationStore.actions.deleteAutomationBlock(loopBlock) } catch (error) { @@ -89,10 +86,6 @@ } async function deleteStep() { - let loopBlock = $selectedAutomation?.definition.steps.find( - x => x.blockToLoop === block.id - ) - try { if (loopBlock) { await automationStore.actions.deleteAutomationBlock(loopBlock) @@ -168,8 +161,8 @@ $automationStore.blockDefinitions.ACTION.LOOP.schema.inputs .properties )} - block={loopBlock} {webhookModal} + block={loopBlock} /> @@ -191,7 +184,7 @@ {#if !isTrigger}
- {#if block?.features?.[Features.LOOPING] || !block.features} + {#if !loopBlock && (block?.features?.[Features.LOOPING] || !block.features)} addLooping()} icon="Reuse"> Add Looping diff --git a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte index 0ce9c781ac..09b9cf07db 100644 --- a/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/EditAutomationPopover.svelte @@ -32,7 +32,12 @@
- Duplicate + Duplicate Edit Delete diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index a8fa700b90..823dcc432b 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -309,7 +309,7 @@ } function canShowField(key, value) { - const dependsOn = value.dependsOn + const dependsOn = value?.dependsOn return !dependsOn || !!inputData[dependsOn] } @@ -333,7 +333,7 @@ : null}>{value.title || (key === "row" ? "Table" : key)} {/if} - {#if value.type === "string" && value.enum && canShowField(key)} + {#if value.type === "string" && value.enum && canShowField(key, value)} -
- {/if} - {#each configKeys as configKey} - {#if schema[configKey].type === "object"} -
- - -
- - {:else if schema[configKey].type === "boolean"} -
- - -
- {:else if schema[configKey].type === "longForm"} -
- -