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..b25360235f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.7.7-alpha.5", + "version": "2.7.21-alpha.2", "npmClient": "yarn", "packages": [ "packages/backend-core", @@ -16,7 +16,6 @@ "packages/worker", "packages/pro/packages/pro" ], - "useWorkspaces": true, "command": { "publish": { "ignoreChanges": [ diff --git a/package.json b/package.json index 56f015f8c0..49ffc5fef7 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,10 @@ "husky": "^8.0.3", "js-yaml": "^4.1.0", "kill-port": "^1.6.1", - "lerna": "7.0.0-alpha.0", + "lerna": "^7.0.1", "madge": "^6.0.0", "minimist": "^1.2.8", - "nx": "^16.2.1", + "nx": "^16.3.2", "prettier": "^2.3.1", "prettier-plugin-svelte": "^2.3.0", "rimraf": "^3.0.2", 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/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/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/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/ExportAppModal.svelte b/packages/builder/src/components/start/ExportAppModal.svelte index 948416b192..4a69aaef74 100644 --- a/packages/builder/src/components/start/ExportAppModal.svelte +++ b/packages/builder/src/components/start/ExportAppModal.svelte @@ -1,27 +1,128 @@ - - - Apps can be exported with or without data that is within internal tables - - select this below. - + + {#if currentStep === Step.CONFIG} + + + + + {#if !encypt} + + {/if} + {/if} + {#if currentStep === Step.SET_PASSWORD} + + {/if} diff --git a/packages/builder/src/helpers/validation/validation.js b/packages/builder/src/helpers/validation/validation.js index db5dfe4430..f64bf56835 100644 --- a/packages/builder/src/helpers/validation/validation.js +++ b/packages/builder/src/helpers/validation/validation.js @@ -6,7 +6,6 @@ export function createValidationStore(initialValue, ...validators) { let touched = false const value = writable(initialValue || "") - const error = derived(value, $v => validate($v, validators)) const touchedStore = derived(value, () => { if (!touched) { touched = true @@ -14,6 +13,10 @@ export function createValidationStore(initialValue, ...validators) { } return touched }) + const error = derived( + [value, touchedStore], + ([$v, $t]) => $t && validate($v, validators) + ) return [value, error, touchedStore] } diff --git a/packages/builder/src/helpers/validation/yup/index.js b/packages/builder/src/helpers/validation/yup/index.js index 20ddaebb1a..b5bdf030a5 100644 --- a/packages/builder/src/helpers/validation/yup/index.js +++ b/packages/builder/src/helpers/validation/yup/index.js @@ -5,6 +5,7 @@ import { notifications } from "@budibase/bbui" export const createValidationStore = () => { const DEFAULT = { + values: {}, errors: {}, touched: {}, valid: false, @@ -33,6 +34,9 @@ export const createValidationStore = () => { case "email": propertyValidator = string().email().nullable() break + case "password": + propertyValidator = string().nullable() + break default: propertyValidator = string().nullable() } @@ -41,9 +45,68 @@ export const createValidationStore = () => { propertyValidator = propertyValidator.required() } + // We want to do this after the possible required validation, to prioritise the required error + switch (type) { + case "password": + propertyValidator = propertyValidator.min(8) + break + } + validator[propertyName] = propertyValidator } + const observe = async (propertyName, value) => { + const values = get(validation).values + let fieldIsValid + if (!values.hasOwnProperty(propertyName)) { + // Initial setup + values[propertyName] = value + return + } + + if (value === values[propertyName]) { + return + } + + const obj = object().shape(validator) + try { + validation.update(store => { + store.errors[propertyName] = null + return store + }) + await obj.validateAt(propertyName, { [propertyName]: value }) + fieldIsValid = true + } catch (error) { + const [fieldError] = error.errors + if (fieldError) { + validation.update(store => { + store.errors[propertyName] = capitalise(fieldError) + store.valid = false + return store + }) + } + } + + if (fieldIsValid) { + // Validate the rest of the fields + try { + await obj.validate( + { ...values, [propertyName]: value }, + { abortEarly: false } + ) + validation.update(store => { + store.valid = true + return store + }) + } catch { + validation.update(store => { + store.valid = false + return store + }) + } + } + } + const check = async values => { const obj = object().shape(validator) // clear the previous errors @@ -87,5 +150,6 @@ export const createValidationStore = () => { check, addValidator, addValidatorType, + observe, } } diff --git a/packages/builder/src/pages/builder/Branding.svelte b/packages/builder/src/pages/builder/Branding.svelte index 142473abb8..e51ca1c70d 100644 --- a/packages/builder/src/pages/builder/Branding.svelte +++ b/packages/builder/src/pages/builder/Branding.svelte @@ -8,7 +8,7 @@ $: platformTitle = !$auth.user && platformTitleText ? platformTitleText : "Budibase" - $: faviconUrl = $organisation.faviconUrl || "https://i.imgur.com/Xhdt1YP.png" + $: faviconUrl = $organisation.faviconUrl || "/builder/bblogo.png" onMount(async () => { await organisation.init() @@ -27,6 +27,6 @@ {:else} - + {/if} 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 @@ - + { ), "process.env.SENTRY_DSN": JSON.stringify(process.env.SENTRY_DSN), }), + viteStaticCopy({ + targets: [ + { + src: "../../node_modules/@fontsource/source-sans-pro", + dest: "fonts", + }, + { + src: "../../node_modules/remixicon/fonts/*", + dest: "fonts", + }, + ], + }), ], 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/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}