import { useTranslation } from "react-i18next"; import type { BackupListResponse } from "../../../api"; import { SettingsToggleRow } from "../SettingsToggleRow"; import { SettingsNumberRow } from "../SettingsNumberRow"; import { SettingsTextRow } from "../SettingsTextRow"; import type { BackupSettingsMigrationCandidate, BackupSettingsMigrationConflict } from "@fusion/core"; import type { SectionBaseProps } from "./context"; import { LoadingSpinner } from "../../LoadingSpinner"; export interface DatabaseBackupsSectionProps extends SectionBaseProps { backupInfo: BackupListResponse | null; backupLoading: boolean; onBackupNow: () => void; } /* FNXC:SettingsBackups 2026-07-16-14:35: The global PostgreSQL cluster contains every project, so database controls and the one shared backup destination live in this global section. Memory snapshots remain in the project section. */ export function DatabaseBackupsSection({ form, setForm, backupInfo, backupLoading, onBackupNow }: DatabaseBackupsSectionProps) { const { t } = useTranslation("app"); const conflicts = form.backupSettingsMigrationConflicts ?? []; const chooseCandidate = (conflict: BackupSettingsMigrationConflict, candidate: BackupSettingsMigrationCandidate) => { setForm((current) => ({ ...current, [conflict.key]: candidate.value, backupSettingsMigrationConflicts: conflicts.filter((entry) => entry.key !== conflict.key), })); }; return (<> {conflicts.length > 0 &&
{t("settings.backups.migrationConflict", "Choose database backup settings")}

{t("settings.backups.migrationConflictHelp", "Existing projects used different backup settings. Choose the value to use globally.")}

{conflicts.map((conflict) =>
{conflict.key} {conflict.candidates.map((candidate, index) => )}
)}
}

{t("settings.backups.databaseBackups", "Database Backups")}

{t("settings.database.advanced", "Advanced database settings")} setForm((f) => ({ ...f, embeddedPostgresMaxConnections: v ?? undefined }))} error={form.embeddedPostgresMaxConnections !== undefined && (form.embeddedPostgresMaxConnections < 32 || form.embeddedPostgresMaxConnections > 2000) ? t("settings.database.embeddedConnectionCapError", "Enter a value between 32 and 2,000.") : undefined} />
setForm((f) => ({ ...f, autoBackupEnabled: v === true }))} /> setForm((f) => ({ ...f, autoBackupSchedule: v ?? "" }))} error={form.autoBackupSchedule && !/^[\s\d*,/-]+$/.test(form.autoBackupSchedule) ? t("settings.backups.invalidCronExpressionFormat", "Invalid cron expression format") : undefined} /> setForm((f) => ({ ...f, autoBackupRetention: v ?? undefined }))} error={form.autoBackupRetention !== undefined && (form.autoBackupRetention < 1 || form.autoBackupRetention > 100) ? t("settings.backups.mustBeBetween1And100", "Must be between 1 and 100") : undefined} /> setForm((f) => ({ ...f, autoBackupDir: v ?? "" }))} error={form.autoBackupDir && form.autoBackupDir.includes("..") ? t("settings.backups.pathCannotContainParentDirectoryTraversal", "Path cannot contain parent directory traversal (..)") : undefined} /> {backupLoading ? (
) : backupInfo ? (
{backupInfo.count} {t("settings.backups.backups", "backups")}
{backupInfo.totalSize > 1024 * 1024 ? `${(backupInfo.totalSize / (1024 * 1024)).toFixed(1)} MB` : `${(backupInfo.totalSize / 1024).toFixed(1)} KB`} {t("settings.backups.totalSize", "total size")}
{backupInfo.backups.length > 0 && (
{t("settings.backups.view", "View ")}{backupInfo.backups.length}{t("settings.backups.backupS", " backup(s)")}
    {backupInfo.backups.slice(0, 10).map((backup) => (
  • {backup.filename} {backup.size > 1024 * 1024 ? `${(backup.size / (1024 * 1024)).toFixed(1)} MB` : `${(backup.size / 1024).toFixed(1)} KB`}
  • ))} {backupInfo.backups.length > 10 && (
  • {t("settings.backups.and", "...and ")}{backupInfo.backups.length - 10}{t("settings.backups.more", " more")}
  • )}
)}
) : null}
); } export default DatabaseBackupsSection;