Backups UI Changes (#9726)
* Backups UI Changes * PR Feedback --------- Co-authored-by: Rory Powell <rory.codes@gmail.com>
This commit is contained in:
parent
faaf01cd53
commit
e7f8a8a801
|
@ -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
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
import { truncate } from "lodash"
|
|
||||||
|
|
||||||
export let value
|
|
||||||
$: truncatedValue = truncate(value, { length: 12 })
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{truncatedValue}
|
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }) {
|
||||||
|
|
|
@ -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 },
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue