diff --git a/backend/src/services/restoreService.js b/backend/src/services/restoreService.js index 0ed1cbe6..7096d73b 100644 --- a/backend/src/services/restoreService.js +++ b/backend/src/services/restoreService.js @@ -1000,11 +1000,27 @@ END $$;` await reinitPool(); this.log('info', 'Knex pool re-initialized'); - // Run migrations to ensure schema is up to date. Use the - // module-level `db` export, which is the Proxy that now points - // at the freshly-initialized pool. - this.log('info', 'Running database migrations...'); - await db.migrate.latest(); + // NOTE: we deliberately do NOT call `db.migrate.latest()` here. + // + // The picpeak migrations directory contains `helpers.js` (a + // shared helper module, not a migration), plus `core/` and + // `legacy/` subdirectories. Knex's built-in migrator scans the + // top-level directory and rejects any file without `up`/`down` + // exports — so `db.migrate.latest()` throws + // Invalid migration: helpers.js must have both an up and down function + // every time it runs in this codebase. The production code path + // uses `npm run migrate:safe` (run-migrations-safe.js) which + // knows to skip helpers.js + walks core/ explicitly. + // + // For restore: the dump we just loaded already contains the + // schema state of whatever migrations had been applied at + // backup time. If the running image has NEWER migrations that + // need to run on top of the restored DB, those will be applied + // on the NEXT container start by wait-for-db.sh + the safe + // runner. That's a one-restart penalty in the unusual case of + // restoring from a backup older than the current image, and + // matches what picpeak does on every other boot already. + this.log('info', 'Skipping in-process migrate (deferred to next boot via safe runner)'); // Replay the snapshotted operator-meta settings on top of the // restored DB. UPSERT by setting_key — if the backup had the diff --git a/frontend/src/components/admin/BackupCoverageCard.tsx b/frontend/src/components/admin/BackupCoverageCard.tsx index 511d8cf1..3a6ffdba 100644 --- a/frontend/src/components/admin/BackupCoverageCard.tsx +++ b/frontend/src/components/admin/BackupCoverageCard.tsx @@ -14,7 +14,8 @@ import { Loader2, } from 'lucide-react'; import { useQuery } from '@tanstack/react-query'; -import { format } from 'date-fns'; +// Locale-aware formatters per [[feedback_respect_general_format_settings]]. +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; import { Card, Button } from '../common'; import { @@ -41,6 +42,7 @@ import { */ export const BackupCoverageCard: React.FC = () => { const { t } = useTranslation(); + const { formatDateTime } = useLocalizedDate(); const { data, isLoading, isError, error, refetch, isFetching } = useQuery({ queryKey: ['backup-coverage'], queryFn: () => adminService.getBackupCoverage(), @@ -75,7 +77,7 @@ export const BackupCoverageCard: React.FC = () => {
{t('backup.coverage.generatedAt', 'Coverage generated: {{when}}', { - when: format(new Date(data.generatedAt), 'yyyy-MM-dd HH:mm:ss'), + when: formatDateTime(new Date(data.generatedAt)), })}
> @@ -164,6 +166,7 @@ const DatabaseStatusCard: React.FC<{ database: BackupCoverageReport['database']; }> = ({ database }) => { const { t } = useTranslation(); + const { formatDateTime } = useLocalizedDate(); const isInline = database.mode === 'inline'; const tone: Tone = database.ok ? 'green' : 'red'; const dumpAge = database.lastDumpAgeMs !== null @@ -194,7 +197,7 @@ const DatabaseStatusCard: React.FC<{ <>- {format(new Date(backup.created_at), 'PPp')} + {formatDateTime(new Date(backup.created_at))}
diff --git a/frontend/src/components/admin/BackupHistory.jsx b/frontend/src/components/admin/BackupHistory.jsx index 1bde610a..08cc1aab 100644 --- a/frontend/src/components/admin/BackupHistory.jsx +++ b/frontend/src/components/admin/BackupHistory.jsx @@ -20,11 +20,17 @@ import { RefreshCw, Loader2 } from 'lucide-react'; -import { format, formatDistanceToNow } from 'date-fns'; import { toast } from 'react-toastify'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button, Card, Input, Loading } from '../common'; import { api } from '../../config/api'; +// Per [[feedback_respect_general_format_settings]]: route every displayed +// date/time through useLocalizedDate so the admin's general_date_format + +// general_time_format settings apply uniformly. Previously the backup +// History pane used raw date-fns format() with hard-coded 'p' (12-hour +// AM/PM) and 'PPP' (US-locale long date), which ignored the settings — +// Ralf 2026-05-31 flagged "11:25 PM" on a 24h-configured install. +import { useLocalizedDate } from '../../hooks/useLocalizedDate'; const statusIcons = { completed: { icon: CheckCircle, color: 'text-green-500' }, @@ -48,6 +54,12 @@ export const BackupHistory = () => { const [filterStatus, setFilterStatus] = useState('all'); const [currentPage, setCurrentPage] = useState(1); const queryClient = useQueryClient(); + // Locale-aware formatters that respect admin's general_date_format + + // general_time_format settings. See useLocalizedDate.ts for the full + // contract; formatTime gives "HH:mm" (24h) or "h:mm a" (12h) based on + // the setting, format(date) honors general_date_format, and + // formatDistanceToNow returns "2 minutes ago" in the admin's i18n locale. + const { format, formatTime, formatDistanceToNow } = useLocalizedDate(); // Fetch backup history const { data, isLoading, refetch } = useQuery({ @@ -90,7 +102,7 @@ export const BackupHistory = () => { }; const handleDelete = (backup) => { - if (window.confirm(`Are you sure you want to delete this backup from ${format(new Date(backup.created_at), 'PPP')}?`)) { + if (window.confirm(`Are you sure you want to delete this backup from ${format(new Date(backup.created_at))}?`)) { deleteMutation.mutate(backup.id); } }; @@ -200,10 +212,10 @@ export const BackupHistory = () => {- {format(new Date(backup.created_at), 'PPP')} + {format(new Date(backup.created_at))}
- {format(new Date(backup.created_at), 'p')} • {formatDistanceToNow(new Date(backup.created_at), { addSuffix: true })} + {formatTime(new Date(backup.created_at))} • {formatDistanceToNow(new Date(backup.created_at), { addSuffix: true })}
{t('backup.integrity.scannedAt', 'Last checked: {{when}}', { - when: format(new Date(report.scannedAt), 'yyyy-MM-dd HH:mm:ss'), + when: formatDateTime(new Date(report.scannedAt)), })}