mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-06 08:58:05 +00:00
fix(ui): use Docker health status terms on container cards (#1696)
Per-container metadata showed healthcheck passing/failing instead of Docker's healthy/unhealthy/starting, which disagreed with the stack pill. Render the normalized healthStatus token directly and document the same terms. Closes #1677
This commit is contained in:
@@ -58,7 +58,7 @@ Below the action bar, the **CONTAINERS** section lists every container the stack
|
||||
| **Status badge** | `✓` (green, healthy or running), `✗` (red, exited or unhealthy), `…` (yellow, starting). |
|
||||
| **Name** | The Docker container name; falls back to the first 12 characters of the container ID. |
|
||||
| **Uptime / state** | `up 2h 15m` for running containers, the raw state for everything else. |
|
||||
| **Healthcheck label** | `healthcheck passing`, `healthcheck failing`, or `healthcheck starting`, only when a healthcheck is defined. |
|
||||
| **Healthcheck label** | `healthy`, `unhealthy`, or `starting`, only when a healthcheck is defined. |
|
||||
| **Port mapping** | The first detected web-UI port, formatted `host → container/proto`. The mapping itself is a link that opens the service in a new tab, with a **Copy URL** button beside it. The address uses the active node's host and switches to `https` for port 443. |
|
||||
| **Action buttons** | **Image source links**, **View logs**, **Open bash shell** (admin only), **Service actions**. Per-container image references and registry links live here, not in the header. |
|
||||
| **Live stats** | CPU, memory, and net I/O with rolling sparklines. Only rendered while the container is running. Stats refresh on the same 1500 ms cadence as the dashboard. |
|
||||
|
||||
@@ -185,8 +185,8 @@ The header answers two questions at a glance:
|
||||
|
||||
- **What is this?** A breadcrumb (`LOCAL · STACKS · NAME`) and the stack name as the title.
|
||||
- **Is it healthy?** A state pill to the right of the title reports the live state:
|
||||
- `running · healthy` (green) when at least one container reports a passing healthcheck.
|
||||
- `running · unhealthy` (red) when any container reports a failing healthcheck.
|
||||
- `running · healthy` (green) when at least one container reports Docker health status `healthy`.
|
||||
- `running · unhealthy` (red) when any container reports Docker health status `unhealthy`.
|
||||
- `running · starting` (amber) during the Docker healthcheck start period.
|
||||
- `running` (green) when no healthcheck is defined.
|
||||
- `exited` (red) when no containers are up.
|
||||
@@ -215,9 +215,9 @@ Below the header, each container in the stack gets a single row that answers "is
|
||||
|
||||
Each row includes:
|
||||
|
||||
- **Health badge.** A colored glyph reports the Docker healthcheck state: `✓` green for passing, `✗` red for failing, `…` amber while the healthcheck start period is in flight. Containers without a `healthcheck:` block show a neutral `✓`.
|
||||
- **Health badge.** A colored glyph reports the Docker healthcheck state: `✓` green for `healthy`, `✗` red for `unhealthy`, `…` amber while the healthcheck start period is in flight. Containers without a `healthcheck:` block show a neutral `✓`.
|
||||
- **Container name** in mono.
|
||||
- **Meta line.** Uptime (`up 12 hours`) and the primary port mapping (`8989 → 8989/tcp`).
|
||||
- **Meta line.** Uptime (`up 12 hours`), the Docker health status when defined (`healthy`, `unhealthy`, or `starting`), and the primary port mapping (`8989 → 8989/tcp`).
|
||||
- **Open link.** When the container publishes a port, the mapping itself is a link (`8989 → 8989/tcp ↗`) that opens the service in a new tab, with a **Copy URL** button beside it. The address uses the active node's host and switches to `https` for port 443. Recognised multi-port apps open their web path automatically (for example, Plex opens `/web`).
|
||||
- **Live stat tiles.** Three tiles show CPU, memory, and network I/O with a rolling sparkline. The sparkline uses the cyan data color and refreshes roughly every 1.5 seconds.
|
||||
- **Action icons.** The image source links button (see above), plus shortcuts to **View logs**, open a bash shell, and (on single-service stacks) the per-container Start / Stop / Restart kebab.
|
||||
|
||||
@@ -549,3 +549,58 @@ describe('containers load states', () => {
|
||||
expect(screen.queryByRole('button', { name: /Monitor / })).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ContainersHealth Docker health status labels', () => {
|
||||
const OLD_PHRASES = ['healthcheck passing', 'healthcheck failing', 'healthcheck starting'] as const;
|
||||
const HEALTH_TOKENS = ['healthy', 'unhealthy', 'starting'] as const;
|
||||
|
||||
function metaLine(): HTMLElement {
|
||||
const parent = screen.getByText('up 2 hours').parentElement;
|
||||
if (!parent) throw new Error('expected meta line parent');
|
||||
return parent;
|
||||
}
|
||||
|
||||
function metaSpanTexts(meta: HTMLElement = metaLine()): Array<string | null> {
|
||||
return Array.from(meta.querySelectorAll('span')).map((el) => el.textContent);
|
||||
}
|
||||
|
||||
function renderWithHealth(healthStatus?: ContainerInfo['healthStatus']) {
|
||||
const base = container([{ PrivatePort: 80, PublicPort: 8080 }]);
|
||||
return renderHealth(
|
||||
healthStatus === undefined ? base : ({ ...base, healthStatus } as ContainerInfo),
|
||||
);
|
||||
}
|
||||
|
||||
function expectNoLegacyPhrases(meta: HTMLElement) {
|
||||
for (const phrase of OLD_PHRASES) {
|
||||
expect(meta.textContent).not.toContain(phrase);
|
||||
}
|
||||
}
|
||||
|
||||
it.each(HEALTH_TOKENS)('renders exact meta-line token for healthStatus %s', (token) => {
|
||||
renderWithHealth(token);
|
||||
const meta = metaLine();
|
||||
const labels = metaSpanTexts(meta);
|
||||
expect(labels[0]).toBe('up 2 hours');
|
||||
expect(labels).toContain(token);
|
||||
expect(labels.filter((t) => t === token)).toHaveLength(1);
|
||||
expectNoLegacyPhrases(meta);
|
||||
expect(screen.getByRole('link', { name: /8080/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['none', 'none'],
|
||||
['omitted', undefined],
|
||||
] as const)('omits a health token when healthStatus is %s', (_case, healthStatus) => {
|
||||
renderWithHealth(healthStatus);
|
||||
const meta = metaLine();
|
||||
const labels = metaSpanTexts(meta);
|
||||
expect(labels).toContain('up 2 hours');
|
||||
for (const token of HEALTH_TOKENS) {
|
||||
expect(labels).not.toContain(token);
|
||||
}
|
||||
expect(labels.filter((t) => t === '·')).toHaveLength(1);
|
||||
expectNoLegacyPhrases(meta);
|
||||
expect(screen.getByRole('link', { name: /8080/ })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,9 +64,7 @@ const healthcheckLabel = (
|
||||
health?: 'healthy' | 'unhealthy' | 'starting' | 'none',
|
||||
): string | null => {
|
||||
if (!health || health === 'none') return null;
|
||||
if (health === 'healthy') return 'healthcheck passing';
|
||||
if (health === 'unhealthy') return 'healthcheck failing';
|
||||
return 'healthcheck starting';
|
||||
return health;
|
||||
};
|
||||
|
||||
type StackPill = {
|
||||
|
||||
Reference in New Issue
Block a user