diff --git a/hosting/couchdb/runner.sh b/hosting/couchdb/runner.sh index e56b8e0e7f..9f6a853ca7 100644 --- a/hosting/couchdb/runner.sh +++ b/hosting/couchdb/runner.sh @@ -76,6 +76,6 @@ done # CouchDB needs the `_users` and `_replicator` databases to exist before it will # function correctly, so we create them here. -curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_users -curl -X PUT http://${COUCHDB_USER}:${COUCHDB_PASSWORD}@localhost:5984/_replicator +curl -X PUT -u "${COUCHDB_USER}:${COUCHDB_PASSWORD}" http://localhost:5984/_users +curl -X PUT -u "${COUCHDB_USER}:${COUCHDB_PASSWORD}" http://localhost:5984/_replicator sleep infinity \ No newline at end of file diff --git a/hosting/docker-compose.yaml b/hosting/docker-compose.yaml index 7803916069..36b88466fe 100644 --- a/hosting/docker-compose.yaml +++ b/hosting/docker-compose.yaml @@ -26,7 +26,7 @@ services: BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL} BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD} PLUGINS_DIR: ${PLUGINS_DIR} - OFFLINE_MODE: ${OFFLINE_MODE} + OFFLINE_MODE: ${OFFLINE_MODE:-} depends_on: - worker-service - redis-service @@ -53,7 +53,7 @@ services: INTERNAL_API_KEY: ${INTERNAL_API_KEY} REDIS_URL: redis-service:6379 REDIS_PASSWORD: ${REDIS_PASSWORD} - OFFLINE_MODE: ${OFFLINE_MODE} + OFFLINE_MODE: ${OFFLINE_MODE:-} depends_on: - redis-service - minio-service @@ -109,7 +109,7 @@ services: redis-service: restart: unless-stopped image: redis - command: redis-server --requirepass ${REDIS_PASSWORD} + command: redis-server --requirepass "${REDIS_PASSWORD}" volumes: - redis_data:/data diff --git a/lerna.json b/lerna.json index 71c53cd3fa..81b35e2a6e 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.14.2", + "version": "2.14.5", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/account-portal b/packages/account-portal index b11e6b4737..b23fb3b179 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit b11e6b47370d9b77c63648b45929c86bfed6360c +Subproject commit b23fb3b17961fb04badd9487913a683fcf26dbe6 diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 138dbbd9e0..0fec786c31 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -166,6 +166,8 @@ const environment = { DISABLE_JWT_WARNING: process.env.DISABLE_JWT_WARNING, BLACKLIST_IPS: process.env.BLACKLIST_IPS, SERVICE_TYPE: "unknown", + PASSWORD_MIN_LENGTH: process.env.PASSWORD_MIN_LENGTH, + PASSWORD_MAX_LENGTH: process.env.PASSWORD_MAX_LENGTH, /** * 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/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 16f658b90a..d357dbdbdc 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -15,6 +15,7 @@ import * as identity from "../context/identity" import env from "../environment" import { Ctx, EndpointMatcher, SessionCookie } from "@budibase/types" import { InvalidAPIKeyError, ErrorCode } from "../errors" +import tracer from "dd-trace" const ONE_MINUTE = env.SESSION_UPDATE_PERIOD ? parseInt(env.SESSION_UPDATE_PERIOD) @@ -166,6 +167,16 @@ export default function ( if (!authenticated) { authenticated = false } + + if (user) { + tracer.setUser({ + id: user?._id, + tenantId: user?.tenantId, + budibaseAccess: user?.budibaseAccess, + status: user?.status, + }) + } + // isAuthenticated is a function, so use a variable to be able to check authed state finalise(ctx, { authenticated, user, internal, version, publicEndpoint }) diff --git a/packages/backend-core/src/redis/redlockImpl.ts b/packages/backend-core/src/redis/redlockImpl.ts index e57a3721b5..7009dc6f55 100644 --- a/packages/backend-core/src/redis/redlockImpl.ts +++ b/packages/backend-core/src/redis/redlockImpl.ts @@ -2,7 +2,6 @@ import Redlock from "redlock" import { getLockClient } from "./init" import { LockOptions, LockType } from "@budibase/types" import * as context from "../context" -import { logWarn } from "../logging" import { utils } from "@budibase/shared-core" import { Duration } from "../utils" diff --git a/packages/backend-core/src/security/auth.ts b/packages/backend-core/src/security/auth.ts index c90d9df09b..1cce35a0af 100644 --- a/packages/backend-core/src/security/auth.ts +++ b/packages/backend-core/src/security/auth.ts @@ -1,7 +1,7 @@ -import { env } from ".." +import env from "../environment" -export const PASSWORD_MIN_LENGTH = +(process.env.PASSWORD_MIN_LENGTH || 8) -export const PASSWORD_MAX_LENGTH = +(process.env.PASSWORD_MAX_LENGTH || 512) +export const PASSWORD_MIN_LENGTH = +(env.PASSWORD_MIN_LENGTH || 8) +export const PASSWORD_MAX_LENGTH = +(env.PASSWORD_MAX_LENGTH || 512) export function validatePassword( password: string diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index 3214b3ab63..4d0d216603 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -44,6 +44,12 @@ type GroupFns = { getBulk: GroupGetFn getGroupBuilderAppIds: GroupBuildersFn } +type CreateAdminUserOpts = { + ssoId?: string + hashPassword?: boolean + requirePassword?: boolean + skipPasswordValidation?: boolean +} type FeatureFns = { isSSOEnforced: FeatureFn; isAppBuildersEnabled: FeatureFn } const bulkDeleteProcessing = async (dbUser: User) => { @@ -112,9 +118,11 @@ export class UserDB { throw new HTTPError("Password change is disabled for this user", 400) } - const passwordValidation = validatePassword(password) - if (!passwordValidation.valid) { - throw new HTTPError(passwordValidation.error, 400) + if (!opts.skipPasswordValidation) { + const passwordValidation = validatePassword(password) + if (!passwordValidation.valid) { + throw new HTTPError(passwordValidation.error, 400) + } } hashedPassword = opts.hashPassword ? await hash(password) : password @@ -489,7 +497,7 @@ export class UserDB { email: string, password: string, tenantId: string, - opts?: { ssoId?: string; hashPassword?: boolean; requirePassword?: boolean } + opts?: CreateAdminUserOpts ) { const user: User = { email: email, @@ -513,6 +521,7 @@ export class UserDB { return await UserDB.save(user, { hashPassword: opts?.hashPassword, requirePassword: opts?.requirePassword, + skipPasswordValidation: opts?.skipPasswordValidation, }) } diff --git a/packages/bbui/src/ActionButton/ActionButton.svelte b/packages/bbui/src/ActionButton/ActionButton.svelte index 427a98f888..0e6ec3d155 100644 --- a/packages/bbui/src/ActionButton/ActionButton.svelte +++ b/packages/bbui/src/ActionButton/ActionButton.svelte @@ -130,5 +130,6 @@ max-width: 150px; transform: translateX(-50%); text-align: center; + z-index: 1; } diff --git a/packages/bbui/src/DetailSummary/DetailSummary.svelte b/packages/bbui/src/DetailSummary/DetailSummary.svelte index e5d6fda86b..2cbb6796f3 100644 --- a/packages/bbui/src/DetailSummary/DetailSummary.svelte +++ b/packages/bbui/src/DetailSummary/DetailSummary.svelte @@ -78,7 +78,7 @@ var(--spacing-xl); } .property-panel.no-title { - padding: var(--spacing-xl); + padding-top: var(--spacing-xl); } .show { diff --git a/packages/bbui/src/Form/Field.svelte b/packages/bbui/src/Form/Field.svelte index 0c031b0235..1770438c3c 100644 --- a/packages/bbui/src/Form/Field.svelte +++ b/packages/bbui/src/Form/Field.svelte @@ -51,15 +51,13 @@ margin-top: var(--spectrum-global-dimension-size-75); align-items: center; } - .helpText :global(svg) { - width: 14px; - color: var(--grey-5); + width: 13px; + color: var(--spectrum-global-color-gray-600); margin-right: 6px; } - .helpText span { - color: var(--grey-7); + color: var(--spectrum-global-color-gray-800); font-size: var(--spectrum-global-dimension-font-size-75); } diff --git a/packages/builder/src/api.js b/packages/builder/src/api.js index 37894d9bbc..ac878bf82f 100644 --- a/packages/builder/src/api.js +++ b/packages/builder/src/api.js @@ -5,7 +5,7 @@ import { } from "@budibase/frontend-core" import { store } from "./builderStore" import { get } from "svelte/store" -import { auth } from "./stores/portal" +import { auth, navigation } from "./stores/portal" export const API = createAPIClient({ attachHeaders: headers => { @@ -45,4 +45,15 @@ export const API = createAPIClient({ } } }, + onMigrationDetected: appId => { + const updatingUrl = `/builder/app/updating/${appId}` + + if (window.location.pathname === updatingUrl) { + return + } + + get(navigation).goto( + `${updatingUrl}?returnUrl=${encodeURIComponent(window.location.pathname)}` + ) + }, }) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index d86e94aba2..52368a0723 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -465,8 +465,8 @@ const filterCategoryByContext = (component, context) => { const { _component } = component if (_component.endsWith("formblock")) { if ( - (component.actionType == "Create" && context.type === "schema") || - (component.actionType == "View" && context.type === "form") + (component.actionType === "Create" && context.type === "schema") || + (component.actionType === "View" && context.type === "form") ) { return false } @@ -474,20 +474,21 @@ const filterCategoryByContext = (component, context) => { return true } +// Enrich binding category information for certain components const getComponentBindingCategory = (component, context, def) => { let icon = def.icon let category = component._instanceName if (component._component.endsWith("formblock")) { - let contextCategorySuffix = { - form: "Fields", - schema: "Row", + if (context.type === "form") { + category = `${component._instanceName} - Fields` + icon = "Form" + } else if (context.type === "schema") { + category = `${component._instanceName} - Row` + icon = "Data" } - category = `${component._instanceName} - ${ - contextCategorySuffix[context.type] - }` - icon = context.type === "form" ? "Form" : "Data" } + return { icon, category, diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index aaa0eb0184..b05b127b1c 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -85,7 +85,6 @@ const INITIAL_FRONTEND_STATE = { selectedScreenId: null, selectedComponentId: null, selectedLayoutId: null, - hoverComponentId: null, // Client state selectedComponentInstance: null, @@ -93,6 +92,9 @@ const INITIAL_FRONTEND_STATE = { // Onboarding onboarding: false, tourNodes: null, + + // UI state + hoveredComponentId: null, } export const getFrontendStore = () => { @@ -610,12 +612,12 @@ export const getFrontendStore = () => { // Use default config if the 'buttons' prop has never been initialised if (!("buttons" in enrichedComponent)) { enrichedComponent["buttons"] = - Utils.buildDynamicButtonConfig(enrichedComponent) + Utils.buildFormBlockButtonConfig(enrichedComponent) migrated = true } else if (enrichedComponent["buttons"] == null) { // Ignore legacy config if 'buttons' has been reset by 'resetOn' const { _id, actionType, dataSource } = enrichedComponent - enrichedComponent["buttons"] = Utils.buildDynamicButtonConfig({ + enrichedComponent["buttons"] = Utils.buildFormBlockButtonConfig({ _id, actionType, dataSource, @@ -1289,15 +1291,14 @@ export const getFrontendStore = () => { const settings = getComponentSettings(component._component) const updatedSetting = settings.find(setting => setting.key === name) - // Can be a single string or array of strings - const resetFields = settings.filter(setting => { - return ( + // Reset dependent fields + settings.forEach(setting => { + const needsReset = name === setting.resetOn || (Array.isArray(setting.resetOn) && setting.resetOn.includes(name)) - ) - }) - resetFields?.forEach(setting => { - component[setting.key] = null + if (needsReset) { + component[setting.key] = setting.defaultValue || null + } }) if ( @@ -1414,6 +1415,18 @@ export const getFrontendStore = () => { return state }) }, + hover: (componentId, notifyClient = true) => { + if (componentId === get(store).hoveredComponentId) { + return + } + store.update(state => { + state.hoveredComponentId = componentId + return state + }) + if (notifyClient) { + store.actions.preview.sendEvent("hover-component", componentId) + } + }, }, links: { save: async (url, title) => { diff --git a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte index 0c97853dd6..c99de272f5 100644 --- a/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte +++ b/packages/builder/src/components/automation/AutomationBuilder/FlowChart/ActionModal.svelte @@ -158,7 +158,7 @@ {#if isDisabled && !syncAutomationsEnabled && !triggerAutomationsEnabled && lockedFeatures.includes(action.stepId)}
- Business + Premium
{:else if isDisabled} diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte index 851c5b39c9..937e3b6c69 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte @@ -41,7 +41,7 @@ { label: "False", value: "false" }, ]} /> -{:else if schema.type === "array"} +{:else if schemaHasOptions(schema) && schema.type === "array"} onChange(e, field)} useLabel={false} /> -{:else if ["string", "number", "bigint", "barcodeqr"].includes(schema.type)} +{:else if ["string", "number", "bigint", "barcodeqr", "array"].includes(schema.type)} { + if (!Array.isArray(val)) { + return null + } return val?.map(button => { return button._component ? button : buildPseudoInstance(button) }) diff --git a/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte b/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte index cce11e4b17..384f9bf098 100644 --- a/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte +++ b/packages/builder/src/components/design/settings/controls/DraggableList/DraggableList.svelte @@ -13,6 +13,8 @@ export let draggable = true export let focus + let zoneType = generate() + let store = writable({ selected: null, actions: { @@ -46,6 +48,7 @@ return { id: listItemKey ? item[listItemKey] : generate(), item, + type: zoneType, } }) .filter(item => item.id) @@ -83,6 +86,8 @@ items: draggableItems, dropTargetStyle: { outline: "none" }, dragDisabled: !draggable || inactive, + type: zoneType, + dropFromOthersDisabled: true, }} on:finalize={handleFinalize} on:consider={updateRowOrder} diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte index 7f1ac1cf25..e864a4c2aa 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldConfiguration.svelte @@ -14,6 +14,7 @@ import { convertOldFieldFormat, getComponentForField } from "./utils" export let componentInstance + export let bindings export let value const dispatch = createEventDispatcher() @@ -28,7 +29,9 @@ let selectAll = true - $: bindings = getBindableProperties($selectedScreen, componentInstance._id) + $: resolvedBindings = + bindings || getBindableProperties($selectedScreen, componentInstance._id) + $: actionType = componentInstance.actionType let componentBindings = [] @@ -39,7 +42,10 @@ ) } - $: datasource = getDatasourceForProvider($currentAsset, componentInstance) + $: datasource = + componentInstance.dataSource || + getDatasourceForProvider($currentAsset, componentInstance) + $: resourceId = datasource?.resourceId || datasource?.tableId $: if (!isEqual(value, cachedValue)) { @@ -179,7 +185,7 @@ listType={FieldSetting} listTypeProps={{ componentBindings, - bindings, + bindings: resolvedBindings, }} /> {/if} diff --git a/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte new file mode 100644 index 0000000000..2941821dec --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte @@ -0,0 +1,182 @@ + + +
+ +
+ + diff --git a/packages/builder/src/components/design/settings/controls/FormStepControls.svelte b/packages/builder/src/components/design/settings/controls/FormStepControls.svelte new file mode 100644 index 0000000000..638d80945d --- /dev/null +++ b/packages/builder/src/components/design/settings/controls/FormStepControls.svelte @@ -0,0 +1,84 @@ + + +{#if stepCount === 1} +
+ { + stepAction("addStep") + }} + > + Add Step + +
+{:else} +
+ { + stepAction("previousStep") + }} + tooltip={"Previous step"} + /> + { + stepAction("nextStep") + }} + tooltip={"Next step"} + /> + { + stepAction("removeStep") + }} + tooltip={"Remove step"} + /> + { + stepAction("addStep") + }} + tooltip={"Add step"} + /> +
+{/if} + + diff --git a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js index 72fdbe4108..e7b1727b54 100644 --- a/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js +++ b/packages/builder/src/components/design/settings/controls/GridColumnConfiguration/getColumns.js @@ -114,7 +114,7 @@ const getColumns = ({ primary, sortable, updateSortable: newDraggableList => { - onChange(toGridFormat(newDraggableList.concat(primary))) + onChange(toGridFormat(newDraggableList.concat(primary || []))) }, update: newEntry => { const newDraggableList = draggableList.map(entry => { diff --git a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte index a6f3d1b218..c20dd9310b 100644 --- a/packages/builder/src/components/design/settings/controls/PropertyControl.svelte +++ b/packages/builder/src/components/design/settings/controls/PropertyControl.svelte @@ -24,6 +24,7 @@ export let propertyFocus = false export let info = null export let disableBindings = false + export let wide $: nullishValue = value == null || value === "" $: allBindings = getAllBindings(bindings, componentBindings, nested) @@ -78,7 +79,7 @@
@@ -104,6 +105,7 @@ {...props} on:drawerHide on:drawerShow + on:meta />
{#if info} @@ -146,15 +148,28 @@ .control { position: relative; } - .property-control.wide .control { - grid-column: 1 / -1; - } .text { font-size: var(--spectrum-global-dimension-font-size-75); color: var(--grey-6); grid-column: 2 / 2; } + + .property-control.wide .control { + flex: 1; + } + .property-control.wide { + grid-template-columns: unset; + display: flex; + flex-direction: column; + width: 100%; + } + .property-control.wide > * { + width: 100%; + } .property-control.wide .text { grid-column: 1 / -1; } + .property-control.wide .label { + margin-bottom: -8px; + } diff --git a/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte b/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte index 7e63bf38df..0eb93732c3 100644 --- a/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte +++ b/packages/builder/src/components/design/settings/controls/RelationshipFilterEditor.svelte @@ -1,6 +1,9 @@