feat(ui): CheckpointsTable emits grid fragment + locale sub-line
This commit is contained in:
@@ -38,91 +38,96 @@ export function CheckpointsTable({
|
||||
const hidden = checkpoints.length - visible.length;
|
||||
|
||||
return (
|
||||
<div className={styles.checkpointsSection}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.checkpointsHeader}
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-expanded={open}
|
||||
>
|
||||
<span className={styles.checkpointsChevron}>{open ? '\u25BE' : '\u25B8'}</span>
|
||||
<span>Checkpoints</span>
|
||||
{' '}
|
||||
<span className={styles.checkpointsCount}>({checkpoints.length})</span>
|
||||
</button>
|
||||
<>
|
||||
<span className={styles.configLabel}>Checkpoints</span>
|
||||
<div className={styles.checkpointsTriggerCell}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.checkpointsTrigger}
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-expanded={open}
|
||||
>
|
||||
<span className={styles.checkpointsChevron}>{open ? '\u25BE' : '\u25B8'}</span>
|
||||
{open ? 'Collapse' : 'Expand'} ({checkpoints.length})
|
||||
</button>
|
||||
</div>
|
||||
{open && (
|
||||
<div className={styles.checkpointsTable}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>JAR</th>
|
||||
<th>Deployed by</th>
|
||||
<th>Deployed</th>
|
||||
<th>Strategy</th>
|
||||
<th>Outcome</th>
|
||||
<th aria-label="open"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{visible.map((d) => {
|
||||
const v = versionMap.get(d.appVersionId);
|
||||
const archived = !v;
|
||||
const strategyLabel =
|
||||
d.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling';
|
||||
return (
|
||||
<tr
|
||||
key={d.id}
|
||||
className={archived ? styles.checkpointArchived : undefined}
|
||||
onClick={() => onSelect(d.id)}
|
||||
>
|
||||
<td>
|
||||
<Badge label={v ? `v${v.version}` : '?'} color="auto" />
|
||||
</td>
|
||||
<td className={styles.jarCell}>
|
||||
{v ? (
|
||||
<span className={styles.jarName}>{v.jarFilename}</span>
|
||||
) : (
|
||||
<>
|
||||
<span className={styles.jarStrike}>JAR pruned</span>
|
||||
<div className={styles.archivedHint}>archived — JAR pruned</div>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{d.createdBy ?? <span className={styles.muted}>—</span>}
|
||||
</td>
|
||||
<td>
|
||||
{d.deployedAt && timeAgo(d.deployedAt)}
|
||||
<div className={styles.isoSubline}>{d.deployedAt}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={styles.strategyPill}>{strategyLabel}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className={`${styles.outcomePill} ${styles[`outcome-${d.status}` as keyof typeof styles] || ''}`}
|
||||
>
|
||||
{d.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className={styles.chevron}>›</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{hidden > 0 && !expanded && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.showOlderBtn}
|
||||
onClick={() => setExpanded(true)}
|
||||
>
|
||||
Show older ({hidden}) — archived, postmortem only
|
||||
</button>
|
||||
)}
|
||||
<div className={styles.checkpointsTableFullRow}>
|
||||
<div className={styles.checkpointsTable}>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Version</th>
|
||||
<th>JAR</th>
|
||||
<th>Deployed by</th>
|
||||
<th>Deployed</th>
|
||||
<th>Strategy</th>
|
||||
<th>Outcome</th>
|
||||
<th aria-label="open"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{visible.map((d) => {
|
||||
const v = versionMap.get(d.appVersionId);
|
||||
const archived = !v;
|
||||
const strategyLabel =
|
||||
d.deploymentStrategy === 'BLUE_GREEN' ? 'blue/green' : 'rolling';
|
||||
return (
|
||||
<tr
|
||||
key={d.id}
|
||||
className={archived ? styles.checkpointArchived : undefined}
|
||||
onClick={() => onSelect(d.id)}
|
||||
>
|
||||
<td>
|
||||
<Badge label={v ? `v${v.version}` : '?'} color="auto" />
|
||||
</td>
|
||||
<td className={styles.jarCell}>
|
||||
{v ? (
|
||||
<span className={styles.jarName}>{v.jarFilename}</span>
|
||||
) : (
|
||||
<>
|
||||
<span className={styles.jarStrike}>JAR pruned</span>
|
||||
<div className={styles.archivedHint}>archived — JAR pruned</div>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{d.createdBy ?? <span className={styles.muted}>—</span>}
|
||||
</td>
|
||||
<td>
|
||||
{d.deployedAt && timeAgo(d.deployedAt)}
|
||||
<div className={styles.isoSubline}>
|
||||
{d.deployedAt && new Date(d.deployedAt).toLocaleString()}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span className={styles.strategyPill}>{strategyLabel}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
className={`${styles.outcomePill} ${styles[`outcome-${d.status}` as keyof typeof styles] || ''}`}
|
||||
>
|
||||
{d.status}
|
||||
</span>
|
||||
</td>
|
||||
<td className={styles.chevron}>›</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
{hidden > 0 && !expanded && (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.showOlderBtn}
|
||||
onClick={() => setExpanded(true)}
|
||||
>
|
||||
Show older ({hidden}) — archived, postmortem only
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user