Skip to content

Commit

Permalink
Merge branch 'v3-ui' into queryui-default
Browse files Browse the repository at this point in the history
  • Loading branch information
samwho committed Oct 23, 2024
2 parents da2b2e5 + 612bd51 commit 17c9d9f
Show file tree
Hide file tree
Showing 74 changed files with 1,908 additions and 718 deletions.
22 changes: 19 additions & 3 deletions packages/backend-core/src/security/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,22 @@ export function getBuiltinRole(roleId: string): Role | undefined {
return cloneDeep(role)
}

export function validInherits(
allRoles: RoleDoc[],
inherits?: string | string[]
): boolean {
if (!inherits) {
return false
}
const find = (id: string) => allRoles.find(r => roleIDsAreEqual(r._id!, id))
if (Array.isArray(inherits)) {
const filtered = inherits.filter(roleId => find(roleId))
return inherits.length !== 0 && filtered.length === inherits.length
} else {
return !!find(inherits)
}
}

/**
* Works through the inheritance ranks to see how far up the builtin stack this ID is.
*/
Expand Down Expand Up @@ -290,7 +306,7 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string): string {
: roleId1
}

export function compareRoleIds(roleId1: string, roleId2: string) {
export function roleIDsAreEqual(roleId1: string, roleId2: string) {
// make sure both role IDs are prefixed correctly
return prefixRoleID(roleId1) === prefixRoleID(roleId2)
}
Expand Down Expand Up @@ -323,7 +339,7 @@ export function findRole(
roleId = prefixRoleID(roleId)
}
const dbRole = roles.find(
role => role._id && compareRoleIds(role._id, roleId)
role => role._id && roleIDsAreEqual(role._id, roleId)
)
if (!dbRole && !isBuiltin(roleId) && opts?.defaultPublic) {
return cloneDeep(BUILTIN_ROLES.PUBLIC)
Expand Down Expand Up @@ -557,7 +573,7 @@ export class AccessController {
}

return (
roleIds?.find(roleId => compareRoleIds(roleId, tryingRoleId)) !==
roleIds?.find(roleId => roleIDsAreEqual(roleId, tryingRoleId)) !==
undefined
)
}
Expand Down
1 change: 1 addition & 0 deletions packages/bbui/src/InlineAlert/InlineAlert.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
16 changes: 13 additions & 3 deletions packages/bbui/src/List/ListItem.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script>
import Icon from "../Icon/Icon.svelte"
import StatusLight from "../StatusLight/StatusLight.svelte"
export let icon = null
export let iconColor = null
Expand All @@ -8,16 +9,20 @@
export let url = null
export let hoverable = false
export let showArrow = false
export let selected = false
</script>

<a
href={url}
class="list-item"
class:hoverable={hoverable || url != null}
on:click
class:selected
>
<div class="left">
{#if icon}
{#if icon === "StatusLight"}
<StatusLight square size="L" color={iconColor} />
{:else if icon}
<Icon name={icon} color={iconColor} />
{/if}
<div class="list-item__text">
Expand All @@ -43,7 +48,7 @@

<style>
.list-item {
padding: var(--spacing-m);
padding: var(--spacing-m) var(--spacing-l);
background: var(--spectrum-global-color-gray-75);
display: flex;
flex-direction: row;
Expand All @@ -66,15 +71,20 @@
}
.hoverable:hover {
cursor: pointer;
}
.hoverable:not(.selected):hover {
background: var(--spectrum-global-color-gray-200);
}
.selected {
background: var(--spectrum-global-color-blue-100);
}
.left,
.right {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--spacing-s);
gap: var(--spacing-m);
}
.left {
width: 0;
Expand Down
1 change: 1 addition & 0 deletions packages/bbui/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { default as EnvDropdown } from "./Form/EnvDropdown.svelte"
export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Search } from "./Form/Search.svelte"
export { default as RichTextField } from "./Form/RichTextField.svelte"
export { default as FieldLabel } from "./Form/FieldLabel.svelte"
export { default as Slider } from "./Form/Slider.svelte"
export { default as File } from "./Form/File.svelte"

Expand Down
2 changes: 2 additions & 0 deletions packages/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@
"@codemirror/state": "^6.2.0",
"@codemirror/theme-one-dark": "^6.1.2",
"@codemirror/view": "^6.11.2",
"@dagrejs/dagre": "1.1.4",
"@fontsource/source-sans-pro": "^5.0.3",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1",
"@xyflow/svelte": "^0.1.18",
"@zerodevx/svelte-json-view": "^1.0.7",
"codemirror": "^5.65.16",
"cron-parser": "^4.9.0",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,31 +1,195 @@
<script>
import { ActionButton } from "@budibase/bbui"
import { permissions } from "stores/builder"
import ManageAccessModal from "../modals/ManageAccessModal.svelte"
import {
ActionButton,
Input,
Select,
Label,
List,
ListItem,
notifications,
} from "@budibase/bbui"
import { permissions as permissionsStore, roles } from "stores/builder"
import DetailPopover from "components/common/DetailPopover.svelte"
import EditRolesButton from "./EditRolesButton.svelte"
import { PermissionSource } from "@budibase/types"
import { capitalise } from "helpers"
import InfoDisplay from "pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Component/InfoDisplay.svelte"
import { Roles } from "constants/backend"
export let resourceId
let resourcePermissions
const inheritedRoleId = "inherited"
const builtins = [Roles.ADMIN, Roles.POWER, Roles.BASIC, Roles.PUBLIC]
let permissions
let showPopover = true
let dependantsInfoMessage
$: fetchPermissions(resourceId)
$: loadDependantInfo(resourceId)
$: roleMismatch = checkRoleMismatch(permissions)
$: selectedRole = roleMismatch ? null : permissions?.[0]?.value
$: readableRole = selectedRole
? $roles.find(x => x._id === selectedRole)?.uiMetadata.displayName
: null
$: buttonLabel = readableRole ? `Access: ${readableRole}` : "Access"
$: highlight = roleMismatch || selectedRole === Roles.PUBLIC
$: builtInRoles = builtins.map(roleId => $roles.find(x => x._id === roleId))
$: customRoles = $roles
.filter(x => !builtins.includes(x._id))
.slice()
.toSorted((a, b) => {
const aName = a.uiMetadata.displayName || a.name
const bName = b.uiMetadata.displayName || b.name
return aName < bName ? -1 : 1
})
const fetchPermissions = async id => {
resourcePermissions = await permissions.forResourceDetailed(id)
const res = await permissionsStore.forResourceDetailed(id)
permissions = Object.entries(res?.permissions || {}).map(([perm, info]) => {
let enriched = {
permission: perm,
value:
info.permissionType === PermissionSource.INHERITED
? inheritedRoleId
: info.role,
options: [...$roles],
}
if (info.inheritablePermission) {
enriched.options.unshift({
_id: inheritedRoleId,
name: `Inherit (${
$roles.find(x => x._id === info.inheritablePermission).name
})`,
})
}
return enriched
})
}
const checkRoleMismatch = permissions => {
if (!permissions || permissions.length < 2) {
return false
}
return (
permissions[0].value !== permissions[1].value ||
permissions[0].value === inheritedRoleId
)
}
const loadDependantInfo = async resourceId => {
const dependantsInfo = await permissionsStore.getDependantsInfo(resourceId)
const resourceByType = dependantsInfo?.resourceByType
if (resourceByType) {
const total = Object.values(resourceByType).reduce((p, c) => p + c, 0)
let resourceDisplay =
Object.keys(resourceByType).length === 1 && resourceByType.view
? "view"
: "resource"
if (total === 1) {
dependantsInfoMessage = `1 ${resourceDisplay} is inheriting this access`
} else if (total > 1) {
dependantsInfoMessage = `${total} ${resourceDisplay}s are inheriting this access`
} else {
dependantsInfoMessage = null
}
} else {
dependantsInfoMessage = null
}
}
const changePermission = async role => {
try {
await permissionsStore.save({
level: "read",
role,
resource: resourceId,
})
await permissionsStore.save({
level: "write",
role,
resource: resourceId,
})
await fetchPermissions(resourceId)
notifications.success("Updated permissions")
} catch (error) {
console.error(error)
notifications.error("Error updating permissions")
}
}
</script>
<DetailPopover title="Manage access" {showPopover}>
<DetailPopover title="Select access role" {showPopover}>
<svelte:fragment slot="anchor" let:open>
<ActionButton icon="LockClosed" selected={open} quiet>Access</ActionButton>
<ActionButton
icon="LockClosed"
selected={open || highlight}
quiet
accentColor={highlight ? "#ff0000" : null}
>
{buttonLabel}
</ActionButton>
</svelte:fragment>
{#if resourcePermissions}
<ManageAccessModal {resourceId} permissions={resourcePermissions} />
{#if roleMismatch}
<div class="row">
<Label extraSmall grey>Level</Label>
<Label extraSmall grey>Role</Label>
{#each permissions as permission}
<Input value={capitalise(permission.permission)} disabled />
<Select
placeholder={false}
value={permission.value}
on:change={e => changePermission(e.detail)}
disabled
options={permission.options}
getOptionLabel={x => x.name}
getOptionValue={x => x._id}
/>
{/each}
</div>
<InfoDisplay
error
icon="Alert"
body="Your previous configuration is shown above.<br/> Please choose a single role for read and write access."
/>
{/if}
<List>
{#each builtInRoles as role}
<ListItem
title={role.uiMetadata.displayName}
subtitle={role.uiMetadata.description}
hoverable
selected={selectedRole === role._id}
icon="StatusLight"
iconColor={role.uiMetadata.color}
on:click={() => changePermission(role._id)}
/>
{/each}
{#each customRoles as role}
<ListItem
title={role.uiMetadata.displayName}
subtitle={role.uiMetadata.description}
hoverable
selected={selectedRole === role._id}
icon="StatusLight"
iconColor={role.uiMetadata.color}
on:click={() => changePermission(role._id)}
/>
{/each}
</List>
{#if dependantsInfoMessage}
<InfoDisplay info body={dependantsInfoMessage} />
{/if}
<EditRolesButton
on:show={() => (showPopover = false)}
on:hide={() => (showPopover = true)}
/>
</DetailPopover>
<style>
.row {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: var(--spacing-s);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
const generateAutomation = () => {
popover?.hide()
dispatch("request-generate")
dispatch("generate")
}
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
<DetailPopover title="Generate" bind:this={popover}>
<svelte:fragment slot="anchor" let:open>
<ActionButton selected={open}>
<ActionButton quiet selected={open}>
<div class="center">
<img height={16} alt="magic wand" src={MagicWand} />
Generate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
const generateScreen = () => {
popover?.hide()
dispatch("request-generate")
dispatch("generate")
}
</script>
Expand Down
Loading

0 comments on commit 17c9d9f

Please sign in to comment.