Refine Sentinel security dashboard hierarchy
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 47s

This commit is contained in:
OfficeCom Codex
2026-08-01 02:09:53 +02:00
parent 7009596efc
commit c73bab139b
4 changed files with 45 additions and 5 deletions

View File

@@ -66,3 +66,13 @@ body { background:radial-gradient(circle at 8% -10%,rgba(102,176,225,.24),transp
} }
@keyframes sentinel-rise { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } } @keyframes sentinel-rise { from { opacity:0; transform:translateY(12px); } to { opacity:1; transform:translateY(0); } }
/* Operational hierarchy and responsive chrome. */
.skip-link { position:fixed; z-index:20; top:10px; left:10px; padding:10px 13px; border-radius:8px; background:var(--lime); color:var(--navy); font-weight:800; text-decoration:none; transform:translateY(-160%); transition:transform .2s ease; }
.skip-link:focus { transform:translateY(0); }
.wordmark { display:flex; align-items:center; gap:8px; }.wordmark .wordmark-mark { display:grid; flex:0 0 auto; place-items:center; width:29px; height:29px; margin:0; border-radius:8px; background:var(--lime); color:#102a43; font:800 10px var(--font-sans); letter-spacing:0; }.wordmark .wordmark-name { display:grid; gap:1px; color:#fff; font:800 18px/1 var(--font-sans); letter-spacing:-.045em; }.wordmark .wordmark-name small { color:#9cc8e8; font:700 8px/1 var(--font-sans); letter-spacing:.12em; text-transform:uppercase; }
.app-footer { display:flex; justify-content:space-between; gap:16px; max-width:1280px; margin:0 auto; padding:0 6vw 32px; color:#6c8494; font-size:10px; letter-spacing:.04em; }.app-footer span:first-child { color:#426277; font-weight:800; text-transform:uppercase; }
.priority-board { display:grid; grid-template-columns:minmax(240px,.82fr) minmax(0,1.65fr); gap:1px; margin:0 0 30px; overflow:hidden; border:1px solid #193a53; border-radius:16px; background:#193a53; box-shadow:0 20px 42px rgba(16,42,67,.15); }.priority-intro { display:grid; align-content:space-between; min-height:272px; padding:27px; color:#eaf3f8; background:radial-gradient(circle at 15% 8%,rgba(79,163,223,.2),transparent 16rem),linear-gradient(145deg,#153b58,#0d2437); }.priority-intro .eyebrow { color:#a4cae4; }.priority-intro h2 { margin:10px 0; font-size:clamp(28px,3.2vw,43px); line-height:.95; letter-spacing:-.065em; }.priority-intro p { max-width:290px; margin:0; color:#b7ccda; font-size:13px; line-height:1.55; }.priority-sync { display:flex; align-items:center; gap:8px; margin-top:20px; color:#9fc0d2; font:700 10px var(--font-sans); }.priority-sync i { width:7px; height:7px; border-radius:50%; background:#72d39c; box-shadow:0 0 0 5px rgba(114,211,156,.12); }.priority-list { display:grid; align-content:center; gap:1px; background:#d7e4ec; }.priority-item { display:grid; grid-template-columns:34px minmax(0,1fr) 24px; align-items:center; gap:15px; min-height:90px; padding:16px 22px; background:rgba(255,255,255,.96); color:var(--ink); text-decoration:none; transition:background .2s ease,transform .2s ease; }.priority-item:hover { background:#f4faff; }.priority-item.critical:hover { background:#fff4f1; }.priority-index { align-self:start; color:#8aa0ae; font:700 11px var(--font-mono); }.priority-item strong,.priority-item small { display:block; }.priority-item strong { margin:6px 0 3px; font-size:18px; letter-spacing:-.035em; }.priority-item small { overflow:hidden; color:#657f90; font-size:11px; text-overflow:ellipsis; white-space:nowrap; }.priority-arrow { color:#3a7196; font-size:20px; transition:transform .2s ease; }.priority-item:hover .priority-arrow { transform:translate(2px,-2px); }.priority-calm { display:grid; align-content:center; justify-items:start; gap:9px; padding:30px; color:#eaf3f8; background:linear-gradient(145deg,#173d4b,#0e2a35); }.priority-calm .status-orb { margin:0 0 8px; }.priority-calm strong { font-size:22px; letter-spacing:-.04em; }.priority-calm p { margin:0; color:#b4cbd4; font-size:13px; }.priority-board.calm { grid-template-columns:minmax(240px,.82fr) minmax(0,1.65fr); }
.device-hero { display:grid; grid-template-columns:minmax(0,1fr) 240px; gap:28px; align-items:end; min-height:225px; margin:0 0 18px; padding:31px 33px; overflow:hidden; border:1px solid rgba(134,192,225,.24); border-radius:18px; color:#ecf5fb; background:radial-gradient(circle at 88% 10%,rgba(96,183,227,.25),transparent 17rem),linear-gradient(135deg,#102f49,#0a1d2c 72%); box-shadow:0 24px 50px rgba(18,51,75,.15); }.device-hero.warning { background:radial-gradient(circle at 88% 10%,rgba(247,181,91,.22),transparent 17rem),linear-gradient(135deg,#40301a,#20180e 72%); }.device-hero.critical { background:radial-gradient(circle at 88% 10%,rgba(240,110,100,.24),transparent 17rem),linear-gradient(135deg,#45252b,#1d1116 72%); }.device-hero .eyebrow { color:#a6d2ec; }.device-hero h1 { margin:9px 0 12px; font-size:clamp(37px,5vw,62px); line-height:.92; letter-spacing:-.07em; }.device-hero p { margin:0; color:#bdd3e1; font-size:13px; line-height:1.55; }.device-hero-state { display:grid; gap:5px; padding:18px; border:1px solid rgba(168,216,241,.26); border-radius:14px; background:rgba(4,22,35,.34); }.device-hero-state > span:not(.status-orb) { color:#a9c7d8; font-size:10px; font-weight:800; letter-spacing:.11em; text-transform:uppercase; }.device-hero-state strong { font-size:27px; letter-spacing:-.055em; text-transform:capitalize; }.device-hero-state small { color:#c5d9e5; font-size:11px; line-height:1.45; }
.header-links a,.state,.event-count,.recipient-form button,.rule-actions button,table,th,td,.map-toolbar input,.map-toolbar select,.map-toolbar button { font-family:var(--font-sans); }
@media (max-width:850px) { .masthead { gap:14px; align-items:center; }.header-links { gap:4px; overflow:auto; max-width:calc(100vw - 155px); flex-wrap:nowrap; }.header-links a { flex:0 0 auto; padding:7px 8px; font-size:11px; }.wordmark .wordmark-name small { display:none; }.app-footer { align-items:flex-start; flex-direction:column; padding-bottom:24px; }.priority-board,.priority-board.calm,.device-hero { grid-template-columns:1fr; }.priority-intro { min-height:215px; }.priority-list { gap:1px; }.priority-item { min-height:84px; padding:15px 17px; }.priority-item small { white-space:normal; }.device-hero { min-height:0; padding:28px 24px; }.device-hero-state { max-width:none; }.device-hero h1 { font-size:42px; } }

View File

@@ -10,10 +10,12 @@
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
</head> </head>
<body class="app-shell"> <body class="app-shell">
<a class="skip-link" href="#main-content">Zum Inhalt springen</a>
<header class="masthead"> <header class="masthead">
<a class="wordmark" href="{{ url_for('overview') }}"><span>OC</span>Sentinel</a> <a class="wordmark" href="{{ url_for('overview') }}"><span class="wordmark-mark">OC</span><span class="wordmark-name">Sentinel <small>Security console</small></span></a>
<nav class="header-links"><a class="{{ 'active' if request.endpoint in ('overview', 'organization') else '' }}" href="{{ url_for('overview') }}">Lagebild</a><a class="{{ 'active' if request.endpoint == 'network' else '' }}" href="{{ url_for('network') }}">Zugriffswege</a><a class="{{ 'active' if request.endpoint in ('reports', 'weekly_report') else '' }}" href="{{ url_for('reports') }}">Berichte</a><a class="{{ 'active' if request.endpoint in ('recipients', 'add_recipient', 'toggle_recipient', 'delete_recipient') else '' }}" href="{{ url_for('recipients') }}">Empfaenger</a></nav> <nav class="header-links" aria-label="Hauptnavigation"><a class="{{ 'active' if request.endpoint in ('overview', 'organization') else '' }}" href="{{ url_for('overview') }}">Lagebild</a><a class="{{ 'active' if request.endpoint == 'network' else '' }}" href="{{ url_for('network') }}">Zugriffswege</a><a class="{{ 'active' if request.endpoint in ('reports', 'weekly_report') else '' }}" href="{{ url_for('reports') }}">Berichte</a><a class="{{ 'active' if request.endpoint in ('recipients', 'add_recipient', 'toggle_recipient', 'delete_recipient') else '' }}" href="{{ url_for('recipients') }}">Empfaenger</a></nav>
</header> </header>
<main>{% block content %}{% endblock %}</main> <main id="main-content">{% block content %}{% endblock %}</main>
<footer class="app-footer"><span>OfficeCom Sentinel</span><span>Interne Sicherheitskonsole · Verdichtete Endpoint-Signale</span></footer>
</body> </body>
</html> </html>

View File

@@ -1,7 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ report[0] }} - OC Sentinel{% endblock %} {% block title %}{{ report[0] }} - OC Sentinel{% endblock %}
{% block content %} {% block content %}
<section class="panel"><div class="panel-heading"><h2>{{ report[0] }}</h2><span class="state {{ report[6] }}">{{ report[6] }}</span>{% if report[8] %}<span class="state {{ 'current' if event.is_current else 'historic' }}">{{ 'aktuell' if event.is_current else 'historisch' }}: {{ event.label }}</span>{% endif %}</div></section> <section class="device-hero {{ report[6] }}">
<div><span class="eyebrow">Geraeteanalyse</span><h1>{{ report[0] }}</h1><p>Letzter Scan {{ report[5] or '-' }} · Client zuletzt gesehen {{ report[2] or '-' }}</p></div>
<div class="device-hero-state"><span class="status-orb"></span><span>Aktueller Status</span><strong>{{ report[6] }}</strong>{% if report[8] %}<small>{{ 'Aktuelles Signal' if event.is_current else 'Historisches Signal' }} · {{ event.label }}</small>{% else %}<small>Keine Ereignisse im letzten Scan</small>{% endif %}</div>
</section>
<section class="metrics compact-metrics"><article><span>Ereignisse</span><strong>{{ report[8] }}</strong></article><article><span>Quell-IPs</span><strong>{{ report[9] }}</strong></article><article><span>CVEs</span><strong>{{ report[10] }}</strong></article><article><span>Kritische CVEs</span><strong class="critical">{{ report[11] }}</strong></article></section> <section class="metrics compact-metrics"><article><span>Ereignisse</span><strong>{{ report[8] }}</strong></article><article><span>Quell-IPs</span><strong>{{ report[9] }}</strong></article><article><span>CVEs</span><strong>{{ report[10] }}</strong></article><article><span>Kritische CVEs</span><strong class="critical">{{ report[11] }}</strong></article></section>
{% if ransomware_beta.enabled %}<section class="panel ransomware-panel {{ ransomware_beta.state }}"><div class="panel-heading"><span class="eyebrow">Passive Beta</span><h2>Ransomware-Frueherkennung <small>{{ ransomware_beta.state }}</small></h2><p>{{ ransomware_beta.reason }}</p></div><div class="table-wrap"><table><thead><tr><th>Zeitpunkt</th><th>Signal</th><th>Prozess</th><th>Quelle</th><th>Bewertung</th></tr></thead><tbody>{% for signal in ransomware_beta.signals %}<tr><td>{{ signal.timestamp }}</td><td>{{ signal.category }}</td><td>{{ signal.process }}</td><td>{{ signal.source }}</td><td><span class="state {{ 'critical' if signal.confidence == 'high' else 'warning' }}">{{ signal.confidence }}</span></td></tr>{% else %}<tr><td colspan="5" class="empty-state">Keine Signale im aktuellen Beta-Zeitfenster.</td></tr>{% endfor %}</tbody></table></div></section>{% endif %} {% if ransomware_beta.enabled %}<section class="panel ransomware-panel {{ ransomware_beta.state }}"><div class="panel-heading"><span class="eyebrow">Passive Beta</span><h2>Ransomware-Frueherkennung <small>{{ ransomware_beta.state }}</small></h2><p>{{ ransomware_beta.reason }}</p></div><div class="table-wrap"><table><thead><tr><th>Zeitpunkt</th><th>Signal</th><th>Prozess</th><th>Quelle</th><th>Bewertung</th></tr></thead><tbody>{% for signal in ransomware_beta.signals %}<tr><td>{{ signal.timestamp }}</td><td>{{ signal.category }}</td><td>{{ signal.process }}</td><td>{{ signal.source }}</td><td><span class="state {{ 'critical' if signal.confidence == 'high' else 'warning' }}">{{ signal.confidence }}</span></td></tr>{% else %}<tr><td colspan="5" class="empty-state">Keine Signale im aktuellen Beta-Zeitfenster.</td></tr>{% endfor %}</tbody></table></div></section>{% endif %}
{% if ransomware_beta.enabled %}<section class="panel sensor-coverage-panel"><div class="panel-heading"><span class="eyebrow">Beta-Abdeckung</span><h2>Erkennungsquellen <small>Verfuegbarkeit im letzten Scan</small></h2><p>Ein unauffaelliger Zeitraum ist nur dann belastbar, wenn die benoetigten Datenquellen erreichbar waren.</p></div><div class="sensor-grid">{% for sensor in ransomware_sensors %}<article class="sensor-card {{ sensor.state }}"><div><span class="sensor-dot"></span><span class="sensor-state">{{ sensor.state }}</span></div><strong>{{ sensor.label }}</strong><p>{% if not sensor.enabled %}Nicht fuer diesen Client aktiviert.{% elif sensor.available %}{{ sensor.event_count }} Ereignisse im Zeitfenster verarbeitet.{% elif sensor.state == 'not-installed' %}Protokollquelle ist auf diesem System nicht installiert.{% else %}Quelle konnte im letzten Scan nicht verwendet werden.{% endif %}</p></article>{% else %}<div class="sensor-empty">Dieser Client sendet noch keine Abdeckungsdaten. Nach dem Update auf Beta 1.5.0-beta.5 erscheint die Sensoransicht automatisch.</div>{% endfor %}</div></section>{% endif %} {% if ransomware_beta.enabled %}<section class="panel sensor-coverage-panel"><div class="panel-heading"><span class="eyebrow">Beta-Abdeckung</span><h2>Erkennungsquellen <small>Verfuegbarkeit im letzten Scan</small></h2><p>Ein unauffaelliger Zeitraum ist nur dann belastbar, wenn die benoetigten Datenquellen erreichbar waren.</p></div><div class="sensor-grid">{% for sensor in ransomware_sensors %}<article class="sensor-card {{ sensor.state }}"><div><span class="sensor-dot"></span><span class="sensor-state">{{ sensor.state }}</span></div><strong>{{ sensor.label }}</strong><p>{% if not sensor.enabled %}Nicht fuer diesen Client aktiviert.{% elif sensor.available %}{{ sensor.event_count }} Ereignisse im Zeitfenster verarbeitet.{% elif sensor.state == 'not-installed' %}Protokollquelle ist auf diesem System nicht installiert.{% else %}Quelle konnte im letzten Scan nicht verwendet werden.{% endif %}</p></article>{% else %}<div class="sensor-empty">Dieser Client sendet noch keine Abdeckungsdaten. Nach dem Update auf Beta 1.5.0-beta.5 erscheint die Sensoransicht automatisch.</div>{% endfor %}</div></section>{% endif %}

View File

@@ -16,6 +16,31 @@
<article><span>Abdeckung</span><strong class="{% if coverage[2] %}warning{% else %}ok{% endif %}">{{ coverage[1] }}/{{ coverage[0] }}</strong><small>{{ coverage[2] }} stumm &gt; 36 Std.</small></article> <article><span>Abdeckung</span><strong class="{% if coverage[2] %}warning{% else %}ok{% endif %}">{{ coverage[1] }}/{{ coverage[0] }}</strong><small>{{ coverage[2] }} stumm &gt; 36 Std.</small></article>
</section> </section>
{% if alerts %}
<section class="priority-board">
<div class="priority-intro">
<span class="eyebrow">Einsatzfokus</span>
<h2>Was jetzt<br>Aufmerksamkeit braucht.</h2>
<p>Priorisiert nach Schweregrad und zuletzt gemeldetem Signal.</p>
<div class="priority-sync"><i></i><span>Letzter Datenstand: {{ summary[7] or '-' }}</span></div>
</div>
<div class="priority-list">
{% for alert in alerts[:3] %}
<a class="priority-item {{ alert.alert_state }}" href="{{ url_for('device', machine_name=alert.machine_name) }}">
<span class="priority-index">0{{ loop.index }}</span>
<div><span class="state {{ alert.alert_state }}">{{ alert.alert_state }}</span><strong>{{ alert.machine_name }}</strong><small>{{ alert.total_events }} Ereignisse · {{ alert.unique_ip_count }} Quell-IPs · {{ alert.event.label }}</small></div>
<span class="priority-arrow" aria-hidden="true"></span>
</a>
{% endfor %}
</div>
</section>
{% else %}
<section class="priority-board calm">
<div class="priority-intro"><span class="eyebrow">Einsatzfokus</span><h2>Aktuell keine<br>offenen Signale.</h2><p>Die meldenden Systeme liefern derzeit keine auffaelligen Sicherheitsereignisse.</p><div class="priority-sync"><i></i><span>Letzter Datenstand: {{ summary[7] or '-' }}</span></div></div>
<div class="priority-calm"><span class="status-orb"></span><strong>Keine unmittelbare Aktion notwendig</strong><p>Behalte Abdeckung und Berichtsintervall im Blick.</p></div>
</section>
{% endif %}
<section class="panel trend-panel"> <section class="panel trend-panel">
<div class="panel-heading"><span class="eyebrow">Letzte 14 Tage</span><h2>Signalverlauf <small>Verdichtete Scan-Ergebnisse pro Tag</small></h2></div> <div class="panel-heading"><span class="eyebrow">Letzte 14 Tage</span><h2>Signalverlauf <small>Verdichtete Scan-Ergebnisse pro Tag</small></h2></div>
<div class="trend-chart" aria-label="Signalverlauf der letzten 14 Tage">{% for day in trend %}<article class="trend-day"><div class="trend-bar" style="--bar: {{ (day.event_count * 100 / trend_max)|round(0, 'floor') }}%"><span class="trend-critical" style="--critical: {{ (day.critical_count * 100 / trend_max)|round(0, 'floor') }}%"></span><span class="trend-warning" style="--warning: {{ (day.warning_count * 100 / trend_max)|round(0, 'floor') }}%"></span></div><strong>{{ day.event_count }}</strong><small>{{ day.day.strftime('%d.%m.') }}</small></article>{% else %}<p class="empty-state">Noch keine Trenddaten vorhanden.</p>{% endfor %}</div> <div class="trend-chart" aria-label="Signalverlauf der letzten 14 Tage">{% for day in trend %}<article class="trend-day"><div class="trend-bar" style="--bar: {{ (day.event_count * 100 / trend_max)|round(0, 'floor') }}%"><span class="trend-critical" style="--critical: {{ (day.critical_count * 100 / trend_max)|round(0, 'floor') }}%"></span><span class="trend-warning" style="--warning: {{ (day.warning_count * 100 / trend_max)|round(0, 'floor') }}%"></span></div><strong>{{ day.event_count }}</strong><small>{{ day.day.strftime('%d.%m.') }}</small></article>{% else %}<p class="empty-state">Noch keine Trenddaten vorhanden.</p>{% endfor %}</div>
@@ -34,7 +59,7 @@
{% if alerts %} {% if alerts %}
<section class="panel alert-panel"> <section class="panel alert-panel">
<div class="panel-heading"><h2>Auffaellige Geraete <small>{{ current_alert_count }} aktuell, {{ alerts|length - current_alert_count }} historisch</small></h2></div> <div class="panel-heading"><span class="eyebrow">Vollstaendige Liste</span><h2>Auffaellige Geraete <small>{{ current_alert_count }} aktuell, {{ alerts|length - current_alert_count }} historisch</small></h2></div>
<div class="alert-grid"> <div class="alert-grid">
{% for alert in alerts %} {% for alert in alerts %}
<a class="alert-card {{ alert.alert_state }}" href="{{ url_for('device', machine_name=alert.machine_name) }}"> <a class="alert-card {{ alert.alert_state }}" href="{{ url_for('device', machine_name=alert.machine_name) }}">