feat(ui): wire CheckpointsTable + Drawer into IdentitySection (delete old Checkpoints)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-23 13:46:31 +02:00
parent 25d2a3014a
commit 571f85cd0f
2 changed files with 36 additions and 74 deletions

View File

@@ -1,67 +0,0 @@
import { useState } from 'react';
import { Button, Badge } from '@cameleer/design-system';
import type { Deployment, AppVersion } from '../../../api/queries/admin/apps';
import { timeAgo } from '../../../utils/format-utils';
import styles from './AppDeploymentPage.module.css';
interface CheckpointsProps {
deployments: Deployment[];
versions: AppVersion[];
currentDeploymentId: string | null;
onRestore: (deploymentId: string) => void;
}
export function Checkpoints({ deployments, versions, currentDeploymentId, onRestore }: CheckpointsProps) {
const [open, setOpen] = useState(false);
const versionMap = new Map(versions.map((v) => [v.id, v]));
// Any deployment that captured a snapshot is restorable — that covers RUNNING,
// DEGRADED, and STOPPED (blue/green swap previous, user-stopped). Exclude the
// currently-running one and anything without a snapshot (FAILED, STARTING).
const checkpoints = deployments
.filter((d) => d.deployedConfigSnapshot && d.id !== currentDeploymentId)
.sort((a, b) => (b.deployedAt ?? '').localeCompare(a.deployedAt ?? ''));
return (
<div className={styles.checkpointsRow}>
<button
type="button"
className={styles.disclosureToggle}
onClick={() => setOpen(!open)}
>
{open ? '▼' : '▶'} Checkpoints ({checkpoints.length})
</button>
{open && (
<div className={styles.checkpointList}>
{checkpoints.length === 0 && (
<div className={styles.checkpointEmpty}>No past deployments yet.</div>
)}
{checkpoints.map((d) => {
const v = versionMap.get(d.appVersionId);
const jarAvailable = !!v;
return (
<div key={d.id} className={styles.checkpointRow}>
<Badge label={v ? `v${v.version}` : '?'} color="auto" />
<span className={styles.checkpointMeta}>
{d.deployedAt ? timeAgo(d.deployedAt) : '—'}
</span>
{!jarAvailable && (
<span className={styles.checkpointArchived}>archived, JAR unavailable</span>
)}
<Button
size="sm"
variant="ghost"
disabled={!jarAvailable}
title={!jarAvailable ? 'JAR was pruned by the environment retention policy' : undefined}
onClick={() => onRestore(d.id)}
>
Restore
</Button>
</div>
);
})}
</div>
)}
</div>
);
}

View File

@@ -20,7 +20,8 @@ import type { Deployment } from '../../../api/queries/admin/apps';
import { useApplicationConfig, useUpdateApplicationConfig } from '../../../api/queries/commands';
import { PageLoader } from '../../../components/PageLoader';
import { IdentitySection } from './IdentitySection';
import { Checkpoints } from './Checkpoints';
import { CheckpointsTable } from './CheckpointsTable';
import { CheckpointDetailDrawer } from './CheckpointDetailDrawer';
import { MonitoringTab } from './ConfigTabs/MonitoringTab';
import { ResourcesTab } from './ConfigTabs/ResourcesTab';
import { VariablesTab } from './ConfigTabs/VariablesTab';
@@ -90,6 +91,7 @@ export default function AppDeploymentPage() {
const [tab, setTab] = useState<TabKey>('monitoring');
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [stopTarget, setStopTarget] = useState<string | null>(null);
const [selectedCheckpointId, setSelectedCheckpointId] = useState<string | null>(null);
const lastDerivedRef = useRef<string>('');
// Initialize name from app when it loads
@@ -348,6 +350,15 @@ export default function AppDeploymentPage() {
return true; // redeploy always enabled
})();
// Checkpoint drawer derivations
const jarRetentionCount = env?.jarRetentionCount ?? null;
const selectedDep = selectedCheckpointId
? deployments.find((d) => d.id === selectedCheckpointId) ?? null
: null;
const selectedDepVersion = selectedDep
? versions.find((v) => v.id === selectedDep.appVersionId)
: undefined;
// ── Loading guard ──────────────────────────────────────────────────
if (envLoading || appsLoading) return <PageLoader />;
if (!env) return <div>Select an environment first.</div>;
@@ -435,12 +446,30 @@ export default function AppDeploymentPage() {
deploying={deploymentInProgress}
>
{app && (
<Checkpoints
deployments={deployments}
versions={versions}
currentDeploymentId={currentDeployment?.id ?? null}
onRestore={handleRestore}
/>
<>
<CheckpointsTable
deployments={deployments}
versions={versions}
currentDeploymentId={currentDeployment?.id ?? null}
jarRetentionCount={jarRetentionCount}
onSelect={setSelectedCheckpointId}
/>
{selectedDep && (
<CheckpointDetailDrawer
open
onClose={() => setSelectedCheckpointId(null)}
deployment={selectedDep}
version={selectedDepVersion}
appSlug={app.slug}
envSlug={selectedEnv ?? ''}
currentForm={form}
onRestore={(deploymentId) => {
handleRestore(deploymentId);
setSelectedCheckpointId(null);
}}
/>
)}
</>
)}
</IdentitySection>