Add role setting to navigation links to allow easily customising which roles see which links

This commit is contained in:
Andrew Kingston 2022-06-09 14:28:02 +01:00
parent 5a35c182c1
commit 3d1c5111e9
7 changed files with 63 additions and 31 deletions

View File

@ -11,6 +11,7 @@
import { dndzone } from "svelte-dnd-action" import { dndzone } from "svelte-dnd-action"
import { generate } from "shortid" import { generate } from "shortid"
import { store } from "builderStore" import { store } from "builderStore"
import RoleSelect from "components/design/settings/controls/RoleSelect.svelte"
export let links = [] export let links = []
@ -75,6 +76,7 @@
placeholder="URL" placeholder="URL"
options={urlOptions} options={urlOptions}
/> />
<RoleSelect bind:value={link.roleId} placeholder="Minimum role" />
<Icon <Icon
name="Close" name="Close"
hoverable hoverable
@ -95,7 +97,7 @@
<style> <style>
.container { .container {
width: 100%; width: 100%;
max-width: 600px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
} }
.links { .links {

View File

@ -59,6 +59,7 @@
$screenStore.screens, $screenStore.screens,
$devToolsStore.role $devToolsStore.role
) )
permissionError = false
routeStore.actions.navigate(route) routeStore.actions.navigate(route)
} else { } else {
// No screens likely means the user has no permissions to view this app // No screens likely means the user has no permissions to view this app
@ -120,17 +121,24 @@
dir="ltr" dir="ltr"
class="spectrum spectrum--medium {$themeStore.theme}" class="spectrum spectrum--medium {$themeStore.theme}"
> >
{#if permissionError} <DeviceBindingsProvider>
<div class="error">
<Layout justifyItems="center" gap="S">
{@html ErrorSVG}
<Heading size="L">You don't have permission to use this app</Heading>
<Body size="S">Ask your administrator to grant you access</Body>
</Layout>
</div>
{:else if $screenStore.activeLayout}
<UserBindingsProvider> <UserBindingsProvider>
<DeviceBindingsProvider> {#if permissionError}
<div class="error-wrapper">
{#if isDevPreview}
<DevToolsHeader />
{/if}
<div class="error">
<Layout justifyItems="center" gap="S">
{@html ErrorSVG}
<Heading size="L"
>You don't have permission to use this app</Heading
>
<Body size="S">Ask your administrator to grant you access</Body>
</Layout>
</div>
</div>
{:else if $screenStore.activeLayout}
<StateBindingsProvider> <StateBindingsProvider>
<RowSelectionProvider> <RowSelectionProvider>
<!-- Settings bar can be rendered outside of device preview --> <!-- Settings bar can be rendered outside of device preview -->
@ -198,9 +206,9 @@
</div> </div>
</RowSelectionProvider> </RowSelectionProvider>
</StateBindingsProvider> </StateBindingsProvider>
</DeviceBindingsProvider> {/if}
</UserBindingsProvider> </UserBindingsProvider>
{/if} </DeviceBindingsProvider>
</div> </div>
<KeyboardManager /> <KeyboardManager />
{/if} {/if}
@ -248,8 +256,11 @@
overflow: hidden; overflow: hidden;
} }
.error-wrapper {
width: 100%;
height: 100%;
}
.error { .error {
position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
display: grid; display: grid;
@ -258,23 +269,19 @@
text-align: center; text-align: center;
padding: 20px; padding: 20px;
} }
.error :global(svg) { .error :global(svg) {
fill: var(--spectrum-global-color-gray-500); fill: var(--spectrum-global-color-gray-500);
width: 80px; width: 80px;
height: 80px; height: 80px;
} }
.error :global(h1), .error :global(h1),
.error :global(p) { .error :global(p) {
color: var(--spectrum-global-color-gray-800); color: var(--spectrum-global-color-gray-800);
} }
.error :global(p) { .error :global(p) {
font-style: italic; font-style: italic;
margin-top: -0.5em; margin-top: -0.5em;
} }
.error :global(h1) { .error :global(h1) {
font-weight: 400; font-weight: 400;
} }
@ -284,12 +291,10 @@
#clip-root.preview { #clip-root.preview {
padding: 2px; padding: 2px;
} }
#clip-root.tablet-preview { #clip-root.tablet-preview {
width: calc(1024px + 6px); width: calc(1024px + 6px);
height: calc(768px + 6px); height: calc(768px + 6px);
} }
#clip-root.mobile-preview { #clip-root.mobile-preview {
width: calc(390px + 6px); width: calc(390px + 6px);
height: calc(844px + 6px); height: calc(844px + 6px);

View File

