diff --git a/lerna.json b/lerna.json index 03d2196d25..e72132901e 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.6.19-alpha.26", + "version": "2.6.19-alpha.32", "npmClient": "yarn", "packages": [ "packages/backend-core", diff --git a/packages/backend-core/src/cache/tests/writethrough.spec.ts b/packages/backend-core/src/cache/tests/writethrough.spec.ts index e4c7cc6e64..92b073ed64 100644 --- a/packages/backend-core/src/cache/tests/writethrough.spec.ts +++ b/packages/backend-core/src/cache/tests/writethrough.spec.ts @@ -72,16 +72,12 @@ describe("writethrough", () => { writethrough.put({ ...current, value: 4 }), ]) + // with a lock, this will work const newRev = responses.map(x => x.rev).find(x => x !== current._rev) expect(newRev).toBeDefined() expect(responses.map(x => x.rev)).toEqual( expect.arrayContaining([current._rev, current._rev, newRev]) ) - expectFunctionWasCalledTimesWith( - mocks.alerts.logWarn, - 2, - "Ignoring redlock conflict in write-through cache" - ) const output = await db.get(current._id) expect(output.value).toBe(4) diff --git a/packages/backend-core/src/logging/pino/logger.ts b/packages/backend-core/src/logging/pino/logger.ts index cebc78ffc7..c96bc83e04 100644 --- a/packages/backend-core/src/logging/pino/logger.ts +++ b/packages/backend-core/src/logging/pino/logger.ts @@ -96,6 +96,7 @@ if (!env.DISABLE_PINO_LOGGER) { const mergingObject: any = { err: error, + pid: process.pid, ...contextObject, } diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index 55b891ea84..7fe61a409e 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -4,10 +4,10 @@ import { LockOptions, LockType } from "@budibase/types" import * as context from "../context" import env from "../environment" -const getClient = async ( +async function getClient( type: LockType, opts?: Redlock.Options -): Promise => { +): Promise { if (type === LockType.CUSTOM) { return newRedlock(opts) } @@ -18,6 +18,9 @@ const getClient = async ( case LockType.TRY_ONCE: { return newRedlock(OPTIONS.TRY_ONCE) } + case LockType.TRY_TWICE: { + return newRedlock(OPTIONS.TRY_TWICE) + } case LockType.DEFAULT: { return newRedlock(OPTIONS.DEFAULT) } @@ -35,6 +38,9 @@ const OPTIONS = { // immediately throws an error if the lock is already held retryCount: 0, }, + TRY_TWICE: { + retryCount: 1, + }, TEST: { // higher retry count in unit tests // due to high contention. @@ -62,7 +68,7 @@ const OPTIONS = { }, } -const newRedlock = async (opts: Redlock.Options = {}) => { +export async function newRedlock(opts: Redlock.Options = {}) { let options = { ...OPTIONS.DEFAULT, ...opts } const redisWrapper = await getLockClient() const client = redisWrapper.getClient() @@ -81,22 +87,26 @@ type RedlockExecution = | SuccessfulRedlockExecution | UnsuccessfulRedlockExecution -export const doWithLock = async ( +function getLockName(opts: LockOptions) { + // determine lock name + // by default use the tenantId for uniqueness, unless using a system lock + const prefix = opts.systemLock ? "system" : context.getTenantId() + let name: string = `lock:${prefix}_${opts.name}` + // add additional unique name if required + if (opts.resource) { + name = name + `_${opts.resource}` + } + return name +} + +export async function doWithLock( opts: LockOptions, task: () => Promise -): Promise> => { +): Promise> { const redlock = await getClient(opts.type, opts.customOptions) let lock try { - // determine lock name - // by default use the tenantId for uniqueness, unless using a system lock - const prefix = opts.systemLock ? "system" : context.getTenantId() - let name: string = `lock:${prefix}_${opts.name}` - - // add additional unique name if required - if (opts.resource) { - name = name + `_${opts.resource}` - } + const name = getLockName(opts) // create the lock lock = await redlock.lock(name, opts.ttl) @@ -112,7 +122,6 @@ export const doWithLock = async ( if (opts.type === LockType.TRY_ONCE) { // don't throw for try-once locks, they will always error // due to retry count (0) exceeded - console.warn(e) return { executed: false } } else { console.error(e) diff --git a/packages/backend-core/src/redis/utils.ts b/packages/backend-core/src/redis/utils.ts index 2c49ee4941..f8b815824c 100644 --- a/packages/backend-core/src/redis/utils.ts +++ b/packages/backend-core/src/redis/utils.ts @@ -27,6 +27,7 @@ export enum Databases { GENERIC_CACHE = "data_cache", WRITE_THROUGH = "writeThrough", LOCKS = "locks", + SOCKET_IO = "socket_io", } /** diff --git a/packages/bbui/src/Avatar/Avatar.svelte b/packages/bbui/src/Avatar/Avatar.svelte index 1e4cefd8ce..0faf50f55a 100644 --- a/packages/bbui/src/Avatar/Avatar.svelte +++ b/packages/bbui/src/Avatar/Avatar.svelte @@ -13,10 +13,12 @@ export let url = "" export let disabled = false export let initials = "JD" + export let color = null const DefaultColor = "#3aab87" - $: color = getColor(initials) + $: avatarColor = color || getColor(initials) + $: style = getStyle(size, avatarColor) const getColor = initials => { if (!initials?.length) { @@ -26,6 +28,12 @@ const hue = ((code % 26) / 26) * 360 return `hsl(${hue}, 50%, 50%)` } + + const getStyle = (sizeKey, color) => { + const size = `var(${sizes.get(sizeKey)})` + const fontSize = `calc(${size} / 2)` + return `width:${size}; height:${size}; font-size:${fontSize}; background:${color};` + } {#if url} @@ -37,13 +45,7 @@ style="width: var({sizes.get(size)}); height: var({sizes.get(size)});" /> {:else} -
+
{initials || ""}
{/if} diff --git a/packages/bbui/src/Form/Core/Dropzone.svelte b/packages/bbui/src/Form/Core/Dropzone.svelte index 64d851b1e4..e9ee75bd8b 100644 --- a/packages/bbui/src/Form/Core/Dropzone.svelte +++ b/packages/bbui/src/Form/Core/Dropzone.svelte @@ -165,7 +165,7 @@ {/if} {#if !disabled}
- +
{/if}
@@ -209,7 +209,7 @@ {/if} {#if !disabled}
- +
{/if} diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index d15cdb6e98..9dca6a64e6 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -2,6 +2,7 @@ import { getFrontendStore } from "./store/frontend" import { getAutomationStore } from "./store/automation" import { getTemporalStore } from "./store/temporal" import { getThemeStore } from "./store/theme" +import { getUserStore } from "./store/users" import { derived } from "svelte/store" import { findComponent, findComponentPath } from "./componentUtils" import { RoleUtils } from "@budibase/frontend-core" @@ -12,6 +13,7 @@ export const store = getFrontendStore() export const automationStore = getAutomationStore() export const themeStore = getThemeStore() export const temporalStore = getTemporalStore() +export const userStore = getUserStore() // Setup history for screens export const screenHistoryStore = createHistoryStore({ diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index 0da194dbec..91b3f2fc17 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -37,8 +37,10 @@ import { } from "builderStore/dataBinding" import { makePropSafe as safe } from "@budibase/string-templates" import { getComponentFieldOptions } from "helpers/formFields" +import { createBuilderWebsocket } from "builderStore/websocket" const INITIAL_FRONTEND_STATE = { + initialised: false, apps: [], name: "", url: "", @@ -71,6 +73,7 @@ const INITIAL_FRONTEND_STATE = { highlightedSettingKey: null, propertyFocus: null, builderSidePanel: false, + hasLock: true, // URL params selectedScreenId: null, @@ -87,6 +90,7 @@ const INITIAL_FRONTEND_STATE = { export const getFrontendStore = () => { const store = writable({ ...INITIAL_FRONTEND_STATE }) + let websocket // This is a fake implementation of a "patch" API endpoint to try and prevent // 409s. All screen doc mutations (aside from creation) use this function, @@ -111,10 +115,11 @@ export const getFrontendStore = () => { store.actions = { reset: () => { store.set({ ...INITIAL_FRONTEND_STATE }) + websocket?.disconnect() }, initialise: async pkg => { - const { layouts, screens, application, clientLibPath } = pkg - + const { layouts, screens, application, clientLibPath, hasLock } = pkg + websocket = createBuilderWebsocket() await store.actions.components.refreshDefinitions(application.appId) // Reset store state @@ -138,6 +143,8 @@ export const getFrontendStore = () => { upgradableVersion: application.upgradableVersion, navigation: application.navigation || {}, usedPlugins: application.usedPlugins || [], + hasLock, + initialised: true, })) screenHistoryStore.reset() automationHistoryStore.reset() diff --git a/packages/builder/src/builderStore/store/users.js b/packages/builder/src/builderStore/store/users.js new file mode 100644 index 0000000000..18f343c884 --- /dev/null +++ b/packages/builder/src/builderStore/store/users.js @@ -0,0 +1,42 @@ +import { writable, get } from "svelte/store" + +export const getUserStore = () => { + const store = writable([]) + + const init = users => { + store.set(users) + } + + const updateUser = user => { + const $users = get(store) + if (!$users.some(x => x.sessionId === user.sessionId)) { + store.set([...$users, user]) + } else { + store.update(state => { + const index = state.findIndex(x => x.sessionId === user.sessionId) + state[index] = user + return state.slice() + }) + } + } + + const removeUser = user => { + store.update(state => { + return state.filter(x => x.sessionId !== user.sessionId) + }) + } + + const reset = () => { + store.set([]) + } + + return { + ...store, + actions: { + init, + updateUser, + removeUser, + reset, + }, + } +} diff --git a/packages/builder/src/builderStore/websocket.js b/packages/builder/src/builderStore/websocket.js new file mode 100644 index 0000000000..d58eb24bbf --- /dev/null +++ b/packages/builder/src/builderStore/websocket.js @@ -0,0 +1,39 @@ +import { createWebsocket } from "@budibase/frontend-core" +import { userStore } from "builderStore" +import { datasources, tables } from "stores/backend" + +export const createBuilderWebsocket = () => { + const socket = createWebsocket("/socket/builder") + + // Connection events + socket.on("connect", () => { + socket.emit("get-users", null, response => { + userStore.actions.init(response.users) + }) + }) + socket.on("connect_error", err => { + console.log("Failed to connect to builder websocket:", err.message) + }) + + // User events + socket.on("user-update", userStore.actions.updateUser) + socket.on("user-disconnect", userStore.actions.removeUser) + + // Table events + socket.on("table-change", ({ id, table }) => { + tables.replaceTable(id, table) + }) + + // Datasource events + socket.on("datasource-change", ({ id, datasource }) => { + datasources.replaceDatasource(id, datasource) + }) + + return { + ...socket, + disconnect: () => { + socket?.disconnect() + userStore.actions.reset() + }, + } +} diff --git a/packages/builder/src/components/backend/DataTable/DataTable.svelte b/packages/builder/src/components/backend/DataTable/DataTable.svelte index 83e115ebf2..3e9b7d831e 100644 --- a/packages/builder/src/components/backend/DataTable/DataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/DataTable.svelte @@ -16,11 +16,11 @@ import GridEditColumnModal from "components/backend/DataTable/modals/grid/GridEditColumnModal.svelte" const userSchemaOverrides = { - firstName: { name: "First name", disabled: true }, - lastName: { name: "Last name", disabled: true }, - email: { name: "Email", disabled: true }, - roleId: { name: "Role", disabled: true }, - status: { name: "Status", disabled: true }, + firstName: { displayName: "First name", disabled: true }, + lastName: { displayName: "Last name", disabled: true }, + email: { displayName: "Email", disabled: true }, + roleId: { displayName: "Role", disabled: true }, + status: { displayName: "Status", disabled: true }, } $: id = $tables.selected?._id @@ -36,7 +36,7 @@ allowAddRows={!isUsersTable} allowDeleteRows={!isUsersTable} schemaOverrides={isUsersTable ? userSchemaOverrides : null} - on:updatetable={e => tables.updateTable(e.detail)} + showAvatars={false} > {#if isInternal} diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte deleted file mode 100644 index 290f25b941..0000000000 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/CreateDatasourceModal.svelte +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - - {#if integration?.auth?.type === "google"} - - {:else} - - {/if} - - - - {#if integration.type === "REST"} - modal.show()} - /> - {/if} - - - - showImportModal()} - showCancelButton={false} - size="M" - onConfirm={() => { - chooseNextModal() - }} - > - - Get started with Budibase DB -
selectIntegration(IntegrationTypes.INTERNAL)} - class="item hoverable" - > -
- -
- Budibase DB - Non-relational -
-
-
-
- - - Connect to an external datasource -
- {#each sortedIntegrations.filter(([key, val]) => key !== IntegrationTypes.INTERNAL && !val.custom) as [integrationType, schema]} - selectIntegration(evt.detail)} - {schema} - bind:integrationType - {integration} - /> - {/each} -
-
- - {#if customIntegrations.length > 0} - - Custom datasource -
- {#each customIntegrations as [integrationType, schema]} - selectIntegration(evt.detail)} - {schema} - bind:integrationType - {integration} - /> - {/each} -
-
- {/if} -
-
- - diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte index e75109b1be..21c7e07a25 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/DatasourceConfigModal.svelte @@ -11,7 +11,6 @@ import { DatasourceFeature } from "@budibase/types" export let integration - export let modal // kill the reference so the input isn't saved let datasource = cloneDeep(integration) @@ -62,7 +61,6 @@ saveDatasource()} - onCancel={() => modal.show()} confirmText={datasource.plus ? "Connect" : "Save and continue to query"} cancelText="Back" showSecondaryButton={datasource.plus} diff --git a/packages/builder/src/components/backend/DatasourceNavigator/modals/GoogleDatasourceConfigModal.svelte b/packages/builder/src/components/backend/DatasourceNavigator/modals/GoogleDatasourceConfigModal.svelte index de9ecce778..0783a9fe53 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/modals/GoogleDatasourceConfigModal.svelte +++ b/packages/builder/src/components/backend/DatasourceNavigator/modals/GoogleDatasourceConfigModal.svelte @@ -8,7 +8,6 @@ import { onMount } from "svelte" export let integration - export let modal // kill the reference so the input isn't saved let datasource = cloneDeep(integration) @@ -21,7 +20,6 @@ modal.show()} cancelText="Back" size="L" > diff --git a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte index 8d8418eb81..f34a3e9c98 100644 --- a/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/TableDataImport.svelte @@ -4,6 +4,7 @@ import { API } from "api" import { parseFile } from "./utils" + let fileInput let error = null let fileName = null let fileType = null @@ -16,6 +17,7 @@ export let schema = {} export let allValid = true export let displayColumn = null + export let promptUpload = false const typeOptions = [ { @@ -99,10 +101,19 @@ schema[name].type = e.detail schema[name].constraints = FIELDS[e.detail.toUpperCase()].constraints } + + const openFileUpload = (promptUpload, fileInput) => { + if (promptUpload && fileInput) { + fileInput.click() + } + } + + $: openFileUpload(promptUpload, fileInput)
{} export let afterSave = async table => { @@ -136,7 +137,13 @@ - +
diff --git a/packages/builder/src/components/common/AppLockModal.svelte b/packages/builder/src/components/common/AppLockModal.svelte deleted file mode 100644 index 875b4afce0..0000000000 --- a/packages/builder/src/components/common/AppLockModal.svelte +++ /dev/null @@ -1,143 +0,0 @@ - - -{#if lockedBy} -
- { - e.stopPropagation() - appLockModal.show() - }} - /> -
-{/if} - - - - - - Apps are locked to prevent work being lost from overlapping changes - between your team. - - {#if lockedByYou && getExpiryDuration(app) > 0} - - {processStringSync( - "This lock will expire in {{ duration time 'millisecond' }} from now.", - { - time: getExpiryDuration(app), - } - )} - - {/if} -
- - - {#if lockedByYou} - - {/if} - -
-
-
-
- - diff --git a/packages/builder/src/components/common/FontAwesomeIcon.svelte b/packages/builder/src/components/common/FontAwesomeIcon.svelte index 84c16abeda..364b3af25f 100644 --- a/packages/builder/src/components/common/FontAwesomeIcon.svelte +++ b/packages/builder/src/components/common/FontAwesomeIcon.svelte @@ -8,6 +8,7 @@ faLock, faFileArrowUp, faChevronLeft, + faCircleInfo, } from "@fortawesome/free-solid-svg-icons" import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons" @@ -20,7 +21,8 @@ faDiscord, faEnvelope, faFileArrowUp, - faChevronLeft + faChevronLeft, + faCircleInfo ) dom.watch() diff --git a/packages/builder/src/components/deploy/AppActions.svelte b/packages/builder/src/components/deploy/AppActions.svelte index daed564204..9813237317 100644 --- a/packages/builder/src/components/deploy/AppActions.svelte +++ b/packages/builder/src/components/deploy/AppActions.svelte @@ -113,109 +113,113 @@ }) -
-
-
- -
- - - {#if isPublished} -
-
- -
- -
- - Your published app - - - {processStringSync( - "Last published {{ duration time 'millisecond' }} ago", - { - time: - new Date().getTime() - - new Date(latestDeployments[0].updatedAt).getTime(), - } - )} - - -
- - -
-
-
-
+{#if $store.hasLock} +
+
+
+
- {/if} + - {#if !isPublished} - - {/if} + {#if isPublished} +
+
+ +
+ +
+ + Your published app + + + {processStringSync( + "Last published {{ duration time 'millisecond' }} ago", + { + time: + new Date().getTime() - + new Date(latestDeployments[0].updatedAt).getTime(), + } + )} + + +
+ + +
+
+
+
+
+ {/if} - - + {#if !isPublished} { - store.update(state => { - state.builderSidePanel = true - return state - }) - }} - > - Users - - - -
-
+ tooltip="Your app has not been published yet" + disabled + /> + {/if} - - Are you sure you want to unpublish the app {selectedApp?.name}? - + + + { + store.update(state => { + state.builderSidePanel = true + return state + }) + }} + > + Users + + + +
+
+ + + Are you sure you want to unpublish the app {selectedApp?.name}? + +{/if}
- + {#if $store.hasLock} + + {/if}
diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index ff728312c1..4da0da44f9 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -1,5 +1,5 @@ @@ -134,74 +138,89 @@
-
- -
- -
- $goto("../../portal/apps")}> - Exit to portal - - $goto(`../../portal/overview/${application}`)} - > - Overview - - $goto(`../../portal/overview/${application}/access`)} - > - Access - - - $goto(`../../portal/overview/${application}/automation-history`)} - > - Automation history - - $goto(`../../portal/overview/${application}/backups`)} - > - Backups - + {#if $store.initialised} +
+ +
+ +
+ $goto("../../portal/apps")}> + Exit to portal + + $goto(`../../portal/overview/${application}`)} + > + Overview + + + $goto(`../../portal/overview/${application}/access`)} + > + Access + + + $goto(`../../portal/overview/${application}/automation-history`)} + > + Automation history + + + $goto(`../../portal/overview/${application}/backups`)} + > + Backups + - - $goto(`../../portal/overview/${application}/name-and-url`)} - > - Name and URL - - $goto(`../../portal/overview/${application}/version`)} - > - Version - -
- {$store.name} -
-
- - {#each $layout.children as { path, title }} - - - - {/each} - -
-
- -
+ + $goto(`../../portal/overview/${application}/name-and-url`)} + > + Name and URL + + + $goto(`../../portal/overview/${application}/version`)} + > + Version + +
+ {$store.name} +
+
+ {#if $store.hasLock} + + {#each $layout.children as { path, title }} + + + + {/each} + + {:else} +
+ + Another user is currently editing your screens and automations +
+ {/if} +
+
+ + +
+ {/if}
{#await promise}
{:then _} - +
+ +
{:catch error}

Something went wrong: {error.message}

{/await} @@ -237,6 +256,7 @@ box-sizing: border-box; align-items: stretch; border-bottom: var(--border-light); + z-index: 2; } .topleftnav { @@ -270,4 +290,18 @@ align-items: center; gap: var(--spacing-l); } + + .secondary-editor { + align-self: center; + display: flex; + flex-direction: row; + gap: 8px; + } + + .body { + flex: 1 1 auto; + z-index: 1; + display: flex; + flex-direction: column; + } diff --git a/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte index 74dfe671ab..79ca5df168 100644 --- a/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/automate/_layout.svelte @@ -8,6 +8,15 @@ import { onDestroy, onMount } from "svelte" import { syncURLToState } from "helpers/urlStateSync" import * as routify from "@roxi/routify" + import { store } from "builderStore" + import { redirect } from "@roxi/routify" + + // Prevent access for other users than the lock holder + $: { + if (!$store.hasLock) { + $redirect("../data") + } + } // Keep URL and state in sync for selected screen ID const stopSyncing = syncURLToState({ diff --git a/packages/builder/src/pages/builder/app/[application]/data/_DatasourceOption.svelte b/packages/builder/src/pages/builder/app/[application]/data/_DatasourceOption.svelte new file mode 100644 index 0000000000..8afc930bae --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/_DatasourceOption.svelte @@ -0,0 +1,51 @@ + + +
+
+
+ +
+ {title} +
+ +
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte index 8b401866f5..87c4db81df 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/_layout.svelte @@ -1,21 +1,20 @@
- - - - - - - + {#if !$isActive("./new")} + + + + + + + {/if}
diff --git a/packages/builder/src/pages/builder/app/[application]/data/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/index.svelte index b2aca1f7f3..47939f09b4 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/index.svelte @@ -1,22 +1,17 @@ - - diff --git a/packages/builder/src/pages/builder/app/[application]/data/new.svelte b/packages/builder/src/pages/builder/app/[application]/data/new.svelte new file mode 100644 index 0000000000..f8e8fd85e7 --- /dev/null +++ b/packages/builder/src/pages/builder/app/[application]/data/new.svelte @@ -0,0 +1,257 @@ + + + + + + + + {#if integration?.auth?.type === "google"} + + {:else} + + {/if} + + +
+
+ {#if hasData} + + {/if} +
+
+ Add new data source +
+ +
+ Get started with our Budibase DB + +
+ +
+ + + + + + + + + +
+ +
+ Or connect to an external datasource +
+ +
+ {#each integrations as [key, value]} + handleIntegrationSelect(key)} + title={value.friendlyName} + description={value.type} + {disabled} + > + + + {/each} +
+
+ + diff --git a/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte index ec21d909aa..d23514ae6d 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_layout.svelte @@ -1,2 +1,14 @@ + + diff --git a/packages/builder/src/pages/builder/apps/index.svelte b/packages/builder/src/pages/builder/apps/index.svelte index 4b77671345..1806d51b69 100644 --- a/packages/builder/src/pages/builder/apps/index.svelte +++ b/packages/builder/src/pages/builder/apps/index.svelte @@ -5,7 +5,6 @@ Divider, ActionMenu, MenuItem, - Avatar, Page, Icon, Body, @@ -22,6 +21,8 @@ import { processStringSync } from "@budibase/string-templates" import Spaceman from "assets/bb-space-man.svg" import Logo from "assets/bb-emblem.svg" + import { UserAvatar } from "@budibase/frontend-core" + import { helpers } from "@budibase/shared-core" let loaded = false let userInfoModal @@ -96,11 +97,7 @@
- +
userInfoModal.show()}> @@ -125,7 +122,7 @@
- Hey {$auth.user.firstName || $auth.user.email} + Hey {helpers.getUserLabel($auth.user)} Welcome to the {$organisation.company} portal. Below you'll find the diff --git a/packages/builder/src/pages/builder/portal/_components/UserDropdown.svelte b/packages/builder/src/pages/builder/portal/_components/UserDropdown.svelte index 935d69812f..9faae70aa9 100644 --- a/packages/builder/src/pages/builder/portal/_components/UserDropdown.svelte +++ b/packages/builder/src/pages/builder/portal/_components/UserDropdown.svelte @@ -1,11 +1,12 @@ {#if row?.user?.email} @@ -19,7 +14,7 @@ on:focus={() => (showTooltip = true)} on:mouseleave={() => (showTooltip = false)} > - +
{#if showTooltip}
diff --git a/packages/builder/src/pages/builder/portal/apps/index.svelte b/packages/builder/src/pages/builder/portal/apps/index.svelte index ce1c249087..8b8451f036 100644 --- a/packages/builder/src/pages/builder/portal/apps/index.svelte +++ b/packages/builder/src/pages/builder/portal/apps/index.svelte @@ -355,7 +355,6 @@ justify-content: flex-start; align-items: stretch; gap: var(--spacing-xl); - overflow: hidden; } .empty-wrapper { diff --git a/packages/builder/src/pages/builder/portal/apps/onboarding/_components/DataPanel.svelte b/packages/builder/src/pages/builder/portal/apps/onboarding/_components/DataPanel.svelte deleted file mode 100644 index 9a7fffd893..0000000000 --- a/packages/builder/src/pages/builder/portal/apps/onboarding/_components/DataPanel.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - -
- - -
diff --git a/packages/builder/src/pages/builder/portal/apps/onboarding/_components/DatasourceConfigPanel.svelte b/packages/builder/src/pages/builder/portal/apps/onboarding/_components/DatasourceConfigPanel.svelte deleted file mode 100644 index 2b44648279..0000000000 --- a/packages/builder/src/pages/builder/portal/apps/onboarding/_components/DatasourceConfigPanel.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - -
- -
- - {#each Object.entries(fields) as [name, { type, default: defaultValue, required }]} - {#if type !== "boolean"} - {}} - label={formatName(name)} - {type} - /> - {/if} - {/each} - {#each Object.entries(fields) as [name, { type, default: defaultValue, required }]} - {#if type === "boolean"} - - {/if} - {/each} - -
- {#if isGoogle} - - {:else} - - {/if} -
- - 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 3e970ac360..0b290decbf 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 @@ -1,6 +1,5 @@ - - - -
- {#if stage === "name"} - (stage = "data")} /> - {:else if googleComplete} -
- Please login to your Google account in the new tab which as opened to - continue. -
- {:else if integrationsLoading || creationLoading} -
- -
- {:else if stage === "data"} - (stage = "name")}> -
- handleCreateApp({ useSampleData: true })} - > -
-
- -
- Budibase Sample data -
-
-
-
- -
-
- -
- Upload data (CSV or JSON) -
-
-
- {#each Object.entries(plusIntegrations) as [integrationType, schema]} -
- (stage = integrationType)}> -
-
- -
- {schema.friendlyName} -
-
-
- {/each} -
- {:else if stage in plusIntegrations} - (stage = "data")} - onNext={data => { - const isGoogle = data.isGoogle - delete data.isGoogle - return handleCreateApp({ datasourceConfig: data, isGoogle }) - }} - /> - {:else} -

There was an problem. Please refresh the page and try again.

- {/if} +
- +
@@ -258,35 +77,4 @@ .full-width { width: 100%; } - .centered { - display: flex; - justify-content: center; - align-items: center; - min-height: 400px; - } - - .dataButton { - margin-bottom: 12px; - } - - .dataButtonContent { - display: flex; - align-items: center; - } - - .budibaseLogo { - height: 20px; - } - - .dataButtonIcon { - width: 22px; - display: flex; - justify-content: center; - margin-right: 16px; - } - - .dataButtonContent :global(svg) { - font-size: 18px; - color: white; - } diff --git a/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte b/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte index 806589e421..8ee469a914 100644 --- a/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/overview/[appId]/_layout.svelte @@ -24,7 +24,6 @@ import { AppStatus } from "constants" import analytics, { Events, EventSource } from "analytics" import { store } from "builderStore" - import AppLockModal from "components/common/AppLockModal.svelte" import EditableIcon from "components/common/EditableIcon.svelte" import { API } from "api" import ConfirmDialog from "components/common/ConfirmDialog.svelte" @@ -80,13 +79,6 @@ } const editApp = () => { - if (appLocked && !lockedByYou) { - const identifier = app?.lockedBy?.firstName || app?.lockedBy?.email - notifications.warning( - `App locked by ${identifier}. Please allow lock to expire or have them unlock this app.` - ) - return - } $goto(`../../../app/${app.devId}`) } @@ -135,7 +127,6 @@ />
-