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 { useApplicationConfig, useUpdateApplicationConfig } from '../../../api/queries/commands';
|
||||||
import { PageLoader } from '../../../components/PageLoader';
|
import { PageLoader } from '../../../components/PageLoader';
|
||||||
import { IdentitySection } from './IdentitySection';
|
import { IdentitySection } from './IdentitySection';
|
||||||
import { Checkpoints } from './Checkpoints';
|
import { CheckpointsTable } from './CheckpointsTable';
|
||||||
|
import { CheckpointDetailDrawer } from './CheckpointDetailDrawer';
|
||||||
import { MonitoringTab } from './ConfigTabs/MonitoringTab';
|
import { MonitoringTab } from './ConfigTabs/MonitoringTab';
|
||||||
import { ResourcesTab } from './ConfigTabs/ResourcesTab';
|
import { ResourcesTab } from './ConfigTabs/ResourcesTab';
|
||||||
import { VariablesTab } from './ConfigTabs/VariablesTab';
|
import { VariablesTab } from './ConfigTabs/VariablesTab';
|
||||||
@@ -90,6 +91,7 @@ export default function AppDeploymentPage() {
|
|||||||
const [tab, setTab] = useState<TabKey>('monitoring');
|
const [tab, setTab] = useState<TabKey>('monitoring');
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
const [deleteConfirm, setDeleteConfirm] = useState(false);
|
||||||
const [stopTarget, setStopTarget] = useState<string | null>(null);
|
const [stopTarget, setStopTarget] = useState<string | null>(null);
|
||||||
|
const [selectedCheckpointId, setSelectedCheckpointId] = useState<string | null>(null);
|
||||||
const lastDerivedRef = useRef<string>('');
|
const lastDerivedRef = useRef<string>('');
|
||||||
|
|
||||||
// Initialize name from app when it loads
|
// Initialize name from app when it loads
|
||||||
@@ -348,6 +350,15 @@ export default function AppDeploymentPage() {
|
|||||||
return true; // redeploy always enabled
|
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 ──────────────────────────────────────────────────
|
// ── Loading guard ──────────────────────────────────────────────────
|
||||||
if (envLoading || appsLoading) return <PageLoader />;
|
if (envLoading || appsLoading) return <PageLoader />;
|
||||||
if (!env) return <div>Select an environment first.</div>;
|
if (!env) return <div>Select an environment first.</div>;
|
||||||
@@ -435,12 +446,30 @@ export default function AppDeploymentPage() {
|
|||||||
deploying={deploymentInProgress}
|
deploying={deploymentInProgress}
|
||||||
>
|
>
|
||||||
{app && (
|
{app && (
|
||||||
<Checkpoints
|
<>
|
||||||
deployments={deployments}
|
<CheckpointsTable
|
||||||
versions={versions}
|
deployments={deployments}
|
||||||
currentDeploymentId={currentDeployment?.id ?? null}
|
versions={versions}
|
||||||
onRestore={handleRestore}
|
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>
|
</IdentitySection>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user