Add read-only Kubernetes cluster view alongside Docker container view

New modules/kubernetes.py lists pods and deployments across the 8 app
namespaces via the in-cluster management-platform-viewer-sa (read-only,
no secrets/exec/log), with metrics-server usage as a best-effort extra
that degrades silently when unreachable. New /cluster page and
/api/cluster endpoint, nav entry, and templates render pods/deployments
grouped by namespace using the existing card/badge design system.
Additive only — no existing Docker routes or views touched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 11:40:03 +01:00
parent c58f19ccda
commit 84f80b7779
6 changed files with 431 additions and 1 deletions

View File

@@ -1028,6 +1028,72 @@ a.sidebar-brand-link {
gap: 10px;
}
/* ── CLUSTER PAGE ──────────────────────────────────────────── */
.badge-warn, .badge.warn { background: rgba(245,185,66,0.12); color: var(--yellow); border: 1px solid rgba(245,185,66,0.25); }
.badge-warn::before, .badge.warn::before { content:''; width:5px; height:5px; background:var(--yellow); border-radius:50%; }
[data-theme="light"] .badge-warn, [data-theme="light"] .badge.warn { background: rgba(217,119,6,0.1); }
.cluster-summary {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
}
@media (max-width: 900px) { .cluster-summary { grid-template-columns: repeat(2, 1fr); } }
.ns-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
gap: 16px;
}
.ns-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 18px 20px;
box-shadow: var(--shadow);
}
.ns-card-header {
display: flex; align-items: center; gap: 10px;
padding-bottom: 12px; margin-bottom: 14px;
border-bottom: 1px solid var(--border);
}
.ns-name { font-family: var(--mono); font-size: 13px; font-weight: 600; color: var(--text); }
.ns-counts { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--text3); }
.ns-deploys-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.deploy-chip {
display: inline-flex; align-items: center; gap: 7px;
padding: 5px 11px; border-radius: 20px;
background: var(--surface2); border: 1px solid var(--border);
font-family: var(--mono); font-size: 11px; color: var(--text2);
}
.deploy-chip .dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.ns-pods-list { display: flex; flex-direction: column; gap: 2px; }
.pod-row {
display: flex; align-items: center; gap: 10px;
padding: 8px 4px;
border-bottom: 1px solid var(--border);
font-size: 12px;
}
.ns-pods-list .pod-row:last-child { border-bottom: none; }
.pod-status-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
.pod-name { font-family: var(--mono); color: var(--text); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pod-ready { font-family: var(--mono); font-size: 11px; color: var(--text3); }
.pod-restarts { font-family: var(--mono); font-size: 11px; min-width: 60px; text-align: right; color: var(--text3); }
.pod-restarts.warn { color: var(--yellow); }
.pod-age { font-family: var(--mono); font-size: 11px; color: var(--text3); min-width: 56px; text-align: right; }
.status-healthy { color: var(--green); background: var(--green); }
.status-degraded { color: var(--yellow); background: var(--yellow); }
.status-failed { color: var(--red); background: var(--red); }
.cluster-note {
font-size: 12px; color: var(--text3); font-family: var(--mono);
padding: 10px 14px; margin-bottom: 18px;
background: var(--surface2); border: 1px solid var(--border); border-radius: var(--radius);
}
/* ── MODAL ─────────────────────────────────────────────────── */
.modal-overlay {
position: fixed; inset: 0; z-index: 1000;