mirror of
https://github.com/Corfirean/coa-server-manager.git
synced 2026-11-11 20:01:01 +00:00
UI: translate Backups page
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0b975a7c42
commit
b5bfeb20e7
6 files changed
+274
-52
No files matched your search
@@ -226,6 +226,52 @@ const de: Partial<Record<keyof typeof en, string>> = {
|
||||
"about.upToDate": "CoA Server Manager ist auf dem neuesten Stand.",
|
||||
"about.checkFailed": "Die Suche nach einer neuen Version ist fehlgeschlagen. Prüfe deine Internetverbindung und versuche es erneut.",
|
||||
"about.installFailed": "Das Update konnte nicht installiert werden. Dein Server wurde nicht angetastet.",
|
||||
|
||||
// Backups
|
||||
"bk.title": "Sicherungen",
|
||||
"bk.trigger.manual": "Manuelle Sicherung",
|
||||
"bk.trigger.automatic": "Automatische Sicherung",
|
||||
"bk.trigger.before-update": "Vor dem Update",
|
||||
"bk.trigger.before-restore": "Sicherheitskopie vor einer Wiederherstellung",
|
||||
"bk.trigger.before-migration": "Vor dem Datenbank-Update",
|
||||
"bk.trigger.before-bots": "Vor der Installation der Begleiter",
|
||||
"bk.trigger.before-repair": "Vor der Reparatur",
|
||||
"bk.trigger.before-dangerous-change": "Vor einer riskanten Änderung",
|
||||
"bk.kind.quick": "Charaktere, Konten und Einstellungen",
|
||||
"bk.kind.full": "Alles, einschließlich der Weltdatenbank",
|
||||
"bk.kind.config": "Nur Einstellungen",
|
||||
"bk.kind.database": "Charaktere und Konten",
|
||||
"bk.db.characters": "Charaktere",
|
||||
"bk.db.auth": "Konten",
|
||||
"bk.db.world": "Weltdaten",
|
||||
"bk.today": "Heute {time}",
|
||||
"bk.yesterday": "Gestern {time}",
|
||||
"bk.created": "Sicherung erstellt.",
|
||||
"bk.last": "Letzte Sicherung: {when}",
|
||||
"bk.none": "Noch keine Sicherungen",
|
||||
"bk.keptNext": "Sicherungen liegen neben deinem Server und überschreiben nie deine Live-Daten.",
|
||||
"bk.backUpNow": "Jetzt sichern",
|
||||
"bk.full": "Vollsicherung",
|
||||
"bk.fullHint": "Sichert auch die große Weltdatenbank",
|
||||
"bk.settingsOnly": "Nur Einstellungen",
|
||||
"bk.working": "Wird ausgeführt …",
|
||||
"bk.failed": "Das hat nicht funktioniert",
|
||||
"bk.restoreFrom": "Wiederherstellen aus „{what}“, {when}",
|
||||
"bk.restoreNote": "Es wird nichts gelöscht. Deine aktuellen Daten werden zuerst gesichert, und eine ersetzte Datenbank bleibt unter neuem Namen erhalten.",
|
||||
"bk.stopFirst": "Stoppe zuerst den Server – Datenbanken können nur bei gestopptem Server wiederhergestellt werden.",
|
||||
"bk.settingsRestored": "Einstellungen wiederhergestellt. Eine Sicherheitskopie der vorherigen Einstellungen wurde gespeichert.",
|
||||
"bk.restoreSettings": "Einstellungen wiederherstellen",
|
||||
"bk.confirmDb": "Datenbank „{db}“ aus dieser Sicherung wiederherstellen? Fortschritt seit {when} wird beiseitegelegt (nicht gelöscht).",
|
||||
"bk.dbRestored": "{n} Tabellen wiederhergestellt. Die vorherigen Daten bleiben als {schema} erhalten.",
|
||||
"bk.restoreDb": "{db} wiederherstellen",
|
||||
"bk.loading": "Wird geladen …",
|
||||
"bk.empty": "Deine Sicherungen erscheinen hier.",
|
||||
"bk.restore": "Wiederherstellen",
|
||||
"bk.verify": "Prüfen",
|
||||
"bk.intact": "Diese Sicherung ist unversehrt.",
|
||||
"bk.problem": "Problem gefunden: {details}",
|
||||
"bk.delete": "Löschen",
|
||||
"bk.confirmDelete": "Diese Sicherung löschen? Nur diese Sicherung wird entfernt; dein Server bleibt unberührt.",
|
||||
};
|
||||
|
||||
export default de;
|
||||
@@ -226,6 +226,52 @@ const en = {
|
||||
"about.upToDate": "CoA Server Manager is up to date.",
|
||||
"about.checkFailed": "Could not check for a new version. Check your internet connection and try again.",
|
||||
"about.installFailed": "The update could not be installed. Your server was not touched.",
|
||||
|
||||
// Backups
|
||||
"bk.title": "Backups",
|
||||
"bk.trigger.manual": "Manual backup",
|
||||
"bk.trigger.automatic": "Automatic backup",
|
||||
"bk.trigger.before-update": "Before update",
|
||||
"bk.trigger.before-restore": "Safety copy before a restore",
|
||||
"bk.trigger.before-migration": "Before database update",
|
||||
"bk.trigger.before-bots": "Before installing companions",
|
||||
"bk.trigger.before-repair": "Before repair",
|
||||
"bk.trigger.before-dangerous-change": "Before a risky change",
|
||||
"bk.kind.quick": "Characters, accounts and settings",
|
||||
"bk.kind.full": "Everything, including the world database",
|
||||
"bk.kind.config": "Settings only",
|
||||
"bk.kind.database": "Characters and accounts",
|
||||
"bk.db.characters": "Characters",
|
||||
"bk.db.auth": "Accounts",
|
||||
"bk.db.world": "World data",
|
||||
"bk.today": "Today {time}",
|
||||
"bk.yesterday": "Yesterday {time}",
|
||||
"bk.created": "Backup created.",
|
||||
"bk.last": "Last backup: {when}",
|
||||
"bk.none": "No backups yet",
|
||||
"bk.keptNext": "Backups are kept next to your server and never overwrite your live data.",
|
||||
"bk.backUpNow": "Back up now",
|
||||
"bk.full": "Full backup",
|
||||
"bk.fullHint": "Also saves the large world database",
|
||||
"bk.settingsOnly": "Settings only",
|
||||
"bk.working": "Working…",
|
||||
"bk.failed": "That did not work",
|
||||
"bk.restoreFrom": "Restore from {what}, {when}",
|
||||
"bk.restoreNote": "Nothing is deleted. Your current data is saved first, and a replaced database is kept alongside under a new name.",
|
||||
"bk.stopFirst": "Stop the server first — databases can only be restored while it is stopped.",
|
||||
"bk.settingsRestored": "Settings restored. A safety copy of the previous settings was saved.",
|
||||
"bk.restoreSettings": "Restore settings",
|
||||
"bk.confirmDb": "Restore the {db} database from this backup? Progress made since {when} will be set aside (not deleted).",
|
||||
"bk.dbRestored": "Restored {n} tables. The previous data is kept as {schema}.",
|
||||
"bk.restoreDb": "Restore {db}",
|
||||
"bk.loading": "Loading…",
|
||||
"bk.empty": "Your backups will appear here.",
|
||||
"bk.restore": "Restore",
|
||||
"bk.verify": "Verify",
|
||||
"bk.intact": "This backup is intact.",
|
||||
"bk.problem": "Problem found: {details}",
|
||||
"bk.delete": "Delete",
|
||||
"bk.confirmDelete": "Delete this backup? Only this backup is removed; your server is not affected.",
|
||||
} as const;
|
||||
|
||||
export default en;
|
||||
@@ -226,6 +226,52 @@ const es: Partial<Record<keyof typeof en, string>> = {
|
||||
"about.upToDate": "CoA Server Manager está actualizado.",
|
||||
"about.checkFailed": "No se pudo buscar una versión nueva. Comprueba tu conexión a internet e inténtalo de nuevo.",
|
||||
"about.installFailed": "No se pudo instalar la actualización. Tu servidor no se ha tocado.",
|
||||
|
||||
// Backups
|
||||
"bk.title": "Copias de seguridad",
|
||||
"bk.trigger.manual": "Copia manual",
|
||||
"bk.trigger.automatic": "Copia automática",
|
||||
"bk.trigger.before-update": "Antes de actualizar",
|
||||
"bk.trigger.before-restore": "Copia de seguridad antes de restaurar",
|
||||
"bk.trigger.before-migration": "Antes de actualizar la base de datos",
|
||||
"bk.trigger.before-bots": "Antes de instalar compañeros",
|
||||
"bk.trigger.before-repair": "Antes de reparar",
|
||||
"bk.trigger.before-dangerous-change": "Antes de un cambio arriesgado",
|
||||
"bk.kind.quick": "Personajes, cuentas y ajustes",
|
||||
"bk.kind.full": "Todo, incluida la base de datos del mundo",
|
||||
"bk.kind.config": "Solo ajustes",
|
||||
"bk.kind.database": "Personajes y cuentas",
|
||||
"bk.db.characters": "Personajes",
|
||||
"bk.db.auth": "Cuentas",
|
||||
"bk.db.world": "Datos del mundo",
|
||||
"bk.today": "Hoy {time}",
|
||||
"bk.yesterday": "Ayer {time}",
|
||||
"bk.created": "Copia creada.",
|
||||
"bk.last": "Última copia: {when}",
|
||||
"bk.none": "Aún no hay copias",
|
||||
"bk.keptNext": "Las copias se guardan junto a tu servidor y nunca sobrescriben tus datos activos.",
|
||||
"bk.backUpNow": "Copiar ahora",
|
||||
"bk.full": "Copia completa",
|
||||
"bk.fullHint": "También guarda la gran base de datos del mundo",
|
||||
"bk.settingsOnly": "Solo ajustes",
|
||||
"bk.working": "Trabajando…",
|
||||
"bk.failed": "Eso no funcionó",
|
||||
"bk.restoreFrom": "Restaurar desde «{what}», {when}",
|
||||
"bk.restoreNote": "No se borra nada. Primero se guardan tus datos actuales y la base de datos reemplazada se conserva con otro nombre.",
|
||||
"bk.stopFirst": "Detén primero el servidor: las bases de datos solo se pueden restaurar con el servidor detenido.",
|
||||
"bk.settingsRestored": "Ajustes restaurados. Se guardó una copia de seguridad de los ajustes anteriores.",
|
||||
"bk.restoreSettings": "Restaurar ajustes",
|
||||
"bk.confirmDb": "¿Restaurar la base de datos «{db}» desde esta copia? El progreso desde {when} se apartará (no se borra).",
|
||||
"bk.dbRestored": "Se restauraron {n} tablas. Los datos anteriores se conservan como {schema}.",
|
||||
"bk.restoreDb": "Restaurar: {db}",
|
||||
"bk.loading": "Cargando…",
|
||||
"bk.empty": "Tus copias aparecerán aquí.",
|
||||
"bk.restore": "Restaurar",
|
||||
"bk.verify": "Verificar",
|
||||
"bk.intact": "Esta copia está intacta.",
|
||||
"bk.problem": "Problema detectado: {details}",
|
||||
"bk.delete": "Eliminar",
|
||||
"bk.confirmDelete": "¿Eliminar esta copia? Solo se elimina esta copia; tu servidor no se ve afectado.",
|
||||
};
|
||||
|
||||
export default es;
|
||||
@@ -226,6 +226,52 @@ const fr: Partial<Record<keyof typeof en, string>> = {
|
||||
"about.upToDate": "CoA Server Manager est à jour.",
|
||||
"about.checkFailed": "Impossible de rechercher une nouvelle version. Vérifiez votre connexion internet et réessayez.",
|
||||
"about.installFailed": "La mise à jour n’a pas pu être installée. Votre serveur n’a pas été touché.",
|
||||
|
||||
// Backups
|
||||
"bk.title": "Sauvegardes",
|
||||
"bk.trigger.manual": "Sauvegarde manuelle",
|
||||
"bk.trigger.automatic": "Sauvegarde automatique",
|
||||
"bk.trigger.before-update": "Avant la mise à jour",
|
||||
"bk.trigger.before-restore": "Copie de sécurité avant une restauration",
|
||||
"bk.trigger.before-migration": "Avant la mise à jour de la base de données",
|
||||
"bk.trigger.before-bots": "Avant l’installation des compagnons",
|
||||
"bk.trigger.before-repair": "Avant la réparation",
|
||||
"bk.trigger.before-dangerous-change": "Avant un changement risqué",
|
||||
"bk.kind.quick": "Personnages, comptes et paramètres",
|
||||
"bk.kind.full": "Tout, y compris la base de données du monde",
|
||||
"bk.kind.config": "Paramètres uniquement",
|
||||
"bk.kind.database": "Personnages et comptes",
|
||||
"bk.db.characters": "Personnages",
|
||||
"bk.db.auth": "Comptes",
|
||||
"bk.db.world": "Données du monde",
|
||||
"bk.today": "Aujourd’hui {time}",
|
||||
"bk.yesterday": "Hier {time}",
|
||||
"bk.created": "Sauvegarde créée.",
|
||||
"bk.last": "Dernière sauvegarde : {when}",
|
||||
"bk.none": "Aucune sauvegarde pour l’instant",
|
||||
"bk.keptNext": "Les sauvegardes sont conservées à côté de votre serveur et n’écrasent jamais vos données actives.",
|
||||
"bk.backUpNow": "Sauvegarder maintenant",
|
||||
"bk.full": "Sauvegarde complète",
|
||||
"bk.fullHint": "Enregistre aussi la grande base de données du monde",
|
||||
"bk.settingsOnly": "Paramètres uniquement",
|
||||
"bk.working": "En cours…",
|
||||
"bk.failed": "Cela n’a pas fonctionné",
|
||||
"bk.restoreFrom": "Restaurer depuis « {what} », {when}",
|
||||
"bk.restoreNote": "Rien n’est supprimé. Vos données actuelles sont d’abord sauvegardées, et une base remplacée est conservée sous un nouveau nom.",
|
||||
"bk.stopFirst": "Arrêtez d’abord le serveur : les bases de données ne peuvent être restaurées que serveur arrêté.",
|
||||
"bk.settingsRestored": "Paramètres restaurés. Une copie de sécurité des paramètres précédents a été enregistrée.",
|
||||
"bk.restoreSettings": "Restaurer les paramètres",
|
||||
"bk.confirmDb": "Restaurer la base « {db} » depuis cette sauvegarde ? La progression depuis {when} sera mise de côté (pas supprimée).",
|
||||
"bk.dbRestored": "{n} tables restaurées. Les données précédentes sont conservées sous {schema}.",
|
||||
"bk.restoreDb": "Restaurer : {db}",
|
||||
"bk.loading": "Chargement…",
|
||||
"bk.empty": "Vos sauvegardes apparaîtront ici.",
|
||||
"bk.restore": "Restaurer",
|
||||
"bk.verify": "Vérifier",
|
||||
"bk.intact": "Cette sauvegarde est intacte.",
|
||||
"bk.problem": "Problème détecté : {details}",
|
||||
"bk.delete": "Supprimer",
|
||||
"bk.confirmDelete": "Supprimer cette sauvegarde ? Seule cette sauvegarde est supprimée ; votre serveur n’est pas affecté.",
|
||||
};
|
||||
|
||||
export default fr;
|
||||
@@ -226,6 +226,52 @@ const ru: Partial<Record<keyof typeof en, string>> = {
|
||||
"about.upToDate": "У вас последняя версия CoA Server Manager.",
|
||||
"about.checkFailed": "Не удалось проверить наличие новой версии. Проверьте подключение к интернету и повторите попытку.",
|
||||
"about.installFailed": "Не удалось установить обновление. Ваш сервер не затронут.",
|
||||
|
||||
// Backups
|
||||
"bk.title": "Резервные копии",
|
||||
"bk.trigger.manual": "Копия, сделанная вручную",
|
||||
"bk.trigger.automatic": "Автоматическая копия",
|
||||
"bk.trigger.before-update": "Перед обновлением",
|
||||
"bk.trigger.before-restore": "Страховочная копия перед восстановлением",
|
||||
"bk.trigger.before-migration": "Перед обновлением базы данных",
|
||||
"bk.trigger.before-bots": "Перед установкой компаньонов",
|
||||
"bk.trigger.before-repair": "Перед восстановлением файлов",
|
||||
"bk.trigger.before-dangerous-change": "Перед рискованным изменением",
|
||||
"bk.kind.quick": "Персонажи, учётные записи и настройки",
|
||||
"bk.kind.full": "Всё, включая базу данных мира",
|
||||
"bk.kind.config": "Только настройки",
|
||||
"bk.kind.database": "Персонажи и учётные записи",
|
||||
"bk.db.characters": "Персонажи",
|
||||
"bk.db.auth": "Учётные записи",
|
||||
"bk.db.world": "Данные мира",
|
||||
"bk.today": "Сегодня, {time}",
|
||||
"bk.yesterday": "Вчера, {time}",
|
||||
"bk.created": "Резервная копия создана.",
|
||||
"bk.last": "Последняя копия: {when}",
|
||||
"bk.none": "Резервных копий пока нет",
|
||||
"bk.keptNext": "Копии хранятся рядом с сервером и никогда не перезаписывают ваши рабочие данные.",
|
||||
"bk.backUpNow": "Сделать копию",
|
||||
"bk.full": "Полная копия",
|
||||
"bk.fullHint": "Также сохраняет большую базу данных мира",
|
||||
"bk.settingsOnly": "Только настройки",
|
||||
"bk.working": "Работаю…",
|
||||
"bk.failed": "Не получилось",
|
||||
"bk.restoreFrom": "Восстановить из копии «{what}», {when}",
|
||||
"bk.restoreNote": "Ничего не удаляется. Сначала сохраняются ваши текущие данные, а заменённая база остаётся рядом под новым именем.",
|
||||
"bk.stopFirst": "Сначала остановите сервер — базы данных можно восстановить только при остановленном сервере.",
|
||||
"bk.settingsRestored": "Настройки восстановлены. Страховочная копия прежних настроек сохранена.",
|
||||
"bk.restoreSettings": "Восстановить настройки",
|
||||
"bk.confirmDb": "Восстановить базу данных «{db}» из этой копии? Всё, что произошло после {when}, будет отложено в сторону (не удалено).",
|
||||
"bk.dbRestored": "Восстановлено таблиц: {n}. Прежние данные сохранены как {schema}.",
|
||||
"bk.restoreDb": "Восстановить: {db}",
|
||||
"bk.loading": "Загрузка…",
|
||||
"bk.empty": "Ваши резервные копии появятся здесь.",
|
||||
"bk.restore": "Восстановить",
|
||||
"bk.verify": "Проверить",
|
||||
"bk.intact": "Эта копия цела.",
|
||||
"bk.problem": "Обнаружена проблема: {details}",
|
||||
"bk.delete": "Удалить",
|
||||
"bk.confirmDelete": "Удалить эту копию? Удаляется только она; ваш сервер не затрагивается.",
|
||||
};
|
||||
|
||||
export default ru;
|
||||
+44
-52
@@ -4,26 +4,13 @@ import { Loader2, ShieldCheck } from "lucide-react";
|
||||
import { api, asUiError, type BackupKind, type RecoveryPoint, type UiError } from "@/lib/api";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { useHuman, useI18n, type Key, type Locale } from "@/i18n";
|
||||
|
||||
const TRIGGER: Record<string, string> = {
|
||||
manual: "Manual backup",
|
||||
automatic: "Automatic backup",
|
||||
"before-update": "Before update",
|
||||
"before-restore": "Safety copy before a restore",
|
||||
"before-migration": "Before database update",
|
||||
"before-bots": "Before installing companions",
|
||||
"before-repair": "Before repair",
|
||||
"before-dangerous-change": "Before a risky change",
|
||||
};
|
||||
const TRIGGER_KEYS = ["manual", "automatic", "before-update", "before-restore", "before-migration", "before-bots", "before-repair", "before-dangerous-change"];
|
||||
|
||||
const KIND: Record<BackupKind, string> = {
|
||||
quick: "Characters, accounts and settings",
|
||||
full: "Everything, including the world database",
|
||||
config: "Settings only",
|
||||
database: "Characters and accounts",
|
||||
};
|
||||
const DB_KEYS = ["characters", "auth", "world"];
|
||||
|
||||
const DB_LABEL: Record<string, string> = { characters: "Characters", auth: "Accounts", world: "World data" };
|
||||
type T = (k: Key, v?: Record<string, string | number>) => string;
|
||||
|
||||
function size(bytes: number): string {
|
||||
if (bytes >= 1 << 30) return `${(bytes / (1 << 30)).toFixed(1)} GB`;
|
||||
@@ -31,17 +18,22 @@ function size(bytes: number): string {
|
||||
return `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
||||
}
|
||||
|
||||
function when(iso: string): string {
|
||||
function when(iso: string, t: T, locale: Locale): string {
|
||||
const d = new Date(iso);
|
||||
const today = new Date();
|
||||
const time = d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
||||
if (d.toDateString() === today.toDateString()) return `Today ${time}`;
|
||||
const time = d.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
|
||||
if (d.toDateString() === today.toDateString()) return t("bk.today", { time });
|
||||
const y = new Date(today.getTime() - 86400000);
|
||||
if (d.toDateString() === y.toDateString()) return `Yesterday ${time}`;
|
||||
return `${d.toLocaleDateString([], { day: "numeric", month: "short", year: "numeric" })} ${time}`;
|
||||
if (d.toDateString() === y.toDateString()) return t("bk.yesterday", { time });
|
||||
return `${d.toLocaleDateString(locale, { day: "numeric", month: "short", year: "numeric" })} ${time}`;
|
||||
}
|
||||
|
||||
export function BackupsPage({ serverId }: { serverId: string }) {
|
||||
const { t, locale } = useI18n();
|
||||
const human = useHuman();
|
||||
const w = (iso: string) => when(iso, t, locale);
|
||||
const trigger = (x: string) => (TRIGGER_KEYS.includes(x) ? t(`bk.trigger.${x}` as Key) : x);
|
||||
const dbLabel = (x: string) => (DB_KEYS.includes(x) ? t(`bk.db.${x}` as Key) : x);
|
||||
const [points, setPoints] = useState<RecoveryPoint[] | null>(null);
|
||||
const [busy, setBusy] = useState<string | null>(null);
|
||||
const [step, setStep] = useState<string | null>(null);
|
||||
@@ -84,54 +76,54 @@ export function BackupsPage({ serverId }: { serverId: string }) {
|
||||
const create = (kind: BackupKind) =>
|
||||
run("create", async () => {
|
||||
await api.createBackup(serverId, kind);
|
||||
return "Backup created.";
|
||||
return t("bk.created");
|
||||
});
|
||||
|
||||
const last = points?.[0];
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
<h1 className="text-2xl font-semibold">Backups</h1>
|
||||
<p className="mt-1 text-muted">Is my data safe?</p>
|
||||
<h1 className="text-2xl font-semibold">{t("bk.title")}</h1>
|
||||
<p className="mt-1 text-muted">{t("q.backups")}</p>
|
||||
|
||||
<Card className="mt-6 p-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<ShieldCheck className={last ? "h-6 w-6 text-ok" : "h-6 w-6 text-muted"} aria-hidden />
|
||||
<div>
|
||||
<p className="font-medium">{last ? `Last backup: ${when(last.created_at)}` : "No backups yet"}</p>
|
||||
<p className="text-sm text-muted">Backups are kept next to your server and never overwrite your live data.</p>
|
||||
<p className="font-medium">{last ? t("bk.last", { when: w(last.created_at) }) : t("bk.none")}</p>
|
||||
<p className="text-sm text-muted">{t("bk.keptNext")}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex flex-wrap items-center gap-3">
|
||||
<Button variant="primary" disabled={!!busy} onClick={() => void create("quick")}>
|
||||
{busy === "create" && <Loader2 className="h-4 w-4 animate-spin" aria-hidden />}
|
||||
Back up now
|
||||
{t("bk.backUpNow")}
|
||||
</Button>
|
||||
<Button disabled={!!busy} onClick={() => void create("full")} title="Also saves the large world database">
|
||||
Full backup
|
||||
<Button disabled={!!busy} onClick={() => void create("full")} title={t("bk.fullHint")}>
|
||||
{t("bk.full")}
|
||||
</Button>
|
||||
<Button variant="ghost" disabled={!!busy} onClick={() => void create("config")}>
|
||||
Settings only
|
||||
{t("bk.settingsOnly")}
|
||||
</Button>
|
||||
{busy === "create" && <span className="text-sm text-muted" role="status">{step ?? "Working…"}</span>}
|
||||
{busy === "create" && <span className="text-sm text-muted" role="status">{step ?? t("bk.working")}</span>}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{notice && <p className="mt-3 text-sm text-ok" role="status">{notice}</p>}
|
||||
{error && (
|
||||
<Card className="mt-4 border-bad/40 p-4" role="alert">
|
||||
<p className="font-medium text-bad">{error.human.code === "unknown" ? "That did not work" : error.human.title}</p>
|
||||
<p className="mt-1 text-sm text-muted">{error.human.code === "unknown" ? error.technical : error.human.message}</p>
|
||||
<p className="font-medium text-bad">{error.human.code === "unknown" ? t("bk.failed") : human(error.human).title}</p>
|
||||
<p className="mt-1 text-sm text-muted">{error.human.code === "unknown" ? error.technical : human(error.human).message}</p>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{restoring && (
|
||||
<Card className="mt-4 border-gold/40 p-5">
|
||||
<p className="font-medium">Restore from {TRIGGER[restoring.trigger] ?? restoring.trigger}, {when(restoring.created_at)}</p>
|
||||
<p className="font-medium">{t("bk.restoreFrom", { what: trigger(restoring.trigger), when: w(restoring.created_at) })}</p>
|
||||
<p className="mt-1 text-sm text-muted">
|
||||
Nothing is deleted. Your current data is saved first, and a replaced database is kept alongside under a new name.
|
||||
{t("bk.restoreNote")}
|
||||
</p>
|
||||
{running && <p className="mt-2 text-sm text-warn">Stop the server first — databases can only be restored while it is stopped.</p>}
|
||||
{running && <p className="mt-2 text-sm text-warn">{t("bk.stopFirst")}</p>}
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{restoring.components.map((c) =>
|
||||
c.name === "configs" ? (
|
||||
@@ -143,11 +135,11 @@ export function BackupsPage({ serverId }: { serverId: string }) {
|
||||
void run("restore", async () => {
|
||||
await api.restoreConfigs(serverId, restoring.id);
|
||||
setRestoring(null);
|
||||
return "Settings restored. A safety copy of the previous settings was saved.";
|
||||
return t("bk.settingsRestored");
|
||||
})
|
||||
}
|
||||
>
|
||||
Restore settings
|
||||
{t("bk.restoreSettings")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
@@ -155,39 +147,39 @@ export function BackupsPage({ serverId }: { serverId: string }) {
|
||||
size="sm"
|
||||
disabled={!!busy || running}
|
||||
onClick={() => {
|
||||
if (!window.confirm(`Restore the ${DB_LABEL[c.name] ?? c.name} database from this backup? Progress made since ${when(restoring.created_at)} will be set aside (not deleted).`)) return;
|
||||
if (!window.confirm(t("bk.confirmDb", { db: dbLabel(c.name), when: w(restoring.created_at) }))) return;
|
||||
void run("restore", async () => {
|
||||
const r = await api.restoreDatabase(serverId, restoring.id, c.name);
|
||||
setRestoring(null);
|
||||
return `Restored ${r.tables_restored} tables. The previous data is kept as ${r.previous_schema}.`;
|
||||
return t("bk.dbRestored", { n: r.tables_restored, schema: r.previous_schema });
|
||||
});
|
||||
}}
|
||||
>
|
||||
Restore {DB_LABEL[c.name]?.toLowerCase() ?? c.name}
|
||||
{t("bk.restoreDb", { db: dbLabel(c.name) })}
|
||||
</Button>
|
||||
),
|
||||
)}
|
||||
<Button size="sm" variant="ghost" onClick={() => setRestoring(null)}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="mt-6 divide-y divide-line rounded-card border border-line bg-card/60">
|
||||
{points === null && <p className="p-4 text-muted">Loading…</p>}
|
||||
{points?.length === 0 && <p className="p-4 text-muted">Your backups will appear here.</p>}
|
||||
{points === null && <p className="p-4 text-muted">{t("bk.loading")}</p>}
|
||||
{points?.length === 0 && <p className="p-4 text-muted">{t("bk.empty")}</p>}
|
||||
{points?.map((p) => (
|
||||
<div key={p.id} className="flex items-center justify-between gap-4 p-4">
|
||||
<div>
|
||||
<p className="font-medium">{p.label && p.trigger === "manual" && p.label !== "e2e" ? p.label : (TRIGGER[p.trigger] ?? p.trigger)}</p>
|
||||
<p className="font-medium">{p.label && p.trigger === "manual" && p.label !== "e2e" ? p.label : trigger(p.trigger)}</p>
|
||||
<p className="text-sm text-muted">
|
||||
{when(p.created_at)} · {KIND[p.kind]} · {size(p.components.reduce((a, c) => a + c.bytes, 0))}
|
||||
{w(p.created_at)} · {t(`bk.kind.${p.kind}` as Key)} · {size(p.components.reduce((a, c) => a + c.bytes, 0))}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex shrink-0 gap-1">
|
||||
<Button size="sm" disabled={!!busy} onClick={() => setRestoring(p)}>
|
||||
Restore
|
||||
{t("bk.restore")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -196,25 +188,25 @@ export function BackupsPage({ serverId }: { serverId: string }) {
|
||||
onClick={() =>
|
||||
void run("verify", async () => {
|
||||
const v = await api.verifyBackup(serverId, p.id);
|
||||
return v.ok ? "This backup is intact." : `Problem found: ${v.problems.join("; ")}`;
|
||||
return v.ok ? t("bk.intact") : t("bk.problem", { details: v.problems.join("; ") });
|
||||
})
|
||||
}
|
||||
>
|
||||
Verify
|
||||
{t("bk.verify")}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
disabled={!!busy}
|
||||
onClick={() => {
|
||||
if (window.confirm("Delete this backup? Only this backup is removed; your server is not affected.")) {
|
||||
if (window.confirm(t("bk.confirmDelete"))) {
|
||||
void run("delete", async () => {
|
||||
await api.deleteBackup(serverId, p.id);
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
{t("bk.delete")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user