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:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
<>
|
||||
<CheckpointsTable
|
||||
deployments={deployments}
|
||||
versions={versions}
|
||||
currentDeploymentId={currentDeployment?.id ?? null}
|
||||
onRestore={handleRestore}
|
||||
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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user