backup page fixes and new impls for new apis (#3437)

* wip: backup page fixes and new impls for new apis

* wip: more progress on backup fixes, almost done

* lint

* Backups cleanup

* Don't show create warning if creating

* Fix ongoing state

* Download support

* Support ready

* Disable auto backup button

* Use auth param for download of backups

* Disable install buttons when backup is in progress, add retrying

* Make prepare button have immediate feedback, don't refresh backups in all cases

* Intl:extract & rebase fixes

* Updated changelog and fix lint

---------

Co-authored-by: Prospector <prospectordev@gmail.com>
This commit is contained in:
Sticks
2025-04-17 04:26:13 -04:00
committed by GitHub
parent 817151e47c
commit f8494030aa
30 changed files with 1550 additions and 1145 deletions

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot-icon lucide-bot"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-archive-icon lucide-folder-archive"><circle cx="15" cy="19" r="2"/><path d="M20.9 19.8A2 2 0 0 0 22 18V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h5.1"/><path d="M15 11v-1"/><path d="M15 17v-2"/></svg>

After

Width:  |  Height:  |  Size: 463 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw-icon lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-cw-icon lucide-rotate-cw"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>

After

Width:  |  Height:  |  Size: 324 B

View File

@@ -40,6 +40,7 @@ import _BellRingIcon from './icons/bell-ring.svg?component'
import _BookIcon from './icons/book.svg?component'
import _BookTextIcon from './icons/book-text.svg?component'
import _BookmarkIcon from './icons/bookmark.svg?component'
import _BotIcon from './icons/bot.svg?component'
import _BoxIcon from './icons/box.svg?component'
import _BoxImportIcon from './icons/box-import.svg?component'
import _BracesIcon from './icons/braces.svg?component'
@@ -76,6 +77,7 @@ import _FileIcon from './icons/file.svg?component'
import _FileTextIcon from './icons/file-text.svg?component'
import _FilterIcon from './icons/filter.svg?component'
import _FilterXIcon from './icons/filter-x.svg?component'
import _FolderArchiveIcon from './icons/folder-archive.svg?component'
import _FolderOpenIcon from './icons/folder-open.svg?component'
import _FolderSearchIcon from './icons/folder-search.svg?component'
import _GapIcon from './icons/gap.svg?component'
@@ -137,6 +139,8 @@ import _ReplyIcon from './icons/reply.svg?component'
import _ReportIcon from './icons/report.svg?component'
import _RestoreIcon from './icons/restore.svg?component'
import _RightArrowIcon from './icons/right-arrow.svg?component'
import _RotateCounterClockwiseIcon from './icons/rotate-ccw.svg?component'
import _RotateClockwiseIcon from './icons/rotate-cw.svg?component'
import _SaveIcon from './icons/save.svg?component'
import _ScaleIcon from './icons/scale.svg?component'
import _ScanEyeIcon from './icons/scan-eye.svg?component'
@@ -238,6 +242,7 @@ export const BellRingIcon = _BellRingIcon
export const BookIcon = _BookIcon
export const BookTextIcon = _BookTextIcon
export const BookmarkIcon = _BookmarkIcon
export const BotIcon = _BotIcon
export const BoxIcon = _BoxIcon
export const BoxImportIcon = _BoxImportIcon
export const BracesIcon = _BracesIcon
@@ -274,6 +279,7 @@ export const FileIcon = _FileIcon
export const FileTextIcon = _FileTextIcon
export const FilterIcon = _FilterIcon
export const FilterXIcon = _FilterXIcon
export const FolderArchiveIcon = _FolderArchiveIcon
export const FolderOpenIcon = _FolderOpenIcon
export const FolderSearchIcon = _FolderSearchIcon
export const GapIcon = _GapIcon
@@ -335,6 +341,8 @@ export const ReplyIcon = _ReplyIcon
export const ReportIcon = _ReportIcon
export const RestoreIcon = _RestoreIcon
export const RightArrowIcon = _RightArrowIcon
export const RotateCounterClockwiseIcon = _RotateCounterClockwiseIcon
export const RotateClockwiseIcon = _RotateClockwiseIcon
export const SaveIcon = _SaveIcon
export const ScaleIcon = _ScaleIcon
export const ScanEyeIcon = _ScanEyeIcon

View File

@@ -195,7 +195,7 @@ const colorVariables = computed(() => {
> *:first-child
> *:first-child
> :is(button, a, .button-like):first-child {
@apply flex cursor-pointer flex-row items-center justify-center border-solid border-2 border-transparent bg-[--_bg] text-[--_text] h-[--_height] min-w-[--_width] rounded-[--_radius] px-[--_padding-x] py-[--_padding-y] gap-[--_gap] font-[--_font-weight];
@apply flex cursor-pointer flex-row items-center justify-center border-solid border-2 border-transparent bg-[--_bg] text-[--_text] h-[--_height] min-w-[--_width] rounded-[--_radius] px-[--_padding-x] py-[--_padding-y] gap-[--_gap] font-[--_font-weight] whitespace-nowrap;
transition:
scale 0.125s ease-in-out,
background-color 0.25s ease-in-out,
@@ -204,6 +204,7 @@ const colorVariables = computed(() => {
svg:first-child {
color: var(--_icon, var(--_text));
transition: color 0.25s ease-in-out;
flex-shrink: 0;
}
&[disabled],

View File

@@ -0,0 +1,83 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
progress: number
max?: number
color?: 'brand' | 'green' | 'red' | 'orange' | 'blue' | 'purple' | 'gray'
waiting?: boolean
}>(),
{
max: 1,
color: 'brand',
waiting: false,
},
)
const colors = {
brand: {
fg: 'bg-brand',
bg: 'bg-brand-highlight',
},
green: {
fg: 'bg-green',
bg: 'bg-bg-green',
},
red: {
fg: 'bg-red',
bg: 'bg-bg-red',
},
orange: {
fg: 'bg-orange',
bg: 'bg-bg-orange',
},
blue: {
fg: 'bg-blue',
bg: 'bg-bg-blue',
},
purple: {
fg: 'bg-purple',
bg: 'bg-bg-purple',
},
gray: {
fg: 'bg-gray',
bg: 'bg-bg-gray',
},
}
const percent = computed(() => props.progress / props.max)
</script>
<template>
<div class="flex w-[15rem] h-1 rounded-full overflow-hidden" :class="colors[props.color].bg">
<div
class="rounded-full progress-bar"
:class="[colors[props.color].fg, { 'progress-bar--waiting': waiting }]"
:style="!waiting ? { width: `${percent * 100}%` } : {}"
></div>
</div>
</template>
<style scoped lang="scss">
.progress-bar {
transition: width 0.2s ease-in-out;
}
.progress-bar--waiting {
animation: progress-bar-waiting 1s linear infinite;
position: relative;
}
@keyframes progress-bar-waiting {
0% {
left: -50%;
width: 20%;
}
50% {
width: 60%;
}
100% {
left: 100%;
width: 20%;
}
}
</style>

View File

@@ -26,6 +26,7 @@ export { default as Page } from './base/Page.vue'
export { default as Pagination } from './base/Pagination.vue'
export { default as PopoutMenu } from './base/PopoutMenu.vue'
export { default as PreviewSelectButton } from './base/PreviewSelectButton.vue'
export { default as ProgressBar } from './base/ProgressBar.vue'
export { default as ProjectCard } from './base/ProjectCard.vue'
export { default as RadialHeader } from './base/RadialHeader.vue'
export { default as RadioButtons } from './base/RadioButtons.vue'
@@ -98,3 +99,6 @@ export { default as VersionSummary } from './version/VersionSummary.vue'
// Settings
export { default as ThemeSelector } from './settings/ThemeSelector.vue'
// Servers
export { default as BackupWarning } from './servers/backups/BackupWarning.vue'

View File

@@ -5,26 +5,28 @@
<span class="font-extrabold text-contrast text-lg">{{ title }}</span>
</slot>
</template>
<div>
<div class="markdown-body max-w-[35rem]" v-html="renderString(description)" />
<label v-if="hasToType" for="confirmation" class="confirmation-label">
<div class="flex flex-col gap-4">
<div
v-if="description"
class="markdown-body max-w-[35rem]"
v-html="renderString(description)"
/>
<slot />
<label v-if="hasToType" for="confirmation">
<span>
<strong>To verify, type</strong>
<em class="confirmation-text"> {{ confirmationText }} </em>
<strong>below:</strong>
To confirm you want to proceed, type
<span class="italic font-bold">{{ confirmationText }}</span> below:
</span>
</label>
<div class="confirmation-input">
<input
v-if="hasToType"
id="confirmation"
v-model="confirmation_typed"
type="text"
placeholder="Type here..."
@input="type"
/>
</div>
<div class="flex gap-2 mt-6">
<input
v-if="hasToType"
id="confirmation"
v-model="confirmation_typed"
type="text"
placeholder="Type here..."
class="max-w-[20rem]"
/>
<div class="flex gap-2">
<ButtonStyled :color="danger ? 'red' : 'brand'">
<button :disabled="action_disabled" @click="proceed">
<component :is="proceedIcon" />
@@ -65,8 +67,8 @@ const props = defineProps({
},
description: {
type: String,
default: 'No description defined',
required: true,
default: undefined,
required: false,
},
proceedIcon: {
type: Object,
@@ -95,21 +97,20 @@ const props = defineProps({
const emit = defineEmits(['proceed'])
const modal = ref(null)
const action_disabled = ref(props.hasToType)
const confirmation_typed = ref('')
const action_disabled = computed(
() =>
props.hasToType &&
confirmation_typed.value.toLowerCase() !== props.confirmationText.toLowerCase(),
)
function proceed() {
modal.value.hide()
confirmation_typed.value = ''
emit('proceed')
}
function type() {
if (props.hasToType) {
action_disabled.value =
confirmation_typed.value.toLowerCase() !== props.confirmationText.toLowerCase()
}
}
function show() {
modal.value.show()
}

View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
import { IssuesIcon } from '@modrinth/assets'
import AutoLink from '../../base/AutoLink.vue'
defineProps<{
backupLink: string
}>()
</script>
<template>
<div
class="flex gap-3 rounded-2xl border-2 border-solid border-orange bg-bg-orange px-4 py-3 font-medium text-contrast"
>
<IssuesIcon class="mt-1 h-5 w-5 shrink-0 text-orange" />
<span class="leading-normal">
You may want to
<AutoLink
:to="backupLink"
class="font-semibold text-orange hover:underline active:brightness-125"
>create a backup</AutoLink
>
before proceeding, as this process is irreversible and may permanently alter your world or the
files on your server.
</span>
</div>
</template>

View File

@@ -11,6 +11,12 @@
"button.create-a-project": {
"defaultMessage": "Create a project"
},
"button.download": {
"defaultMessage": "Download"
},
"button.downloading": {
"defaultMessage": "Downloading"
},
"button.edit": {
"defaultMessage": "Edit"
},
@@ -422,6 +428,9 @@
"servers.notice.level.info.name": {
"defaultMessage": "Info"
},
"servers.notice.level.survey.name": {
"defaultMessage": "Survey"
},
"servers.notice.level.warn.name": {
"defaultMessage": "Warning"
},

View File

@@ -49,6 +49,14 @@ export const commonMessages = defineMessages({
id: 'label.description',
defaultMessage: 'Description',
},
downloadButton: {
id: 'button.download',
defaultMessage: 'Download',
},
downloadingButton: {
id: 'button.downloading',
defaultMessage: 'Downloading',
},
editButton: {
id: 'button.edit',
defaultMessage: 'Edit',

View File

@@ -10,6 +10,18 @@ export type VersionEntry = {
}
const VERSIONS: VersionEntry[] = [
{
date: `2025-04-17T01:30:00-07:00`,
product: 'servers',
body: `### Improvements
- Completely overhauled the Backups interface and fixed them being non-functional.
- Backups will now show progress when creating and restoring.
- Backups now have a "Prepare download" phase, which will prepare a backup file for downloading.
- You can now cancel a backup in progress and retry a failed backup.
- When a backup is in progress, you will no longer be allowed to modify the modpack or loader.
- Removed the ability to create backups on install automatically, and replaced with a notice that you may want to create a backup before installing a new modpack or loader. This is because the previous implementation of backup on install was unreliable and buggy. We are working on a better implementation for this feature and plan for it to return in the future.
- Temporarily disabled auto backups button, since they are currently not working.`,
},
{
date: `2025-04-15T16:35:00-07:00`,
product: 'servers',