@ -2,10 +2,12 @@
import { getContext, setContext } from "svelte" import { getContext, setContext } from "svelte"
import { writable } from "svelte/store" import { writable } from "svelte/store"
import { Heading, Icon } from "@budibase/bbui" import { Heading, Icon } from "@budibase/bbui"
import { FieldTypes } from "../../constants" import { FieldTypes } from "constants"
import active from "svelte-spa-router/active" import active from "svelte-spa-router/active"
import { RoleUtils } from "@budibase/frontend-core"
const { routeStore, styleable, linkable, builderStore } = getContext("sdk") const sdk = getContext("sdk")
const { routeStore, styleable, linkable, builderStore, currentRole } = sdk
const component = getContext("component") const component = getContext("component")
const context = getContext("context") const context = getContext("context")
@ -50,7 +52,7 @@
}) })
setContext("layout", store) setContext("layout", store)
$: validLinks = links?.filter(link => link.text && link.url) || [] $: validLinks = getValidLinks(links, $currentRole)
$: typeClass = NavigationClasses[navigation] || NavigationClasses.None $: typeClass = NavigationClasses[navigation] || NavigationClasses.None
$: navWidthClass = WidthClasses[navWidth || width] || WidthClasses.Large $: navWidthClass = WidthClasses[navWidth || width] || WidthClasses.Large
$: pageWidthClass = WidthClasses[pageWidth || width] || WidthClasses.Large $: pageWidthClass = WidthClasses[pageWidth || width] || WidthClasses.Large
@ -79,6 +81,17 @@
} }
} }
const getValidLinks = (allLinks, role) => {
// Strip links missing required info
let validLinks = (allLinks || []).filter(link => link.text && link.url)
// Filter to only links allowed by the current role
const priority = RoleUtils.getRolePriority(role)
return validLinks.filter(link => {
return !link.roleId || RoleUtils.getRolePriority(link.roleId) <= priority
})
}
const isInternal = url => { const isInternal = url => {
return url.startsWith("/") return url.startsWith("/")
} }

View File

@ -21,11 +21,11 @@
} }
onMount(() => { onMount(() => {
resizeObserver.observe(document.getElementById("app-root")) resizeObserver.observe(document.getElementById("spectrum-root"))
}) })
onDestroy(() => { onDestroy(() => {
resizeObserver.unobserve(document.getElementById("app-root")) resizeObserver.unobserve(document.getElementById("spectrum-root"))
}) })
</script> </script>

View File

@ -1,6 +1,6 @@
<script> <script>
import Provider from "./Provider.svelte" import Provider from "./Provider.svelte"
import { authStore, devToolsStore } from "stores" import { authStore, currentRole } from "stores"
import { ActionTypes } from "constants" import { ActionTypes } from "constants"
import { Constants } from "@budibase/frontend-core" import { Constants } from "@budibase/frontend-core"
@ -17,10 +17,6 @@
] ]
</script> </script>
<Provider <Provider key="user" data={{ ...$authStore, roleId: $currentRole }} {actions}>
key="user"
data={{ ...$authStore, roleId: $devToolsStore.role || $authStore?.roleId }}
{actions}
>
<slot /> <slot />
</Provider> </Provider>

View File

@ -8,6 +8,7 @@ import {
uploadStore, uploadStore,
rowSelectionStore, rowSelectionStore,
componentStore, componentStore,
currentRole,
} from "stores" } from "stores"
import { styleable } from "utils/styleable" import { styleable } from "utils/styleable"
import { linkable } from "utils/linkable" import { linkable } from "utils/linkable"
@ -26,6 +27,7 @@ export default {
builderStore, builderStore,
uploadStore, uploadStore,
componentStore, componentStore,
currentRole,
styleable, styleable,
linkable, linkable,
getAction, getAction,

View File

@ -1,3 +1,7 @@
import { derived } from "svelte/store"
import { devToolsStore } from "./devTools.js"
import { authStore } from "./auth.js"
export { authStore } from "./auth" export { authStore } from "./auth"
export { appStore } from "./app" export { appStore } from "./app"
export { notificationStore } from "./notification" export { notificationStore } from "./notification"
@ -13,8 +17,18 @@ export { devToolsStore } from "./devTools"
export { componentStore } from "./components" export { componentStore } from "./components"
export { uploadStore } from "./uploads.js" export { uploadStore } from "./uploads.js"
export { rowSelectionStore } from "./rowSelection.js" export { rowSelectionStore } from "./rowSelection.js"
// Context stores are layered and duplicated, so it is not a singleton // Context stores are layered and duplicated, so it is not a singleton
export { createContextStore } from "./context" export { createContextStore } from "./context"
// Initialises an app by loading screens and routes // Initialises an app by loading screens and routes
export { initialise } from "./initialise" export { initialise } from "./initialise"
// Derive the current role of the logged-in user, which may be overridden by
// dev tools
export const currentRole = derived(
[devToolsStore, authStore],
([$devToolsStore, $authStore]) => {
return $devToolsStore.role || $authStore?.roleId
}
)