Overview Tab refactoring and general updates to the homepage

This commit is contained in:
Dean 2022-05-06 15:52:49 +01:00
parent 1c5990b9d7
commit fffb4b0174
11 changed files with 265 additions and 201 deletions

View File

@ -5,20 +5,23 @@
ModalContent, ModalContent,
Modal, Modal,
notifications, notifications,
ProgressCircle,
} from "@budibase/bbui" } from "@budibase/bbui"
import { auth, apps } from "stores/portal" import { auth, apps } from "stores/portal"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { API } from "api" import { API } from "api"
export let app export let app
export let buttonSize = "M"
let APP_DEV_LOCK_SECONDS = 600 let APP_DEV_LOCK_SECONDS = 600 //common area for this?
let appLockModal let appLockModal
let processing = false
$: lockedBy = app?.lockedBy $: lockedBy = app?.lockedBy
$: lockedByYou = $auth.user.email === lockedBy?.email $: lockedByYou = $auth.user.email === lockedBy?.email
$: lockIdentifer = `${ $: lockIdentifer = `${
Object.prototype.hasOwnProperty.call(lockedBy, "firstName") lockedBy && Object.prototype.hasOwnProperty.call(lockedBy, "firstName")
? lockedBy?.firstName ? lockedBy?.firstName
: lockedBy?.email : lockedBy?.email
}` }`
@ -28,7 +31,7 @@
$: lockExpiry = getExpiryDuration(app) $: lockExpiry = getExpiryDuration(app)
const getExpiryDuration = app => { const getExpiryDuration = app => {
if (!app.lockedBy) { if (!app?.lockedBy?.lockedAt) {
return -1 return -1
} }
let expiry = let expiry =
@ -37,6 +40,7 @@
} }
const releaseLock = async () => { const releaseLock = async () => {
processing = true
if (app) { if (app) {
try { try {
await API.releaseAppLock(app.devId) await API.releaseAppLock(app.devId)
@ -48,6 +52,7 @@
} else { } else {
notifications.error("No application is selected") notifications.error("No application is selected")
} }
processing = false
} }
</script> </script>
@ -57,6 +62,7 @@
quiet quiet
secondary secondary
icon="LockClosed" icon="LockClosed"
size={buttonSize}
on:click={() => { on:click={() => {
appLockModal.show() appLockModal.show()
}} }}
@ -93,6 +99,7 @@
<Button <Button
secondary secondary
quiet={lockedBy && lockedByYou} quiet={lockedBy && lockedByYou}
disabled={processing}
on:click={() => { on:click={() => {
appLockModal.hide() appLockModal.hide()
}} }}
@ -104,12 +111,17 @@
{#if lockedByYou && lockExpiry > 0} {#if lockedByYou && lockExpiry > 0}
<Button <Button
secondary secondary
disabled={processing}
on:click={() => { on:click={() => {
releaseLock() releaseLock()
appLockModal.hide() appLockModal.hide()
}} }}
> >
<span class="unlock">Release Lock</span> {#if processing}
<ProgressCircle overBackground={true} size="S" />
{:else}
<span class="unlock">Release Lock</span>
{/if}
</Button> </Button>
{/if} {/if}
</ButtonGroup> </ButtonGroup>

View File

@ -11,6 +11,16 @@
import { API } from "api" import { API } from "api"
import clientPackage from "@budibase/client/package.json" import clientPackage from "@budibase/client/package.json"
export function show() {
updateModal.show()
}
export function hide() {
updateModal.hide()
}
export let hideIcon = false
let updateModal let updateModal
$: appId = $store.appId $: appId = $store.appId
@ -57,9 +67,11 @@
} }
</script> </script>
<div class="icon-wrapper" class:highlight={updateAvailable}> {#if !hideIcon}
<Icon name="Refresh" hoverable on:click={updateModal.show} /> <div class="icon-wrapper" class:highlight={updateAvailable}>
</div> <Icon name="Refresh" hoverable on:click={updateModal.show} />
</div>
{/if}
<Modal bind:this={updateModal}> <Modal bind:this={updateModal}>
<ModalContent <ModalContent
title="App version" title="App version"

View File

@ -36,7 +36,7 @@
{/if} {/if}
</div> </div>
<div class="desktop"> <div class="desktop">
<AppLockModal {app} /> <AppLockModal {app} buttonSize="S" />
</div> </div>
<div class="desktop"> <div class="desktop">
<div class="app-status"> <div class="app-status">

View File

@ -2,7 +2,6 @@
import { import {
Heading, Heading,
Layout, Layout,
Detail,
Button, Button,
Input, Input,
Select, Select,
@ -312,48 +311,7 @@
{welcomeBody} {welcomeBody}
</Body> </Body>
</Layout> </Layout>
<div class="buttons">
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
</Button>
{#if $apps?.length > 0}
<Button
icon="Experience"
size="M"
quiet
secondary
on:click={$goto("/builder/portal/apps/templates")}
>
Templates
</Button>
{/if}
{#if !$apps?.length}
<Button
dataCy="import-app-btn"
icon="Import"
size="L"
quiet
secondary
on:click={initiateAppImport}
>
Import app
</Button>
{/if}
</div>
</div> </div>
<div>
<Layout gap="S" justifyItems="center">
<img class="img-logo img-size" alt="logo" src={Logo} />
</Layout>
</div>
<Divider size="S" />
</div> </div>
{#if !$apps?.length && $templates?.length} {#if !$apps?.length && $templates?.length}
@ -363,7 +321,40 @@
{#if enrichedApps.length} {#if enrichedApps.length}
<Layout noPadding gap="S"> <Layout noPadding gap="S">
<div class="title"> <div class="title">
<Detail size="L">Apps</Detail> <div class="buttons">
<Button
dataCy="create-app-btn"
size="M"
icon="Add"
cta
on:click={initiateAppCreation}
>
{createAppButtonText}
</Button>
{#if $apps?.length > 0}
<Button
icon="Experience"
size="M"
quiet
secondary
on:click={$goto("/builder/portal/apps/templates")}
>
Templates
</Button>
{/if}
{#if !$apps?.length}
<Button
dataCy="import-app-btn"
icon="Import"
size="L"
quiet
secondary
on:click={initiateAppImport}
>
Import app
</Button>
{/if}
</div>
{#if enrichedApps.length > 1} {#if enrichedApps.length > 1}
<div class="app-actions"> <div class="app-actions">
{#if cloud} {#if cloud}
@ -394,7 +385,7 @@
</div> </div>
{/if} {/if}
</div> </div>
<Divider size="S" />
<div class="appTable"> <div class="appTable">
{#each filteredApps as app (app.appId)} {#each filteredApps as app (app.appId)}
<AppRow <AppRow
@ -475,9 +466,6 @@
.app-actions :global(> button) { .app-actions :global(> button) {
margin-right: 10px; margin-right: 10px;
} }
.title .welcome > .buttons {
padding-top: 30px;
}
.title { .title {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -13,12 +13,9 @@
ProgressCircle, ProgressCircle,
} from "@budibase/bbui" } from "@budibase/bbui"
import OverviewTab from "../_components/OverviewTab.svelte" import OverviewTab from "../_components/OverviewTab.svelte"
import VersionTab from "../_components/VersionTab.svelte" import SettingsTab from "../_components/SettingsTab.svelte"
import SelfHostTab from "../_components/SelfHostTab.svelte"
import { API } from "api" import { API } from "api"
import { onMount } from "svelte"
import { store } from "builderStore" import { store } from "builderStore"
import { roles, flags } from "stores/backend"
import { apps, auth } from "stores/portal" import { apps, auth } from "stores/portal"
import analytics, { Events, EventSource } from "analytics" import analytics, { Events, EventSource } from "analytics"
import { AppStatus } from "constants" import { AppStatus } from "constants"
@ -37,7 +34,7 @@
$: lockedBy = selectedApp?.lockedBy $: lockedBy = selectedApp?.lockedBy
$: lockedByYou = $auth.user.email === lockedBy?.email $: lockedByYou = $auth.user.email === lockedBy?.email
$: lockIdentifer = `${ $: lockIdentifer = `${
Object.prototype.hasOwnProperty.call(lockedBy, "firstName") lockedBy && Object.prototype.hasOwnProperty.call(lockedBy, "firstName")
? lockedBy?.firstName ? lockedBy?.firstName
: lockedBy?.email : lockedBy?.email
}` }`
@ -55,13 +52,7 @@
selectedApp?.status === AppStatus.DEPLOYED && latestDeployments?.length > 0 selectedApp?.status === AppStatus.DEPLOYED && latestDeployments?.length > 0
$: appUrl = `${window.origin}/app${selectedApp?.url}` $: appUrl = `${window.origin}/app${selectedApp?.url}`
$: tabs = [ $: tabs = ["Overview", "Automation History", "Backups", "Settings"]
"Overview",
"Automation History",
"Backups",
"App Version",
"Self-host",
]
$: selectedTab = "Overview" $: selectedTab = "Overview"
const handleTabChange = tabKey => { const handleTabChange = tabKey => {
@ -78,9 +69,8 @@
try { try {
const pkg = await API.fetchAppPackage(application) const pkg = await API.fetchAppPackage(application)
await store.actions.initialise(pkg) await store.actions.initialise(pkg)
// await automationStore.actions.fetch() await apps.load()
await roles.fetch() deployments = await fetchDeployments()
await flags.fetch()
return pkg return pkg
} catch (error) { } catch (error) {
notifications.error(`Error initialising app: ${error?.message}`) notifications.error(`Error initialising app: ${error?.message}`)
@ -108,8 +98,6 @@
} }
} }
//Show prod: published, appUrl
//Behaviour incorrect. It should be enabled if at least 1 live deployment is available
const viewApp = () => { const viewApp = () => {
if (isPublished) { if (isPublished) {
analytics.captureEvent(Events.APP.VIEW_PUBLISHED, { analytics.captureEvent(Events.APP.VIEW_PUBLISHED, {
@ -129,18 +117,6 @@
} }
$goto(`../../../app/${app.devId}`) $goto(`../../../app/${app.devId}`)
} }
onMount(async () => {
// if (!hasSynced && application) {
// try {
// await API.syncApp(application)
// } catch (error) {
// notifications.error("Failed to sync with production database")
// }
// hasSynced = true
// }
deployments = await fetchDeployments()
})
</script> </script>
<Page wide> <Page wide>
@ -187,7 +163,14 @@
disabled={!isPublished} disabled={!isPublished}
on:click={viewApp}>View app</Button on:click={viewApp}>View app</Button
> >
<Button size="M" cta icon="Edit" on:click={editApp(selectedApp)}> <Button
size="M"
cta
icon="Edit"
on:click={() => {
editApp(selectedApp)
}}
>
<span>Edit</span> <span>Edit</span>
</Button> </Button>
</ButtonGroup> </ButtonGroup>
@ -213,11 +196,8 @@
<Tab title="Backups"> <Tab title="Backups">
<div class="container">Backups contents</div> <div class="container">Backups contents</div>
</Tab> </Tab>
<Tab title="App Version"> <Tab title="Settings">
<VersionTab app={selectedApp} /> <SettingsTab app={selectedApp} />
</Tab>
<Tab title="Self-host">
<SelfHostTab />
</Tab> </Tab>
</Tabs> </Tabs>
{:catch error} {:catch error}

View File

@ -1,17 +1,43 @@
<script> <script>
import DashCard from "../../../../../components/common/DashCard.svelte" import DashCard from "components/common/DashCard.svelte"
import { AppStatus } from "constants" import { AppStatus } from "constants"
import { Icon, Heading, Link, Avatar } from "@budibase/bbui" import { Icon, Heading, Link, Avatar, notifications } from "@budibase/bbui"
import { store } from "builderStore" import { store } from "builderStore"
import clientPackage from "@budibase/client/package.json" import clientPackage from "@budibase/client/package.json"
import { processStringSync } from "@budibase/string-templates" import { processStringSync } from "@budibase/string-templates"
import { users, auth } from "stores/portal"
export let app export let app
export let deployments export let deployments
export let navigateTab export let navigateTab
const userInit = async () => {
try {
await users.init()
} catch (error) {
notifications.error("Error getting user list")
}
}
let userPromise = userInit()
$: updateAvailable = clientPackage.version !== $store.version $: updateAvailable = clientPackage.version !== $store.version
$: isPublished = app.status === AppStatus.DEPLOYED $: isPublished = app && app?.status === AppStatus.DEPLOYED
$: appEditorId = !app?.updatedBy ? $auth.user._id : app?.updatedBy
$: appEditorText = appEditor?.firstName || appEditor?.email
$: filteredUsers = !appEditorId
? []
: $users.filter(user => user._id === appEditorId)
$: appEditor = filteredUsers.length ? filteredUsers[0] : null
const getInitials = user => {
let initials = ""
initials += user.firstName ? user.firstName[0] : ""
initials += user.lastName ? user.lastName[0] : ""
return initials == "" ? user.email[0] : initials
}
</script> </script>
<div class="overview-tab"> <div class="overview-tab">
@ -46,14 +72,21 @@
</div> </div>
</DashCard> </DashCard>
<DashCard title={"Last Edited"}> <DashCard title={"Last Edited"}>
{app.updatedAt}
<div class="last-edited-content"> <div class="last-edited-content">
<!-- Where is this information sourced? auditLog > Placeholder, metadata --> {#await userPromise}
<div class="updated-by"> <Avatar size="M" initials={"-"} />
<!-- Add a link to the user? new window? --> {:then _}
<Avatar size="M" initials={app.updatedBy.initials} /> <div class="updated-by">
<div>{app.updatedBy.firstName}</div> {#if appEditor}
</div> <Avatar size="M" initials={getInitials(appEditor)} />
<div>
{appEditor._id === $auth.user._id ? "You" : appEditorText}
</div>
{/if}
</div>
{:catch error}
<p>Could not fetch user: {error.message}</p>
{/await}
<p class="last-edit-text"> <p class="last-edit-text">
{#if app} {#if app}
{processStringSync( {processStringSync(
@ -70,7 +103,7 @@
title={"App Version"} title={"App Version"}
showIcon={true} showIcon={true}
action={() => { action={() => {
navigateTab("App Version") navigateTab("Settings")
}} }}
> >
<div class="version-content"> <div class="version-content">
@ -81,7 +114,7 @@
<Link <Link
on:click={() => { on:click={() => {
if (typeof navigateTab === "function") { if (typeof navigateTab === "function") {
navigateTab("App Version") navigateTab("Settings")
} }
}} }}
> >
@ -120,7 +153,12 @@
</div> </div>
</div> </div>
</DashCard> </DashCard>
<DashCard title={"Backups"}> <DashCard
title={"Backups"}
action={() => {
navigateTab("Backups")
}}
>
<div class="backups-content">test</div> <div class="backups-content">test</div>
</DashCard> </DashCard>
</div> </div>

View File

@ -1,37 +0,0 @@
<script>
import { Layout, Divider, Heading, Body, Page, Button } from "@budibase/bbui"
const selfHostPath =
"https://docs.budibase.com/docs/hosting-methods#self-host-budibase"
</script>
<div class="self-host-tab">
<Page wide={false}>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading>Self-host Budibase</Heading>
<Divider />
<Body>
<p>
Self-host Budibase for free to get unlimited apps and more - and it
only takes a few minutes!
</p>
<div class="page-action">
<Button
cta
on:click={() => {
window.open(selfHostPath, "_blank")
}}>Self-host Budibase</Button
>
</div>
</Body>
</Layout>
</Layout>
</Page>
</div>
<style>
.page-action {
padding-top: var(--spacing-xl);
}
</style>

View File

@ -0,0 +1,131 @@
<script>
import {
Layout,
Divider,
Heading,
Body,
Page,
Button,
Modal,
} from "@budibase/bbui"
import { store } from "builderStore"
import { auth } from "stores/portal"
import clientPackage from "@budibase/client/package.json"
import VersionModal from "components/deploy/VersionModal.svelte"
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
export let app
let versionModal
let updatingModal
let selfHostPath =
"https://docs.budibase.com/docs/hosting-methods#self-host-budibase"
$: updateAvailable = clientPackage.version !== $store.version
$: appUrl = `${window.origin}/app${app?.url}`
$: lockedBy = app?.lockedBy
$: lockedByYou = $auth.user.email === lockedBy?.email
</script>
<div class="version-tab">
<Page wide={false}>
<Layout gap="XL" noPadding>
<Layout gap="XS" noPadding>
<Heading size="S">Name and Url</Heading>
<Divider />
<Body>
<div class="app-details">
<div class="app-name">
<div class="name-title detail-title">Name</div>
<div class="name">{app?.name}</div>
</div>
<div class="app-url">
<div class="url-title detail-title">Url Path</div>
<div class="url">{appUrl}</div>
</div>
</div>
<div class="page-action">
<Button
cta
secondary
on:click={() => {
updatingModal.show()
}}
disabled={lockedBy && !lockedByYou}
>
Edit
</Button>
</div>
</Body>
</Layout>
<Layout gap="XS" noPadding>
<Heading size="S">App Version</Heading>
<Divider />
<Body>
{#if updateAvailable}
<p>
The app is currently using version <strong>{app?.version}</strong>
but version <strong>{clientPackage.version}</strong> is available.
</p>
{:else}
<p>
The app is currently using version <strong>{app?.version}</strong
>. You're running the latest!
</p>
{/if}
<p>
Updates can contain new features, performance improvements and bug
fixes.
</p>
<div class="page-action">
<Button
cta
on:click={versionModal.show()}
disabled={!updateAvailable}>Update App</Button
>
</div>
</Body>
</Layout>
<Layout gap="XS" noPadding>
<Heading size="S">Self-host Budibase</Heading>
<Divider />
<Body>
<p>
Self-host Budibase for free to get unlimited apps and more - and it
only takes a few minutes!
</p>
<div class="page-action">
<Button
cta
on:click={() => {
window.open(selfHostPath, "_blank")
}}>Self-host Budibase</Button
>
</div>
</Body>
</Layout>
</Layout>
<VersionModal bind:this={versionModal} hideIcon={true} />
<Modal bind:this={updatingModal} padding={false} width="600px">
<UpdateAppModal {app} />
</Modal>
</Page>
</div>
<style>
.page-action {
padding-top: var(--spacing-xl);
}
.app-details {
display: flex;
flex-direction: column;
gap: var(--spacing-m);
}
.detail-title {
color: var(--spectrum-global-color-gray-600);
font-size: var(
--spectrum-alias-font-size-default,
var(--spectrum-global-dimension-font-size-100)
);
}
</style>

View File

@ -1,48 +0,0 @@
<script>
import { Layout, Divider, Heading, Body, Page, Button } from "@budibase/bbui"
import { store } from "builderStore"
import clientPackage from "@budibase/client/package.json"
export let app
//Review locking behaviour.
//The app just needs to be unlocked/lockedByYou to proceed?
$: updateAvailable = clientPackage.version !== $store.version
</script>
<div class="version-tab">
<Page wide={false}>
<Layout noPadding>
<Layout gap="XS" noPadding>
<Heading>App Version</Heading>
<Divider />
<Body>
{#if updateAvailable}
<p>
The app is currently using version <strong>{app?.version}</strong>
but version <strong>{clientPackage.version}</strong> is available.
</p>
{:else}
<p>
The app is currently using version <strong>{app?.version}</strong
>. You're running the latest!
</p>
{/if}
<p>
Updates can contain new features, performance improvements and bug
fixes.
</p>
<div class="page-action">
<Button cta on:click={() => {}}>Update App</Button>
</div>
</Body>
</Layout>
</Layout>
</Page>
</div>
<style>
.page-action {
padding-top: var(--spacing-xl);
}
</style>

View File

@ -52,20 +52,8 @@ async function updateAppUpdatedAt(ctx) {
const metadata = await db.get(DocumentTypes.APP_METADATA) const metadata = await db.get(DocumentTypes.APP_METADATA)
metadata.updatedAt = new Date().toISOString() metadata.updatedAt = new Date().toISOString()
const getInitials = user => { metadata.updatedBy = getGlobalIDFromUserMetadataID(ctx.user.userId)
let initials = ""
initials += user.firstName ? user.firstName[0] : ""
initials += user.lastName ? user.lastName[0] : ""
return initials == "" ? undefined : initials
}
metadata.updatedBy = {
email: ctx.user.email,
firstName: ctx.user.firstName,
lastName: ctx.user.lastName,
initials: getInitials(ctx.user),
_id: getGlobalIDFromUserMetadataID(ctx.user.userId),
}
const response = await db.put(metadata) const response = await db.put(metadata)
metadata._rev = response.rev metadata._rev = response.rev
await appCache.invalidateAppMetadata(appId, metadata) await appCache.invalidateAppMetadata(appId, metadata)