-
+
+ {{ formatDateTime(backup.created_at) }}
+
+
{{
@@ -228,95 +206,42 @@ const messages = defineMessages({
-
-
-
-
-
- {{ formatDateTime(backup.created_at) }}
-
-
-
-
+
+ {{ formatDateTime(backup.created_at) }}
+
+
+
-
-
- emit('retry')">
-
- {{ formatMessage(commonMessages.retryButton) }}
-
-
-
- emit('delete', true)">
-
- {{ formatMessage(commonMessages.deleteLabel) }}
-
-
-
-
-
- emit('delete')">
- {{ formatMessage(commonMessages.cancelButton) }}
-
-
-
-
-
-
- {{ formatMessage(messages.rename) }}
-
-
-
-
-
-
- emit('restore')"
- >
-
- {{ formatMessage(messages.restore) }}
-
-
-
-
-
-
- {{ formatMessage(commonMessages.downloadButton) }}
-
-
- {{ formatMessage(messages.rename) }}
-
-
- {{ formatMessage(commonMessages.deleteLabel) }}
-
-
-
-
+
+ emit('restore')"
+ >
+
+ {{ formatMessage(messages.restore) }}
+
+
+
+
+
+
+ {{ formatMessage(commonMessages.downloadButton) }}
+
+
+ {{ formatMessage(messages.rename) }}
+
+
+ {{ formatMessage(commonMessages.deleteLabel) }}
+
+
+
{{
diff --git a/packages/ui/src/components/servers/backups/BackupProgressAdmonition.vue b/packages/ui/src/components/servers/backups/BackupProgressAdmonition.vue
new file mode 100644
index 000000000..d1110a901
--- /dev/null
+++ b/packages/ui/src/components/servers/backups/BackupProgressAdmonition.vue
@@ -0,0 +1,221 @@
+
+
+
+
+
+
+
+
+
+
{{ title }}
+
+
+ {{ relativeTime(createdAt) }}
+
+
+
{{ description }}
+
+
+
+
+
+ {{ formatMessage(commonMessages.cancelButton) }}
+
+
+
+
+
+ {{ formatMessage(commonMessages.retryButton) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/servers/backups/BackupProgressAdmonitions.vue b/packages/ui/src/components/servers/backups/BackupProgressAdmonitions.vue
new file mode 100644
index 000000000..b6ae21906
--- /dev/null
+++ b/packages/ui/src/components/servers/backups/BackupProgressAdmonitions.vue
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui/src/components/servers/backups/BackupRestoreModal.vue b/packages/ui/src/components/servers/backups/BackupRestoreModal.vue
index f9b5a5c57..bdd95ec2d 100644
--- a/packages/ui/src/components/servers/backups/BackupRestoreModal.vue
+++ b/packages/ui/src/components/servers/backups/BackupRestoreModal.vue
@@ -1,17 +1,17 @@
-
+
Stop the server before restoring a backup.
-
-
- This will overwrite all files in the server and replace them with the files from the backup.
+
+ Restoring your server will replace the current world and server files. Any changes made
+ since that backup will be permanently lost.
Backup
-
+
diff --git a/packages/ui/src/components/servers/backups/index.ts b/packages/ui/src/components/servers/backups/index.ts
index f63786bed..abcd1d34a 100644
--- a/packages/ui/src/components/servers/backups/index.ts
+++ b/packages/ui/src/components/servers/backups/index.ts
@@ -1,6 +1,8 @@
export { default as BackupCreateModal } from './BackupCreateModal.vue'
export { default as BackupDeleteModal } from './BackupDeleteModal.vue'
export { default as BackupItem } from './BackupItem.vue'
+export { default as BackupProgressAdmonition } from './BackupProgressAdmonition.vue'
+export { default as BackupProgressAdmonitions } from './BackupProgressAdmonitions.vue'
export { default as BackupRenameModal } from './BackupRenameModal.vue'
export { default as BackupRestoreModal } from './BackupRestoreModal.vue'
export { default as BackupWarning } from './BackupWarning.vue'
diff --git a/packages/ui/src/layouts/shared/content-tab/layout.vue b/packages/ui/src/layouts/shared/content-tab/layout.vue
index 03bb92cf8..af488a742 100644
--- a/packages/ui/src/layouts/shared/content-tab/layout.vue
+++ b/packages/ui/src/layouts/shared/content-tab/layout.vue
@@ -144,6 +144,10 @@ const messages = defineMessages({
id: 'content.page-layout.busy-description',
defaultMessage: 'Please wait for the operation to complete before editing content.',
},
+ pleaseWait: {
+ id: 'content.page-layout.please-wait',
+ defaultMessage: 'Please wait',
+ },
})
const ctx = injectContentManager()
@@ -493,7 +497,11 @@ const confirmUnlinkModal = ref
>()
:categories="ctx.modpack.value.categories"
:has-update="ctx.modpack.value.hasUpdate"
:disabled="ctx.modpack.value.disabled || ctx.isBusy.value"
- :disabled-text="ctx.modpack.value.disabledText"
+ :disabled-text="
+ ctx.modpack.value.disabledText ??
+ ctx.busyMessage?.value ??
+ (ctx.isBusy.value ? formatMessage(messages.pleaseWait) : undefined)
+ "
:show-content-hint="
!!(ctx.showContentHint?.value && ctx.modpack.value && ctx.items.value.length === 0)
"
diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/content.vue b/packages/ui/src/layouts/wrapped/hosting/manage/content.vue
index 48efa41a7..db5a530b6 100644
--- a/packages/ui/src/layouts/wrapped/hosting/manage/content.vue
+++ b/packages/ui/src/layouts/wrapped/hosting/manage/content.vue
@@ -789,14 +789,21 @@ provideContentManager({
isBusy: computed(() => busyReasons.value.length > 0),
busyMessage: computed(() => {
const bannerCoversInstalling = server.value?.status === 'installing' || isSyncingContent.value
- const nonBannerReasons = bannerCoversInstalling
- ? busyReasons.value.filter(
- (r) =>
- r.reason.id !== 'servers.busy.installing' &&
- r.reason.id !== 'servers.busy.syncing-content',
- )
- : busyReasons.value
- return nonBannerReasons.length > 0 ? formatMessage(nonBannerReasons[0].reason) : null
+ const filteredReasons = busyReasons.value.filter((r) => {
+ if (
+ bannerCoversInstalling &&
+ (r.reason.id === 'servers.busy.installing' ||
+ r.reason.id === 'servers.busy.syncing-content')
+ )
+ return false
+ if (
+ r.reason.id === 'servers.busy.backup-creating' ||
+ r.reason.id === 'servers.busy.backup-restoring'
+ )
+ return false
+ return true
+ })
+ return filteredReasons.length > 0 ? formatMessage(filteredReasons[0].reason) : null
}),
getItemId: (item) => item.file_name,
contentTypeLabel: type,
diff --git a/packages/ui/src/layouts/wrapped/hosting/manage/files.vue b/packages/ui/src/layouts/wrapped/hosting/manage/files.vue
index 8c3c12e84..70ade05d7 100644
--- a/packages/ui/src/layouts/wrapped/hosting/manage/files.vue
+++ b/packages/ui/src/layouts/wrapped/hosting/manage/files.vue
@@ -21,8 +21,8 @@
-
- {{ busyTooltip }}
+
+ {{ formatMessage(nonBackupBusyReasons[0].reason) }}
File operations are disabled while the operation is in progress.
@@ -327,6 +327,13 @@ const serverBusy = computed(() => busyReasons.value.length > 0)
const busyTooltip = computed(() =>
busyReasons.value.length > 0 ? formatMessage(busyReasons.value[0].reason) : undefined,
)
+const nonBackupBusyReasons = computed(() =>
+ busyReasons.value.filter(
+ (r) =>
+ r.reason.id !== 'servers.busy.backup-creating' &&
+ r.reason.id !== 'servers.busy.backup-restoring',
+ ),
+)
const queryClient = useQueryClient()
interface BaseOperation {
diff --git a/packages/ui/src/locales/en-US/index.json b/packages/ui/src/locales/en-US/index.json
index e02334f52..58f9dded0 100644
--- a/packages/ui/src/locales/en-US/index.json
+++ b/packages/ui/src/locales/en-US/index.json
@@ -335,6 +335,9 @@
"content.page-layout.no-content-found": {
"defaultMessage": "No content found."
},
+ "content.page-layout.please-wait": {
+ "defaultMessage": "Please wait"
+ },
"content.page-layout.search-placeholder": {
"defaultMessage": "Search {count} {contentType}..."
},
@@ -1814,15 +1817,57 @@
"search.filter_type.shader_loader": {
"defaultMessage": "Loader"
},
+ "servers.backups.admonition.backup-failed.description": {
+ "defaultMessage": "Something went wrong while creating {backupName}. Please try again or contact support if the issue continues."
+ },
+ "servers.backups.admonition.backup-failed.title": {
+ "defaultMessage": "Backup failed"
+ },
+ "servers.backups.admonition.backup-queued.description": {
+ "defaultMessage": "{backupName} is queued and will start shortly."
+ },
+ "servers.backups.admonition.backup-queued.title": {
+ "defaultMessage": "Backup queued"
+ },
+ "servers.backups.admonition.creating-backup.description": {
+ "defaultMessage": "Saving world data and server configuration for {backupName}. This can take a few minutes."
+ },
+ "servers.backups.admonition.creating-backup.title": {
+ "defaultMessage": "Creating backup"
+ },
+ "servers.backups.admonition.fallback-name": {
+ "defaultMessage": "your backup"
+ },
+ "servers.backups.admonition.restore-failed.description": {
+ "defaultMessage": "Something went wrong while restoring from {backupName}. Please try again or contact support if the issue continues."
+ },
+ "servers.backups.admonition.restore-failed.title": {
+ "defaultMessage": "Restoring from backup failed"
+ },
+ "servers.backups.admonition.restore-queued.description": {
+ "defaultMessage": "Restoring from {backupName} is queued and will start shortly."
+ },
+ "servers.backups.admonition.restore-queued.title": {
+ "defaultMessage": "Restoring from backup queued"
+ },
+ "servers.backups.admonition.restore-successful.description": {
+ "defaultMessage": "Your server has been restored to {backupName} and is ready to start."
+ },
+ "servers.backups.admonition.restore-successful.title": {
+ "defaultMessage": "Restoring from backup successful"
+ },
+ "servers.backups.admonition.restoring-backup.description": {
+ "defaultMessage": "Restoring your server from {backupName}. This may take a couple of minutes."
+ },
+ "servers.backups.admonition.restoring-backup.title": {
+ "defaultMessage": "Restoring from backup"
+ },
"servers.backups.item.auto": {
"defaultMessage": "Auto"
},
"servers.backups.item.backup-schedule": {
"defaultMessage": "Backup schedule"
},
- "servers.backups.item.creating-backup": {
- "defaultMessage": "Creating backup..."
- },
"servers.backups.item.failed-to-create-backup": {
"defaultMessage": "Failed to create backup"
},
@@ -1832,21 +1877,12 @@
"servers.backups.item.manual-backup": {
"defaultMessage": "Manual backup"
},
- "servers.backups.item.queued-for-backup": {
- "defaultMessage": "Backup queued"
- },
- "servers.backups.item.queued-for-restore": {
- "defaultMessage": "Restore queued"
- },
"servers.backups.item.rename": {
"defaultMessage": "Rename"
},
"servers.backups.item.restore": {
"defaultMessage": "Restore"
},
- "servers.backups.item.restoring-backup": {
- "defaultMessage": "Restoring from backup..."
- },
"servers.notice.dismiss": {
"defaultMessage": "Dismiss"
},
diff --git a/packages/ui/src/stories/base/Admonition.stories.ts b/packages/ui/src/stories/base/Admonition.stories.ts
index c3d5f7311..7ec3c77ff 100644
--- a/packages/ui/src/stories/base/Admonition.stories.ts
+++ b/packages/ui/src/stories/base/Admonition.stories.ts
@@ -24,6 +24,7 @@ export const AllTypes: Story = {
+
`,
}),
@@ -36,3 +37,11 @@ export const WithHeader: Story = {
body: 'Please read this carefully before proceeding.',
},
}
+
+export const Success: Story = {
+ args: {
+ type: 'success',
+ header: 'Operation Complete',
+ body: 'Everything went smoothly.',
+ },
+}
diff --git a/packages/ui/src/stories/servers/BackupProgressAdmonition.stories.ts b/packages/ui/src/stories/servers/BackupProgressAdmonition.stories.ts
new file mode 100644
index 000000000..83a3a9651
--- /dev/null
+++ b/packages/ui/src/stories/servers/BackupProgressAdmonition.stories.ts
@@ -0,0 +1,114 @@
+import type { Meta, StoryObj } from '@storybook/vue3-vite'
+
+import BackupProgressAdmonition from '../../components/servers/backups/BackupProgressAdmonition.vue'
+
+const meta = {
+ title: 'Servers/BackupProgressAdmonition',
+ component: BackupProgressAdmonition,
+ parameters: {
+ layout: 'padded',
+ },
+} satisfies Meta
+
+export default meta
+type Story = StoryObj
+
+const justNow = new Date().toISOString()
+const eightMinsAgo = new Date(Date.now() - 8 * 60 * 1000).toISOString()
+const fiveHoursAgo = new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString()
+
+export const AllStates: Story = {
+ render: () => ({
+ components: { BackupProgressAdmonition },
+ setup() {
+ const now = new Date().toISOString()
+ const mins8 = new Date(Date.now() - 8 * 60 * 1000).toISOString()
+ const hours5 = new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString()
+ return { now, mins8, hours5 }
+ },
+ template: /*html*/ `
+
+
Backup Creation
+
+
+
+
+ Backup Restoration
+
+
+
+
+
+ `,
+ }),
+}
+
+export const BackupQueued: Story = {
+ args: {
+ type: 'create',
+ state: 'ongoing',
+ progress: 0,
+ backupName: 'World Backup 1',
+ createdAt: justNow,
+ },
+}
+
+export const CreatingBackup: Story = {
+ args: {
+ type: 'create',
+ state: 'ongoing',
+ progress: 0.33,
+ backupName: 'World Backup 1',
+ createdAt: eightMinsAgo,
+ },
+}
+
+export const BackupFailed: Story = {
+ args: {
+ type: 'create',
+ state: 'failed',
+ progress: 0,
+ backupName: 'World Backup 1',
+ createdAt: fiveHoursAgo,
+ },
+}
+
+export const RestoreQueued: Story = {
+ args: {
+ type: 'restore',
+ state: 'ongoing',
+ progress: 0,
+ backupName: 'World Backup 1',
+ createdAt: justNow,
+ },
+}
+
+export const RestoringBackup: Story = {
+ args: {
+ type: 'restore',
+ state: 'ongoing',
+ progress: 0.33,
+ backupName: 'World Backup 1',
+ createdAt: eightMinsAgo,
+ },
+}
+
+export const RestoreSuccessful: Story = {
+ args: {
+ type: 'restore',
+ state: 'done',
+ progress: 1,
+ backupName: 'World Backup 1',
+ createdAt: fiveHoursAgo,
+ },
+}
+
+export const RestoreFailed: Story = {
+ args: {
+ type: 'restore',
+ state: 'failed',
+ progress: 0,
+ backupName: 'World Backup 1',
+ createdAt: fiveHoursAgo,
+ },
+}
diff --git a/packages/ui/src/utils/auto-icons.ts b/packages/ui/src/utils/auto-icons.ts
index b0bf3e8ad..6750446a6 100644
--- a/packages/ui/src/utils/auto-icons.ts
+++ b/packages/ui/src/utils/auto-icons.ts
@@ -4,6 +4,7 @@ import {
BracesIcon,
CalendarIcon,
CardIcon,
+ CheckCircleIcon,
CurrencyIcon,
DiscordIcon,
FileArchiveIcon,
@@ -66,6 +67,7 @@ export const SEVERITY_ICONS: Record = {
warning: IssuesIcon,
error: XCircleIcon,
critical: XCircleIcon,
+ success: CheckCircleIcon,
}
export const PROJECT_STATUS_ICONS: Record = {