Backups UI Changes (#9726)

* Backups UI Changes

* PR Feedback

---------

Co-authored-by: Rory Powell <rory.codes@gmail.com>
This commit is contained in:
Gerard Burns 2023-02-22 10:03:11 +00:00 committed by GitHub
parent faaf01cd53
commit e7f8a8a801
10 changed files with 67 additions and 116 deletions

View File

@ -0,0 +1,13 @@
const getUserInitials = user => {
if (user.firstName && user.lastName) {
return user.firstName[0] + user.lastName[0]
} else if (user.firstName) {
return user.firstName[0]
} else if (user.email) {
return user.email[0]
}
return "U"
}
export default getUserInitials

View File

@ -3,31 +3,26 @@
ActionMenu, ActionMenu,
MenuItem, MenuItem,
Icon, Icon,
Input,
Heading, Heading,
Body, Body,
Modal, Modal,
} from "@budibase/bbui" } from "@budibase/bbui"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import CreateRestoreModal from "./CreateRestoreModal.svelte" import CreateRestoreModal from "./CreateRestoreModal.svelte"
import { createEventDispatcher, onMount } from "svelte" import { createEventDispatcher } from "svelte"
export let row export let row
let deleteDialog let deleteDialog
let restoreDialog let restoreDialog
let updateDialog
let name
let restoreBackupModal let restoreBackupModal
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onClickRestore = name => { const onClickRestore = () => {
dispatch("buttonclick", { dispatch("buttonclick", {
type: "backupRestore", type: "backupRestore",
name,
backupId: row._id, backupId: row._id,
restoreBackupName: name,
}) })
} }
@ -38,21 +33,9 @@
}) })
} }
const onClickUpdate = () => {
dispatch("buttonclick", {
type: "backupUpdate",
backupId: row._id,
name,
})
}
async function downloadExport() { async function downloadExport() {
window.open(`/api/apps/${row.appId}/backups/${row._id}/file`, "_blank") window.open(`/api/apps/${row.appId}/backups/${row._id}/file`, "_blank")
} }
onMount(() => {
name = row.name
})
</script> </script>
<div class="cell"> <div class="cell">
@ -66,12 +49,11 @@
<MenuItem on:click={deleteDialog.show} icon="Delete">Delete</MenuItem> <MenuItem on:click={deleteDialog.show} icon="Delete">Delete</MenuItem>
<MenuItem on:click={downloadExport} icon="Download">Download</MenuItem> <MenuItem on:click={downloadExport} icon="Download">Download</MenuItem>
{/if} {/if}
<MenuItem on:click={updateDialog.show} icon="Edit">Rename</MenuItem>
</ActionMenu> </ActionMenu>
</div> </div>
<Modal bind:this={restoreBackupModal}> <Modal bind:this={restoreBackupModal}>
<CreateRestoreModal confirm={name => onClickRestore(name)} /> <CreateRestoreModal confirm={onClickRestore} />
</Modal> </Modal>
<ConfirmDialog <ConfirmDialog
@ -80,9 +62,7 @@
onOk={onClickDelete} onOk={onClickDelete}
title="Confirm Deletion" title="Confirm Deletion"
> >
Are you sure you wish to delete the backup Are you sure you wish to delete this backup? This action cannot be undone.
<i>{row.name}?</i>
This action cannot be undone.
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog <ConfirmDialog
@ -92,21 +72,10 @@
title="Confirm restore" title="Confirm restore"
warning={false} warning={false}
> >
<Heading size="S">{row.name || "Backup"}</Heading> <Heading size="S">Backup</Heading>
<Body size="S">{new Date(row.timestamp).toLocaleString()}</Body> <Body size="S">{new Date(row.timestamp).toLocaleString()}</Body>
</ConfirmDialog> </ConfirmDialog>
<ConfirmDialog
bind:this={updateDialog}
disabled={!name}
okText="Confirm"
onOk={onClickUpdate}
title="Update Backup"
warning={false}
>
<Input onlabel="Backup name" bind:value={name} />
</ConfirmDialog>
<style> <style>
.cell { .cell {
display: flex; display: flex;

View File

@ -1,22 +0,0 @@
<script>
import { ModalContent, Input } from "@budibase/bbui"
import { auth } from "stores/portal"
export let createManualBackup
let templateName = $auth.user.firstName
? `${$auth.user.firstName}'s Backup`
: "New Backup"
let name = templateName
</script>
<ModalContent
onConfirm={() => createManualBackup(name)}
title="Create new backup"
diabled={!name}
confirmText="Create"
><Input label="Backup name" bind:value={name} /></ModalContent
>
<style>
</style>

View File

@ -1,8 +0,0 @@
<script>
import { truncate } from "lodash"
export let value
$: truncatedValue = truncate(value, { length: 12 })
</script>
{truncatedValue}

View File

@ -0,0 +1,10 @@
<script>
import dayjs from "dayjs"
import relativeTime from "dayjs/plugin/relativeTime"
dayjs.extend(relativeTime)
export let value
</script>
<span title={value}>{dayjs(value).fromNow()}</span>

View File

@ -1,17 +1,14 @@
<script> <script>
import getUserInitials from "helpers/userInitials.js"
import { Avatar } from "@budibase/bbui"
export let value export let value
let firstName = value?.firstName $: initials = getUserInitials(value)
let lastName = value?.lastName || ""
$: username =
firstName && lastName ? `${firstName} ${lastName}` : value?.email
</script> </script>
<div class="cell"> <div title={value.email} class="cell">
{#if value != null} <Avatar size="M" {initials} />
<div>{username}</div>
{/if}
</div> </div>
<style> <style>

View File

@ -4,7 +4,6 @@
DatePicker, DatePicker,
Divider, Divider,
Layout, Layout,
Modal,
notifications, notifications,
Pagination, Pagination,
Select, Select,
@ -16,25 +15,22 @@
} from "@budibase/bbui" } from "@budibase/bbui"
import { backups, licensing, auth, admin, overview } from "stores/portal" import { backups, licensing, auth, admin, overview } from "stores/portal"
import { createPaginationStore } from "helpers/pagination" import { createPaginationStore } from "helpers/pagination"
import DateRenderer from "components/common/renderers/DateTimeRenderer.svelte" import TimeAgoRenderer from "./_components/TimeAgoRenderer.svelte"
import AppSizeRenderer from "./_components/AppSizeRenderer.svelte" import AppSizeRenderer from "./_components/AppSizeRenderer.svelte"
import CreateBackupModal from "./_components/CreateBackupModal.svelte"
import ActionsRenderer from "./_components/ActionsRenderer.svelte" import ActionsRenderer from "./_components/ActionsRenderer.svelte"
import UserRenderer from "./_components/UserRenderer.svelte" import UserRenderer from "./_components/UserRenderer.svelte"
import StatusRenderer from "./_components/StatusRenderer.svelte" import StatusRenderer from "./_components/StatusRenderer.svelte"
import TypeRenderer from "./_components/TypeRenderer.svelte" import TypeRenderer from "./_components/TypeRenderer.svelte"
import NameRenderer from "./_components/NameRenderer.svelte"
import BackupsDefault from "assets/backups-default.png" import BackupsDefault from "assets/backups-default.png"
import { BackupTrigger, BackupType } from "constants/backend/backups" import { BackupTrigger, BackupType } from "constants/backend/backups"
import { onMount } from "svelte" import { onMount } from "svelte"
let loading = true
let backupData = null let backupData = null
let modal
let pageInfo = createPaginationStore() let pageInfo = createPaginationStore()
let filterOpt = null let filterOpt = null
let startDate = null let startDate = null
let endDate = null let endDate = null
let loaded = false
let filters = [ let filters = [
{ {
label: "Manual backup", label: "Manual backup",
@ -44,10 +40,6 @@
label: "Published backup", label: "Published backup",
value: { type: BackupType.BACKUP, trigger: BackupTrigger.PUBLISH }, value: { type: BackupType.BACKUP, trigger: BackupTrigger.PUBLISH },
}, },
{
label: "Scheduled backup",
value: { type: BackupType.BACKUP, trigger: BackupTrigger.SCHEDULED },
},
{ {
label: "Pre-restore backup", label: "Pre-restore backup",
value: { type: BackupType.BACKUP, trigger: BackupTrigger.RESTORING }, value: { type: BackupType.BACKUP, trigger: BackupTrigger.RESTORING },
@ -71,10 +63,6 @@
displayName: "Date", displayName: "Date",
width: "auto", width: "auto",
}, },
name: {
displayName: "Name",
width: "auto",
},
appSize: { appSize: {
displayName: "App size", displayName: "App size",
width: "auto", width: "auto",
@ -96,11 +84,10 @@
const customRenderers = [ const customRenderers = [
{ column: "appSize", component: AppSizeRenderer }, { column: "appSize", component: AppSizeRenderer },
{ column: "actions", component: ActionsRenderer }, { column: "actions", component: ActionsRenderer },
{ column: "createdAt", component: DateRenderer }, { column: "createdAt", component: TimeAgoRenderer },
{ column: "createdBy", component: UserRenderer }, { column: "createdBy", component: UserRenderer },
{ column: "status", component: StatusRenderer }, { column: "status", component: StatusRenderer },
{ column: "type", component: TypeRenderer }, { column: "type", component: TypeRenderer },
{ column: "name", component: NameRenderer },
] ]
function flattenBackups(backups) { function flattenBackups(backups) {
@ -126,11 +113,11 @@
backupData = flattenBackups(response.data) backupData = flattenBackups(response.data)
} }
async function createManualBackup(name) { async function createManualBackup() {
try { try {
loading = true
let response = await backups.createManualBackup({ let response = await backups.createManualBackup({
appId: app.instance._id, appId: app.instance._id,
name,
}) })
await fetchBackups(filterOpt, page) await fetchBackups(filterOpt, page)
notifications.success(response.message) notifications.success(response.message)
@ -139,6 +126,20 @@
} }
} }
const poll = backupData => {
if (backupData === null) {
return
}
if (backupData.some(datum => datum.status === "started")) {
setTimeout(() => fetchBackups(filterOpt, page), 2000)
} else {
loading = false
}
}
$: poll(backupData)
async function handleButtonClick({ detail }) { async function handleButtonClick({ detail }) {
if (detail.type === "backupDelete") { if (detail.type === "backupDelete") {
await backups.deleteBackup({ await backups.deleteBackup({
@ -165,7 +166,7 @@
onMount(async () => { onMount(async () => {
await fetchBackups(filterOpt, page, startDate, endDate) await fetchBackups(filterOpt, page, startDate, endDate)
loaded = true loading = false
}) })
</script> </script>
@ -206,7 +207,7 @@
View plans View plans
</Button> </Button>
</div> </div>
{:else if !backupData?.length && loaded && !filterOpt && !startDate} {:else if !backupData?.length && !loading && !filterOpt && !startDate}
<div class="center"> <div class="center">
<Layout noPadding gap="S" justifyItems="center"> <Layout noPadding gap="S" justifyItems="center">
<img height="130px" src={BackupsDefault} alt="BackupsDefault" /> <img height="130px" src={BackupsDefault} alt="BackupsDefault" />
@ -215,11 +216,13 @@
<Body>You can manually back up your app any time</Body> <Body>You can manually back up your app any time</Body>
</Layout> </Layout>
<div> <div>
<Button on:click={modal.show} cta>Create backup</Button> <Button cta disabled={loading} on:click={createManualBackup}>
Create backup
</Button>
</div> </div>
</Layout> </Layout>
</div> </div>
{:else if loaded} {:else}
<Layout noPadding gap="M" alignContent="start"> <Layout noPadding gap="M" alignContent="start">
<div class="controls"> <div class="controls">
<div class="search"> <div class="search">
@ -245,7 +248,9 @@
/> />
</div> </div>
<div> <div>
<Button cta on:click={modal.show}>Create new backup</Button> <Button cta disabled={loading} on:click={createManualBackup}
>Create new backup</Button
>
</div> </div>
</div> </div>
<div class="table"> <div class="table">
@ -275,10 +280,6 @@
{/if} {/if}
</Layout> </Layout>
<Modal bind:this={modal}>
<CreateBackupModal {createManualBackup} />
</Modal>
<style> <style>
.title { .title {
display: flex; display: flex;

View File

@ -2,6 +2,7 @@ import { derived, writable, get } from "svelte/store"
import { API } from "api" import { API } from "api"
import { admin } from "stores/portal" import { admin } from "stores/portal"
import analytics from "analytics" import analytics from "analytics"
import getUserInitials from "helpers/userInitials.js"
export function createAuthStore() { export function createAuthStore() {
const auth = writable({ const auth = writable({
@ -18,16 +19,7 @@ export function createAuthStore() {
let isBuilder = false let isBuilder = false
if ($store.user) { if ($store.user) {
const user = $store.user const user = $store.user
if (user.firstName) { initials = getUserInitials(user)
initials = user.firstName[0]
if (user.lastName) {
initials += user.lastName[0]
}
} else if (user.email) {
initials = user.email[0]
} else {
initials = "Unknown"
}
isAdmin = !!user.admin?.global isAdmin = !!user.admin?.global
isBuilder = !!user.builder?.global isBuilder = !!user.builder?.global
} }

View File

@ -30,8 +30,8 @@ export function createBackupsStore() {
return API.deleteBackup({ appId, backupId }) return API.deleteBackup({ appId, backupId })
} }
async function createManualBackup(appId, name) { async function createManualBackup(appId) {
return API.createManualBackup(appId, name) return API.createManualBackup(appId)
} }
async function updateBackup({ appId, backupId, name }) { async function updateBackup({ appId, backupId, name }) {

View File

@ -21,10 +21,9 @@ export const buildBackupsEndpoints = API => ({
}) })
}, },
createManualBackup: async ({ appId, name }) => { createManualBackup: async ({ appId }) => {
return await API.post({ return await API.post({
url: `/api/apps/${appId}/backups`, url: `/api/apps/${appId}/backups`,
body: { name },
}) })
}, },