Initial commit: IT Nexus Web-App
This commit is contained in:
629
agent-cs/network-topology.html
Normal file
629
agent-cs/network-topology.html
Normal 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 ? ` · <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>
|
||||
Reference in New Issue
Block a user