feat(backup): take Tinyforge DB snapshot before every deploy
Adds an opt-in "auto_backup_before_deploy" setting that triggers a "pre-deploy" backup at the start of every project deploy via the deploy pipeline (covers both the async HTTP path and the sync poller/webhook path). Failures are logged to the deploy log but do not abort — missing a backup is preferable to refusing to ship a fix. - store: settings.auto_backup_before_deploy column + scan/update wiring - backup: accept "pre-deploy" as a valid backup_type - deployer: small PreDeployBackuper interface, hooked into runDeploy right after settings load and before any state-mutating work - api: settings request/response surface the new flag - web: ToggleSwitch on the backup settings page; "Pre-deploy" badge variant in the backup list (badge-warning so it stands out) - i18n: en/ru strings for the toggle, help text, and badge label
This commit is contained in:
@@ -553,6 +553,9 @@
|
||||
"restoreFailed": "Failed to restore backup",
|
||||
"typeManual": "Manual",
|
||||
"typeAuto": "Auto",
|
||||
"typePreDeploy": "Pre-deploy",
|
||||
"preDeploy": "Backup before every deploy",
|
||||
"preDeployHelp": "Take a Tinyforge DB snapshot at the start of every project deploy. Independent of the periodic schedule above; restorable from this list under the \"Pre-deploy\" type.",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"saved": "Backup settings saved",
|
||||
|
||||
@@ -553,6 +553,9 @@
|
||||
"restoreFailed": "Не удалось восстановить резервную копию",
|
||||
"typeManual": "Ручная",
|
||||
"typeAuto": "Авто",
|
||||
"typePreDeploy": "Перед деплоем",
|
||||
"preDeploy": "Снимок перед каждым деплоем",
|
||||
"preDeployHelp": "Создавать снимок БД Tinyforge в начале каждого деплоя проекта. Независимо от периодического расписания выше; восстанавливается из списка ниже по типу «Перед деплоем».",
|
||||
"save": "Сохранить",
|
||||
"saving": "Сохранение...",
|
||||
"saved": "Настройки копирования сохранены",
|
||||
|
||||
@@ -134,6 +134,7 @@ export interface Settings {
|
||||
backup_enabled: boolean;
|
||||
backup_interval_hours: number;
|
||||
backup_retention_count: number;
|
||||
auto_backup_before_deploy: boolean;
|
||||
stats_interval_seconds: number;
|
||||
stats_retention_hours: number;
|
||||
updated_at: string;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
let backupEnabled = $state(false);
|
||||
let backupIntervalHours = $state('24');
|
||||
let backupRetentionCount = $state('10');
|
||||
let autoBackupBeforeDeploy = $state(false);
|
||||
let backups = $state<BackupInfo[]>([]);
|
||||
|
||||
let confirmDeleteId = $state('');
|
||||
@@ -38,6 +39,7 @@
|
||||
backupEnabled = settings.backup_enabled ?? false;
|
||||
backupIntervalHours = String(settings.backup_interval_hours ?? 24);
|
||||
backupRetentionCount = String(settings.backup_retention_count ?? 10);
|
||||
autoBackupBeforeDeploy = settings.auto_backup_before_deploy ?? false;
|
||||
backups = backupList ?? [];
|
||||
} catch (err) {
|
||||
toasts.error(err instanceof Error ? err.message : 'Failed to load backup settings');
|
||||
@@ -53,7 +55,8 @@
|
||||
await updateSettings({
|
||||
backup_enabled: backupEnabled,
|
||||
backup_interval_hours: Math.max(1, parseInt(backupIntervalHours, 10) || 24),
|
||||
backup_retention_count: Math.max(1, parseInt(backupRetentionCount, 10) || 10)
|
||||
backup_retention_count: Math.max(1, parseInt(backupRetentionCount, 10) || 10),
|
||||
auto_backup_before_deploy: autoBackupBeforeDeploy
|
||||
});
|
||||
toasts.success($t('settingsBackup.saved'));
|
||||
} catch (err) {
|
||||
@@ -181,6 +184,15 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Pre-deploy backup toggle: independent of the periodic auto-backup. -->
|
||||
<div class="flex items-center gap-3">
|
||||
<ToggleSwitch bind:checked={autoBackupBeforeDeploy} label={$t('settingsBackup.preDeploy')} />
|
||||
<div>
|
||||
<span class="text-sm font-medium text-[var(--text-primary)]">{$t('settingsBackup.preDeploy')}</span>
|
||||
<p class="text-xs text-[var(--text-tertiary)]">{$t('settingsBackup.preDeployHelp')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<button onclick={handleSave} disabled={saving}
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-[var(--color-brand-600)] px-4 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-150 hover:bg-[var(--color-brand-700)] disabled:opacity-50 active:animate-press">
|
||||
@@ -233,8 +245,12 @@
|
||||
<td class="px-4 py-3 text-[var(--text-secondary)]">{formatSize(backup.size_bytes)}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium
|
||||
{backup.backup_type === 'auto' ? 'badge-info' : 'badge-success'}">
|
||||
{backup.backup_type === 'auto' ? $t('settingsBackup.typeAuto') : $t('settingsBackup.typeManual')}
|
||||
{backup.backup_type === 'auto' ? 'badge-info' : backup.backup_type === 'pre-deploy' ? 'badge-warning' : 'badge-success'}">
|
||||
{backup.backup_type === 'auto'
|
||||
? $t('settingsBackup.typeAuto')
|
||||
: backup.backup_type === 'pre-deploy'
|
||||
? $t('settingsBackup.typePreDeploy')
|
||||
: $t('settingsBackup.typeManual')}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-[var(--text-secondary)]">{$fmt.dateTime(toUtcIso(backup.created_at))}</td>
|
||||
|
||||
Reference in New Issue
Block a user