diff --git a/packages/backend-core/src/security/roles.ts b/packages/backend-core/src/security/roles.ts
index a64be6b319..7fb4eefc8b 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 aa0b0112d8..94c82dcd65 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 42c18934cf..9904a8877b 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 aaf228638d..42a107ee2e 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 4bb6d1bb05..b8a5ae7d5a 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 1bd6ba49bc..567a7c0b5d 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 4605b0c182..72495fe3c9 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 5b5daac183..973a239bd8 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 4b7f26709c..994a56e833 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 0f19bb3e1f..85d94ba350 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 8f1bfdc177..a438abac56 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 1ae9d3ac14..913d452e7c 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 3398c8102c..9c7fb2859a 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 5e2a585b4a..dd943f1282 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 c37dee60e0..7782c33215 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 f32ba810b0..0169da90c2 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
}