2745 lines
112 KiB
HTML
2745 lines
112 KiB
HTML
<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>IT Nexus — Benutzerverwaltung</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500&display=swap" rel="stylesheet" />
|
|
<style>
|
|
:root {
|
|
--bg: #0f172a;
|
|
--bg-deep: #0a1120;
|
|
--card: #1e293b;
|
|
--card-glass: rgba(255, 255, 255, 0.04);
|
|
--card-glass-2: rgba(255, 255, 255, 0.06);
|
|
--line: rgba(255, 255, 255, 0.08);
|
|
--line-strong: rgba(255, 255, 255, 0.14);
|
|
|
|
--teal: #3fa3a3;
|
|
--teal-2: #5dbcbc;
|
|
--teal-glow: rgba(63, 163, 163, 0.18);
|
|
--teal-soft: rgba(63, 163, 163, 0.10);
|
|
|
|
--text: #f1f5f9;
|
|
--text-2: #cbd5e1;
|
|
--text-3: #94a3b8;
|
|
--text-4: #64748b;
|
|
|
|
--green: #34d399;
|
|
--green-soft: rgba(52, 211, 153, 0.14);
|
|
--red: #ef4444;
|
|
--red-soft: rgba(239, 68, 68, 0.14);
|
|
--amber: #f59e0b;
|
|
--amber-soft: rgba(245, 158, 11, 0.14);
|
|
--blue: #60a5fa;
|
|
--blue-soft: rgba(96, 165, 250, 0.14);
|
|
--purple: #a78bfa;
|
|
--purple-soft: rgba(167, 139, 250, 0.14);
|
|
--pink: #f472b6;
|
|
--pink-soft: rgba(244, 114, 182, 0.14);
|
|
|
|
--r-sm: 8px;
|
|
--r: 10px;
|
|
--r-lg: 14px;
|
|
|
|
--sidebar-w: 232px;
|
|
--list-w: 310px;
|
|
--topbar-h: 56px;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
html, body { margin: 0; padding: 0; }
|
|
body {
|
|
background: var(--bg);
|
|
background-image:
|
|
radial-gradient(1200px 700px at 90% -10%, rgba(63,163,163,0.10), transparent 60%),
|
|
radial-gradient(900px 600px at -5% 100%, rgba(63,163,163,0.06), transparent 55%);
|
|
background-attachment: fixed;
|
|
color: var(--text);
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
letter-spacing: -0.005em;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
overflow: hidden;
|
|
}
|
|
::selection { background: var(--teal-glow); color: var(--text); }
|
|
|
|
/* ===== Shell ===== */
|
|
.app {
|
|
display: grid;
|
|
grid-template-columns: var(--sidebar-w) 1fr;
|
|
height: 100vh;
|
|
}
|
|
|
|
/* ===== Sidebar ===== */
|
|
.sidebar {
|
|
background: rgba(15, 23, 42, 0.6);
|
|
backdrop-filter: blur(20px) saturate(160%);
|
|
-webkit-backdrop-filter: blur(20px) saturate(160%);
|
|
border-right: 1px solid var(--line);
|
|
padding: 18px 10px;
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--line-strong) transparent;
|
|
}
|
|
.sidebar::-webkit-scrollbar { width: 6px; }
|
|
.sidebar::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 3px; }
|
|
|
|
.brand {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 4px 10px 22px;
|
|
}
|
|
.brand-mark {
|
|
width: 30px; height: 30px;
|
|
border-radius: 8px;
|
|
background: linear-gradient(135deg, var(--teal-2), var(--teal));
|
|
display: grid; place-items: center;
|
|
color: #fff; font-weight: 700; font-size: 13px;
|
|
box-shadow: 0 4px 14px -4px var(--teal-glow);
|
|
}
|
|
.brand-name { font-weight: 600; font-size: 13.5px; letter-spacing: -0.01em; }
|
|
.brand-sub { font-size: 10.5px; color: var(--text-4); letter-spacing: 0.04em; text-transform: uppercase; margin-top: 1px; }
|
|
|
|
.nav-search {
|
|
display: flex; align-items: center; gap: 8px;
|
|
padding: 7px 10px;
|
|
background: var(--card-glass);
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
margin: 0 4px 18px;
|
|
color: var(--text-3);
|
|
font-size: 12.5px;
|
|
cursor: text;
|
|
}
|
|
.nav-search .kbd {
|
|
margin-left: auto;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 10px;
|
|
padding: 1px 5px;
|
|
background: rgba(255,255,255,0.06);
|
|
border: 1px solid var(--line);
|
|
border-radius: 4px;
|
|
color: var(--text-4);
|
|
}
|
|
|
|
.nav-group { margin-bottom: 14px; }
|
|
.nav-group-title {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--text-4);
|
|
padding: 4px 12px 6px;
|
|
}
|
|
.nav-item {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 6px 11px;
|
|
border-radius: 7px;
|
|
color: var(--text-3);
|
|
text-decoration: none;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
margin-bottom: 1px;
|
|
transition: background 0.12s, color 0.12s;
|
|
position: relative;
|
|
}
|
|
.nav-item:hover { background: var(--card-glass); color: var(--text); }
|
|
.nav-item svg { width: 15px; height: 15px; stroke-width: 1.75; flex-shrink: 0; color: var(--text-4); }
|
|
.nav-item:hover svg { color: var(--text-3); }
|
|
.nav-item.active {
|
|
background: var(--teal-soft);
|
|
color: var(--teal-2);
|
|
box-shadow: inset 0 0 0 1px rgba(63,163,163,0.18);
|
|
}
|
|
.nav-item.active svg { color: var(--teal-2); }
|
|
.nav-badge {
|
|
margin-left: auto;
|
|
font-size: 10.5px;
|
|
padding: 1px 7px;
|
|
background: rgba(255,255,255,0.07);
|
|
border-radius: 999px;
|
|
color: var(--text-2);
|
|
font-weight: 600;
|
|
}
|
|
.nav-badge.danger { background: var(--red); color: #fff; }
|
|
.nav-badge.accent { background: var(--teal); color: #fff; }
|
|
|
|
/* ===== Main ===== */
|
|
.main {
|
|
min-width: 0;
|
|
display: grid;
|
|
grid-template-rows: var(--topbar-h) 1fr;
|
|
overflow: hidden;
|
|
}
|
|
.topbar {
|
|
display: flex; align-items: center;
|
|
padding: 0 24px;
|
|
border-bottom: 1px solid var(--line);
|
|
background: rgba(15, 23, 42, 0.55);
|
|
backdrop-filter: blur(18px) saturate(160%);
|
|
-webkit-backdrop-filter: blur(18px) saturate(160%);
|
|
}
|
|
.breadcrumb {
|
|
display: flex; align-items: center; gap: 8px;
|
|
font-size: 13px; color: var(--text-3);
|
|
}
|
|
.breadcrumb .current { color: var(--text); font-weight: 500; }
|
|
.breadcrumb .sep { color: var(--text-4); }
|
|
|
|
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 4px; }
|
|
.icon-btn {
|
|
width: 32px; height: 32px;
|
|
display: grid; place-items: center;
|
|
border-radius: 8px;
|
|
color: var(--text-3);
|
|
cursor: pointer;
|
|
border: none;
|
|
background: transparent;
|
|
transition: background 0.15s, color 0.15s;
|
|
position: relative;
|
|
}
|
|
.icon-btn:hover { background: var(--card-glass); color: var(--text); }
|
|
.icon-btn svg { width: 16px; height: 16px; stroke-width: 1.75; }
|
|
.icon-btn .dot {
|
|
position: absolute; top: 7px; right: 7px;
|
|
width: 6px; height: 6px;
|
|
background: var(--red);
|
|
border: 1.5px solid var(--bg);
|
|
border-radius: 50%;
|
|
}
|
|
.user-chip {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 4px 4px 4px 12px;
|
|
margin-left: 8px;
|
|
background: var(--card-glass);
|
|
border: 1px solid var(--line);
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
}
|
|
.user-chip .uc-name { font-size: 12.5px; font-weight: 600; color: var(--text); }
|
|
.user-chip .uc-av {
|
|
width: 24px; height: 24px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--teal-2), var(--teal));
|
|
display: grid; place-items: center;
|
|
font-size: 10px; font-weight: 700; color: #fff;
|
|
}
|
|
|
|
/* ===== Page workspace (master-detail) ===== */
|
|
.workspace {
|
|
display: grid;
|
|
grid-template-columns: var(--list-w) 1fr;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ===== User list (left) ===== */
|
|
.list-panel {
|
|
border-right: 1px solid var(--line);
|
|
background: rgba(15, 23, 42, 0.4);
|
|
display: flex; flex-direction: column;
|
|
min-height: 0;
|
|
}
|
|
.list-head {
|
|
padding: 18px 20px 14px;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.list-title-row {
|
|
display: flex; align-items: baseline; justify-content: space-between;
|
|
margin-bottom: 4px;
|
|
}
|
|
.list-title {
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
letter-spacing: -0.018em;
|
|
}
|
|
.list-count {
|
|
font-size: 12px;
|
|
color: var(--text-4);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.list-sub {
|
|
font-size: 12px;
|
|
color: var(--text-3);
|
|
margin-bottom: 14px;
|
|
}
|
|
.list-search {
|
|
position: relative;
|
|
margin-bottom: 10px;
|
|
}
|
|
.list-search svg {
|
|
position: absolute; left: 10px; top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 14px; height: 14px;
|
|
color: var(--text-4);
|
|
pointer-events: none;
|
|
}
|
|
.list-search input {
|
|
width: 100%;
|
|
padding: 8px 10px 8px 32px;
|
|
background: var(--card-glass);
|
|
border: 1px solid var(--line);
|
|
border-radius: 8px;
|
|
color: var(--text);
|
|
font-family: inherit;
|
|
font-size: 13px;
|
|
outline: none;
|
|
transition: border-color 0.15s, background 0.15s;
|
|
}
|
|
.list-search input:focus {
|
|
border-color: rgba(63,163,163,0.4);
|
|
background: var(--card-glass-2);
|
|
box-shadow: 0 0 0 3px var(--teal-soft);
|
|
}
|
|
.list-search input::placeholder { color: var(--text-4); }
|
|
.filter-row {
|
|
display: flex; gap: 6px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.filter-pill {
|
|
border: 1px solid var(--line);
|
|
background: transparent;
|
|
color: var(--text-3);
|
|
font-size: 11.5px;
|
|
font-weight: 500;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: all 0.15s;
|
|
}
|
|
.filter-pill:hover { color: var(--text); border-color: var(--line-strong); }
|
|
.filter-pill.on {
|
|
background: rgba(63,163,163,0.14);
|
|
border-color: rgba(63,163,163,0.35);
|
|
color: var(--teal-2);
|
|
}
|
|
|
|
.list-scroll {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--line-strong) transparent;
|
|
padding: 6px 8px 24px;
|
|
}
|
|
.list-scroll::-webkit-scrollbar { width: 6px; }
|
|
.list-scroll::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 3px; }
|
|
|
|
.list-section-label {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
color: var(--text-4);
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
padding: 14px 12px 6px;
|
|
}
|
|
|
|
.user-row {
|
|
display: flex; align-items: center; gap: 11px;
|
|
padding: 9px 12px 9px 11px;
|
|
border-radius: 9px;
|
|
cursor: pointer;
|
|
position: relative;
|
|
border: 1px solid transparent;
|
|
transition: background 0.12s, border-color 0.12s;
|
|
}
|
|
.user-row:hover { background: var(--card-glass); }
|
|
.user-row .ur-av {
|
|
width: 34px; height: 34px;
|
|
border-radius: 50%;
|
|
display: grid; place-items: center;
|
|
font-size: 12px; font-weight: 700;
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
background: var(--av-bg, linear-gradient(135deg, var(--teal-2), var(--teal)));
|
|
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.08);
|
|
}
|
|
.user-row .ur-body { flex: 1; min-width: 0; }
|
|
.user-row .ur-name {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
letter-spacing: -0.005em;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.user-row .ur-meta {
|
|
display: flex; align-items: center; gap: 6px;
|
|
font-size: 11.5px;
|
|
color: var(--text-4);
|
|
margin-top: 1px;
|
|
}
|
|
.user-row .ur-meta .dot {
|
|
width: 3px; height: 3px; border-radius: 50%;
|
|
background: var(--text-4);
|
|
flex-shrink: 0;
|
|
}
|
|
.user-row .ur-meta .email {
|
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
min-width: 0;
|
|
}
|
|
.user-row .ur-presence {
|
|
position: absolute;
|
|
bottom: 8px; left: 31px;
|
|
width: 9px; height: 9px;
|
|
border-radius: 50%;
|
|
background: var(--text-4);
|
|
border: 2px solid var(--bg);
|
|
box-shadow: 0 0 0 1px var(--line);
|
|
}
|
|
.user-row .ur-presence.online { background: var(--green); }
|
|
.user-row .ur-presence.away { background: var(--amber); }
|
|
|
|
.user-row.active {
|
|
background: var(--teal-soft);
|
|
border-color: rgba(63,163,163,0.22);
|
|
box-shadow: inset 3px 0 0 var(--teal), 0 0 24px -8px var(--teal-glow);
|
|
}
|
|
.user-row.active .ur-name { color: var(--text); }
|
|
|
|
/* role tints for avatars */
|
|
.av-admin { background: linear-gradient(135deg, #f87171, #dc2626); }
|
|
.av-manager { background: linear-gradient(135deg, var(--teal-2), var(--teal)); }
|
|
.av-dev { background: linear-gradient(135deg, #c4b5fd, #8b5cf6); }
|
|
.av-staff { background: linear-gradient(135deg, #93c5fd, #3b82f6); }
|
|
.av-intern { background: linear-gradient(135deg, #94a3b8, #64748b); }
|
|
.av-extern { background: linear-gradient(135deg, #fcd34d, #d97706); }
|
|
|
|
.role-chip {
|
|
display: inline-flex; align-items: center; gap: 5px;
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
padding: 1.5px 7px;
|
|
border-radius: 999px;
|
|
letter-spacing: 0.01em;
|
|
flex-shrink: 0;
|
|
}
|
|
.role-chip.admin { background: var(--red-soft); color: #fca5a5; }
|
|
.role-chip.manager { background: var(--teal-soft); color: var(--teal-2); box-shadow: inset 0 0 0 1px rgba(63,163,163,0.25); }
|
|
.role-chip.dev { background: var(--purple-soft);color: #c4b5fd; }
|
|
.role-chip.staff { background: var(--blue-soft); color: #93c5fd; }
|
|
.role-chip.intern { background: rgba(148,163,184,0.14); color: #cbd5e1; }
|
|
.role-chip.extern { background: var(--amber-soft); color: #fcd34d; }
|
|
|
|
/* ===== Detail panel ===== */
|
|
.detail-panel {
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--line-strong) transparent;
|
|
min-width: 0;
|
|
}
|
|
.detail-panel::-webkit-scrollbar { width: 8px; }
|
|
.detail-panel::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 4px; }
|
|
|
|
.detail-inner {
|
|
padding: 28px 36px 56px;
|
|
max-width: 1240px;
|
|
}
|
|
|
|
/* Detail header */
|
|
.detail-head {
|
|
display: flex; align-items: flex-start; gap: 20px;
|
|
flex-wrap: wrap;
|
|
padding-bottom: 24px;
|
|
border-bottom: 1px solid var(--line);
|
|
margin-bottom: 0;
|
|
}
|
|
.dh-avatar {
|
|
width: 60px; height: 60px;
|
|
border-radius: 50%;
|
|
display: grid; place-items: center;
|
|
font-size: 20px; font-weight: 700; color: #fff;
|
|
flex-shrink: 0;
|
|
box-shadow: 0 0 0 1px rgba(255,255,255,0.1), 0 8px 24px -6px var(--teal-glow);
|
|
position: relative;
|
|
}
|
|
.dh-avatar .dh-presence {
|
|
position: absolute;
|
|
right: 0; bottom: 2px;
|
|
width: 14px; height: 14px;
|
|
border-radius: 50%;
|
|
background: var(--green);
|
|
border: 3px solid var(--bg);
|
|
}
|
|
.dh-body { flex: 1 1 280px; min-width: 0; padding-top: 2px; }
|
|
.dh-name {
|
|
font-size: 26px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
letter-spacing: -0.025em;
|
|
line-height: 1.15;
|
|
margin: 0 0 6px;
|
|
}
|
|
.dh-name-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
|
.dh-handle {
|
|
font-size: 12.5px;
|
|
color: var(--text-4);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
padding: 2px 8px;
|
|
background: var(--card-glass);
|
|
border: 1px solid var(--line);
|
|
border-radius: 6px;
|
|
}
|
|
.dh-email {
|
|
font-size: 13.5px;
|
|
color: var(--text-3);
|
|
margin: 0 0 12px;
|
|
display: flex; align-items: center; gap: 6px;
|
|
}
|
|
.dh-email svg { width: 13px; height: 13px; color: var(--text-4); }
|
|
.dh-badges { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
.dh-badge {
|
|
display: inline-flex; align-items: center; gap: 6px;
|
|
font-size: 11.5px;
|
|
font-weight: 600;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
background: var(--card-glass);
|
|
border: 1px solid var(--line);
|
|
color: var(--text-2);
|
|
}
|
|
.dh-badge svg { width: 12px; height: 12px; }
|
|
.dh-badge.role-manager { background: var(--teal-soft); color: var(--teal-2); border-color: rgba(63,163,163,0.3); }
|
|
.dh-badge.dept { color: var(--text-2); }
|
|
.dh-badge.status-active { background: var(--green-soft); color: var(--green); border-color: rgba(52,211,153,0.25); }
|
|
.dh-badge.status-active .pulse { width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 0 0 rgba(52,211,153,0.5); animation: pulse 1.8s infinite; }
|
|
@keyframes pulse {
|
|
0% { box-shadow: 0 0 0 0 rgba(52,211,153,0.5); }
|
|
70% { box-shadow: 0 0 0 6px rgba(52,211,153,0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); }
|
|
}
|
|
.dh-actions { display: flex; gap: 8px; padding-top: 4px; }
|
|
.btn {
|
|
display: inline-flex; align-items: center; gap: 7px;
|
|
padding: 8px 14px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
border: 1px solid var(--line);
|
|
background: var(--card-glass);
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
font-family: inherit;
|
|
transition: background 0.15s, border-color 0.15s, transform 0.06s;
|
|
white-space: nowrap;
|
|
}
|
|
.btn:hover { background: var(--card-glass-2); border-color: var(--line-strong); }
|
|
.btn:active { transform: scale(0.985); }
|
|
.btn svg { width: 14px; height: 14px; stroke-width: 1.75; }
|
|
.btn.primary {
|
|
background: var(--teal);
|
|
border-color: var(--teal);
|
|
color: #fff;
|
|
box-shadow: 0 0 0 1px rgba(63,163,163,0.4), 0 8px 24px -8px var(--teal-glow);
|
|
}
|
|
.btn.primary:hover { background: var(--teal-2); border-color: var(--teal-2); }
|
|
.btn.danger { color: #fca5a5; }
|
|
.btn.danger:hover { background: var(--red-soft); border-color: rgba(239,68,68,0.35); color: #fecaca; }
|
|
|
|
/* ===== Tabs ===== */
|
|
.tabs-bar {
|
|
display: flex;
|
|
gap: 4px;
|
|
margin-top: 22px;
|
|
margin-bottom: 24px;
|
|
border-bottom: 1px solid var(--line);
|
|
position: relative;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
}
|
|
.tabs-bar::-webkit-scrollbar { display: none; }
|
|
.tab {
|
|
padding: 10px 14px;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text-3);
|
|
font-family: inherit;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
margin-bottom: -1px;
|
|
display: inline-flex; align-items: center; gap: 8px;
|
|
transition: color 0.15s;
|
|
letter-spacing: -0.005em;
|
|
}
|
|
.tab:hover { color: var(--text); }
|
|
.tab.active {
|
|
color: var(--teal-2);
|
|
border-bottom-color: var(--teal);
|
|
}
|
|
.tab .tab-count {
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
padding: 1px 6px;
|
|
border-radius: 999px;
|
|
background: var(--card-glass-2);
|
|
color: var(--text-3);
|
|
}
|
|
.tab.active .tab-count {
|
|
background: var(--teal-soft);
|
|
color: var(--teal-2);
|
|
}
|
|
|
|
/* ===== Tab content ===== */
|
|
.tab-panel { display: none; }
|
|
.tab-panel.active { display: block; animation: fadein 0.2s ease; }
|
|
@keyframes fadein { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
|
|
|
|
/* Stat row */
|
|
.stat-row {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.stat {
|
|
padding: 16px 18px;
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.stat-head {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
margin-bottom: 10px;
|
|
}
|
|
.stat-icon {
|
|
width: 28px; height: 28px;
|
|
border-radius: 7px;
|
|
background: var(--s-bg, var(--card-glass-2));
|
|
color: var(--s-color, var(--text-2));
|
|
display: grid; place-items: center;
|
|
}
|
|
.stat-icon svg { width: 14px; height: 14px; stroke-width: 1.75; }
|
|
.stat-trend {
|
|
font-size: 11px;
|
|
color: var(--text-3);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.stat-trend.up { color: var(--green); }
|
|
.stat-trend.down { color: var(--red); }
|
|
.stat-value {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
letter-spacing: -0.025em;
|
|
line-height: 1;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.stat-value .u { font-size: 13px; color: var(--text-3); font-weight: 500; margin-left: 4px; }
|
|
.stat-label {
|
|
font-size: 12px;
|
|
color: var(--text-3);
|
|
margin-top: 6px;
|
|
}
|
|
.s-status { --s-bg: var(--green-soft); --s-color: var(--green); }
|
|
.s-assets { --s-bg: var(--blue-soft); --s-color: var(--blue); }
|
|
.s-lic { --s-bg: var(--purple-soft); --s-color: var(--purple); }
|
|
.s-fido { --s-bg: var(--amber-soft); --s-color: var(--amber); }
|
|
|
|
/* Grid */
|
|
.grid-2 {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 14px;
|
|
margin-bottom: 14px;
|
|
}
|
|
.grid-3 {
|
|
display: grid;
|
|
grid-template-columns: 1.4fr 1fr;
|
|
gap: 14px;
|
|
}
|
|
|
|
/* Card (glass) */
|
|
.card {
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
padding: 20px 22px;
|
|
}
|
|
.card-head {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
}
|
|
.card-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
letter-spacing: -0.005em;
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.card-title svg { width: 14px; height: 14px; color: var(--text-3); }
|
|
.card-actions { display: flex; gap: 4px; }
|
|
.card-link {
|
|
font-size: 12px;
|
|
color: var(--teal-2);
|
|
text-decoration: none;
|
|
font-weight: 500;
|
|
}
|
|
.card-link:hover { color: var(--teal); }
|
|
|
|
/* Info rows */
|
|
.info-list { display: flex; flex-direction: column; }
|
|
.info-row {
|
|
display: grid;
|
|
grid-template-columns: 140px 1fr;
|
|
gap: 16px;
|
|
padding: 9px 0;
|
|
font-size: 13px;
|
|
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
}
|
|
.info-row:last-child { border-bottom: none; padding-bottom: 0; }
|
|
.info-row:first-child { padding-top: 0; }
|
|
.info-row .k {
|
|
color: var(--text-4);
|
|
font-weight: 500;
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.info-row .k svg { width: 13px; height: 13px; }
|
|
.info-row .v {
|
|
color: var(--text);
|
|
font-weight: 500;
|
|
min-width: 0;
|
|
display: flex; align-items: center; gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.info-row .v.mono { font-family: 'JetBrains Mono', monospace; font-size: 12.5px; color: var(--text-2); }
|
|
.info-row .v .mini-av {
|
|
width: 20px; height: 20px;
|
|
border-radius: 50%;
|
|
display: grid; place-items: center;
|
|
font-size: 9px; font-weight: 700;
|
|
color: #fff;
|
|
flex-shrink: 0;
|
|
}
|
|
.copy-btn {
|
|
width: 22px; height: 22px;
|
|
display: inline-grid; place-items: center;
|
|
border: 1px solid var(--line);
|
|
background: transparent;
|
|
border-radius: 5px;
|
|
color: var(--text-4);
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: opacity 0.15s, color 0.15s;
|
|
}
|
|
.info-row:hover .copy-btn { opacity: 1; }
|
|
.copy-btn:hover { color: var(--text); }
|
|
.copy-btn svg { width: 11px; height: 11px; }
|
|
|
|
/* Security item */
|
|
.sec-list { display: flex; flex-direction: column; gap: 10px; }
|
|
.sec-item {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 12px 14px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
border-radius: 10px;
|
|
}
|
|
.sec-icon {
|
|
width: 32px; height: 32px;
|
|
border-radius: 8px;
|
|
background: var(--si-bg, var(--card-glass-2));
|
|
color: var(--si-color, var(--text-2));
|
|
display: grid; place-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.sec-icon svg { width: 15px; height: 15px; stroke-width: 1.75; }
|
|
.sec-body { flex: 1; min-width: 0; }
|
|
.sec-label { font-size: 12.5px; font-weight: 600; color: var(--text); margin-bottom: 1px; }
|
|
.sec-sub { font-size: 11.5px; color: var(--text-4); }
|
|
.sec-status {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
padding: 3px 9px;
|
|
border-radius: 999px;
|
|
flex-shrink: 0;
|
|
}
|
|
.sec-status.ok { background: var(--green-soft); color: var(--green); }
|
|
.sec-status.warn { background: var(--amber-soft); color: var(--amber); }
|
|
.sec-status.off { background: rgba(148,163,184,0.14); color: var(--text-3); }
|
|
.si-ok { --si-bg: var(--green-soft); --si-color: var(--green); }
|
|
.si-warn { --si-bg: var(--amber-soft); --si-color: var(--amber); }
|
|
.si-key { --si-bg: var(--teal-soft); --si-color: var(--teal-2); }
|
|
.si-pwd { --si-bg: var(--blue-soft); --si-color: var(--blue); }
|
|
|
|
/* Activity */
|
|
.activity-list {
|
|
display: flex; flex-direction: column;
|
|
position: relative;
|
|
}
|
|
.activity-list::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 13px; top: 14px; bottom: 14px;
|
|
width: 1px;
|
|
background: var(--line);
|
|
}
|
|
.activity {
|
|
display: grid;
|
|
grid-template-columns: 28px 1fr auto;
|
|
gap: 12px;
|
|
align-items: flex-start;
|
|
padding: 8px 0;
|
|
position: relative;
|
|
}
|
|
.act-dot {
|
|
width: 28px; height: 28px;
|
|
border-radius: 50%;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
display: grid; place-items: center;
|
|
color: var(--ad-color, var(--text-3));
|
|
z-index: 1;
|
|
}
|
|
.act-dot svg { width: 12px; height: 12px; stroke-width: 2; }
|
|
.ad-login { --ad-color: var(--green); }
|
|
.ad-pwd { --ad-color: var(--amber); }
|
|
.ad-asset { --ad-color: var(--blue); }
|
|
.ad-key { --ad-color: var(--teal-2); }
|
|
.ad-perm { --ad-color: var(--purple); }
|
|
.act-body { padding: 4px 0; }
|
|
.act-text { font-size: 13px; color: var(--text-2); line-height: 1.4; }
|
|
.act-text b { color: var(--text); font-weight: 600; }
|
|
.act-meta { font-size: 11.5px; color: var(--text-4); margin-top: 2px; font-family: 'JetBrains Mono', monospace; }
|
|
.act-time { font-size: 11.5px; color: var(--text-4); padding-top: 6px; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
|
|
/* Groups */
|
|
.group-list {
|
|
display: flex; flex-wrap: wrap; gap: 8px;
|
|
}
|
|
.group-chip {
|
|
display: inline-flex; align-items: center; gap: 7px;
|
|
padding: 6px 12px 6px 8px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
color: var(--text-2);
|
|
font-weight: 500;
|
|
}
|
|
.group-chip .g-dot {
|
|
width: 6px; height: 6px;
|
|
border-radius: 50%;
|
|
background: var(--gc-color, var(--text-3));
|
|
}
|
|
.group-chip.entra { --gc-color: var(--blue); }
|
|
.group-chip.ad { --gc-color: var(--purple); }
|
|
.group-chip.role { --gc-color: var(--teal-2); }
|
|
.group-chip.dist { --gc-color: var(--amber); }
|
|
|
|
.group-section { margin-bottom: 16px; }
|
|
.group-section:last-child { margin-bottom: 0; }
|
|
.group-section h4 {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: var(--text-4);
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
/* Permissions */
|
|
.perm-list { display: flex; flex-direction: column; gap: 6px; }
|
|
.perm-item {
|
|
display: flex; align-items: center; gap: 10px;
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid transparent;
|
|
transition: border-color 0.15s;
|
|
}
|
|
.perm-item:hover { border-color: var(--line); }
|
|
.perm-item .p-check {
|
|
width: 16px; height: 16px;
|
|
border-radius: 4px;
|
|
background: var(--green-soft);
|
|
color: var(--green);
|
|
display: grid; place-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.perm-item .p-check svg { width: 10px; height: 10px; stroke-width: 3; }
|
|
.perm-item.off .p-check { background: rgba(148,163,184,0.14); color: var(--text-4); }
|
|
.perm-item .p-label { font-size: 12.5px; color: var(--text-2); flex: 1; }
|
|
.perm-item.off .p-label { color: var(--text-4); }
|
|
.perm-item .p-scope { font-size: 11px; color: var(--text-4); font-family: 'JetBrains Mono', monospace; }
|
|
|
|
/* Empty hint for non-Übersicht tabs */
|
|
.empty-hint {
|
|
padding: 60px 20px;
|
|
text-align: center;
|
|
color: var(--text-3);
|
|
border: 1px dashed var(--line);
|
|
border-radius: var(--r-lg);
|
|
}
|
|
.empty-hint svg { width: 28px; height: 28px; color: var(--text-4); margin-bottom: 12px; }
|
|
.empty-hint h3 { font-size: 15px; font-weight: 600; color: var(--text-2); margin: 0 0 4px; }
|
|
.empty-hint p { font-size: 13px; margin: 0; max-width: 360px; margin-inline: auto; }
|
|
|
|
/* Asset table compact */
|
|
.mini-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
.mini-table th {
|
|
text-align: left;
|
|
padding: 8px 14px;
|
|
background: var(--card-glass-2);
|
|
color: var(--text-4);
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
border-bottom: 1px solid var(--line);
|
|
}
|
|
.mini-table td {
|
|
padding: 11px 14px;
|
|
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
color: var(--text-2);
|
|
vertical-align: middle;
|
|
}
|
|
.mini-table tr:last-child td { border-bottom: none; }
|
|
.mini-table tr:hover td { background: var(--card-glass); }
|
|
.mini-table .asset-name { font-weight: 600; color: var(--text); }
|
|
.mini-table .asset-sub { font-size: 11.5px; color: var(--text-4); margin-top: 1px; }
|
|
.mini-table .mono { font-family: 'JetBrains Mono', monospace; font-size: 11.5px; }
|
|
.table-card { padding: 0; overflow: hidden; }
|
|
|
|
/* ===== Assets tab ===== */
|
|
.asset-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.device-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
.device-card {
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
padding: 18px;
|
|
position: relative;
|
|
transition: border-color 0.15s, transform 0.15s;
|
|
cursor: pointer;
|
|
}
|
|
.device-card:hover { border-color: var(--line-strong); transform: translateY(-1px); }
|
|
.device-card .dc-top {
|
|
display: flex; align-items: flex-start; gap: 12px;
|
|
margin-bottom: 14px;
|
|
}
|
|
.device-icon {
|
|
width: 44px; height: 44px;
|
|
border-radius: 10px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
color: var(--text-2);
|
|
display: grid; place-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.device-icon svg { width: 22px; height: 22px; stroke-width: 1.5; }
|
|
.device-icon.nb { color: var(--blue); background: var(--blue-soft); border-color: rgba(96,165,250,0.2); }
|
|
.device-icon.mon { color: var(--purple); background: var(--purple-soft); border-color: rgba(167,139,250,0.2); }
|
|
.device-icon.phn { color: var(--green); background: var(--green-soft); border-color: rgba(52,211,153,0.2); }
|
|
.device-icon.key { color: var(--amber); background: var(--amber-soft); border-color: rgba(245,158,11,0.2); }
|
|
.device-icon.dock { color: var(--pink); background: var(--pink-soft); border-color: rgba(244,114,182,0.2); }
|
|
.dc-meta { flex: 1; min-width: 0; }
|
|
.dc-name {
|
|
font-size: 14px; font-weight: 600; color: var(--text);
|
|
letter-spacing: -0.005em; line-height: 1.2;
|
|
margin-bottom: 3px;
|
|
}
|
|
.dc-model {
|
|
font-size: 12px; color: var(--text-3);
|
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.dc-status-dot {
|
|
width: 8px; height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--green);
|
|
box-shadow: 0 0 8px var(--green);
|
|
flex-shrink: 0;
|
|
margin-top: 4px;
|
|
}
|
|
.dc-status-dot.off { background: var(--text-4); box-shadow: none; }
|
|
.dc-status-dot.warn { background: var(--amber); box-shadow: 0 0 8px var(--amber); }
|
|
|
|
.dc-health {
|
|
display: flex; flex-direction: column; gap: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
.dc-bar {
|
|
display: flex; flex-direction: column; gap: 4px;
|
|
}
|
|
.dc-bar-label {
|
|
display: flex; justify-content: space-between;
|
|
font-size: 11px; color: var(--text-4);
|
|
}
|
|
.dc-bar-label b { color: var(--text-2); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
.dc-bar-track {
|
|
height: 4px;
|
|
background: rgba(255,255,255,0.05);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
.dc-bar-fill {
|
|
height: 100%;
|
|
background: var(--bar-color, var(--teal));
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.dc-footer {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding-top: 12px;
|
|
border-top: 1px solid var(--line);
|
|
font-size: 11px;
|
|
color: var(--text-4);
|
|
font-family: 'JetBrains Mono', monospace;
|
|
}
|
|
.dc-tag {
|
|
display: inline-flex; align-items: center; gap: 4px;
|
|
font-family: 'Inter', sans-serif;
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
padding: 2px 7px;
|
|
border-radius: 999px;
|
|
background: var(--card-glass-2);
|
|
color: var(--text-3);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
|
|
/* ===== Licenses tab ===== */
|
|
.lic-summary {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
gap: 12px;
|
|
margin-bottom: 22px;
|
|
padding: 18px 22px;
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
}
|
|
.lic-summary > div { padding: 0 8px; }
|
|
.lic-summary > div + div { border-left: 1px solid var(--line); }
|
|
.lic-summary .ls-label {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: var(--text-4);
|
|
margin-bottom: 6px;
|
|
}
|
|
.lic-summary .ls-value {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
letter-spacing: -0.025em;
|
|
line-height: 1.1;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.lic-summary .ls-sub {
|
|
font-size: 11.5px;
|
|
color: var(--text-3);
|
|
margin-top: 3px;
|
|
}
|
|
|
|
.lic-cat-title {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--text-4);
|
|
margin: 18px 0 10px;
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.lic-cat-title:first-child { margin-top: 0; }
|
|
.lic-cat-title .ct-count {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
color: var(--text-4);
|
|
font-weight: 500;
|
|
letter-spacing: 0;
|
|
}
|
|
.lic-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 10px;
|
|
}
|
|
.lic-card {
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: 12px;
|
|
padding: 14px 16px;
|
|
display: flex; align-items: center; gap: 12px;
|
|
transition: border-color 0.15s;
|
|
}
|
|
.lic-card:hover { border-color: var(--line-strong); }
|
|
.lic-logo {
|
|
width: 38px; height: 38px;
|
|
border-radius: 9px;
|
|
display: grid; place-items: center;
|
|
font-size: 14px; font-weight: 700; color: #fff;
|
|
flex-shrink: 0;
|
|
background: var(--lc-bg, linear-gradient(135deg, var(--teal-2), var(--teal)));
|
|
}
|
|
.lic-body { flex: 1; min-width: 0; }
|
|
.lic-name {
|
|
font-size: 13px; font-weight: 600; color: var(--text);
|
|
letter-spacing: -0.005em;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.lic-plan {
|
|
font-size: 11.5px; color: var(--text-4);
|
|
margin-top: 1px;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.lic-cost {
|
|
font-size: 12px; font-weight: 600;
|
|
color: var(--text-2);
|
|
font-variant-numeric: tabular-nums;
|
|
text-align: right;
|
|
flex-shrink: 0;
|
|
}
|
|
.lic-cost .pm { font-size: 10.5px; color: var(--text-4); font-weight: 500; display: block; margin-top: 1px; }
|
|
.lc-m365 { --lc-bg: linear-gradient(135deg, #5b9bff, #2563eb); }
|
|
.lc-adobe { --lc-bg: linear-gradient(135deg, #ef4444, #b91c1c); }
|
|
.lc-github{ --lc-bg: linear-gradient(135deg, #4a4a52, #1f2937); }
|
|
.lc-figma { --lc-bg: linear-gradient(135deg, #f472b6, #a855f7); }
|
|
.lc-jb { --lc-bg: linear-gradient(135deg, #fb7185, #f59e0b); }
|
|
.lc-slack { --lc-bg: linear-gradient(135deg, #a78bfa, #7c3aed); }
|
|
.lc-1pwd { --lc-bg: linear-gradient(135deg, #38bdf8, #0284c7); }
|
|
.lc-aws { --lc-bg: linear-gradient(135deg, #fcd34d, #f59e0b); color: #1f2937; }
|
|
.lc-zoom { --lc-bg: linear-gradient(135deg, #60a5fa, #1d4ed8); }
|
|
.lc-tw { --lc-bg: linear-gradient(135deg, #14b8a6, #0d9488); }
|
|
.lc-no { --lc-bg: linear-gradient(135deg, #94a3b8, #475569); }
|
|
.lc-int { --lc-bg: linear-gradient(135deg, #f472b6, #db2777); }
|
|
|
|
/* ===== Entra/AD tab ===== */
|
|
.entra-grid {
|
|
display: grid;
|
|
grid-template-columns: 1.2fr 1fr;
|
|
gap: 14px;
|
|
margin-bottom: 14px;
|
|
}
|
|
.identity-card {
|
|
background:
|
|
radial-gradient(400px 200px at 100% 0%, rgba(63,163,163,0.08), transparent 60%),
|
|
var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
padding: 22px;
|
|
}
|
|
.sync-diagram {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto 1fr;
|
|
gap: 14px;
|
|
align-items: center;
|
|
padding: 16px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
border-radius: 10px;
|
|
margin-bottom: 18px;
|
|
}
|
|
.sync-node {
|
|
text-align: center;
|
|
}
|
|
.sync-node .sn-icon {
|
|
width: 44px; height: 44px;
|
|
margin: 0 auto 6px;
|
|
border-radius: 11px;
|
|
display: grid; place-items: center;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
color: var(--text-2);
|
|
}
|
|
.sync-node.cloud .sn-icon { color: var(--blue); background: var(--blue-soft); border-color: rgba(96,165,250,0.25); }
|
|
.sync-node.ad .sn-icon { color: var(--purple); background: var(--purple-soft); border-color: rgba(167,139,250,0.25); }
|
|
.sync-node .sn-icon svg { width: 22px; height: 22px; stroke-width: 1.5; }
|
|
.sync-node .sn-label { font-size: 11.5px; color: var(--text-3); font-weight: 600; }
|
|
.sync-node .sn-id { font-size: 10.5px; color: var(--text-4); font-family: 'JetBrains Mono', monospace; margin-top: 2px; }
|
|
.sync-arrow {
|
|
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
|
color: var(--teal-2);
|
|
}
|
|
.sync-arrow .line {
|
|
width: 60px; height: 2px;
|
|
background: linear-gradient(90deg, transparent, var(--teal), transparent);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.sync-arrow .line::after {
|
|
content: '';
|
|
position: absolute; top: 0; left: -20%;
|
|
width: 20%; height: 100%;
|
|
background: var(--teal-2);
|
|
box-shadow: 0 0 8px var(--teal);
|
|
animation: flow 2s linear infinite;
|
|
}
|
|
@keyframes flow { from { left: -20%; } to { left: 100%; } }
|
|
.sync-arrow .lbl { font-size: 9.5px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; }
|
|
|
|
.ca-list { display: flex; flex-direction: column; gap: 8px; }
|
|
.ca-policy {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 11px 13px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
border-radius: 9px;
|
|
}
|
|
.ca-policy .cap-icon {
|
|
width: 28px; height: 28px;
|
|
border-radius: 7px;
|
|
display: grid; place-items: center;
|
|
background: var(--green-soft);
|
|
color: var(--green);
|
|
flex-shrink: 0;
|
|
}
|
|
.ca-policy .cap-icon svg { width: 13px; height: 13px; stroke-width: 2.5; }
|
|
.ca-policy.blocked .cap-icon { background: var(--red-soft); color: var(--red); }
|
|
.ca-policy.report .cap-icon { background: var(--amber-soft); color: var(--amber); }
|
|
.ca-policy .cap-body { flex: 1; min-width: 0; }
|
|
.ca-policy .cap-name { font-size: 12.5px; font-weight: 600; color: var(--text); }
|
|
.ca-policy .cap-cond { font-size: 11px; color: var(--text-4); margin-top: 1px; }
|
|
.ca-policy .cap-mode {
|
|
font-size: 10px; font-weight: 600;
|
|
padding: 2px 7px;
|
|
border-radius: 999px;
|
|
background: var(--green-soft);
|
|
color: var(--green);
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
flex-shrink: 0;
|
|
}
|
|
.ca-policy.report .cap-mode { background: var(--amber-soft); color: var(--amber); }
|
|
|
|
.token-preview {
|
|
margin-top: 14px;
|
|
padding: 14px 16px;
|
|
background: rgba(0,0,0,0.25);
|
|
border: 1px solid var(--line);
|
|
border-radius: 9px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 11.5px;
|
|
line-height: 1.6;
|
|
color: var(--text-2);
|
|
overflow-x: auto;
|
|
}
|
|
.token-preview .tk { color: var(--teal-2); }
|
|
.token-preview .tv { color: #fcd34d; }
|
|
.token-preview .tn { color: var(--text-4); }
|
|
|
|
/* ===== FIDO tab ===== */
|
|
.fido-hero {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
gap: 12px;
|
|
margin-bottom: 22px;
|
|
}
|
|
.fido-key-card {
|
|
background:
|
|
radial-gradient(300px 160px at 100% 0%, rgba(245,158,11,0.08), transparent 60%),
|
|
var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
padding: 22px;
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
.fido-key-card.primary { box-shadow: 0 0 0 1px rgba(63,163,163,0.18), 0 16px 40px -16px var(--teal-glow); border-color: rgba(63,163,163,0.25); }
|
|
.fkc-head {
|
|
display: flex; align-items: flex-start; justify-content: space-between;
|
|
margin-bottom: 18px;
|
|
}
|
|
.fkc-visual {
|
|
width: 56px; height: 56px;
|
|
border-radius: 12px;
|
|
background: linear-gradient(140deg, #fcd34d 0%, #f59e0b 50%, #b45309 100%);
|
|
display: grid; place-items: center;
|
|
box-shadow: 0 12px 28px -10px rgba(245,158,11,0.4), inset 0 1px 0 rgba(255,255,255,0.3);
|
|
color: #422006;
|
|
position: relative;
|
|
}
|
|
.fkc-visual::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 18px; height: 18px;
|
|
border-radius: 50%;
|
|
background: rgba(255,255,255,0.25);
|
|
border: 1.5px solid rgba(255,255,255,0.5);
|
|
}
|
|
.fkc-visual svg { width: 22px; height: 22px; position: relative; z-index: 1; }
|
|
.fkc-status {
|
|
font-size: 10.5px; font-weight: 600;
|
|
padding: 3px 10px;
|
|
border-radius: 999px;
|
|
background: var(--green-soft);
|
|
color: var(--green);
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
.fkc-name {
|
|
font-size: 17px;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
letter-spacing: -0.018em;
|
|
margin-bottom: 4px;
|
|
}
|
|
.fkc-sub {
|
|
font-size: 12.5px;
|
|
color: var(--text-3);
|
|
margin-bottom: 16px;
|
|
}
|
|
.fkc-stats {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 0;
|
|
padding-top: 14px;
|
|
border-top: 1px solid var(--line);
|
|
}
|
|
.fkc-stat .v { font-size: 14px; font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
.fkc-stat .l { font-size: 10.5px; color: var(--text-4); margin-top: 2px; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 500; }
|
|
.fkc-stat + .fkc-stat { border-left: 1px solid var(--line); padding-left: 14px; }
|
|
.fkc-stat:first-child { padding-right: 14px; }
|
|
|
|
.add-key-card {
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
border: 1.5px dashed var(--line-strong);
|
|
border-radius: var(--r-lg);
|
|
padding: 22px;
|
|
display: flex; flex-direction: column;
|
|
align-items: center; justify-content: center;
|
|
gap: 8px;
|
|
color: var(--text-3);
|
|
cursor: pointer;
|
|
transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
min-height: 220px;
|
|
}
|
|
.add-key-card:hover { border-color: var(--teal); color: var(--teal-2); background: var(--teal-soft); }
|
|
.add-key-card .akc-plus {
|
|
width: 40px; height: 40px;
|
|
border-radius: 50%;
|
|
border: 1.5px dashed currentColor;
|
|
display: grid; place-items: center;
|
|
}
|
|
.add-key-card .akc-plus svg { width: 18px; height: 18px; stroke-width: 1.75; }
|
|
.add-key-card .akc-title { font-size: 13px; font-weight: 600; color: var(--text-2); }
|
|
.add-key-card:hover .akc-title { color: var(--teal-2); }
|
|
.add-key-card .akc-sub { font-size: 11.5px; }
|
|
|
|
/* ===== Lifecycle tab ===== */
|
|
.lc-stage-bar {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
gap: 0;
|
|
background: var(--card-glass);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid var(--line);
|
|
border-radius: var(--r-lg);
|
|
padding: 6px;
|
|
margin-bottom: 18px;
|
|
position: relative;
|
|
}
|
|
.lc-stage {
|
|
padding: 14px 12px;
|
|
text-align: left;
|
|
border-radius: 9px;
|
|
position: relative;
|
|
color: var(--text-4);
|
|
}
|
|
.lc-stage .lcs-num {
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
color: var(--text-4);
|
|
margin-bottom: 4px;
|
|
}
|
|
.lc-stage .lcs-label {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text-3);
|
|
letter-spacing: -0.005em;
|
|
}
|
|
.lc-stage .lcs-date {
|
|
font-size: 11px;
|
|
color: var(--text-4);
|
|
margin-top: 3px;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.lc-stage.done { color: var(--text-2); }
|
|
.lc-stage.done .lcs-num { color: var(--green); }
|
|
.lc-stage.done .lcs-label { color: var(--text); }
|
|
.lc-stage.current {
|
|
background: var(--teal-soft);
|
|
box-shadow: inset 0 0 0 1px rgba(63,163,163,0.3);
|
|
}
|
|
.lc-stage.current .lcs-num { color: var(--teal-2); }
|
|
.lc-stage.current .lcs-label { color: var(--teal-2); }
|
|
.lc-stage.done::after {
|
|
content: '';
|
|
position: absolute;
|
|
right: -1px; top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 2px; height: 14px;
|
|
background: var(--green);
|
|
border-radius: 1px;
|
|
opacity: 0.4;
|
|
}
|
|
|
|
.lc-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 14px;
|
|
}
|
|
|
|
.milestone-list {
|
|
display: flex; flex-direction: column;
|
|
position: relative;
|
|
}
|
|
.milestone-list::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 9px; top: 18px; bottom: 18px;
|
|
width: 2px;
|
|
background: var(--line);
|
|
}
|
|
.milestone {
|
|
display: grid;
|
|
grid-template-columns: 20px 1fr auto;
|
|
gap: 14px;
|
|
align-items: flex-start;
|
|
padding: 10px 0;
|
|
position: relative;
|
|
}
|
|
.ms-dot {
|
|
width: 20px; height: 20px;
|
|
border-radius: 50%;
|
|
background: var(--card);
|
|
border: 2px solid var(--line);
|
|
display: grid; place-items: center;
|
|
z-index: 1;
|
|
margin-top: 1px;
|
|
}
|
|
.ms-dot.done {
|
|
background: var(--green);
|
|
border-color: var(--green);
|
|
box-shadow: 0 0 0 4px rgba(52,211,153,0.12);
|
|
}
|
|
.ms-dot.done svg { width: 10px; height: 10px; color: #052e1a; stroke-width: 3.5; }
|
|
.ms-dot.current {
|
|
border-color: var(--teal);
|
|
background: var(--bg);
|
|
box-shadow: 0 0 0 4px var(--teal-soft);
|
|
}
|
|
.ms-dot.current::after {
|
|
content: ''; width: 8px; height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--teal);
|
|
box-shadow: 0 0 8px var(--teal);
|
|
}
|
|
.ms-dot.upcoming { border-style: dashed; }
|
|
.ms-body .ms-title {
|
|
font-size: 13px; font-weight: 600;
|
|
color: var(--text);
|
|
letter-spacing: -0.005em;
|
|
}
|
|
.ms-body .ms-sub {
|
|
font-size: 11.5px; color: var(--text-4);
|
|
margin-top: 1px;
|
|
}
|
|
.ms-time {
|
|
font-size: 11px; color: var(--text-4);
|
|
font-variant-numeric: tabular-nums;
|
|
padding-top: 2px;
|
|
text-align: right;
|
|
white-space: nowrap;
|
|
}
|
|
.ms-time.future { color: var(--teal-2); }
|
|
|
|
.contract-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 14px;
|
|
}
|
|
.kv-card {
|
|
padding: 14px 16px;
|
|
background: var(--card-glass-2);
|
|
border: 1px solid var(--line);
|
|
border-radius: 10px;
|
|
}
|
|
.kv-card .kvc-label {
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
color: var(--text-4);
|
|
margin-bottom: 6px;
|
|
}
|
|
.kv-card .kvc-value {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
letter-spacing: -0.005em;
|
|
}
|
|
.kv-card .kvc-sub {
|
|
font-size: 11.5px;
|
|
color: var(--text-3);
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* Util */
|
|
.row-flex { display: flex; align-items: center; gap: 10px; }
|
|
.spacer { flex: 1; }
|
|
|
|
/* Section label */
|
|
.section-title {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
color: var(--text-4);
|
|
margin: 24px 0 12px;
|
|
}
|
|
.section-title:first-of-type { margin-top: 0; }
|
|
|
|
@media (max-width: 1200px) {
|
|
.stat-row { grid-template-columns: repeat(2, 1fr); }
|
|
.grid-2, .grid-3 { grid-template-columns: 1fr; }
|
|
.entra-grid, .lc-grid { grid-template-columns: 1fr; }
|
|
.fido-hero { grid-template-columns: 1fr 1fr; }
|
|
.detail-inner { padding: 24px 24px 56px; }
|
|
}
|
|
@media (max-width: 920px) {
|
|
.app { grid-template-columns: 1fr; }
|
|
.sidebar { display: none; }
|
|
:root { --list-w: 280px; }
|
|
.detail-inner { padding: 20px 18px 48px; }
|
|
.fido-hero, .asset-stats { grid-template-columns: 1fr; }
|
|
.lc-stage-bar { grid-template-columns: repeat(2, 1fr); }
|
|
.contract-grid { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="app">
|
|
|
|
<!-- ====== Sidebar ====== -->
|
|
<aside class="sidebar">
|
|
<div class="brand">
|
|
<div class="brand-mark">C</div>
|
|
<div>
|
|
<div class="brand-name">Cereda Systems</div>
|
|
<div class="brand-sub">IT Nexus</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="nav-search">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" width="14" height="14"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
|
|
<span>Suchen…</span>
|
|
<span class="kbd">⌘K</span>
|
|
</div>
|
|
|
|
<div class="nav-group">
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></svg>
|
|
Dashboard
|
|
</a>
|
|
</div>
|
|
|
|
<div class="nav-group">
|
|
<div class="nav-group-title">Helpdesk</div>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M22 12c0-5.5-4.5-10-10-10S2 6.5 2 12v6a2 2 0 0 0 2 2h2v-7H4v-1a8 8 0 0 1 16 0v1h-2v7h2a2 2 0 0 0 2-2z"/></svg>
|
|
Tickets <span class="nav-badge danger">2</span>
|
|
</a>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 4.5A2.5 2.5 0 0 1 6.5 2H20v17H6.5a2.5 2.5 0 0 0 0 5H20"/><path d="M8 7h8M8 11h6"/></svg>
|
|
Wissensdatenbank
|
|
</a>
|
|
</div>
|
|
|
|
<div class="nav-group">
|
|
<div class="nav-group-title">IT-Verwaltung</div>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="7" width="20" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M12 12v3"/></svg>
|
|
Assets <span class="nav-badge">65</span>
|
|
</a>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
|
|
Lizenzen
|
|
</a>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m21 2-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0 3 3L22 7l-3-3m-3.5 3.5L19 4"/></svg>
|
|
FIDO-Keys
|
|
</a>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 12a9 9 0 1 1-9-9c2.5 0 4.78 1 6.43 2.57L21 8"/><path d="M21 3v5h-5"/></svg>
|
|
Lifecycle
|
|
</a>
|
|
</div>
|
|
|
|
<div class="nav-group">
|
|
<div class="nav-group-title">Monitoring</div>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
|
Monitoring
|
|
</a>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
Defender
|
|
</a>
|
|
</div>
|
|
|
|
<div class="nav-group">
|
|
<div class="nav-group-title">Administration</div>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
|
Entra Rechte
|
|
</a>
|
|
<a class="nav-item active">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
Benutzer <span class="nav-badge accent">132</span>
|
|
</a>
|
|
<a class="nav-item">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 2 4 5v6c0 5 3.5 8 8 11 4.5-3 8-6 8-11V5z"/><path d="M9 12l2 2 4-4"/></svg>
|
|
Security Reports
|
|
</a>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- ====== Main ====== -->
|
|
<main class="main">
|
|
<header class="topbar">
|
|
<div class="breadcrumb">
|
|
<span>Administration</span>
|
|
<span class="sep">/</span>
|
|
<span class="current">Benutzerverwaltung</span>
|
|
</div>
|
|
<div class="topbar-right">
|
|
<button class="icon-btn" title="Benutzer einladen">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="8.5" cy="7" r="4"/><path d="M20 8v6M23 11h-6"/></svg>
|
|
</button>
|
|
<button class="icon-btn" title="Export">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
|
|
</button>
|
|
<button class="icon-btn" title="Benachrichtigungen">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9M13.7 21a2 2 0 0 1-3.4 0"/></svg>
|
|
<span class="dot"></span>
|
|
</button>
|
|
<div class="user-chip">
|
|
<span class="uc-name">Simon Grüssing</span>
|
|
<span class="uc-av">SG</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- ====== Workspace ====== -->
|
|
<div class="workspace">
|
|
|
|
<!-- === User list === -->
|
|
<aside class="list-panel">
|
|
<div class="list-head">
|
|
<div class="list-title-row">
|
|
<div class="list-title">Benutzer</div>
|
|
<div class="list-count" id="listCount">132 gesamt</div>
|
|
</div>
|
|
<div class="list-sub">Verwalte Identitäten, Rollen & Zugriffe</div>
|
|
|
|
<div class="list-search">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
|
|
<input id="searchInput" type="text" placeholder="Name oder E-Mail suchen…" />
|
|
</div>
|
|
|
|
<div class="filter-row" id="filterRow">
|
|
<button class="filter-pill on" data-role="all">Alle</button>
|
|
<button class="filter-pill" data-role="admin">Admin</button>
|
|
<button class="filter-pill" data-role="manager">Manager</button>
|
|
<button class="filter-pill" data-role="dev">Dev</button>
|
|
<button class="filter-pill" data-role="staff">Staff</button>
|
|
<button class="filter-pill" data-role="extern">Extern</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="list-scroll" id="listScroll">
|
|
<!-- users injected by JS -->
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- === Detail === -->
|
|
<section class="detail-panel">
|
|
<div class="detail-inner" id="detailInner">
|
|
<!-- detail injected by JS -->
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- ============ DATA + LOGIC ============ -->
|
|
<script>
|
|
/* ---- Users ---- */
|
|
const USERS = [
|
|
{ id: 'sgruessing', first: 'Simon', last: 'Grüssing', email: 's.gruessing@cereda.de', role: 'admin', roleLabel: 'Super Admin', dept: 'IT & Infrastruktur', position: 'Head of IT', phone: '+49 2351 9921-104', location: 'Lüdenscheid', manager: 'Marcus Cereda', managerInit: 'MC', joined: '12. März 2019', presence: 'online', initials: 'SG', avClass: 'av-admin', stats: { assets: 4, lic: 12, fido: 2, sessions: 3 }, mfa: 'FIDO2 + TOTP', lastPwd: 'vor 23 Tagen', lastLogin: 'vor 4 Minuten', risk: 'Niedrig', section: 'Favoriten' },
|
|
{ id: 'mcereda', first: 'Marcus', last: 'Cereda', email: 'm.cereda@cereda.de', role: 'admin', roleLabel: 'CEO', dept: 'Geschäftsführung', position: 'Geschäftsführer', phone: '+49 2351 9921-100', location: 'Lüdenscheid', manager: '—', managerInit: '', joined: '01. Jan 2008', presence: 'online', initials: 'MC', avClass: 'av-admin', stats: { assets: 3, lic: 14, fido: 2, sessions: 2 }, mfa: 'FIDO2 + TOTP', lastPwd: 'vor 12 Tagen', lastLogin: 'vor 1 Stunde', risk: 'Niedrig', section: 'Favoriten' },
|
|
{ id: 'akoehler', first: 'Anna', last: 'Köhler', email: 'a.koehler@cereda.de', role: 'manager', roleLabel: 'Manager', dept: 'Vertrieb', position: 'Sales Lead DACH', phone: '+49 2351 9921-220', location: 'Lüdenscheid', manager: 'Marcus Cereda', managerInit: 'MC', joined: '04. Sep 2020', presence: 'online', initials: 'AK', avClass: 'av-manager', stats: { assets: 2, lic: 9, fido: 1, sessions: 1 }, mfa: 'FIDO2', lastPwd: 'vor 41 Tagen', lastLogin: 'vor 12 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'tlange', first: 'Thomas', last: 'Lange', email: 't.lange@cereda.de', role: 'dev', roleLabel: 'Developer', dept: 'Engineering', position: 'Senior Software Engineer', phone: '+49 2351 9921-340', location: 'Remote', manager: 'Simon Grüssing', managerInit: 'SG', joined: '17. Mai 2021', presence: 'online', initials: 'TL', avClass: 'av-dev', stats: { assets: 2, lic: 11, fido: 2, sessions: 2 }, mfa: 'FIDO2 + TOTP', lastPwd: 'vor 8 Tagen', lastLogin: 'vor 2 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'jweber', first: 'Julia', last: 'Weber', email: 'j.weber@cereda.de', role: 'dev', roleLabel: 'Developer', dept: 'Engineering', position: 'Frontend Engineer', phone: '+49 2351 9921-342', location: 'Lüdenscheid', manager: 'Simon Grüssing', managerInit: 'SG', joined: '02. Nov 2022', presence: 'away', initials: 'JW', avClass: 'av-dev', stats: { assets: 1, lic: 8, fido: 1, sessions: 1 }, mfa: 'TOTP', lastPwd: 'vor 67 Tagen', lastLogin: 'vor 3 Stunden', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'rkrause', first: 'Robert', last: 'Krause', email: 'r.krause@cereda.de', role: 'staff', roleLabel: 'Mitarbeiter', dept: 'Marketing', position: 'Content Manager', phone: '+49 2351 9921-410', location: 'Lüdenscheid', manager: 'Anna Köhler', managerInit: 'AK', joined: '15. Aug 2023', presence: 'online', initials: 'RK', avClass: 'av-staff', stats: { assets: 1, lic: 6, fido: 1, sessions: 1 }, mfa: 'TOTP', lastPwd: 'vor 33 Tagen', lastLogin: 'vor 28 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'sbecker', first: 'Sandra', last: 'Becker', email: 's.becker@cereda.de', role: 'manager', roleLabel: 'Manager', dept: 'Personal', position: 'HR Manager', phone: '+49 2351 9921-510', location: 'Lüdenscheid', manager: 'Marcus Cereda', managerInit: 'MC', joined: '21. Feb 2019', presence: 'online', initials: 'SB', avClass: 'av-manager', stats: { assets: 1, lic: 7, fido: 1, sessions: 1 }, mfa: 'FIDO2', lastPwd: 'vor 19 Tagen', lastLogin: 'vor 1 Stunde', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'mschmitt', first: 'Michael', last: 'Schmitt', email: 'm.schmitt@cereda.de', role: 'staff', roleLabel: 'Mitarbeiter', dept: 'Vertrieb', position: 'Account Manager', phone: '+49 2351 9921-225', location: 'Köln', manager: 'Anna Köhler', managerInit: 'AK', joined: '07. Apr 2024', presence: 'offline', initials: 'MS', avClass: 'av-staff', stats: { assets: 2, lic: 8, fido: 0, sessions: 0 }, mfa: 'TOTP', lastPwd: 'vor 91 Tagen', lastLogin: 'vor 2 Tagen', risk: 'Mittel', section: 'Alle Benutzer' },
|
|
{ id: 'lhoffmann', first: 'Lisa', last: 'Hoffmann', email: 'l.hoffmann@cereda.de', role: 'staff', roleLabel: 'Mitarbeiter', dept: 'Finanzen', position: 'Buchhaltung', phone: '+49 2351 9921-620', location: 'Lüdenscheid', manager: 'Sandra Becker', managerInit: 'SB', joined: '11. Jun 2020', presence: 'online', initials: 'LH', avClass: 'av-staff', stats: { assets: 1, lic: 5, fido: 1, sessions: 1 }, mfa: 'FIDO2', lastPwd: 'vor 14 Tagen', lastLogin: 'vor 22 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'dwagner', first: 'Daniel', last: 'Wagner', email: 'd.wagner@cereda.de', role: 'dev', roleLabel: 'Developer', dept: 'Engineering', position: 'DevOps Engineer', phone: '+49 2351 9921-345', location: 'Remote', manager: 'Simon Grüssing', managerInit: 'SG', joined: '03. Jan 2022', presence: 'online', initials: 'DW', avClass: 'av-dev', stats: { assets: 2, lic: 10, fido: 2, sessions: 2 }, mfa: 'FIDO2 + TOTP', lastPwd: 'vor 6 Tagen', lastLogin: 'vor 8 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'kfischer', first: 'Karin', last: 'Fischer', email: 'k.fischer@cereda.de', role: 'staff', roleLabel: 'Mitarbeiter', dept: 'Office', position: 'Office Management', phone: '+49 2351 9921-110', location: 'Lüdenscheid', manager: 'Sandra Becker', managerInit: 'SB', joined: '18. Sep 2017', presence: 'online', initials: 'KF', avClass: 'av-staff', stats: { assets: 1, lic: 4, fido: 1, sessions: 1 }, mfa: 'TOTP', lastPwd: 'vor 28 Tagen', lastLogin: 'vor 15 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'fbauer', first: 'Felix', last: 'Bauer', email: 'f.bauer@cereda.de', role: 'admin', roleLabel: 'IT Admin', dept: 'IT & Infrastruktur', position: 'Systemadministrator', phone: '+49 2351 9921-103', location: 'Lüdenscheid', manager: 'Simon Grüssing', managerInit: 'SG', joined: '02. Aug 2021', presence: 'online', initials: 'FB', avClass: 'av-admin', stats: { assets: 3, lic: 12, fido: 2, sessions: 2 }, mfa: 'FIDO2 + TOTP', lastPwd: 'vor 11 Tagen', lastLogin: 'vor 6 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'pmueller', first: 'Paula', last: 'Müller', email: 'p.mueller@cereda.de', role: 'staff', roleLabel: 'Mitarbeiter', dept: 'Marketing', position: 'Grafikdesignerin', phone: '+49 2351 9921-415', location: 'Lüdenscheid', manager: 'Robert Krause', managerInit: 'RK', joined: '14. Mai 2023', presence: 'away', initials: 'PM', avClass: 'av-staff', stats: { assets: 1, lic: 6, fido: 1, sessions: 1 }, mfa: 'FIDO2', lastPwd: 'vor 22 Tagen', lastLogin: 'vor 1 Stunde', risk: 'Niedrig', section: 'Alle Benutzer' },
|
|
{ id: 'jschwarz', first: 'Jens', last: 'Schwarz', email: 'j.schwarz@externfirma.com', role: 'extern', roleLabel: 'Extern', dept: 'Beratung', position: 'Externer Berater', phone: '+49 30 1234 5678', location: 'Berlin', manager: 'Marcus Cereda', managerInit: 'MC', joined: '15. Jan 2025', presence: 'offline', initials: 'JS', avClass: 'av-extern', stats: { assets: 0, lic: 2, fido: 0, sessions: 0 }, mfa: 'TOTP', lastPwd: 'vor 4 Tagen', lastLogin: 'vor 5 Tagen', risk: 'Mittel', section: 'Alle Benutzer' },
|
|
{ id: 'tneumann', first: 'Tim', last: 'Neumann', email: 't.neumann@cereda.de', role: 'staff', roleLabel: 'Praktikant', dept: 'Engineering', position: 'Werkstudent', phone: '+49 2351 9921-348', location: 'Lüdenscheid', manager: 'Thomas Lange', managerInit: 'TL', joined: '01. Apr 2026', presence: 'online', initials: 'TN', avClass: 'av-intern', stats: { assets: 1, lic: 3, fido: 0, sessions: 1 }, mfa: 'TOTP', lastPwd: 'vor 2 Tagen', lastLogin: 'vor 45 Minuten', risk: 'Niedrig', section: 'Alle Benutzer' }
|
|
];
|
|
|
|
/* ---- State ---- */
|
|
let state = {
|
|
selected: 'tlange',
|
|
filter: 'all',
|
|
search: '',
|
|
tab: 'overview'
|
|
};
|
|
|
|
/* ---- Helpers ---- */
|
|
const $ = (q, r=document) => r.querySelector(q);
|
|
const $$ = (q, r=document) => [...r.querySelectorAll(q)];
|
|
const esc = (s) => String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
|
|
|
/* ---- Render list ---- */
|
|
function renderList() {
|
|
const term = state.search.trim().toLowerCase();
|
|
const filtered = USERS.filter(u => {
|
|
if (state.filter !== 'all' && u.role !== state.filter) return false;
|
|
if (term) {
|
|
const hay = (u.first + ' ' + u.last + ' ' + u.email).toLowerCase();
|
|
if (!hay.includes(term)) return false;
|
|
}
|
|
return true;
|
|
});
|
|
$('#listCount').textContent = filtered.length + ' von ' + USERS.length;
|
|
|
|
// Group by section
|
|
const sections = {};
|
|
filtered.forEach(u => {
|
|
(sections[u.section] = sections[u.section] || []).push(u);
|
|
});
|
|
const order = ['Favoriten', 'Alle Benutzer'];
|
|
|
|
let html = '';
|
|
order.forEach(label => {
|
|
if (!sections[label]) return;
|
|
html += `<div class="list-section-label">${esc(label)}</div>`;
|
|
sections[label].forEach(u => {
|
|
html += `
|
|
<div class="user-row ${u.id === state.selected ? 'active' : ''}" data-id="${u.id}">
|
|
<div class="ur-av ${u.avClass}">${esc(u.initials)}</div>
|
|
<div class="ur-presence ${u.presence}"></div>
|
|
<div class="ur-body">
|
|
<div class="ur-name">${esc(u.first + ' ' + u.last)}</div>
|
|
<div class="ur-meta">
|
|
<span class="email">${esc(u.email)}</span>
|
|
</div>
|
|
</div>
|
|
<span class="role-chip ${u.role}">${esc(u.roleLabel)}</span>
|
|
</div>`;
|
|
});
|
|
});
|
|
|
|
if (filtered.length === 0) {
|
|
html = `<div style="padding:40px 20px;text-align:center;color:var(--text-4);font-size:13px;">Keine Benutzer gefunden</div>`;
|
|
}
|
|
$('#listScroll').innerHTML = html;
|
|
|
|
$$('.user-row').forEach(el => el.addEventListener('click', () => {
|
|
state.selected = el.dataset.id;
|
|
state.tab = 'overview';
|
|
renderList(); renderDetail();
|
|
}));
|
|
}
|
|
|
|
/* ---- Render detail ---- */
|
|
function renderDetail() {
|
|
const u = USERS.find(x => x.id === state.selected) || USERS[0];
|
|
|
|
const tabs = [
|
|
{ id: 'overview', label: 'Übersicht' },
|
|
{ id: 'assets', label: 'Assets', count: u.stats.assets },
|
|
{ id: 'lic', label: 'Lizenzen', count: u.stats.lic },
|
|
{ id: 'entra', label: 'Entra/AD' },
|
|
{ id: 'fido', label: 'FIDO-Keys', count: u.stats.fido },
|
|
{ id: 'lifecycle', label: 'Lifecycle' }
|
|
];
|
|
|
|
const tabsHtml = tabs.map(t => `
|
|
<button class="tab ${state.tab === t.id ? 'active' : ''}" data-tab="${t.id}">
|
|
${esc(t.label)}
|
|
${t.count != null ? `<span class="tab-count">${t.count}</span>` : ''}
|
|
</button>
|
|
`).join('');
|
|
|
|
$('#detailInner').innerHTML = `
|
|
<div class="detail-head">
|
|
<div class="dh-avatar ${u.avClass}">
|
|
${esc(u.initials)}
|
|
<span class="dh-presence" style="${u.presence !== 'online' ? 'background:var(--amber)' : ''}${u.presence === 'offline' ? ';background:var(--text-4)' : ''}"></span>
|
|
</div>
|
|
<div class="dh-body">
|
|
<div class="dh-name-row">
|
|
<h1 class="dh-name">${esc(u.first + ' ' + u.last)}</h1>
|
|
<span class="dh-handle">@${esc(u.id)}</span>
|
|
</div>
|
|
<div class="dh-email">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><path d="m22 6-10 7L2 6"/></svg>
|
|
${esc(u.email)}
|
|
</div>
|
|
<div class="dh-badges">
|
|
<span class="dh-badge role-manager" style="${u.role === 'admin' ? 'background:var(--red-soft);color:#fca5a5;border-color:rgba(239,68,68,0.3)' : u.role === 'dev' ? 'background:var(--purple-soft);color:#c4b5fd;border-color:rgba(167,139,250,0.3)' : u.role === 'staff' ? 'background:var(--blue-soft);color:#93c5fd;border-color:rgba(96,165,250,0.3)' : u.role === 'extern' ? 'background:var(--amber-soft);color:#fcd34d;border-color:rgba(245,158,11,0.3)' : ''}">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2 4 5v6c0 5 3.5 8 8 11 4.5-3 8-6 8-11V5z"/></svg>
|
|
${esc(u.roleLabel)}
|
|
</span>
|
|
<span class="dh-badge dept">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M3 21h18M5 21V7l7-4 7 4v14M9 9h.01M15 9h.01M9 13h.01M15 13h.01M9 17h6"/></svg>
|
|
${esc(u.dept)}
|
|
</span>
|
|
<span class="dh-badge status-active">
|
|
<span class="pulse"></span>
|
|
Aktiv
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="dh-actions">
|
|
<button class="btn">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4z"/></svg>
|
|
Bearbeiten
|
|
</button>
|
|
<button class="btn">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="16" r="1"/><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
|
Passwort reset
|
|
</button>
|
|
<button class="btn danger" title="Weitere Aktionen">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tabs-bar" id="tabsBar">${tabsHtml}</div>
|
|
|
|
<div id="tabContent">${renderTab(u)}</div>
|
|
`;
|
|
|
|
$$('.tab').forEach(el => el.addEventListener('click', () => {
|
|
state.tab = el.dataset.tab;
|
|
renderDetail();
|
|
}));
|
|
}
|
|
|
|
/* ---- Tab content ---- */
|
|
function renderTab(u) {
|
|
if (state.tab === 'overview') return overviewHtml(u);
|
|
if (state.tab === 'assets') return assetsHtml(u);
|
|
if (state.tab === 'lic') return licensesHtml(u);
|
|
if (state.tab === 'entra') return entraHtml(u);
|
|
if (state.tab === 'fido') return fidoHtml(u);
|
|
if (state.tab === 'lifecycle') return lifecycleHtml(u);
|
|
return '';
|
|
}
|
|
|
|
function overviewHtml(u) {
|
|
return `
|
|
<div class="tab-panel active">
|
|
|
|
<div class="stat-row">
|
|
<div class="stat">
|
|
<div class="stat-head">
|
|
<div class="stat-icon s-status"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg></div>
|
|
<span class="stat-trend">${esc(u.lastLogin)}</span>
|
|
</div>
|
|
<div class="stat-value">Aktiv</div>
|
|
<div class="stat-label">Konto Status</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-head">
|
|
<div class="stat-icon s-assets"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="7" width="20" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></div>
|
|
<span class="stat-trend">zugewiesen</span>
|
|
</div>
|
|
<div class="stat-value">${u.stats.assets}</div>
|
|
<div class="stat-label">Assets</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-head">
|
|
<div class="stat-icon s-lic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg></div>
|
|
<span class="stat-trend">aktiv</span>
|
|
</div>
|
|
<div class="stat-value">${u.stats.lic}</div>
|
|
<div class="stat-label">Lizenzen</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-head">
|
|
<div class="stat-icon s-fido"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15.5 7.5 3 3L22 7l-3-3M21 2l-9.6 9.6a5.5 5.5 0 1 1-2 2L19 4"/></svg></div>
|
|
<span class="stat-trend">${u.mfa.includes('FIDO') ? 'gepaart' : '—'}</span>
|
|
</div>
|
|
<div class="stat-value">${u.stats.fido}</div>
|
|
<div class="stat-label">FIDO-Keys</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-2">
|
|
<!-- Persönliche Informationen -->
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
|
Persönliche Informationen
|
|
</div>
|
|
<a class="card-link" href="#">Bearbeiten</a>
|
|
</div>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<div class="k"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="12" cy="7" r="4"/><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/></svg>Position</div>
|
|
<div class="v">${esc(u.position)}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M3 21h18M5 21V7l7-4 7 4v14M9 9h.01M15 9h.01M9 13h.01M15 13h.01M9 17h6"/></svg>Abteilung</div>
|
|
<div class="v">${esc(u.dept)}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M22 16.92V21a1 1 0 0 1-1.11 1 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6A19.79 19.79 0 0 1 3.18 4.11 1 1 0 0 1 4.18 3h4.09a1 1 0 0 1 1 .75c.12.93.34 1.84.66 2.71a1 1 0 0 1-.21 1L8 9.09a16 16 0 0 0 6 6l1.62-1.62a1 1 0 0 1 1-.21c.87.32 1.78.54 2.71.66a1 1 0 0 1 .67 1z"/></svg>Telefon</div>
|
|
<div class="v mono">${esc(u.phone)}<button class="copy-btn" title="Kopieren"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button></div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>Standort</div>
|
|
<div class="v">${esc(u.location)}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>Vorgesetzter</div>
|
|
<div class="v">
|
|
${u.managerInit ? `<span class="mini-av" style="background:linear-gradient(135deg,#5dbcbc,#3fa3a3)">${esc(u.managerInit)}</span>` : ''}
|
|
${esc(u.manager)}
|
|
</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>Eintritt</div>
|
|
<div class="v">${esc(u.joined)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sicherheit -->
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
Sicherheit & MFA
|
|
</div>
|
|
<span class="dh-badge status-active" style="font-size:10.5px;padding:2px 8px;">Risiko: ${esc(u.risk)}</span>
|
|
</div>
|
|
<div class="sec-list">
|
|
<div class="sec-item">
|
|
<div class="sec-icon si-key"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15.5 7.5 3 3L22 7l-3-3M21 2l-9.6 9.6a5.5 5.5 0 1 1-2 2L19 4"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Mehrfaktor-Authentifizierung</div>
|
|
<div class="sec-sub">${esc(u.mfa)}</div>
|
|
</div>
|
|
<span class="sec-status ok">Aktiv</span>
|
|
</div>
|
|
<div class="sec-item">
|
|
<div class="sec-icon si-pwd"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Passwort</div>
|
|
<div class="sec-sub">Letzte Änderung ${esc(u.lastPwd)}</div>
|
|
</div>
|
|
<span class="sec-status ${u.lastPwd.includes('91') || u.lastPwd.includes('67') ? 'warn' : 'ok'}">${u.lastPwd.includes('91') ? 'Veraltet' : 'OK'}</span>
|
|
</div>
|
|
<div class="sec-item">
|
|
<div class="sec-icon si-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Conditional Access</div>
|
|
<div class="sec-sub">3 Richtlinien zugewiesen · Compliant</div>
|
|
</div>
|
|
<span class="sec-status ok">OK</span>
|
|
</div>
|
|
<div class="sec-item">
|
|
<div class="sec-icon ${u.stats.sessions > 0 ? 'si-ok' : 'si-warn'}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Aktive Sessions</div>
|
|
<div class="sec-sub">${u.stats.sessions} Geräte · Letzte Anmeldung ${esc(u.lastLogin)}</div>
|
|
</div>
|
|
<span class="sec-status ${u.stats.sessions > 0 ? 'ok' : 'off'}">${u.stats.sessions > 0 ? u.stats.sessions + ' aktiv' : 'Keine'}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-3">
|
|
<!-- Aktivitäten -->
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
|
Letzte Aktivitäten
|
|
</div>
|
|
<a class="card-link" href="#">Audit-Log →</a>
|
|
</div>
|
|
<div class="activity-list">
|
|
<div class="activity">
|
|
<div class="act-dot ad-login"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4M10 17l5-5-5-5M15 12H3"/></svg></div>
|
|
<div class="act-body">
|
|
<div class="act-text"><b>Anmeldung erfolgreich</b> via FIDO2 Key</div>
|
|
<div class="act-meta">192.168.0.45 · Edge 124 · Windows 11</div>
|
|
</div>
|
|
<div class="act-time">${esc(u.lastLogin)}</div>
|
|
</div>
|
|
<div class="activity">
|
|
<div class="act-dot ad-asset"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="7" width="20" height="13" rx="2"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg></div>
|
|
<div class="act-body">
|
|
<div class="act-text">Asset <b>TBO-NB-02</b> zugewiesen</div>
|
|
<div class="act-meta">Durch Felix Bauer · Lifecycle Update</div>
|
|
</div>
|
|
<div class="act-time">vor 2 Tagen</div>
|
|
</div>
|
|
<div class="activity">
|
|
<div class="act-dot ad-pwd"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
|
|
<div class="act-body">
|
|
<div class="act-text">Passwort geändert</div>
|
|
<div class="act-meta">Eigenständig · Self-Service Portal</div>
|
|
</div>
|
|
<div class="act-time">${esc(u.lastPwd)}</div>
|
|
</div>
|
|
<div class="activity">
|
|
<div class="act-dot ad-key"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="m15.5 7.5 3 3L22 7l-3-3M21 2l-9.6 9.6a5.5 5.5 0 1 1-2 2L19 4"/></svg></div>
|
|
<div class="act-body">
|
|
<div class="act-text">FIDO-Key <b>YubiKey 5C NFC</b> registriert</div>
|
|
<div class="act-meta">Backup-Token paired</div>
|
|
</div>
|
|
<div class="act-time">vor 14 Tagen</div>
|
|
</div>
|
|
<div class="activity">
|
|
<div class="act-dot ad-perm"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg></div>
|
|
<div class="act-body">
|
|
<div class="act-text">Gruppenmitgliedschaft <b>${esc(u.dept)}</b> erweitert</div>
|
|
<div class="act-meta">+ M365 E5 Lizenzpool</div>
|
|
</div>
|
|
<div class="act-time">vor 21 Tagen</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gruppen & Rollen -->
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
|
Gruppen & Rollen
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group-section">
|
|
<h4>Entra ID Rollen</h4>
|
|
<div class="group-list">
|
|
${u.role === 'admin' ? `
|
|
<span class="group-chip role"><span class="g-dot"></span>Global Administrator</span>
|
|
<span class="group-chip role"><span class="g-dot"></span>Intune Administrator</span>` : ''}
|
|
${u.role === 'manager' ? `<span class="group-chip role"><span class="g-dot"></span>User Administrator</span>` : ''}
|
|
${u.role === 'dev' ? `<span class="group-chip role"><span class="g-dot"></span>Application Developer</span>` : ''}
|
|
${u.role === 'staff' || u.role === 'extern' ? `<span class="group-chip role"><span class="g-dot"></span>Member</span>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group-section">
|
|
<h4>Sicherheitsgruppen</h4>
|
|
<div class="group-list">
|
|
<span class="group-chip entra"><span class="g-dot"></span>SG-${esc(u.dept.split(' ')[0])}</span>
|
|
<span class="group-chip entra"><span class="g-dot"></span>SG-MFA-Required</span>
|
|
<span class="group-chip entra"><span class="g-dot"></span>SG-VPN-Users</span>
|
|
${u.role !== 'extern' ? `<span class="group-chip entra"><span class="g-dot"></span>SG-Intranet-Full</span>` : ''}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group-section">
|
|
<h4>Verteiler</h4>
|
|
<div class="group-list">
|
|
<span class="group-chip dist"><span class="g-dot"></span>alle@cereda.de</span>
|
|
<span class="group-chip dist"><span class="g-dot"></span>${esc(u.dept.toLowerCase().split(' ')[0])}@cereda.de</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="group-section">
|
|
<h4>App-Berechtigungen</h4>
|
|
<div class="perm-list">
|
|
<div class="perm-item"><span class="p-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></span><span class="p-label">IT Nexus Portal</span><span class="p-scope">${u.role === 'admin' ? 'admin' : 'user'}</span></div>
|
|
<div class="perm-item"><span class="p-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></span><span class="p-label">Microsoft 365</span><span class="p-scope">e3</span></div>
|
|
<div class="perm-item ${u.role === 'extern' ? 'off' : ''}"><span class="p-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></span><span class="p-label">Cereda Secure Share</span><span class="p-scope">${u.role === 'extern' ? '—' : 'editor'}</span></div>
|
|
<div class="perm-item ${u.role === 'staff' || u.role === 'extern' ? 'off' : ''}"><span class="p-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></span><span class="p-label">Azure DevOps</span><span class="p-scope">${u.role === 'staff' || u.role === 'extern' ? '—' : 'contributor'}</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function assetsHtml(u) {
|
|
const allAssets = [
|
|
{ name: 'TBO-NB-02', type: 'Notebook', model: 'Dell Latitude 7440', sn: 'SN1234567890', cls: 'nb', status: 'online', cpu: 32, ram: 51, disk: 58, warranty: 64, age: '14 Monate', value: '€ 1.890',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="4" width="20" height="13" rx="1.5"/><path d="M1 21h22M9 21v-4M15 21v-4"/></svg>' },
|
|
{ name: 'TBO-MON-14', type: 'Monitor', model: 'Dell U2723QE 27" 4K', sn: 'CN0XYZ12345', cls: 'mon', status: 'online', cpu: 0, ram: 0, disk: 0, warranty: 78, age: '11 Monate', value: '€ 620',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="3" width="20" height="14" rx="1.5"/><path d="M8 21h8M12 17v4"/></svg>' },
|
|
{ name: 'TBO-PHN-08', type: 'Smartphone', model: 'iPhone 15 Pro 256GB', sn: 'F2LXJ7Q8N9PR', cls: 'phn', status: 'online', cpu: 0, ram: 0, disk: 42, warranty: 88, age: '6 Monate', value: '€ 1.299',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="6" y="2" width="12" height="20" rx="2.5"/><path d="M11 18h2"/></svg>' },
|
|
{ name: 'TBO-DOCK-02', type: 'Dock', model: 'Dell WD22TB4', sn: 'CN-DK-22-4421', cls: 'dock', status: 'online', cpu: 0, ram: 0, disk: 0, warranty: 50, age: '20 Monate', value: '€ 320',
|
|
icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="9" width="20" height="9" rx="1.5"/><path d="M6 9V6a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v3M6 13h4M14 13h4"/></svg>' }
|
|
];
|
|
const assets = allAssets.slice(0, Math.max(u.stats.assets, 1));
|
|
|
|
const totalValue = assets.reduce((s, a) => s + parseInt(a.value.replace(/\D/g, '')), 0);
|
|
|
|
return `
|
|
<div class="tab-panel active">
|
|
<div class="asset-stats">
|
|
<div class="stat">
|
|
<div class="stat-head"><div class="stat-icon s-assets"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="7" width="20" height="13" rx="2"/></svg></div></div>
|
|
<div class="stat-value">${assets.length}</div>
|
|
<div class="stat-label">Geräte zugewiesen</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-head"><div class="stat-icon s-lic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg></div></div>
|
|
<div class="stat-value">€ ${totalValue.toLocaleString('de-DE')}</div>
|
|
<div class="stat-label">Buchwert gesamt</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-head"><div class="stat-icon s-status"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div></div>
|
|
<div class="stat-value">${assets.filter(a => a.status === 'online').length}/<span style="color:var(--text-4);font-size:18px">${assets.length}</span></div>
|
|
<div class="stat-label">Aktuell online</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-head"><div class="stat-icon s-fido"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 8v4l3 3"/><circle cx="12" cy="12" r="10"/></svg></div></div>
|
|
<div class="stat-value">${assets[0]?.age || '—'}</div>
|
|
<div class="stat-label">Ältestes Gerät</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-title">Zugewiesene Hardware</div>
|
|
<div class="device-grid">
|
|
${assets.map(a => `
|
|
<div class="device-card">
|
|
<div class="dc-top">
|
|
<div class="device-icon ${a.cls}">${a.icon}</div>
|
|
<div class="dc-meta">
|
|
<div class="dc-name">${esc(a.name)}</div>
|
|
<div class="dc-model">${esc(a.model)}</div>
|
|
</div>
|
|
<div class="dc-status-dot ${a.status === 'online' ? '' : 'off'}" title="${esc(a.status)}"></div>
|
|
</div>
|
|
|
|
<div class="dc-health">
|
|
${a.cpu > 0 ? `
|
|
<div class="dc-bar">
|
|
<div class="dc-bar-label"><span>CPU-Last</span><b>${a.cpu}%</b></div>
|
|
<div class="dc-bar-track"><div class="dc-bar-fill" style="width:${a.cpu}%;--bar-color:${a.cpu > 75 ? 'var(--red)' : a.cpu > 50 ? 'var(--amber)' : 'var(--teal)'}"></div></div>
|
|
</div>` : ''}
|
|
${a.disk > 0 ? `
|
|
<div class="dc-bar">
|
|
<div class="dc-bar-label"><span>Speicher</span><b>${a.disk}%</b></div>
|
|
<div class="dc-bar-track"><div class="dc-bar-fill" style="width:${a.disk}%;--bar-color:var(--blue)"></div></div>
|
|
</div>` : ''}
|
|
<div class="dc-bar">
|
|
<div class="dc-bar-label"><span>Garantie</span><b>${a.warranty}%</b></div>
|
|
<div class="dc-bar-track"><div class="dc-bar-fill" style="width:${a.warranty}%;--bar-color:${a.warranty < 25 ? 'var(--red)' : a.warranty < 50 ? 'var(--amber)' : 'var(--green)'}"></div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dc-footer">
|
|
<span class="dc-tag">${esc(a.type)}</span>
|
|
<span>${esc(a.sn)}</span>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function licensesHtml(u) {
|
|
const cats = {
|
|
'Produktivität': [
|
|
{ name: 'Microsoft 365 E3', plan: 'Office Apps, Teams, SharePoint', cost: '€ 32,40', cls: 'lc-m365', initials: 'M' },
|
|
{ name: 'Zoom Pro', plan: 'Bis 100 Teilnehmer · Cloud-Rec.', cost: '€ 13,99', cls: 'lc-zoom', initials: 'Z' },
|
|
{ name: 'Slack Pro', plan: 'Active Seat · 90 Tage History', cost: '€ 7,25', cls: 'lc-slack', initials: 'S' },
|
|
{ name: '1Password Business', plan: 'Persönlicher Tresor + Teams', cost: '€ 7,99', cls: 'lc-1pwd', initials: '1' }
|
|
],
|
|
'Entwicklung': [
|
|
{ name: 'GitHub Enterprise', plan: 'Cloud · Seat', cost: '€ 19,00', cls: 'lc-github', initials: 'G' },
|
|
{ name: 'JetBrains All Products', plan: 'Personal · IDE Suite', cost: '€ 24,90', cls: 'lc-jb', initials: 'J' },
|
|
{ name: 'AWS Developer Support', plan: 'Pro Account · Shared', cost: '€ 29,00', cls: 'lc-aws', initials: 'A' }
|
|
],
|
|
'Design & Tools': [
|
|
{ name: 'Adobe Creative Cloud', plan: 'All Apps · 100 GB', cost: '€ 67,99', cls: 'lc-adobe', initials: 'A' },
|
|
{ name: 'Figma Professional', plan: 'Editor Seat', cost: '€ 12,00', cls: 'lc-figma', initials: 'F' },
|
|
{ name: 'Notion Plus', plan: 'Team Member', cost: '€ 9,50', cls: 'lc-no', initials: 'N' },
|
|
{ name: 'Tailwind UI', plan: 'Team License', cost: '€ 8,30', cls: 'lc-tw', initials: 'T' },
|
|
{ name: 'Intercom Helpdesk', plan: 'Pro Seat', cost: '€ 39,00', cls: 'lc-int', initials: 'I' }
|
|
]
|
|
};
|
|
|
|
const flat = Object.values(cats).flat().slice(0, Math.max(u.stats.lic, 1));
|
|
const totalCost = flat.reduce((s, l) => s + parseFloat(l.cost.replace(/[^\d,]/g, '').replace(',', '.')), 0);
|
|
const expiringSoon = Math.min(2, flat.length);
|
|
|
|
// re-group filtered
|
|
let assigned = flat;
|
|
let catGroups = {};
|
|
Object.entries(cats).forEach(([k, list]) => {
|
|
const got = list.filter(l => assigned.includes(l));
|
|
if (got.length) catGroups[k] = got;
|
|
});
|
|
|
|
return `
|
|
<div class="tab-panel active">
|
|
<div class="lic-summary">
|
|
<div>
|
|
<div class="ls-label">Aktive Lizenzen</div>
|
|
<div class="ls-value">${assigned.length}</div>
|
|
<div class="ls-sub">Aus ${u.stats.lic} zugewiesenen Plätzen</div>
|
|
</div>
|
|
<div>
|
|
<div class="ls-label">Kosten pro Monat</div>
|
|
<div class="ls-value">€ ${totalCost.toFixed(2).replace('.', ',')}</div>
|
|
<div class="ls-sub">€ ${(totalCost * 12).toFixed(0)} jährlich</div>
|
|
</div>
|
|
<div>
|
|
<div class="ls-label">Verlängerung fällig</div>
|
|
<div class="ls-value" style="color:${expiringSoon ? 'var(--amber)' : 'var(--green)'}">${expiringSoon}</div>
|
|
<div class="ls-sub">${expiringSoon ? 'in den nächsten 60 Tagen' : 'Keine in Kürze'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
${Object.entries(catGroups).map(([cat, list]) => `
|
|
<div class="lic-cat-title">
|
|
<span>${esc(cat)}</span>
|
|
<span class="ct-count">${list.length}</span>
|
|
</div>
|
|
<div class="lic-grid">
|
|
${list.map(l => `
|
|
<div class="lic-card">
|
|
<div class="lic-logo ${l.cls}">${esc(l.initials)}</div>
|
|
<div class="lic-body">
|
|
<div class="lic-name">${esc(l.name)}</div>
|
|
<div class="lic-plan">${esc(l.plan)}</div>
|
|
</div>
|
|
<div class="lic-cost">${esc(l.cost)}<span class="pm">/Monat</span></div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function entraHtml(u) {
|
|
const objId = '8f3a9e2b-4c1d-4f8a-9b2c-' + (u.id + '000000000000').slice(0, 12);
|
|
return `
|
|
<div class="tab-panel active">
|
|
<div class="entra-grid">
|
|
<div class="identity-card">
|
|
<div class="card-head" style="margin-bottom:14px">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M18 8h1a4 4 0 0 1 0 8h-1M2 8h16v9a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
Hybrid Identity
|
|
</div>
|
|
<span class="sec-status ok">Synchronisiert</span>
|
|
</div>
|
|
|
|
<div class="sync-diagram">
|
|
<div class="sync-node cloud">
|
|
<div class="sn-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/></svg></div>
|
|
<div class="sn-label">Entra ID</div>
|
|
<div class="sn-id">cereda.onmicrosoft.com</div>
|
|
</div>
|
|
<div class="sync-arrow">
|
|
<div class="line"></div>
|
|
<div class="lbl">Connect Sync · 28 min</div>
|
|
<div class="line"></div>
|
|
</div>
|
|
<div class="sync-node ad">
|
|
<div class="sn-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v6c0 1.66 4.03 3 9 3s9-1.34 9-3V5M3 11v6c0 1.66 4.03 3 9 3s9-1.34 9-3v-6"/></svg></div>
|
|
<div class="sn-label">Active Directory</div>
|
|
<div class="sn-id">CEREDA.local</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<div class="k">Object ID</div>
|
|
<div class="v mono">${esc(objId)}<button class="copy-btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></button></div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">UPN</div>
|
|
<div class="v mono">${esc(u.email)}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">sAMAccountName</div>
|
|
<div class="v mono">CEREDA\\${esc(u.id)}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Distinguished Name</div>
|
|
<div class="v mono" style="font-size:11.5px">CN=${esc(u.first + ' ' + u.last)},OU=${esc(u.dept.split(' ')[0])},OU=Users,DC=cereda,DC=local</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">User Type</div>
|
|
<div class="v">${u.role === 'extern' ? '<span class="role-chip extern">Guest</span>' : '<span class="role-chip manager">Member</span>'}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Source of Authority</div>
|
|
<div class="v">Hybrid · Windows Server AD</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Account Status</div>
|
|
<div class="v"><span class="sec-status ok">Enabled · Not locked</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="token-preview">
|
|
<span class="tn">// Letztes ID-Token (decoded JWT)</span>
|
|
{
|
|
<span class="tk">"aud"</span>: <span class="tv">"api://it-nexus.cereda.de"</span>,
|
|
<span class="tk">"oid"</span>: <span class="tv">"${esc(objId)}"</span>,
|
|
<span class="tk">"preferred_username"</span>: <span class="tv">"${esc(u.email)}"</span>,
|
|
<span class="tk">"roles"</span>: [<span class="tv">"${esc(u.roleLabel)}"</span>],
|
|
<span class="tk">"groups"</span>: [<span class="tv">"SG-${esc(u.dept.split(' ')[0])}"</span>, <span class="tv">"SG-MFA-Required"</span>],
|
|
<span class="tk">"amr"</span>: [<span class="tv">"pwd"</span>, <span class="tv">"fido"</span>],
|
|
<span class="tk">"iat"</span>: <span class="tv">1748432104</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex;flex-direction:column;gap:14px">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
Conditional Access
|
|
</div>
|
|
<span class="card-link">3 zugewiesen</span>
|
|
</div>
|
|
<div class="ca-list">
|
|
<div class="ca-policy">
|
|
<div class="cap-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="cap-body">
|
|
<div class="cap-name">MFA für alle Cloud-Apps</div>
|
|
<div class="cap-cond">Wenn: Außerhalb Firmennetz</div>
|
|
</div>
|
|
<span class="cap-mode">Grant</span>
|
|
</div>
|
|
<div class="ca-policy">
|
|
<div class="cap-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="cap-body">
|
|
<div class="cap-name">Compliant Device erforderlich</div>
|
|
<div class="cap-cond">Wenn: Zugriff auf SharePoint, Exchange</div>
|
|
</div>
|
|
<span class="cap-mode">Grant</span>
|
|
</div>
|
|
<div class="ca-policy blocked">
|
|
<div class="cap-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></div>
|
|
<div class="cap-body">
|
|
<div class="cap-name">Legacy Auth blockieren</div>
|
|
<div class="cap-cond">Protokoll: IMAP, POP3, ActiveSync</div>
|
|
</div>
|
|
<span class="cap-mode" style="background:var(--red-soft);color:var(--red)">Block</span>
|
|
</div>
|
|
<div class="ca-policy report">
|
|
<div class="cap-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg></div>
|
|
<div class="cap-body">
|
|
<div class="cap-name">Risk-basiertes MFA</div>
|
|
<div class="cap-cond">Wenn: Sign-in Risk ≥ medium</div>
|
|
</div>
|
|
<span class="cap-mode">Report</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
|
Aktive Sessions
|
|
</div>
|
|
</div>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<div class="k">Geräte</div>
|
|
<div class="v">${u.stats.sessions} aktiv</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Letzte Sign-in</div>
|
|
<div class="v">${esc(u.lastLogin)}</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Sign-in Risk</div>
|
|
<div class="v"><span class="sec-status ok">${esc(u.risk)}</span></div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">User Risk</div>
|
|
<div class="v"><span class="sec-status ok">Niedrig</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function fidoHtml(u) {
|
|
const keys = [
|
|
{ name: 'YubiKey 5C NFC', model: 'Yubico · USB-C / NFC', sn: 'YK-A7F3E2', added: '14. Mai 2026', last: 'vor 4 Min.', uses: 1247, primary: true },
|
|
{ name: 'YubiKey 5 NFC', model: 'Yubico · USB-A / NFC (Backup)', sn: 'YK-B92D11', added: '14. Mai 2026', last: 'vor 12 T.', uses: 38, primary: false }
|
|
].slice(0, u.stats.fido);
|
|
|
|
if (keys.length === 0) {
|
|
return `
|
|
<div class="tab-panel active">
|
|
<div class="empty-hint" style="padding:80px 24px">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="m15.5 7.5 3 3L22 7l-3-3M21 2l-9.6 9.6a5.5 5.5 0 1 1-2 2L19 4"/></svg>
|
|
<h3>Kein Sicherheitsschlüssel registriert</h3>
|
|
<p>Für diesen Benutzer wurde noch kein FIDO2-Schlüssel hinterlegt. Stark empfohlen für alle privilegierten Konten.</p>
|
|
<button class="btn primary" style="margin-top:16px">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg>
|
|
Sicherheitsschlüssel hinzufügen
|
|
</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
return `
|
|
<div class="tab-panel active">
|
|
<div class="fido-hero">
|
|
${keys.map(k => `
|
|
<div class="fido-key-card ${k.primary ? 'primary' : ''}">
|
|
<div class="fkc-head">
|
|
<div class="fkc-visual">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m15.5 7.5 3 3L22 7l-3-3M21 2l-9.6 9.6a5.5 5.5 0 1 1-2 2L19 4"/></svg>
|
|
</div>
|
|
<span class="fkc-status">${k.primary ? 'Primär' : 'Backup'}</span>
|
|
</div>
|
|
<div class="fkc-name">${esc(k.name)}</div>
|
|
<div class="fkc-sub">${esc(k.model)} · SN <span style="font-family:'JetBrains Mono',monospace;color:var(--text-2)">${esc(k.sn)}</span></div>
|
|
<div class="fkc-stats">
|
|
<div class="fkc-stat">
|
|
<div class="v">${k.uses.toLocaleString('de-DE')}</div>
|
|
<div class="l">Auth. gesamt</div>
|
|
</div>
|
|
<div class="fkc-stat">
|
|
<div class="v">${esc(k.last)}</div>
|
|
<div class="l">Letzte Nutzung</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
${keys.length < 3 ? `
|
|
<div class="add-key-card">
|
|
<div class="akc-plus"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg></div>
|
|
<div class="akc-title">Weiteren Schlüssel registrieren</div>
|
|
<div class="akc-sub">YubiKey, Titan, Feitian — WebAuthn</div>
|
|
</div>` : ''}
|
|
</div>
|
|
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>
|
|
Authentifizierungs-Aktivität
|
|
</div>
|
|
<span class="card-link">Letzte 7 Tage</span>
|
|
</div>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<div class="k">Erfolgreiche Anmeldungen</div>
|
|
<div class="v"><b style="color:var(--green)">${42 + u.stats.fido * 3}</b> via FIDO2</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Touch-Bestätigungen</div>
|
|
<div class="v">${38 + u.stats.fido * 3} · User Presence</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">PIN-Eingaben</div>
|
|
<div class="v">${4 + u.stats.fido} · User Verification</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Fehlversuche</div>
|
|
<div class="v"><span style="color:var(--green)">0</span> blockiert · 0 Rate-Limit</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Verwendete Browser</div>
|
|
<div class="v">Edge 124 · Safari 17 · Firefox 126</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
Empfehlungen
|
|
</div>
|
|
</div>
|
|
<div class="sec-list">
|
|
<div class="sec-item">
|
|
<div class="sec-icon si-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Mindestens 2 Schlüssel registriert</div>
|
|
<div class="sec-sub">Primär & Backup vorhanden</div>
|
|
</div>
|
|
<span class="sec-status ok">Erfüllt</span>
|
|
</div>
|
|
<div class="sec-item">
|
|
<div class="sec-icon si-ok"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Schlüssel-Attestation aktiv</div>
|
|
<div class="sec-sub">Nur zertifizierte Authenticators</div>
|
|
</div>
|
|
<span class="sec-status ok">Erfüllt</span>
|
|
</div>
|
|
<div class="sec-item">
|
|
<div class="sec-icon si-warn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg></div>
|
|
<div class="sec-body">
|
|
<div class="sec-label">Passwortlose Anmeldung</div>
|
|
<div class="sec-sub">Optional — Passwort noch aktiv</div>
|
|
</div>
|
|
<span class="sec-status warn">Empfohlen</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function lifecycleHtml(u) {
|
|
return `
|
|
<div class="tab-panel active">
|
|
<div class="lc-stage-bar">
|
|
<div class="lc-stage done">
|
|
<div class="lcs-num">01 · ABGESCHLOSSEN</div>
|
|
<div class="lcs-label">Pre-Onboarding</div>
|
|
<div class="lcs-date">— 7 Tage vor Start</div>
|
|
</div>
|
|
<div class="lc-stage done">
|
|
<div class="lcs-num">02 · ABGESCHLOSSEN</div>
|
|
<div class="lcs-label">Onboarding</div>
|
|
<div class="lcs-date">${esc(u.joined)}</div>
|
|
</div>
|
|
<div class="lc-stage current">
|
|
<div class="lcs-num">03 · LAUFEND</div>
|
|
<div class="lcs-label">Aktiv</div>
|
|
<div class="lcs-date">Seit ${esc(u.joined)}</div>
|
|
</div>
|
|
<div class="lc-stage">
|
|
<div class="lcs-num">04</div>
|
|
<div class="lcs-label">Rollenwechsel</div>
|
|
<div class="lcs-date">— optional</div>
|
|
</div>
|
|
<div class="lc-stage">
|
|
<div class="lcs-num">05</div>
|
|
<div class="lcs-label">Offboarding</div>
|
|
<div class="lcs-date">— offen</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="lc-grid">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
Meilensteine & Termine
|
|
</div>
|
|
<a class="card-link" href="#">Alle anzeigen</a>
|
|
</div>
|
|
<div class="milestone-list">
|
|
<div class="milestone">
|
|
<div class="ms-dot done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Vertrag unterzeichnet</div>
|
|
<div class="ms-sub">${esc(u.dept)} · ${esc(u.position)}</div>
|
|
</div>
|
|
<div class="ms-time">${esc(u.joined)}</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Hardware ausgehändigt</div>
|
|
<div class="ms-sub">${u.stats.assets} Geräte · Setup durch IT</div>
|
|
</div>
|
|
<div class="ms-time">${esc(u.joined)}</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">IT-Schulung & Security Awareness</div>
|
|
<div class="ms-sub">ISO 27001 · Phishing-Simulation bestanden</div>
|
|
</div>
|
|
<div class="ms-time">vor 90 T.</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="20 6 9 17 4 12"/></svg></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Probezeit abgeschlossen</div>
|
|
<div class="ms-sub">Übernahme durch ${esc(u.manager)}</div>
|
|
</div>
|
|
<div class="ms-time">vor 60 T.</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot current"></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Letzte Recertification der Berechtigungen</div>
|
|
<div class="ms-sub">Quartalsweise durch ${esc(u.manager)}</div>
|
|
</div>
|
|
<div class="ms-time">vor 14 T.</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot upcoming"></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Nächstes Mitarbeitergespräch</div>
|
|
<div class="ms-sub">Geplant mit ${esc(u.manager)}</div>
|
|
</div>
|
|
<div class="ms-time future">in 42 T.</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot upcoming"></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Hardware-Refresh fällig</div>
|
|
<div class="ms-sub">Notebook-Tausch nach 36 Monaten</div>
|
|
</div>
|
|
<div class="ms-time future">in 184 T.</div>
|
|
</div>
|
|
<div class="milestone">
|
|
<div class="ms-dot upcoming"></div>
|
|
<div class="ms-body">
|
|
<div class="ms-title">Security-Schulung Wiederholung</div>
|
|
<div class="ms-sub">Jährliche Pflichtschulung</div>
|
|
</div>
|
|
<div class="ms-time future">in 275 T.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex;flex-direction:column;gap:14px">
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
Vertrag & Rolle
|
|
</div>
|
|
</div>
|
|
<div class="contract-grid">
|
|
<div class="kv-card">
|
|
<div class="kvc-label">Eintritt</div>
|
|
<div class="kvc-value">${esc(u.joined)}</div>
|
|
<div class="kvc-sub">Unbefristet</div>
|
|
</div>
|
|
<div class="kv-card">
|
|
<div class="kvc-label">Beschäftigung</div>
|
|
<div class="kvc-value">${u.role === 'extern' ? 'Externer Auftrag' : 'Vollzeit · 40h'}</div>
|
|
<div class="kvc-sub">${u.role === 'extern' ? 'Werkvertrag' : 'TVöD-äquiv.'}</div>
|
|
</div>
|
|
<div class="kv-card">
|
|
<div class="kvc-label">Standort</div>
|
|
<div class="kvc-value">${esc(u.location)}</div>
|
|
<div class="kvc-sub">${u.location === 'Remote' ? '100 % Homeoffice' : 'Hybrid 3:2'}</div>
|
|
</div>
|
|
<div class="kv-card">
|
|
<div class="kvc-label">Kostenstelle</div>
|
|
<div class="kvc-value">CC-${u.dept.split(' ')[0].toUpperCase().slice(0,3)}-${1000 + u.first.length * 7}</div>
|
|
<div class="kvc-sub">${esc(u.dept)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head">
|
|
<div class="card-title">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
|
|
Offboarding-Bereitschaft
|
|
</div>
|
|
<span class="sec-status off">Nicht aktiv</span>
|
|
</div>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<div class="k">Geplantes Austrittsdatum</div>
|
|
<div class="v" style="color:var(--text-4)">— Keines hinterlegt</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Datenübergabe-Plan</div>
|
|
<div class="v">Automatisch · OneDrive, Mail-Archiv</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Hardware-Rückgabe</div>
|
|
<div class="v">${u.stats.assets} Geräte registriert</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Lizenzfreigabe</div>
|
|
<div class="v">${u.stats.lic} Lizenzen · ca. € ${(u.stats.lic * 24).toFixed(0)}/Monat</div>
|
|
</div>
|
|
<div class="info-row">
|
|
<div class="k">Wissenstransfer</div>
|
|
<div class="v" style="color:var(--text-4)">— Noch nicht initiiert</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;gap:8px;margin-top:14px">
|
|
<button class="btn" style="flex:1;justify-content:center">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
|
Austritt planen
|
|
</button>
|
|
<button class="btn" style="flex:1;justify-content:center">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg>
|
|
Lifecycle Export
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/* ---- Init ---- */
|
|
$('#searchInput').addEventListener('input', (e) => {
|
|
state.search = e.target.value;
|
|
renderList();
|
|
});
|
|
$$('#filterRow .filter-pill').forEach(b => b.addEventListener('click', () => {
|
|
state.filter = b.dataset.role;
|
|
$$('#filterRow .filter-pill').forEach(x => x.classList.toggle('on', x === b));
|
|
renderList();
|
|
}));
|
|
|
|
renderList();
|
|
renderDetail();
|
|
</script>
|
|
</body>
|
|
</html>
|