feat: live-refresh stack detail container and health state (#1712)

* feat: live-refresh stack detail container and health state

Keep the open stack's container cards in sync with Docker via state-invalidate events and a visibility-aware poll, without reloading compose, env, or logs.

* fix: remove unused _ms parameter from visibilityInterval mock

Fixes the @typescript-eslint/no-unused-vars ESLint error in CI lint job.

* fix: stop stack detail live-refresh when leaving the editor

Gate poll and invalidate handling on editor visibility, refresh the
current selection after a mid-flight stack switch, and skip starting
visibilityInterval when the tab is already hidden.

* fix: avoid return in finally for stack detail live-refresh

Satisfy no-unsafe-finally by gating the trailing refresh with a positive
condition instead of early returns inside the finally block.
This commit is contained in:
Anso
2026-07-28 09:24:18 -04:00
committed by GitHub
parent 681ecc7047
commit 60092dd462
11 changed files with 946 additions and 16 deletions
@@ -334,6 +334,13 @@ export interface ContainersHealthProps {
containersLoadStatus?: 'idle' | 'loading' | 'success' | 'error';
containersLoadError?: string | null;
onRetryContainersLoad?: () => void;
/**
* Soft live-refresh failures exhausted. Shown only when container cards are
* visible (containersLoadStatus === 'success'). When status is 'error', the
* existing error card Retry is sufficient and this chip is suppressed.
*/
syncStale?: boolean;
onRetrySync?: () => void;
}
// Per-container health strip: status badge, uptime, ports, and CPU/Mem/Net
@@ -357,6 +364,8 @@ export function ContainersHealth({
containersLoadStatus = 'success',
containersLoadError = null,
onRetryContainersLoad,
syncStale = false,
onRetrySync,
}: ContainersHealthProps) {
// Multi-service only: a single-service stack keeps the existing flat layout
// untouched, including its per-container Start/Stop/Restart kebab.
@@ -723,6 +732,17 @@ export function ContainersHealth({
</TooltipProvider>
</div>
)}
{syncStale && onRetrySync && (
<div className="mb-3 flex items-center justify-between gap-2 rounded-md border border-warning/30 bg-warning/10 px-2 py-1.5">
<span className="text-[10px] uppercase tracking-wider font-mono text-warning-foreground">
Container state may be stale
</span>
<Button type="button" variant="outline" size="sm" className="h-7" onClick={onRetrySync}>
<RefreshCw className="h-3.5 w-3.5" />
Retry
</Button>
</div>
)}
{densityToolbar}
{isMultiService ? (
<div className="flex flex-col gap-3">