More WIP portal redesign!

This commit is contained in:
Andrew Kingston 2022-11-09 09:45:37 +00:00
parent bc6c5d6a43
commit c1d7cb84ca
28 changed files with 295 additions and 243 deletions

View File

@ -25,7 +25,7 @@
} }
.narrow { .narrow {
max-width: 800px; max-width: 840px;
margin: 0; margin: 0;
} }
</style> </style>

View File

@ -12,7 +12,7 @@
dummy.select() dummy.select()
document.execCommand("copy") document.execCommand("copy")
document.body.removeChild(dummy) document.body.removeChild(dummy)
notifications.success(`URL copied to clipboard`) notifications.success(`Copied to clipboard`)
} }
</script> </script>

View File

@ -15,6 +15,7 @@
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: stretch; align-items: stretch;
gap: 4px;
} }
.title { .title {
margin-left: var(--spacing-m); margin-left: var(--spacing-m);

View File

@ -1,41 +0,0 @@
<script>
import { ModalContent, Body, notifications } from "@budibase/bbui"
import { auth } from "stores/portal"
import { onMount } from "svelte"
import CopyInput from "components/common/inputs/CopyInput.svelte"
let apiKey = null
async function generateAPIKey() {
try {
apiKey = await auth.generateAPIKey()
notifications.success("New API key generated")
} catch (err) {
notifications.error("Unable to generate new API key")
}
// need to return false to keep modal open
return false
}
onMount(async () => {
try {
apiKey = await auth.fetchAPIKey()
} catch (err) {
notifications.error("Unable to fetch API key")
}
})
</script>
<ModalContent
title="Developer information"
showConfirmButton={false}
showSecondaryButton={true}
secondaryButtonText="Re-generate key"
secondaryAction={generateAPIKey}
>
<Body size="S">
You can find information about your developer account here, such as the API
key used to access the Budibase API.
</Body>
<CopyInput bind:value={apiKey} label="API key" />
</ModalContent>

View File

@ -11,8 +11,12 @@
<div class="app-row"> <div class="app-row">
<div class="header"> <div class="header">
<div class="title" data-cy={`${app.devId}`}> <div class="title" data-cy={`${app.devId}`}>
<div class="app-icon" style="color: {app.icon?.color || ''}"> <div class="app-icon">
<Icon size="L" name={app.icon?.name || "Apps"} /> <Icon
size="L"
name={app.icon?.name || "Apps"}
color={app.icon?.color}
/>
</div> </div>
<div class="name" data-cy="app-name-link" on:click={() => editApp(app)}> <div class="name" data-cy="app-name-link" on:click={() => editApp(app)}>
<Heading size="S"> <Heading size="S">
@ -96,7 +100,7 @@
.app-status:not(.deployed) :global(.spectrum-Icon), .app-status:not(.deployed) :global(.spectrum-Icon),
.app-status:not(.deployed) :global(.spectrum-Body) { .app-status:not(.deployed) :global(.spectrum-Body) {
color: var(--spectrum-global-color-gray-700); color: var(--spectrum-global-color-gray-600);
} }
.app-row-actions { .app-row-actions {

View File

@ -89,7 +89,7 @@
{#if $auth.user && loaded} {#if $auth.user && loaded}
<div class="container"> <div class="container">
<Page> <Page narrow>
<div class="content"> <div class="content">
<Layout noPadding> <Layout noPadding>
<div class="header"> <div class="header">
@ -196,6 +196,11 @@
.container { .container {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
padding: 80px;
} }
.content { .content {
width: 100%; width: 100%;

View File

@ -15,14 +15,12 @@
import { onMount } from "svelte" import { onMount } from "svelte"
import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte" import UpdateUserInfoModal from "components/settings/UpdateUserInfoModal.svelte"
import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte" import ChangePasswordModal from "components/settings/ChangePasswordModal.svelte"
import UpdateAPIKeyModal from "components/settings/UpdateAPIKeyModal.svelte"
import Logo from "assets/bb-emblem.svg" import Logo from "assets/bb-emblem.svg"
import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags" import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags"
let loaded = false let loaded = false
let userInfoModal let userInfoModal
let changePasswordModal let changePasswordModal
let apiKeyModal
let mobileMenuVisible = false let mobileMenuVisible = false
let activeTab = "Apps" let activeTab = "Apps"
@ -42,92 +40,79 @@
title: "Users", title: "Users",
href: "/builder/portal/users/users", href: "/builder/portal/users/users",
}, },
{ title: "Auth", href: "/builder/portal/manage/auth" },
{ title: "Email", href: "/builder/portal/manage/email" },
{ {
title: "Plugins", title: "Plugins",
href: "/builder/portal/manage/plugins", href: "/builder/portal/plugins",
badge: "New", badge: "New",
}, },
{ {
title: "Organisation", title: "Usage",
href: "/builder/portal/settings/organisation", href: "/builder/portal/usage",
heading: "Settings",
}, },
{ {
title: "Theming", title: "Settings",
href: "/builder/portal/settings/theming", href: "/builder/portal/settings",
},
])
if (!$adminStore.cloud) {
menu = menu.concat([
{
title: "Update",
href: "/builder/portal/settings/update",
},
])
}
} else {
menu = menu.concat([
{
title: "Theming",
href: "/builder/portal/settings/theming",
heading: "Settings",
}, },
]) ])
// if (!$adminStore.cloud) {
// menu = menu.concat([
// {
// title: "Update",
// href: "/builder/portal/settings/update",
// },
// ])
// }
} }
// add link to account portal if the user has access // add link to account portal if the user has access
let accountSectionAdded = false let accountSectionAdded = false
// link out to account-portal if account holder in cloud or always in self-host // link out to account-portal if account holder in cloud or always in self-host
if ($auth?.user?.accountPortalAccess || (!$adminStore.cloud && admin)) { // if ($auth?.user?.accountPortalAccess || (!$adminStore.cloud && admin)) {
accountSectionAdded = true // accountSectionAdded = true
menu = menu.concat([ // menu = menu.concat([
{ // {
title: "Account", // title: "Account",
href: $adminStore.accountPortalUrl, // href: $adminStore.accountPortalUrl,
heading: "Account", // heading: "Account",
}, // },
]) // ])
} // }
//
if (isEnabled(TENANT_FEATURE_FLAGS.LICENSING)) { // if (isEnabled(TENANT_FEATURE_FLAGS.LICENSING)) {
// always show usage in self-host or cloud if licensing enabled // // always show usage in self-host or cloud if licensing enabled
menu = menu.concat([ // menu = menu.concat([
{ // {
title: "Usage", // title: "Usage",
href: "/builder/portal/settings/usage", // href: "/builder/portal/settings/usage",
heading: accountSectionAdded ? "" : "Account", // heading: accountSectionAdded ? "" : "Account",
}, // },
]) // ])
//
// show the relevant hosting upgrade page // // show the relevant hosting upgrade page
if ($adminStore.cloud && $auth?.user?.accountPortalAccess) { // if ($adminStore.cloud && $auth?.user?.accountPortalAccess) {
menu = menu.concat([ // menu = menu.concat([
{ // {
title: "Upgrade", // title: "Upgrade",
href: $adminStore.accountPortalUrl + "/portal/upgrade", // href: $adminStore.accountPortalUrl + "/portal/upgrade",
badge: "New", // badge: "New",
}, // },
]) // ])
} // }
//
// show the billing page to licensed account holders in cloud // // show the billing page to licensed account holders in cloud
if ( // if (
$auth?.user?.accountPortalAccess && // $auth?.user?.accountPortalAccess &&
$auth.user.account.stripeCustomerId // $auth.user.account.stripeCustomerId
) { // ) {
menu = menu.concat([ // menu = menu.concat([
{ // {
title: "Billing", // title: "Billing",
href: $adminStore.accountPortalUrl + "/portal/billing", // href: $adminStore.accountPortalUrl + "/portal/billing",
}, // },
]) // ])
} // }
} // }
menu = menu.filter(item => !!item) menu = menu.filter(item => !!item)
return menu return menu
@ -216,11 +201,6 @@
> >
Update user information Update user information
</MenuItem> </MenuItem>
{#if $auth.isBuilder}
<MenuItem icon="Key" on:click={() => apiKeyModal.show()}>
View API key
</MenuItem>
{/if}
<MenuItem <MenuItem
icon="LockClosed" icon="LockClosed"
on:click={() => changePasswordModal.show()} on:click={() => changePasswordModal.show()}
@ -247,9 +227,6 @@
<Modal bind:this={changePasswordModal}> <Modal bind:this={changePasswordModal}>
<ChangePasswordModal /> <ChangePasswordModal />
</Modal> </Modal>
<Modal bind:this={apiKeyModal}>
<UpdateAPIKeyModal />
</Modal>
{/if} {/if}
<style> <style>

View File

@ -39,7 +39,7 @@
let automationErrors let automationErrors
let accessFilterList = null let accessFilterList = null
$: welcomeHeader = `Welcome ${auth?.user?.firstName || "back"}` $: welcomeHeader = `Welcome ${$auth?.user?.firstName || "back"}`
$: enrichedApps = enrichApps($apps, $auth.user, sortBy) $: enrichedApps = enrichApps($apps, $auth.user, sortBy)
$: filteredApps = enrichedApps.filter( $: filteredApps = enrichedApps.filter(
app => app =>

View File

@ -1,20 +0,0 @@
<script>
import { Page } from "@budibase/bbui"
import { auth } from "stores/portal"
import { page, redirect } from "@roxi/routify"
// Only admins allowed here
$: {
if (!$auth.isAdmin) {
$redirect("../")
}
}
$: wide = $page.path.includes("email/:template")
</script>
{#if $auth.isAdmin}
<Page maxWidth="90ch" {wide}>
<slot />
</Page>
{/if}

View File

@ -8,6 +8,7 @@
Divider, Divider,
Modal, Modal,
Search, Search,
Page,
} from "@budibase/bbui" } from "@budibase/bbui"
import { onMount } from "svelte" import { onMount } from "svelte"
import { plugins, admin } from "stores/portal" import { plugins, admin } from "stores/portal"
@ -42,43 +43,42 @@
}) })
</script> </script>
<Layout noPadding> <Page narrow>
<Layout gap="XS" noPadding>
<Heading size="M">Plugins</Heading>
<Body>Add your own custom datasources and components.</Body>
</Layout>
<Divider size="S" />
<Layout noPadding> <Layout noPadding>
<div class="controls"> <Layout gap="XS" noPadding>
<div> <Heading size="M">Plugins</Heading>
<Button on:click={modal.show} newStyles cta icon={"Add"}> <Body>Add your own custom datasources and components.</Body>
Add plugin </Layout>
</Button> <Divider />
</div> <Layout noPadding>
{#if $plugins?.length} <div class="controls">
<div class="filters"> <div>
<div class="select"> <Button on:click={modal.show} newStyles cta>Add plugin</Button>
<Select
bind:value={filter}
placeholder={null}
options={filterOptions}
autoWidth
quiet
/>
</div>
<Search bind:value={searchTerm} placeholder="Search plugins" />
</div> </div>
{#if $plugins?.length}
<div class="filters">
<div class="select">
<Select
bind:value={filter}
placeholder={null}
options={filterOptions}
autoWidth
/>
</div>
<Search bind:value={searchTerm} placeholder="Search plugins" />
</div>
{/if}
</div>
{#if filteredPlugins?.length}
<Layout noPadding gap="S">
{#each filteredPlugins as plugin (plugin._id)}
<PluginRow {plugin} />
{/each}
</Layout>
{/if} {/if}
</div> </Layout>
{#if filteredPlugins?.length}
<Layout noPadding gap="S">
{#each filteredPlugins as plugin (plugin._id)}
<PluginRow {plugin} />
{/each}
</Layout>
{/if}
</Layout> </Layout>
</Layout> </Page>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<AddPluginModal /> <AddPluginModal />

View File

@ -1,7 +1,60 @@
<script> <script>
import { url, isActive } from "@roxi/routify"
import { Page } from "@budibase/bbui" import { Page } from "@budibase/bbui"
import { Content, SideNav, SideNavItem } from "components/portal/page"
</script> </script>
<Page maxWidth="90ch"> <Page narrow>
<slot /> <Content>
<div slot="side-nav">
<SideNav title="Settings">
<SideNavItem
text="API Key"
url={$url("./api")}
active={$isActive("./api")}
/>
<SideNavItem
text="Auth"
url={$url("./auth")}
active={$isActive("./auth")}
/>
<SideNavItem
text="Email"
url={$url("./email")}
active={$isActive("./email")}
/>
<SideNavItem
text="Organisation"
url={$url("./organisation")}
active={$isActive("./organisation")}
/>
<SideNavItem
text="Version"
url={$url("./version")}
active={$isActive("./version")}
/>
<SideNavItem
text="Theme"
url={$url("./theme")}
active={$isActive("./theme")}
/>
<SideNavItem
text="Upgrade"
url={$url("./upgrade")}
active={$isActive("./upgrade")}
/>
<SideNavItem
text="Secrets"
url={$url("./secrets")}
active={$isActive("./secrets")}
/>
<SideNavItem
text="Scheduled Backups"
url={$url("./backups")}
active={$isActive("./backups")}
/>
</SideNav>
</div>
<slot />
</Content>
</Page> </Page>

View File

@ -0,0 +1,65 @@
<script>
import {
Layout,
Heading,
Body,
Divider,
Button,
Label,
notifications,
} from "@budibase/bbui"
import { auth } from "stores/portal"
import { onMount } from "svelte"
import CopyInput from "components/common/inputs/CopyInput.svelte"
let apiKey = null
async function generateAPIKey() {
try {
apiKey = await auth.generateAPIKey()
notifications.success("New API key generated")
} catch (err) {
notifications.error("Unable to generate new API key")
}
// need to return false to keep modal open
return false
}
onMount(async () => {
try {
apiKey = await auth.fetchAPIKey()
} catch (err) {
notifications.error("Unable to fetch API key")
}
})
</script>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading size="M">API Key</Heading>
<Body>You API key to access the Budibase public API</Body>
</Layout>
<Divider />
<div class="fields">
<div class="field">
<Label size="L">API key</Label>
<CopyInput bind:value={apiKey} />
</div>
</div>
<div>
<Button newStyles secondary on:click={generateAPIKey}>Regenerate key</Button
>
</div>
</Layout>
<style>
.fields {
display: grid;
grid-gap: var(--spacing-m);
}
.field {
display: grid;
grid-template-columns: 120px 1fr;
align-items: center;
}
</style>

View File

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

View File

@ -6,13 +6,13 @@
<Layout noPadding> <Layout noPadding>
<Layout gap="XS" noPadding> <Layout gap="XS" noPadding>
<Heading size="M">Theming</Heading> <Heading size="M">Theme</Heading>
<Body>Customize how Budibase looks and feels.</Body> <Body>Customize how Budibase looks and feels.</Body>
</Layout> </Layout>
<Divider /> <Divider />
<div class="fields"> <div class="fields">
<div class="field"> <div class="field">
<Label size="L">Builder theme</Label> <Label size="L">Theme</Label>
<Select <Select
options={Constants.Themes} options={Constants.Themes}
bind:value={$themeStore.theme} bind:value={$themeStore.theme}

View File

@ -8,6 +8,7 @@
Detail, Detail,
Link, Link,
TooltipWrapper, TooltipWrapper,
Page,
} from "@budibase/bbui" } from "@budibase/bbui"
import { onMount } from "svelte" import { onMount } from "svelte"
import { admin, auth, licensing } from "../../../../stores/portal" import { admin, auth, licensing } from "../../../../stores/portal"
@ -176,32 +177,32 @@
</script> </script>
{#if loaded} {#if loaded}
<Layout noPadding> <Page narrow>
<Layout noPadding gap="XS"> <Layout noPadding>
<Heading>Usage</Heading> <Layout noPadding gap="XS">
<Body> <Heading>Usage</Heading>
Get information about your current usage within Budibase. <Body>
{#if accountPortalAccess} <div>Get information about your current usage within Budibase.</div>
To upgrade your plan and usage limits visit your <Link {#if accountPortalAccess}
on:click={goToAccountPortal} <div>
size="L">Account</Link To upgrade your plan and usage limits visit your <Link
> on:click={goToAccountPortal}
{:else} size="L">Account</Link
To upgrade your plan and usage limits contact your account holder. >
{/if} </div>
</Body> {/if}
</Layout> </Body>
<Divider /> </Layout>
<DashCard <Divider />
description="YOUR CURRENT PLAN" <DashCard
title={planTitle()} description="YOUR CURRENT PLAN"
{primaryActionText} title={planTitle()}
primaryAction={accountPortalAccess ? goToAccountPortal : undefined} {primaryActionText}
{textRows} primaryAction={accountPortalAccess ? goToAccountPortal : undefined}
> {textRows}
<Layout gap="S" noPadding> >
<Layout gap="S"> <div class="content">
<div class="usages"> <div class="column">
<Layout noPadding> <Layout noPadding>
{#each staticUsage as usage} {#each staticUsage as usage}
<div class="usage"> <div class="usage">
@ -213,44 +214,51 @@
{/each} {/each}
</Layout> </Layout>
</div> </div>
</Layout>
{#if monthlyUsage.length} {#if monthlyUsage.length}
<div class="monthly-container"> <div class="column">
<Layout gap="S"> <Layout noPadding gap="M">
<Heading size="S" weight="light">Monthly</Heading> <Layout gap="XS" noPadding>
<div class="detail"> <Heading size="S">Monthly limits</Heading>
<TooltipWrapper tooltip={new Date(quotaReset)}> <div class="detail">
<Detail size="M">Resets in {daysRemainingInMonth} days</Detail <TooltipWrapper tooltip={new Date(quotaReset)}>
> <Detail size="M">
</TooltipWrapper> Resets in {daysRemainingInMonth} days
</div> </Detail>
<div class="usages"> </TooltipWrapper>
<Layout noPadding> </div>
</Layout>
<Layout noPadding gap="M">
{#each monthlyUsage as usage} {#each monthlyUsage as usage}
<div class="usage"> <Usage
<Usage {usage}
{usage} warnWhenFull={WARN_USAGE.includes(usage.name)}
warnWhenFull={WARN_USAGE.includes(usage.name)} />
/>
</div>
{/each} {/each}
</Layout> </Layout>
</div> </Layout>
</Layout> </div>
</div> {/if}
{/if} </div>
</Layout> </DashCard>
</DashCard> </Layout>
</Layout> </Page>
{/if} {/if}
<style> <style>
.usages { .content {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
gap: 40px;
}
.column {
flex: 1 1 0;
} }
.detail :global(.spectrum-Detail) { .detail :global(.spectrum-Detail) {
color: var(--spectrum-global-color-gray-700); color: var(--spectrum-global-color-gray-700);
margin-bottom: 5px;
} }
.detail :global(.icon) { .detail :global(.icon) {
margin-bottom: 0; margin-bottom: 0;

View File

@ -104,7 +104,7 @@
</script> </script>
{#if loaded} {#if loaded}
<Layout noPadding gap="XL"> <Layout noPadding gap="L">
<Breadcrumbs> <Breadcrumbs>
<Breadcrumb url={$url("./")} text="Groups" /> <Breadcrumb url={$url("./")} text="Groups" />
<Breadcrumb text={group?.name} /> <Breadcrumb text={group?.name} />

View File

@ -189,7 +189,7 @@
</script> </script>
{#if loaded} {#if loaded}
<Layout gap="XL" noPadding> <Layout gap="L" noPadding>
<Breadcrumbs> <Breadcrumbs>
<Breadcrumb url={$url("./")} text="Users" /> <Breadcrumb url={$url("./")} text="Users" />
<Breadcrumb text={user?.email} /> <Breadcrumb text={user?.email} />