Show summarized security events above raw export
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 24s
OfficeCom Sentinel Client / build-client-windows (push) Successful in 49s

This commit is contained in:
OfficeCom Codex
2026-07-27 14:42:28 +02:00
parent c74d5582b0
commit 66dcfe09b6
3 changed files with 25 additions and 14 deletions

View File

@@ -171,22 +171,32 @@ def device(machine_name):
abort(404)
payload = report[12]
ninja = payload.get("NinjaOne") or payload.get("ninjaOne") or {}
runtime = payload.get("Runtime") or payload.get("runtime") or {}
raw_export = {
"organization_name": ninja.get("OrganizationName") or ninja.get("organizationName") or "Nicht zugeordnet",
"organization_id": ninja.get("OrganizationId") or ninja.get("organizationId") or "-",
"generated_at": payload.get("GeneratedAtUtc") or payload.get("generatedAtUtc") or report[4],
"client_version": payload.get("ClientVersion") or payload.get("clientVersion") or report[3] or "-",
"upload_status": runtime.get("UploadStatus") or runtime.get("uploadStatus") or "unbekannt",
"error_count": len(payload.get("Errors") or payload.get("errors") or []),
}
event_groups = {}
for entry in payload.get("Events") or payload.get("events") or []:
event_type = entry.get("Target") or entry.get("target") or "Sicherheitsereignis"
account = entry.get("Username") or entry.get("username") or "-"
source_ip = entry.get("SourceIp") or entry.get("sourceIp") or "-"
key = (event_type, account, source_ip)
group = event_groups.setdefault(
key,
{"type": event_type, "account": account, "source_ip": source_ip, "count": 0, "latest": "-"},
)
group["count"] += 1
timestamp = entry.get("Timestamp") or entry.get("timestamp") or "-"
if timestamp > group["latest"]:
group["latest"] = timestamp
security_events = sorted(
event_groups.values(),
key=lambda entry: (entry["latest"], entry["count"]),
reverse=True,
)[:25]
return render_template(
"device.html",
report=report,
event=event_metadata(payload),
payload=payload,
raw_export=raw_export,
security_events=security_events,
payload_pretty=json.dumps(payload, indent=2, ensure_ascii=False),
)

View File

@@ -27,4 +27,4 @@ table { width:100%; border-collapse:collapse; font-family:'Roboto',sans-serif; f
.recipient-form input:focus,.recipient-form select:focus { outline:2px solid rgba(36,90,133,.25); border-color:#245a85; }
.rule-actions .button-secondary { border-color:var(--line); background:#f8fbfd; }
.recipient-intro { max-width:720px; margin:6px 0 28px; }.recipient-intro h1 { margin:9px 0 10px; font-size:46px; line-height:1; letter-spacing:-.055em; }.recipient-intro p { margin:0; color:var(--muted); font-size:16px; line-height:1.55; }.recipient-intro strong { color:var(--ink); }.recipient-create-panel { margin-top:0; border-color:#c8dbe8; }.recipient-create-panel .panel-heading h2,.recipient-rules-panel .panel-heading h2 { margin:7px 0 8px; }.recipient-create-panel .panel-heading p { margin:0 0 20px; }.recipient-form button { white-space:nowrap; }.recipient-rules-panel { padding-bottom:12px; }.recipient-rules-panel .panel-heading { display:flex; align-items:end; justify-content:space-between; gap:16px; }.recipient-rules-panel .panel-heading h2 { margin-bottom:20px; }.recipient-rules-panel .panel-heading small { display:inline-block; margin-left:7px; padding:4px 7px; border-radius:12px; background:#edf4f8; color:#4d687b; font-size:10px; font-weight:700; letter-spacing:.04em; vertical-align:middle; }.recipient-table td { height:64px; }.recipient-table tr:last-child td { border-bottom:0; }.recipient-email { color:#245a85; font-weight:500; }.actions-heading { text-align:right; }.recipient-table .rule-actions { justify-content:flex-end; }.empty-state { padding:30px 10px !important; color:var(--muted); text-align:center; }
.compact-metrics { grid-template-columns:repeat(5,1fr); }.compact-metrics article { min-height:108px; }.compact-metrics article small { display:block; margin-top:6px; color:var(--muted); font:500 11px 'Roboto',sans-serif; }.raw-export-card { border-color:#c8dbe8 !important; background:#f2f8fb !important; }.raw-export-panel { margin-top:8px; }.raw-export-panel .panel-heading h2 { margin:7px 0 8px; }.raw-export-panel .panel-heading p { margin:0 0 20px; }.raw-export-summary { display:grid; grid-template-columns:repeat(4,1fr); gap:10px; }.raw-export-summary article { padding:15px; border:1px solid #dce6ee; border-radius:6px; background:#f8fbfd; }.raw-export-summary span,.raw-export-summary small { display:block; color:var(--muted); font:700 10px 'Roboto',sans-serif; letter-spacing:.07em; text-transform:uppercase; }.raw-export-summary strong { display:block; margin:8px 0 4px; color:var(--ink); font-size:16px; overflow-wrap:anywhere; }.raw-export-summary small { font-weight:500; letter-spacing:0; text-transform:none; }.raw-json { margin-top:18px; border-top:1px solid var(--line); }.raw-json summary { padding:14px 0; color:#245a85; cursor:pointer; font:700 12px 'Roboto',sans-serif; }.raw-json pre { margin-bottom:0; } @media (max-width:850px) { .compact-metrics,.raw-export-summary { grid-template-columns:repeat(2,1fr); }.compact-metrics article:last-child { grid-column:span 2; } }
.compact-metrics { grid-template-columns:repeat(4,1fr); }.event-summary-panel { margin-top:8px; }.event-summary-panel .panel-heading h2,.raw-export-panel .panel-heading h2 { margin:7px 0 8px; }.event-summary-panel .panel-heading p,.raw-export-panel .panel-heading p { margin:0 0 20px; }.event-count { display:inline-grid; min-width:28px; min-height:28px; place-items:center; border-radius:14px; background:#fff0d7; color:var(--amber); font:700 12px 'Roboto',sans-serif; }.raw-export-panel { margin-top:8px; }.raw-json { margin-top:18px; border-top:1px solid var(--line); }.raw-json summary { padding:14px 0; color:#245a85; cursor:pointer; font:700 12px 'Roboto',sans-serif; }.raw-json pre { margin-bottom:0; } @media (max-width:850px) { .compact-metrics { grid-template-columns:repeat(2,1fr); }.compact-metrics article:last-child { grid-column:span 2; } }

View File

@@ -2,6 +2,7 @@
{% block title %}{{ report[0] }} - OC Sentinel{% endblock %}
{% 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="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><article class="raw-export-card"><span>Roh-Export</span><strong>JSON</strong><small>{{ raw_export.upload_status }}</small></article></section>
<section class="panel raw-export-panel"><div class="panel-heading"><span class="eyebrow">Technische Daten</span><h2>Roh-Export</h2><p>Zusammenfassung der zuletzt eingegangenen Clientdaten.</p></div><div class="raw-export-summary"><article><span>Organisation</span><strong>{{ raw_export.organization_name }}</strong><small>ID {{ raw_export.organization_id }}</small></article><article><span>Erstellt</span><strong>{{ raw_export.generated_at }}</strong><small>UTC-Zeitstempel des Clients</small></article><article><span>Client</span><strong>{{ raw_export.client_version }}</strong><small>Upload: {{ raw_export.upload_status }}</small></article><article><span>Scanner-Hinweise</span><strong>{{ raw_export.error_count }}</strong><small>gemeldete Fehler</small></article></div><details class="raw-json"><summary>Vollstaendigen JSON-Export anzeigen</summary><pre>{{ payload_pretty }}</pre></details></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="panel event-summary-panel"><div class="panel-heading"><span class="eyebrow">Schnelluebersicht</span><h2>Erkannte Sicherheitsereignisse</h2><p>Fehlgeschlagene Anmeldungen und weitere Vorfaelle aus dem letzten Scan, nach Konto und Quell-IP zusammengefasst.</p></div><div class="table-wrap"><table><thead><tr><th>Vorfall</th><th>Konto</th><th>Quell-IP</th><th>Letzter Zeitpunkt</th><th>Anzahl</th></tr></thead><tbody>{% for entry in security_events %}<tr><td><strong>{{ entry.type }}</strong></td><td>{{ entry.account }}</td><td>{{ entry.source_ip }}</td><td>{{ entry.latest }}</td><td><span class="event-count">{{ entry.count }}</span></td></tr>{% else %}<tr><td colspan="5" class="empty-state">Keine sicherheitsrelevanten Ereignisse im letzten Scan.</td></tr>{% endfor %}</tbody></table></div></section>
<section class="panel raw-export-panel"><div class="panel-heading"><span class="eyebrow">Technische Daten</span><h2>Roh-Export</h2><p>Vollstaendige, unveraenderte Nutzlast des zuletzt eingegangenen Scans.</p></div><details class="raw-json" open><summary>JSON-Rohdaten</summary><pre>{{ payload_pretty }}</pre></details></section>
{% endblock %}