diff --git a/.gitignore b/.gitignore index 0726b2d7..e71594b5 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ mobile/ios/App/ForgeApp/Generated/ todos/*.md !todos/.gitkeep forge.code-workspace +.worktrees diff --git a/apps/cms/src/admin/pages/SystemStatus.tsx b/apps/cms/src/admin/pages/SystemStatus.tsx index 5abed233..ef78cb03 100644 --- a/apps/cms/src/admin/pages/SystemStatus.tsx +++ b/apps/cms/src/admin/pages/SystemStatus.tsx @@ -26,6 +26,11 @@ type PhaseResult = { errors: number } +type PhaseWatermark = { + phase: string + lastSyncedAt: string +} + type SyncStatus = { inProgress: boolean lastRun: string | null @@ -39,6 +44,15 @@ type SyncStatus = { duration?: number error?: string } | null + isProduction?: boolean + // Persisted data (available after restart) + persistedLastRun?: string | null + phaseWatermarks?: PhaseWatermark[] +} + +type LocalImport = { + lastImportedAt: string | null + snapshotKey: string | null } type SnapshotStatus = { @@ -50,6 +64,16 @@ type SnapshotStatus = { sizeBytes?: number error?: string } | null + isProduction?: boolean + // Persisted data (available after restart) + persistedLastRun?: string | null + latestSnapshot?: { + key: string + lastModified: string + sizeBytes: number + } | null + // Local import data (non-production only) + localImport?: LocalImport | null } function formatBytes(bytes: number): string { @@ -144,6 +168,56 @@ function PhaseResultsTable({ phases }: { phases: PhaseResult[] }) { ) } +function PhaseWatermarksTable({ + watermarks, +}: { + watermarks: PhaseWatermark[] +}) { + if (watermarks.length === 0) return null + + return ( + + + Phase watermarks (from database) + + + + + {["Phase", "Last Synced"].map((h) => ( + + ))} + + + + {watermarks.map((w) => ( + + + + + ))} + +
+ + {h} + +
+ {w.phase} + + + {formatTime(w.lastSyncedAt)} + +
+
+ ) +} + function SyncCard({ status, onTrigger, @@ -155,6 +229,10 @@ function SyncCard({ }) { const inProgress = status?.inProgress ?? false const error = status?.lastResult?.error + const isProduction = status?.isProduction ?? false + + // Use in-memory lastRun, or fall back to persisted watermark time + const effectiveLastRun = status?.lastRun ?? status?.persistedLastRun ?? null return ( @@ -165,12 +243,22 @@ function SyncCard({ - {status?.lastRun && ( + {!isProduction && ( + + + Core sync is disabled outside production. Use{" "} + pnpm data-import to restore a snapshot locally. + + + )} + + {effectiveLastRun && ( - Last run: {formatTime(status.lastRun)} - {status.lastResult?.duration && + Last run: {formatTime(effectiveLastRun)} + {status?.lastResult?.duration && ` (${formatDuration(status.lastResult.duration)})`} + {!status?.lastRun && status?.persistedLastRun && " (from database)"} )} @@ -201,6 +289,13 @@ function SyncCard({ )} + {!inProgress && + !status?.lastResult?.phases && + status?.phaseWatermarks && + status.phaseWatermarks.length > 0 && ( + + )} + {error && ( @@ -209,16 +304,18 @@ function SyncCard({ )} - - - + {isProduction && ( + + + + )} ) } @@ -236,6 +333,16 @@ function SnapshotCard({ }) { const inProgress = status?.inProgress ?? false const error = status?.lastResult?.error + const isProduction = status?.isProduction ?? false + + // Use in-memory lastRun, or fall back to persisted S3 metadata + const effectiveLastRun = status?.lastRun ?? status?.persistedLastRun ?? null + const effectiveSize = + status?.lastResult?.sizeBytes ?? status?.latestSnapshot?.sizeBytes ?? null + const effectiveKey = + status?.lastResult?.key ?? status?.latestSnapshot?.key ?? null + + const localImport = status?.localImport return ( @@ -246,24 +353,66 @@ function SnapshotCard({ - {status?.lastRun && ( + {!isProduction && ( + + + Snapshot creation is disabled outside production. Use{" "} + pnpm data-import to download a snapshot locally. + + + )} + + {isProduction && effectiveLastRun && ( - Last snapshot: {formatTime(status.lastRun)} - {status.lastResult?.duration && + Last snapshot: {formatTime(effectiveLastRun)} + {status?.lastResult?.duration && ` (${formatDuration(status.lastResult.duration)})`} + {!status?.lastRun && status?.persistedLastRun && " (from S3)"} + + + )} + + {isProduction && effectiveKey && ( + + + Key: {effectiveKey} )} - {status?.lastResult?.sizeBytes && ( + {effectiveSize && ( - Size: {formatBytes(status.lastResult.sizeBytes)} + Size: {formatBytes(effectiveSize)} )} + {!isProduction && localImport && ( + + {localImport.lastImportedAt ? ( + <> + + Last local import: {formatTime(localImport.lastImportedAt)} + + {localImport.snapshotKey && ( + + + Snapshot: {localImport.snapshotKey} + + + )} + + ) : ( + + No local import recorded. Run pnpm data-import to + restore production data. + + )} + + )} + {inProgress && ( @@ -282,15 +431,17 @@ function SnapshotCard({ )} - - {downloadUrl && !inProgress && ( + {isProduction && ( + + )} + {isProduction && downloadUrl && !inProgress && (