Initial commit: IT Nexus Web-App

This commit is contained in:
2026-06-01 20:49:07 +02:00
commit 8023765e6c
387 changed files with 106900 additions and 0 deletions

View File

@@ -0,0 +1,629 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>IT Nexus — Live Netzwerk-Topologie</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #04040A; font-family: 'Segoe UI', system-ui, sans-serif; color: #E4E4E7; }
/* ── TOPBAR (mit Buttons) ── */
#topbar {
position: fixed; top: 0; left: 0; right: 0; height: 52px;
background: rgba(6,6,14,0.99); border-bottom: 1px solid #0F0F1A;
display: flex; align-items: center; gap: 10px; padding: 0 16px;
z-index: 100;
}
#topbar-title { font-size: 13px; font-weight: 700; color: #FAFAFA; margin-right: 16px; white-space: nowrap; }
#topbar-title em { color: #009B9A; font-style: normal; }
.sim-sep { width: 1px; height: 22px; background: #1A1A24; margin: 0 6px; }
.sim-lbl { font-size: 9px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: #27272A; white-space: nowrap; }
.tb {
padding: 6px 13px; border-radius: 8px;
border: 1px solid #1A1A24;
background: rgba(14,14,22,0.9);
color: #52525B; font-size: 11px; font-weight: 600;
cursor: pointer; font-family: inherit; white-space: nowrap;
transition: all 0.2s;
}
.tb:hover { border-color: #3F3F46; color: #E4E4E7; background: rgba(30,30,44,0.9); }
.tb:active { transform: scale(0.97); }
.tb.active-flow { border-color: var(--tc); color: var(--tc); box-shadow: 0 0 8px var(--tc-g); }
/* ── LOG PANEL (links, volle Höhe) ── */
#logpanel {
position: fixed; top: 52px; left: 0; bottom: 0; width: 270px;
background: rgba(4,4,12,0.99); border-right: 1px solid #0D0D1A;
padding: 18px 16px; z-index: 80; overflow: hidden;
display: flex; flex-direction: column;
}
#log-lbl {
font-size: 8.5px; font-weight: 800; letter-spacing: 0.14em;
color: #1A1A28; text-transform: uppercase; margin-bottom: 16px;
flex-shrink: 0;
}
#log-body { display: flex; flex-direction: column; gap: 0; flex: 1; overflow: hidden; }
.ll {
display: flex; flex-direction: column; gap: 3px;
padding: 10px 12px 10px 14px;
border-left: 2px solid var(--lc, #1E1E2E);
margin-bottom: 8px;
background: rgba(255,255,255,0.018);
border-radius: 0 8px 8px 0;
animation: fadeIn .3s ease;
}
@keyframes fadeIn { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }
.ll-t {
font-family: 'Consolas', monospace;
font-size: 10px; color: #2A2A3C;
}
.ll-step { font-size: 10px; font-weight: 700; color: #3A3A50; margin-bottom: 2px; }
.ll-m { font-size: 13px; font-weight: 600; line-height: 1.4; }
/* ── NODES ── */
.node {
position: fixed; z-index: 20;
background: rgba(10,10,18,0.97);
border: 1px solid #14141E;
border-radius: 11px;
padding: 10px 13px;
width: 158px;
backdrop-filter: blur(12px);
transition: box-shadow 0.25s, border-color 0.25s, transform 0.15s;
cursor: default; user-select: none;
}
.node:hover { border-color: var(--nc, #27272A); transform: translateY(-1px); z-index: 30; }
.node.lit {
border-color: var(--nc) !important;
box-shadow: 0 0 0 1px var(--nc), 0 0 20px var(--ng), inset 0 0 14px var(--ni);
}
.nh { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.ni { font-size: 15px; line-height: 1; flex-shrink: 0; }
.nn { font-size: 11.5px; font-weight: 700; color: #F4F4F5; }
.nb {
margin-left: auto; font-size: 8.5px; font-weight: 700;
padding: 2px 6px; border-radius: 4px; white-space: nowrap;
background: rgba(34,197,94,.14); color: #4ADE80;
}
.nb.w { background: rgba(251,191,36,.14); color: #FCD34D; }
.nb.g { background: rgba(82,82,91,.16); color: #52525B; }
.nb.b { background: rgba(59,130,246,.14); color: #93C5FD; }
.ns { font-size: 9.5px; color: #2E2E42; line-height: 1.45; }
/* ── ZONE LABELS ── */
.zl {
position: fixed; z-index: 25;
font-size: 8.5px; font-weight: 800; letter-spacing: 0.14em;
text-transform: uppercase; color: #14141E; pointer-events: none;
}
/* ── LEGEND ── */
#legend {
position: fixed; right: 14px; top: 62px; z-index: 80;
background: rgba(6,6,14,0.97); border: 1px solid #0E0E18;
border-radius: 11px; padding: 12px 14px; width: 178px;
backdrop-filter: blur(12px);
}
#legend h3 { font-size: 8.5px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; color: #22222E; margin-bottom: 9px; }
.li { display: flex; align-items: center; gap: 9px; font-size: 10px; color: #52525B; margin-bottom: 5px; }
.ld { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; }
/* ── BG + SVG ── */
#bg { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
#sv { position: fixed; inset: 0; z-index: 10; pointer-events: none; overflow: visible; }
</style>
</head>
<body>
<canvas id="bg"></canvas>
<svg id="sv" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="glow" x="-60%" y="-60%" width="220%" height="220%">
<feGaussianBlur stdDeviation="3.5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow2" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="7" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
</svg>
<!-- ════ TOPBAR ════ -->
<div id="topbar">
<span id="topbar-title">IT <em>Nexus</em> — Architektur</span>
<div class="sim-sep"></div>
<span class="sim-lbl">Simulation:</span>
<button class="tb" style="--tc:#3B82F6;--tc-g:rgba(59,130,246,.3)" onclick="doCheckin()">🔄 Check-in</button>
<button class="tb" style="--tc:#F59E0B;--tc-g:rgba(245,158,11,.3)" onclick="doAnnouncement()">📢 Ankündigung</button>
<button class="tb" style="--tc:#EF4444;--tc-g:rgba(239,68,68,.3)" onclick="doPatch()">🔧 Patch-Befehl</button>
<button class="tb" style="--tc:#2DD4BF;--tc-g:rgba(45,212,191,.3)" onclick="doUpdate()">⬇️ Agent-Update</button>
<button class="tb" style="--tc:#0EA5E9;--tc-g:rgba(14,165,233,.3)" onclick="doIntune()">📦 Intune-Deploy</button>
</div>
<!-- ════ NODES ════ -->
<div class="zl" id="zl-pc">Windows-Endgerät</div>
<div class="node" id="n-svc" style="--nc:#3B82F6;--ng:rgba(59,130,246,.25);--ni:rgba(59,130,246,.04)">
<div class="nh"><span class="ni">⚙️</span><span class="nn">Agent Service</span><span class="nb">Running</span></div>
<div class="ns">Windows Service · SYSTEM<br>Check-in · Commands · Auto-Update</div>
</div>
<div class="node" id="n-dash" style="--nc:#6366F1;--ng:rgba(99,102,241,.25);--ni:rgba(99,102,241,.04)">
<div class="nh"><span class="ni">📊</span><span class="nn">Dashboard</span><span class="nb b">Tray</span></div>
<div class="ns">WPF · Dark-Theme · Autostart<br>Liest status.json alle 5 Sek</div>
</div>
<div class="node" id="n-nfy" style="--nc:#F59E0B;--ng:rgba(245,158,11,.25);--ni:rgba(245,158,11,.04)">
<div class="nh"><span class="ni">🔔</span><span class="nn">Notification</span><span class="nb g">On Demand</span></div>
<div class="ns">WPF Popup · schtasks.exe<br>Läuft als eingeloggter User</div>
</div>
<div class="zl" id="zl-srv">IT Nexus Server · 192.168.0.194</div>
<div class="node" id="n-ngx" style="--nc:#22C55E;--ng:rgba(34,197,94,.25);--ni:rgba(34,197,94,.04)">
<div class="nh"><span class="ni">🌐</span><span class="nn">nginx</span><span class="nb">Port 443</span></div>
<div class="ns">Reverse Proxy · TLS<br>/api/* → Backend · / → Frontend</div>
</div>
<div class="node" id="n-be" style="--nc:#10B981;--ng:rgba(16,185,129,.25);--ni:rgba(16,185,129,.04)">
<div class="nh"><span class="ni"></span><span class="nn">Node.js Backend</span><span class="nb">Port 5000</span></div>
<div class="ns">Express · JWT · Agent-Key Auth<br>Cron Jobs · KI-Integration</div>
</div>
<div class="node" id="n-db" style="--nc:#6B7280;--ng:rgba(107,114,128,.22);--ni:rgba(107,114,128,.04)">
<div class="nh"><span class="ni">🗄️</span><span class="nn">SQLite DB</span><span class="nb g">Volume</span></div>
<div class="ns">monitoring_agents · tickets<br>patch_commands · announcements</div>
</div>
<div class="node" id="n-fe" style="--nc:#3B82F6;--ng:rgba(59,130,246,.25);--ni:rgba(59,130,246,.04)">
<div class="nh"><span class="ni">⚛️</span><span class="nn">React Frontend</span><span class="nb b">Port 3000</span></div>
<div class="ns">Monitoring · Patch · Helpdesk<br>KI-Assistent · Dokumentation</div>
</div>
<div class="zl" id="zl-ext">Externe Dienste</div>
<div class="node" id="n-grp" style="--nc:#818CF8;--ng:rgba(129,140,248,.25);--ni:rgba(129,140,248,.04)">
<div class="nh"><span class="ni">☁️</span><span class="nn">Microsoft Graph</span><span class="nb w">Azure AD</span></div>
<div class="ns">Teams · MDO Alerts · Mail<br>SecurityAlert.Read.All</div>
</div>
<div class="node" id="n-ai" style="--nc:#2DD4BF;--ng:rgba(45,212,191,.25);--ni:rgba(45,212,191,.04)">
<div class="nh"><span class="ni">🤖</span><span class="nn">Anthropic Claude</span><span class="nb w">claude-sonnet-4-6</span></div>
<div class="ns">KI-Assistent · Wissensdatenbank<br>Ticket-Analyse · Automatisierung</div>
</div>
<div class="node" id="n-pmx" style="--nc:#F97316;--ng:rgba(249,115,22,.25);--ni:rgba(249,115,22,.04)">
<div class="nh"><span class="ni">🖥️</span><span class="nn">Proxmox API</span><span class="nb w">hve-01</span></div>
<div class="ns">VM/CT Status · Ressourcen<br>192.168.0.184:8006</div>
</div>
<div class="zl" id="zl-adm">IT-Administrator</div>
<div class="node" id="n-web" style="--nc:#E4E4E7;--ng:rgba(228,228,231,.15);--ni:rgba(228,228,231,.03)">
<div class="nh"><span class="ni">🧑‍💼</span><span class="nn">Web Browser</span><span class="nb">HTTPS</span></div>
<div class="ns">it-nexus.cereda-systems.de<br>Monitoring · Patch · Helpdesk</div>
</div>
<div class="node" id="n-itune" style="--nc:#0EA5E9;--ng:rgba(14,165,233,.25);--ni:rgba(14,165,233,.04)">
<div class="nh"><span class="ni">📦</span><span class="nn">Intune / MDM</span><span class="nb g">Deploy</span></div>
<div class="ns">setup.exe · GUID-Erkennung<br>Rollout auf Endgeräte</div>
</div>
<!-- ════ LEGEND ════ -->
<div id="legend">
<h3>Datenflüsse</h3>
<div class="li"><div class="ld" style="background:#3B82F6;box-shadow:0 0 5px #3B82F6"></div>Check-in (60s)</div>
<div class="li"><div class="ld" style="background:#22C55E;box-shadow:0 0 5px #22C55E"></div>Response / Commands</div>
<div class="li"><div class="ld" style="background:#6B7280;box-shadow:0 0 4px #6B7280"></div>SQLite DB-Operationen</div>
<div class="li"><div class="ld" style="background:#6366F1;box-shadow:0 0 5px #6366F1"></div>Dashboard Refresh</div>
<div class="li"><div class="ld" style="background:#818CF8;box-shadow:0 0 5px #818CF8"></div>Microsoft Graph API</div>
<div class="li"><div class="ld" style="background:#2DD4BF;box-shadow:0 0 5px #2DD4BF"></div>Anthropic Claude AI</div>
<div class="li"><div class="ld" style="background:#F97316;box-shadow:0 0 5px #F97316"></div>Proxmox API</div>
<div class="li"><div class="ld" style="background:#F4F4F5;box-shadow:0 0 4px #F4F4F5"></div>Admin Web-Zugriff</div>
<div class="li"><div class="ld" style="background:#F59E0B;box-shadow:0 0 5px #F59E0B"></div>Ankündigung</div>
<div class="li"><div class="ld" style="background:#EF4444;box-shadow:0 0 5px #EF4444"></div>Patch-Command / Update</div>
</div>
<!-- ════ LOG PANEL LINKS ════ -->
<div id="logpanel">
<div id="log-lbl">Was passiert gerade</div>
<div id="log-body">
<div class="ll" style="--lc:#1E1E2E">
<div class="ll-t"></div>
<div class="ll-m" style="color:#27273A">Oben einen Button drücken<br>um eine Simulation zu starten</div>
</div>
</div>
</div>
<script>
// ════════════════════════════════════════════════════════
// BACKGROUND GRID
// ════════════════════════════════════════════════════════
const bgc = document.getElementById('bg');
const bgx = bgc.getContext('2d');
function drawGrid() {
bgc.width = innerWidth; bgc.height = innerHeight;
bgx.fillStyle = '#04040A';
bgx.fillRect(0, 0, bgc.width, bgc.height);
// Subtle dot grid
bgx.fillStyle = 'rgba(255,255,255,0.028)';
for (let x = 0; x < bgc.width; x += 52)
for (let y = 0; y < bgc.height; y += 52) {
bgx.beginPath(); bgx.arc(x, y, 0.8, 0, Math.PI*2); bgx.fill();
}
// Zone backgrounds
const zones = [
{ x: LP+4, w: C(0)+NW/2-LP-4, color: 'rgba(59,130,246,0.018)' },
{ x: C(1)-NW/2-16, w: NW+32, color: 'rgba(34,197,94,0.015)' },
{ x: C(2)-NW/2-16, w: NW+32, color: 'rgba(129,140,248,0.015)'},
{ x: C(3)-NW/2-16, w: innerWidth-C(3)+NW/2, color: 'rgba(228,228,231,0.012)'},
];
const top = TB, bot = bgc.height - LH;
zones.forEach(z => {
bgx.fillStyle = z.color;
bgx.beginPath();
bgx.roundRect(z.x, top+4, z.w, bot-top-8, 14);
bgx.fill();
});
}
// ════════════════════════════════════════════════════════
// LAYOUT ENGINE
// ════════════════════════════════════════════════════════
const TB = 52, LH = 0, LP = 270, NW = 158, NH = 72;
function C(col) { // column center x — beginnt nach dem Log-Panel
const avail = innerWidth - LP;
return LP + avail * [0.11, 0.35, 0.62, 0.86][col];
}
function R(row) { // row center y
const avail = innerHeight - TB - LH - 16;
return TB + avail * [0.10, 0.34, 0.58, 0.82][row];
}
const NODE_POS = {
'n-svc': [0, 0], 'n-dash': [0, 1], 'n-nfy': [0, 2],
'n-ngx': [1, 0], 'n-be': [1, 1], 'n-db': [1, 2], 'n-fe': [1, 3],
'n-grp': [2, 0], 'n-ai': [2, 1], 'n-pmx': [2, 2],
'n-web': [3, 0], 'n-itune': [3, 2],
};
const ZONE_LABELS = {
'zl-pc': [0, 0.02], 'zl-srv': [1, 0.02],
'zl-ext': [2, 0.02], 'zl-adm': [3, 0.02],
};
const centers = {}; // id → {x, y}
function layout() {
Object.entries(NODE_POS).forEach(([id, [col, row]]) => {
const el = document.getElementById(id); if (!el) return;
const cx = C(col), cy = R(row);
el.style.left = (cx - NW/2) + 'px';
el.style.top = (cy - NH/2) + 'px';
centers[id] = { x: cx, y: cy };
});
Object.entries(ZONE_LABELS).forEach(([id, [col, rf]]) => {
const el = document.getElementById(id); if (!el) return;
const avail = innerHeight - TB;
el.style.left = (C(col) - 70) + 'px';
el.style.top = (TB + avail * rf + 4) + 'px';
});
}
// ════════════════════════════════════════════════════════
// CONNECTION PATHS
// ════════════════════════════════════════════════════════
const sv = document.getElementById('sv');
const connPaths = {}; // id → SVGPathElement
const CONNS = [
// id, from, to, color, curveDir, label
['ci_out', 'n-svc', 'n-ngx', '#3B82F6', 1, 'Check-in → nginx'],
['ci_in', 'n-ngx', 'n-svc', '#22C55E', -1, 'Response ← nginx'],
['ngx_be', 'n-ngx', 'n-be', '#10B981', 1, 'nginx → Backend'],
['be_ngx', 'n-be', 'n-ngx', '#10B981', -1, 'Backend → nginx'],
['be_db', 'n-be', 'n-db', '#6B7280', 1, 'Backend → SQLite'],
['db_be', 'n-db', 'n-be', '#6B7280', -1, 'SQLite → Backend'],
['dash_read','n-dash', 'n-svc', '#6366F1', 1, 'Dashboard liest status.json'],
['be_grp', 'n-be', 'n-grp', '#818CF8', 0, 'Backend → MS Graph'],
['be_ai', 'n-be', 'n-ai', '#2DD4BF', 0, 'Backend → Claude AI'],
['be_pmx', 'n-be', 'n-pmx', '#F97316', 0, 'Backend → Proxmox'],
['web_ngx', 'n-web', 'n-ngx', '#E4E4E7', -1, 'Admin → nginx'],
['ngx_fe', 'n-ngx', 'n-fe', '#3B82F6', -1, 'nginx → Frontend'],
['fe_web', 'n-fe', 'n-web', '#3B82F6', 1, 'Frontend → Browser'],
['ann', 'n-svc', 'n-nfy', '#F59E0B', 1, 'Ankündigung → Notification'],
['cmd', 'n-be', 'n-svc', '#EF4444', -1, 'Command → Service'],
['itune', 'n-itune','n-svc', '#0EA5E9', -1, 'Intune → Service (Deploy)'],
];
function nodeEdge(fromId, toId) {
const f = centers[fromId], t = centers[toId];
if (!f || !t) return { fx:0,fy:0, tx:0,ty:0 };
const dx = t.x-f.x, dy = t.y-f.y;
const ang = Math.atan2(dy, dx);
const hw = NW/2+4, hh = NH/2+4;
// From
const sf = Math.min(hw/Math.abs(Math.cos(ang)||1e-9), hh/Math.abs(Math.sin(ang)||1e-9));
const fx = f.x + Math.cos(ang)*Math.min(sf, Math.hypot(hw,hh));
const fy = f.y + Math.sin(ang)*Math.min(sf, Math.hypot(hw,hh));
// To
const a2 = ang+Math.PI;
const st = Math.min(hw/Math.abs(Math.cos(a2)||1e-9), hh/Math.abs(Math.sin(a2)||1e-9));
const tx = t.x + Math.cos(a2)*Math.min(st, Math.hypot(hw,hh));
const ty = t.y + Math.sin(a2)*Math.min(st, Math.hypot(hw,hh));
return { fx, fy, tx, ty };
}
function pathD(fx, fy, tx, ty, curveDir) {
const dx = tx-fx, dy = ty-fy;
const len = Math.hypot(dx,dy)||1;
// Perpendicular offset
const nx = -dy/len, ny = dx/len;
const curve = len * 0.22 * curveDir;
const cx = (fx+tx)/2 + nx*curve;
const cy = (fy+ty)/2 + ny*curve;
return { d: `M${fx},${fy} Q${cx},${cy} ${tx},${ty}`, cx, cy };
}
// Pre-stored bezier control points for animation
const connData = {}; // id → { fx,fy,cx,cy,tx,ty, path }
function buildPaths() {
sv.querySelectorAll('.cp').forEach(e=>e.remove());
Object.keys(connPaths).forEach(k=>delete connPaths[k]);
CONNS.forEach(([id, fromId, toId, color, curveDir]) => {
const {fx,fy,tx,ty} = nodeEdge(fromId, toId);
const {d, cx, cy} = pathD(fx,fy,tx,ty, curveDir);
const p = document.createElementNS('http://www.w3.org/2000/svg','path');
p.setAttribute('d', d);
p.setAttribute('stroke', color+'1A');
p.setAttribute('stroke-width','1.5');
p.setAttribute('fill','none');
p.setAttribute('stroke-dasharray','3 10');
p.classList.add('cp');
sv.appendChild(p);
connPaths[id] = p;
connData[id] = { fx,fy,cx,cy,tx,ty, color, fromId, toId, curveDir };
});
}
// ════════════════════════════════════════════════════════
// PACKET SYSTEM
// ════════════════════════════════════════════════════════
let packets = [];
function quadBez(t, fx,fy,cx,cy,tx,ty) {
const m=1-t;
return { x: m*m*fx + 2*m*t*cx + t*t*tx, y: m*m*fy + 2*m*t*cy + t*t*ty };
}
function spawn(connId, color, speed, label, statKey, size=5) {
const d = connData[connId]; if(!d) return;
const {fx,fy,cx,cy,tx,ty,fromId,toId} = d;
// Glow trail
const trail = document.createElementNS('http://www.w3.org/2000/svg','circle');
trail.setAttribute('r', size*1.8);
trail.setAttribute('fill', color+'22');
// Core dot
const core = document.createElementNS('http://www.w3.org/2000/svg','circle');
core.setAttribute('r', size);
core.setAttribute('fill', color);
core.setAttribute('filter','url(#glow)');
// Bright center
const hi = document.createElementNS('http://www.w3.org/2000/svg','circle');
hi.setAttribute('r', size*0.45);
hi.setAttribute('fill','#ffffff88');
const g = document.createElementNS('http://www.w3.org/2000/svg','g');
g.append(trail,core,hi);
sv.appendChild(g);
// Highlight path
const pEl = connPaths[connId];
if(pEl) pEl.setAttribute('stroke', color+'50');
packets.push({ g, t:0, speed, fx,fy,cx,cy,tx,ty, color, pEl, toId, label, done:false });
addLog(color, label);
}
// ════════════════════════════════════════════════════════
// ANIMATION LOOP
// ════════════════════════════════════════════════════════
let lastT=0;
function frame(now) {
const dt = Math.min(now-(lastT||now), 50);
lastT=now;
packets = packets.filter(p => {
if(p.done){ p.g.remove(); return false; }
p.t = Math.min(p.t + p.speed*dt/1000, 1);
const pos = quadBez(p.t, p.fx,p.fy,p.cx,p.cy,p.tx,p.ty);
const pos2 = quadBez(Math.max(0,p.t-0.04), p.fx,p.fy,p.cx,p.cy,p.tx,p.ty);
const dx=pos.x-pos2.x, dy=pos.y-pos2.y;
p.g.setAttribute('transform',`translate(${pos.x},${pos.y})`);
// Rotate trail in direction of travel
p.g.children[0].setAttribute('cx', -dx*3);
p.g.children[0].setAttribute('cy', -dy*3);
if(p.t>=1){
if(p.pEl) p.pEl.setAttribute('stroke', connData[Object.keys(connData).find(k=>connData[k].fromId===connData[Object.keys(connData).find(k2=>connData[k2].fromId===p.pEl?.dataset?.from)])]?.color+'1A' || '#ffffff1A');
if(p.pEl) p.pEl.setAttribute('stroke', p.color+'1A');
if(p.toId) pulseNode(p.toId, p.color);
p.done=true;
}
return true;
});
requestAnimationFrame(frame);
}
function pulseNode(id, color) {
const el = document.getElementById(id); if(!el) return;
el.classList.add('lit');
el.style.setProperty('--nc', color);
el.style.setProperty('--ng', hexToRgba(color, 0.28));
el.style.setProperty('--ni', hexToRgba(color, 0.05));
setTimeout(()=>el.classList.remove('lit'), 700);
}
function hexToRgba(hex, a) {
const r=parseInt(hex.slice(1,3),16), g=parseInt(hex.slice(3,5),16), b=parseInt(hex.slice(5,7),16);
return `rgba(${r},${g},${b},${a})`;
}
// ════════════════════════════════════════════════════════
// TRIGGER SCENARIOS
// ════════════════════════════════════════════════════════
function doCheckin() {
// 1. Agent schickt alle 60 Sek seine Daten an den Server
addLog('#3B82F6', '1/4 · Agent schickt Systemdaten an Server (CPU, RAM, Disk, BitLocker…)');
setTimeout(()=>spawn('ci_out','#3B82F6',.45,'Agent → nginx: Systemdaten senden',null), 0);
setTimeout(()=>spawn('ngx_be','#3B82F6',.50,'nginx → Backend: weiterleiten',null), 900);
// 2. Backend speichert in DB
setTimeout(()=>{
addLog('#6B7280','2/4 · Backend speichert Daten in SQLite-Datenbank');
spawn('be_db','#6B7280',.55,'Backend → SQLite: Gerätedaten speichern',null);
}, 1700);
// 3. Backend antwortet mit Commands / Ankündigungen
setTimeout(()=>{
addLog('#22C55E','3/4 · Server antwortet: keine neuen Befehle — alles OK');
spawn('be_ngx','#22C55E',.50,'Backend → nginx: Antwort mit Status',null);
}, 2600);
setTimeout(()=>spawn('ci_in','#22C55E',.45,'nginx → Agent: Antwort empfangen',null), 3400);
// 4. Agent schreibt status.json, Dashboard liest sie
setTimeout(()=>{
addLog('#6366F1','4/4 · Agent aktualisiert status.json · Dashboard zeigt neue Werte');
spawn('dash_read','#6366F1',.65,'Dashboard liest status.json (alle 5 Sek)',null);
pulseNode('n-dash','#6366F1');
}, 4200);
}
function doAnnouncement() {
addLog('#F59E0B','1/5 · Admin tippt Ankündigung im Web-Dashboard ein');
// Admin → Server
setTimeout(()=>spawn('web_ngx','#F59E0B',.43,'Browser → nginx: Ankündigung speichern',null,6), 0);
setTimeout(()=>spawn('ngx_be','#F59E0B',.50,'nginx → Backend: weiterleiten',null,6), 800);
setTimeout(()=>{
addLog('#F59E0B','2/5 · Backend speichert Ankündigung in Datenbank');
spawn('be_db','#F59E0B',.55,'Backend → SQLite: Ankündigung speichern',null,6);
}, 1500);
// Beim nächsten Check-in bekommt Agent die Ankündigung
setTimeout(()=>{
addLog('#F59E0B','3/5 · Beim nächsten Check-in: Agent empfängt Ankündigung');
spawn('ci_in','#F59E0B',.45,'nginx → Agent: Ankündigung mitschicken',null,6);
}, 3000);
// Agent startet Dialog als eingeloggter User
setTimeout(()=>{
addLog('#F59E0B','4/5 · Agent startet Popup-Dialog als eingeloggter Windows-User');
spawn('ann','#F59E0B',.38,'Agent → Notification-Fenster öffnen (schtasks)',null,6);
}, 4200);
setTimeout(()=>{
addLog('#22C55E','5/5 · User klickt "Gelesen" → Bestätigung geht zurück an Server ✅');
pulseNode('n-nfy','#22C55E');
spawn('ci_out','#22C55E',.43,'Agent → Server: Ankündigung bestätigt (ACK)',null,6);
}, 6500);
}
function doPatch() {
addLog('#EF4444','1/5 · Admin klickt "Windows Updates installieren" im Patch-Management');
setTimeout(()=>spawn('web_ngx','#EF4444',.43,'Browser → nginx: Patch-Befehl senden',null,6), 0);
setTimeout(()=>spawn('ngx_be','#EF4444',.50,'nginx → Backend: weiterleiten',null,6), 800);
setTimeout(()=>{
addLog('#EF4444','2/5 · Backend schreibt Befehl in die Datenbank (Command-Queue)');
spawn('be_db','#EF4444',.55,'Backend → SQLite: Befehl in Queue eintragen',null,6);
}, 1500);
setTimeout(()=>{
addLog('#EF4444','3/5 · Beim nächsten Check-in (max. 60 Sek) bekommt Agent den Befehl');
spawn('ci_in','#EF4444',.45,'nginx → Agent: Befehl "install_updates" mitschicken',null,6);
}, 4000);
setTimeout(()=>{
addLog('#EF4444','4/5 · Agent führt Windows Updates aus (UsoClient.exe)…');
pulseNode('n-svc','#EF4444');
}, 5200);
setTimeout(()=>{
addLog('#22C55E','5/5 · Agent meldet Ergebnis zurück: "done" ✅');
spawn('ci_out','#22C55E',.43,'Agent → Server: Ergebnis melden (done)',null,6);
}, 8000);
}
function doUpdate() {
addLog('#2DD4BF','1/4 · Server hat neue Agent-Version — Agent merkt das beim Check-in');
setTimeout(()=>spawn('ci_in','#2DD4BF',.45,'nginx → Agent: "neue Version v2.1.0 verfügbar"',null,6), 200);
setTimeout(()=>{
addLog('#2DD4BF','2/4 · Agent lädt neue EXE vom Server herunter');
spawn('cmd','#2DD4BF',.32,'Agent → Server: neue EXE herunterladen',null,7);
}, 1400);
setTimeout(()=>{
addLog('#2DD4BF','3/4 · Agent stoppt Service, ersetzt EXE-Datei, startet Service neu');
pulseNode('n-svc','#2DD4BF');
}, 4500);
setTimeout(()=>{
addLog('#22C55E','4/4 · Update fertig — Agent läuft mit neuer Version ✅');
spawn('ci_out','#22C55E',.43,'Agent → Server: Check-in mit neuer Version',null,6);
}, 6000);
}
function doIntune() {
addLog('#0EA5E9','1/3 · Intune (MDM) erkennt: Agent fehlt auf diesem Gerät');
setTimeout(()=>{
addLog('#0EA5E9','2/3 · Intune schiebt setup.exe aufs Gerät und führt sie aus');
spawn('itune','#0EA5E9',.28,'Intune → Gerät: setup.exe installieren',null,7);
}, 500);
setTimeout(()=>{
addLog('#0EA5E9',' Installer: Service registrieren · Autostart · Start-Menü Eintrag');
pulseNode('n-svc','#0EA5E9');
pulseNode('n-dash','#0EA5E9');
}, 5000);
setTimeout(()=>{
addLog('#22C55E','3/3 · Agent startet, meldet sich beim Server an ✅');
spawn('ci_out','#22C55E',.43,'Agent → Server: erster Check-in nach Installation',null,6);
}, 6500);
}
// ════════════════════════════════════════════════════════
// EVENT LOG
// ════════════════════════════════════════════════════════
const logLines = [];
function addLog(color, msg) {
const t = new Date().toLocaleTimeString('de-DE',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});
const stepMatch = msg.match(/^(\d+\/\d+)\s*·\s*(.*)/);
const step = stepMatch ? stepMatch[1] : '';
const text = stepMatch ? stepMatch[2] : msg;
logLines.unshift({t, step, text, color});
if(logLines.length>6) logLines.pop();
document.getElementById('log-body').innerHTML = logLines.map((l,i) => {
const opacity = i === 0 ? '1' : i === 1 ? '0.7' : i === 2 ? '0.5' : '0.3';
return `<div class="ll" style="--lc:${l.color};opacity:${opacity}">
<div class="ll-t">${l.t}${l.step ? ` &nbsp;·&nbsp; <b>${l.step}</b>` : ''}</div>
<div class="ll-m" style="color:${l.color}">${l.text}</div>
</div>`;
}).join('');
}
// ════════════════════════════════════════════════════════
// INIT + RESIZE
// ════════════════════════════════════════════════════════
function init() {
layout();
drawGrid();
buildPaths();
}
window.addEventListener('resize', ()=>{
clearTimeout(window._rt);
window._rt = setTimeout(()=>{ init(); },80);
});
window.addEventListener('load', ()=>{
setTimeout(()=>{
init();
requestAnimationFrame(frame);
}, 120);
});
init();
</script>
</body>
</html>