410 lines
45 KiB
HTML
410 lines
45 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>WEVAL — Architecture Control</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
:root{
|
|
--bg:#09090b;--s1:#18181b;--s2:#27272a;--s3:#3f3f46;
|
|
--card:#18181b;--card-h:#1e1e22;
|
|
--border:rgba(255,255,255,.08);--border-h:rgba(255,255,255,.16);
|
|
--acc:#3b82f6;--g:#22c55e;--r:#ef4444;--o:#eab308;--p:#a78bfa;--bl:#60a5fa;--y:#fbbf24;
|
|
--t:#fafafa;--dim:#a1a1aa;--dim2:#71717a;--dim3:#52525b;
|
|
--glow:0 0 15px rgba(59,130,246,.15),0 0 30px rgba(59,130,246,.08);
|
|
--glow-g:0 0 15px rgba(34,197,94,.15),0 0 30px rgba(34,197,94,.08);
|
|
--glow-r:0 0 15px rgba(239,68,68,.15),0 0 30px rgba(239,68,68,.08);
|
|
--radius:12px;
|
|
--hf:'JetBrains Mono',monospace;--bf:'Inter',system-ui,sans-serif}
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
body{font-family:var(--bf);background:var(--bg);color:var(--t);min-height:100vh;overflow-x:hidden;
|
|
background-image:none}
|
|
::selection{background:rgba(0,229,255,.2);color:#fff}
|
|
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--dim2);border-radius:3px}
|
|
|
|
/* ANIMATIONS */
|
|
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
|
|
@keyframes glow-pulse{0%,100%{box-shadow:var(--glow)}50%{box-shadow:0 0 40px rgba(0,229,255,.2)}}
|
|
@keyframes spin{to{transform:rotate(360deg)}}
|
|
@keyframes dash{to{stroke-dashoffset:0}}
|
|
.fade{animation:fadeUp .5s ease both}
|
|
.d1{animation-delay:.05s}.d2{animation-delay:.1s}.d3{animation-delay:.15s}.d4{animation-delay:.2s}.d5{animation-delay:.25s}
|
|
|
|
.wrap{max-width:1480px;margin:0 auto;padding:16px 24px}
|
|
|
|
/* HEADER */
|
|
.hdr{display:flex;align-items:center;justify-content:space-between;padding:16px 0;border-bottom:1px solid var(--border)}
|
|
.hdr-left{display:flex;align-items:center;gap:14px}
|
|
.logo{font-family:var(--bf);font-size:1.2rem;font-weight:700;color:var(--t);letter-spacing:-.04em}
|
|
.logo b{color:var(--acc)}
|
|
.hdr-badge{padding:3px 10px;border-radius:20px;background:rgba(0,255,136,.08);border:1px solid rgba(0,255,136,.15);font-size:.62rem;font-family:var(--hf);color:var(--g);text-transform:uppercase;letter-spacing:1px}
|
|
.hdr-right{text-align:right}
|
|
.live-dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:var(--glow-g);animation:pulse 2s infinite;margin-right:5px}
|
|
.hdr-ts{font-family:var(--hf);font-size:.7rem;color:var(--dim)}
|
|
|
|
/* SEARCH */
|
|
.search-wrap{position:relative;margin:14px 0}
|
|
.search-input{width:100%;padding:12px 18px 12px 42px;border-radius:12px;border:1px solid var(--border);background:var(--card);backdrop-filter:blur(12px);color:var(--t);font-size:.85rem;font-family:var(--bf);outline:none;transition:all .25s}
|
|
.search-input:focus{border-color:var(--acc);box-shadow:var(--glow)}
|
|
.search-icon{position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--dim);font-size:1.1rem}
|
|
.search-results{position:absolute;top:100%;left:0;right:0;background:var(--card);backdrop-filter:blur(16px);border:1px solid var(--border);border-top:none;border-radius:0 0 12px 12px;max-height:280px;overflow-y:auto;z-index:100;display:none}
|
|
.search-results.open{display:block}
|
|
.sr-item{padding:10px 16px;border-bottom:1px solid rgba(0,229,255,.04);cursor:pointer;font-size:.8rem;display:flex;justify-content:space-between;align-items:center;transition:background .15s}
|
|
.sr-item:hover{background:rgba(0,229,255,.06)}
|
|
|
|
/* ALERTS */
|
|
.alerts{display:flex;gap:8px;overflow-x:auto;padding:12px 0;margin-bottom:4px}
|
|
.alert{display:flex;align-items:center;gap:5px;padding:5px 14px;border-radius:20px;font-size:.68rem;font-weight:500;white-space:nowrap;cursor:pointer;transition:all .2s;backdrop-filter:blur(8px)}
|
|
.alert:hover{transform:scale(1.04)}
|
|
.alert-crit{background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.15);color:var(--r)}
|
|
.alert-warn{background:rgba(255,159,67,.08);border:1px solid rgba(255,159,67,.15);color:var(--o)}
|
|
.alert-ok{background:rgba(0,255,136,.06);border:1px solid rgba(0,255,136,.12);color:var(--g)}
|
|
.alert-info{background:rgba(0,229,255,.05);border:1px solid rgba(0,229,255,.1);color:var(--acc)}
|
|
|
|
/* TABS */
|
|
.tabs{display:flex;gap:2px;border-bottom:1px solid var(--border);overflow-x:auto;padding-bottom:0;margin:0}
|
|
.tab{padding:10px 18px;font-size:.8rem;color:var(--dim2);cursor:pointer;border:none;background:none;font-family:var(--bf);font-weight:500;letter-spacing:-.01em;border-bottom:2px solid transparent;transition:all .2s;white-space:nowrap}
|
|
.tab.on{color:var(--acc);border-bottom-color:var(--acc)}
|
|
.tab:hover{color:var(--t)}
|
|
.pn{display:none}.pn.on{display:block;animation:fadeUp .4s ease}
|
|
|
|
/* GRID */
|
|
.g{display:grid;gap:12px;margin:14px 0}
|
|
.g2{grid-template-columns:1fr 1fr}.g3{grid-template-columns:1fr 1fr 1fr}.g4{grid-template-columns:repeat(4,1fr)}.g5{grid-template-columns:repeat(5,1fr)}
|
|
@media(max-width:1100px){.g4,.g5{grid-template-columns:repeat(2,1fr)}}
|
|
@media(max-width:700px){.g2,.g3,.g4,.g5{grid-template-columns:1fr}}
|
|
|
|
/* CARD */
|
|
.cd{background:var(--card);backdrop-filter:blur(12px);border:1px solid var(--border);border-radius:var(--radius);padding:16px;transition:all .25s}
|
|
.cd:hover{border-color:var(--border-h);box-shadow:var(--glow)}
|
|
.cd.click{cursor:pointer}
|
|
.cd.click:hover{transform:translateY(-2px)}
|
|
.cd h3{font-family:var(--hf);font-size:.62rem;color:var(--dim);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px}
|
|
|
|
/* KPI */
|
|
.kpi{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:20px 12px;min-height:100px}
|
|
.kpi-val{font-family:var(--hf);font-size:1.9rem;font-weight:700;line-height:1;margin-bottom:6px;letter-spacing:-1px}
|
|
.kpi-label{font-size:.72rem;color:var(--dim);font-weight:400;margin-top:2px}
|
|
.kpi-sub{font-size:.62rem;color:var(--dim2)}
|
|
|
|
/* TAG */
|
|
.tg{display:inline-block;padding:2px 8px;border-radius:10px;font-size:.58rem;font-weight:600;font-family:var(--hf);letter-spacing:.3px}
|
|
.tg-g{background:rgba(0,255,136,.1);color:var(--g);border:1px solid rgba(0,255,136,.15)}
|
|
.tg-r{background:rgba(255,71,87,.1);color:var(--r);border:1px solid rgba(255,71,87,.15)}
|
|
.tg-o{background:rgba(255,159,67,.08);color:var(--o);border:1px solid rgba(255,159,67,.12)}
|
|
.tg-b{background:rgba(96,165,250,.1);color:var(--bl);border:1px solid rgba(96,165,250,.15)}
|
|
.tg-p{background:rgba(167,139,250,.1);color:var(--p);border:1px solid rgba(167,139,250,.15)}
|
|
.tg-c{background:rgba(0,229,255,.06);color:var(--acc);border:1px solid rgba(0,229,255,.1)}
|
|
.tg-y{background:rgba(251,191,36,.08);color:var(--y);border:1px solid rgba(251,191,36,.12)}
|
|
.tg-d{background:rgba(45,55,72,.4);color:var(--dim);border:1px solid rgba(45,55,72,.6)}
|
|
|
|
/* TABLE */
|
|
table{width:100%;border-collapse:collapse;font-size:.76rem}
|
|
th{text-align:left;padding:8px 10px;font-size:.7rem;font-weight:600;letter-spacing:.03em;color:var(--dim2);border-bottom:1px solid var(--border)}
|
|
td{padding:6px 10px;border-bottom:1px solid rgba(0,229,255,.03)}
|
|
tr:hover td{background:rgba(0,229,255,.02)}
|
|
|
|
/* BAR */
|
|
.bar{height:3px;border-radius:2px;background:var(--dim2);overflow:hidden}.bar-f{height:100%;border-radius:2px;transition:width .8s ease}
|
|
|
|
/* SCORE RING */
|
|
.ring{position:relative;margin:0 auto}.ring svg{display:block}
|
|
.ring svg{transform:rotate(-90deg)}
|
|
.ring .rv{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center}
|
|
.ring-n{font-family:var(--hf);font-weight:700;line-height:1}
|
|
|
|
/* WM PANEL */
|
|
.wm{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:16px 0;align-items:start}
|
|
.wm-hd{font-family:var(--hf);font-size:.6rem;color:var(--acc);text-transform:uppercase;letter-spacing:2px;margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
|
.wm-hd::before{content:'';width:3px;height:12px;background:var(--acc);border-radius:2px}
|
|
.wm-row{display:flex;align-items:center;gap:8px;padding:7px 0;border-bottom:1px solid rgba(0,229,255,.04);font-size:.78rem}
|
|
.wm-row:last-child{border:none}
|
|
.wm-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;animation:pulse 3s infinite}
|
|
.wm-label{flex:1;color:var(--t);font-weight:400}
|
|
.wm-ts{font-family:var(--hf);font-size:.62rem;color:var(--dim)}
|
|
.wm-gap{padding:8px 0;border-bottom:1px solid rgba(0,229,255,.04)}
|
|
.wm-gap:last-child{border:none}
|
|
.wm-gap-t{font-weight:600;font-size:.78rem;color:var(--t);margin-bottom:3px}
|
|
.wm-gap-d{font-size:.68rem;color:var(--dim);line-height:1.4}
|
|
.wm-tl{position:relative;padding-left:18px}
|
|
.wm-tl-i{position:relative;padding:5px 0 5px 12px;border-left:1px solid var(--dim2);font-size:.72rem}
|
|
.wm-tl-i::before{content:'';position:absolute;left:-4px;top:9px;width:7px;height:7px;border-radius:50%;background:var(--acc);box-shadow:0 0 8px rgba(0,229,255,.4)}
|
|
.wm-tl-t{color:var(--t);font-weight:500}.wm-tl-d{font-family:var(--hf);font-size:.58rem;color:var(--dim);margin-top:1px}
|
|
.wm-btn{width:100%;padding:10px;background:linear-gradient(135deg,rgba(0,229,255,.15),rgba(0,229,255,.05));border:1px solid rgba(0,229,255,.2);border-radius:10px;color:var(--acc);font-family:var(--hf);font-size:.72rem;font-weight:700;cursor:pointer;transition:all .25s;letter-spacing:.5px;text-transform:uppercase}
|
|
.wm-btn:hover{background:linear-gradient(135deg,rgba(0,229,255,.25),rgba(0,229,255,.1));box-shadow:var(--glow);transform:translateY(-1px)}
|
|
.wm-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
|
|
|
|
/* BPMN */
|
|
.bpmn-card{border-left:3px solid var(--g);margin-bottom:8px}
|
|
.bpmn-card.standby{border-left-color:var(--o)}
|
|
.bpmn-hd{display:flex;justify-content:space-between;align-items:center;cursor:pointer;padding:4px 0}
|
|
.bpmn-hd:hover .bpmn-nm{color:var(--acc)}
|
|
.bpmn-nm{font-weight:700;color:var(--t);font-size:.88rem;transition:color .15s}
|
|
.bpmn-flow{display:flex;flex-wrap:wrap;gap:3px;align-items:center;margin-top:8px}
|
|
.bpmn-step{display:flex;align-items:center;gap:4px;padding:5px 10px;border-radius:8px;background:rgba(0,229,255,.03);border:1px solid var(--border);font-size:.68rem;cursor:pointer;transition:all .2s}
|
|
.bpmn-step:hover{border-color:var(--acc);background:rgba(0,229,255,.06);box-shadow:var(--glow)}
|
|
.bpmn-arr{color:var(--dim);font-size:.6rem}
|
|
|
|
/* SOA */
|
|
.soa{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:.72rem;cursor:pointer;transition:all .2s}
|
|
.soa:hover{border-color:var(--acc);box-shadow:var(--glow)}
|
|
|
|
/* PIPE */
|
|
.pipe{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;border:1px solid var(--border);background:var(--card);margin-bottom:5px;cursor:pointer;transition:all .2s}
|
|
.pipe:hover{border-color:var(--acc);box-shadow:var(--glow)}
|
|
.pipe-dot{width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:var(--glow-g);animation:pulse 3s infinite}
|
|
.pipe-nm{font-weight:600;font-size:.8rem;color:var(--t);min-width:140px}
|
|
.pipe-desc{font-size:.68rem;color:var(--dim);flex:1}
|
|
.pipe-freq{font-family:var(--hf);font-size:.62rem;color:var(--acc);background:rgba(0,229,255,.06);padding:2px 8px;border-radius:6px;border:1px solid rgba(0,229,255,.1)}
|
|
|
|
/* RECO */
|
|
.reco{display:flex;gap:10px;padding:10px 12px;border-radius:10px;border:1px solid var(--border);background:var(--card);margin-bottom:6px;cursor:pointer;transition:all .2s}
|
|
.reco:hover{border-color:var(--border-h);box-shadow:var(--glow)}
|
|
.reco-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;margin-top:5px}
|
|
.reco-t{font-weight:600;font-size:.8rem;color:var(--t)}.reco-d{font-size:.68rem;color:var(--dim);margin-top:2px;line-height:1.4}
|
|
|
|
/* MODAL */
|
|
.modal-bg{position:fixed;inset:0;background:rgba(3,5,8,.9);backdrop-filter:blur(8px);z-index:200;display:none;align-items:center;justify-content:center}
|
|
.modal-bg.open{display:flex}
|
|
.modal{background:var(--card-h);backdrop-filter:blur(16px);border:1px solid var(--border-h);border-radius:16px;padding:24px;max-width:650px;width:92%;max-height:80vh;overflow-y:auto;animation:fadeUp .3s ease}
|
|
.modal-x{float:right;background:none;border:none;color:var(--dim);font-size:1.2rem;cursor:pointer;padding:4px 8px;border-radius:6px;transition:all .15s}
|
|
.modal-x:hover{color:var(--r);background:rgba(255,71,87,.1)}
|
|
.modal h2{font-family:var(--hf);font-size:.9rem;color:var(--acc);margin-bottom:12px}
|
|
|
|
/* AGENT */
|
|
.agt{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;border:1px solid var(--border);background:var(--card);cursor:pointer;transition:all .2s}
|
|
.agt:hover{border-color:var(--acc);box-shadow:var(--glow)}
|
|
.agt-icon{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-family:var(--hf);font-size:.68rem;font-weight:700;flex-shrink:0}
|
|
|
|
.decision{padding:8px 12px;border-left:3px solid var(--p);background:var(--card);margin-bottom:5px;border-radius:0 8px 8px 0;font-size:.72rem;line-height:1.5}
|
|
.commit{display:flex;gap:8px;padding:5px 0;border-bottom:1px solid rgba(0,229,255,.03);font-size:.72rem;align-items:center}
|
|
.commit-h{font-family:var(--hf);color:var(--acc);font-size:.62rem;flex-shrink:0}
|
|
.commit-m{color:var(--t);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
|
|
section{margin:14px 0}section>h2{font-family:var(--bf);font-size:.82rem;font-weight:600;color:var(--t);letter-spacing:-.02em;margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid var(--border)}
|
|
h2 .cnt{font-size:.65rem;color:var(--acc);margin-left:6px;font-weight:400;letter-spacing:0}
|
|
|
|
/* PREMIUM OVERRIDES */
|
|
.kpi{position:relative;overflow:hidden;transition:all .3s}
|
|
.kpi::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 10%,rgba(255,255,255,.06) 50%,transparent 90%);pointer-events:none}
|
|
.kpi:hover{transform:translateY(-2px);box-shadow:0 4px 24px rgba(0,0,0,.3),var(--glow)}
|
|
.kpi-val{font-size:2rem!important;font-variant-numeric:tabular-nums lining-nums;letter-spacing:-.04em;color:var(--t)!important}
|
|
.kpi-label{font-size:.78rem!important;font-weight:500;margin-top:6px!important;color:var(--t)!important}
|
|
.kpi-sub{margin-top:4px!important}
|
|
|
|
/* Premium cards */
|
|
.cd{position:relative;overflow:hidden}
|
|
.cd::after{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent 20%,rgba(255,255,255,.04) 50%,transparent 80%);pointer-events:none}
|
|
|
|
/* WM section headers */
|
|
.wm-hd{font-size:.65rem!important;padding-bottom:8px;border-bottom:1px solid rgba(0,229,255,.06);margin-bottom:12px!important}
|
|
.wm-hd::before{width:3px;height:14px;border-radius:2px;box-shadow:0 0 8px var(--acc)}
|
|
|
|
/* Premium rows */
|
|
.wm-row{padding:8px 4px!important;transition:background .15s;border-radius:6px;margin:0 -4px}
|
|
.wm-row:hover{background:rgba(0,229,255,.03)}
|
|
.wm-dot{width:8px!important;height:8px!important;box-shadow:0 0 6px currentColor}
|
|
|
|
/* Gap items */
|
|
.wm-gap{padding:10px 0!important;transition:background .15s;border-radius:6px}
|
|
.wm-gap:hover{background:rgba(0,229,255,.02)}
|
|
.wm-gap-t{font-size:.82rem!important;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
.wm-gap-d{font-size:.7rem!important;margin-top:4px!important;padding-left:0}
|
|
|
|
/* Timeline premium */
|
|
.wm-tl-i{padding:8px 0 8px 16px!important}
|
|
.wm-tl-i::before{width:8px!important;height:8px!important;box-shadow:0 0 12px rgba(0,229,255,.5)!important}
|
|
.wm-tl-t{font-size:.76rem!important}
|
|
.wm-tl-d{font-size:.6rem!important;margin-top:2px!important}
|
|
|
|
/* Trigger button premium */
|
|
.wm-btn{padding:12px!important;font-size:.76rem!important;border-radius:10px!important;transition:all .3s!important}
|
|
.wm-btn:hover{box-shadow:0 4px 24px rgba(0,229,255,.2)!important;transform:translateY(-2px)!important}
|
|
|
|
/* Overview 3-col cards */
|
|
.ov-top{display:grid;grid-template-columns:280px 1fr 240px;gap:14px;margin:14px 0;align-items:stretch}
|
|
.ov-score{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:20px}
|
|
.ov-cx{padding:16px}
|
|
.ov-sys{padding:16px}
|
|
.ov-sys-row{display:flex;justify-content:space-between;align-items:center;padding:6px 4px;border-bottom:1px solid rgba(0,229,255,.04);transition:background .15s;border-radius:4px;margin:0 -4px}
|
|
.ov-sys-row:hover{background:rgba(0,229,255,.03)}
|
|
.ov-sys-row:last-child{border:none}
|
|
.ov-sys-label{font-size:.8rem;color:var(--t)}
|
|
.ov-sys-val{font-family:var(--hf);font-weight:700;font-size:.9rem}
|
|
|
|
/* CORTEX mini cards */
|
|
.cx-card{background:var(--s2);border:1px solid var(--border);border-radius:var(--radius);padding:16px 10px;text-align:center;transition:all .25s}
|
|
.cx-card:hover{border-color:var(--border-h);box-shadow:0 2px 16px rgba(0,0,0,.3);transform:translateY(-1px)}
|
|
.cx-val{font-family:var(--hf);font-size:1.5rem;font-weight:700;line-height:1;margin-bottom:6px;font-variant-numeric:tabular-nums}
|
|
.cx-label{font-size:.58rem;color:var(--dim);text-transform:uppercase;letter-spacing:1px}
|
|
|
|
footer{text-align:center;padding:20px;color:var(--dim);font-size:.58rem;font-family:var(--hf);letter-spacing:1px;border-top:1px solid var(--border);margin-top:24px}
|
|
#ld{position:fixed;inset:0;background:var(--bg);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:999;gap:12px}
|
|
#ld .spin{width:32px;height:32px;border:2px solid var(--dim2);border-top-color:var(--acc);border-radius:50%;animation:spin .8s linear infinite}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="ld"><div class="spin"></div><div style="font-family:var(--hf);font-size:.72rem;color:var(--acc);letter-spacing:2px">SCANNING INFRASTRUCTURE</div></div>
|
|
<div class="wrap" id="app" style="display:none">
|
|
|
|
<div class="hdr fade"><div class="hdr-left"><div class="logo">WEVAL <b>ARCHITECTURE</b></div><div class="hdr-badge">live</div></div><div class="hdr-right"><div class="hdr-ts"><span class="live-dot"></span><span id="ts">—</span></div><div class="hdr-ts" id="st"></div></div></div>
|
|
|
|
<div class="search-wrap fade d1"><span class="search-icon">⌕</span><input class="search-input" id="gs" placeholder="Search services, APIs, processes, agents, domains..." oninput="gSearch(this.value)" onfocus="this.parentNode.querySelector('.search-results').classList.add('open')" onblur="setTimeout(()=>document.querySelector('.search-results').classList.remove('open'),200)"><div class="search-results" id="gsr"></div></div>
|
|
|
|
<div class="alerts fade d2" id="alerts"></div>
|
|
<div class="g g5 fade d3" id="kpi"></div>
|
|
<div class="tabs fade d4" id="tabs"></div>
|
|
<div id="pn"></div>
|
|
|
|
<footer>WEVAL CONSULTING · ARCHITECTURE RÉFÉRENTIEL · AUTONOMOUS ENGINE · WEVIA MASTER</footer>
|
|
</div>
|
|
|
|
<div class="modal-bg" id="modal" onclick="if(event.target===this)xModal()"><div class="modal"><button class="modal-x" onclick="xModal()">✕</button><div id="mc"></div></div></div>
|
|
|
|
<script>
|
|
let D=null,T=null,ct='overview',cf='all',sq='';
|
|
const TB=[{id:'overview',l:'Overview'},{id:'reco',l:'Health'},{id:'cortex',l:'CORTEX'},{id:'pipes',l:'Pipelines'},{id:'apps',l:'Apps'},{id:'infra',l:'Infra'},{id:'ai',l:'AI Stack'},{id:'data',l:'Data'},{id:'bpmn',l:'BPMN'},{id:'soa',l:'SOA'},{id:'topo',l:'Topology'},{id:'log',l:'Changelog'}];
|
|
const tg=(t,c)=>`<span class="tg tg-${c}">${t}</span>`;
|
|
const kp=(v,l,s,c)=>`<div class="cd click kpi fade" onclick="drill('${l}')"><div class="kpi-val" style="color:var(--${c})">${v}</div><div class="kpi-label">${l}</div><div class="kpi-sub">${s}</div></div>`;
|
|
function oModal(h){document.getElementById('mc').innerHTML=h;document.getElementById('modal').classList.add('open')}
|
|
function xModal(){document.getElementById('modal').classList.remove('open')}
|
|
function drill(l){let h=`<h2>${l}</h2>`;
|
|
if(l==='Docker'){h+='<table><thead><tr><th>Container</th><th>Status</th></tr></thead><tbody>';D.docker.forEach(c=>{const u=c.status.includes('Up');h+=`<tr><td style="font-weight:600">${c.name}</td><td>${tg(u?'UP':'DOWN',u?'g':'r')}</td></tr>`});h+='</tbody></table>'}
|
|
else if(l==='APIs'){h+=`<p style="margin:8px 0">S204: ${D.screens.s204_api_php} PHP endpoints</p><p>S95 Arsenal: ${D.screens.s95_arsenal_api} APIs</p>`}
|
|
else if(l.includes('cran')){h+=`<p style="margin:8px 0">S204 HTML: ${D.screens.s204_html}</p><p>S95 Arsenal HTML: ${D.screens.s95_arsenal_html}</p>`}
|
|
else{const r=D.recommendations||{};(r.recommendations||[]).forEach(rc=>{h+=recoH(rc)})}
|
|
oModal(h)}
|
|
function gSearch(q){if(!q||q.length<2){document.getElementById('gsr').innerHTML='';return}const ql=q.toLowerCase();let r=[];
|
|
D.applications.forEach(a=>{if(JSON.stringify(a).toLowerCase().includes(ql))r.push({l:a.name,t:'App',tab:'apps'})});
|
|
D.docker.forEach(c=>{if(c.name.toLowerCase().includes(ql))r.push({l:c.name,t:'Docker',tab:'infra'})});
|
|
if(T?.bpmn_processes)T.bpmn_processes.forEach(p=>{if(JSON.stringify(p).toLowerCase().includes(ql))r.push({l:p.name,t:'BPMN',tab:'bpmn'})});
|
|
if(T?.soa_services)T.soa_services.forEach(s=>{if(s.name.toLowerCase().includes(ql))r.push({l:s.name,t:'SOA',tab:'soa'})});
|
|
D.ai_providers.forEach(p=>{if(JSON.stringify(p).toLowerCase().includes(ql))r.push({l:p.name,t:'AI',tab:'ai'})});
|
|
document.getElementById('gsr').innerHTML=r.slice(0,8).map(i=>`<div class="sr-item" onclick="sw('${i.tab}');document.querySelector('.search-results').classList.remove('open')"><span>${i.l}</span>${tg(i.t,'c')}</div>`).join('')||'<div class="sr-item" style="color:var(--dim)">No results</div>'}
|
|
async function load(){try{const[r1,r2]=await Promise.all([fetch('/api/architecture-index.json?t='+Date.now()),fetch('/api/architecture-topology.json?t='+Date.now())]);D=await r1.json();T=await r2.json();render();document.getElementById('ld').style.display='none';document.getElementById('app').style.display='block'}catch(e){document.getElementById('ld').innerHTML='<div style="color:var(--r)">'+e.message+'</div>'}}
|
|
function sw(id){ct=id;document.querySelectorAll('.tab').forEach(t=>t.classList.toggle('on',t.dataset.id===id));document.querySelectorAll('.pn').forEach(p=>p.classList.toggle('on',p.id==='p-'+id))}
|
|
function render(){
|
|
document.getElementById('ts').textContent=D.generated;document.getElementById('st').textContent=D.scan_time_ms+'ms';
|
|
const tv=D.qdrant.reduce((s,q)=>s+q.vectors,0),rec=D.recommendations||{},recs=rec.recommendations||[];
|
|
const crits=recs.filter(r=>r.severity==='critical'),warns=recs.filter(r=>r.severity==='warning');
|
|
let al='';crits.forEach(r=>{al+=`<div class="alert alert-crit" onclick="sw('reco')">⚠ ${r.title}</div>`});
|
|
warns.forEach(r=>{al+=`<div class="alert alert-warn" onclick="sw('reco')">${r.title}</div>`});
|
|
al+=`<div class="alert alert-ok">L99: ${D.l99?.auth?.pass||0}/${(D.l99?.auth?.pass||0)+(D.l99?.auth?.fail||0)} pass</div>`;
|
|
al+=`<div class="alert alert-info">BPMN: ${T?.bpmn_processes?.length||0} proc</div>`;
|
|
al+=`<div class="alert alert-info">SOA: ${T?.soa_stats?.total_services||0} svc</div>`;
|
|
const ux=D.ux_agent||{};
|
|
al+=`<div class="alert ${ux.fail?'alert-crit':'alert-ok'}" onclick="sw('overview')">UX: ${ux.pass||0}/${ux.total||0} pass</div>`;
|
|
document.getElementById('alerts').innerHTML=al;
|
|
document.getElementById('kpi').innerHTML=[kp(D.docker.length,'Docker','containers','g'),kp(D.screens.s204_api_php+D.screens.s95_arsenal_api,'APIs','endpoints','bl'),kp(D.screens.s204_html+D.screens.s95_arsenal_html,'Écrans','pages','p'),kp(tv.toLocaleString(),'Vecteurs','Qdrant','o'),kp(rec.score+'/100','Health','score','acc')].join('');
|
|
document.getElementById('tabs').innerHTML=TB.map(t=>`<button class="tab${t.id===ct?' on':''}" data-id="${t.id}" onclick="sw('${t.id}')">${t.l}</button>`).join('');
|
|
document.getElementById('pn').innerHTML=TB.map(t=>`<div class="pn${t.id===ct?' on':''}" id="p-${t.id}">${rp(t.id)}</div>`).join('')}
|
|
function rp(id){switch(id){case'overview':return rpO();case'reco':return rpR();case'cortex':return rpC();case'pipes':return rpP();case'apps':return rpA();case'infra':return rpI();case'ai':return rpAI();case'data':return rpD();case'bpmn':return rpB();case'soa':return rpS();case'topo':return rpT();case'log':return rpL();default:return''}}
|
|
function recoH(r){const sc={critical:'r',warning:'o',info:'b',opportunity:'g'}[r.severity]||'d';return`<div class="reco" onclick="oModal('<h2>${esc(r.title)}</h2><p>${esc(r.detail)}</p><p>${r.category} · ${r.severity} · ${r.action}</p>')"><div class="reco-dot" style="background:var(--${sc})"></div><div style="flex:1"><div class="reco-t">${r.title}</div><div class="reco-d">${r.detail}</div><div style="margin-top:4px">${tg(r.category,'p')} ${tg(r.severity,sc)}${r.auto_fixed?' <span style="color:var(--g);font-size:.62rem;font-weight:600">✓ FIXED</span>':''}</div></div></div>`}
|
|
function esc(s){return(s||'').replace(/'/g,"\\'")}
|
|
|
|
// === OVERVIEW (WEVIA Master Panel wired by Opus) ===
|
|
function rpO(){
|
|
const rec=D.recommendations||{},cx=D.cortex||{},opt=D.optimizations||{},bpmn=T?.bpmn_processes||[];
|
|
const score=rec.score||0,cls=score>=80?'g':score>=50?'o':'r',circ=2*Math.PI*42,off=circ*(1-score/100);
|
|
const totalAuto=bpmn.reduce((s,p)=>s+p.steps.filter(s=>s.status==='automated').length,0);
|
|
const totalSteps=bpmn.reduce((s,p)=>s+p.steps.length,0)||1;const autoPct=Math.round(totalAuto/totalSteps*100);
|
|
const acCirc=2*Math.PI*45,acOff=acCirc*(1-autoPct/100),acCol=autoPct>=70?'var(--g)':autoPct>=40?'var(--o)':'var(--r)';
|
|
const recs=(rec.recommendations||[]).slice(0,4);const aiOpts=T?.ai_optimizations||[];
|
|
const decs=opt.architecture_decisions||[];const commits=opt.recent_commits||[];
|
|
|
|
// === TOP 3 COLUMNS ===
|
|
let h=`<div class="ov-top">
|
|
<div class="cd ov-score"><h3>Health Score</h3>
|
|
<div class="ring" style="width:110px;height:110px;margin:8px auto"><svg width="110" height="110" viewBox="0 0 110 110"><circle cx="55" cy="55" r="46" fill="none" stroke="var(--dim2)" stroke-width="5"/><circle cx="55" cy="55" r="46" fill="none" stroke="var(--${cls})" stroke-width="5" stroke-linecap="round" stroke-dasharray="${2*Math.PI*46}" stroke-dashoffset="${2*Math.PI*46*(1-score/100)}" style="transition:stroke-dashoffset 1.2s ease"/></svg><div class="rv"><div class="ring-n" style="font-size:2.2rem;color:var(--${cls});">${score}</div><div style="font-size:.6rem;color:var(--dim);margin-top:2px">/100</div></div></div>
|
|
<div style="display:flex;gap:14px;margin-top:10px">${['critical','warning','info','opportunity'].map(s=>{const n=rec[s]||0;const co={critical:'r',warning:'o',info:'bl',opportunity:'g'}[s];return`<div style="text-align:center"><div style="font-family:var(--hf);font-size:1.1rem;font-weight:700;color:var(--${co});text-shadow:0 0 12px var(--${co})">${n}</div><div style="font-size:.52rem;color:var(--dim);text-transform:uppercase;letter-spacing:.5px;margin-top:2px">${s.substr(0,4)}</div></div>`}).join('')}</div>
|
|
</div>
|
|
|
|
<div class="cd ov-cx"><h3>CORTEX Engine</h3>
|
|
<div class="g g2" style="gap:10px;margin-top:4px">${[['FAST.PHP',cx.fast_lines,'bl'],['ROUTER',cx.router_functions,'p'],['PROVIDERS',D.ai_providers.length,'g'],['OLLAMA',D.ollama.length,'o']].map(([l,v,c])=>`<div class="cx-card"><div class="cx-val" style="color:var(--${c})">${v}</div><div class="cx-label">${l}</div></div>`).join('')}</div>
|
|
</div>
|
|
|
|
<div class="cd ov-sys"><h3>Systèmes</h3>
|
|
${[['Pipelines',(opt.pipelines||[]).length,'g'],['Agents',(opt.agents_deployed||[]).length,'p'],['Crons',D.crons?.s204_total||0,'bl'],['Docker',D.docker.length,'acc'],['Domains',D.domains.length,'y'],['SOA',T?.soa_stats?.total_services||0,'o'],['UX Tests',(D.ux_agent?.pass||0)+'/'+(D.ux_agent?.total||0),'g']].map(([l,v,c])=>`<div class="ov-sys-row"><span class="ov-sys-label">${l}</span><span class="ov-sys-val" style="color:var(--${c})">${v}</span></div>`).join('')}
|
|
</div></div>`;
|
|
|
|
// === WEVIA MASTER PANEL ===
|
|
h+=`<div class="wm">
|
|
<div>
|
|
<div class="cd"><div class="wm-hd">WEVIA Master — Actions</div>
|
|
${[{i:'S',c:'g',t:'Scan ('+D.scan_time_ms+'ms)',tm:D.generated?.substr(11,5)},{i:'T',c:'acc',t:'Topology ('+(T?.stats?.nodes||0)+' nodes)',tm:T?.generated?.substr(11,5)},{i:'R',c:'p',t:'Score '+rec.score+'/100',tm:D.generated?.substr(11,5)},{i:'F',c:'bl',t:'Auto-Fix ('+(rec.auto_fixed||0)+')',tm:D.generated?.substr(11,5)},{i:'B',c:'y',t:'BPMN ('+bpmn.length+' proc)',tm:T?.generated?.substr(11,5)}].map(a=>`<div class="wm-row"><div class="wm-dot" style="background:var(--${a.c})"></div><span class="wm-label">${a.t}</span><span class="wm-ts">${a.tm||'--'}</span></div>`).join('')}
|
|
</div>
|
|
<div class="cd" style="margin-top:14px"><div class="wm-hd">Gap Analysis</div>
|
|
${[...recs,...aiOpts.map(o=>({title:o.action,detail:o.detail,severity:o.priority==='high'?'critical':'warning',category:o.category}))].slice(0,5).map(g=>{const sc={critical:'r',warning:'o',opportunity:'g'}[g.severity]||'d';return`<div class="wm-gap"><div class="wm-gap-t">${g.title} ${tg(g.severity||'?',sc)} ${tg(g.category||'','p')}</div><div class="wm-gap-d">${g.detail||''}</div></div>`}).join('')||'<div style="color:var(--dim);font-size:.72rem;padding:8px">No gaps detected</div>'}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="cd" style="text-align:center"><div class="wm-hd" style="text-align:left">Automation Coverage</div>
|
|
<div class="ring" style="width:140px;height:140px;margin:12px auto"><svg width="140" height="140" viewBox="0 0 140 140"><defs><linearGradient id="acg" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="var(--g)"/><stop offset="100%" stop-color="var(--acc)"/></linearGradient></defs><circle cx="70" cy="70" r="56" fill="none" stroke="var(--dim2)" stroke-width="7"/><circle cx="70" cy="70" r="56" fill="none" stroke="url(#acg)" stroke-width="7" stroke-linecap="round" stroke-dasharray="${2*Math.PI*56}" stroke-dashoffset="${2*Math.PI*56*(1-autoPct/100)}" transform="rotate(-90 70 70)" style="transition:stroke-dashoffset 1.2s ease"/></svg><div class="rv"><div class="ring-n" style="font-size:2.4rem;color:${acCol};text-shadow:0 0 24px ${acCol}">${autoPct}%</div></div></div>
|
|
<div style="font-size:.72rem;color:var(--dim);margin-top:8px">${totalAuto}/${totalSteps} steps · ${bpmn.length} processes</div>
|
|
</div>
|
|
|
|
<div class="cd" style="margin-top:14px"><div class="wm-hd">Timeline</div>
|
|
<div class="wm-tl">${[...decs.map(d=>({t:d.fact?.substr(0,60),d:d.created_at?.substr(0,16)})),...commits.map(c=>({t:c.msg?.substr(0,60),d:c.date?.substr(0,16)}))].slice(0,5).map(t=>`<div class="wm-tl-i"><div class="wm-tl-t">${t.t||''}</div><div class="wm-tl-d">${t.d||''}</div></div>`).join('')||'<div style="color:var(--dim);font-size:.72rem">—</div>'}</div>
|
|
</div>
|
|
|
|
<div class="cd" style="margin-top:14px"><div class="wm-hd">Control</div>
|
|
<button class="wm-btn" id="wmb" onclick="trgScan()">▶ TRIGGER SCAN</button>
|
|
<div style="font-size:.58rem;color:var(--dim);text-align:center;margin-top:6px;font-family:var(--hf)" id="wms">Last: ${D.generated}</div>
|
|
</div>
|
|
</div></div>`;
|
|
|
|
return h}
|
|
async function trgScan(){const b=document.getElementById('wmb'),s=document.getElementById('wms');b.disabled=true;b.textContent='SCANNING...';try{await fetch('/api/architecture-scanner.php');s.textContent='Done!';setTimeout(load,1000)}catch(e){s.textContent='Error'}setTimeout(()=>{b.disabled=false;b.textContent='▶ TRIGGER SCAN'},3000)}
|
|
|
|
function rpR(){const rec=D.recommendations||{},recs=rec.recommendations||[],decs=D.optimizations?.architecture_decisions||[];let h=`<div class="g g3"><div class="cd kpi"><div class="kpi-val" style="color:var(--${rec.score>=80?'g':'o'})">${rec.score}/100</div><div class="kpi-label">Score</div></div><div class="cd"><h3>Breakdown</h3><div class="g g2" style="gap:6px">${['critical','warning','info','opportunity'].map(s=>`<div style="text-align:center;padding:6px;background:rgba(0,229,255,.02);border-radius:6px"><div style="font-size:1rem;font-weight:700;color:var(--${{critical:'r',warning:'o',info:'b',opportunity:'g'}[s]})">${rec[s]||0}</div><div class="kpi-sub">${s}</div></div>`).join('')}</div></div><div class="cd"><h3>Auto-Fixes</h3>${(rec.fixes_log||[]).map(f=>`<div style="padding:3px 0;font-size:.72rem"><span style="color:var(--g)">✓</span> ${f.title}</div>`).join('')||'<div class="dim" style="font-size:.72rem">None this cycle</div>'}</div></div>`;
|
|
h+='<section><h2>Recommendations</h2>';recs.forEach(r=>{h+=recoH(r)});h+='</section>';
|
|
if(decs.length){h+='<section><h2>Architecture Decisions</h2>';decs.forEach(d=>{h+=`<div class="decision">${d.fact}<div style="font-size:.6rem;color:var(--dim);margin-top:2px">${d.created_at}</div></div>`});h+='</section>'}return h}
|
|
|
|
function rpC(){const cx=D.cortex||{};let h=`<div class="g g4">${kp(cx.fast_lines,'CORTEX','lines','bl')}${kp(cx.router_lines,'Router','lines','p')}${kp(cx.router_functions,'Functions','routing','g')}${kp(D.ai_providers.length,'Providers','0€','o')}</div>`;
|
|
h+='<section><h2>Smart Router</h2><div class="g g4">';[['T0: Local','weval-brain-v3','~200ms','p'],['T1: Free','Cerebras · Groq · SambaNova','~1.3s','g'],['T2: Fallback','Mistral · Cohere · Gemini','~2s','b'],['T3: Emergency','HuggingFace · Replicate','~3s','d']].forEach(([n,d,l,c])=>{h+=`<div class="cd" style="border-top:2px solid var(--${c})"><h3>${n}</h3><div style="font-size:.78rem;color:var(--t);margin:3px 0">${d}</div><div class="kpi-sub">${l}</div></div>`});
|
|
h+='</div></section><section><h2>Providers</h2><table><thead><tr><th>Provider</th><th>Model</th><th>Tier</th></tr></thead><tbody>';
|
|
D.ai_providers.forEach(p=>{h+=`<tr><td style="font-weight:600">${p.name}</td><td>${p.model}</td><td>${tg(p.tier,p.tier==='T0'?'p':p.tier==='T1'?'g':p.tier==='T2'?'b':'d')}</td></tr>`});h+='</tbody></table></section>';return h}
|
|
|
|
function rpP(){const opt=D.optimizations||{},colors=['#60a5fa','#00ff88','#ff4757','#a78bfa','#ff9f43','#fbbf24','#00e5ff','#39d2c0'];let h='<section><h2>Pipelines</h2>';(opt.pipelines||[]).forEach(p=>{h+=`<div class="pipe"><div class="pipe-dot"></div><div class="pipe-nm">${p.name}</div><div class="pipe-desc">${p.desc}</div>${p.freq?`<div class="pipe-freq">${p.freq}</div>`:''}</div>`});
|
|
h+='</section><section><h2>Agents</h2><div class="g g2">';(opt.agents_deployed||[]).forEach((a,i)=>{const bg=colors[i%8];h+=`<div class="agt"><div class="agt-icon" style="background:${bg}18;color:${bg}">A${i+1}</div><div><div style="font-weight:600;font-size:.8rem;color:var(--t)">${a.name}</div><div style="font-size:.65rem;color:var(--dim)">${a.role}</div></div>${tg(a.status,'g')}</div>`});
|
|
h+='</div></section><section><h2>Crons '+tg(D.crons?.s204_total||0,'c')+'</h2><table><thead><tr><th>Name</th><th>Freq</th><th>Script</th></tr></thead><tbody>';(D.crons?.key_crons||[]).forEach(c=>{h+=`<tr><td style="font-weight:600">${c.name}</td><td>${tg(c.freq,'c')}</td><td style="font-family:var(--hf);font-size:.62rem">${c.target}</td></tr>`});h+='</tbody></table></section>';return h}
|
|
|
|
function rpA(){let h=`<div style="display:flex;gap:6px;margin-bottom:10px;flex-wrap:wrap"><input style="padding:6px 12px;border-radius:8px;border:1px solid var(--border);background:var(--card);color:var(--t);font-size:.78rem;width:200px;outline:none;font-family:var(--bf)" placeholder="Filter..." oninput="sq=this.value;document.getElementById('p-apps').innerHTML=rpA()">`;['all','authentik','internal','public'].forEach(f=>{h+=`<button class="tab${cf===f?' on':''}" onclick="cf='${f}';document.getElementById('p-apps').innerHTML=rpA()" style="padding:4px 10px;border-radius:8px">${f}</button>`});
|
|
h+='</div><table><thead><tr><th>App</th><th>Type</th><th>URL</th><th>Server</th><th>Auth</th></tr></thead><tbody>';
|
|
D.applications.filter(a=>{if(cf!=='all'&&a.auth!==cf)return false;if(sq&&!JSON.stringify(a).toLowerCase().includes(sq.toLowerCase()))return false;return true}).forEach(a=>{h+=`<tr style="cursor:pointer" onclick="oModal('<h2>${a.name}</h2><p>Type: ${a.type}</p><p>URL: ${a.url||'-'}</p><p>Port: ${a.port||'-'}</p><p>Server: ${a.server}</p><p>Auth: ${a.auth}</p>')"><td style="font-weight:600">${a.name}</td><td>${tg(a.type,'p')}</td><td style="font-family:var(--hf);font-size:.62rem">${a.url||'-'}${a.port?' :'+a.port:''}</td><td>${tg(a.server,'b')}</td><td>${tg(a.auth,a.auth==='authentik'?'g':a.auth==='public'?'c':'d')}</td></tr>`});
|
|
h+='</tbody></table>';return h}
|
|
|
|
function rpI(){let h='<section><h2>Servers</h2><div class="g g3">';D.servers.forEach(s=>{const dp=s.disk_pct||0,dc=dp>90?'r':dp>80?'o':'g';h+=`<div class="cd click" onclick="oModal('<h2>${s.id}</h2><p>IP: ${s.ip}${s.private?' / '+s.private:''}</p><p>Role: ${s.role}</p><p>Disk: ${dp}%</p><p>Uptime: ${s.uptime||'?'}</p>')"><div style="display:flex;justify-content:space-between"><span style="font-weight:700;color:var(--t)">${s.id}</span>${tg(s.nginx==='active'||s.sentinel?'UP':'?',s.nginx==='active'||s.sentinel?'g':'d')}</div><div style="font-family:var(--hf);font-size:.68rem;color:var(--acc)">${s.ip}</div><div style="display:flex;gap:12px;margin-top:4px"><div><span style="font-family:var(--hf);font-weight:700;color:var(--${dc})">${dp}%</span> <span class="dim" style="font-size:.62rem">disk</span></div>${s.disk_avail?`<div><span style="font-family:var(--hf);font-weight:700">${s.disk_avail}</span> <span class="dim" style="font-size:.62rem">free</span></div>`:''}</div><div class="bar" style="margin-top:6px"><div class="bar-f" style="width:${dp}%;background:var(--${dc})"></div></div></div>`});
|
|
h+='</div></section><section><h2>Docker '+tg(D.docker.length,'g')+'</h2><div class="g g4">';D.docker.forEach(c=>{const u=c.status.includes('Up'),hl=c.status.includes('healthy');h+=`<div class="cd click" style="padding:8px" onclick="oModal('<h2>${c.name}</h2><p>${c.status}</p>')"><div style="display:flex;justify-content:space-between;align-items:center"><span style="font-weight:600;font-size:.74rem;color:${u?'var(--t)':'var(--r)'}">${c.name}</span>${tg(hl?'healthy':u?'up':'down',hl?'g':u?'b':'r')}</div></div>`});
|
|
h+='</div></section><section><h2>Domains '+tg(D.domains.length,'b')+'</h2><table><thead><tr><th>Config</th><th>Domains</th><th>SSL</th><th>Auth</th></tr></thead><tbody>';D.domains.forEach(dm=>{h+=`<tr><td style="font-family:var(--hf);font-size:.62rem">${dm.file}</td><td style="font-size:.68rem">${dm.server_names.join(', ')}</td><td>${tg(dm.ssl?'SSL':'HTTP',dm.ssl?'g':'r')}</td><td>${tg(dm.auth_complete?'OK':dm.authentik?'MISS':'—',dm.auth_complete?'g':dm.authentik?'r':'d')}</td></tr>`});
|
|
h+='</tbody></table></section>';return h}
|
|
|
|
function rpAI(){let h=`<div class="g g4">${kp(D.ai_providers.length,'Providers','0€','g')}${kp(D.ollama.length,'Ollama','models','p')}${kp(D.qdrant.reduce((s,q)=>s+q.vectors,0).toLocaleString(),'Vectors','Qdrant','bl')}${kp('0€','Cost','monthly','acc')}</div><div class="g g2"><div class="cd"><h3>Ollama</h3><table><thead><tr><th>Model</th><th>Family</th><th>Size</th></tr></thead><tbody>`;D.ollama.forEach(m=>{h+=`<tr><td style="font-weight:600">${m.name}</td><td>${m.family}</td><td>${m.size_gb}GB</td></tr>`});h+='</tbody></table></div><div class="cd"><h3>Qdrant</h3><table><thead><tr><th>Collection</th><th>Vectors</th></tr></thead><tbody>';D.qdrant.forEach(q=>{h+=`<tr><td style="font-weight:600">${q.name}</td><td style="font-family:var(--hf)">${q.vectors.toLocaleString()}</td></tr>`});h+='</tbody></table></div></div>';return h}
|
|
|
|
function rpD(){let h=`<div class="g g3">${kp(D.wiki?.total_entries||0,'Wiki','entries','p')}${kp(D.databases?.s204?.length||0,'DBs','PostgreSQL','bl')}${kp(Object.values(D.databases?.key_tables||{}).reduce((s,v)=>s+v,0).toLocaleString(),'Records','key tables','o')}</div><div class="g g2"><div class="cd"><h3>Databases</h3><div style="display:flex;flex-wrap:wrap;gap:5px">${(D.databases?.s204||[]).map(db=>tg(db,'b')).join('')}</div></div><div class="cd"><h3>Key Tables</h3>`;Object.entries(D.databases?.key_tables||{}).forEach(([k,v])=>{h+=`<div style="display:flex;justify-content:space-between;padding:3px 0;border-bottom:1px solid rgba(0,229,255,.03);font-size:.74rem"><span>${k}</span><span style="font-family:var(--hf);color:var(--acc)">${v.toLocaleString()}</span></div>`});h+='</div></div>';return h}
|
|
|
|
function rpB(){if(!T?.bpmn_processes)return'<div style="color:var(--dim);padding:20px">Loading BPMN...</div>';const procs=T.bpmn_processes,ta=procs.reduce((s,p)=>s+p.steps.filter(s=>s.status==='automated').length,0),ts=procs.reduce((s,p)=>s+p.steps.length,0)||1;
|
|
let h=`<div class="g g3">${kp(procs.length,'Processes','BPMN','p')}${kp(Math.round(ta/ts*100)+'%','Automation','rate','g')}${kp(procs.filter(p=>p.status==='active').length+'/'+procs.length,'Active','running','bl')}</div>`;
|
|
procs.forEach((p,pi)=>{const au=p.steps.filter(s=>s.status==='automated').length,pct=Math.round(au/p.steps.length*100);
|
|
h+=`<div class="cd bpmn-card${p.status==='standby'?' standby':''}" style="margin-bottom:8px"><div class="bpmn-hd" onclick="document.getElementById('bx${pi}').classList.toggle('on');this.querySelector('.bpmn-tog').textContent=document.getElementById('bx${pi}').classList.contains('on')?'▲':'▼'"><div><span class="bpmn-nm">${p.id} — ${p.name}</span> ${tg(p.status,p.status==='active'?'g':'o')} ${(p.swimlanes||[]).map(s=>tg(s,'b')).join(' ')}</div><div style="display:flex;align-items:center;gap:6px"><div class="bar" style="width:50px"><div class="bar-f" style="width:${pct}%;background:var(--${pct>=80?'g':pct>=50?'o':'r'})"></div></div><span style="font-family:var(--hf);font-size:.62rem;color:var(--acc)">${pct}%</span><span class="bpmn-tog dim" style="font-size:.6rem">▼</span></div></div>`;
|
|
h+=`<div class="pn${pi===0?' on':''}" id="bx${pi}"><div class="bpmn-flow">`;p.steps.forEach((s,i)=>{const sc={automated:'g',trigger:'acc','semi-auto':'o',manual:'d',standby:'y'}[s.status]||'d';h+=`<div class="bpmn-step" onclick="oModal('<h2>${esc(s.task)}</h2><p>Actor: ${esc(s.actor)}</p><p>Type: ${s.type}</p><p>Status: ${s.status}</p>')"><div class="dot" style="width:6px;height:6px;border-radius:${s.type==='gateway'?'2px':'50%'};background:var(--${sc});box-shadow:0 0 6px var(--${sc})"></div><span style="color:var(--t)">${s.task}</span><span style="font-size:.58rem;color:var(--dim)">${s.actor}</span></div>`;if(i<p.steps.length-1)h+='<span class="bpmn-arr">→</span>'});h+='</div></div></div>'});return h}
|
|
|
|
function rpS(){if(!T?.soa_services)return'<div style="color:var(--dim);padding:20px">Loading SOA...</div>';const svc=T.soa_services,stats=T.soa_stats||{},groups={};svc.forEach(s=>{const g=s.group||'other';if(!groups[g])groups[g]=[];groups[g].push(s)});const gc={auth:'#a78bfa',ai:'#00e5ff',data:'#ff9f43',monitoring:'#39d2c0',crm:'#60a5fa',analytics:'#00ff88',chat:'#ff9f43',service:'#4a5568',api:'#ff4757',automation:'#a78bfa'};
|
|
let h=`<div class="g g3">${kp(stats.total_services||0,'Services','SOA','acc')}${kp(stats.active||0,'Active','UP','g')}${kp(Object.keys(groups).length,'Groups','categories','p')}</div>`;
|
|
Object.entries(groups).sort((a,b)=>b[1].length-a[1].length).forEach(([g,svcs])=>{h+=`<section><h2 style="color:${gc[g]||'var(--dim)'}">${g.toUpperCase()} ${tg(svcs.length,'c')}</h2><div style="display:flex;flex-wrap:wrap;gap:5px">`;svcs.forEach(s=>{const up=['healthy','active','up'].includes(s.status);h+=`<div class="soa" onclick="oModal('<h2>${s.name}</h2><p>Type: ${s.type}</p><p>Server: ${s.server}</p><p>Group: ${s.group}</p><p>Status: ${s.status}</p>')"><div class="soa-dot" style="width:5px;height:5px;border-radius:50%;background:${up?'var(--g)':'var(--r)'};box-shadow:0 0 6px ${up?'var(--g)':'var(--r)'}"></div><span style="font-weight:600;color:var(--t)">${s.name}</span>${tg(s.server,'b')}</div>`});h+='</div></section>'});return h}
|
|
|
|
function rpT(){if(!T)return'<div style="color:var(--dim);padding:20px">Loading...</div>';const nodes=T.nodes||[],edges=T.edges||[],groups={};nodes.forEach(n=>{const g=n.group||'other';if(!groups[g])groups[g]=[];groups[g].push(n)});const gc={server:'#60a5fa',auth:'#a78bfa',ai:'#00e5ff',data:'#ff9f43',nginx:'#fbbf24',monitoring:'#39d2c0',crm:'#60a5fa',analytics:'#00ff88',chat:'#ff9f43',cloud:'#00e5ff',service:'#4a5568',api:'#ff4757',automation:'#a78bfa'};
|
|
let h=`<div class="g g3">${kp(T.stats.nodes,'Nodes','discovered','acc')}${kp(T.stats.edges,'Edges','connections','bl')}${kp(T.stats.scan_ms+'ms','Scan','time','g')}</div>`;
|
|
h+='<div style="display:flex;flex-wrap:wrap;gap:5px;margin:8px 0">';Object.entries(groups).forEach(([g,ns])=>{h+=`<div style="display:flex;align-items:center;gap:3px;padding:2px 8px;background:var(--card);border-radius:8px;border:1px solid var(--border);font-size:.62rem"><div style="width:5px;height:5px;border-radius:50%;background:${gc[g]||'#4a5568'}"></div>${g} (${ns.length})</div>`});h+='</div>';
|
|
Object.entries(groups).sort((a,b)=>b[1].length-a[1].length).forEach(([g,ns])=>{h+=`<div style="margin-bottom:10px"><div style="font-family:var(--hf);font-size:.6rem;color:${gc[g]||'var(--dim)'};text-transform:uppercase;letter-spacing:1px;margin-bottom:4px">${g} (${ns.length})</div><div style="display:flex;flex-wrap:wrap;gap:4px">`;ns.forEach(n=>{const sc=['healthy','up','active'].includes(n.status);h+=`<div class="soa" onclick="oModal('<h2>${n.label}</h2><p>Type: ${n.type}</p><p>Server: ${n.server}</p><p>Status: ${n.status}</p>${n.ip?'<p>IP: '+n.ip+'</p>':''}')"><div class="soa-dot" style="width:5px;height:5px;border-radius:50%;background:${sc?'var(--g)':'var(--r)'}"></div><span style="color:var(--t)">${n.label}</span></div>`});h+='</div></div>'});
|
|
h+=`<section style="margin-top:14px"><h2>Connections ${tg(edges.length,'c')}</h2><table><thead><tr><th>From</th><th>To</th><th>Port</th><th>Type</th></tr></thead><tbody>`;edges.forEach(e=>{h+=`<tr><td style="font-size:.68rem">${e.from.replace(/^(docker_|nginx_|port_|service_)/,'')}</td><td style="font-size:.68rem">${e.to.replace(/^(docker_|nginx_|port_|service_)/,'')}</td><td style="font-family:var(--hf);font-size:.6rem;color:var(--acc)">${e.label||''}</td><td>${tg(e.type,e.type==='proxy'?'b':e.type==='auth'?'p':'d')}</td></tr>`});h+='</tbody></table></section>';return h}
|
|
|
|
function rpL(){const commits=D.optimizations?.recent_commits||[],fixes=D.optimizations?.auto_fixes||[];let h='<div class="g g2"><div class="cd"><h3>Git '+tg(commits.length,'c')+'</h3>';commits.forEach(c=>{h+=`<div class="commit"><span class="commit-h">${c.hash}</span><span class="commit-m">${c.msg}</span></div>`});if(!commits.length)h+='<div class="dim" style="font-size:.72rem">—</div>';h+='</div><div class="cd"><h3>Auto-Fixes '+tg(fixes.length,'g')+'</h3>';fixes.forEach(f=>{h+=`<div class="decision" style="border-color:var(--g)">${f.fact}<div style="font-size:.58rem;color:var(--dim);margin-top:2px">${f.created_at}</div></div>`});if(!fixes.length)h+='<div class="dim" style="font-size:.72rem">—</div>';h+='</div></div>';return h}
|
|
|
|
load();setInterval(load,5*60*1000);
|
|
</script>
|
|
</body>
|
|
</html>
|