Skip to content

Commit 05e83a8

Browse files
committed
Show stopped containers and align metrics
1 parent f74f3a5 commit 05e83a8

4 files changed

Lines changed: 87 additions & 8 deletions

File tree

ROADMAP.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
✅ One-time schedules
2626
✅ ntfy.sh notifications
2727
✅ GHCR Docker image publishing
28+
✅ Dashboard shows stopped/errored containers
29+
✅ Host metrics refresh keeps previous values
30+
✅ Host metrics cards aligned across hosts
2831

2932
---
3033

app/main.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,15 @@ def index():
14871487
except:
14881488
pass
14891489

1490+
state = container.attrs.get('State', {}) or {}
1491+
status = state.get('Status') or container.status
1492+
exit_code = state.get('ExitCode')
1493+
status_display = status
1494+
status_class = status
1495+
if status == 'exited' and exit_code not in (None, 0):
1496+
status_display = 'error'
1497+
status_class = 'error'
1498+
14901499
image_display = strip_image_tag(image_name)
14911500
image_version, version_source = get_image_version(container, image_name, docker_client)
14921501
host_color = host_color_map.get(host_id, HOST_DEFAULT_COLOR)
@@ -1495,7 +1504,10 @@ def index():
14951504
container_list.append({
14961505
'id': container.id[:12],
14971506
'name': container.name,
1498-
'status': container.status,
1507+
'status': status,
1508+
'status_display': status_display,
1509+
'status_class': status_class,
1510+
'exit_code': exit_code,
14991511
'health': health_status,
15001512
'image': image_name,
15011513
'image_display': image_display,
@@ -1625,6 +1637,15 @@ def get_containers():
16251637
except:
16261638
pass
16271639

1640+
state = container.attrs.get('State', {}) or {}
1641+
status = state.get('Status') or container.status
1642+
exit_code = state.get('ExitCode')
1643+
status_display = status
1644+
status_class = status
1645+
if status == 'exited' and exit_code not in (None, 0):
1646+
status_display = 'error'
1647+
status_class = 'error'
1648+
16281649
image_display = strip_image_tag(image_name)
16291650
image_version, version_source = get_image_version(container, image_name, docker_client)
16301651
host_color = host_color_map.get(host_id, HOST_DEFAULT_COLOR)
@@ -1633,7 +1654,10 @@ def get_containers():
16331654
container_list.append({
16341655
'id': container.id[:12],
16351656
'name': container.name,
1636-
'status': container.status,
1657+
'status': status,
1658+
'status_display': status_display,
1659+
'status_class': status_class,
1660+
'exit_code': exit_code,
16371661
'health': health_status,
16381662
'image': image_name,
16391663
'image_display': image_display,

templates/index.html

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,31 @@
162162
background: #f8d7da;
163163
color: #721c24;
164164
}
165+
166+
.status.error {
167+
background: #f5c6cb;
168+
color: #721c24;
169+
}
165170

166171
.status.created {
167172
background: #d1ecf1;
168173
color: #0c5460;
169174
}
175+
176+
.status.paused {
177+
background: #fff3cd;
178+
color: #856404;
179+
}
180+
181+
.status.restarting {
182+
background: #fde2c9;
183+
color: #8a4b08;
184+
}
185+
186+
.status.dead {
187+
background: #e2e3e5;
188+
color: #383d41;
189+
}
170190

171191
.btn {
172192
padding: 6px 14px;
@@ -916,6 +936,7 @@ <h2>Docker Containers</h2>
916936
<option value="">All Statuses</option>
917937
<option value="running">Running</option>
918938
<option value="exited">Exited</option>
939+
<option value="error">Error</option>
919940
<option value="paused">Paused</option>
920941
<option value="restarting">Restarting</option>
921942
<option value="unhealthy">Unhealthy</option>
@@ -977,17 +998,20 @@ <h2>Docker Containers</h2>
977998
</thead>
978999
<tbody>
9791000
{% for container in containers %}
980-
<tr data-name="{{ container.name|lower }}" data-status="{{ container.status }}" data-health="{{ container.health or '' }}" data-host="{{ container.host_name }}" data-stack="{{ container.stack }}" data-image="{{ container.image_display }}" data-ip="{{ container.ip_addresses }}" data-tags="{% for tag in container.tags %}{{ tag.name }}{% if not loop.last %},{% endif %}{% endfor %}" data-cpu="0" data-memory="0">
1001+
<tr data-name="{{ container.name|lower }}" data-status="{{ container.status }}" data-exit-code="{{ container.exit_code if container.exit_code is not none else '' }}" data-health="{{ container.health or '' }}" data-host="{{ container.host_name }}" data-stack="{{ container.stack }}" data-image="{{ container.image_display }}" data-ip="{{ container.ip_addresses }}" data-tags="{% for tag in container.tags %}{{ tag.name }}{% if not loop.last %},{% endif %}{% endfor %}" data-cpu="0" data-memory="0">
9811002
<td>
982-
<input type="checkbox" class="container-select" data-id="{{ container.id }}" data-name="{{ container.name }}" data-host-id="{{ container.host_id }}" data-status="{{ container.status }}" onchange="updateSelectionState()">
1003+
<input type="checkbox" class="container-select" data-id="{{ container.id }}" data-name="{{ container.name }}" data-host-id="{{ container.host_id }}" data-status="{{ container.status }}" data-exit-code="{{ container.exit_code if container.exit_code is not none else '' }}" onchange="updateSelectionState()">
9831004
</td>
9841005
<td>
9851006
<div class="tooltip-container">
9861007
<strong>{{ container.name }}</strong>
9871008
<span class="tooltip-text">
9881009
<div><span class="tooltip-label">ID:</span> {{ container.id }}</div>
9891010
<div><span class="tooltip-label">Image:</span> {{ container.image }}</div>
990-
<div><span class="tooltip-label">Status:</span> {{ container.status }}</div>
1011+
<div><span class="tooltip-label">Status:</span> {{ container.status_display }}</div>
1012+
{% if container.exit_code is not none %}
1013+
<div><span class="tooltip-label">Exit code:</span> {{ container.exit_code }}</div>
1014+
{% endif %}
9911015
<div><span class="tooltip-label">Created:</span> {{ container.created[:19].replace('T', ' ') }}</div>
9921016
<div><span class="tooltip-label">Version:</span> {{ container.image_version }}</div>
9931017
</span>
@@ -1005,7 +1029,7 @@ <h2>Docker Containers</h2>
10051029
<td><span style="font-family: monospace; font-size: 12px; color: #555;">{{ container.ip_addresses }}</span></td>
10061030
<td class="status-cell">
10071031
<div class="status-stack">
1008-
<span class="status {{ container.status }}">{{ container.status }}</span>
1032+
<span class="status {{ container.status_class }}">{{ container.status_display }}</span>
10091033
</div>
10101034
{% if container.health %}
10111035
<span class="tooltip-container" style="position: absolute; top: 8px; right: 8px;">
@@ -2218,6 +2242,7 @@ <h3>⌨️ Keyboard Shortcuts</h3>
22182242
rows.forEach(row => {
22192243
const name = row.dataset.name || '';
22202244
const status = row.dataset.status || '';
2245+
const exitCode = row.dataset.exitCode || '';
22212246
const health = row.dataset.health || '';
22222247
const host = row.dataset.host || '';
22232248
const stack = row.dataset.stack || '';
@@ -2229,6 +2254,8 @@ <h3>⌨️ Keyboard Shortcuts</h3>
22292254
matchesStatus = true;
22302255
} else if (statusFilter === 'unhealthy' || statusFilter === 'starting' || statusFilter === 'healthy') {
22312256
matchesStatus = health === statusFilter;
2257+
} else if (statusFilter === 'error') {
2258+
matchesStatus = status === 'exited' && exitCode !== '' && exitCode !== '0';
22322259
} else {
22332260
matchesStatus = status === statusFilter;
22342261
}

templates/metrics.html

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
display: grid;
9696
grid-template-columns: repeat(2, 1fr);
9797
gap: 15px;
98+
grid-auto-rows: 1fr;
9899
}
99100

100101
.metric-box {
@@ -103,6 +104,13 @@
103104
border-radius: 8px;
104105
}
105106

107+
.metrics-grid .metric-box {
108+
display: flex;
109+
flex-direction: column;
110+
justify-content: center;
111+
min-height: 88px;
112+
}
113+
106114
.metric-label {
107115
font-size: 0.8em;
108116
color: var(--text-secondary);
@@ -405,10 +413,27 @@ <h1>Host Metrics</h1>
405413
container.dataset.loaded = 'true';
406414

407415
Promise.all(detailPromises).then(details => {
408-
const detailMap = {};
416+
const detailResults = {};
409417
details.forEach(item => {
410418
if (!item || !item.host_id) return;
411-
detailMap[item.host_id] = item.data || { error: true };
419+
if (item.data) {
420+
detailResults[item.host_id] = item.data;
421+
}
422+
});
423+
424+
const detailMap = {};
425+
hosts.forEach(host => {
426+
if (host.status !== 'online') {
427+
return;
428+
}
429+
const nextDetail = detailResults[host.host_id];
430+
if (nextDetail) {
431+
detailMap[host.host_id] = nextDetail;
432+
} else if (lastDetailMap[host.host_id]) {
433+
detailMap[host.host_id] = lastDetailMap[host.host_id];
434+
} else {
435+
detailMap[host.host_id] = { error: true };
436+
}
412437
});
413438

414439
container.innerHTML = renderHosts(hosts, detailMap, false);

0 commit comments

Comments
 (0)