Files
qweZabbixDashboard/public/style.css
Christoph Gasser 101aaa6e32 Rework device type view to group by deviceType tag
- /api/devices now groups hosts by deviceType tag instead of host group;
  hosts without the tag are skipped
- /api/detail device lookup filters by deviceType tag instead of groupid
- getTag() is now case-insensitive on the tag name
- Removed selectHostGroups from fetchKFCData (no longer needed)
- Frontend: hex id and Zabbix deep-link use deviceType instead of groupid
- Smaller hex label (1.2rem) for device type view via hex-item--device class
- Skip DOM re-render on auto-refresh when data is unchanged (lastDataKey diff)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 14:59:17 +02:00

630 lines
14 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ── Variables ─────────────────────────────────────────────────────────────── */
:root {
--bg: #ffffff;
--surface: #f6f8fa;
--surface-2: #eef1f4;
--surface-3: #e1e4e8;
--border: #d0d7de;
--text: #1f2328;
--text-muted: #636c76;
--accent: #0969da;
--accent-dim: rgba(9,105,218,0.08);
--c-ok: #2da44e;
--c-warning: #c69026;
--c-average: #e16f24;
--c-high: #d1242f;
--c-disaster: #8250df;
--c-unknown: #8c959f;
--hex-w: 132px; /* = hex-h × sin(60°) = 152 × 0.866 — required for regular hexagon */
--hex-h: 152px;
--hex-gap: 6px;
}
/* ── Reset ─────────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.5;
}
/* ── Header ────────────────────────────────────────────────────────────────── */
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
height: 54px;
background: var(--surface);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 200;
gap: 16px;
}
.header-brand {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.brand-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--c-ok);
box-shadow: 0 0 0 3px rgba(45,164,78,0.2);
}
.brand-name {
font-weight: 700;
font-size: 0.95rem;
color: var(--text);
}
/* View toggle */
.view-toggle {
display: flex;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 8px;
padding: 3px;
gap: 2px;
}
.toggle-btn {
padding: 5px 14px;
border: none;
background: transparent;
color: var(--text-muted);
border-radius: 6px;
cursor: pointer;
font-size: 0.8rem;
font-weight: 500;
transition: background 0.12s, color 0.12s;
white-space: nowrap;
}
.toggle-btn.active {
background: var(--surface-3);
color: var(--text);
}
.toggle-btn:not(.active):hover { color: var(--text); }
/* Header meta */
.header-meta {
display: flex;
align-items: center;
gap: 10px;
flex-shrink: 0;
}
.last-updated {
font-size: 0.72rem;
color: var(--text-muted);
white-space: nowrap;
}
.refresh-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: 1px solid var(--border);
border-radius: 6px;
background: transparent;
color: var(--text-muted);
cursor: pointer;
transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.refresh-btn:hover { color: var(--text); background: var(--surface-2); }
.refresh-btn.spinning svg { animation: spin 0.6s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
/* ── Subbar ────────────────────────────────────────────────────────────────── */
.subbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
height: 42px;
background: var(--surface);
border-bottom: 1px solid var(--border);
gap: 16px;
}
.legend {
display: flex;
gap: 18px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.73rem;
color: var(--text-muted);
}
.swatch {
width: 10px;
height: 10px;
border-radius: 2px;
flex-shrink: 0;
}
.swatch.ok { background: var(--c-ok); }
.swatch.warning { background: var(--c-warning); }
.swatch.average { background: var(--c-average); }
.swatch.high { background: var(--c-high); }
.swatch.disaster { background: var(--c-disaster); }
.summary {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
flex-shrink: 0;
}
.summary strong { color: var(--text); }
/* ── Countries split layout ────────────────────────────────────────────────── */
.countries-wrap {
display: flex;
min-height: calc(100vh - 96px);
align-items: stretch;
}
.country-col {
flex: 1;
min-width: 0;
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
}
.country-col:last-child {
border-right: none;
}
.country-col-header {
padding: 14px 20px 12px;
font-size: 1rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
color: var(--text);
border-bottom: 1px solid var(--border);
background: var(--surface);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.country-col-count {
font-size: 0.7rem;
font-weight: 500;
color: var(--text-muted);
text-transform: none;
letter-spacing: 0;
}
.country-col-stats {
font-size: 0.78rem;
font-weight: 400;
color: var(--text-muted);
text-transform: none;
letter-spacing: 0;
white-space: nowrap;
}
.country-col-body {
padding: 20px;
flex: 1;
overflow-y: auto;
}
/* ── Loading / Error ───────────────────────────────────────────────────────── */
.page-center {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
padding: 80px 24px;
color: var(--text-muted);
font-size: 0.875rem;
width: 100%;
}
.spinner {
width: 32px;
height: 32px;
border: 3px solid var(--surface-3);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
.error-box {
background: #fff0ee;
border: 1px solid #ffa198;
color: #b91c1c;
border-radius: 8px;
padding: 14px 18px;
font-size: 0.85rem;
max-width: 600px;
margin: 20px;
}
/* ── Hex Grid ──────────────────────────────────────────────────────────────── */
.hex-col-wrap {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.hex-row {
display: flex;
gap: var(--hex-gap);
/* Vertical overlap for equal gaps on all 6 sides:
center-to-center-y = (hex-w + gap) × sin(60°)
margin-top = -(hex-h - center-to-center-y) = -(hex-h/4 - gap × 0.866) */
margin-top: calc(var(--hex-h) * -0.25 + var(--hex-gap) * 0.866);
}
.hex-row:first-child {
margin-top: 0;
}
.hex-row.offset {
margin-left: calc(var(--hex-w) / 2 + var(--hex-gap) / 2);
}
/* ── Hex Item ──────────────────────────────────────────────────────────────── */
.hex-item {
width: var(--hex-w);
height: var(--hex-h);
clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: filter 0.15s, transform 0.15s;
position: relative;
padding: 18px 16px;
flex-shrink: 0;
user-select: none;
}
.hex-item:hover {
filter: brightness(1.1) saturate(1.15);
transform: scale(1.07);
z-index: 10;
}
.hex-item:active { transform: scale(0.97); }
.hex-item--device .hex-label {
font-size: 1.2rem;
font-weight: 700;
}
.hex-label {
font-size: 1.8rem;
font-weight: 800;
color: #fff;
text-align: center;
line-height: 1.2;
word-break: break-word;
max-width: 108px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
text-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.hex-hosts {
font-size: 1rem;
font-weight: 500;
color: rgba(255,255,255,0.82);
margin-top: 2px;
line-height: 1;
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.hex-count {
font-size: 0.68rem;
font-weight: 800;
color: rgba(255,255,255,0.95);
margin-top: 3px;
line-height: 1;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
/* ── Severity colours ──────────────────────────────────────────────────────── */
.sev--1 { background: var(--c-ok); }
.sev-2 { background: var(--c-warning); }
.sev-3 { background: var(--c-average); }
.sev-4 { background: var(--c-high); }
.sev-5 {
background: var(--c-disaster);
animation: disaster-pulse 1.8s ease-in-out infinite;
}
@keyframes disaster-pulse {
0%,100% { filter: brightness(1); }
50% { filter: brightness(1.3) saturate(1.2); }
}
/* ── Problem list ──────────────────────────────────────────────────────────── */
.problems-section {
margin-top: 28px;
padding-top: 20px;
border-top: 2px solid var(--border);
}
.problems-section-title {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-bottom: 14px;
}
.problem-group {
margin-bottom: 18px;
}
.problem-group-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 6px;
padding-bottom: 4px;
border-bottom: 1px solid var(--border);
}
.problem-group-label {
font-size: 0.82rem;
font-weight: 700;
color: var(--text);
}
.problem-group-count {
font-size: 0.68rem;
font-weight: 600;
background: var(--surface-3);
color: var(--text-muted);
padding: 1px 8px;
border-radius: 10px;
}
.problem-list-row {
display: grid;
grid-template-columns: 8px minmax(0, 1.2fr) minmax(0, 2.5fr) auto;
align-items: center;
gap: 10px;
padding: 6px 10px;
border-radius: 6px;
margin-bottom: 3px;
background: var(--surface);
border: 1px solid var(--border);
}
.problem-list-row:hover {
background: var(--surface-2);
}
.pl-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.pl-host {
font-size: 1rem;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pl-desc {
font-size: 1rem;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pl-time {
font-size: 1rem;
color: var(--text-muted);
white-space: nowrap;
}
/* ── Modal ─────────────────────────────────────────────────────────────────── */
.modal {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.35);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
backdrop-filter: blur(2px);
}
.modal-panel {
background: var(--bg);
border: 1px solid var(--border);
border-radius: 14px;
width: 100%;
max-width: 560px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 20px 60px rgba(0,0,0,0.18);
animation: modal-in 0.16s ease-out;
}
@keyframes modal-in {
from { opacity: 0; transform: scale(0.96) translateY(6px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
background: var(--surface);
}
.modal-title-wrap {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.modal-sev-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.modal-header h2 {
font-size: 0.95rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.modal-close {
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
border: 1px solid var(--border);
background: transparent;
color: var(--text-muted);
cursor: pointer;
border-radius: 6px;
flex-shrink: 0;
transition: background 0.12s, color 0.12s;
}
.modal-close:hover { background: var(--surface-3); color: var(--text); }
.modal-body {
overflow-y: auto;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 10px;
}
.host-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 14px 16px;
}
.host-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.host-name {
font-size: 0.85rem;
font-weight: 600;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.sev-badge {
font-size: 0.65rem;
font-weight: 700;
padding: 3px 9px;
border-radius: 20px;
white-space: nowrap;
flex-shrink: 0;
color: #fff;
}
.problem-list { display: flex; flex-direction: column; gap: 6px; }
.problem-row {
display: flex;
align-items: flex-start;
gap: 8px;
}
.problem-pip {
width: 6px;
height: 6px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 5px;
}
.problem-text { font-size: 0.78rem; line-height: 1.4; }
.problem-time { font-size: 0.68rem; color: var(--text-muted); margin-top: 1px; }
.no-problems {
font-size: 0.78rem;
color: var(--c-ok);
font-weight: 500;
}
.modal-loading {
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
}
/* ── Responsive ────────────────────────────────────────────────────────────── */
@media (max-width: 700px) {
:root {
--hex-w: 95px; /* 110 × 0.866 */
--hex-h: 110px;
--hex-gap: 4px;
}
.countries-wrap { flex-direction: column; }
.country-col { border-right: none; border-bottom: 1px solid var(--border); }
.hex-label { font-size: 0.82rem; max-width: 68px; }
}