diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 1a2e74f863..d095e3c2a4 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -56,7 +56,6 @@ jobs: run: yarn install:pro $BRANCH $BASE_BRANCH - run: yarn - run: yarn bootstrap - - run: yarn build:client - run: yarn test - uses: codecov/codecov-action@v3 with: @@ -78,28 +77,28 @@ jobs: - run: yarn bootstrap - run: yarn test:pro - integration-test: - runs-on: ubuntu-latest - services: - couchdb: - image: ibmcom/couchdb3 - env: - COUCHDB_PASSWORD: budibase - COUCHDB_USER: budibase - ports: - - 4567:5984 - steps: - - uses: actions/checkout@v2 - - name: Use Node.js 14.x - uses: actions/setup-node@v1 - with: - node-version: 14.x - - name: Install Pro - run: yarn install:pro $BRANCH $BASE_BRANCH - - run: yarn - - run: yarn bootstrap - - run: yarn build - - run: | - cd qa-core - yarn - yarn api:test:ci +# integration-test: +# runs-on: ubuntu-latest +# services: +# couchdb: +# image: ibmcom/couchdb3 +# env: +# COUCHDB_PASSWORD: budibase +# COUCHDB_USER: budibase +# ports: +# - 4567:5984 +# steps: +# - uses: actions/checkout@v2 +# - name: Use Node.js 14.x +# uses: actions/setup-node@v1 +# with: +# node-version: 14.x +# - name: Install Pro +# run: yarn install:pro $BRANCH $BASE_BRANCH +# - run: yarn +# - run: yarn bootstrap +# - run: yarn build +# - run: | +# cd qa-core +# yarn +# yarn api:test:ci diff --git a/.github/workflows/deploy-preprod.yml b/.github/workflows/deploy-preprod.yml index a1eee2c465..57e2504ded 100644 --- a/.github/workflows/deploy-preprod.yml +++ b/.github/workflows/deploy-preprod.yml @@ -17,6 +17,7 @@ jobs: id: version run: | if [ -z "${{ github.event.inputs.version }}" ]; then + git pull release_version=$(cat lerna.json | jq -r '.version') else release_version=${{ github.event.inputs.version }} diff --git a/.github/workflows/release-master.yml b/.github/workflows/release-master.yml index 20a48f5802..41af142bfc 100644 --- a/.github/workflows/release-master.yml +++ b/.github/workflows/release-master.yml @@ -134,6 +134,7 @@ jobs: - name: Get the latest budibase release version id: version run: | + git pull release_version=$(cat lerna.json | jq -r '.version') echo "RELEASE_VERSION=$release_version" >> $GITHUB_ENV diff --git a/charts/budibase/templates/app-service-deployment.yaml b/charts/budibase/templates/app-service-deployment.yaml index 9ff7dc1ddc..df4070806f 100644 --- a/charts/budibase/templates/app-service-deployment.yaml +++ b/charts/budibase/templates/app-service-deployment.yaml @@ -212,11 +212,24 @@ spec: image: budibase/apps:{{ .Values.globals.appVersion }} imagePullPolicy: Always livenessProbe: + httpGet: + path: /health + port: {{ .Values.services.apps.port }} + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 3 + readinessProbe: httpGet: path: /health port: {{ .Values.services.apps.port }} initialDelaySeconds: 5 periodSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 3 + name: bbapps ports: - containerPort: {{ .Values.services.apps.port }} diff --git a/charts/budibase/templates/worker-service-deployment.yaml b/charts/budibase/templates/worker-service-deployment.yaml index f998e5dfb9..f48d19689b 100644 --- a/charts/budibase/templates/worker-service-deployment.yaml +++ b/charts/budibase/templates/worker-service-deployment.yaml @@ -202,11 +202,23 @@ spec: image: budibase/worker:{{ .Values.globals.appVersion }} imagePullPolicy: Always livenessProbe: + httpGet: + path: /health + port: {{ .Values.services.worker.port }} + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 3 + readinessProbe: httpGet: path: /health port: {{ .Values.services.worker.port }} initialDelaySeconds: 5 periodSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + timeoutSeconds: 3 name: bbworker ports: - containerPort: {{ .Values.services.worker.port }} diff --git a/charts/budibase/values.yaml b/charts/budibase/values.yaml index ed4ff014a9..ccbbf9878e 100644 --- a/charts/budibase/values.yaml +++ b/charts/budibase/values.yaml @@ -343,6 +343,7 @@ couchdb: ## Configure liveness and readiness probe values ## Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes + # FOR COUCHDB livenessProbe: enabled: true failureThreshold: 3 diff --git a/lerna.json b/lerna.json index 7575b73237..7db5c851c2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.4.27-alpha.8", + "version": "2.4.43", "npmClient": "yarn", "packages": [ "packages/*" diff --git a/package.json b/package.json index 592d389dec..1ae713f96c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "setup": "node ./hosting/scripts/setup.js && yarn && yarn bootstrap && yarn build && yarn dev", "bootstrap": "lerna bootstrap && lerna link && ./scripts/link-dependencies.sh", "build": "lerna run build", - "build:client": "lerna run build --ignore @budibase/backend-core --ignore @budibase/worker --ignore @budibase/server --ignore @budibase/builder --ignore @budibase/cli --ignore @budibase/sdk", "build:dev": "lerna run prebuild && tsc --build --watch --preserveWatchOutput", "build:backend": "lerna run build --ignore @budibase/client --ignore @budibase/bbui --ignore @budibase/builder --ignore @budibase/cli", "build:sdk": "lerna run build:sdk", @@ -45,7 +44,7 @@ "dev": "yarn run kill-all && lerna link && lerna run --parallel dev:builder --concurrency 1", "dev:noserver": "yarn run kill-builder && lerna link && lerna run dev:stack:up && lerna run --parallel dev:builder --concurrency 1 --ignore @budibase/backend-core --ignore @budibase/server --ignore @budibase/worker", "dev:server": "yarn run kill-server && lerna run --parallel dev:builder --concurrency 1 --scope @budibase/backend-core --scope @budibase/worker --scope @budibase/server", - "test": "lerna run test", + "test": "lerna run test --stream", "test:pro": "bash scripts/pro/test.sh", "lint:eslint": "eslint packages && eslint qa-core", "lint:prettier": "prettier --check \"packages/**/*.{js,ts,svelte}\" && prettier --write \"examples/**/*.{js,ts,svelte}\" && prettier --check \"qa-core/**/*.{js,ts,svelte}\"", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index af82a2dad8..fcc5acd8d3 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/backend-core", - "version": "2.4.27-alpha.8", + "version": "2.4.43", "description": "Budibase backend core libraries used in server and worker", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -24,7 +24,7 @@ "dependencies": { "@budibase/nano": "10.1.2", "@budibase/pouchdb-replication-stream": "1.2.10", - "@budibase/types": "2.4.27-alpha.8", + "@budibase/types": "^2.4.43", "@shopify/jest-koa-mocks": "5.0.1", "@techpass/passport-openidconnect": "0.3.2", "aws-cloudfront-sign": "2.2.0", diff --git a/packages/backend-core/scripts/test.sh b/packages/backend-core/scripts/test.sh index 2965134399..3d8240e65a 100644 --- a/packages/backend-core/scripts/test.sh +++ b/packages/backend-core/scripts/test.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -e if [[ -n $CI ]] then @@ -7,6 +8,6 @@ then jest --coverage --runInBand --forceExit else # --maxWorkers performs better in development - echo "jest --coverage" - jest --coverage -fi + echo "jest --coverage --forceExit" + jest --coverage --forceExit +fi \ No newline at end of file diff --git a/packages/backend-core/src/auth/auth.ts b/packages/backend-core/src/auth/auth.ts index 26c7cd4e26..fb2fd2cf51 100644 --- a/packages/backend-core/src/auth/auth.ts +++ b/packages/backend-core/src/auth/auth.ts @@ -199,7 +199,6 @@ export async function platformLogout(opts: PlatformLogoutOpts) { } else { // clear cookies clearCookie(ctx, Cookie.Auth) - clearCookie(ctx, Cookie.CurrentApp) } const sessionIds = sessions.map(({ sessionId }) => sessionId) diff --git a/packages/backend-core/src/blacklist/blacklist.ts b/packages/backend-core/src/blacklist/blacklist.ts new file mode 100644 index 0000000000..1fbb4683f9 --- /dev/null +++ b/packages/backend-core/src/blacklist/blacklist.ts @@ -0,0 +1,54 @@ +import dns from "dns" +import net from "net" +import env from "../environment" +import { promisify } from "util" + +let blackListArray: string[] | undefined +const performLookup = promisify(dns.lookup) + +async function lookup(address: string): Promise { + if (!net.isIP(address)) { + // need this for URL parsing simply + if (!address.startsWith("http")) { + address = `https://${address}` + } + address = new URL(address).hostname + } + const addresses = await performLookup(address, { + all: true, + }) + return addresses.map(addr => addr.address) +} + +export async function refreshBlacklist() { + const blacklist = env.BLACKLIST_IPS + const list = blacklist?.split(",") || [] + let final: string[] = [] + for (let addr of list) { + const trimmed = addr.trim() + if (!net.isIP(trimmed)) { + const addresses = await lookup(trimmed) + final = final.concat(addresses) + } else { + final.push(trimmed) + } + } + blackListArray = final +} + +export async function isBlacklisted(address: string): Promise { + if (!blackListArray) { + await refreshBlacklist() + } + if (blackListArray?.length === 0) { + return false + } + // no need for DNS + let ips: string[] + if (!net.isIP(address)) { + ips = await lookup(address) + } else { + ips = [address] + } + return !!blackListArray?.find(addr => ips.includes(addr)) +} diff --git a/packages/backend-core/src/blacklist/index.ts b/packages/backend-core/src/blacklist/index.ts new file mode 100644 index 0000000000..b5123eed3e --- /dev/null +++ b/packages/backend-core/src/blacklist/index.ts @@ -0,0 +1 @@ +export * from "./blacklist" diff --git a/packages/backend-core/src/blacklist/tests/blacklist.spec.ts b/packages/backend-core/src/blacklist/tests/blacklist.spec.ts new file mode 100644 index 0000000000..23a8dd1454 --- /dev/null +++ b/packages/backend-core/src/blacklist/tests/blacklist.spec.ts @@ -0,0 +1,46 @@ +import { refreshBlacklist, isBlacklisted } from ".." +import env from "../../environment" + +describe("blacklist", () => { + beforeAll(async () => { + env._set( + "BLACKLIST_IPS", + "www.google.com,192.168.1.1, 1.1.1.1,2.2.2.2/something" + ) + await refreshBlacklist() + }) + + it("should blacklist 192.168.1.1", async () => { + expect(await isBlacklisted("192.168.1.1")).toBe(true) + }) + + it("should allow 192.168.1.2", async () => { + expect(await isBlacklisted("192.168.1.2")).toBe(false) + }) + + it("should blacklist www.google.com", async () => { + expect(await isBlacklisted("www.google.com")).toBe(true) + }) + + it("should handle a complex domain", async () => { + expect( + await isBlacklisted("https://www.google.com/derp/?something=1") + ).toBe(true) + }) + + it("should allow www.microsoft.com", async () => { + expect(await isBlacklisted("www.microsoft.com")).toBe(false) + }) + + it("should blacklist an IP that needed trimming", async () => { + expect(await isBlacklisted("1.1.1.1")).toBe(true) + }) + + it("should blacklist 1.1.1.1/something", async () => { + expect(await isBlacklisted("1.1.1.1/something")).toBe(true) + }) + + it("should blacklist 2.2.2.2", async () => { + expect(await isBlacklisted("2.2.2.2")).toBe(true) + }) +}) diff --git a/packages/backend-core/src/configs/configs.ts b/packages/backend-core/src/configs/configs.ts index b461497747..c279babb71 100644 --- a/packages/backend-core/src/configs/configs.ts +++ b/packages/backend-core/src/configs/configs.ts @@ -32,8 +32,7 @@ export async function getConfig( const db = context.getGlobalDB() try { // await to catch error - const config = (await db.get(generateConfigID(type))) as T - return config + return (await db.get(generateConfigID(type))) as T } catch (e: any) { if (e.status === 404) { return diff --git a/packages/backend-core/src/configs/tests/configs.spec.ts b/packages/backend-core/src/configs/tests/configs.spec.ts index 079f2ab681..45e56a2581 100644 --- a/packages/backend-core/src/configs/tests/configs.spec.ts +++ b/packages/backend-core/src/configs/tests/configs.spec.ts @@ -1,4 +1,9 @@ -import { DBTestConfiguration, generator, testEnv } from "../../../tests" +import { + DBTestConfiguration, + generator, + testEnv, + structures, +} from "../../../tests" import { ConfigType } from "@budibase/types" import env from "../../environment" import * as configs from "../configs" @@ -113,4 +118,71 @@ describe("configs", () => { }) }) }) + + describe("getGoogleDatasourceConfig", () => { + function setEnvVars() { + env.GOOGLE_CLIENT_SECRET = "test" + env.GOOGLE_CLIENT_ID = "test" + } + + function unsetEnvVars() { + env.GOOGLE_CLIENT_SECRET = undefined + env.GOOGLE_CLIENT_ID = undefined + } + + describe("cloud", () => { + beforeEach(() => { + testEnv.cloudHosted() + }) + + it("returns from env vars", async () => { + await config.doInTenant(async () => { + setEnvVars() + const config = await configs.getGoogleDatasourceConfig() + unsetEnvVars() + + expect(config).toEqual({ + activated: true, + clientID: "test", + clientSecret: "test", + }) + }) + }) + + it("returns undefined when no env vars are configured", async () => { + await config.doInTenant(async () => { + const config = await configs.getGoogleDatasourceConfig() + expect(config).toBeUndefined() + }) + }) + }) + + describe("self host", () => { + beforeEach(() => { + testEnv.selfHosted() + }) + + it("returns from config", async () => { + await config.doInTenant(async () => { + const googleDoc = structures.sso.googleConfigDoc() + await configs.save(googleDoc) + const config = await configs.getGoogleDatasourceConfig() + expect(config).toEqual(googleDoc.config) + }) + }) + + it("falls back to env vars when config is disabled", async () => { + await config.doInTenant(async () => { + setEnvVars() + const config = await configs.getGoogleDatasourceConfig() + unsetEnvVars() + expect(config).toEqual({ + activated: true, + clientID: "test", + clientSecret: "test", + }) + }) + }) + }) + }) }) diff --git a/packages/backend-core/src/constants/misc.ts b/packages/backend-core/src/constants/misc.ts index e25c90575f..15cec7a6b9 100644 --- a/packages/backend-core/src/constants/misc.ts +++ b/packages/backend-core/src/constants/misc.ts @@ -4,7 +4,6 @@ export enum UserStatus { } export enum Cookie { - CurrentApp = "budibase:currentapp", Auth = "budibase:auth", Init = "budibase:init", ACCOUNT_RETURN_URL = "budibase:account:returnurl", diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index f1c96c7fec..5718494fc4 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -104,6 +104,7 @@ const environment = { SMTP_PORT: parseInt(process.env.SMTP_PORT || ""), SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS, DISABLE_JWT_WARNING: process.env.DISABLE_JWT_WARNING, + BLACKLIST_IPS: process.env.BLACKLIST_IPS, /** * Enable to allow an admin user to login using a password. * This can be useful to prevent lockout when configuring SSO. diff --git a/packages/backend-core/src/index.ts b/packages/backend-core/src/index.ts index a6d5423756..30072196ba 100644 --- a/packages/backend-core/src/index.ts +++ b/packages/backend-core/src/index.ts @@ -26,6 +26,7 @@ export * as utils from "./utils" export * as errors from "./errors" export * as timers from "./timers" export { default as env } from "./environment" +export * as blacklist from "./blacklist" export { SearchParams } from "./db" // Add context to tenancy for backwards compatibility // only do this for external usages to prevent internal diff --git a/packages/backend-core/src/middleware/passport/datasource/google.ts b/packages/backend-core/src/middleware/passport/datasource/google.ts index 32451cb8d2..6fd4e9ff32 100644 --- a/packages/backend-core/src/middleware/passport/datasource/google.ts +++ b/packages/backend-core/src/middleware/passport/datasource/google.ts @@ -78,17 +78,23 @@ export async function postAuth( ), { successRedirect: "/", failureRedirect: "/error" }, async (err: any, tokens: string[]) => { + const baseUrl = `/builder/app/${authStateCookie.appId}/data` // update the DB for the datasource with all the user info await doWithDB(authStateCookie.appId, async (db: Database) => { - const datasource = await db.get(authStateCookie.datasourceId) + let datasource + try { + datasource = await db.get(authStateCookie.datasourceId) + } catch (err: any) { + if (err.status === 404) { + ctx.redirect(baseUrl) + } + } if (!datasource.config) { datasource.config = {} } datasource.config.auth = { type: "google", ...tokens } await db.put(datasource) - ctx.redirect( - `/builder/app/${authStateCookie.appId}/data/datasource/${authStateCookie.datasourceId}` - ) + ctx.redirect(`${baseUrl}/datasource/${authStateCookie.datasourceId}`) }) } )(ctx, next) diff --git a/packages/backend-core/tests/utilities/structures/index.ts b/packages/backend-core/tests/utilities/structures/index.ts index ca77f476d0..ff2e5b147f 100644 --- a/packages/backend-core/tests/utilities/structures/index.ts +++ b/packages/backend-core/tests/utilities/structures/index.ts @@ -8,4 +8,5 @@ export * as plugins from "./plugins" export * as sso from "./sso" export * as tenant from "./tenants" export * as users from "./users" +export * as userGroups from "./userGroups" export { generator } from "./generator" diff --git a/packages/backend-core/tests/utilities/structures/sso.ts b/packages/backend-core/tests/utilities/structures/sso.ts index 7413fa3c09..9da9c82223 100644 --- a/packages/backend-core/tests/utilities/structures/sso.ts +++ b/packages/backend-core/tests/utilities/structures/sso.ts @@ -1,4 +1,6 @@ import { + ConfigType, + GoogleConfig, GoogleInnerConfig, JwtClaims, OAuth2, @@ -10,10 +12,10 @@ import { User, } from "@budibase/types" import { generator } from "./generator" -import { uuid, email } from "./common" +import { email, uuid } from "./common" import * as shared from "./shared" -import _ from "lodash" import { user } from "./shared" +import _ from "lodash" export function OAuth(): OAuth2 { return { @@ -107,3 +109,11 @@ export function googleConfig(): GoogleInnerConfig { clientSecret: generator.string(), } } + +export function googleConfigDoc(): GoogleConfig { + return { + _id: "config_google", + type: ConfigType.GOOGLE, + config: googleConfig(), + } +} diff --git a/packages/backend-core/tests/utilities/structures/userGroups.ts b/packages/backend-core/tests/utilities/structures/userGroups.ts new file mode 100644 index 0000000000..4dc870a00a --- /dev/null +++ b/packages/backend-core/tests/utilities/structures/userGroups.ts @@ -0,0 +1,10 @@ +import { UserGroup } from "@budibase/types" +import { generator } from "./generator" + +export function userGroup(): UserGroup { + return { + name: generator.word(), + icon: generator.word(), + color: generator.word(), + } +} diff --git a/packages/bbui/package.json b/packages/bbui/package.json index 2e3a5cf3f2..c4cf512fce 100644 --- a/packages/bbui/package.json +++ b/packages/bbui/package.json @@ -1,7 +1,7 @@ { "name": "@budibase/bbui", "description": "A UI solution used in the different Budibase projects.", - "version": "2.4.27-alpha.8", + "version": "2.4.43", "license": "MPL-2.0", "svelte": "src/index.js", "module": "dist/bbui.es.js", @@ -38,8 +38,8 @@ ], "dependencies": { "@adobe/spectrum-css-workflow-icons": "1.2.1", - "@budibase/shared-core": "2.4.27-alpha.8", - "@budibase/string-templates": "2.4.27-alpha.8", + "@budibase/shared-core": "^2.4.43", + "@budibase/string-templates": "^2.4.43", "@spectrum-css/accordion": "3.0.24", "@spectrum-css/actionbutton": "1.0.1", "@spectrum-css/actiongroup": "1.0.1", diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 43729cd794..932236bc0c 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -7,7 +7,7 @@ export let title export let fillWidth export let left = "314px" - export let width = "calc(100% - 576px)" + export let width = "calc(100% - 626px)" let visible = false diff --git a/packages/bbui/src/Form/Core/File.svelte b/packages/bbui/src/Form/Core/File.svelte new file mode 100644 index 0000000000..618cccd941 --- /dev/null +++ b/packages/bbui/src/Form/Core/File.svelte @@ -0,0 +1,115 @@ + + + + +
+ {#if value} +
+ {#if previewUrl} + + {/if} +
{value.name}
+ {#if value.size} +
+ {#if value.size <= BYTES_IN_MB} + {`${value.size / BYTES_IN_KB} KB`} + {:else} + {`${value.size / BYTES_IN_MB} MB`} + {/if} +
+ {/if} + {#if !disabled || (allowClear === true && disabled)} +
+ +
+ {/if} +
+ {/if} + {title} +
+ + diff --git a/packages/bbui/src/Form/Core/Select.svelte b/packages/bbui/src/Form/Core/Select.svelte index af45c1d9ff..2fad886910 100644 --- a/packages/bbui/src/Form/Core/Select.svelte +++ b/packages/bbui/src/Form/Core/Select.svelte @@ -42,9 +42,13 @@ } const getFieldText = (value, options, placeholder) => { - // Always use placeholder if no value if (value == null || value === "") { - return placeholder !== false ? "Choose an option" : "" + // Explicit false means use no placeholder and allow an empty fields + if (placeholder === false) { + return "" + } + // Otherwise we use the placeholder if possible + return placeholder || "Choose an option" } return getFieldAttribute(getOptionLabel, value, options) diff --git a/packages/bbui/src/Form/Core/index.js b/packages/bbui/src/Form/Core/index.js index 7c81cfd70b..b0edf52748 100644 --- a/packages/bbui/src/Form/Core/index.js +++ b/packages/bbui/src/Form/Core/index.js @@ -13,3 +13,4 @@ export { default as CoreDropzone } from "./Dropzone.svelte" export { default as CoreStepper } from "./Stepper.svelte" export { default as CoreRichTextField } from "./RichTextField.svelte" export { default as CoreSlider } from "./Slider.svelte" +export { default as CoreFile } from "./File.svelte" diff --git a/packages/bbui/src/Form/File.svelte b/packages/bbui/src/Form/File.svelte new file mode 100644 index 0000000000..03cacea814 --- /dev/null +++ b/packages/bbui/src/Form/File.svelte @@ -0,0 +1,37 @@ + + + + + diff --git a/packages/bbui/src/index.js b/packages/bbui/src/index.js index 0c18156052..d26b938dd5 100644 --- a/packages/bbui/src/index.js +++ b/packages/bbui/src/index.js @@ -77,6 +77,7 @@ export { default as IconSideNav } from "./IconSideNav/IconSideNav.svelte" export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte" export { default as Slider } from "./Form/Slider.svelte" export { default as Accordion } from "./Accordion/Accordion.svelte" +export { default as File } from "./Form/File.svelte" // Renderers export { default as BoldRenderer } from "./Table/BoldRenderer.svelte" diff --git a/packages/builder/index.html b/packages/builder/index.html index e3383cda39..96abc8e582 100644 --- a/packages/builder/index.html +++ b/packages/builder/index.html @@ -1,17 +1,17 @@ + Budibase - - + + + \ No newline at end of file diff --git a/packages/builder/package.json b/packages/builder/package.json index 7dec7b4e5f..51bce9ab4a 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -1,6 +1,6 @@ { "name": "@budibase/builder", - "version": "2.4.27-alpha.8", + "version": "2.4.43", "license": "GPL-3.0", "private": true, "scripts": { @@ -58,11 +58,11 @@ } }, "dependencies": { - "@budibase/bbui": "2.4.27-alpha.8", - "@budibase/client": "2.4.27-alpha.8", - "@budibase/frontend-core": "2.4.27-alpha.8", - "@budibase/shared-core": "2.4.27-alpha.8", - "@budibase/string-templates": "2.4.27-alpha.8", + "@budibase/bbui": "^2.4.43", + "@budibase/client": "^2.4.43", + "@budibase/frontend-core": "^2.4.43", + "@budibase/shared-core": "^2.4.43", + "@budibase/string-templates": "^2.4.43", "@fortawesome/fontawesome-svg-core": "^6.2.1", "@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", diff --git a/packages/builder/src/builderStore/componentUtils.js b/packages/builder/src/builderStore/componentUtils.js index a9425da742..16b972058e 100644 --- a/packages/builder/src/builderStore/componentUtils.js +++ b/packages/builder/src/builderStore/componentUtils.js @@ -163,7 +163,12 @@ export const getComponentSettings = componentType => { def.settings ?.filter(setting => setting.section) .forEach(section => { - settings = settings.concat(section.settings || []) + settings = settings.concat( + (section.settings || []).map(setting => ({ + ...setting, + section: section.name, + })) + ) }) } componentSettingCache[componentType] = settings diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 51f88add27..3fc0eb769e 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -22,6 +22,7 @@ import { findComponent, getComponentSettings, makeComponentUnique, + findComponentPath, } from "../componentUtils" import { Helpers } from "@budibase/bbui" import { Utils } from "@budibase/frontend-core" @@ -30,7 +31,12 @@ import { DB_TYPE_INTERNAL, DB_TYPE_EXTERNAL, } from "constants/backend" -import { getSchemaForDatasource } from "builderStore/dataBinding" +import { + buildFormSchema, + getSchemaForDatasource, +} from "builderStore/dataBinding" +import { makePropSafe as safe } from "@budibase/string-templates" +import { getComponentFieldOptions } from "helpers/formFields" const INITIAL_FRONTEND_STATE = { apps: [], @@ -63,17 +69,19 @@ const INITIAL_FRONTEND_STATE = { customTheme: {}, previewDevice: "desktop", highlightedSettingKey: null, + builderSidePanel: false, // URL params selectedScreenId: null, selectedComponentId: null, selectedLayoutId: null, - // onboarding + // Client state + selectedComponentInstance: null, + + // Onboarding onboarding: false, tourNodes: null, - - builderSidePanel: false, } export const getFrontendStore = () => { @@ -262,22 +270,27 @@ export const getFrontendStore = () => { } }, save: async screen => { - /* - Temporarily disabled to accomodate migration issues. - store.actions.screens.validate(screen) - */ - const state = get(store) + // Validate screen structure + // Temporarily disabled to accommodate migration issues + // store.actions.screens.validate(screen) + + // Check screen definition for any component settings which need updated + store.actions.screens.enrichEmptySettings(screen) + + // Save screen const creatingNewScreen = screen._id === undefined const savedScreen = await API.saveScreen(screen) const routesResponse = await API.fetchAppRoutes() - let usedPlugins = state.usedPlugins // 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) usedPlugins = application.usedPlugins || [] } + // Update state store.update(state => { // Update screen object const idx = state.screens.findIndex(x => x._id === savedScreen._id) @@ -298,7 +311,6 @@ export const getFrontendStore = () => { // Update used plugins state.usedPlugins = usedPlugins - return state }) return savedScreen @@ -406,6 +418,17 @@ export const getFrontendStore = () => { } await store.actions.screens.patch(patch, screen._id) }, + enrichEmptySettings: screen => { + // Flatten the recursive component tree + const components = findAllMatchingComponents(screen.props, x => x) + + // Iterate over all components and run checks + components.forEach(component => { + store.actions.components.enrichEmptySettings(component, { + screen, + }) + }) + }, }, preview: { setDevice: device => { @@ -493,65 +516,155 @@ export const getFrontendStore = () => { } return get(store).components[componentName] }, - createInstance: (componentName, presetProps) => { + getDefaultDatasource: () => { + // Ignore users table + const validTables = get(tables).list.filter(x => x._id !== "ta_users") + + // Try to use their own internal table first + let table = validTables.find(table => { + return ( + table.sourceId !== BUDIBASE_INTERNAL_DB_ID && + table.type === DB_TYPE_INTERNAL + ) + }) + if (table) { + return table + } + + // Then try sample data + table = validTables.find(table => { + return ( + table.sourceId === BUDIBASE_INTERNAL_DB_ID && + table.type === DB_TYPE_INTERNAL + ) + }) + if (table) { + return table + } + + // Finally try an external table + return validTables.find(table => table.type === DB_TYPE_EXTERNAL) + }, + enrichEmptySettings: (component, opts) => { + if (!component?._component) { + return + } + const defaultDS = store.actions.components.getDefaultDatasource() + const settings = getComponentSettings(component._component) + const { parent, screen, useDefaultValues } = opts || {} + const treeId = parent?._id || component._id + if (!screen) { + return + } + settings.forEach(setting => { + const value = component[setting.key] + + // Fill empty settings + if (value == null || value === "") { + if (setting.type === "multifield" && setting.selectAllFields) { + // Select all schema fields where required + component[setting.key] = Object.keys(defaultDS?.schema || {}) + } else if ( + (setting.type === "dataSource" || setting.type === "table") && + defaultDS + ) { + // Select default datasource where required + component[setting.key] = { + label: defaultDS.name, + tableId: defaultDS._id, + type: "table", + } + } else if (setting.type === "dataProvider") { + // Pick closest data provider where required + const path = findComponentPath(screen.props, treeId) + const providers = path.filter(component => + component._component?.endsWith("/dataprovider") + ) + if (providers.length) { + const id = providers[providers.length - 1]?._id + component[setting.key] = `{{ literal ${safe(id)} }}` + } + } else if (setting.type.startsWith("field/")) { + // Autofill form field names + // Get all available field names in this form schema + let fieldOptions = getComponentFieldOptions( + screen.props, + treeId, + setting.type, + false + ) + + // Get all currently used fields + const form = findClosestMatchingComponent( + screen.props, + treeId, + x => x._component === "@budibase/standard-components/form" + ) + const usedFields = Object.keys(buildFormSchema(form) || {}) + + // Filter out already used fields + fieldOptions = fieldOptions.filter(x => !usedFields.includes(x)) + + // Set field name and also assume we have a label setting + if (fieldOptions[0]) { + component[setting.key] = fieldOptions[0] + component.label = fieldOptions[0] + } + } else if (useDefaultValues && setting.defaultValue !== undefined) { + // Use default value where required + component[setting.key] = setting.defaultValue + } + } + + // Validate non-empty settings + else { + if (setting.type === "dataProvider") { + // Validate data provider exists, or else clear it + const treeId = parent?._id || component._id + const path = findComponentPath(screen?.props, treeId) + const providers = path.filter(component => + component._component?.endsWith("/dataprovider") + ) + // Validate non-empty values + const valid = providers?.some(dp => value.includes?.(dp._id)) + if (!valid) { + if (providers.length) { + const id = providers[providers.length - 1]?._id + component[setting.key] = `{{ literal ${safe(id)} }}` + } else { + delete component[setting.key] + } + } + } + } + }) + }, + createInstance: (componentName, presetProps, parent) => { const definition = store.actions.components.getDefinition(componentName) if (!definition) { return null } - // Flattened settings - const settings = getComponentSettings(componentName) - - let dataSourceField = settings.find( - setting => setting.type == "dataSource" || setting.type == "table" - ) - - let defaultDatasource - if (dataSourceField) { - const _tables = get(tables) - const filteredTables = _tables.list.filter( - table => table._id != "ta_users" - ) - - const internalTable = filteredTables.find( - table => - table.sourceId === BUDIBASE_INTERNAL_DB_ID && - table.type == DB_TYPE_INTERNAL - ) - - const defaultSourceTable = filteredTables.find( - table => - table.sourceId !== BUDIBASE_INTERNAL_DB_ID && - table.type == DB_TYPE_INTERNAL - ) - - const defaultExternalTable = filteredTables.find( - table => table.type == DB_TYPE_EXTERNAL - ) - - defaultDatasource = - defaultSourceTable || internalTable || defaultExternalTable + // Generate basic component structure + let instance = { + _id: Helpers.uuid(), + _component: definition.component, + _styles: { + normal: {}, + hover: {}, + active: {}, + }, + _instanceName: `New ${definition.friendlyName || definition.name}`, + ...presetProps, } - // Generate default props - let props = { ...presetProps } - settings.forEach(setting => { - if (setting.type === "multifield" && setting.selectAllFields) { - props[setting.key] = Object.keys(defaultDatasource.schema || {}) - } else if (setting.defaultValue !== undefined) { - props[setting.key] = setting.defaultValue - } + // Enrich empty settings + store.actions.components.enrichEmptySettings(instance, { + parent, + screen: get(selectedScreen), + useDefaultValues: true, }) - // Set a default datasource - if (dataSourceField && defaultDatasource) { - props[dataSourceField.key] = { - label: defaultDatasource.name, - tableId: defaultDatasource._id, - type: "table", - } - } - // Add any extra properties the component needs let extras = {} if (definition.hasChildren) { @@ -569,17 +682,8 @@ export const getFrontendStore = () => { extras.step = formSteps.length + 1 extras._instanceName = `Step ${formSteps.length + 1}` } - return { - _id: Helpers.uuid(), - _component: definition.component, - _styles: { - normal: {}, - hover: {}, - active: {}, - }, - _instanceName: `New ${definition.friendlyName || definition.name}`, - ...cloneDeep(props), + ...cloneDeep(instance), ...extras, } }, @@ -587,7 +691,8 @@ export const getFrontendStore = () => { const state = get(store) const componentInstance = store.actions.components.createInstance( componentName, - presetProps + presetProps, + parent ) if (!componentInstance) { return @@ -1123,6 +1228,52 @@ export const getFrontendStore = () => { }) } }, + addParent: async (componentId, parentType) => { + if (!componentId || !parentType) { + return + } + + // Create new parent instance + const newParentDefinition = store.actions.components.createInstance( + parentType, + null, + parent + ) + if (!newParentDefinition) { + return + } + + // Replace component with a version wrapped in a new parent + await store.actions.screens.patch(screen => { + // Get this component definition and parent definition + let definition = findComponent(screen.props, componentId) + let oldParentDefinition = findComponentParent( + screen.props, + componentId + ) + if (!definition || !oldParentDefinition) { + return false + } + + // Replace component with parent + const index = oldParentDefinition._children.findIndex( + component => component._id === componentId + ) + if (index === -1) { + return false + } + oldParentDefinition._children[index] = { + ...newParentDefinition, + _children: [definition], + } + }) + + // Select the new parent + store.update(state => { + state.selectedComponentId = newParentDefinition._id + return state + }) + }, }, links: { save: async (url, title) => { diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index f47fb8a086..eb9c618c24 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -32,8 +32,8 @@ import { getSchemaForTable } from "builderStore/dataBinding" import { Utils } from "@budibase/frontend-core" import { TriggerStepID, ActionStepID } from "constants/backend/automations" - import { cloneDeep } from "lodash/fp" import { onMount } from "svelte" + import { cloneDeep } from "lodash/fp" export let block export let testData @@ -214,8 +214,6 @@ function saveFilters(key) { const filters = LuceneUtils.buildLuceneQuery(tempFilters) const defKey = `${key}-def` - inputData[key] = filters - inputData[defKey] = tempFilters onChange({ detail: filters }, key) // need to store the builder definition in the automation onChange({ detail: tempFilters }, defKey) diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte index c14455b7fc..1080fc7305 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelector.svelte @@ -95,8 +95,11 @@ } const onChange = (e, field, type) => { - value[field] = coerce(e.detail, type) - dispatch("change", value) + let newValue = { + ...value, + [field]: coerce(e.detail, type), + } + dispatch("change", newValue) } const onChangeSetting = (e, field) => { diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 162240e12c..0c5adfc18c 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -136,6 +136,7 @@ const onUpdateColumns = () => { selectedRows = [] fetch.refresh() + tables.fetchTable(id) } // Fetch data whenever rows are modified. Unfortunately we have to lose diff --git a/packages/builder/src/components/backend/DatasourceNavigator/_components/GoogleButton.svelte b/packages/builder/src/components/backend/DatasourceNavigator/_components/GoogleButton.svelte index 4622bd7b19..0cf92dde2b 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/_components/GoogleButton.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/_components/GoogleButton.svelte @@ -5,18 +5,28 @@ export let preAuthStep export let datasource + export let disabled $: tenantId = $auth.tenantId diff --git a/packages/builder/src/helpers/formFields.js b/packages/builder/src/helpers/formFields.js new file mode 100644 index 0000000000..eaf260a9b6 --- /dev/null +++ b/packages/builder/src/helpers/formFields.js @@ -0,0 +1,32 @@ +import { findClosestMatchingComponent } from "builderStore/componentUtils" +import { + getDatasourceForProvider, + getSchemaForDatasource, +} from "builderStore/dataBinding" + +export const getComponentFieldOptions = (asset, id, type, loose = true) => { + const form = findClosestMatchingComponent( + asset, + id, + component => component._component === "@budibase/standard-components/form" + ) + const datasource = getDatasourceForProvider(asset, form) + const schema = getSchemaForDatasource(asset, datasource, { + formSchema: true, + }).schema + + // Get valid types for this field + let types = [type] + if (loose) { + if (type === "field/options" || type === "field/longform") { + // Allow options and longform to be used on string fields as well + types = [type, "field/string"] + } + } + types = types.map(type => type.slice(type.indexOf("/") + 1)) + + // Find fields of valid types + return Object.entries(schema || {}) + .filter(entry => types.includes(entry[1].type)) + .map(entry => entry[0]) +} diff --git a/packages/builder/src/pages/builder/Branding.svelte b/packages/builder/src/pages/builder/Branding.svelte new file mode 100644 index 0000000000..142473abb8 --- /dev/null +++ b/packages/builder/src/pages/builder/Branding.svelte @@ -0,0 +1,32 @@ + + + + + + {platformTitle} + + {#if loaded && !$auth.user && faviconUrl} + + {:else} + + + {/if} + diff --git a/packages/builder/src/pages/builder/_layout.svelte b/packages/builder/src/pages/builder/_layout.svelte index 8d604e8790..b216958045 100644 --- a/packages/builder/src/pages/builder/_layout.svelte +++ b/packages/builder/src/pages/builder/_layout.svelte @@ -4,29 +4,33 @@ import { onMount } from "svelte" import { CookieUtils, Constants } from "@budibase/frontend-core" import { API } from "api" + import Branding from "./Branding.svelte" let loaded = false $: multiTenancyEnabled = $admin.multiTenancy $: hasAdminUser = $admin?.checklist?.adminUser?.checked + $: baseUrl = $admin?.baseUrl $: tenantSet = $auth.tenantSet - $: cloud = $admin.cloud + $: cloud = $admin?.cloud $: user = $auth.user $: useAccountPortal = cloud && !$admin.disableAccountPortal const validateTenantId = async () => { const host = window.location.host - if (host.includes("localhost:")) { + if (host.includes("localhost:") || !baseUrl) { // ignore local dev return } - // e.g. ['tenant', 'budibase', 'app'] vs ['budibase', 'app'] + const mainHost = new URL(baseUrl).host let urlTenantId - const hostParts = host.split(".") - if (hostParts.length > 2) { - urlTenantId = hostParts[0] + // remove the main host part + const hostParts = host.split(mainHost).filter(part => part !== "") + // if there is a part left, it has to be the tenant ID subdomain + if (hostParts.length === 1) { + urlTenantId = hostParts[0].replace(/\./g, "") } if (user && user.tenantId) { @@ -40,13 +44,15 @@ return } - if (user.tenantId !== urlTenantId) { + if (urlTenantId && user.tenantId !== urlTenantId) { // user should not be here - play it safe and log them out try { await auth.logout() await auth.setOrganisation(null) } catch (error) { - // Swallow error and do nothing + console.error( + `Tenant mis-match - "${urlTenantId}" and "${user.tenantId}" - logout` + ) } } } else { @@ -73,7 +79,7 @@ } // Validate tenant if in a multi-tenant env - if (useAccountPortal && multiTenancyEnabled) { + if (multiTenancyEnabled) { await validateTenantId() } } catch (error) { @@ -146,6 +152,9 @@ } + + + {#if loaded} {/if} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte index 9534df5f10..cf109d63b5 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/AppPreview.svelte @@ -220,6 +220,9 @@ } else if (type === "drop-new-component") { const { component, parent, index } = data await store.actions.components.create(component, null, parent, index) + } else if (type === "add-parent-component") { + const { componentId, parentType } = data + await store.actions.components.addParent(componentId, parentType) } else { console.warn(`Client sent unknown event type: ${type}`) } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte index 1e889ffe93..2ff605cc77 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsPanel.svelte @@ -37,7 +37,7 @@ {#if $selectedComponent} {#key $selectedComponent._id} - +
{#each tabs as tab} diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte index 21bed847f5..8fa0af45ed 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ComponentSettingsSection.svelte @@ -117,49 +117,52 @@ {#each sections as section, idx (section.name)} {#if section.visible} - {#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen} - updateSetting({ key: "_instanceName" }, val)} - /> - {/if} - {#each section.settings as setting (setting.key)} - {#if setting.visible} +
+ {#if idx === 0 && !componentInstance._component.endsWith("/layout") && !isScreen} updateSetting(setting, val)} - highlighted={$store.highlightedSettingKey === setting.key} - info={setting.info} - props={{ - // Generic settings - placeholder: setting.placeholder || null, - - // Select settings - options: setting.options || [], - - // Number fields - min: setting.min ?? null, - max: setting.max ?? null, - }} - {bindings} - {componentBindings} - {componentInstance} - {componentDefinition} + control={Input} + label="Name" + key="_instanceName" + value={componentInstance._instanceName} + onChange={val => updateSetting({ key: "_instanceName" }, val)} /> {/if} - {/each} - {#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")} - - {/if} + {#each section.settings as setting (setting.key)} + {#if setting.visible} + updateSetting(setting, val)} + highlighted={$store.highlightedSettingKey === setting.key} + info={setting.info} + props={{ + // Generic settings + placeholder: setting.placeholder || null, + + // Select settings + options: setting.options || [], + + // Number fields + min: setting.min ?? null, + max: setting.max ?? null, + }} + {bindings} + {componentBindings} + {componentInstance} + {componentDefinition} + /> + {/if} + {/each} + {#if idx === 0 && componentDefinition?.component?.endsWith("/fieldgroup")} + + {/if} +
{/if} {/each} @@ -168,3 +171,13 @@ {/if} + + diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ConditionalUIDrawer.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ConditionalUIDrawer.svelte index 855c42173b..db6e66b87b 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ConditionalUIDrawer.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/ConditionalUIDrawer.svelte @@ -62,7 +62,7 @@ type: "text", }) $: settingOptions = settings.map(setting => ({ - label: setting.label, + label: makeLabel(setting), value: setting.key, })) $: conditions.forEach(link => { @@ -71,6 +71,15 @@ } }) + const makeLabel = setting => { + const { section, label } = setting + if (section) { + return label ? `${section} - ${label}` : section + } else { + return label + } + } + const getSettingDefinition = key => { return settings.find(setting => setting.key === key) } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/DesignSection.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/DesignSection.svelte index fa1a357f38..444ded7e1f 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/DesignSection.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/DesignSection.svelte @@ -27,7 +27,6 @@ -
+
{#each properties as prop (`${componentInstance._id}-${prop.key}-${prop.label}`)} -
- updateStyle(prop.key, val)} - props={getControlProps(prop)} - {bindings} - /> -
+ updateStyle(prop.key, val)} + props={getControlProps(prop)} + {bindings} + /> {/each}
diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js index d4912241b3..7c65b6e6ec 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/_components/settings/componentStyles.js @@ -3,7 +3,6 @@ import ColorPicker from "components/design/settings/controls/ColorPicker.svelte" export const margin = { label: "Margin", - columns: "1fr 1fr", settings: [ { label: "Top", @@ -90,7 +89,6 @@ export const margin = { export const padding = { label: "Padding", - columns: "1fr 1fr", settings: [ { label: "Top", @@ -177,7 +175,6 @@ export const padding = { export const size = { label: "Size", - columns: "1fr 1fr", settings: [ { label: "Width", @@ -196,7 +193,6 @@ export const size = { export const background = { label: "Background", - columns: "auto 1fr", settings: [ { label: "Color", @@ -285,7 +281,6 @@ export const background = { export const border = { label: "Border", - columns: "1fr 1fr", settings: [ { label: "Color", diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte index 092e429515..6372ceeed0 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/components/[componentId]/new/_components/NewComponentPanel.svelte @@ -1,22 +1,13 @@ - - - - - {#if loaded} - logo - {/if} - Log in to Budibase - - - {#if loaded && ($organisation.google || $organisation.oidc)} - - - - - {/if} +{#if loaded} + + + + {#if loaded} + logo + {/if} + + {$organisation.loginHeading || "Log in to Budibase"} + + + + {#if loaded && ($organisation.google || $organisation.oidc)} + + + + + {/if} + {#if !$organisation.isSSOEnforced} + + + { + formData = { + ...formData, + username: e.detail, + } + }} + validate={() => { + let fieldError = { + username: !formData.username + ? "Please enter a valid email" + : undefined, + } + errors = handleError({ ...errors, ...fieldError }) + }} + error={errors.username} + /> + { + formData = { + ...formData, + password: e.detail, + } + }} + validate={() => { + let fieldError = { + password: !formData.password + ? "Please enter your password" + : undefined, + } + errors = handleError({ ...errors, ...fieldError }) + }} + error={errors.password} + /> + + {/if} + {#if !$organisation.isSSOEnforced} - - - { - formData = { - ...formData, - username: e.detail, - } - }} - validate={() => { - let fieldError = { - username: !formData.username - ? "Please enter a valid email" - : undefined, - } - errors = handleError({ ...errors, ...fieldError }) - }} - error={errors.username} - /> - { - formData = { - ...formData, - password: e.detail, - } - }} - validate={() => { - let fieldError = { - password: !formData.password - ? "Please enter your password" - : undefined, - } - errors = handleError({ ...errors, ...fieldError }) - }} - error={errors.password} - /> - + + + + + + + {/if} + + {#if cloud} + + By using Budibase Cloud +
+ you are agreeing to our + + License Agreement + + {/if}
- {#if !$organisation.isSSOEnforced} - - - - - - - {/if} - - {#if cloud} - - By using Budibase Cloud -
- you are agreeing to our - - License Agreement - - - {/if} -
-
+ +{/if} diff --git a/packages/builder/src/pages/builder/portal/settings/organisation.svelte b/packages/builder/src/pages/builder/portal/settings/organisation.svelte index bba046ce10..d2af329cb9 100644 --- a/packages/builder/src/pages/builder/portal/settings/organisation.svelte +++ b/packages/builder/src/pages/builder/portal/settings/organisation.svelte @@ -7,12 +7,10 @@ Divider, Label, Input, - Dropzone, notifications, Toggle, } from "@budibase/bbui" import { auth, organisation, admin } from "stores/portal" - import { API } from "api" import { writable } from "svelte/store" import { redirect } from "@roxi/routify" @@ -28,32 +26,14 @@ company: $organisation.company, platformUrl: $organisation.platformUrl, analyticsEnabled: $organisation.analyticsEnabled, - logo: $organisation.logoUrl - ? { url: $organisation.logoUrl, type: "image", name: "Logo" } - : null, }) - let loading = false - async function uploadLogo(file) { - try { - let data = new FormData() - data.append("file", file) - await API.uploadLogo(data) - } catch (error) { - notifications.error("Error uploading logo") - } - } + let loading = false async function saveConfig() { loading = true try { - // Upload logo if required - if ($values.logo && !$values.logo.url) { - await uploadLogo($values.logo) - await organisation.init() - } - const config = { isSSOEnforced: $values.isSSOEnforced, company: $values.company ?? "", @@ -61,11 +41,6 @@ analyticsEnabled: $values.analyticsEnabled, } - // Remove logo if required - if (!$values.logo) { - config.logoUrl = "" - } - // Update settings await organisation.save(config) } catch (error) { @@ -87,21 +62,7 @@
- + {#if !$admin.cloud}