Add role setting to navigation links to allow easily customising which roles see which links
This commit is contained in:
parent
5a35c182c1
commit
3d1c5111e9
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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("/")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue