feat(backup): 玩过的世界每天在停服后自动打一个定时恢复点,按服按 owner 保留 7 个、90 天过期
This commit is contained in:
29 files changed
+1028
-59
No files matched your search
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"title": "Backups & restore",
|
||||
"subtitle": "A backup is saved automatically when a world is archived after long inactivity. You can use these backups to restore the world — the server must be stopped first.",
|
||||
"subtitle": "A world that was played is backed up automatically once a day after its server stops, and again before it is archived after long inactivity. You can use these backups to restore the world — the server must be stopped first.",
|
||||
"back_to_console": "Back to console",
|
||||
"not_yours_title": "No permission to access backups",
|
||||
"not_yours_body": "Only the owner or an admin can view and restore this server's backups.",
|
||||
"latest_title": "Latest backup",
|
||||
"history_note": "Backups can be restored until they expire, then they are cleaned up automatically. Each server keeps only its most recent manual backups (older ones are removed once a new one finishes) and its last 3 pre-restore snapshots.",
|
||||
"history_note": "Backups can be restored until they expire, then they are cleaned up automatically. Each server keeps only its most recent manual backups and its most recent scheduled backups (older ones of the same kind are removed once a new one finishes), and its last 3 pre-restore snapshots.",
|
||||
"reason_inactive": "Idle archive",
|
||||
"reason_manual": "Manual backup",
|
||||
"reason_pre_restore": "Pre-restore snapshot",
|
||||
"reason_scheduled": "Scheduled backup",
|
||||
"reason_label": "Reason: {{reason}}",
|
||||
"backup_now": "Back up now",
|
||||
"backup_in_progress": "Starting backup…",
|
||||
@@ -18,12 +19,13 @@
|
||||
"expired": "Expired",
|
||||
"former_owner": "Former owner: {{owner}}",
|
||||
"empty_title": "No backups for this server",
|
||||
"empty_hint": "A backup is saved when a world is archived after long inactivity — or create one now with “Back up now”.",
|
||||
"empty_hint": "A scheduled backup is saved once the server has run and stopped — or create one now with “Back up now”.",
|
||||
"jobs_title": "Recent operations",
|
||||
"jobs_empty": "No recent backup or restore operations.",
|
||||
"job_backup": "Backup",
|
||||
"job_restore": "Restore",
|
||||
"job_pre_restore": "Pre-restore snapshot",
|
||||
"job_scheduled": "Scheduled backup",
|
||||
"job_running": "Running",
|
||||
"job_succeeded": "Succeeded",
|
||||
"job_failed": "Failed",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"title": "备份与恢复",
|
||||
"subtitle": "服务器长期闲置并被自动回收前,系统会自动创建备份。可以使用备份随时恢复世界,恢复前需要先停止服务器。",
|
||||
"subtitle": "玩过的世界会在服务器停止后每天自动备份一次,长期闲置被自动回收前也会自动备份。可以使用备份随时恢复世界,恢复前需要先停止服务器。",
|
||||
"back_to_console": "返回控制台",
|
||||
"not_yours_title": "无权访问备份",
|
||||
"not_yours_body": "只有所有者或管理员才能查看并恢复该服务器的备份。",
|
||||
"latest_title": "最新备份",
|
||||
"history_note": "历史备份在过期前均可用于恢复,到期后自动清理。手动备份每台服务器只保留最近几份,新备份完成后会自动删除更早的手动备份;恢复前快照保留最近 3 份。",
|
||||
"history_note": "历史备份在过期前均可用于恢复,到期后自动清理。手动备份和定时备份每台服务器各只保留最近几份,新备份完成后会自动删除更早的同类备份;恢复前快照保留最近 3 份。",
|
||||
"reason_inactive": "闲置自动回收",
|
||||
"reason_manual": "手动备份",
|
||||
"reason_pre_restore": "恢复前快照",
|
||||
"reason_scheduled": "定时备份",
|
||||
"reason_label": "原因:{{reason}}",
|
||||
"backup_now": "立即备份",
|
||||
"backup_in_progress": "正在启动备份……",
|
||||
@@ -18,12 +19,13 @@
|
||||
"expired": "已过期",
|
||||
"former_owner": "原世界所有者:{{owner}}",
|
||||
"empty_title": "暂无备份",
|
||||
"empty_hint": "服务器长期闲置被自动回收时会生成备份,也可以点“立即备份”马上创建。",
|
||||
"empty_hint": "服务器运行过并停止后会自动生成定时备份,也可以点“立即备份”马上创建。",
|
||||
"jobs_title": "最近操作",
|
||||
"jobs_empty": "暂无备份或恢复操作记录。",
|
||||
"job_backup": "备份",
|
||||
"job_restore": "恢复",
|
||||
"job_pre_restore": "恢复前快照",
|
||||
"job_scheduled": "定时备份",
|
||||
"job_running": "进行中",
|
||||
"job_succeeded": "成功",
|
||||
"job_failed": "失败",
|
||||
|
||||
@@ -2420,7 +2420,7 @@ export interface components {
|
||||
former_owner?: string;
|
||||
/** Format: int64 */
|
||||
size_bytes: number;
|
||||
/** @description inactive_15d (idle reclaim), manual (on demand) or pre_restore (the safety snapshot in front of a restore). */
|
||||
/** @description inactive_15d (idle reclaim), manual (on demand), pre_restore (the safety snapshot in front of a restore) or scheduled (the daily restore point felis-api takes of a world played since its last one, once the server stops). */
|
||||
reason: string;
|
||||
status: string;
|
||||
/** Format: date-time */
|
||||
@@ -5548,6 +5548,8 @@ export interface operations {
|
||||
then_restore_reason?: "snapshot_failed" | "not_configured" | "server_gone" | "server_started" | "restore_busy";
|
||||
/** @description The backup the chained restore extracts. */
|
||||
restore_backup_id?: string;
|
||||
/** @description A backup felis-api took on its own: the daily restore point of a played world. Omitted when false. */
|
||||
scheduled?: boolean;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -214,6 +214,9 @@ export interface ServerJob {
|
||||
then_restore?: string;
|
||||
then_restore_reason?: string;
|
||||
restore_backup_id?: string;
|
||||
/** A backup felis-api took on its own: the daily restore point of a world
|
||||
* played since its last one, taken once the server stops. */
|
||||
scheduled?: boolean;
|
||||
}
|
||||
|
||||
/** WhitelistImage is one row of GET /images (the create-form dropdown source). */
|
||||
|
||||
@@ -82,6 +82,23 @@ describe("ServerBackups", () => {
|
||||
expect(screen.queryByText("Latest backup")).toBeNull();
|
||||
});
|
||||
|
||||
it("names the daily restore points felis-api takes, in the list and in recent operations", async () => {
|
||||
calls.listBackups.mockResolvedValue({
|
||||
backups: [{ ...backup("bk-sched", 3), reason: "scheduled" }, backup("bk-manual", 5)],
|
||||
total: 2,
|
||||
});
|
||||
calls.serverJobs.mockResolvedValue([
|
||||
{ name: "backup-survival-aa", kind: "backup", state: "succeeded", scheduled: true },
|
||||
{ name: "backup-survival-bb", kind: "backup", state: "succeeded" },
|
||||
]);
|
||||
renderPage();
|
||||
const sched = await screen.findByText("3 hours ago");
|
||||
expect(sched.closest("tr")?.textContent).toContain("Scheduled backup");
|
||||
expect(screen.getByText("5 hours ago").closest("tr")?.textContent).toContain("Manual backup");
|
||||
const ops = await screen.findAllByText(/^(Scheduled backup|Backup)$/, { selector: "li span" });
|
||||
expect(ops.map((el) => el.textContent)).toEqual(["Scheduled backup", "Backup"]);
|
||||
});
|
||||
|
||||
it("shows no pager when the server's backups fit on one page", async () => {
|
||||
calls.listBackups.mockResolvedValue({ backups: [backup("bk-1", 3)], total: 1 });
|
||||
renderPage();
|
||||
|
||||
@@ -75,6 +75,8 @@ function BackupRow({
|
||||
? t("reason_manual")
|
||||
: b.reason === "pre_restore"
|
||||
? t("reason_pre_restore")
|
||||
: b.reason === "scheduled"
|
||||
? t("reason_scheduled")
|
||||
: t("reason_label", { reason: b.reason });
|
||||
|
||||
// Below md the row stops being a table row: the when/why block takes the full
|
||||
@@ -684,6 +686,8 @@ export function ServerBackups() {
|
||||
: j.kind === "backup"
|
||||
? j.then_restore
|
||||
? t("job_pre_restore")
|
||||
: j.scheduled
|
||||
? t("job_scheduled")
|
||||
: t("job_backup")
|
||||
: j.kind}
|
||||
</span>
|
||||
|
||||
Reference in new issue
Block a user