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;