Improve routing based on auth and roles, and use redirects rather than pushing new routes

This commit is contained in:
Andrew Kingston 2021-05-18 14:39:26 +01:00
parent 473c9ebfa6
commit 78ba798be2
10 changed files with 155 additions and 200 deletions

View File

@ -25,11 +25,11 @@
<Layout noPadding gap="XS" alignContent="start"> <Layout noPadding gap="XS" alignContent="start">
<div class="preview" use:gradient={{ seed: app.name }} /> <div class="preview" use:gradient={{ seed: app.name }} />
<div class="title"> <div class="title">
<Link on:click={() => openApp(app)}> <div class="name" on:click={() => openApp(app)}>
<Heading size="XS"> <Heading size="XS">
{app.name} {app.name}
</Heading> </Heading>
</Link> </div>
<ActionMenu align="right"> <ActionMenu align="right">
<Icon slot="control" name="More" hoverable /> <Icon slot="control" name="More" hoverable />
<MenuItem on:click={() => exportApp(app)} icon="Download"> <MenuItem on:click={() => exportApp(app)} icon="Download">
@ -76,7 +76,7 @@
align-items: center; align-items: center;
} }
.title :global(a) { .name {
text-decoration: none; text-decoration: none;
flex: 1 1 auto; flex: 1 1 auto;
width: 0; width: 0;

View File

@ -1,13 +1,6 @@
<script> <script>
import { gradient } from "actions" import { gradient } from "actions"
import { import { Heading, Button, Icon, ActionMenu, MenuItem } from "@budibase/bbui"
Heading,
Button,
Icon,
ActionMenu,
MenuItem,
Link,
} from "@budibase/bbui"
import { auth } from "stores/backend" import { auth } from "stores/backend"
export let app export let app
@ -21,11 +14,11 @@
<div class="title" class:last> <div class="title" class:last>
<div class="preview" use:gradient={{ seed: app.name }} /> <div class="preview" use:gradient={{ seed: app.name }} />
<Link on:click={() => openApp(app)}> <div class="name" on:click={() => openApp(app)}>
<Heading size="XS"> <Heading size="XS">
{app.name} {app.name}
</Heading> </Heading>
</Link> </div>
</div> </div>
<div class:last> <div class:last>
Edited {Math.round(Math.random() * 10 + 1)} months ago Edited {Math.round(Math.random() * 10 + 1)} months ago
@ -66,7 +59,7 @@
width: 40px; width: 40px;
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
} }
.title :global(a) { .name {
text-decoration: none; text-decoration: none;
} }
.title :global(h1:hover) { .title :global(h1:hover) {

View File

@ -1,6 +1,6 @@
<script> <script>
import { onMount } from "svelte" import { onMount } from "svelte"
import { isActive, goto } from "@roxi/routify" import { isActive, goto, redirect } from "@roxi/routify"
import { auth } from "stores/backend" import { auth } from "stores/backend"
import { admin } from "stores/portal" import { admin } from "stores/portal"
@ -16,14 +16,14 @@
// Force creation of an admin user if one doesn't exist // Force creation of an admin user if one doesn't exist
$: { $: {
if (loaded && !hasAdminUser) { if (loaded && !hasAdminUser) {
$goto("./admin") $redirect("./admin")
} }
} }
// Redirect to log in at any time if the user isn't authenticated // Redirect to log in at any time if the user isn't authenticated
$: { $: {
if (loaded && hasAdminUser && !$auth.user && !$isActive("./auth")) { if (loaded && hasAdminUser && !$auth.user && !$isActive("./auth")) {
$goto("./auth/login") $redirect("./auth/login")
} }
} }
</script> </script>

View File

@ -0,0 +1,7 @@
<script>
import { auth } from "stores/backend"
</script>
{#if $auth.user}
<slot />
{/if}

View File

@ -9,154 +9,86 @@
Avatar, Avatar,
Page, Page,
Icon, Icon,
notifications,
Body, Body,
} from "@budibase/bbui" } from "@budibase/bbui"
import api, { del } from "builderStore/api"
import analytics from "analytics"
import { onMount } from "svelte" import { onMount } from "svelte"
import { apps, organisation } from "stores/portal" import { apps, organisation } from "stores/portal"
import { auth } from "stores/backend" import { auth } from "stores/backend"
import download from "downloadjs"
import { goto } from "@roxi/routify" import { goto } from "@roxi/routify"
import { AppStatus } from "constants" import { AppStatus } from "constants"
import { gradient } from "actions" import { gradient } from "actions"
let layout = "grid"
let template
let appToDelete
let creationModal
let deletionModal
let creatingApp = false
let loaded = false let loaded = false
const checkKeys = async () => {
const response = await api.get(`/api/keys/`)
const keys = await response.json()
if (keys.userId) {
analytics.identify(keys.userId)
}
}
const initiateAppCreation = () => {
creationModal.show()
creatingApp = true
}
const initiateAppImport = () => {
template = { fromFile: true }
creationModal.show()
creatingApp = true
}
const stopAppCreation = () => {
template = null
creatingApp = false
}
const openApp = app => {
$goto(`../../app/${app._id}`)
}
const exportApp = app => {
try {
download(
`/api/backups/export?appId=${app._id}&appname=${encodeURIComponent(
app.name
)}`
)
notifications.success("App export complete")
} catch (err) {
console.error(err)
notifications.error("App export failed")
}
}
const deleteApp = app => {
appToDelete = app
deletionModal.show()
}
const confirmDeleteApp = async () => {
if (!appToDelete) {
return
}
await del(`/api/applications/${appToDelete?._id}`)
await apps.load()
appToDelete = null
}
onMount(async () => { onMount(async () => {
checkKeys()
await apps.load(AppStatus.DEV) await apps.load(AppStatus.DEV)
loaded = true loaded = true
}) })
$: console.log($auth.user)
</script> </script>
<div class="container"> {#if loaded}
<Page> <div class="container">
<div class="content"> <Page>
<Layout noPadding> <div class="content">
<img src={$organisation.logoUrl} /> <Layout noPadding>
<div class="info-title"> <img src={$organisation.logoUrl} />
<Layout noPadding gap="XS"> <div class="info-title">
<Heading size="L">Hey {$auth.user.email}</Heading> <Layout noPadding gap="XS">
<Body noPadding> <Heading size="L">Hey {$auth.user.email}</Heading>
Welcome to the {$organisation.company} portal. Below you'll find the <Body noPadding>
list of apps that you have access to, as well as company news and the Welcome to the {$organisation.company} portal. Below you'll find
employee handbook. the list of apps that you have access to, as well as company news
</Body> and the employee handbook.
</Layout> </Body>
<ActionMenu align="right"> </Layout>
<div slot="control" class="avatar"> <ActionMenu align="right">
<Avatar size="M" name="John Doe" /> <div slot="control" class="avatar">
<Icon size="XL" name="ChevronDown" /> <Avatar size="M" name="John Doe" />
</div> <Icon size="XL" name="ChevronDown" />
<MenuItem icon="UserEdit" on:click={auth.logout}>
Update user information
</MenuItem>
<MenuItem icon="LockClosed" on:click={auth.logout}>
Update password
</MenuItem>
<MenuItem icon="UserDeveloper" on:click={() => $goto("../portal")}>
Open developer mode
</MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div>
<Divider />
<div class="apps-title">
<Heading>Apps</Heading>
<Select placeholder="Filter by groups" />
</div>
<div class="group">
<Layout gap="S" noPadding>
<div class="group-title">
<Body weight="500" noPadding size="XS">GROUP</Body>
{#if $auth.user?.builder?.global}
<Icon name="Settings" hoverable />
{/if}
</div>
{#each $apps as app, idx (app.appId)}
<div class="app" on:click={() => $goto(`../app/${app.appId}`)}>
<div class="preview" use:gradient={{ seed: app.name }} />
<div class="app-info">
<Heading size="XS">{app.name}</Heading>
<Body noPadding size="S">
Edited {Math.round(Math.random() * 10 + 1)} months ago
</Body>
</div>
<Icon name="ChevronRight" />
</div> </div>
{/each} <MenuItem icon="UserEdit">Update user information</MenuItem>
</Layout> <MenuItem icon="LockClosed">Update password</MenuItem>
</div> <MenuItem
</Layout> icon="UserDeveloper"
</div> on:click={() => $goto("../portal")}
</Page> >
</div> Open developer mode
</MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div>
<Divider />
<div class="apps-title">
<Heading>Apps</Heading>
<Select placeholder="Filter by groups" />
</div>
<div class="group">
<Layout gap="S" noPadding>
<div class="group-title">
<Body weight="500" noPadding size="XS">GROUP</Body>
{#if $auth.user?.builder?.global}
<Icon name="Settings" hoverable />
{/if}
</div>
{#each $apps as app, idx (app.appId)}
<a class="app" target="_blank" href={`/${app.appId}`}>
<div class="preview" use:gradient={{ seed: app.name }} />
<div class="app-info">
<Heading size="XS">{app.name}</Heading>
<Body noPadding size="S">
Edited {Math.round(Math.random() * 10 + 1)} months ago
</Body>
</div>
<Icon name="ChevronRight" />
</a>
{/each}
</Layout>
</div>
</Layout>
</div>
</Page>
</div>
{/if}
<style> <style>
.container { .container {
@ -208,6 +140,7 @@
border-radius: var(--border-radius-s); border-radius: var(--border-radius-s);
align-items: center; align-items: center;
grid-gap: var(--spacing-xl); grid-gap: var(--spacing-xl);
color: inherit;
} }
.app:hover { .app:hover {
cursor: pointer; cursor: pointer;

View File

@ -1,4 +1,4 @@
<script> <script>
import { goto } from "@roxi/routify" import { redirect } from "@roxi/routify"
$goto("./login") $redirect("./login")
</script> </script>

View File

@ -1,4 +1,14 @@
<script> <script>
import { goto } from "@roxi/routify" import { redirect } from "@roxi/routify"
$goto("./portal") import { auth } from "stores/backend"
$: {
if (!$auth.user) {
$redirect("./auth/login")
} else if ($auth.user.builder?.global) {
$redirect("./portal")
} else {
$redirect("./apps")
}
}
</script> </script>

View File

@ -1,5 +1,5 @@
<script> <script>
import { isActive, goto } from "@roxi/routify" import { isActive, redirect, goto } from "@roxi/routify"
import { import {
Icon, Icon,
Avatar, Avatar,
@ -15,12 +15,12 @@
import { organisation } from "stores/portal" import { organisation } from "stores/portal"
import { auth } from "stores/backend" import { auth } from "stores/backend"
import BuilderSettingsModal from "components/start/BuilderSettingsModal.svelte" import BuilderSettingsModal from "components/start/BuilderSettingsModal.svelte"
import { onMount } from "svelte"
let oldSettingsModal let oldSettingsModal
let loaded = false
organisation.init() const menu = [
let menu = [
{ title: "Apps", href: "/builder/portal/apps" }, { title: "Apps", href: "/builder/portal/apps" },
{ title: "Drafts", href: "/builder/portal/drafts" }, { title: "Drafts", href: "/builder/portal/drafts" },
{ title: "Users", href: "/builder/portal/manage/users", heading: "Manage" }, { title: "Users", href: "/builder/portal/manage/users", heading: "Manage" },
@ -35,54 +35,66 @@
{ title: "Theming", href: "/builder/portal/theming" }, { title: "Theming", href: "/builder/portal/theming" },
{ title: "Account", href: "/builder/portal/account" }, { title: "Account", href: "/builder/portal/account" },
] ]
onMount(async () => {
// Prevent non-builders from accessing the portal
if (!$auth.user?.builder?.global) {
$redirect("../")
} else {
await organisation.init()
loaded = true
}
})
</script> </script>
<div class="container"> {#if loaded}
<div class="nav"> <div class="container">
<Layout paddingX="L" paddingY="L"> <div class="nav">
<div class="branding"> <Layout paddingX="L" paddingY="L">
<div class="name" on:click={() => $goto("./apps")}> <div class="branding">
<img <div class="name" on:click={() => $goto("./apps")}>
src={$organisation?.logoUrl || "https://i.imgur.com/ZKyklgF.png"} <img
alt="Logotype" src={$organisation?.logoUrl || "https://i.imgur.com/ZKyklgF.png"}
/> alt="Logotype"
<span>{$organisation?.company || "Budibase"}</span> />
<span>{$organisation?.company || "Budibase"}</span>
</div>
<div class="onboarding">
<ConfigChecklist />
</div>
</div> </div>
<div class="onboarding"> <div class="menu">
<ConfigChecklist /> <Navigation>
{#each menu as { title, href, heading }}
<Item selected={$isActive(href)} {href} {heading}>{title}</Item>
{/each}
</Navigation>
</div> </div>
</div> </Layout>
<div class="menu">
<Navigation>
{#each menu as { title, href, heading }}
<Item selected={$isActive(href)} {href} {heading}>{title}</Item>
{/each}
</Navigation>
</div>
</Layout>
</div>
<div class="main">
<div class="toolbar">
<Search placeholder="Global search" />
<ActionMenu align="right">
<div slot="control" class="avatar">
<Avatar size="M" name="John Doe" />
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem icon="Settings" on:click={oldSettingsModal.show}>
Old settings
</MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div> </div>
<div class="content"> <div class="main">
<slot /> <div class="toolbar">
<Search placeholder="Global search" />
<ActionMenu align="right">
<div slot="control" class="avatar">
<Avatar size="M" name="John Doe" />
<Icon size="XL" name="ChevronDown" />
</div>
<MenuItem icon="Settings" on:click={oldSettingsModal.show}>
Old settings
</MenuItem>
<MenuItem icon="LogOut" on:click={auth.logout}>Log out</MenuItem>
</ActionMenu>
</div>
<div class="content">
<slot />
</div>
</div> </div>
</div> </div>
</div> <Modal bind:this={oldSettingsModal} width="30%">
<Modal bind:this={oldSettingsModal} width="30%"> <BuilderSettingsModal />
<BuilderSettingsModal /> </Modal>
</Modal> {/if}
<style> <style>
.container { .container {

View File

@ -1,4 +1,4 @@
<script> <script>
import { goto } from "@roxi/routify" import { redirect } from "@roxi/routify"
$goto("./apps") $redirect("./apps")
</script> </script>

View File

@ -1,4 +1,4 @@
<script> <script>
import { goto } from "@roxi/routify" import { redirect } from "@roxi/routify"
$goto("./builder") $redirect("./builder")
</script> </script>