diff --git a/.github/workflows/deploy-cloud.yaml b/.github/workflows/deploy-cloud.yaml index 9f933746d6..389b10f7d3 100644 --- a/.github/workflows/deploy-cloud.yaml +++ b/.github/workflows/deploy-cloud.yaml @@ -12,31 +12,22 @@ jobs: 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: Pull values.yaml from budibase-infra + - name: Fail if not a tag run: | - curl -H "Authorization: token ${{ secrets.GH_ACCESS_TOKEN }}" \ - -H 'Accept: application/vnd.github.v3.raw' \ - -o values.production.yaml \ - -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/values.yaml - wc -l values.production.yaml + 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 @@ -48,29 +39,10 @@ jobs: fi echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 + - uses: passeidireto/trigger-external-workflow-action@main + env: + PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }} with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: eu-west-1 - - - name: Deploy to EKS - uses: craftech-io/eks-helm-deploy-action@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS__KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: eu-west-1 - cluster-name: budibase-eks-production - config-files: values.production.yaml - chart-path: charts/budibase - namespace: budibase - values: globals.appVersion=v${{ env.RELEASE_VERSION }},services.couchdb.url=${{ secrets.PRODUCTION_COUCHDB_URL }},services.couchdb.password=${{ secrets.PRODUCTION_COUCHDB_PASSWORD }} - name: budibase-prod - - - name: Discord Webhook Action - uses: tsickert/discord-webhook@v4.0.0 - with: - webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} - content: "Production Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Cloud." - embed-title: ${{ env.RELEASE_VERSION }} + repository: budibase/budibase-deploys + event: budicloud-prod-deploy + github_pat: ${{ secrets.GH_ACCESS_TOKEN }} diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml index 81395bc7e3..9b7bca4770 100644 --- a/.github/workflows/deploy-preprod.yml +++ b/.github/workflows/deploy-preprod.yml @@ -24,51 +24,18 @@ jobs: 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: | release_version=$(cat lerna.json | jq -r '.version') echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: eu-west-1 - - name: Pull values.yaml from budibase-infra - run: | - curl -H "Authorization: token ${{ secrets.GH_ACCESS_TOKEN }}" \ - -H 'Accept: application/vnd.github.v3.raw' \ - -o values.preprod.yaml \ - -L https://api.github.com/repos/budibase/budibase-infra/contents/kubernetes/budibase-preprod/values.yaml - wc -l values.preprod.yaml - - name: Deploy to Preprod Environment - uses: budibase/helm@v1.8.0 - with: - release: budibase-preprod - namespace: budibase - chart: charts/budibase - token: ${{ github.token }} - helm: helm3 - values: | - globals: - appVersion: v${{ env.RELEASE_VERSION }} - ingress: - enabled: true - nginx: true - value-files: >- - [ - "values.preprod.yaml" - ] + - uses: passeidireto/trigger-external-workflow-action@main env: - KUBECONFIG_FILE: '${{ secrets.PREPROD_KUBECONFIG }}' - - - name: Discord Webhook Action - uses: tsickert/discord-webhook@v4.0.0 + PAYLOAD_VERSION: ${{ env.RELEASE_VERSION }} with: - webhook-url: ${{ secrets.PROD_DEPLOY_WEBHOOK_URL }} - content: "Preprod Deployment Complete: ${{ env.RELEASE_VERSION }} deployed to Budibase Pre-prod." - embed-title: ${{ env.RELEASE_VERSION }} + repository: budibase/budibase-deploys + event: budicloud-preprod-deploy + github_pat: ${{ secrets.GH_ACCESS_TOKEN }} + diff --git a/lerna.json b/lerna.json index 53f54a2828..26b1168353 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.7.7-alpha.5", + "version": "2.7.25-alpha.5", "npmClient": "yarn", "packages": [ "packages/backend-core", diff --git a/package.json b/package.json index 56f015f8c0..9610aa42f1 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,13 @@ "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", @@ -48,9 +48,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", 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/db/lucene.ts b/packages/backend-core/src/db/lucene.ts index 4660be81aa..d501bb2166 100644 --- a/packages/backend-core/src/db/lucene.ts +++ b/packages/backend-core/src/db/lucene.ts @@ -343,6 +343,9 @@ export class QueryBuilder { } const oneOf = (key: string, value: any) => { + if (!value) { + return `*:*` + } if (!Array.isArray(value)) { if (typeof value === "string") { value = value.split(",") diff --git a/packages/backend-core/src/db/tests/lucene.spec.ts b/packages/backend-core/src/db/tests/lucene.spec.ts index b4791a86e0..a82828d8f2 100644 --- a/packages/backend-core/src/db/tests/lucene.spec.ts +++ b/packages/backend-core/src/db/tests/lucene.spec.ts @@ -114,6 +114,25 @@ describe("lucene", () => { expect(resp.rows.length).toBe(2) }) + it("should return all rows when doing a one of search against falsey value", async () => { + const builder = new QueryBuilder(dbName, INDEX_NAME) + builder.addOneOf("property", null) + let resp = await builder.run() + expect(resp.rows.length).toBe(3) + + builder.addOneOf("property", undefined) + resp = await builder.run() + expect(resp.rows.length).toBe(3) + + builder.addOneOf("property", "") + resp = await builder.run() + expect(resp.rows.length).toBe(3) + + builder.addOneOf("property", []) + resp = await builder.run() + expect(resp.rows.length).toBe(0) + }) + it("should be able to perform a contains search", async () => { const builder = new QueryBuilder(dbName, INDEX_NAME) builder.addContains("property", ["word"]) diff --git a/packages/backend-core/src/security/encryption.ts b/packages/backend-core/src/security/encryption.ts index f9adb68955..7a8cfaf04a 100644 --- a/packages/backend-core/src/security/encryption.ts +++ b/packages/backend-core/src/security/encryption.ts @@ -1,12 +1,17 @@ import crypto from "crypto" +import fs from "fs" +import zlib from "zlib" import env from "../environment" +import { join } from "path" const ALGO = "aes-256-ctr" const SEPARATOR = "-" const ITERATIONS = 10000 -const RANDOM_BYTES = 16 const STRETCH_LENGTH = 32 +const SALT_LENGTH = 16 +const IV_LENGTH = 16 + export enum SecretOption { API = "api", ENCRYPTION = "encryption", @@ -31,15 +36,15 @@ export function getSecret(secretOption: SecretOption): string { return secret } -function stretchString(string: string, salt: Buffer) { - return crypto.pbkdf2Sync(string, salt, ITERATIONS, STRETCH_LENGTH, "sha512") +function stretchString(secret: string, salt: Buffer) { + return crypto.pbkdf2Sync(secret, salt, ITERATIONS, STRETCH_LENGTH, "sha512") } export function encrypt( input: string, secretOption: SecretOption = SecretOption.API ) { - const salt = crypto.randomBytes(RANDOM_BYTES) + const salt = crypto.randomBytes(SALT_LENGTH) const stretched = stretchString(getSecret(secretOption), salt) const cipher = crypto.createCipheriv(ALGO, stretched, salt) const base = cipher.update(input) @@ -60,3 +65,115 @@ export function decrypt( const final = decipher.final() return Buffer.concat([base, final]).toString() } + +export async function encryptFile( + { dir, filename }: { dir: string; filename: string }, + secret: string +) { + const outputFileName = `${filename}.enc` + + const filePath = join(dir, filename) + const inputFile = fs.createReadStream(filePath) + const outputFile = fs.createWriteStream(join(dir, outputFileName)) + + const salt = crypto.randomBytes(SALT_LENGTH) + const iv = crypto.randomBytes(IV_LENGTH) + const stretched = stretchString(secret, salt) + const cipher = crypto.createCipheriv(ALGO, stretched, iv) + + outputFile.write(salt) + outputFile.write(iv) + + inputFile.pipe(zlib.createGzip()).pipe(cipher).pipe(outputFile) + + return new Promise<{ filename: string; dir: string }>(r => { + outputFile.on("finish", () => { + r({ + filename: outputFileName, + dir, + }) + }) + }) +} + +async function getSaltAndIV(path: string) { + const fileStream = fs.createReadStream(path) + + const salt = await readBytes(fileStream, SALT_LENGTH) + const iv = await readBytes(fileStream, IV_LENGTH) + fileStream.close() + return { salt, iv } +} + +export async function decryptFile( + inputPath: string, + outputPath: string, + secret: string +) { + const { salt, iv } = await getSaltAndIV(inputPath) + const inputFile = fs.createReadStream(inputPath, { + start: SALT_LENGTH + IV_LENGTH, + }) + + const outputFile = fs.createWriteStream(outputPath) + + const stretched = stretchString(secret, salt) + const decipher = crypto.createDecipheriv(ALGO, stretched, iv) + + const unzip = zlib.createGunzip() + + inputFile.pipe(decipher).pipe(unzip).pipe(outputFile) + + return new Promise((res, rej) => { + outputFile.on("finish", () => { + outputFile.close() + res() + }) + + inputFile.on("error", e => { + outputFile.close() + rej(e) + }) + + decipher.on("error", e => { + outputFile.close() + rej(e) + }) + + unzip.on("error", e => { + outputFile.close() + rej(e) + }) + + outputFile.on("error", e => { + outputFile.close() + rej(e) + }) + }) +} + +function readBytes(stream: fs.ReadStream, length: number) { + return new Promise((resolve, reject) => { + let bytesRead = 0 + const data: Buffer[] = [] + + stream.on("readable", () => { + let chunk + + while ((chunk = stream.read(length - bytesRead)) !== null) { + data.push(chunk) + bytesRead += chunk.length + } + + resolve(Buffer.concat(data)) + }) + + stream.on("end", () => { + reject(new Error("Insufficient data in the stream.")) + }) + + stream.on("error", error => { + reject(error) + }) + }) +} diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index bdf7a38726..e8a3c76c0a 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -140,9 +140,13 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string { * Gets the role object, this is mainly useful for two purposes, to check if the level exists and * to check if the role inherits any others. * @param {string|null} roleId The level ID to lookup. + * @param {object|null} opts options for the function, like whether to halt errors, instead return public. * @returns {Promise} The role object, which may contain an "inherits" property. */ -export async function getRole(roleId?: string): Promise { +export async function getRole( + roleId?: string, + opts?: { defaultPublic?: boolean } +): Promise { if (!roleId) { return undefined } @@ -161,6 +165,9 @@ export async function getRole(roleId?: string): Promise { // finalise the ID role._id = getExternalRoleID(role._id) } catch (err) { + if (!isBuiltin(roleId) && opts?.defaultPublic) { + return cloneDeep(BUILTIN_ROLES.PUBLIC) + } // only throw an error if there is no role at all if (Object.keys(role).length === 0) { throw err diff --git a/packages/bbui/src/Modal/Modal.svelte b/packages/bbui/src/Modal/Modal.svelte index f56ef1187f..384cfe6cac 100644 --- a/packages/bbui/src/Modal/Modal.svelte +++ b/packages/bbui/src/Modal/Modal.svelte @@ -8,6 +8,7 @@ export let fixed = false export let inline = false + export let disableCancel = false const dispatch = createEventDispatcher() let visible = fixed || inline @@ -38,7 +39,7 @@ } export function cancel() { - if (!visible) { + if (!visible || disableCancel) { return } dispatch("cancel") diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index 5bb4ed4523..f8528aac36 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -204,6 +204,12 @@ }) return columns .sort((a, b) => { + if (a.divider) { + return a + } + if (b.divider) { + return b + } const orderA = a.order || Number.MAX_SAFE_INTEGER const orderB = b.order || Number.MAX_SAFE_INTEGER const nameA = getDisplayName(a) diff --git a/packages/builder/index.html b/packages/builder/index.html index 96abc8e582..eb151de75c 100644 --- a/packages/builder/index.html +++ b/packages/builder/index.html @@ -5,9 +5,10 @@ Budibase - - + + + + diff --git a/packages/builder/package.json b/packages/builder/package.json index 73bc003343..a2567dc638 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -70,6 +70,7 @@ "@codemirror/state": "^6.2.0", "@codemirror/theme-one-dark": "^6.1.2", "@codemirror/view": "^6.11.2", + "@fontsource/source-sans-pro": "^5.0.3", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", @@ -122,6 +123,7 @@ "tsconfig-paths": "4.0.0", "typescript": "4.7.3", "vite": "^3.0.8", + "vite-plugin-static-copy": "^0.16.0", "vitest": "^0.29.2" }, "nx": { diff --git a/packages/builder/public/bblogo.png b/packages/builder/public/bblogo.png new file mode 100644 index 0000000000..8c89c12f19 Binary files /dev/null and b/packages/builder/public/bblogo.png differ diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 7f83b2b464..d0414b5733 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -74,6 +74,7 @@ const INITIAL_FRONTEND_STATE = { propertyFocus: null, builderSidePanel: false, hasLock: true, + showPreview: false, // URL params selectedScreenId: null, diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte index c5e4eaf61f..96dc8f4686 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte @@ -12,7 +12,7 @@ import { automationStore, selectedAutomation } from "builderStore" import { admin, licensing } from "stores/portal" import { externalActions } from "./ExternalActions" - import { TriggerStepID } from "constants/backend/automations" + import { TriggerStepID, ActionStepID } from "constants/backend/automations" import { checkForCollectStep } from "builderStore/utils" export let blockIdx @@ -149,7 +149,7 @@
{action.name} - {#if isDisabled && !syncAutomationsEnabled} + {#if isDisabled && !syncAutomationsEnabled && action.stepId === ActionStepID.COLLECT}
Business diff --git a/packages/builder/src/components/backend/DataTable/formula.js b/packages/builder/src/components/backend/DataTable/formula.js index 9fd5949178..43e8dc721e 100644 --- a/packages/builder/src/components/backend/DataTable/formula.js +++ b/packages/builder/src/components/backend/DataTable/formula.js @@ -76,6 +76,10 @@ export function getBindings({ // will be replaced by the main array binding readableBinding: label, runtimeBinding: binding, + display: { + name: label, + type: field.name === FIELDS.LINK.name ? "Array" : field.name, + }, }) } return bindings diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte index fbc38e2daa..1d84dbbe39 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte @@ -57,6 +57,12 @@ } async function saveDatasource() { + if (integration.features?.[DatasourceFeature.CONNECTION_CHECKING]) { + const valid = await validateConfig() + if (!valid) { + return false + } + } try { if (!datasource.name) { datasource.name = name diff --git a/packages/builder/src/components/commandPalette/CommandPalette.svelte b/packages/builder/src/components/commandPalette/CommandPalette.svelte index ae946dc10c..3a369446a3 100644 --- a/packages/builder/src/components/commandPalette/CommandPalette.svelte +++ b/packages/builder/src/components/commandPalette/CommandPalette.svelte @@ -69,7 +69,7 @@ name: "App", description: "", icon: "Play", - action: () => window.open(`/${$store.appId}`), + action: () => store.update(state => ({ ...state, showPreview: true })), }, { type: "Preview", diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 3843eabf45..c95988e90c 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -19,7 +19,7 @@ readableToRuntimeBinding, runtimeToReadableBinding, } from "builderStore/dataBinding" - import { store } from "builderStore" + import { convertToJS } from "@budibase/string-templates" import { admin } from "stores/portal" import CodeEditor from "../CodeEditor/CodeEditor.svelte" @@ -339,25 +339,28 @@ {/if}
- - + {#if drawerActions?.hide} + + {/if} + {#if bindingDrawerActions?.save} + + {/if}
diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 0f709fd262..50e7c66682 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -36,7 +36,7 @@ .map(([name, categoryBindings]) => ({ name, bindings: categoryBindings?.filter(binding => { - return binding.readableBinding.match(searchRgx) + return !search || binding.readableBinding.match(searchRgx) }), })) .filter(category => { @@ -46,7 +46,11 @@ ) }) $: filteredHelpers = helpers?.filter(helper => { - return helper.label.match(searchRgx) || helper.description.match(searchRgx) + return ( + !search || + helper.label.match(searchRgx) || + helper.description.match(searchRgx) + ) }) const getHelperExample = (helper, js) => { @@ -124,9 +128,6 @@ { - if (!search) { - return - } search = null }} class:searching={search} diff --git a/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte b/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte index bb01d557b8..55f7866453 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableCombobox.svelte @@ -76,7 +76,7 @@ {/if}
- + Add the objects on the left to enrich your text. diff --git a/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte b/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte index f41b66bd77..7fbbf75a30 100644 --- a/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte +++ b/packages/builder/src/components/common/bindings/DrawerBindableInput.svelte @@ -5,8 +5,6 @@ runtimeToReadableBinding, } from "builderStore/dataBinding" - import { store } from "builderStore" - import ClientBindingPanel from "components/common/bindings/ClientBindingPanel.svelte" import { createEventDispatcher, setContext } from "svelte" import { isJSBinding } from "@budibase/string-templates" @@ -36,7 +34,6 @@ const saveBinding = () => { onChange(tempValue) - store.actions.settings.propertyFocus(null) onBlur() bindingDrawer.hide() } @@ -70,7 +67,6 @@
{ - store.actions.settings.propertyFocus(key) bindingDrawer.show() }} > diff --git a/packages/builder/src/components/deploy/AppActions.svelte b/packages/builder/src/components/deploy/AppActions.svelte index 9813237317..a85eb5a154 100644 --- a/packages/builder/src/components/deploy/AppActions.svelte +++ b/packages/builder/src/components/deploy/AppActions.svelte @@ -62,7 +62,10 @@ } const previewApp = () => { - window.open(`/${application}`) + store.update(state => ({ + ...state, + showPreview: true, + })) } const viewApp = () => { diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index daa3f29592..57601d47ca 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -73,10 +73,6 @@ if (highlighted) { store.actions.settings.highlight(null) } - // To fix focus 'affect' when property is target of a drawer other actions in the builder. - if (propertyFocus) { - store.actions.settings.propertyFocus(null) - } }) diff --git a/packages/builder/src/components/integration/QueryEditor.svelte b/packages/builder/src/components/integration/QueryEditor.svelte index f543438b9c..51fdb4879e 100644 --- a/packages/builder/src/components/integration/QueryEditor.svelte +++ b/packages/builder/src/components/integration/QueryEditor.svelte @@ -186,7 +186,6 @@ } div :global(.CodeMirror) { - width: var(--code-mirror-width) !important; height: var(--code-mirror-height) !important; border-radius: var(--border-radius-s); font-family: var(--font-mono); diff --git a/packages/builder/src/components/start/CreateAppModal.svelte b/packages/builder/src/components/start/CreateAppModal.svelte index 9e055cd798..cf42668add 100644 --- a/packages/builder/src/components/start/CreateAppModal.svelte +++ b/packages/builder/src/components/start/CreateAppModal.svelte @@ -11,6 +11,7 @@ import TemplateCard from "components/common/TemplateCard.svelte" import createFromScratchScreen from "builderStore/store/screenTemplates/createFromScratchScreen" import { Roles } from "constants/backend" + import { lowercase } from "helpers" export let template @@ -19,6 +20,7 @@ const values = writable({ name: "", url: null }) const validation = createValidationStore() + const encryptionValidation = createValidationStore() $: { const { url } = $values @@ -27,8 +29,11 @@ ...$values, url: url?.[0] === "/" ? url.substring(1, url.length) : url, }) + encryptionValidation.check({ ...$values }) } + $: encryptedFile = $values.file?.name?.endsWith(".enc.tar.gz") + onMount(async () => { const lastChar = $auth.user?.firstName ? $auth.user?.firstName[$auth.user?.firstName.length - 1] @@ -87,6 +92,9 @@ appValidation.name(validation, { apps: applications }) appValidation.url(validation, { apps: applications }) appValidation.file(validation, { template }) + + encryptionValidation.addValidatorType("encryptionPassword", "text", true) + // init validation const { url } = $values validation.check({ @@ -110,6 +118,9 @@ data.append("templateName", template.name) data.append("templateKey", template.key) data.append("templateFile", $values.file) + if ($values.encryptionPassword?.trim()) { + data.append("encryptionPassword", $values.encryptionPassword.trim()) + } } // Create App @@ -143,67 +154,119 @@ $goto(`/builder/app/${createdApp.instance._id}`) } catch (error) { creating = false - console.error(error) - notifications.error("Error creating app") + throw error } } + + const Step = { CONFIG: "config", SET_PASSWORD: "set_password" } + let currentStep = Step.CONFIG + $: stepConfig = { + [Step.CONFIG]: { + title: "Create your app", + confirmText: template?.fromFile ? "Import app" : "Create app", + onConfirm: async () => { + if (encryptedFile) { + currentStep = Step.SET_PASSWORD + return false + } else { + try { + await createNewApp() + } catch (error) { + notifications.error("Error creating app") + } + } + }, + isValid: $validation.valid, + }, + [Step.SET_PASSWORD]: { + title: "Provide the export password", + confirmText: "Import app", + onConfirm: async () => { + try { + await createNewApp() + } catch (e) { + let message = "Error creating app" + if (e.message) { + message += `: ${lowercase(e.message)}` + } + notifications.error(message) + return false + } + }, + isValid: $encryptionValidation.valid, + }, + } - {#if template && !template?.fromFile} - - {/if} - {#if template?.fromFile} - { - $values.file = e.detail?.[0] - $validation.touched.file = true - }} - /> - {/if} - ($validation.touched.name = true)} - on:change={nameToUrl($values.name)} - label="Name" - placeholder={defaultAppName} - /> - - ($validation.touched.url = true)} - on:change={tidyUrl($values.url)} - label="URL" - placeholder={$values.url - ? $values.url - : `/${resolveAppUrl(template, $values.name)}`} - /> - {#if $values.url && $values.url !== "" && !$validation.errors.url} -
- {appUrl} -
+ {#if currentStep === Step.CONFIG} + {#if template && !template?.fromFile} + {/if} -
+ {#if template?.fromFile} + { + $values.file = e.detail?.[0] + $validation.touched.file = true + }} + /> + {/if} + ($validation.touched.name = true)} + on:change={nameToUrl($values.name)} + label="Name" + placeholder={defaultAppName} + /> + + ($validation.touched.url = true)} + on:change={tidyUrl($values.url)} + label="URL" + placeholder={$values.url + ? $values.url + : `/${resolveAppUrl(template, $values.name)}`} + /> + {#if $values.url && $values.url !== "" && !$validation.errors.url} +
+ {appUrl} +
+ {/if} +
+ {/if} + {#if currentStep === Step.SET_PASSWORD} + ($encryptionValidation.touched.encryptionPassword = true)} + error={$encryptionValidation.touched.encryptionPassword && + $encryptionValidation.errors.encryptionPassword} + /> + {/if}
diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index f13aa3dab6..ee3f6e877e 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -24,6 +24,7 @@ import BuilderSidePanel from "./_components/BuilderSidePanel.svelte" import UserAvatars from "./_components/UserAvatars.svelte" import { TOUR_KEYS, TOURS } from "components/portal/onboarding/tours.js" + import PreviewOverlay from "./_components/PreviewOverlay.svelte" export let application @@ -140,7 +141,7 @@ {/if} -
+
{#if $store.initialised}
@@ -230,6 +231,10 @@ {/await}
+{#if $store.showPreview} + +{/if} + @@ -248,6 +253,10 @@ width: 100%; display: flex; flex-direction: column; + transition: filter 260ms ease-out; + } + .root.blur { + filter: blur(8px); } .top-nav { diff --git a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/index.svelte index a853fcea0c..2c60d8160d 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/datasource/[datasourceId]/index.svelte @@ -22,6 +22,7 @@ import ImportRestQueriesModal from "components/backend/DatasourceNavigator/modals/ImportRestQueriesModal.svelte" import { API } from "api" import { DatasourceFeature } from "@budibase/types" + import Spinner from "components/common/Spinner.svelte" const querySchema = { name: {}, @@ -33,6 +34,7 @@ let isValid = true let integration, baseDatasource, datasource let queryList + let loading = false $: baseDatasource = $datasources.selected $: queryList = $queries.list.filter( @@ -65,9 +67,11 @@ } const saveDatasource = async () => { - if (integration.features[DatasourceFeature.CONNECTION_CHECKING]) { + loading = true + if (integration.features?.[DatasourceFeature.CONNECTION_CHECKING]) { const valid = await validateConfig() if (!valid) { + loading = false return false } } @@ -82,6 +86,8 @@ baseDatasource = cloneDeep(datasource) } catch (err) { notifications.error(`Error saving datasource: ${err}`) + } finally { + loading = false } } @@ -119,8 +125,17 @@
Configuration -
diff --git a/packages/builder/src/pages/builder/portal/apps/onboarding/_components/ExampleApp.svelte b/packages/builder/src/pages/builder/portal/apps/onboarding/_components/ExampleApp.svelte index 0b290decbf..48221a918d 100644 --- a/packages/builder/src/pages/builder/portal/apps/onboarding/_components/ExampleApp.svelte +++ b/packages/builder/src/pages/builder/portal/apps/onboarding/_components/ExampleApp.svelte @@ -44,7 +44,7 @@
- Budibase Logo + Budibase Logo

{name}

diff --git a/packages/builder/src/pages/builder/portal/users/users/index.svelte b/packages/builder/src/pages/builder/portal/users/users/index.svelte index f23d84f8bc..0aec29c2e2 100644 --- a/packages/builder/src/pages/builder/portal/users/users/index.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/index.svelte @@ -373,7 +373,7 @@ - + + viteStaticCopy({ + targets: [ + { + src: "../../node_modules/@fontsource/source-sans-pro", + dest, + }, + { + src: "../../node_modules/remixicon/fonts/*", + dest, + }, + ], + }) + export default defineConfig(({ mode }) => { const isProduction = mode === "production" const env = loadEnv(mode, process.cwd()) + + // Plugins to only run in dev + const devOnlyPlugins = [ + // Copy fonts to an additional path so that svelte's automatic + // prefixing of the base URL path can still resolve assets + copyFonts("builder/fonts"), + ] + return { test: { setupFiles: ["./vitest.setup.js"], @@ -59,6 +82,8 @@ export default defineConfig(({ mode }) => { ), "process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN), }), + copyFonts("fonts"), + ...(isProduction ? [] : devOnlyPlugins), ], optimizeDeps: { exclude: ["@roxi/routify"], diff --git a/packages/cli/package.json b/packages/cli/package.json index 3bd5ba279c..37c6f68b3f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,7 +49,7 @@ "pouchdb": "7.3.0", "pouchdb-replication-stream": "1.2.9", "randomstring": "1.1.5", - "tar": "6.1.11", + "tar": "6.1.15", "yaml": "^2.1.1" }, "devDependencies": { diff --git a/packages/client/src/api/api.js b/packages/client/src/api/api.js index 8d29d37bd6..b4a137f85d 100644 --- a/packages/client/src/api/api.js +++ b/packages/client/src/api/api.js @@ -1,7 +1,6 @@ import { createAPIClient } from "@budibase/frontend-core" -import { notificationStore } from "../stores/notification.js" import { authStore } from "../stores/auth.js" -import { devToolsStore } from "../stores/devTools.js" +import { notificationStore, devToolsEnabled, devToolsStore } from "../stores/" import { get } from "svelte/store" export const API = createAPIClient({ @@ -25,9 +24,10 @@ export const API = createAPIClient({ } // Add role header - const devToolsState = get(devToolsStore) - if (devToolsState.enabled && devToolsState.role) { - headers["x-budibase-role"] = devToolsState.role + const $devToolsStore = get(devToolsStore) + const $devToolsEnabled = get(devToolsEnabled) + if ($devToolsEnabled && $devToolsStore.role) { + headers["x-budibase-role"] = $devToolsStore.role } }, diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index ce8722bb9b..f9ecb495d1 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -17,6 +17,7 @@ appStore, devToolsStore, environmentStore, + devToolsEnabled, } from "stores" import NotificationDisplay from "components/overlay/NotificationDisplay.svelte" import ConfirmationDisplay from "components/overlay/ConfirmationDisplay.svelte" @@ -47,10 +48,7 @@ let permissionError = false // Determine if we should show devtools or not - $: showDevTools = - !$builderStore.inBuilder && - $devToolsStore.enabled && - !$routeStore.queryParams?.peek + $: showDevTools = $devToolsEnabled && !$routeStore.queryParams?.peek // Handle no matching route $: { @@ -107,6 +105,7 @@ lang="en" dir="ltr" class="spectrum spectrum--medium {$themeStore.baseTheme} {$themeStore.theme}" + class:builder={$builderStore.inBuilder} > @@ -223,12 +222,14 @@ overflow: hidden; height: 100%; width: 100%; - background: transparent; display: flex; flex-direction: row; justify-content: center; align-items: center; } + #spectrum-root.builder { + background: transparent; + } #clip-root { max-width: 100%; diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index 244500f266..ba5bbedd2f 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -180,10 +180,7 @@ {/if}