From a99a0a6e3082dd99848c739b052f6d122a7f2172 Mon Sep 17 00:00:00 2001 From: Ap_Tx <3045929398@qq.com> Date: Sat, 3 Oct 2026 09:43:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(backup):=20=E5=AE=9E=E4=BE=8B=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E7=AC=AC5=E8=BD=AE=E2=80=94=E2=80=94=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=A4=87=E4=BB=BD=E8=AE=BE=E7=BD=AE/=E6=8E=92?= =?UTF-8?q?=E9=99=A4=E9=80=89=E6=8B=A9=E5=99=A8/=E4=BB=93=E5=BA=93?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E5=AE=9E=E4=BE=8B=E8=AE=BE=E7=BD=AE=E4=B8=8E=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B5=EF=BC=8C=E8=A1=A5=E9=BD=90=E4=B8=AD?= =?UTF-8?q?=E8=8B=B1=E7=B9=81=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BackupExclusionSelector.vue | 132 ++++ .../ui/instance_settings/BackupSettings.vue | 675 ++++++++++++++++++ .../ui/modal/InstanceSettingsModal.vue | 9 + .../ui/settings/BackupRepositorySettings.vue | 167 +++++ .../ui/settings/StorageBackupSettings.vue | 2 + .../app-frontend/src/locales/en-US/index.json | 165 +++++ .../app-frontend/src/locales/zh-CN/index.json | 165 +++++ .../app-frontend/src/locales/zh-TW/index.json | 165 +++++ 8 files changed, 1480 insertions(+) create mode 100644 apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue create mode 100644 apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue create mode 100644 apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue diff --git a/apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue b/apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue new file mode 100644 index 0000000..7544988 --- /dev/null +++ b/apps/app-frontend/src/components/ui/instance_settings/BackupExclusionSelector.vue @@ -0,0 +1,132 @@ + + + diff --git a/apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue new file mode 100644 index 0000000..5299c11 --- /dev/null +++ b/apps/app-frontend/src/components/ui/instance_settings/BackupSettings.vue @@ -0,0 +1,675 @@ + + + diff --git a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue index b5869c2..d7fa727 100644 --- a/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue +++ b/apps/app-frontend/src/components/ui/modal/InstanceSettingsModal.vue @@ -20,6 +20,7 @@ import type { PlatformTag } from '@modrinth/utils' import { useQuery, useQueryClient } from '@tanstack/vue-query' import { computed, nextTick, ref, watch } from 'vue' +import BackupSettings from '@/components/ui/instance_settings/BackupSettings.vue' import CoreComponentsSettings from '@/components/ui/instance_settings/CoreComponentsSettings.vue' import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue' import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue' @@ -105,6 +106,14 @@ const tabs = computed(() => [ icon: WrenchIcon, content: InstallationSettings, }, + { + name: defineMessage({ + id: 'instance.settings.tabs.backups', + defaultMessage: 'Backups', + }), + icon: FileArchiveIcon, + content: BackupSettings, + }, { // Core component editing is instance-specific and advanced, so it is intentionally excluded from first-run onboarding. name: defineMessage({ diff --git a/apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue b/apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue new file mode 100644 index 0000000..46e3dd8 --- /dev/null +++ b/apps/app-frontend/src/components/ui/settings/BackupRepositorySettings.vue @@ -0,0 +1,167 @@ + + + diff --git a/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue b/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue index b1c7803..b557d8e 100644 --- a/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue +++ b/apps/app-frontend/src/components/ui/settings/StorageBackupSettings.vue @@ -1,4 +1,5 @@ @@ -6,6 +7,7 @@ import StorageSettings from './StorageSettings.vue' diff --git a/apps/app-frontend/src/locales/en-US/index.json b/apps/app-frontend/src/locales/en-US/index.json index 9841f94..9373038 100644 --- a/apps/app-frontend/src/locales/en-US/index.json +++ b/apps/app-frontend/src/locales/en-US/index.json @@ -10000,5 +10000,170 @@ }, "app.navigation.screenshots": { "message": "Screenshots" + }, + "instance.backups.added-size": { + "message": "{size} newly stored" + }, + "instance.backups.all-included": { + "message": "All instance files and folders are included." + }, + "instance.backups.backup-now": { + "message": "Back up now" + }, + "instance.backups.cancel": { + "message": "Cancel backup" + }, + "instance.backups.choose": { + "message": "Everything is backed up by default. Add files or folders that should be excluded." + }, + "instance.backups.delete": { + "message": "Delete backup" + }, + "instance.backups.delete-body": { + "message": "This snapshot will be permanently deleted. Unused stored files will also be removed." + }, + "instance.backups.delete-title": { + "message": "Delete this backup?" + }, + "instance.backups.description": { + "message": "Create deduplicated snapshots of the whole instance while it is closed." + }, + "instance.backups.disable": { + "message": "Disable backups" + }, + "instance.backups.disable-blocked": { + "message": "Delete every snapshot before disabling backups." + }, + "instance.backups.edit-exclusions": { + "message": "Edit exclusions" + }, + "instance.backups.empty": { + "message": "No backups have been created yet." + }, + "instance.backups.enable": { + "message": "Enable backups" + }, + "instance.backups.excluded-paths": { + "message": "Excluded: {paths}" + }, + "instance.backups.exclusions.add-file": { + "message": "Add excluded file" + }, + "instance.backups.exclusions.add-folder": { + "message": "Add excluded folder" + }, + "instance.backups.exclusions.empty": { + "message": "No files or folders are excluded." + }, + "instance.backups.exclusions.remove": { + "message": "Remove {path} from exclusions" + }, + "instance.backups.loading": { + "message": "Loading backup settings..." + }, + "instance.backups.progress-bytes": { + "message": "{processed} of {total}" + }, + "instance.backups.restore": { + "message": "Restore backup" + }, + "instance.backups.restore-body": { + "message": "Upcoming operations: add {added, plural, one {# file} other {# files}}, modify {modified, plural, one {# file} other {# files}}, and delete {deleted, plural, one {# file} other {# files}}. Continue?" + }, + "instance.backups.restore-title": { + "message": "Restore this backup?" + }, + "instance.backups.save-exclusions": { + "message": "Save exclusions" + }, + "instance.backups.snapshot-meta": { + "message": "{files, plural, one {# file} other {# files}} · {size}" + }, + "instance.backups.snapshots": { + "message": "Snapshots" + }, + "instance.backups.stage.applying": { + "message": "Applying changed files" + }, + "instance.backups.stage.failed": { + "message": "Backup failed" + }, + "instance.backups.stage.hashing": { + "message": "Hashing files" + }, + "instance.backups.stage.interrupted": { + "message": "Backup operation was interrupted" + }, + "instance.backups.stage.materializing": { + "message": "Preparing changed files" + }, + "instance.backups.stage.preview": { + "message": "Analyzing restore changes" + }, + "instance.backups.stage.queued": { + "message": "Waiting to start" + }, + "instance.backups.stage.saving": { + "message": "Saving snapshot index" + }, + "instance.backups.stage.scanning": { + "message": "Scanning files" + }, + "instance.backups.stage.validating": { + "message": "Validating restore" + }, + "instance.backups.title": { + "message": "Instance backups" + }, + "instance.backups.unavailable": { + "message": "Backups are unavailable" + }, + "instance.backups.unavailable.direct-linked": { + "message": "Instances linked directly to another launcher cannot be backed up." + }, + "instance.backups.unavailable.external-directory": { + "message": "Instances that use an external game directory cannot be backed up." + }, + "instance.backups.unavailable.not-installed": { + "message": "Finish installing this instance before enabling backups." + }, + "instance.backups.unavailable.root-link": { + "message": "Instances whose root folder is a link cannot be backed up." + }, + "instance.backups.unavailable.root-missing": { + "message": "The instance folder is missing." + }, + "instance.backups.unavailable.symlink": { + "message": "Imported symbolic-link instances cannot be backed up." + }, + "settings.backups.repository.change": { + "message": "Change folder" + }, + "settings.backups.repository.description": { + "message": "Deduplicated instance backup data and its independent index database." + }, + "settings.backups.repository.location": { + "message": "Location" + }, + "settings.backups.repository.location-description": { + "message": "Changing the location moves the complete repository. Choose a new or empty folder." + }, + "settings.backups.repository.moving": { + "message": "Moving repository..." + }, + "settings.backups.repository.moving-progress": { + "message": "Moving repository: {processed} of {total}" + }, + "settings.backups.repository.title": { + "message": "Backup repository" + }, + "settings.backups.repository.unavailable": { + "message": "Repository unavailable" + }, + "settings.backups.repository.usage": { + "message": "Storage usage" + }, + "settings.backups.repository.usage-description": { + "message": "{stored} stored for {logical} of backup data across {count, plural, one {# snapshot} other {# snapshots}}." } } diff --git a/apps/app-frontend/src/locales/zh-CN/index.json b/apps/app-frontend/src/locales/zh-CN/index.json index 9b367e0..a2ffa43 100644 --- a/apps/app-frontend/src/locales/zh-CN/index.json +++ b/apps/app-frontend/src/locales/zh-CN/index.json @@ -10764,5 +10764,170 @@ }, "app.navigation.screenshots": { "message": "截图" + }, + "instance.backups.added-size": { + "message": "新增存储 {size}" + }, + "instance.backups.all-included": { + "message": "实例中的所有文件和文件夹都会纳入备份。" + }, + "instance.backups.backup-now": { + "message": "立即备份" + }, + "instance.backups.cancel": { + "message": "取消备份" + }, + "instance.backups.choose": { + "message": "默认备份实例中的全部内容,请添加不需要备份的文件或文件夹。" + }, + "instance.backups.delete": { + "message": "删除备份" + }, + "instance.backups.delete-body": { + "message": "此快照将被永久删除,不再使用的仓库文件也会一并清理。" + }, + "instance.backups.delete-title": { + "message": "删除此备份?" + }, + "instance.backups.description": { + "message": "在实例关闭时,为整个实例创建去重快照。" + }, + "instance.backups.disable": { + "message": "关闭备份" + }, + "instance.backups.disable-blocked": { + "message": "必须先删除全部快照才能关闭备份。" + }, + "instance.backups.edit-exclusions": { + "message": "编辑排除项" + }, + "instance.backups.empty": { + "message": "尚未创建任何备份。" + }, + "instance.backups.enable": { + "message": "启用备份" + }, + "instance.backups.excluded-paths": { + "message": "不备份:{paths}" + }, + "instance.backups.exclusions.add-file": { + "message": "添加忽略文件" + }, + "instance.backups.exclusions.add-folder": { + "message": "添加忽略文件夹" + }, + "instance.backups.exclusions.empty": { + "message": "没有忽略任何文件或文件夹。" + }, + "instance.backups.exclusions.remove": { + "message": "从排除项中移除 {path}" + }, + "instance.backups.loading": { + "message": "正在加载备份设置……" + }, + "instance.backups.progress-bytes": { + "message": "已处理 {processed},共 {total}" + }, + "instance.backups.restore": { + "message": "恢复备份" + }, + "instance.backups.restore-body": { + "message": "即将进行的操作:新增 {added} 个文件,修改 {modified} 个文件,删除 {deleted} 个文件。是否继续?" + }, + "instance.backups.restore-title": { + "message": "恢复此备份?" + }, + "instance.backups.save-exclusions": { + "message": "保存排除项" + }, + "instance.backups.snapshot-meta": { + "message": "{files, plural, other {# 个文件}} · {size}" + }, + "instance.backups.snapshots": { + "message": "快照" + }, + "instance.backups.stage.applying": { + "message": "正在应用变更文件" + }, + "instance.backups.stage.failed": { + "message": "备份失败" + }, + "instance.backups.stage.hashing": { + "message": "正在计算文件哈希" + }, + "instance.backups.stage.interrupted": { + "message": "备份任务已中断" + }, + "instance.backups.stage.materializing": { + "message": "正在准备变更文件" + }, + "instance.backups.stage.preview": { + "message": "正在分析恢复变更" + }, + "instance.backups.stage.queued": { + "message": "等待开始" + }, + "instance.backups.stage.saving": { + "message": "正在保存快照索引" + }, + "instance.backups.stage.scanning": { + "message": "正在扫描文件" + }, + "instance.backups.stage.validating": { + "message": "正在验证恢复任务" + }, + "instance.backups.title": { + "message": "实例备份" + }, + "instance.backups.unavailable": { + "message": "备份不可用" + }, + "instance.backups.unavailable.direct-linked": { + "message": "不支持备份直接关联到其他启动器的实例。" + }, + "instance.backups.unavailable.external-directory": { + "message": "不支持备份使用外部游戏目录的实例。" + }, + "instance.backups.unavailable.not-installed": { + "message": "请先完成实例安装,再启用备份。" + }, + "instance.backups.unavailable.root-link": { + "message": "不支持备份根文件夹为链接的实例。" + }, + "instance.backups.unavailable.root-missing": { + "message": "实例文件夹不存在。" + }, + "instance.backups.unavailable.symlink": { + "message": "不支持备份以符号链接导入的实例。" + }, + "settings.backups.repository.change": { + "message": "更改文件夹" + }, + "settings.backups.repository.description": { + "message": "存放去重后的实例备份数据及其独立索引数据库。" + }, + "settings.backups.repository.location": { + "message": "位置" + }, + "settings.backups.repository.location-description": { + "message": "更改位置会移动完整仓库。请选择新的或空的文件夹。" + }, + "settings.backups.repository.moving": { + "message": "正在移动仓库……" + }, + "settings.backups.repository.moving-progress": { + "message": "正在移动备份仓库:已处理 {processed},共 {total}" + }, + "settings.backups.repository.title": { + "message": "备份文件仓库" + }, + "settings.backups.repository.unavailable": { + "message": "仓库不可用" + }, + "settings.backups.repository.usage": { + "message": "存储占用" + }, + "settings.backups.repository.usage-description": { + "message": "实际存储 {stored},对应 {logical} 的备份数据,共 {count, plural, other {# 个快照}}。" } } diff --git a/apps/app-frontend/src/locales/zh-TW/index.json b/apps/app-frontend/src/locales/zh-TW/index.json index 912d919..ac75760 100644 --- a/apps/app-frontend/src/locales/zh-TW/index.json +++ b/apps/app-frontend/src/locales/zh-TW/index.json @@ -10195,5 +10195,170 @@ }, "app.instance.dependencies.zoom-out": { "message": "縮小" + }, + "instance.backups.added-size": { + "message": "新增存储 {size}" + }, + "instance.backups.all-included": { + "message": "实例中的所有文件和文件夹都会纳入备份。" + }, + "instance.backups.backup-now": { + "message": "立即备份" + }, + "instance.backups.cancel": { + "message": "取消备份" + }, + "instance.backups.choose": { + "message": "默认备份实例中的全部内容,请添加不需要备份的文件或文件夹。" + }, + "instance.backups.delete": { + "message": "删除备份" + }, + "instance.backups.delete-body": { + "message": "此快照将被永久删除,不再使用的仓库文件也会一并清理。" + }, + "instance.backups.delete-title": { + "message": "删除此备份?" + }, + "instance.backups.description": { + "message": "在实例关闭时,为整个实例创建去重快照。" + }, + "instance.backups.disable": { + "message": "关闭备份" + }, + "instance.backups.disable-blocked": { + "message": "必须先删除全部快照才能关闭备份。" + }, + "instance.backups.edit-exclusions": { + "message": "编辑排除项" + }, + "instance.backups.empty": { + "message": "尚未创建任何备份。" + }, + "instance.backups.enable": { + "message": "启用备份" + }, + "instance.backups.excluded-paths": { + "message": "不备份:{paths}" + }, + "instance.backups.exclusions.add-file": { + "message": "添加忽略文件" + }, + "instance.backups.exclusions.add-folder": { + "message": "添加忽略文件夹" + }, + "instance.backups.exclusions.empty": { + "message": "没有忽略任何文件或文件夹。" + }, + "instance.backups.exclusions.remove": { + "message": "从排除项中移除 {path}" + }, + "instance.backups.loading": { + "message": "正在加载备份设置……" + }, + "instance.backups.progress-bytes": { + "message": "已处理 {processed},共 {total}" + }, + "instance.backups.restore": { + "message": "恢复备份" + }, + "instance.backups.restore-body": { + "message": "即将进行的操作:新增 {added} 个文件,修改 {modified} 个文件,删除 {deleted} 个文件。是否继续?" + }, + "instance.backups.restore-title": { + "message": "恢复此备份?" + }, + "instance.backups.save-exclusions": { + "message": "保存排除项" + }, + "instance.backups.snapshot-meta": { + "message": "{files, plural, other {# 个文件}} · {size}" + }, + "instance.backups.snapshots": { + "message": "快照" + }, + "instance.backups.stage.applying": { + "message": "正在应用变更文件" + }, + "instance.backups.stage.failed": { + "message": "备份失败" + }, + "instance.backups.stage.hashing": { + "message": "正在计算文件哈希" + }, + "instance.backups.stage.interrupted": { + "message": "备份任务已中断" + }, + "instance.backups.stage.materializing": { + "message": "正在准备变更文件" + }, + "instance.backups.stage.preview": { + "message": "正在分析恢复变更" + }, + "instance.backups.stage.queued": { + "message": "等待开始" + }, + "instance.backups.stage.saving": { + "message": "正在保存快照索引" + }, + "instance.backups.stage.scanning": { + "message": "正在扫描文件" + }, + "instance.backups.stage.validating": { + "message": "正在验证恢复任务" + }, + "instance.backups.title": { + "message": "实例备份" + }, + "instance.backups.unavailable": { + "message": "备份不可用" + }, + "instance.backups.unavailable.direct-linked": { + "message": "不支持备份直接关联到其他启动器的实例。" + }, + "instance.backups.unavailable.external-directory": { + "message": "不支持备份使用外部游戏目录的实例。" + }, + "instance.backups.unavailable.not-installed": { + "message": "请先完成实例安装,再启用备份。" + }, + "instance.backups.unavailable.root-link": { + "message": "不支持备份根文件夹为链接的实例。" + }, + "instance.backups.unavailable.root-missing": { + "message": "实例文件夹不存在。" + }, + "instance.backups.unavailable.symlink": { + "message": "不支持备份以符号链接导入的实例。" + }, + "settings.backups.repository.change": { + "message": "更改文件夹" + }, + "settings.backups.repository.description": { + "message": "存放去重后的实例备份数据及其独立索引数据库。" + }, + "settings.backups.repository.location": { + "message": "位置" + }, + "settings.backups.repository.location-description": { + "message": "更改位置会移动完整仓库。请选择新的或空的文件夹。" + }, + "settings.backups.repository.moving": { + "message": "正在移动仓库……" + }, + "settings.backups.repository.moving-progress": { + "message": "正在移动备份仓库:已处理 {processed},共 {total}" + }, + "settings.backups.repository.title": { + "message": "备份文件仓库" + }, + "settings.backups.repository.unavailable": { + "message": "仓库不可用" + }, + "settings.backups.repository.usage": { + "message": "存储占用" + }, + "settings.backups.repository.usage-description": { + "message": "实际存储 {stored},对应 {logical} 的备份数据,共 {count, plural, other {# 个快照}}。" } }