Improve routing based on auth and roles, and use redirects rather than pushing new routes
This commit is contained in:
parent
473c9ebfa6
commit
78ba798be2
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import { auth } from "stores/backend"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $auth.user}
|
||||||
|
<slot />
|
||||||
|
{/if}
|
|
@ -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;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
$goto("./login")
|
$redirect("./login")
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
$goto("./apps")
|
$redirect("./apps")
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@roxi/routify"
|
import { redirect } from "@roxi/routify"
|
||||||
$goto("./builder")
|
$redirect("./builder")
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue