feat(dashboard): Kacheln und Karten verlinken auf ihre Bereiche

Die Statistik-Kacheln springen jetzt zu ihren Zielen. Organisationen und
Nutzer zur Organisations-Verwaltung, Aktive Lizenzen zum Lizenzen-Tab,
Vorfälle öffnen den Monitor in einem neuen Tab (im Portal gibt es keine
Vorfalls-Seite). Die Karte "Bald ablaufende Lizenzen" führt zum
Lizenzen-Tab, "Letzte Aktivität" zum Audit-Log. Hover-Optik mit
Gold-Rahmen und Chevron, bedienbar auch per Tastatur (Enter/Leertaste).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-07-25 14:07:55 +00:00
Ursprung 8fd087ea0c
Commit 7751657444
3 geänderte Dateien mit 44 neuen und 8 gelöschten Zeilen

Datei anzeigen

@@ -1390,3 +1390,14 @@ details.adv-section .adv-hint { grid-column: 1 / -1; font-size: 12px; color: var
[data-theme="light"] .audit-entry-detail summary { color: #475569; } [data-theme="light"] .audit-entry-detail summary { color: #475569; }
[data-theme="light"] /* === Verwendungs-Sicht (Phase 6) === */ [data-theme="light"] /* === Verwendungs-Sicht (Phase 6) === */
.activity-cell { color: #475569; } .activity-cell { color: #475569; }
/* === Verlinkte Dashboard-Kacheln und -Karten === */
.stat-card-link { cursor: pointer; transition: border-color 0.15s ease, transform 0.15s ease; }
.stat-card-link:hover, .stat-card-link:focus-visible {
border-color: var(--accent);
transform: translateY(-1px);
}
.card-link { cursor: pointer; transition: border-color 0.15s ease; }
.card-link:hover, .card-link:focus-visible { border-color: var(--accent); }
.card-link .card-link-chevron { color: var(--text-muted); transition: color 0.15s ease, transform 0.15s ease; }
.card-link:hover .card-link-chevron { color: var(--accent); transform: translateX(2px); }

Datei anzeigen

@@ -6,7 +6,7 @@
<title>AegisSight Monitor-Verwaltung</title> <title>AegisSight Monitor-Verwaltung</title>
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg"> <link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
<link rel="apple-touch-icon" href="/static/favicon.svg"> <link rel="apple-touch-icon" href="/static/favicon.svg">
<link rel="stylesheet" href="/static/css/style.css?v=20260725a"> <link rel="stylesheet" href="/static/css/style.css?v=20260725b">
<script>(function(){var t=localStorage.getItem('portal_theme');if(t==='light')document.documentElement.setAttribute('data-theme','light');try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}})()</script> <script>(function(){var t=localStorage.getItem('portal_theme');if(t==='light')document.documentElement.setAttribute('data-theme','light');try{var a=JSON.parse(localStorage.getItem('osint_a11y')||'{}');Object.keys(a).forEach(function(k){if(a[k])document.documentElement.setAttribute('data-a11y-'+k,'true');});}catch(e){}})()</script>
<style> <style>
@@ -70,9 +70,10 @@
<div class="stats-grid" id="statsGrid"></div> <div class="stats-grid" id="statsGrid"></div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">
<div class="card"> <div class="card card-link" onclick="gotoSection('licenses')" role="link" tabindex="0" title="Zu den Lizenzen">
<div class="card-header"> <div class="card-header">
<h2>Bald ablaufende Lizenzen</h2> <h2>Bald ablaufende Lizenzen</h2>
<svg class="card-link-chevron" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
</div> </div>
<div class="card-body"> <div class="card-body">
<ul class="expiring-list" id="expiringList"> <ul class="expiring-list" id="expiringList">
@@ -81,9 +82,10 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card card-link" onclick="gotoSection('audit')" role="link" tabindex="0" title="Zum Audit-Log">
<div class="card-header"> <div class="card-header">
<h2>Letzte Aktivität</h2> <h2>Letzte Aktivität</h2>
<svg class="card-link-chevron" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 18 15 12 9 6"/></svg>
</div> </div>
<div class="card-body" id="recentActivity"> <div class="card-body" id="recentActivity">
<div class="text-muted">Laden...</div> <div class="text-muted">Laden...</div>
@@ -1239,7 +1241,7 @@
</div> </div>
<script src="/static/js/a11y.js?v=20260725a"></script> <script src="/static/js/a11y.js?v=20260725a"></script>
<script src="/static/js/app.js?v=20260725b"></script> <script src="/static/js/app.js?v=20260725c"></script>
<script src="/static/js/sources.js?v=20260522x2"></script> <script src="/static/js/sources.js?v=20260522x2"></script>
<script src="/static/js/x-scraper.js?v=20260522a"></script> <script src="/static/js/x-scraper.js?v=20260522a"></script>
<script src="/static/js/source-health.js?v=20260509l"></script> <script src="/static/js/source-health.js?v=20260509l"></script>

Datei anzeigen

@@ -75,6 +75,26 @@ function openRechnerTab() {
if (tab) tab.click(); if (tab) tab.click();
} }
// Tab-Wechsel per Name, nutzt die vorhandene Nav-Logik
function gotoSection(name) {
const tab = document.querySelector(`.nav-tab[data-section="${name}"]`);
if (tab) tab.click();
}
// Dashboard-Kacheln als Sprungziele verdrahten (Klick + Enter/Leertaste)
function wireStatCardLinks() {
document.querySelectorAll("#statsGrid .stat-card-link").forEach((card) => {
const go = () => {
if (card.dataset.href) window.open(card.dataset.href, "_blank", "noopener");
else if (card.dataset.goto) gotoSection(card.dataset.goto);
};
card.addEventListener("click", go);
card.addEventListener("keydown", (e) => {
if (e.key === "Enter" || e.key === " ") { e.preventDefault(); go(); }
});
});
}
// --- State --- // --- State ---
let currentOrgId = null; let currentOrgId = null;
let orgsCache = []; let orgsCache = [];
@@ -147,27 +167,30 @@ async function loadDashboard() {
try { try {
const stats = await API.get("/api/dashboard/stats"); const stats = await API.get("/api/dashboard/stats");
// Kacheln sind Sprungziele. data-goto wechselt den Tab, data-href oeffnet
// extern (Vorfaelle leben im Monitor, nicht im Portal).
document.getElementById("statsGrid").innerHTML = ` document.getElementById("statsGrid").innerHTML = `
<div class="stat-card"> <div class="stat-card stat-card-link" data-goto="orgs" role="link" tabindex="0" title="Zu den Organisationen">
<div class="stat-label">Organisationen</div> <div class="stat-label">Organisationen</div>
<div class="stat-value">${stats.organizations.total}</div> <div class="stat-value">${stats.organizations.total}</div>
<div class="stat-sub">${stats.organizations.active} aktiv</div> <div class="stat-sub">${stats.organizations.active} aktiv</div>
</div> </div>
<div class="stat-card"> <div class="stat-card stat-card-link" data-goto="orgs" role="link" tabindex="0" title="Nutzer werden je Organisation verwaltet">
<div class="stat-label">Nutzer</div> <div class="stat-label">Nutzer</div>
<div class="stat-value">${stats.users.total}</div> <div class="stat-value">${stats.users.total}</div>
<div class="stat-sub">${stats.users.active} aktiv</div> <div class="stat-sub">${stats.users.active} aktiv</div>
</div> </div>
<div class="stat-card"> <div class="stat-card stat-card-link" data-goto="licenses" role="link" tabindex="0" title="Zu den Lizenzen">
<div class="stat-label">Aktive Lizenzen</div> <div class="stat-label">Aktive Lizenzen</div>
<div class="stat-value">${stats.licenses.active}</div> <div class="stat-value">${stats.licenses.active}</div>
</div> </div>
<div class="stat-card"> <div class="stat-card stat-card-link" data-href="https://monitor.aegis-sight.de" role="link" tabindex="0" title="Vorfälle liegen im Monitor (öffnet neuen Tab)">
<div class="stat-label">Vorfälle</div> <div class="stat-label">Vorfälle</div>
<div class="stat-value">${stats.incidents.total}</div> <div class="stat-value">${stats.incidents.total}</div>
<div class="stat-sub">${stats.incidents.active} aktiv</div> <div class="stat-sub">${stats.incidents.active} aktiv</div>
</div> </div>
`; `;
wireStatCardLinks();
// Expiring licenses // Expiring licenses
const expList = document.getElementById("expiringList"); const expList = document.getElementById("expiringList");