From 4fd74c3a19d4bc2c7e457e458aff1a7df528f3ab Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 9 Sep 2024 17:04:02 +0100 Subject: [PATCH 01/93] Add new access selection UI for tables and views --- .../bbui/src/InlineAlert/InlineAlert.svelte | 1 + packages/bbui/src/List/ListItem.svelte | 9 +- .../buttons/ManageAccessButton.svelte | 195 +++++++++++++++++- .../backend/DataTable/modals/EditRoles.svelte | 5 +- .../DataTable/modals/ManageAccessModal.svelte | 127 ------------ .../_components/Component/InfoDisplay.svelte | 35 ++-- packages/builder/src/stores/builder/roles.js | 19 +- 7 files changed, 239 insertions(+), 152 deletions(-) delete mode 100644 packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte diff --git a/packages/bbui/src/InlineAlert/InlineAlert.svelte b/packages/bbui/src/InlineAlert/InlineAlert.svelte index 3b98936f627..edfa760eb8a 100644 --- a/packages/bbui/src/InlineAlert/InlineAlert.svelte +++ b/packages/bbui/src/InlineAlert/InlineAlert.svelte @@ -8,6 +8,7 @@ export let onConfirm = undefined export let buttonText = "" export let cta = false + $: icon = selectIcon(type) // if newlines used, convert them to different elements $: split = message.split("\n") diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte index 2b1f6b030fc..abcdf2cc6e7 100644 --- a/packages/bbui/src/List/ListItem.svelte +++ b/packages/bbui/src/List/ListItem.svelte @@ -8,6 +8,7 @@ export let url = null export let hoverable = false export let showArrow = false + export let selected = false
{#if icon} @@ -43,7 +45,7 @@ diff --git a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte index 6cec28de1dc..e46a2aa3dc7 100644 --- a/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte @@ -17,7 +17,7 @@ let basePermissions = [] let selectedRole = BASE_ROLE let errors = [] - let builtInRoles = ["Admin", "Power", "Basic", "Public"] + let builtInRoles = ["App admin", "App power user", "App user", "Public user"] let validRegex = /^[a-zA-Z0-9_]*$/ // Don't allow editing of public role $: editableRoles = $roles.filter(role => role._id !== "PUBLIC") @@ -108,6 +108,9 @@ } const getRoleNameError = name => { + if (builtInRoles.includes(name)) { + return null + } const hasUniqueRoleName = !otherRoles ?.map(role => role.name) ?.includes(name) diff --git a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte b/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte deleted file mode 100644 index bc5c437c572..00000000000 --- a/packages/builder/src/components/backend/DataTable/modals/ManageAccessModal.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - -Specify the minimum access level role for this data. -
- - - {#each Object.keys(computedPermissions) as level} - - (tempLabel = e.detail)} + /> +
+ + (tempColor = e.detail)} /> +
+ + + diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index fd7216af772..a515e80de12 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -12,13 +12,13 @@ import { Roles } from "constants/backend" import { NodeWidth, NodeHeight } from "./constants" import { getContext, tick } from "svelte" - import { template } from "lodash" + import { autoLayout } from "./layout" export let data export let isConnectable export let id - const { autoLayout } = getContext("flow") + const { nodes, edges } = getContext("flow") const flow = useSvelteFlow() let anchor @@ -33,7 +33,7 @@ nodes: [{ id }], }) await tick() - autoLayout() + doAutoLayout() } const openPopover = () => { @@ -48,6 +48,13 @@ color: tempColor, }) } + + const doAutoLayout = () => { + const layout = autoLayout({ nodes: $nodes, edges: $edges }) + nodes.set(layout.nodes) + edges.set(layout.edges) + flow.fitView({ maxZoom: 1, duration: 300 }) + }
{ +// Generates a flow compatible structure of nodes and edges from the current roles +export const defaultLayout = () => { const ignoredRoles = [Roles.PUBLIC] const $roles = get(roles) const descriptions = { @@ -59,6 +60,7 @@ export const rolesToLayout = () => { } } +// Converts the flow structure of ndes and edges back into an array of roles export const layoutToRoles = ({ nodes, edges }) => { // Clone and wipe existing inheritance let newRoles = Helpers.cloneDeep(get(roles)).map(role => { @@ -92,7 +94,8 @@ export const layoutToRoles = ({ nodes, edges }) => { return newRoles } -export const dagreLayout = ({ nodes, edges }) => { +// Updates positions of nodes and edges into a nice graph structure +const dagreLayout = ({ nodes, edges }) => { const dagreGraph = new dagre.graphlib.Graph() dagreGraph.setDefaultEdgeLabel(() => ({})) dagreGraph.setGraph({ @@ -118,3 +121,40 @@ export const dagreLayout = ({ nodes, edges }) => { }) return { nodes, edges } } + +// Adds additional edges as needed to the flow structure to ensure compatibility with BB role logic +const sanitiseLayout = ({ nodes, edges }) => { + let additions = [] + + for (let node of nodes) { + // If a node does not inherit anything, let it inherit basic + if (!edges.some(x => x.target === node.id) && node.id !== Roles.BASIC) { + additions.push({ + id: Helpers.uuid(), + source: Roles.BASIC, + target: node.id, + animated: true, + }) + } + + // If a node is not inherited by anything, let it be inherited by admin + if (!edges.some(x => x.source === node.id) && node.id !== Roles.ADMIN) { + additions.push({ + id: Helpers.uuid(), + source: node.id, + target: Roles.ADMIN, + animated: true, + }) + } + } + + return { + nodes, + edges: [...edges, ...additions], + } +} + +// Automatically lays out the graph, sanitising and enriching the structure +export const autoLayout = ({ nodes, edges }) => { + return dagreLayout(sanitiseLayout({ nodes, edges })) +} diff --git a/packages/server/src/sdk/app/permissions/index.ts b/packages/server/src/sdk/app/permissions/index.ts new file mode 100644 index 00000000000..88058b508da --- /dev/null +++ b/packages/server/src/sdk/app/permissions/index.ts @@ -0,0 +1,105 @@ +import { db, roles } from "@budibase/backend-core" +import { features } from "@budibase/pro" +import { + DocumentType, + PermissionLevel, + PermissionSource, + PlanType, + VirtualDocumentType, +} from "@budibase/types" +import { extractViewInfoFromID, isViewID } from "../../../db/utils" +import { + CURRENTLY_SUPPORTED_LEVELS, + getBasePermissions, +} from "../../../utilities/security" +import sdk from "../../../sdk" +import { isV2 } from "../views" + +type ResourcePermissions = Record< + string, + { role: string; type: PermissionSource } +> + +export async function getInheritablePermissions( + resourceId: string +): Promise { + if (isViewID(resourceId)) { + return await getResourcePerms(extractViewInfoFromID(resourceId).tableId) + } +} + +export async function getResourcePerms( + resourceId: string +): Promise { + const rolesList = await roles.getAllRoles() + + let permissions: ResourcePermissions = {} + + const permsToInherit = await getInheritablePermissions(resourceId) + + for (let level of CURRENTLY_SUPPORTED_LEVELS) { + // update the various roleIds in the resource permissions + for (let role of rolesList) { + const rolePerms = roles.checkForRoleResourceArray( + role.permissions || {}, + resourceId + ) + if (rolePerms[resourceId]?.indexOf(level) > -1) { + permissions[level] = { + role: roles.getExternalRoleID(role._id!, role.version), + type: PermissionSource.EXPLICIT, + } + } else if ( + !permissions[level] && + permsToInherit && + permsToInherit[level] + ) { + permissions[level] = { + role: permsToInherit[level].role, + type: PermissionSource.INHERITED, + } + } + } + } + + const basePermissions = Object.entries( + getBasePermissions(resourceId) + ).reduce((p, [level, role]) => { + p[level] = { role, type: PermissionSource.BASE } + return p + }, {}) + const result = Object.assign(basePermissions, permissions) + return result +} + +export async function getDependantResources( + resourceId: string +): Promise | undefined> { + if (db.isTableId(resourceId)) { + const dependants: Record> = {} + + const table = await sdk.tables.getTable(resourceId) + const views = Object.values(table.views || {}) + + for (const view of views) { + if (!isV2(view)) { + continue + } + + const permissions = await getResourcePerms(view.id) + for (const [, roleInfo] of Object.entries(permissions)) { + if (roleInfo.type === PermissionSource.INHERITED) { + dependants[VirtualDocumentType.VIEW] ??= new Set() + dependants[VirtualDocumentType.VIEW].add(view.id) + } + } + } + + return Object.entries(dependants).reduce((p, [type, resources]) => { + p[type] = resources.size + return p + }, {} as Record) + } + + return +} From a5520a973cddb672cfeb806cc918cb00df867d00 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 13:11:41 +0100 Subject: [PATCH 10/93] Use display names and allow descriptions to be edited --- .../backend/RoleEditor/Controls.svelte | 3 ++- .../backend/RoleEditor/RoleNode.svelte | 26 ++++++++++++------- .../components/backend/RoleEditor/layout.js | 2 +- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index eb7b5c046c5..aa0b0112d8f 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -17,9 +17,10 @@ targetPosition: Position.Left, type: "role", data: { - label: "New role", + displayName: "New role", description: "Custom role", custom: true, + color: "var(--spectrum-global-color-gray-700)", }, position: { x: 0, y: 0 }, }, diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index a515e80de12..aaf228638dc 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -23,7 +23,8 @@ let anchor let modal - let tempLabel + let tempDisplayName + let tempDescription let tempColor $: color = data.color || RoleUtils.getRoleColour(id) @@ -37,14 +38,16 @@ } const openPopover = () => { - tempLabel = data.label + tempDisplayName = data.displayName + tempDescription = data.description tempColor = color modal.show() } const saveChanges = () => { flow.updateNodeData(id, { - label: tempLabel, + displayName: tempDisplayName, + description: tempDescription, color: tempColor, }) } @@ -66,8 +69,8 @@
-
- {data.label} +
+ {data.displayName}
{#if data.custom}
@@ -92,14 +95,19 @@ (tempLabel = e.detail)} + value={tempDisplayName} + on:change={e => (tempDisplayName = e.detail)} + /> + (tempDescription = e.detail)} />
@@ -160,7 +168,7 @@ .title :global(.spectrum-Icon) { color: var(--spectrum-global-color-gray-600); } - .label { + .name { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index 84ac34ec033..4bb6d1bb056 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -29,7 +29,7 @@ export const defaultLayout = () => { targetPosition: Position.Left, type: "role", data: { - label: role.name, + displayName: role.displayName || role.name || "", description: descriptions[role._id] || "Custom role", color: role.color, custom: !role._id.match(/[A-Z]+/), From a81d9c6dd1bb9b63da683c9e171abf737b1a1afa Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 15:42:05 +0100 Subject: [PATCH 11/93] Add display name, color and descriptions to roles. Allow row CRUD via new UI --- packages/backend-core/src/security/roles.ts | 44 ++++++++-- .../backend/RoleEditor/Controls.svelte | 34 ++++---- .../backend/RoleEditor/RoleEditor.svelte | 4 +- .../backend/RoleEditor/RoleNode.svelte | 13 +-- .../components/backend/RoleEditor/layout.js | 83 +++++++------------ .../src/components/common/RoleIcon.svelte | 5 +- .../src/components/common/RoleSelect.svelte | 2 +- .../settings/controls/RoleSelect.svelte | 3 +- .../ScreenList/RoleIndicator.svelte | 2 +- .../_components/AppRoleTableRenderer.svelte | 8 +- packages/builder/src/stores/builder/roles.js | 27 ++---- packages/frontend-core/src/utils/roles.js | 13 --- packages/server/src/api/controllers/role.ts | 19 ++++- .../server/src/api/routes/utils/validators.ts | 3 + packages/types/src/api/web/role.ts | 3 + packages/types/src/documents/app/role.ts | 3 + 16 files changed, 141 insertions(+), 125 deletions(-) diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index a64be6b3197..7fb4eefc8ba 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -45,10 +45,22 @@ export class Role implements RoleDoc { inherits?: string version?: string permissions = {} + displayName?: string + color?: string + description?: string - constructor(id: string, name: string, permissionId: string) { + constructor( + id: string, + displayName: string, + description: string, + color: string, + permissionId: string + ) { this._id = id - this.name = name + this.name = id + this.displayName = displayName + this.color = color + this.description = description this.permissionId = permissionId // version for managing the ID - removing the role_ when responding this.version = RoleIDVersion.NAME @@ -63,21 +75,39 @@ export class Role implements RoleDoc { const BUILTIN_ROLES = { ADMIN: new Role( BUILTIN_IDS.ADMIN, - "Admin", + "App admin", + "Can do everything", + "var(--spectrum-global-color-static-red-400)", BuiltinPermissionID.ADMIN ).addInheritance(BUILTIN_IDS.POWER), POWER: new Role( BUILTIN_IDS.POWER, - "Power", + "App power user", + "An app user with more access", + "var(--spectrum-global-color-static-orange-400)", BuiltinPermissionID.POWER ).addInheritance(BUILTIN_IDS.BASIC), BASIC: new Role( BUILTIN_IDS.BASIC, - "Basic", + "App user", + "Any logged in user", + "var(--spectrum-global-color-static-green-400)", BuiltinPermissionID.WRITE ).addInheritance(BUILTIN_IDS.PUBLIC), - PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public", BuiltinPermissionID.PUBLIC), - BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder", BuiltinPermissionID.ADMIN), + PUBLIC: new Role( + BUILTIN_IDS.PUBLIC, + "Public user", + "Accessible to anyone", + "var(--spectrum-global-color-static-blue-400)", + BuiltinPermissionID.PUBLIC + ), + BUILDER: new Role( + BUILTIN_IDS.BUILDER, + "Builder user", + "Users that can edit this app", + "var(--spectrum-global-color-static-magenta-600)", + BuiltinPermissionID.ADMIN + ), } export function getBuiltinRoles(): { [key: string]: RoleDoc } { diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index aa0b0112d8f..94c82dcd65b 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -2,29 +2,28 @@ import { Button, Helpers, ActionButton } from "@budibase/bbui" import { useSvelteFlow, Position } from "@xyflow/svelte" import { getContext, tick } from "svelte" - import { autoLayout } from "./layout" + import { autoLayout, roleToNode } from "./layout" import { ZoomDuration } from "./constants" + import { getSequentialName } from "helpers/duplicate" + import { roles } from "stores/builder" + import { Roles } from "constants/backend" const { nodes, edges } = getContext("flow") const flow = useSvelteFlow() const addRole = async () => { - nodes.update(state => [ - ...state, - { - id: Helpers.uuid(), - sourcePosition: Position.Right, - targetPosition: Position.Left, - type: "role", - data: { - displayName: "New role", - description: "Custom role", - custom: true, - color: "var(--spectrum-global-color-gray-700)", - }, - position: { x: 0, y: 0 }, - }, - ]) + const role = { + name: Helpers.uuid(), + displayName: getSequentialName($nodes, "New role ", { + getName: x => x.data.displayName, + }), + color: "var(--spectrum-global-color-gray-700)", + description: "Custom role", + permissionId: "write", + inherits: Roles.BASIC, + } + const savedRole = await roles.save(role) + nodes.update(state => [...state, roleToNode(savedRole)]) await doAutoLayout() } @@ -36,7 +35,6 @@ flow.fitView({ maxZoom: 1, duration: ZoomDuration, - includeHiddenNodes: true, }) } diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index 42c18934cfc..9904a8877b8 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -4,7 +4,7 @@ import { SvelteFlow, Background, BackgroundVariant } from "@xyflow/svelte" import "@xyflow/svelte/dist/style.css" import RoleNode from "./RoleNode.svelte" - import { defaultLayout, autoLayout } from "./layout" + import { rolesToNodes, autoLayout } from "./layout" import { onMount, setContext } from "svelte" import Controls from "./Controls.svelte" @@ -14,7 +14,7 @@ setContext("flow", { nodes, edges }) onMount(() => { - const layout = autoLayout(defaultLayout()) + const layout = autoLayout(rolesToNodes()) nodes.set(layout.nodes) edges.set(layout.edges) }) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index aaf228638dc..42a107ee2e6 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -12,7 +12,8 @@ import { Roles } from "constants/backend" import { NodeWidth, NodeHeight } from "./constants" import { getContext, tick } from "svelte" - import { autoLayout } from "./layout" + import { autoLayout, nodeToRole } from "./layout" + import { roles } from "stores/builder" export let data export let isConnectable @@ -27,9 +28,8 @@ let tempDescription let tempColor - $: color = data.color || RoleUtils.getRoleColour(id) - const deleteNode = async () => { + await roles.delete(nodeToRole({ id, data })) flow.deleteElements({ nodes: [{ id }], }) @@ -40,11 +40,12 @@ const openPopover = () => { tempDisplayName = data.displayName tempDescription = data.description - tempColor = color + tempColor = data.color modal.show() } - const saveChanges = () => { + const saveChanges = async () => { + await roles.save(nodeToRole({ id, data })) flow.updateNodeData(id, { displayName: tempDisplayName, description: tempDescription, @@ -63,7 +64,7 @@
diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index 4bb6d1bb056..b8a5ae7d5a7 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -6,15 +6,36 @@ import { Roles } from "constants/backend" import { get } from "svelte/store" import { Helpers } from "@budibase/bbui" +// Converts a role doc into a node structure +export const roleToNode = role => ({ + id: role._id, + sourcePosition: Position.Right, + targetPosition: Position.Left, + type: "role", + position: { x: 0, y: 0 }, + data: { + displayName: role.displayName || role.name, + description: role.description || "Custom role", + color: role.color || "var(--spectrum-global-color-static-magenta-400)", + custom: !role._id.match(/[A-Z]+/), + }, +}) + +// Converts a node structure back into a role doc +export const nodeToRole = node => { + const role = get(roles).find(x => x._id === node.id) + return { + ...role, + displayName: node.data.displayName, + color: node.data.color, + description: node.data.description, + } +} + // Generates a flow compatible structure of nodes and edges from the current roles -export const defaultLayout = () => { +export const rolesToNodes = () => { const ignoredRoles = [Roles.PUBLIC] const $roles = get(roles) - const descriptions = { - [Roles.BASIC]: "Basic user", - [Roles.POWER]: "Power user", - [Roles.ADMIN]: "Can do everything", - } let nodes = [] let edges = [] @@ -23,19 +44,11 @@ export const defaultLayout = () => { if (ignoredRoles.includes(role._id)) { continue } - nodes.push({ - id: role._id, - sourcePosition: Position.Right, - targetPosition: Position.Left, - type: "role", - data: { - displayName: role.displayName || role.name || "", - description: descriptions[role._id] || "Custom role", - color: role.color, - custom: !role._id.match(/[A-Z]+/), - }, - }) + // Add node for this role + nodes.push(roleToNode(role)) + + // Add edges for this role let inherits = [] if (role.inherits) { inherits = Array.isArray(role.inherits) ? role.inherits : [role.inherits] @@ -60,40 +73,6 @@ export const defaultLayout = () => { } } -// Converts the flow structure of ndes and edges back into an array of roles -export const layoutToRoles = ({ nodes, edges }) => { - // Clone and wipe existing inheritance - let newRoles = Helpers.cloneDeep(get(roles)).map(role => { - return { ...role, inherits: [] } - }) - - // Copy over names and colours - for (let node of nodes) { - let role = newRoles.find(x => x._id === node.id) - if (role) { - role.name = node.data.label - role.color = node.data.color - } else { - // New role - } - } - - // Build inheritance - for (let edge of edges) { - let role = newRoles.find(x => x._id === edge.target) - if (role) { - role.inherits.push(edge.source) - } else { - // New role - } - } - - // Ensure basic is correct - newRoles.find(x => x._id === Roles.BASIC).inherits = [Roles.BASIC] - - return newRoles -} - // Updates positions of nodes and edges into a nice graph structure const dagreLayout = ({ nodes, edges }) => { const dagreGraph = new dagre.graphlib.Graph() diff --git a/packages/builder/src/components/common/RoleIcon.svelte b/packages/builder/src/components/common/RoleIcon.svelte index 1bd6ba49bcc..567a7c0b5da 100644 --- a/packages/builder/src/components/common/RoleIcon.svelte +++ b/packages/builder/src/components/common/RoleIcon.svelte @@ -1,12 +1,15 @@ diff --git a/packages/builder/src/components/common/RoleSelect.svelte b/packages/builder/src/components/common/RoleSelect.svelte index 4605b0c182d..72495fe3c95 100644 --- a/packages/builder/src/components/common/RoleSelect.svelte +++ b/packages/builder/src/components/common/RoleSelect.svelte @@ -99,7 +99,7 @@ if (role._id === Constants.Roles.CREATOR || role._id === RemoveID) { return null } - return RoleUtils.getRoleColour(role._id) + return role.color || "var(--spectrum-global-color-static-magenta-400)" } const getIcon = role => { diff --git a/packages/builder/src/components/design/settings/controls/RoleSelect.svelte b/packages/builder/src/components/design/settings/controls/RoleSelect.svelte index 5b5daac1830..973a239bd8f 100644 --- a/packages/builder/src/components/design/settings/controls/RoleSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/RoleSelect.svelte @@ -14,7 +14,8 @@ options={$roles} getOptionLabel={role => role.name} getOptionValue={role => role._id} - getOptionColour={role => RoleUtils.getRoleColour(role._id)} + getOptionColour={role => + role.color || "var(--spectrum-global-color-static-magenta-400)"} {placeholder} {error} /> diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte index 4b7f26709c8..994a56e833c 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte @@ -8,7 +8,7 @@ let showTooltip = false - $: color = RoleUtils.getRoleColour(roleId) + $: color = role.color || "var(--spectrum-global-color-static-magenta-400)" $: role = $roles.find(role => role._id === roleId) $: tooltip = roleId === Roles.PUBLIC diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte index 0f19bb3e1f9..85d94ba3508 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte @@ -6,8 +6,9 @@ export let value + $: role = $roles.find(x => x._id === roleId) + const getRoleLabel = roleId => { - const role = $roles.find(x => x._id === roleId) return roleId === Constants.Roles.CREATOR ? capitalise(Constants.Roles.CREATOR.toLowerCase()) : role?.name || "Custom role" @@ -17,7 +18,10 @@ {#if value === Constants.Roles.CREATOR} Can edit {:else} - + Can use as {getRoleLabel(value)} {/if} diff --git a/packages/builder/src/stores/builder/roles.js b/packages/builder/src/stores/builder/roles.js index 8f1bfdc177a..a438abac567 100644 --- a/packages/builder/src/stores/builder/roles.js +++ b/packages/builder/src/stores/builder/roles.js @@ -1,14 +1,6 @@ import { writable } from "svelte/store" import { API } from "api" import { RoleUtils } from "@budibase/frontend-core" -import { Roles } from "constants/backend" - -const ROLE_NAMES = { - [Roles.ADMIN]: "App admin", - [Roles.POWER]: "App power user", - [Roles.BASIC]: "App user", - [Roles.PUBLIC]: "Public user", -} export function createRolesStore() { const { subscribe, update, set } = writable([]) @@ -25,16 +17,7 @@ export function createRolesStore() { const actions = { fetch: async () => { - let roles = await API.getRoles() - - // Update labels - for (let [roleId, name] of Object.entries(ROLE_NAMES)) { - const idx = roles.findIndex(x => x._id === roleId) - if (idx !== -1) { - roles[idx].name = name - } - } - + const roles = await API.getRoles() setRoles(roles) }, fetchByAppId: async appId => { @@ -51,7 +34,13 @@ export function createRolesStore() { save: async role => { const savedRole = await API.saveRole(role) await actions.fetch() - return savedRole + + // When saving a role we get back an _id prefixed by role_, but the API does not want this + // in future requests + return { + ...savedRole, + _id: savedRole._id.replace("role_", ""), + } }, } diff --git a/packages/frontend-core/src/utils/roles.js b/packages/frontend-core/src/utils/roles.js index 1ae9d3ac142..913d452e7cd 100644 --- a/packages/frontend-core/src/utils/roles.js +++ b/packages/frontend-core/src/utils/roles.js @@ -7,20 +7,7 @@ const RolePriorities = { [Roles.BASIC]: 2, [Roles.PUBLIC]: 1, } -const RoleColours = { - [Roles.ADMIN]: "var(--spectrum-global-color-static-red-400)", - [Roles.CREATOR]: "var(--spectrum-global-color-static-magenta-600)", - [Roles.POWER]: "var(--spectrum-global-color-static-orange-400)", - [Roles.BASIC]: "var(--spectrum-global-color-static-green-400)", - [Roles.PUBLIC]: "var(--spectrum-global-color-static-blue-400)", -} export const getRolePriority = role => { return RolePriorities[role] ?? 0 } - -export const getRoleColour = roleId => { - return ( - RoleColours[roleId] ?? "var(--spectrum-global-color-static-magenta-400)" - ) -} diff --git a/packages/server/src/api/controllers/role.ts b/packages/server/src/api/controllers/role.ts index 3398c8102ce..9c7fb2859ab 100644 --- a/packages/server/src/api/controllers/role.ts +++ b/packages/server/src/api/controllers/role.ts @@ -62,7 +62,16 @@ export async function find(ctx: UserCtx) { export async function save(ctx: UserCtx) { const db = context.getAppDB() - let { _id, name, inherits, permissionId, version } = ctx.request.body + let { + _id, + name, + displayName, + description, + color, + inherits, + permissionId, + version, + } = ctx.request.body let isCreate = false const isNewVersion = version === roles.RoleIDVersion.NAME @@ -88,7 +97,13 @@ export async function save(ctx: UserCtx) { ctx.throw(400, "Cannot change custom role name") } - const role = new roles.Role(_id, name, permissionId).addInheritance(inherits) + const role = new roles.Role( + _id, + displayName || name, + description || "Custom role", + color || "var(--spectrum-global-color-static-magenta-400)", + permissionId + ).addInheritance(inherits) if (ctx.request.body._rev) { role._rev = ctx.request.body._rev } diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts index 5e2a585b4af..dd943f12825 100644 --- a/packages/server/src/api/routes/utils/validators.ts +++ b/packages/server/src/api/routes/utils/validators.ts @@ -208,6 +208,9 @@ export function roleValidator() { name: Joi.string() .regex(/^[a-zA-Z0-9_]*$/) .required(), + displayName: Joi.string().optional(), + color: Joi.string().optional(), + description: Joi.string().optional(), // this is the base permission ID (for now a built in) permissionId: Joi.string() .valid(...Object.values(permissions.BuiltinPermissionID)) diff --git a/packages/types/src/api/web/role.ts b/packages/types/src/api/web/role.ts index c37dee60e0f..7782c332156 100644 --- a/packages/types/src/api/web/role.ts +++ b/packages/types/src/api/web/role.ts @@ -4,6 +4,9 @@ export interface SaveRoleRequest { _id?: string _rev?: string name: string + displayName?: string + color?: string + description?: string inherits: string permissionId: string version: string diff --git a/packages/types/src/documents/app/role.ts b/packages/types/src/documents/app/role.ts index f32ba810b00..0169da90c2a 100644 --- a/packages/types/src/documents/app/role.ts +++ b/packages/types/src/documents/app/role.ts @@ -6,4 +6,7 @@ export interface Role extends Document { permissions: { [key: string]: string[] } version?: string name: string + displayName?: string + color?: string + description?: string } From de7604f8fd786176ea66b1bb94d09cfbedcea46b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 15:55:48 +0100 Subject: [PATCH 12/93] Fix role CRUD --- .../builder/src/components/backend/RoleEditor/RoleNode.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 42a107ee2e6..d7db4b72ba9 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -29,12 +29,12 @@ let tempColor const deleteNode = async () => { - await roles.delete(nodeToRole({ id, data })) flow.deleteElements({ nodes: [{ id }], }) await tick() doAutoLayout() + await roles.delete(nodeToRole({ id, data })) } const openPopover = () => { @@ -45,12 +45,12 @@ } const saveChanges = async () => { - await roles.save(nodeToRole({ id, data })) flow.updateNodeData(id, { displayName: tempDisplayName, description: tempDescription, color: tempColor, }) + await roles.save(nodeToRole({ id, data })) } const doAutoLayout = () => { From 0fd38927e2311d9581a1be749244be3842ee6e42 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 15:56:04 +0100 Subject: [PATCH 13/93] Remove log --- .../builder/src/components/backend/RoleEditor/RoleEditor.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index 9904a8877b8..783dc5a108b 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -36,7 +36,6 @@ proOptions={{ hideAttribution: true }} fitViewOptions={{ maxZoom: 1 }} defaultEdgeOptions={{ type: "bezier", animated: true }} - on:nodeclick={event => console.log("on node click", event.detail.node)} > From e47d25cb48d739660dce34cf5646ab40afdbfd72 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 16:20:23 +0100 Subject: [PATCH 14/93] Fix role updating and add custom role type to grid --- .../backend/RoleEditor/RoleNode.svelte | 12 ++--- .../data/table/[tableId]/index.svelte | 15 ++++--- .../src/components/grid/cells/DataCell.svelte | 2 +- .../src/components/grid/cells/RoleCell.svelte | 44 +++++++++++++++++++ .../src/components/grid/lib/renderers.js | 4 ++ .../src/components/grid/stores/columns.js | 1 + 6 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 packages/frontend-core/src/components/grid/cells/RoleCell.svelte diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index d7db4b72ba9..eda225b604f 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -45,12 +45,13 @@ } const saveChanges = async () => { - flow.updateNodeData(id, { + const newData = { displayName: tempDisplayName, description: tempDescription, color: tempColor, - }) - await roles.save(nodeToRole({ id, data })) + } + flow.updateNodeData(id, newData) + await roles.save(nodeToRole({ id, data: newData })) } const doAutoLayout = () => { @@ -81,7 +82,7 @@ {/if}
{#if data.description} -
+
{data.description}
{/if} @@ -169,7 +170,8 @@ .title :global(.spectrum-Icon) { color: var(--spectrum-global-color-gray-600); } - .name { + .name, + .description { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte index b9b58cbfce8..93ec3d07648 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte @@ -6,6 +6,7 @@ integrations, appStore, rowActions, + roles, } from "stores/builder" import { themeStore, admin } from "stores/portal" import { TableNames } from "constants" @@ -26,16 +27,20 @@ import GridRowActionsButton from "components/backend/DataTable/buttons/grid/GridRowActionsButton.svelte" import { DB_TYPE_EXTERNAL } from "constants/backend" - const userSchemaOverrides = { + let generateButton + + $: userSchemaOverrides = { 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 }, + roleId: { + displayName: "Role", + type: "role", + disabled: true, + roles: $roles, + }, } - - let generateButton - $: autoColumnStatus = verifyAutocolumns($tables?.selected) $: duplicates = Object.values(autoColumnStatus).reduce((acc, status) => { if (status.length > 1) { diff --git a/packages/frontend-core/src/components/grid/cells/DataCell.svelte b/packages/frontend-core/src/components/grid/cells/DataCell.svelte index 5d5f06872d0..cd6b61af800 100644 --- a/packages/frontend-core/src/components/grid/cells/DataCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/DataCell.svelte @@ -66,7 +66,7 @@ focus: () => api?.focus?.(), blur: () => api?.blur?.(), isActive: () => api?.isActive?.() ?? false, - onKeyDown: (...params) => api?.onKeyDown(...params), + onKeyDown: (...params) => api?.onKeyDown?.(...params), isReadonly: () => readonly, getType: () => column.schema.type, getValue: () => row[column.name], diff --git a/packages/frontend-core/src/components/grid/cells/RoleCell.svelte b/packages/frontend-core/src/components/grid/cells/RoleCell.svelte new file mode 100644 index 00000000000..b638d0e0e3f --- /dev/null +++ b/packages/frontend-core/src/components/grid/cells/RoleCell.svelte @@ -0,0 +1,44 @@ + + +
+
+ +
+
+ {role?.displayName || role?.name || value} +
+
+ + diff --git a/packages/frontend-core/src/components/grid/lib/renderers.js b/packages/frontend-core/src/components/grid/lib/renderers.js index 2e22ee18711..607240b7a2c 100644 --- a/packages/frontend-core/src/components/grid/lib/renderers.js +++ b/packages/frontend-core/src/components/grid/lib/renderers.js @@ -15,6 +15,7 @@ import AttachmentSingleCell from "../cells/AttachmentSingleCell.svelte" import BBReferenceCell from "../cells/BBReferenceCell.svelte" import SignatureCell from "../cells/SignatureCell.svelte" import BBReferenceSingleCell from "../cells/BBReferenceSingleCell.svelte" +import RoleCell from "../cells/RoleCell.svelte" const TypeComponentMap = { [FieldType.STRING]: TextCell, @@ -33,6 +34,9 @@ const TypeComponentMap = { [FieldType.JSON]: JSONCell, [FieldType.BB_REFERENCE]: BBReferenceCell, [FieldType.BB_REFERENCE_SINGLE]: BBReferenceSingleCell, + + // Custom types for UI only + role: RoleCell, } export const getCellRenderer = column => { return TypeComponentMap[column?.schema?.type] || TextCell diff --git a/packages/frontend-core/src/components/grid/stores/columns.js b/packages/frontend-core/src/components/grid/stores/columns.js index 0073754a5d3..61d1f41fc8a 100644 --- a/packages/frontend-core/src/components/grid/stores/columns.js +++ b/packages/frontend-core/src/components/grid/stores/columns.js @@ -145,6 +145,7 @@ export const initialise = context => { readonly: fieldSchema.readonly, order: fieldSchema.order ?? oldColumn?.order, conditions: fieldSchema.conditions, + enrichValue: fieldSchema.enrichValue, } // Override a few properties for primary display if (field === primaryDisplay) { From 7e6f1407fe142cc26808fd5c41bc71706f5570f1 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 16:30:17 +0100 Subject: [PATCH 15/93] Ensure roles always have new metadta and update access popover --- packages/bbui/src/List/ListItem.svelte | 5 +- .../buttons/ManageAccessButton.svelte | 57 +++++++++---------- .../components/backend/RoleEditor/layout.js | 6 +- packages/builder/src/stores/builder/roles.js | 18 ++++-- 4 files changed, 47 insertions(+), 39 deletions(-) diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte index abcdf2cc6e7..64f646cae8e 100644 --- a/packages/bbui/src/List/ListItem.svelte +++ b/packages/bbui/src/List/ListItem.svelte @@ -1,5 +1,6 @@ From 641982948490f28ff6468d7b208df86ddb1121ea Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 11 Sep 2024 16:51:36 +0100 Subject: [PATCH 18/93] Update other usages of roles to use new metadata --- .../buttons/ManageAccessButton.svelte | 7 - .../DataTable/modals/CreateEditUser.svelte | 2 +- .../backend/DataTable/modals/EditRoles.svelte | 177 ------------------ packages/builder/src/dataBinding.js | 4 +- 4 files changed, 3 insertions(+), 187 deletions(-) delete mode 100644 packages/builder/src/components/backend/DataTable/modals/EditRoles.svelte diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index 4e52a606385..fe24f47cf1b 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -185,13 +185,6 @@ {#if dependantsInfoMessage} {/if} - -
- (showPopover = false)} - on:hide={() => (showPopover = true)} - /> -
diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte deleted file mode 100644 index 9ad41ad6528..00000000000 --- a/packages/builder/src/pages/builder/portal/users/users/_components/UpdateRolesModal.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Update {user.email}'s role for {app.name}. - - (tempDisplayName = e.detail)} /> (tempDescription = e.detail)} />
From 35bdc998ca77b6035de5a389afb8472195a4f1f0 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 10:33:30 +0100 Subject: [PATCH 23/93] Fix roles store update --- packages/builder/src/stores/builder/roles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/stores/builder/roles.js b/packages/builder/src/stores/builder/roles.js index bee0bd9372d..ef4480c6d24 100644 --- a/packages/builder/src/stores/builder/roles.js +++ b/packages/builder/src/stores/builder/roles.js @@ -39,7 +39,7 @@ export function createRolesStore() { roleId: role?._id, roleRev: role?._rev, }) - update(state => state.filter(existing => existing._id !== role._id)) + store.update(state => state.filter(existing => existing._id !== role._id)) }, save: async role => { const savedRole = await API.saveRole(role) From 6f9175168bf377f9f861e3f9da01e86160561831 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 12:23:27 +0100 Subject: [PATCH 24/93] Add custom RBAC edges with inline deletion icon --- .../backend/RoleEditor/RoleEdge.svelte | 114 ++++++++++++++++++ .../backend/RoleEditor/RoleEditor.svelte | 11 +- .../backend/RoleEditor/RoleNode.svelte | 20 ++- .../components/backend/RoleEditor/layout.js | 4 +- 4 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte new file mode 100644 index 00000000000..b4076e22810 --- /dev/null +++ b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte @@ -0,0 +1,114 @@ + + + + + + + +
(labelHovered = true)} + on:mouseout={() => (labelHovered = false)} + > + +
+
+ + diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index 783dc5a108b..d24810f468e 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -4,14 +4,16 @@ import { SvelteFlow, Background, BackgroundVariant } from "@xyflow/svelte" import "@xyflow/svelte/dist/style.css" import RoleNode from "./RoleNode.svelte" + import RoleEdge from "./RoleEdge.svelte" import { rolesToNodes, autoLayout } from "./layout" import { onMount, setContext } from "svelte" import Controls from "./Controls.svelte" const nodes = writable([]) const edges = writable([]) + const dragging = writable(false) - setContext("flow", { nodes, edges }) + setContext("flow", { nodes, edges, dragging }) onMount(() => { const layout = autoLayout(rolesToNodes()) @@ -33,9 +35,12 @@ {edges} snapGrid={[25, 25]} nodeTypes={{ role: RoleNode }} + edgeTypes={{ role: RoleEdge }} proOptions={{ hideAttribution: true }} fitViewOptions={{ maxZoom: 1 }} - defaultEdgeOptions={{ type: "bezier", animated: true }} + defaultEdgeOptions={{ type: "role", animated: true, selectable: false }} + onconnectstart={() => dragging.set(true)} + onconnectend={() => dragging.set(false)} > @@ -81,6 +86,6 @@ /* Edges */ --xy-edge-stroke: var(--edge-color); - --xy-edge-stroke-selected: var(--selected-color); + --xy-edge-stroke-selected: var(--edge-color); } diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index e07ab70ff6c..2614d609f61 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -16,10 +16,9 @@ import { roles } from "stores/builder" export let data - export let isConnectable export let id - const { nodes, edges } = getContext("flow") + const { nodes, edges, dragging } = getContext("flow") const flow = useSvelteFlow() let anchor @@ -31,6 +30,7 @@ $: nameError = validateName(tempDisplayName, $roles) $: descriptionError = validateDescription(tempDescription) $: invalid = nameError || descriptionError + $: targetClasses = `target${$dragging ? "" : " hidden"}` const validateName = (name, roles) => { if (!name?.length) { @@ -109,10 +109,15 @@ {/if}
{#if id !== Roles.BASIC} - + {/if} {#if id !== Roles.ADMIN} - + {/if}
@@ -207,4 +212,11 @@ .node:hover .buttons { display: flex; } + .node :global(.svelte-flow__handle.target) { + background: var(--background-color); + } + .node :global(.svelte-flow__handle.hidden) { + opacity: 0; + pointer-events: none; + } diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index 5dae9a3b5c3..d721549d48b 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -62,7 +62,6 @@ export const rolesToNodes = () => { id: `${sourceRole}-${role._id}`, source: sourceRole, target: role._id, - animated: true, }) } } @@ -79,7 +78,7 @@ const dagreLayout = ({ nodes, edges }) => { dagreGraph.setDefaultEdgeLabel(() => ({})) dagreGraph.setGraph({ rankdir: "LR", - ranksep: 100, + ranksep: 200, nodesep: 100, }) nodes.forEach(node => { @@ -122,7 +121,6 @@ const sanitiseLayout = ({ nodes, edges }) => { id: Helpers.uuid(), source: node.id, target: Roles.ADMIN, - animated: true, }) } } From d80d38196fb4e2be36f47ec8a50701a6ce36af95 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 15:53:25 +0100 Subject: [PATCH 25/93] Update styles of handles and edges --- .../src/components/backend/RoleEditor/RoleEdge.svelte | 5 ++++- .../src/components/backend/RoleEditor/RoleEditor.svelte | 3 +++ .../src/components/backend/RoleEditor/RoleNode.svelte | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte index b4076e22810..243e4b988bf 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte @@ -5,7 +5,7 @@ EdgeLabelRenderer, useSvelteFlow, } from "@xyflow/svelte" - import { Icon } from "@budibase/bbui" + import { Icon, ActionButton } from "@budibase/bbui" import { onMount } from "svelte" export let sourceX @@ -104,6 +104,9 @@ background: var(--background-color); color: var(--spectrum-global-color-gray-600); } + .edge-label :global(svg) { + padding: 8px; + } :global(.svelte-flow__edge:hover .svelte-flow__edge-path), :global(.svelte-flow__edge-path.hovered) { stroke: var(--spectrum-global-color-blue-400); diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index d24810f468e..e64ac7d9353 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -70,6 +70,8 @@ --handle-color: var(--spectrum-global-color-gray-600); --selected-color: var(--spectrum-global-color-blue-400); } + + /* Customise svelte-flow theme */ .flow :global(.svelte-flow) { /* Panel */ --xy-background-color: var(--background-color); @@ -87,5 +89,6 @@ /* Edges */ --xy-edge-stroke: var(--edge-color); --xy-edge-stroke-selected: var(--edge-color); + --xy-edge-stroke-width: 2px; } diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 2614d609f61..97ded3c2ea2 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -212,6 +212,11 @@ .node:hover .buttons { display: flex; } + .node :global(.svelte-flow__handle) { + width: 6px; + height: 6px; + border-width: 2px; + } .node :global(.svelte-flow__handle.target) { background: var(--background-color); } From d4451a487cb8afab1cca561c4f4c089fb965f982 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 16:09:37 +0100 Subject: [PATCH 26/93] Increase max auto zoom and add more constants --- .../src/components/backend/RoleEditor/Controls.svelte | 7 ++++--- .../src/components/backend/RoleEditor/RoleNode.svelte | 4 ++-- .../builder/src/components/backend/RoleEditor/constants.js | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index 94c82dcd65b..8ac8ce26686 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -3,7 +3,7 @@ import { useSvelteFlow, Position } from "@xyflow/svelte" import { getContext, tick } from "svelte" import { autoLayout, roleToNode } from "./layout" - import { ZoomDuration } from "./constants" + import { MaxAutoZoom, ZoomDuration } from "./constants" import { getSequentialName } from "helpers/duplicate" import { roles } from "stores/builder" import { Roles } from "constants/backend" @@ -33,7 +33,7 @@ edges.set(layout.edges) await tick() flow.fitView({ - maxZoom: 1, + maxZoom: MaxAutoZoom, duration: ZoomDuration, }) } @@ -54,7 +54,8 @@
diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 97ded3c2ea2..69d75d9a578 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -10,7 +10,7 @@ FieldLabel, } from "@budibase/bbui" import { Roles } from "constants/backend" - import { NodeWidth, NodeHeight } from "./constants" + import { NodeWidth, NodeHeight, MaxAutoZoom, ZoomDuration } from "./constants" import { getContext, tick } from "svelte" import { autoLayout, nodeToRole } from "./layout" import { roles } from "stores/builder" @@ -79,7 +79,7 @@ const layout = autoLayout({ nodes: $nodes, edges: $edges }) nodes.set(layout.nodes) edges.set(layout.edges) - flow.fitView({ maxZoom: 1, duration: 300 }) + flow.fitView({ maxZoom: MaxAutoZoom, duration: ZoomDuration }) } diff --git a/packages/builder/src/components/backend/RoleEditor/constants.js b/packages/builder/src/components/backend/RoleEditor/constants.js index 76faf284825..d2fb813830e 100644 --- a/packages/builder/src/components/backend/RoleEditor/constants.js +++ b/packages/builder/src/components/backend/RoleEditor/constants.js @@ -1,3 +1,4 @@ export const NodeWidth = 220 export const NodeHeight = 66 export const ZoomDuration = 300 +export const MaxAutoZoom = 1.2 From 87206b1c4320f09906192b728d77c7476b253634 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 16:11:37 +0100 Subject: [PATCH 27/93] Update node spacing --- .../src/components/backend/RoleEditor/RoleEditor.svelte | 3 ++- packages/builder/src/components/backend/RoleEditor/layout.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index e64ac7d9353..01f87b6dea0 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -8,6 +8,7 @@ import { rolesToNodes, autoLayout } from "./layout" import { onMount, setContext } from "svelte" import Controls from "./Controls.svelte" + import { MaxAutoZoom } from "./constants" const nodes = writable([]) const edges = writable([]) @@ -37,7 +38,7 @@ nodeTypes={{ role: RoleNode }} edgeTypes={{ role: RoleEdge }} proOptions={{ hideAttribution: true }} - fitViewOptions={{ maxZoom: 1 }} + fitViewOptions={{ maxZoom: MaxAutoZoom }} defaultEdgeOptions={{ type: "role", animated: true, selectable: false }} onconnectstart={() => dragging.set(true)} onconnectend={() => dragging.set(false)} diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index d721549d48b..c702adb821e 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -79,7 +79,7 @@ const dagreLayout = ({ nodes, edges }) => { dagreGraph.setGraph({ rankdir: "LR", ranksep: 200, - nodesep: 100, + nodesep: 50, }) nodes.forEach(node => { dagreGraph.setNode(node.id, { width: NodeWidth, height: NodeHeight }) From dded09ca9724b594f1950544dc51b539abc9979b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 16:51:36 +0100 Subject: [PATCH 28/93] Update RBAC editor to use a grid and make all dimensions consistent with grid --- .../backend/RoleEditor/RoleEditor.svelte | 5 ++-- .../backend/RoleEditor/RoleNode.svelte | 23 ++++++++++++------- .../backend/RoleEditor/constants.js | 5 ++-- .../components/backend/RoleEditor/layout.js | 12 +++++----- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index 01f87b6dea0..c1772c32bb4 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -8,7 +8,7 @@ import { rolesToNodes, autoLayout } from "./layout" import { onMount, setContext } from "svelte" import Controls from "./Controls.svelte" - import { MaxAutoZoom } from "./constants" + import { GridResolution, MaxAutoZoom } from "./constants" const nodes = writable([]) const edges = writable([]) @@ -34,7 +34,7 @@ fitView {nodes} {edges} - snapGrid={[25, 25]} + snapGrid={[GridResolution, GridResolution]} nodeTypes={{ role: RoleNode }} edgeTypes={{ role: RoleEdge }} proOptions={{ hideAttribution: true }} @@ -76,7 +76,6 @@ .flow :global(.svelte-flow) { /* Panel */ --xy-background-color: var(--background-color); - --xy-background-pattern-color-props: transparent; /* Controls */ --xy-controls-button-background-color: var(--node-background); diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 69d75d9a578..b4a3cafc582 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -156,28 +156,30 @@ width: var(--width); height: var(--height); display: flex; - flex-direction: column; + flex-direction: row; + box-sizing: border-box; } .node.selected { background: var(--spectrum-global-color-blue-100); } .color { border-top-left-radius: 4px; - border-top-right-radius: 4px; - height: 8px; - width: 100%; + border-bottom-left-radius: 4px; + height: 100%; + width: 10px; + flex: 0 0 10px; background: var(--color); } .content { flex: 1 1 auto; - padding: 0 14px 0 14px; + padding: 0 12px; display: flex; flex-direction: column; justify-content: center; align-items: stretch; gap: 2px; border: 1px solid var(--border-color); - border-bottom-left-radius: 4px; + border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .node.selected .content { @@ -199,8 +201,7 @@ .title :global(.spectrum-Icon) { color: var(--spectrum-global-color-gray-600); } - .name, - .description { + .name { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -208,6 +209,12 @@ .description { color: var(--spectrum-global-color-gray-600); font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 1; + line-clamp: 1; + -webkit-box-orient: vertical; } .node:hover .buttons { display: flex; diff --git a/packages/builder/src/components/backend/RoleEditor/constants.js b/packages/builder/src/components/backend/RoleEditor/constants.js index d2fb813830e..d22a228a877 100644 --- a/packages/builder/src/components/backend/RoleEditor/constants.js +++ b/packages/builder/src/components/backend/RoleEditor/constants.js @@ -1,4 +1,5 @@ -export const NodeWidth = 220 -export const NodeHeight = 66 export const ZoomDuration = 300 export const MaxAutoZoom = 1.2 +export const GridResolution = 20 +export const NodeHeight = GridResolution * 3 +export const NodeWidth = GridResolution * 12 diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index c702adb821e..fb1f8e795a1 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -1,5 +1,5 @@ import dagre from "@dagrejs/dagre" -import { NodeWidth, NodeHeight } from "./constants" +import { NodeWidth, NodeHeight, GridResolution } from "./constants" import { Position } from "@xyflow/svelte" import { roles } from "stores/builder" import { Roles } from "constants/backend" @@ -78,8 +78,8 @@ const dagreLayout = ({ nodes, edges }) => { dagreGraph.setDefaultEdgeLabel(() => ({})) dagreGraph.setGraph({ rankdir: "LR", - ranksep: 200, - nodesep: 50, + ranksep: GridResolution * 6, + nodesep: GridResolution * 2, }) nodes.forEach(node => { dagreGraph.setNode(node.id, { width: NodeWidth, height: NodeHeight }) @@ -89,12 +89,12 @@ const dagreLayout = ({ nodes, edges }) => { }) dagre.layout(dagreGraph) nodes.forEach(node => { - const nodeWithPosition = dagreGraph.node(node.id) + const pos = dagreGraph.node(node.id) node.targetPosition = Position.Left node.sourcePosition = Position.Right node.position = { - x: nodeWithPosition.x - NodeWidth / 2, - y: nodeWithPosition.y - NodeHeight / 2, + x: Math.round((pos.x - NodeWidth / 2) / GridResolution) * GridResolution, + y: Math.round((pos.y - NodeHeight / 2) / GridResolution) * GridResolution, } }) return { nodes, edges } From 63dd73f7b6541807533dadb22cdb4f9d67cf5136 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 12 Sep 2024 16:56:25 +0100 Subject: [PATCH 29/93] Update RBAC editor edges to explain what action the delete icon will take --- .../components/backend/RoleEditor/RoleEdge.svelte | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte index 243e4b988bf..5dc21d6f85c 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte @@ -5,8 +5,9 @@ EdgeLabelRenderer, useSvelteFlow, } from "@xyflow/svelte" - import { Icon, ActionButton } from "@budibase/bbui" + import { Icon, TooltipPosition } from "@budibase/bbui" import { onMount } from "svelte" + import { roles } from "stores/builder" export let sourceX export let sourceY @@ -15,6 +16,8 @@ export let targetY export let targetPosition export let id + export let source + export let target const flow = useSvelteFlow() @@ -31,6 +34,12 @@ targetY, targetPosition, }) + $: sourceRole = $roles.find(x => x._id === source) + $: targetRole = $roles.find(x => x._id === target) + $: tooltip = + sourceRole && targetRole + ? `Stop ${targetRole.displayName} from inheriting ${sourceRole.displayName}` + : null const getEdgeClasses = (hovered, labelHovered) => { let classes = "" @@ -81,7 +90,7 @@ on:mouseover={() => (labelHovered = true)} on:mouseout={() => (labelHovered = false)} > - +
From 040405089783741815081a57f0824a8195e7b7c6 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 13 Sep 2024 10:47:56 +0100 Subject: [PATCH 30/93] Reset server changes to master --- packages/backend-core/src/security/roles.ts | 81 +++++++------------ packages/server/src/api/controllers/role.ts | 27 ++----- .../src/api/routes/tests/permissions.spec.ts | 36 +-------- .../server/src/api/routes/utils/validators.ts | 8 +- packages/types/src/api/web/role.ts | 3 - packages/types/src/documents/app/role.ts | 10 ++- 6 files changed, 54 insertions(+), 111 deletions(-) diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts index 097d6e84da7..a7210ec2b8e 100644 --- a/packages/backend-core/src/security/roles.ts +++ b/packages/backend-core/src/security/roles.ts @@ -7,8 +7,9 @@ import { doWithDB, } from "../db" import { getAppDB } from "../context" -import { Screen, Role as RoleDoc } from "@budibase/types" +import { Screen, Role as RoleDoc, RoleUIMetadata } from "@budibase/types" import cloneDeep from "lodash/fp/cloneDeep" +import { RoleColor } from "@budibase/shared-core" export const BUILTIN_ROLE_IDS = { ADMIN: "ADMIN", @@ -45,22 +46,12 @@ export class Role implements RoleDoc { inherits?: string version?: string permissions: Record = {} - displayName?: string - color?: string - description?: string + uiMetadata?: RoleUIMetadata - constructor( - id: string, - displayName: string, - description: string, - color: string, - permissionId: string - ) { + constructor(id: string, permissionId: string, uiMetadata?: RoleUIMetadata) { this._id = id - this.name = id - this.displayName = displayName - this.color = color - this.description = description + this.name = uiMetadata?.displayName || id + this.uiMetadata = uiMetadata this.permissionId = permissionId // version for managing the ID - removing the role_ when responding this.version = RoleIDVersion.NAME @@ -73,41 +64,31 @@ export class Role implements RoleDoc { } const BUILTIN_ROLES = { - ADMIN: new Role( - BUILTIN_IDS.ADMIN, - "App admin", - "Can do everything", - "var(--spectrum-global-color-static-red-400)", - BuiltinPermissionID.ADMIN - ).addInheritance(BUILTIN_IDS.POWER), - POWER: new Role( - BUILTIN_IDS.POWER, - "App power user", - "An app user with more access", - "var(--spectrum-global-color-static-orange-400)", - BuiltinPermissionID.POWER - ).addInheritance(BUILTIN_IDS.BASIC), - BASIC: new Role( - BUILTIN_IDS.BASIC, - "App user", - "Any logged in user", - "var(--spectrum-global-color-static-green-400)", - BuiltinPermissionID.WRITE - ).addInheritance(BUILTIN_IDS.PUBLIC), - PUBLIC: new Role( - BUILTIN_IDS.PUBLIC, - "Public user", - "Accessible to anyone", - "var(--spectrum-global-color-static-blue-400)", - BuiltinPermissionID.PUBLIC - ), - BUILDER: new Role( - BUILTIN_IDS.BUILDER, - "Builder user", - "Users that can edit this app", - "var(--spectrum-global-color-static-magenta-600)", - BuiltinPermissionID.ADMIN - ), + ADMIN: new Role(BUILTIN_IDS.ADMIN, BuiltinPermissionID.ADMIN, { + displayName: "App admin", + description: "Can do everything", + color: RoleColor.ADMIN, + }).addInheritance(BUILTIN_IDS.POWER), + POWER: new Role(BUILTIN_IDS.POWER, BuiltinPermissionID.POWER, { + displayName: "App power user", + description: "An app user with more access", + color: RoleColor.POWER, + }).addInheritance(BUILTIN_IDS.BASIC), + BASIC: new Role(BUILTIN_IDS.BASIC, BuiltinPermissionID.WRITE, { + displayName: "App user", + description: "Any logged in user", + color: RoleColor.BASIC, + }).addInheritance(BUILTIN_IDS.PUBLIC), + PUBLIC: new Role(BUILTIN_IDS.PUBLIC, BuiltinPermissionID.PUBLIC, { + displayName: "Public user", + description: "Accessible to anyone", + color: RoleColor.PUBLIC, + }), + BUILDER: new Role(BUILTIN_IDS.BUILDER, BuiltinPermissionID.ADMIN, { + displayName: "Builder user", + description: "Users that can edit this app", + color: RoleColor.BUILDER, + }), } export function getBuiltinRoles(): { [key: string]: RoleDoc } { diff --git a/packages/server/src/api/controllers/role.ts b/packages/server/src/api/controllers/role.ts index 28e9cb27792..ee1c2239524 100644 --- a/packages/server/src/api/controllers/role.ts +++ b/packages/server/src/api/controllers/role.ts @@ -19,7 +19,7 @@ import { UserMetadata, DocumentType, } from "@budibase/types" -import { sdk as sharedSdk } from "@budibase/shared-core" +import { RoleColor, sdk as sharedSdk } from "@budibase/shared-core" import sdk from "../../sdk" const UpdateRolesOptions = { @@ -62,16 +62,8 @@ export async function find(ctx: UserCtx) { export async function save(ctx: UserCtx) { const db = context.getAppDB() - let { - _id, - name, - displayName, - description, - color, - inherits, - permissionId, - version, - } = ctx.request.body + let { _id, name, inherits, permissionId, version, uiMetadata } = + ctx.request.body let isCreate = false const isNewVersion = version === roles.RoleIDVersion.NAME @@ -97,14 +89,11 @@ export async function save(ctx: UserCtx) { ctx.throw(400, "Cannot change custom role name") } - const role = new roles.Role( - _id, - displayName || name, - description || "Custom role", - color || "var(--spectrum-global-color-static-magenta-400)", - permissionId - ).addInheritance(inherits) - + const role = new roles.Role(_id, permissionId, { + displayName: uiMetadata?.displayName || name, + description: uiMetadata?.description || "Custom role", + color: uiMetadata?.color || RoleColor.DEFAULT_CUSTOM, + }).addInheritance(inherits) if (dbRole?.permissions && !role.permissions) { role.permissions = dbRole.permissions } diff --git a/packages/server/src/api/routes/tests/permissions.spec.ts b/packages/server/src/api/routes/tests/permissions.spec.ts index 0fba930144c..0f059998ae1 100644 --- a/packages/server/src/api/routes/tests/permissions.spec.ts +++ b/packages/server/src/api/routes/tests/permissions.spec.ts @@ -1,16 +1,5 @@ -const mockedSdk = sdk.permissions as jest.Mocked - -import sdk from "../../../sdk" - import { roles } from "@budibase/backend-core" -import { - Document, - DocumentType, - PermissionLevel, - Row, - Table, - ViewV2, -} from "@budibase/types" +import { Document, PermissionLevel, Row, Table, ViewV2 } from "@budibase/types" import * as setup from "./utilities" import { generator, mocks } from "@budibase/backend-core/tests" @@ -36,6 +25,7 @@ describe("/permission", () => { beforeEach(async () => { mocks.licenses.useCloudFree() + table = (await config.createTable()) as typeof table row = await config.createRow() view = await config.api.viewV2.create({ @@ -154,27 +144,7 @@ describe("/permission", () => { await config.api.viewV2.publicSearch(view.id, undefined, { status: 401 }) }) - it("should ignore the view permissions if the flag is not on", async () => { - await config.api.permission.add({ - roleId: STD_ROLE_ID, - resourceId: view.id, - level: PermissionLevel.READ, - }) - await config.api.permission.revoke({ - roleId: STD_ROLE_ID, - resourceId: table._id, - level: PermissionLevel.READ, - }) - // replicate changes before checking permissions - await config.publish() - - await config.api.viewV2.publicSearch(view.id, undefined, { - status: 401, - }) - }) - - it("should use the view permissions if the flag is on", async () => { - mocks.licenses.useViewPermissions() + it("should use the view permissions", async () => { await config.api.permission.add({ roleId: STD_ROLE_ID, resourceId: view.id, diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts index 2b50f818680..b589d44b31c 100644 --- a/packages/server/src/api/routes/utils/validators.ts +++ b/packages/server/src/api/routes/utils/validators.ts @@ -208,9 +208,11 @@ export function roleValidator() { name: Joi.string() .regex(/^[a-zA-Z0-9_]*$/) .required(), - displayName: Joi.string().optional(), - color: Joi.string().optional(), - description: Joi.string().optional(), + uiMetadata: Joi.object({ + displayName: OPTIONAL_STRING, + color: OPTIONAL_STRING, + description: OPTIONAL_STRING, + }).optional(), // this is the base permission ID (for now a built in) permissionId: Joi.string() .valid(...Object.values(permissions.BuiltinPermissionID)) diff --git a/packages/types/src/api/web/role.ts b/packages/types/src/api/web/role.ts index df63fbaa779..644222b8f94 100644 --- a/packages/types/src/api/web/role.ts +++ b/packages/types/src/api/web/role.ts @@ -4,9 +4,6 @@ export interface SaveRoleRequest { _id?: string _rev?: string name: string - displayName?: string - color?: string - description?: string inherits: string permissionId: string version: string diff --git a/packages/types/src/documents/app/role.ts b/packages/types/src/documents/app/role.ts index 29687ce6e12..6557b7e19da 100644 --- a/packages/types/src/documents/app/role.ts +++ b/packages/types/src/documents/app/role.ts @@ -1,13 +1,17 @@ import { Document } from "../document" import { PermissionLevel } from "../../sdk" +export interface RoleUIMetadata { + displayName?: string + color?: string + description?: string +} + export interface Role extends Document { permissionId: string inherits?: string permissions: Record version?: string name: string - displayName?: string - color?: string - description?: string + uiMetadata?: RoleUIMetadata } From 2849a7a4ff59973c743926b9f062ba2b52dafc81 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 13 Sep 2024 14:03:21 +0100 Subject: [PATCH 31/93] Add uiMetdata prefix to roles everywhere --- .../buttons/ManageAccessButton.svelte | 18 +++++++++--------- .../DataTable/modals/CreateEditUser.svelte | 2 +- .../backend/RoleEditor/Controls.svelte | 12 +++++++----- .../backend/RoleEditor/RoleEdge.svelte | 2 +- .../backend/RoleEditor/RoleNode.svelte | 12 ++++-------- .../components/backend/RoleEditor/layout.js | 12 ++++++------ .../src/components/common/RoleSelect.svelte | 8 ++++---- .../design/settings/controls/RoleSelect.svelte | 4 ++-- packages/builder/src/dataBinding.js | 4 ++-- .../ScreenList/RoleIndicator.svelte | 5 +++-- .../_components/AppRoleTableRenderer.svelte | 2 +- packages/builder/src/stores/builder/roles.js | 9 ++++++--- .../src/components/grid/cells/RoleCell.svelte | 5 +++-- 13 files changed, 49 insertions(+), 46 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte index 1725740a7c0..2826d8d9860 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/ManageAccessButton.svelte @@ -29,7 +29,7 @@ $: roleMismatch = checkRoleMismatch(permissions) $: selectedRole = roleMismatch ? null : permissions?.[0]?.value $: readableRole = selectedRole - ? $roles.find(x => x._id === selectedRole)?.displayName + ? $roles.find(x => x._id === selectedRole)?.uiMetadata.displayName : null $: buttonLabel = readableRole ? `Access: ${readableRole}` : "Access" $: highlight = roleMismatch || selectedRole === Roles.PUBLIC @@ -39,8 +39,8 @@ .filter(x => !builtins.includes(x._id)) .slice() .toSorted((a, b) => { - const aName = a.displayName || a.name - const bName = b.displayName || b.name + const aName = a.uiMetadata.displayName || a.name + const bName = b.uiMetadata.displayName || b.name return aName < bName ? -1 : 1 }) @@ -159,23 +159,23 @@ {#each builtInRoles as role} changePermission(role._id)} /> {/each} {#each customRoles as role} changePermission(role._id)} /> {/each} diff --git a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte index befe5ec4846..5ac2beab65c 100644 --- a/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte +++ b/packages/builder/src/components/backend/DataTable/modals/CreateEditUser.svelte @@ -125,7 +125,7 @@ label="Role" bind:value={row.roleId} options={$roles} - getOptionLabel={role => role.displayName} + getOptionLabel={role => role.uiMetadata.displayName} getOptionValue={role => role._id} disabled={!creating} /> diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index 8ac8ce26686..a5c601f2dd7 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -14,11 +14,13 @@ const addRole = async () => { const role = { name: Helpers.uuid(), - displayName: getSequentialName($nodes, "New role ", { - getName: x => x.data.displayName, - }), - color: "var(--spectrum-global-color-gray-700)", - description: "Custom role", + uiMetadata: { + displayName: getSequentialName($roles, "New role ", { + getName: x => x.uiMetadata.displayName, + }), + color: "var(--spectrum-global-color-gray-700)", + description: "Custom role", + }, permissionId: "write", inherits: Roles.BASIC, } diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte index 5dc21d6f85c..4e25294d21d 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte @@ -38,7 +38,7 @@ $: targetRole = $roles.find(x => x._id === target) $: tooltip = sourceRole && targetRole - ? `Stop ${targetRole.displayName} from inheriting ${sourceRole.displayName}` + ? `Stop ${targetRole.uiMetadata.displayName} from inheriting ${sourceRole.uiMetadata.displayName}` : null const getEdgeClasses = (hovered, labelHovered) => { diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index b4a3cafc582..1a387de13e7 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -36,7 +36,7 @@ if (!name?.length) { return "Please enter a name" } - if (roles.some(x => x.displayName === name && x._id !== id)) { + if (roles.some(x => x.uiMetadata.displayName === name && x._id !== id)) { return "That name is already used by another role" } return null @@ -171,6 +171,7 @@ background: var(--color); } .content { + width: 0; flex: 1 1 auto; padding: 0 12px; display: flex; @@ -201,7 +202,8 @@ .title :global(.spectrum-Icon) { color: var(--spectrum-global-color-gray-600); } - .name { + .name, + .description { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -209,12 +211,6 @@ .description { color: var(--spectrum-global-color-gray-600); font-size: 12px; - overflow: hidden; - text-overflow: ellipsis; - display: -webkit-box; - -webkit-line-clamp: 1; - line-clamp: 1; - -webkit-box-orient: vertical; } .node:hover .buttons { display: flex; diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index fb1f8e795a1..3671cb6782f 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -14,9 +14,7 @@ export const roleToNode = role => ({ type: "role", position: { x: 0, y: 0 }, data: { - displayName: role.displayName, - description: role.description, - color: role.color, + ...role.uiMetadata, custom: !role._id.match(/[A-Z]+/), }, }) @@ -26,9 +24,11 @@ export const nodeToRole = node => { const role = get(roles).find(x => x._id === node.id) return { ...role, - displayName: node.data.displayName, - color: node.data.color, - description: node.data.description, + uiMetadata: { + displayName: node.data.displayName, + color: node.data.color, + description: node.data.description, + }, } } diff --git a/packages/builder/src/components/common/RoleSelect.svelte b/packages/builder/src/components/common/RoleSelect.svelte index 152b752fd00..c59dd8fc605 100644 --- a/packages/builder/src/components/common/RoleSelect.svelte +++ b/packages/builder/src/components/common/RoleSelect.svelte @@ -49,8 +49,8 @@ let options = roles .filter(role => allowedRoles.includes(role._id)) .map(role => ({ - color: role.color, - name: enrichLabel(role.displayName), + color: role.uiMetadata.color, + name: enrichLabel(role.uiMetadata.displayName), _id: role._id, })) if (allowedRoles.includes(Constants.Roles.CREATOR)) { @@ -65,8 +65,8 @@ // Allow all core roles let options = roles.map(role => ({ - color: role.color, - name: enrichLabel(role.displayName), + color: role.uiMetadata.color, + name: enrichLabel(role.uiMetadata.displayName), _id: role._id, })) diff --git a/packages/builder/src/components/design/settings/controls/RoleSelect.svelte b/packages/builder/src/components/design/settings/controls/RoleSelect.svelte index 773abd69b9e..ad466cbce76 100644 --- a/packages/builder/src/components/design/settings/controls/RoleSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/RoleSelect.svelte @@ -11,9 +11,9 @@ bind:value on:change options={$roles} - getOptionLabel={role => role.displayName} + getOptionLabel={role => role.uiMetadata.displayName} getOptionValue={role => role._id} - getOptionColour={role => role.color} + getOptionColour={role => role.uiMetadata.color} {placeholder} {error} /> diff --git a/packages/builder/src/dataBinding.js b/packages/builder/src/dataBinding.js index 09ff8e7a812..a59ce16e5a2 100644 --- a/packages/builder/src/dataBinding.js +++ b/packages/builder/src/dataBinding.js @@ -725,10 +725,10 @@ const getRoleBindings = () => { return { type: "context", runtimeBinding: `'${role._id}'`, - readableBinding: `Role.${role.displayName}`, + readableBinding: `Role.${role.uiMetadata.displayName}`, category: "Role", icon: "UserGroup", - display: { type: "string", name: role.displayName }, + display: { type: "string", name: role.uiMetadata.displayName }, } }) } diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte index 4ee5d5dec58..09e29d806ab 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/_components/ScreenList/RoleIndicator.svelte @@ -7,12 +7,13 @@ let showTooltip = false - $: color = role.color || "var(--spectrum-global-color-static-magenta-400)" $: role = $roles.find(role => role._id === roleId) + $: color = + role?.uiMetadata.color || "var(--spectrum-global-color-static-magenta-400)" $: tooltip = roleId === Roles.PUBLIC ? "Open to the public" - : `Requires ${role?.displayName} access` + : `Requires ${role?.uiMetadata.displayName || "Unknown role"} access` diff --git a/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte b/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte index e63ee2493ed..7ed36eb3a4e 100644 --- a/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte +++ b/packages/builder/src/pages/builder/portal/users/users/_components/AppRoleTableRenderer.svelte @@ -11,7 +11,7 @@ const getRoleLabel = roleId => { return roleId === Constants.Roles.CREATOR ? capitalise(Constants.Roles.CREATOR.toLowerCase()) - : role?.displayName || role?.name || "Custom role" + : role?.uiMetadata.displayName || role?.name || "Custom role" } diff --git a/packages/builder/src/stores/builder/roles.js b/packages/builder/src/stores/builder/roles.js index ef4480c6d24..0b322104b54 100644 --- a/packages/builder/src/stores/builder/roles.js +++ b/packages/builder/src/stores/builder/roles.js @@ -9,9 +9,12 @@ export function createRolesStore() { ...role, // Ensure we have new metadata for all roles - displayName: role.displayName || role.name, - color: role.color || "var(--spectrum-global-color-magenta-400)", - description: role.description || "Custom role", + uiMetadata: { + displayName: role.uiMetadata?.displayName || role.name, + color: + role.uiMetadata?.color || "var(--spectrum-global-color-magenta-400)", + description: role.uiMetadata?.description || "Custom role", + }, })) }) diff --git a/packages/frontend-core/src/components/grid/cells/RoleCell.svelte b/packages/frontend-core/src/components/grid/cells/RoleCell.svelte index b638d0e0e3f..82d1e26aa7d 100644 --- a/packages/frontend-core/src/components/grid/cells/RoleCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RoleCell.svelte @@ -12,11 +12,12 @@
- {role?.displayName || role?.name || value} + {role?.uiMetadata?.displayName || role?.name || "Unknown role"}
From bf10b4cd9dd92485c78c4fd80b31378659b302fd Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 16 Sep 2024 09:26:31 +0100 Subject: [PATCH 32/93] Lint --- .../builder/src/components/backend/RoleEditor/Controls.svelte | 2 +- .../builder/src/components/backend/RoleEditor/RoleEdge.svelte | 4 ++-- .../builder/src/components/backend/RoleEditor/RoleNode.svelte | 1 - packages/builder/src/components/common/RoleIcon.svelte | 1 - packages/builder/src/components/common/RoleSelect.svelte | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index a5c601f2dd7..1ddb856b86e 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -1,6 +1,6 @@ @@ -42,6 +174,7 @@ defaultEdgeOptions={{ type: "role", animated: true, selectable: false }} onconnectstart={() => dragging.set(true)} onconnectend={() => dragging.set(false)} + onconnect={onConnect} > diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 9a4f5f54fdb..e0297492375 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -11,13 +11,13 @@ import { Roles } from "constants/backend" import { NodeWidth, NodeHeight, MaxAutoZoom, ZoomDuration } from "./constants" import { getContext, tick } from "svelte" - import { autoLayout, nodeToRole } from "./layout" + import { autoLayout } from "./layout" import { roles } from "stores/builder" export let data export let id - const { nodes, edges, dragging } = getContext("flow") + const { nodes, edges, dragging, updateRole, deleteRole } = getContext("flow") const flow = useSvelteFlow() let anchor @@ -49,12 +49,7 @@ } const deleteNode = async () => { - flow.deleteElements({ - nodes: [{ id }], - }) - await tick() - doAutoLayout() - await roles.delete(nodeToRole({ id, data })) + await deleteRole(id) } const openPopover = () => { @@ -71,7 +66,7 @@ color: tempColor, } flow.updateNodeData(id, newData) - await roles.save(nodeToRole({ id, data: newData })) + await updateRole(id) } const doAutoLayout = () => { diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index 3671cb6782f..d41a2b04e88 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -1,79 +1,11 @@ import dagre from "@dagrejs/dagre" import { NodeWidth, NodeHeight, GridResolution } from "./constants" import { Position } from "@xyflow/svelte" -import { roles } from "stores/builder" import { Roles } from "constants/backend" -import { get } from "svelte/store" import { Helpers } from "@budibase/bbui" -// Converts a role doc into a node structure -export const roleToNode = role => ({ - id: role._id, - sourcePosition: Position.Right, - targetPosition: Position.Left, - type: "role", - position: { x: 0, y: 0 }, - data: { - ...role.uiMetadata, - custom: !role._id.match(/[A-Z]+/), - }, -}) - -// Converts a node structure back into a role doc -export const nodeToRole = node => { - const role = get(roles).find(x => x._id === node.id) - return { - ...role, - uiMetadata: { - displayName: node.data.displayName, - color: node.data.color, - description: node.data.description, - }, - } -} - -// Generates a flow compatible structure of nodes and edges from the current roles -export const rolesToNodes = () => { - const ignoredRoles = [Roles.PUBLIC] - const $roles = get(roles) - - let nodes = [] - let edges = [] - - for (let role of $roles) { - if (ignoredRoles.includes(role._id)) { - continue - } - - // Add node for this role - nodes.push(roleToNode(role)) - - // Add edges for this role - let inherits = [] - if (role.inherits) { - inherits = Array.isArray(role.inherits) ? role.inherits : [role.inherits] - } - for (let sourceRole of inherits) { - // Ensure source role exists - if (!$roles.some(x => x._id === sourceRole)) { - continue - } - edges.push({ - id: `${sourceRole}-${role._id}`, - source: sourceRole, - target: role._id, - }) - } - } - - return { - nodes, - edges, - } -} - // Updates positions of nodes and edges into a nice graph structure -const dagreLayout = ({ nodes, edges }) => { +export const dagreLayout = ({ nodes, edges }) => { const dagreGraph = new dagre.graphlib.Graph() dagreGraph.setDefaultEdgeLabel(() => ({})) dagreGraph.setGraph({ diff --git a/packages/builder/src/pages/builder/app/[application]/data/roles.svelte b/packages/builder/src/pages/builder/app/[application]/data/roles.svelte index bea482d9d2c..01775777303 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/roles.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/roles.svelte @@ -1,5 +1,8 @@ diff --git a/packages/builder/src/stores/builder/roles.js b/packages/builder/src/stores/builder/roles.js index fcbceab1ce2..61f0c2b53db 100644 --- a/packages/builder/src/stores/builder/roles.js +++ b/packages/builder/src/stores/builder/roles.js @@ -1,4 +1,4 @@ -import { derived, writable } from "svelte/store" +import { derived, writable, get } from "svelte/store" import { API } from "api" import { RoleUtils } from "@budibase/frontend-core" @@ -56,6 +56,13 @@ export function createRolesStore() { } }, replace: (roleId, role) => { + // Remove role_ prefix + if (roleId?.startsWith("role_")) { + roleId = roleId.replace("role_", "") + } + if (role?._id.startsWith("role_")) { + role._id = role._id.replace("role_", "") + } console.log("replace", roleId, role) // Handles external updates of roles diff --git a/packages/builder/src/stores/builder/websocket.js b/packages/builder/src/stores/builder/websocket.js index 553d4cdd76e..8a0d83abc10 100644 --- a/packages/builder/src/stores/builder/websocket.js +++ b/packages/builder/src/stores/builder/websocket.js @@ -59,7 +59,7 @@ export const createBuilderWebsocket = appId => { // Role events socket.onOther(BuilderSocketEvent.RoleChange, ({ id, role }) => { - roles.replaceRole(id, role) + roles.replace(id, role) }) // Design section events From d23d4156c34b4b8c391376a2603f8efb1c64025c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 16 Sep 2024 12:17:12 +0100 Subject: [PATCH 35/93] Lint --- .../components/backend/RoleEditor/Controls.svelte | 6 +----- .../components/backend/RoleEditor/RoleNode.svelte | 13 +++++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index 5e5b7bc6fc9..6a580726a65 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -8,10 +8,6 @@ const { nodes, edges, createRole } = getContext("flow") const flow = useSvelteFlow() - const addRole = async () => { - await createRole() - } - const doAutoLayout = async () => { const layout = autoLayout({ nodes: $nodes, edges: $edges }) nodes.set(layout.nodes) @@ -47,7 +43,7 @@
- +
diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte index 0f5f55b1bfb..6169013d129 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEditor.svelte @@ -1,227 +1,8 @@ -
-
- Manage roles -
-
Roles inherit permissions from each other.
-
-
- dragging.set(true)} - onconnectend={() => dragging.set(false)} - onconnect={onConnect} - > - - - -
- - + + + diff --git a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte new file mode 100644 index 00000000000..ae3da3cfdb2 --- /dev/null +++ b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte @@ -0,0 +1,271 @@ + + +
+
+ Manage roles +
+
Roles inherit permissions from each other.
+
+
+ dragging.set(true)} + onconnectend={() => dragging.set(false)} + onconnect={onConnect} + > + + + +
+ + diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 03890ac9b7f..9a65e4dad58 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -1,5 +1,5 @@
@@ -88,12 +86,7 @@ {#if data.custom}
- deleteRole(id)} - /> +
{/if}
@@ -153,9 +146,11 @@ display: flex; flex-direction: row; box-sizing: border-box; + cursor: pointer; } .node.selected { background: var(--spectrum-global-color-blue-100); + cursor: grab; } .color { border-top-left-radius: 4px; @@ -207,7 +202,7 @@ color: var(--spectrum-global-color-gray-600); font-size: 12px; } - .node:hover .buttons { + .node.selected .buttons { display: flex; } .node :global(.svelte-flow__handle) { diff --git a/packages/builder/src/stores/builder/roles.js b/packages/builder/src/stores/builder/roles.js index 61f0c2b53db..cb86991a9a4 100644 --- a/packages/builder/src/stores/builder/roles.js +++ b/packages/builder/src/stores/builder/roles.js @@ -23,7 +23,12 @@ export function createRolesStore() { roles.sort((a, b) => { const priorityA = RoleUtils.getRolePriority(a._id) const priorityB = RoleUtils.getRolePriority(b._id) - return priorityA > priorityB ? -1 : 1 + if (priorityA !== priorityB) { + return priorityA > priorityB ? -1 : 1 + } + const nameA = a.uiMetadata?.displayName || a.name + const nameB = b.uiMetadata?.displayName || b.name + return nameA < nameB ? -1 : 1 }) ) } From e7916c55f7dca49931c53caf4e09360485ce7635 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 16 Sep 2024 13:42:47 +0100 Subject: [PATCH 37/93] Some style updates --- .../backend/RoleEditor/RoleEdge.svelte | 7 +++- .../backend/RoleEditor/RoleNode.svelte | 32 ++++++++----------- .../components/backend/RoleEditor/layout.js | 2 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte index 67e19c48e9a..e8cfb8a5145 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleEdge.svelte @@ -69,7 +69,12 @@ on:mouseover={() => (iconHovered = true)} on:mouseout={() => (iconHovered = false)} > - +
diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 9a65e4dad58..1615a355fa0 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -79,16 +79,8 @@ >
-
-
- {data.displayName} -
- {#if data.custom} -
- - -
- {/if} +
+ {data.displayName}
{#if data.description}
@@ -96,6 +88,12 @@
{/if}
+ {#if data.custom} +
+ + +
+ {/if} {#if id !== Roles.BASIC} {/if} - {#if id !== Roles.ADMIN} + {#if id !== Roles.ADMIN && selected} {/if}
@@ -140,11 +138,11 @@ position: relative; background: var(--node-background); border-radius: 4px; - border: 1px solid transparent; width: var(--width); height: var(--height); display: flex; flex-direction: row; + gap: 12px; box-sizing: border-box; cursor: pointer; } @@ -163,35 +161,33 @@ .content { width: 0; flex: 1 1 auto; - padding: 0 12px; display: flex; flex-direction: column; justify-content: center; align-items: stretch; gap: 2px; border: 1px solid var(--border-color); + border-left-width: 0; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .node.selected .content { border-color: transparent; } - .title, .buttons { display: flex; flex-direction: row; align-items: center; gap: 6px; } - .title { - justify-content: space-between; - } .buttons { display: none; + padding-right: 12px; } - .title :global(.spectrum-Icon) { + .buttons :global(.spectrum-Icon) { color: var(--spectrum-global-color-gray-600); } + .name, .description { white-space: nowrap; diff --git a/packages/builder/src/components/backend/RoleEditor/layout.js b/packages/builder/src/components/backend/RoleEditor/layout.js index d41a2b04e88..103b585ef54 100644 --- a/packages/builder/src/components/backend/RoleEditor/layout.js +++ b/packages/builder/src/components/backend/RoleEditor/layout.js @@ -10,7 +10,7 @@ export const dagreLayout = ({ nodes, edges }) => { dagreGraph.setDefaultEdgeLabel(() => ({})) dagreGraph.setGraph({ rankdir: "LR", - ranksep: GridResolution * 6, + ranksep: GridResolution * 8, nodesep: GridResolution * 2, }) nodes.forEach(node => { From 518f29030f441ca5e282ac91e5c3b570d53b3892 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 16 Sep 2024 13:47:57 +0100 Subject: [PATCH 38/93] Fix edge issue --- .../backend/RoleEditor/RoleFlow.svelte | 4 ---- .../backend/RoleEditor/RoleNode.svelte | 16 ++++++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte index ae3da3cfdb2..afb0e68f6c4 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte @@ -241,8 +241,6 @@ overflow: hidden; position: relative; --background-color: var(--spectrum-global-color-gray-50); - --node-background: var(--spectrum-global-color-gray-100); - --node-background-hover: var(--spectrum-global-color-gray-300); --border-color: var(--spectrum-global-color-gray-300); --edge-color: var(--spectrum-global-color-gray-500); --handle-color: var(--spectrum-global-color-gray-600); @@ -255,8 +253,6 @@ --xy-background-color: var(--background-color); /* Controls */ - --xy-controls-button-background-color: var(--node-background); - --xy-controls-button-background-color-hover: var(--node-background-hover); --xy-controls-button-border-color: var(--border-color); /* Handles */ diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 1615a355fa0..ffc0db3758e 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -1,5 +1,5 @@
Date: Mon, 16 Sep 2024 15:09:15 +0100 Subject: [PATCH 43/93] Lint --- .../builder/src/components/backend/RoleEditor/RoleFlow.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte index d10ffed55ae..9284dafc34b 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte @@ -46,7 +46,7 @@ // Converts a node structure back into a role doc const nodeToRole = node => { const role = $roles.find(x => x._id === node.id) - const inherits = $edges.filter(x => x.target === node.id).map(x => x.source) + // const inherits = $edges.filter(x => x.target === node.id).map(x => x.source) // TODO save inherits array return { ...role, From de34217bb21664dc47c46611c09fcb53051b687c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 23 Sep 2024 11:57:04 +0100 Subject: [PATCH 44/93] Increase spacing to M in list items --- packages/bbui/src/List/ListItem.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bbui/src/List/ListItem.svelte b/packages/bbui/src/List/ListItem.svelte index 384d20d1876..699df2d4560 100644 --- a/packages/bbui/src/List/ListItem.svelte +++ b/packages/bbui/src/List/ListItem.svelte @@ -84,7 +84,7 @@ display: flex; flex-direction: row; align-items: center; - gap: var(--spacing-s); + gap: var(--spacing-m); } .left { width: 0; From b2b6cc2ecfe1db7e5f12e21a1756b679d559885d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 23 Sep 2024 15:45:57 +0100 Subject: [PATCH 45/93] Reorder grid buttons and remove export button for users table --- .../buttons/grid/GridAutomationsButton.svelte | 2 +- .../buttons/grid/GridGenerateButton.svelte | 2 +- .../buttons/grid/GridScreensButton.svelte | 2 +- .../data/table/[tableId]/[viewId]/index.svelte | 4 +--- .../data/table/[tableId]/index.svelte | 14 ++++---------- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridAutomationsButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridAutomationsButton.svelte index 9670bb2f358..b2448eeaf31 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridAutomationsButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridAutomationsButton.svelte @@ -34,7 +34,7 @@ const generateAutomation = () => { popover?.hide() - dispatch("request-generate") + dispatch("generate") } diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridGenerateButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridGenerateButton.svelte index 5cc3aca19e4..5d1ec835d54 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridGenerateButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridGenerateButton.svelte @@ -91,7 +91,7 @@ - +
magic wand Generate diff --git a/packages/builder/src/components/backend/DataTable/buttons/grid/GridScreensButton.svelte b/packages/builder/src/components/backend/DataTable/buttons/grid/GridScreensButton.svelte index 701a286112a..db446b3c9e5 100644 --- a/packages/builder/src/components/backend/DataTable/buttons/grid/GridScreensButton.svelte +++ b/packages/builder/src/components/backend/DataTable/buttons/grid/GridScreensButton.svelte @@ -22,7 +22,7 @@ const generateScreen = () => { popover?.hide() - dispatch("request-generate") + dispatch("generate") } diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte index 82ffbd9ba5f..fefceec55fc 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/[viewId]/index.svelte @@ -56,9 +56,7 @@ - generateButton?.show()} /> - - + generateButton?.show()} /> diff --git a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte index 93ec3d07648..e9c6ad11679 100644 --- a/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/data/table/[tableId]/index.svelte @@ -141,17 +141,11 @@ {/if} {#if !isUsersTable} - - generateButton?.show()} /> - generateButton?.show()} - /> - {/if} - - - - {#if !isUsersTable} + + + generateButton?.show()} /> + generateButton?.show()} /> {/if} From 6f054c390aac696fb75409341ccdacca388ab458 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 25 Sep 2024 11:58:23 +0100 Subject: [PATCH 46/93] Update role nodes to always show icons --- .../backend/RoleEditor/RoleNode.svelte | 73 ++++++++++--------- yarn.lock | 64 +++++++--------- 2 files changed, 68 insertions(+), 69 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index ba0dbe6404c..ac33aa94b08 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -31,7 +31,6 @@ $: descriptionError = validateDescription(tempDescription) $: invalid = nameError || descriptionError $: targetClasses = `target${$dragging ? "" : " hidden"}` - $: sourceClasses = `source${selected ? "" : " hidden"}` const validateName = (name, roles) => { if (!name?.length) { @@ -75,21 +74,23 @@ >
-
- {data.displayName} +
+
+ {data.displayName} +
+ {#if data.description} +
+ {data.description} +
+ {/if}
- {#if data.description} -
- {data.description} + {#if data.custom} +
+ +
{/if}
- {#if data.custom} -
- - -
- {/if} {#if id !== Roles.BASIC} {/if} {#if id !== Roles.ADMIN} - + {/if}
@@ -138,6 +139,7 @@ From 4de91d4e3aaf19488388fe1795e2c7b07948d53e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 09:27:04 +0100 Subject: [PATCH 55/93] Add animated curly brackets --- .../backend/RoleEditor/BracketEdge.svelte | 63 +++++++++++++++ .../backend/RoleEditor/RoleFlow.svelte | 12 +-- .../backend/RoleEditor/RoleNode.svelte | 62 +++----------- .../backend/RoleEditor/constants.js | 2 +- .../components/backend/RoleEditor/utils.js | 81 +++++++++++-------- 5 files changed, 131 insertions(+), 89 deletions(-) create mode 100644 packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte diff --git a/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte b/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte new file mode 100644 index 00000000000..643fe28532b --- /dev/null +++ b/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte @@ -0,0 +1,63 @@ + + + + + diff --git a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte index 7a9280d679d..1e894f0cc98 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte @@ -11,11 +11,13 @@ import "@xyflow/svelte/dist/style.css" import RoleNode from "./RoleNode.svelte" import RoleEdge from "./RoleEdge.svelte" + import BracketEdge from "./BracketEdge.svelte" import { autoLayout, getAdminPosition, getBasicPosition, rolesToLayout, + nodeToRole, } from "./utils" import { setContext, tick } from "svelte" import Controls from "./Controls.svelte" @@ -81,7 +83,7 @@ name: roleId, uiMetadata: { displayName: getSequentialName($roles, "New role ", { - getName: x => x.uiMetadata.displayName, + getName: role => role.uiMetadata.displayName, }), color: "var(--spectrum-global-color-gray-700)", description: "Custom role", @@ -108,7 +110,7 @@ if (metadata) { flow.updateNodeData(roleId, metadata) } - await roles.save(nodeToRole(node)) + await roles.save(nodeToRole({ node, edges: $edges })) } const deleteRole = async roleId => { @@ -119,11 +121,11 @@ } const deleteEdge = async edgeId => { - const edge = $edges.find(x => x.id === edgeId) + const edge = $edges.find(edge => edge.id === edgeId) if (!edge) { return } - edges.set($edges.filter(x => x.id !== edgeId)) + edges.set($edges.filter(edge => edge.id !== edgeId)) await updateRole(edge.target) } @@ -158,7 +160,7 @@ {edges} snapGrid={[GridResolution, GridResolution]} nodeTypes={{ role: RoleNode }} - edgeTypes={{ role: RoleEdge }} + edgeTypes={{ role: RoleEdge, bracket: BracketEdge }} proOptions={{ hideAttribution: true }} fitViewOptions={{ maxZoom: MaxAutoZoom }} defaultEdgeOptions={{ type: "role", animated: true, selectable: false }} diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 57ef54ee5ce..6f1c39d67ea 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -8,8 +8,7 @@ ModalContent, FieldLabel, } from "@budibase/bbui" - import { Roles } from "constants/backend" - import { NodeWidth, NodeHeight, NodeVSpacing } from "./constants" + import { NodeWidth, NodeHeight } from "./constants" import { getContext } from "svelte" import { roles } from "stores/builder" import ConfirmDialog from "components/common/ConfirmDialog.svelte" @@ -70,6 +69,7 @@
@@ -92,24 +92,14 @@
{/if}
- {#if isConnectable} - - - {/if} -
- -{#if id === Roles.BASIC || id === Roles.ADMIN} -
-{/if} + +
diff --git a/packages/builder/src/components/backend/RoleEditor/constants.js b/packages/builder/src/components/backend/RoleEditor/constants.js index ba43e349b51..aa4b1f26f37 100644 --- a/packages/builder/src/components/backend/RoleEditor/constants.js +++ b/packages/builder/src/components/backend/RoleEditor/constants.js @@ -3,5 +3,5 @@ export const MaxAutoZoom = 1.2 export const GridResolution = 20 export const NodeHeight = GridResolution * 3 export const NodeWidth = GridResolution * 12 -export const NodeHSpacing = GridResolution * 4 +export const NodeHSpacing = GridResolution * 6 export const NodeVSpacing = GridResolution * 2 diff --git a/packages/builder/src/components/backend/RoleEditor/utils.js b/packages/builder/src/components/backend/RoleEditor/utils.js index edd4cbe3356..ad0c2e110e6 100644 --- a/packages/builder/src/components/backend/RoleEditor/utils.js +++ b/packages/builder/src/components/backend/RoleEditor/utils.js @@ -8,19 +8,36 @@ import { } from "./constants" import { getNodesBounds, Position } from "@xyflow/svelte" import { Roles } from "constants/backend" +import { roles } from "stores/builder" +import { get } from "svelte/store" // Gets the position of the basic role export const getBasicPosition = bounds => ({ - x: bounds.x - NodeHSpacing - NodeWidth, + x: bounds.x - GridResolution * 4 - NodeWidth, y: bounds.y + bounds.height / 2 - NodeHeight / 2, }) // Gets the position of the admin role export const getAdminPosition = bounds => ({ - x: bounds.x + bounds.width + NodeHSpacing, + x: bounds.x + bounds.width + GridResolution * 4, y: bounds.y + bounds.height / 2 - NodeHeight / 2, }) +// Filters out invalid nodes and edges +const preProcessLayout = ({ nodes, edges }) => { + const ignoredRoles = [Roles.PUBLIC, Roles.POWER] + const edglessRoles = [...ignoredRoles, Roles.BASIC, Roles.ADMIN] + return { + nodes: nodes.filter(node => !ignoredRoles.includes(node.id)), + edges: edges.filter(edge => { + return ( + !edglessRoles.includes(edge.source) && + !edglessRoles.includes(edge.target) + ) + }), + } +} + // Updates positions of nodes and edges into a nice graph structure export const dagreLayout = ({ nodes, edges }) => { const dagreGraph = new dagre.graphlib.Graph() @@ -46,33 +63,34 @@ export const dagreLayout = ({ nodes, edges }) => { y: Math.round((pos.y - NodeHeight / 2) / GridResolution) * GridResolution, } }) + return { nodes, edges } +} +const postProcessLayout = ({ nodes, edges }) => { // Reposition basic and admin to bound the custom nodes const bounds = getNodesBounds(nodes.filter(node => node.data.custom)) nodes.find(x => x.id === Roles.BASIC).position = getBasicPosition(bounds) nodes.find(x => x.id === Roles.ADMIN).position = getAdminPosition(bounds) + // Add custom edges for basic and admin brackets + edges.push({ + id: "basic-bracket", + source: Roles.BASIC, + target: Roles.ADMIN, + type: "bracket", + }) + edges.push({ + id: "admin-bracket", + source: Roles.ADMIN, + target: Roles.BASIC, + type: "bracket", + }) return { nodes, edges } } -// Adds additional edges as needed to the flow structure to ensure compatibility with BB role logic -const sanitiseLayout = ({ nodes, edges }) => { - const ignoredRoles = [Roles.PUBLIC, Roles.POWER] - const edglessRoles = [...ignoredRoles, Roles.BASIC, Roles.ADMIN] - return { - nodes: nodes.filter(node => !ignoredRoles.includes(node.id)), - edges: edges.filter(edge => { - return ( - !edglessRoles.includes(edge.source) && - !edglessRoles.includes(edge.target) - ) - }), - } -} - // Automatically lays out the graph, sanitising and enriching the structure export const autoLayout = ({ nodes, edges }) => { - return dagreLayout(sanitiseLayout({ nodes, edges })) + return postProcessLayout(dagreLayout(preProcessLayout({ nodes, edges }))) } // Converts a role doc into a node structure @@ -99,27 +117,22 @@ export const roleToNode = role => { } // Converts a node structure back into a role doc -export const nodeToRole = node => { - const role = $roles.find(x => x._id === node.id) - const inherits = $edges.filter(x => x.target === node.id).map(x => x.source) - // TODO save inherits array - return { - ...role, - inherits, - uiMetadata: { - displayName: node.data.displayName, - color: node.data.color, - description: node.data.description, - }, - } -} +export const nodeToRole = ({ node, edges }) => ({ + ...get(roles).find(role => role._id === node.id), + inherits: edges.filter(x => x.target === node.id).map(x => x.source), + uiMetadata: { + displayName: node.data.displayName, + color: node.data.color, + description: node.data.description, + }, +}) -// Builds a layout from an array of roles +// Builds a default layout from an array of roles export const rolesToLayout = roles => { let nodes = [] let edges = [] - // Remove some builtins + // Add all nodes and edges for (let role of roles) { // Add node for this role nodes.push(roleToNode(role)) From f430de53692bf27735b67ba80e7d37b2adc2964b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 09:36:18 +0100 Subject: [PATCH 56/93] Add more edge validation --- .../components/backend/RoleEditor/utils.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/utils.js b/packages/builder/src/components/backend/RoleEditor/utils.js index ad0c2e110e6..9156ff81590 100644 --- a/packages/builder/src/components/backend/RoleEditor/utils.js +++ b/packages/builder/src/components/backend/RoleEditor/utils.js @@ -28,12 +28,26 @@ const preProcessLayout = ({ nodes, edges }) => { const ignoredRoles = [Roles.PUBLIC, Roles.POWER] const edglessRoles = [...ignoredRoles, Roles.BASIC, Roles.ADMIN] return { - nodes: nodes.filter(node => !ignoredRoles.includes(node.id)), + nodes: nodes.filter(node => { + // Filter out ignored roles + if (ignoredRoles.includes(node.id)) { + return false + } + return true + }), edges: edges.filter(edge => { - return ( - !edglessRoles.includes(edge.source) && - !edglessRoles.includes(edge.target) - ) + // Filter out edges from ignored roles + if ( + edglessRoles.includes(edge.source) || + edglessRoles.includes(edge.target) + ) { + return false + } + // Filter out edges which have the same source and target + if (edge.source === edge.target) { + return false + } + return true }), } } From fbdbefddf729eac7eff28956f56ec7d1d1fafef7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 11:21:30 +0100 Subject: [PATCH 57/93] Fix resizing issue with brackes --- .../backend/RoleEditor/BracketEdge.svelte | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte b/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte index 643fe28532b..37aa772a22d 100644 --- a/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte +++ b/packages/builder/src/components/backend/RoleEditor/BracketEdge.svelte @@ -7,14 +7,14 @@ export let sourceY const { bounds } = getContext("flow") - const BracketWidth = GridResolution * 2 - const BracketHeight = $bounds.height / 2 + GridResolution * 2 + $: bracketWidth = GridResolution * 3 + $: bracketHeight = $bounds.height / 2 + GridResolution * 2 $: path = getCurlyBracePath( - sourceX + BracketWidth, - sourceY - BracketHeight, - sourceX + BracketWidth, - sourceY + BracketHeight + sourceX + bracketWidth, + sourceY - bracketHeight, + sourceX + bracketWidth, + sourceY + bracketHeight ) const getCurlyBracePath = (x1, y1, x2, y2) => { @@ -35,7 +35,7 @@ const qy1 = y1 - q * w * dx const qx2 = x1 - 0.25 * len * dx + (1 - q) * w * dy - i const qy2 = y1 - 0.25 * len * dy - (1 - q) * w * dx - const tx1 = x1 - 0.5 * len * dx + w * dy - BracketWidth + const tx1 = x1 - 0.5 * len * dx + w * dy - bracketWidth const ty1 = y1 - 0.5 * len * dy - w * dx const qx3 = x2 + q * w * dy - j const qy3 = y2 - q * w * dx From 72f1db8c1026003a8b9fce16b9ca1af7e73a8799 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 11:24:22 +0100 Subject: [PATCH 58/93] Update spacing --- packages/builder/src/components/backend/RoleEditor/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/utils.js b/packages/builder/src/components/backend/RoleEditor/utils.js index 9156ff81590..8aa8d734437 100644 --- a/packages/builder/src/components/backend/RoleEditor/utils.js +++ b/packages/builder/src/components/backend/RoleEditor/utils.js @@ -13,13 +13,13 @@ import { get } from "svelte/store" // Gets the position of the basic role export const getBasicPosition = bounds => ({ - x: bounds.x - GridResolution * 4 - NodeWidth, + x: bounds.x - NodeHSpacing - NodeWidth, y: bounds.y + bounds.height / 2 - NodeHeight / 2, }) // Gets the position of the admin role export const getAdminPosition = bounds => ({ - x: bounds.x + bounds.width + GridResolution * 4, + x: bounds.x + bounds.width + NodeHSpacing, y: bounds.y + bounds.height / 2 - NodeHeight / 2, }) From 303f4001398b7467fa16e7a48b0535d61d80f2d4 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 11:28:41 +0100 Subject: [PATCH 59/93] Prevent selection of basic and admin roles --- .../src/components/backend/RoleEditor/RoleNode.svelte | 7 ++++--- .../builder/src/components/backend/RoleEditor/utils.js | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index 6f1c39d67ea..d8e69df0812 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -70,6 +70,7 @@ class="node" class:selected class:custom={data.custom} + class:selectable={isConnectable} style={`--color:${data.color}; --width:${NodeWidth}px; --height:${NodeHeight}px;`} bind:this={anchor} > @@ -146,13 +147,13 @@ display: flex; flex-direction: row; box-sizing: border-box; - cursor: pointer; transition: background 130ms ease-out; } - .node:hover { + .node.selectable:hover { + cursor: pointer; background: var(--spectrum-global-color-gray-200); } - .node.selected { + .node.selectable.selected { background: var(--spectrum-global-color-blue-100); cursor: grab; } diff --git a/packages/builder/src/components/backend/RoleEditor/utils.js b/packages/builder/src/components/backend/RoleEditor/utils.js index 8aa8d734437..bf02cfd5af4 100644 --- a/packages/builder/src/components/backend/RoleEditor/utils.js +++ b/packages/builder/src/components/backend/RoleEditor/utils.js @@ -127,6 +127,7 @@ export const roleToNode = role => { deletable: custom, draggable: custom, connectable: custom, + selectable: custom, } } From a342aa3c31c915a4547951fc668be446f24ba4fc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 13:08:47 +0100 Subject: [PATCH 60/93] Add empty state node to role editor --- .../backend/RoleEditor/Controls.svelte | 11 ++--- .../backend/RoleEditor/EmptyStateNode.svelte | 24 ++++++++++ .../backend/RoleEditor/RoleFlow.svelte | 8 ++-- .../components/backend/RoleEditor/utils.js | 44 ++++++++++++++++++- 4 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 packages/builder/src/components/backend/RoleEditor/EmptyStateNode.svelte diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index 919093bee56..0b5d70217cf 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -7,10 +7,13 @@ const { nodes, edges, createRole } = getContext("flow") const flow = useSvelteFlow() + const autoFit = () => + flow.fitView({ maxZoom: MaxAutoZoom, duration: ZoomDuration }) const addRole = async () => { await createRole() doAutoLayout() + autoFit() } const doAutoLayout = () => { @@ -33,13 +36,7 @@ on:click={() => flow.zoomOut({ duration: ZoomDuration })} />
- +
diff --git a/packages/builder/src/components/backend/RoleEditor/EmptyStateNode.svelte b/packages/builder/src/components/backend/RoleEditor/EmptyStateNode.svelte new file mode 100644 index 00000000000..2c949ed0f9f --- /dev/null +++ b/packages/builder/src/components/backend/RoleEditor/EmptyStateNode.svelte @@ -0,0 +1,24 @@ + + +
+ Add custom roles for more granular control over permissions +
+ + diff --git a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte index 1e894f0cc98..0bb1b04ddc6 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleFlow.svelte @@ -10,6 +10,7 @@ } from "@xyflow/svelte" import "@xyflow/svelte/dist/style.css" import RoleNode from "./RoleNode.svelte" + import EmptyStateNode from "./EmptyStateNode.svelte" import RoleEdge from "./RoleEdge.svelte" import BracketEdge from "./BracketEdge.svelte" import { @@ -18,6 +19,7 @@ getBasicPosition, rolesToLayout, nodeToRole, + getBounds, } from "./utils" import { setContext, tick } from "svelte" import Controls from "./Controls.svelte" @@ -38,9 +40,7 @@ }) // Derive the bounds of all custom role nodes - const bounds = derivedMemo(nodes, $nodes => { - return getNodesBounds($nodes.filter(node => node.data.custom)) - }) + const bounds = derivedMemo(nodes, getBounds) $: handleExternalRoleChanges($roles) $: updateBuiltins($bounds) @@ -159,7 +159,7 @@ {nodes} {edges} snapGrid={[GridResolution, GridResolution]} - nodeTypes={{ role: RoleNode }} + nodeTypes={{ role: RoleNode, empty: EmptyStateNode }} edgeTypes={{ role: RoleEdge, bracket: BracketEdge }} proOptions={{ hideAttribution: true }} fitViewOptions={{ maxZoom: MaxAutoZoom }} diff --git a/packages/builder/src/components/backend/RoleEditor/utils.js b/packages/builder/src/components/backend/RoleEditor/utils.js index bf02cfd5af4..fce8e9516c1 100644 --- a/packages/builder/src/components/backend/RoleEditor/utils.js +++ b/packages/builder/src/components/backend/RoleEditor/utils.js @@ -11,6 +11,22 @@ import { Roles } from "constants/backend" import { roles } from "stores/builder" import { get } from "svelte/store" +// Calculates the bounds of all custom nodes +export const getBounds = nodes => { + const customNodes = nodes.filter(node => node.data.custom) + + // Empty state bounds which line up with bounds after adding first node + if (!customNodes.length) { + return { + x: 0, + y: 6.5 * GridResolution, + width: 12 * GridResolution, + height: 10 * GridResolution, + } + } + return getNodesBounds(customNodes) +} + // Gets the position of the basic role export const getBasicPosition = bounds => ({ x: bounds.x - NodeHSpacing - NodeWidth, @@ -33,6 +49,10 @@ const preProcessLayout = ({ nodes, edges }) => { if (ignoredRoles.includes(node.id)) { return false } + // Filter out empty state + if (node.id === "empty") { + return false + } return true }), edges: edges.filter(edge => { @@ -82,7 +102,7 @@ export const dagreLayout = ({ nodes, edges }) => { const postProcessLayout = ({ nodes, edges }) => { // Reposition basic and admin to bound the custom nodes - const bounds = getNodesBounds(nodes.filter(node => node.data.custom)) + const bounds = getBounds(nodes) nodes.find(x => x.id === Roles.BASIC).position = getBasicPosition(bounds) nodes.find(x => x.id === Roles.ADMIN).position = getAdminPosition(bounds) @@ -99,6 +119,28 @@ const postProcessLayout = ({ nodes, edges }) => { target: Roles.BASIC, type: "bracket", }) + + // Add empty state node if required + if (!nodes.filter(node => node.data.custom).length) { + nodes.push({ + id: "empty", + type: "empty", + position: { + x: bounds.x + bounds.width / 2 - NodeWidth / 2, + y: bounds.y + bounds.height / 2 - NodeHeight / 2, + }, + data: {}, + measured: { + width: NodeWidth, + height: NodeHeight, + }, + deletable: false, + draggable: false, + connectable: false, + selectable: false, + }) + } + return { nodes, edges } } From a4aac8aae057597a335cbc8c8c669ef5e5c86125 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 13:59:50 +0100 Subject: [PATCH 61/93] Deal with power users --- .../backend/RoleEditor/RoleNode.svelte | 14 ++--- .../components/backend/RoleEditor/utils.js | 53 +++++++++++-------- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte index d8e69df0812..3dbab9537b7 100644 --- a/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte +++ b/packages/builder/src/components/backend/RoleEditor/RoleNode.svelte @@ -12,6 +12,7 @@ import { getContext } from "svelte" import { roles } from "stores/builder" import ConfirmDialog from "components/common/ConfirmDialog.svelte" + import { Roles } from "constants/backend" export let data export let id @@ -68,7 +69,9 @@
@@ -222,11 +224,9 @@ .node :global(.svelte-flow__handle.target) { background: var(--background-color); } - .node :global(.svelte-flow__handle.hidden) { - opacity: 0; - pointer-events: none; - } - .node:not(.custom) :global(.svelte-flow__handle) { + .node:not(.dragging) :global(.svelte-flow__handle.target), + .node:not(.interactive) :global(.svelte-flow__handle), + .node:not(.custom) :global(.svelte-flow__handle.target) { visibility: hidden; pointer-events: none; } diff --git a/packages/builder/src/components/backend/RoleEditor/utils.js b/packages/builder/src/components/backend/RoleEditor/utils.js index fce8e9516c1..9d23d46a753 100644 --- a/packages/builder/src/components/backend/RoleEditor/utils.js +++ b/packages/builder/src/components/backend/RoleEditor/utils.js @@ -13,18 +13,18 @@ import { get } from "svelte/store" // Calculates the bounds of all custom nodes export const getBounds = nodes => { - const customNodes = nodes.filter(node => node.data.custom) + const interactiveNodes = nodes.filter(node => node.data.interactive) // Empty state bounds which line up with bounds after adding first node - if (!customNodes.length) { + if (!interactiveNodes.length) { return { x: 0, - y: 6.5 * GridResolution, + y: -3.5 * GridResolution, width: 12 * GridResolution, height: 10 * GridResolution, } } - return getNodesBounds(customNodes) + return getNodesBounds(interactiveNodes) } // Gets the position of the basic role @@ -41,25 +41,21 @@ export const getAdminPosition = bounds => ({ // Filters out invalid nodes and edges const preProcessLayout = ({ nodes, edges }) => { - const ignoredRoles = [Roles.PUBLIC, Roles.POWER] - const edglessRoles = [...ignoredRoles, Roles.BASIC, Roles.ADMIN] + const ignoredIds = [Roles.PUBLIC, Roles.BASIC, Roles.ADMIN, "empty"] + const targetlessIds = [Roles.POWER] return { nodes: nodes.filter(node => { - // Filter out ignored roles - if (ignoredRoles.includes(node.id)) { - return false - } - // Filter out empty state - if (node.id === "empty") { + // Filter out ignored IDs + if (ignoredIds.includes(node.id)) { return false } return true }), edges: edges.filter(edge => { - // Filter out edges from ignored roles + // Filter out edges from ignored IDs if ( - edglessRoles.includes(edge.source) || - edglessRoles.includes(edge.target) + ignoredIds.includes(edge.source) || + ignoredIds.includes(edge.target) ) { return false } @@ -67,6 +63,10 @@ const preProcessLayout = ({ nodes, edges }) => { if (edge.source === edge.target) { return false } + // Filter out edges which target targetless roles + if (targetlessIds.includes(edge.target)) { + return false + } return true }), } @@ -101,10 +101,17 @@ export const dagreLayout = ({ nodes, edges }) => { } const postProcessLayout = ({ nodes, edges }) => { - // Reposition basic and admin to bound the custom nodes + // Add basic and admin nodes at each edge const bounds = getBounds(nodes) - nodes.find(x => x.id === Roles.BASIC).position = getBasicPosition(bounds) - nodes.find(x => x.id === Roles.ADMIN).position = getAdminPosition(bounds) + const $roles = get(roles) + nodes.push({ + ...roleToNode($roles.find(role => role._id === Roles.BASIC)), + position: getBasicPosition(bounds), + }) + nodes.push({ + ...roleToNode($roles.find(role => role._id === Roles.ADMIN)), + position: getAdminPosition(bounds), + }) // Add custom edges for basic and admin brackets edges.push({ @@ -121,7 +128,7 @@ const postProcessLayout = ({ nodes, edges }) => { }) // Add empty state node if required - if (!nodes.filter(node => node.data.custom).length) { + if (!nodes.some(node => node.data.interactive)) { nodes.push({ id: "empty", type: "empty", @@ -152,6 +159,7 @@ export const autoLayout = ({ nodes, edges }) => { // Converts a role doc into a node structure export const roleToNode = role => { const custom = !role._id.match(/[A-Z]+/) + const interactive = custom || role._id === Roles.POWER return { id: role._id, sourcePosition: Position.Right, @@ -161,15 +169,16 @@ export const roleToNode = role => { data: { ...role.uiMetadata, custom, + interactive, }, measured: { width: NodeWidth, height: NodeHeight, }, deletable: custom, - draggable: custom, - connectable: custom, - selectable: custom, + draggable: interactive, + connectable: interactive, + selectable: interactive, } } From 818732250b0e327d6f3711ad082e7f063aca7104 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 3 Oct 2024 14:13:22 +0100 Subject: [PATCH 62/93] Add auto layout and fit when doing any role operation other than updating metadata --- .../backend/RoleEditor/Controls.svelte | 24 ++++--------------- .../backend/RoleEditor/RoleFlow.svelte | 21 +++++++++++----- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/packages/builder/src/components/backend/RoleEditor/Controls.svelte b/packages/builder/src/components/backend/RoleEditor/Controls.svelte index 0b5d70217cf..e20fab29955 100644 --- a/packages/builder/src/components/backend/RoleEditor/Controls.svelte +++ b/packages/builder/src/components/backend/RoleEditor/Controls.svelte @@ -2,25 +2,10 @@ import { Button, ActionButton } from "@budibase/bbui" import { useSvelteFlow } from "@xyflow/svelte" import { getContext } from "svelte" - import { autoLayout } from "./utils" - import { MaxAutoZoom, ZoomDuration } from "./constants" + import { ZoomDuration } from "./constants" - const { nodes, edges, createRole } = getContext("flow") + const { createRole, layoutAndFit } = getContext("flow") const flow = useSvelteFlow() - const autoFit = () => - flow.fitView({ maxZoom: MaxAutoZoom, duration: ZoomDuration }) - - const addRole = async () => { - await createRole() - doAutoLayout() - autoFit() - } - - const doAutoLayout = () => { - const layout = autoLayout({ nodes: $nodes, edges: $edges }) - nodes.set(layout.nodes) - edges.set(layout.edges) - }
@@ -36,11 +21,10 @@ on:click={() => flow.zoomOut({ duration: ZoomDuration })} />
- - +
- +