819 lines
36 KiB
HTML
819 lines
36 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>WEVAL Enterprise Management — Hub Unifié</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
:root {
|
||
--bg-main: #0a0e1a;
|
||
--bg-card: rgba(30, 41, 59, 0.65);
|
||
--bg-card-hover: rgba(51, 65, 85, 0.85);
|
||
--border: rgba(148, 163, 184, 0.15);
|
||
--border-h: rgba(99, 102, 241, 0.5);
|
||
--text: #e2e8f0;
|
||
--text-dim: #94a3b8;
|
||
--text-muted: #64748b;
|
||
--accent: #6366f1;
|
||
--accent2: #ec4899;
|
||
--success: #10b981;
|
||
--warn: #f59e0b;
|
||
--danger: #dc2626;
|
||
--gradient-1: linear-gradient(135deg, #6366f1 0%, #ec4899 100%);
|
||
--gradient-2: linear-gradient(135deg, #10b981 0%, #06b6d4 100%);
|
||
--gradient-3: linear-gradient(135deg, #f59e0b 0%, #dc2626 100%);
|
||
--gradient-4: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 100%);
|
||
}
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', sans-serif;
|
||
background: var(--bg-main);
|
||
color: var(--text);
|
||
min-height: 100vh;
|
||
background-image:
|
||
radial-gradient(at 15% 10%, rgba(99, 102, 241, 0.12) 0px, transparent 50%),
|
||
radial-gradient(at 85% 90%, rgba(236, 72, 153, 0.08) 0px, transparent 50%),
|
||
radial-gradient(at 50% 50%, rgba(16, 185, 129, 0.04) 0px, transparent 70%);
|
||
background-attachment: fixed;
|
||
}
|
||
.container { max-width: 1600px; margin: 0 auto; padding: 24px; }
|
||
|
||
/* HERO */
|
||
.hero {
|
||
background: var(--gradient-1);
|
||
border-radius: 20px;
|
||
padding: 36px 40px;
|
||
margin-bottom: 28px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
box-shadow: 0 20px 60px rgba(99, 102, 241, 0.25);
|
||
}
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -50%; right: -10%;
|
||
width: 400px; height: 400px;
|
||
background: radial-gradient(circle, rgba(255,255,255,0.15), transparent 70%);
|
||
border-radius: 50%;
|
||
}
|
||
.hero h1 {
|
||
font-size: 36px;
|
||
font-weight: 800;
|
||
color: white;
|
||
letter-spacing: -1px;
|
||
margin-bottom: 10px;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.hero .subtitle {
|
||
color: rgba(255,255,255,0.85);
|
||
font-size: 15px;
|
||
margin-bottom: 22px;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.hero-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||
gap: 16px;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.stat-card {
|
||
background: rgba(255,255,255,0.13);
|
||
backdrop-filter: blur(12px);
|
||
border: 1px solid rgba(255,255,255,0.18);
|
||
border-radius: 14px;
|
||
padding: 16px;
|
||
}
|
||
.stat-val { font-size: 26px; font-weight: 800; color: white; }
|
||
.stat-lbl { font-size: 11px; color: rgba(255,255,255,0.8); text-transform: uppercase; letter-spacing: 1px; margin-top: 4px; }
|
||
|
||
/* SEARCH */
|
||
.search-bar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
background: rgba(10, 14, 26, 0.95);
|
||
backdrop-filter: blur(20px);
|
||
margin: 0 -24px 24px;
|
||
padding: 16px 24px;
|
||
border-bottom: 1px solid var(--border);
|
||
}
|
||
.search-bar input {
|
||
width: 100%;
|
||
background: var(--bg-card);
|
||
border: 2px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 14px 20px 14px 52px;
|
||
color: var(--text);
|
||
font-size: 15px;
|
||
outline: none;
|
||
transition: all .2s;
|
||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2'><circle cx='11' cy='11' r='8'/><line x1='21' y1='21' x2='16.65' y2='16.65'/></svg>");
|
||
background-repeat: no-repeat;
|
||
background-position: 18px center;
|
||
}
|
||
.search-bar input:focus { border-color: var(--accent); }
|
||
|
||
/* QUICK ACTIONS */
|
||
.quick-actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||
gap: 12px;
|
||
margin-bottom: 32px;
|
||
}
|
||
.qa-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 16px;
|
||
cursor: pointer;
|
||
transition: all .2s;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
}
|
||
.qa-card:hover { border-color: var(--border-h); background: var(--bg-card-hover); transform: translateY(-2px); }
|
||
.qa-icon { font-size: 24px; }
|
||
.qa-label { font-weight: 600; font-size: 14px; }
|
||
.qa-chat { font-size: 11px; color: var(--text-dim); margin-top: 2px; }
|
||
|
||
/* CATEGORIES TABS */
|
||
.tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
overflow-x: auto;
|
||
margin-bottom: 20px;
|
||
padding-bottom: 8px;
|
||
}
|
||
.tab {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 10px 16px;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
transition: all .15s;
|
||
}
|
||
.tab:hover { border-color: var(--border-h); }
|
||
.tab.active { background: var(--gradient-1); border-color: transparent; color: white; }
|
||
|
||
/* SECTION GRID */
|
||
.section { margin-bottom: 40px; }
|
||
.section-title {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
.section-count {
|
||
font-size: 12px;
|
||
background: rgba(99, 102, 241, 0.2);
|
||
color: #a5b4fc;
|
||
padding: 3px 10px;
|
||
border-radius: 10px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
.page-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 14px 16px;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
text-decoration: none;
|
||
color: var(--text);
|
||
}
|
||
.page-card:hover { border-color: var(--border-h); background: var(--bg-card-hover); transform: translateX(4px); }
|
||
.page-card .name { font-size: 13px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.page-card .arrow { color: var(--text-muted); font-size: 18px; flex-shrink: 0; }
|
||
|
||
/* SERVICES */
|
||
.services {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
gap: 14px;
|
||
margin-bottom: 32px;
|
||
}
|
||
.service-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: 14px;
|
||
padding: 18px;
|
||
transition: all .2s;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.service-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0; left: 0;
|
||
width: 100%; height: 3px;
|
||
background: var(--gradient-2);
|
||
}
|
||
.service-card:hover { border-color: var(--border-h); transform: translateY(-3px); box-shadow: 0 10px 30px rgba(0,0,0,0.3); }
|
||
.service-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
}
|
||
.service-name { font-size: 16px; font-weight: 700; }
|
||
.service-status {
|
||
width: 10px; height: 10px;
|
||
border-radius: 50%;
|
||
background: var(--success);
|
||
box-shadow: 0 0 10px var(--success);
|
||
}
|
||
.service-status.down { background: var(--danger); box-shadow: 0 0 10px var(--danger); }
|
||
.service-status.unknown { background: var(--text-muted); }
|
||
.service-desc { font-size: 13px; color: var(--text-dim); margin-bottom: 10px; }
|
||
.service-url {
|
||
font-family: 'SF Mono', 'Courier New', monospace;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
background: rgba(0,0,0,0.25);
|
||
padding: 4px 8px;
|
||
border-radius: 6px;
|
||
display: inline-block;
|
||
margin-bottom: 10px;
|
||
}
|
||
.service-btn {
|
||
display: inline-block;
|
||
background: var(--gradient-4);
|
||
padding: 8px 14px;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
text-decoration: none;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* WEVIA */
|
||
.wevia-card {
|
||
background: var(--gradient-4);
|
||
border-radius: 18px;
|
||
padding: 28px;
|
||
margin-bottom: 32px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
box-shadow: 0 15px 40px rgba(139, 92, 246, 0.3);
|
||
}
|
||
.wevia-card::before {
|
||
content: '🧠';
|
||
position: absolute;
|
||
font-size: 200px;
|
||
top: -40px; right: -40px;
|
||
opacity: 0.08;
|
||
}
|
||
.wevia-card h2 {
|
||
color: white;
|
||
font-size: 24px;
|
||
margin-bottom: 8px;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.wevia-sub { color: rgba(255,255,255,0.85); margin-bottom: 18px; position: relative; z-index: 2; }
|
||
.intent-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 10px;
|
||
position: relative;
|
||
z-index: 2;
|
||
}
|
||
.intent-card {
|
||
background: rgba(255,255,255,0.12);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid rgba(255,255,255,0.2);
|
||
border-radius: 10px;
|
||
padding: 12px 14px;
|
||
cursor: pointer;
|
||
transition: all .15s;
|
||
}
|
||
.intent-card:hover { background: rgba(255,255,255,0.22); transform: translateX(3px); }
|
||
.intent-trigger { color: white; font-weight: 600; font-size: 13px; }
|
||
.intent-tool { color: rgba(255,255,255,0.7); font-size: 11px; margin-top: 3px; font-family: monospace; }
|
||
|
||
/* CHAT WIDGET */
|
||
.wevia-chat-bar {
|
||
position: fixed;
|
||
bottom: 20px;
|
||
right: 20px;
|
||
background: var(--gradient-1);
|
||
color: white;
|
||
padding: 14px 24px;
|
||
border-radius: 50px;
|
||
box-shadow: 0 10px 30px rgba(99, 102, 241, 0.5);
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
text-decoration: none;
|
||
transition: all .2s;
|
||
}
|
||
.wevia-chat-bar:hover { transform: translateY(-3px); box-shadow: 0 15px 40px rgba(99, 102, 241, 0.7); }
|
||
|
||
.hide { display: none !important; }
|
||
footer {
|
||
text-align: center;
|
||
color: var(--text-muted);
|
||
padding: 30px;
|
||
font-size: 12px;
|
||
border-top: 1px solid var(--border);
|
||
margin-top: 40px;
|
||
}
|
||
.breadcrumbs { color: var(--text-dim); font-size: 13px; margin-bottom: 20px; }
|
||
.breadcrumbs a { color: var(--accent); text-decoration: none; }
|
||
|
||
/* === Doctrine 84 — Premium page tiles (thumb + title + HTTP badge) === */
|
||
.page-tile { display:flex; flex-direction:column; background:var(--bg-card,#13182a); border:1px solid var(--border,#22283d); border-radius:12px; overflow:hidden; text-decoration:none; transition:all .2s ease; cursor:pointer; }
|
||
.page-tile:hover { transform:translateY(-2px); border-color:#4f46e5; box-shadow:0 8px 24px rgba(79,70,229,.18); }
|
||
.page-tile .thumb-wrap { position:relative; width:100%; aspect-ratio:16/10; background:#0a0f1e; overflow:hidden; }
|
||
.page-tile .thumb-img { width:100%; height:100%; object-fit:cover; display:block; background:#0a0f1e; }
|
||
.page-tile .thumb-img.thumb-missing { opacity:.2; }
|
||
.page-tile .thumb-ext { width:100%; height:100%; display:flex; align-items:center; justify-content:center; color:#4f46e5; font-size:14px; font-weight:600; letter-spacing:1px; background:linear-gradient(135deg,#1a1f35,#0a0f1e); }
|
||
.page-tile .http-badge { position:absolute; top:8px; right:8px; padding:3px 8px; border-radius:6px; font-size:11px; font-weight:600; font-family:monospace; backdrop-filter:blur(6px); }
|
||
.page-tile .http-badge.b-ok { background:rgba(16,185,129,.85); color:#fff; }
|
||
.page-tile .http-badge.b-redir { background:rgba(251,191,36,.85); color:#1f2937; }
|
||
.page-tile .http-badge.b-err { background:rgba(239,68,68,.85); color:#fff; }
|
||
.page-tile .http-badge.b-unk { background:rgba(100,116,139,.75); color:#fff; }
|
||
.page-tile .tile-meta { padding:10px 12px; }
|
||
.page-tile .tile-title { font-size:13px; font-weight:500; color:var(--text,#e2e8f0); margin-bottom:3px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||
.page-tile .tile-path { font-size:11px; color:var(--text-muted,#94a3b8); font-family:monospace; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
||
.grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(240px, 1fr)); gap:14px; }
|
||
/* Skeleton loader while meta fetch in progress */
|
||
.page-tile.loading .thumb-img { background:linear-gradient(90deg,#0a0f1e 0%,#1a1f35 50%,#0a0f1e 100%); background-size:200% 100%; animation:shimmer 1.5s infinite; }
|
||
@keyframes shimmer { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="container">
|
||
<div class="breadcrumbs">WEVAL › Enterprise Management Hub</div>
|
||
|
||
<div class="hero">
|
||
<h1>🏛️ WEVAL Enterprise Management</h1>
|
||
<div class="subtitle">Hub unifié — toute notre architecture, accessible en un clic. Zero régression, enrichissement continu.</div>
|
||
<div class="hero-stats">
|
||
<div class="stat-card"><div class="stat-val" id="statPages">–</div><div class="stat-lbl">Screens</div></div>
|
||
<div class="stat-card"><div class="stat-val" id="statApis">–</div><div class="stat-lbl">APIs REST</div></div>
|
||
<div class="stat-card"><div class="stat-val" id="statWiki">–</div><div class="stat-lbl">Wiki Entries</div></div>
|
||
<div class="stat-card"><div class="stat-val" id="statVault">–</div><div class="stat-lbl">Vault Docs</div></div>
|
||
<div class="stat-card"><div class="stat-val" id="statSvc">–</div><div class="stat-lbl">Services</div></div>
|
||
<div class="stat-card"><div class="stat-val" id="statIntents">–</div><div class="stat-lbl">WEVIA Intents</div></div>
|
||
<div class="stat-card"><div class="stat-val" id="statGrand">–</div><div class="stat-lbl">GRAND TOTAL</div></div>
|
||
<div class="stat-card"><div class="stat-val"><span id="statHealth">–</span>%</div><div class="stat-lbl">Health</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="search-bar">
|
||
<input id="search" type="text" placeholder="🔎 Chercher une page, API, service, ou intent..." autocomplete="off">
|
||
</div>
|
||
|
||
<!-- QUICK ACTIONS -->
|
||
<div class="section">
|
||
<div class="section-title">⚡ Quick Actions <span class="section-count" id="qaCount">0</span></div>
|
||
<div class="quick-actions" id="quickActions"></div>
|
||
</div>
|
||
|
||
<!-- WEVIA MASTER -->
|
||
<div class="wevia-card">
|
||
<h2>🧠 WEVIA Master — Intents via Chat NL</h2>
|
||
<div class="wevia-sub">Pilote toute la plateforme en langage naturel. Clique un intent pour l'exécuter, ou ouvre le chat.</div>
|
||
<div class="intent-grid" id="intentsGrid"></div>
|
||
</div>
|
||
|
||
<!-- SERVICES -->
|
||
<div class="section">
|
||
<div class="section-title">🧬 Services & Capabilities <span class="section-count" id="svcCount">0</span></div>
|
||
<div class="services" id="servicesGrid"></div>
|
||
</div>
|
||
|
||
<!-- CATEGORIES FILTER -->
|
||
<div class="tabs" id="tabs"></div>
|
||
|
||
<!-- PAGES BY CATEGORY -->
|
||
<div id="categoriesContainer"></div>
|
||
|
||
<!-- WIKI -->
|
||
<div class="section">
|
||
<div class="section-title">📚 Wiki Entries <span class="section-count" id="wikiCount">0</span></div>
|
||
<input id="wikiSearch" type="text" placeholder="🔎 Chercher dans le wiki..." style="width:100%;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;padding:10px 14px;color:var(--text);margin-bottom:12px;font-size:13px">
|
||
<div class="grid" id="wikiGrid"></div>
|
||
</div>
|
||
|
||
<!-- VAULT -->
|
||
<div class="section">
|
||
<div class="section-title">🔒 Vault Docs <span class="section-count" id="vaultCount">0</span></div>
|
||
<div class="grid" id="vaultGrid"></div>
|
||
</div>
|
||
|
||
<footer>
|
||
Généré via <a href="/wevia-master.html" style="color: var(--accent);">WEVIA Master</a> —
|
||
Plan action V23+ —
|
||
<a href="/api/plan-action-dp.md" style="color: var(--accent);">plan-action-dp.md</a> —
|
||
<a href="/wiki/P0-BRIEF-DECISIONNEL-17avr.md" style="color: var(--accent);">P0 Brief</a>
|
||
</footer>
|
||
</div>
|
||
|
||
<a href="/wevia-master.html" class="wevia-chat-bar">💬 Chat WEVIA Master</a>
|
||
|
||
<script>
|
||
let INVENTORY = null;
|
||
let CURRENT_CAT = 'all';
|
||
let SEARCH = '';
|
||
|
||
async function loadData() {
|
||
try {
|
||
// Doctrine 84 : preload page meta (titles + HTTP status) in parallel with inventory
|
||
const [rInv, rMeta] = await Promise.all([
|
||
fetch('/api/wem-inventory.json'),
|
||
fetch('/api/wem-page-meta.json').catch(() => null)
|
||
]);
|
||
INVENTORY = await rInv.json();
|
||
if (rMeta && rMeta.ok) {
|
||
try { window.WEM_META = await rMeta.json(); } catch(e) { window.WEM_META = {}; }
|
||
} else { window.WEM_META = {}; }
|
||
render();
|
||
checkHealth();
|
||
checkServices();
|
||
} catch(e) {
|
||
console.error('load err', e);
|
||
document.body.innerHTML = '<div style="padding:40px;text-align:center;color:#dc2626">Erreur chargement inventaire. Vérifiez /api/wem-inventory.json</div>';
|
||
}
|
||
}
|
||
|
||
function checkHealth() {
|
||
fetch('/api/screens-health.json').then(r => r.json()).then(d => {
|
||
const tot = d.total || 0;
|
||
const hth = (d.counts?.UP||0) + (d.counts?.SLOW||0) + (d.counts?.PROTECTED||0);
|
||
const pct = tot > 0 ? Math.round(hth/tot*100) : 0;
|
||
document.getElementById('statHealth').textContent = pct;
|
||
}).catch(()=>{});
|
||
}
|
||
|
||
async function checkServices() {
|
||
// Probe WEVIA Master for capabilities inventory
|
||
try {
|
||
const r = await fetch('/api/wevia-master-api.php', {
|
||
method: 'POST',
|
||
headers: {'Content-Type':'application/json'},
|
||
body: JSON.stringify({message: 'capabilities inventory', session: 'wem', history: []})
|
||
});
|
||
const d = await r.json();
|
||
const content = d.content || '';
|
||
// Parse "✅ Service (HTTP 200)" lines
|
||
document.querySelectorAll('.service-card').forEach(card => {
|
||
const name = card.dataset.name;
|
||
if (content.includes('✅ ' + name)) {
|
||
card.querySelector('.service-status').classList.remove('down','unknown');
|
||
} else if (content.includes('❌ ' + name)) {
|
||
card.querySelector('.service-status').classList.add('down');
|
||
}
|
||
});
|
||
} catch(e) {}
|
||
}
|
||
|
||
function render() {
|
||
// Stats — WEM_V2_WIKI_VAULT
|
||
document.getElementById('statPages').textContent = INVENTORY.total_screens || INVENTORY.total_pages;
|
||
document.getElementById('statApis').textContent = INVENTORY.total_apis;
|
||
if (document.getElementById('statWiki')) document.getElementById('statWiki').textContent = INVENTORY.total_wiki || 0;
|
||
if (document.getElementById('statVault')) document.getElementById('statVault').textContent = INVENTORY.total_vault || 0;
|
||
document.getElementById('statSvc').textContent = INVENTORY.total_services;
|
||
document.getElementById('statIntents').textContent = INVENTORY.total_intents + '+';
|
||
if (document.getElementById('statGrand')) document.getElementById('statGrand').textContent = INVENTORY.grand_total || INVENTORY.total_pages;
|
||
|
||
// Quick actions
|
||
const qa = document.getElementById('quickActions');
|
||
qa.innerHTML = INVENTORY.quick_actions.map(a => `
|
||
<div class="qa-card" onclick="weviaChat('${a.chat.replace(/'/g,"\\'")}')">
|
||
<div class="qa-icon">${a.icon}</div>
|
||
<div style="flex:1;min-width:0">
|
||
<div class="qa-label">${a.label}</div>
|
||
<div class="qa-chat">${a.chat}</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
document.getElementById('qaCount').textContent = INVENTORY.quick_actions.length;
|
||
|
||
// Intents
|
||
const ig = document.getElementById('intentsGrid');
|
||
ig.innerHTML = INVENTORY.wevia_intents.map(i => `
|
||
<div class="intent-card" onclick="weviaChat('${i.trigger.split('/')[0].trim().replace(/'/g,"\\'")}')">
|
||
<div class="intent-trigger">${i.trigger}</div>
|
||
<div class="intent-tool">→ ${i.tool} · ${i.cat}</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
// Services
|
||
const sg = document.getElementById('servicesGrid');
|
||
sg.innerHTML = Object.entries(INVENTORY.external_services).map(([name, svc]) => `
|
||
<div class="service-card" data-name="${name}">
|
||
<div class="service-head">
|
||
<div class="service-name">${name}</div>
|
||
<div class="service-status unknown" title="Status"></div>
|
||
</div>
|
||
<div class="service-desc">${svc.desc}</div>
|
||
<div class="service-url">${svc.url}</div>
|
||
<br>
|
||
<a href="${svc.public}" class="service-btn">Ouvrir Hub</a>
|
||
<a href="${svc.url}" target="_blank" class="service-btn" style="background:rgba(255,255,255,0.15);margin-left:6px">Direct</a>
|
||
</div>
|
||
`).join('');
|
||
document.getElementById('svcCount').textContent = Object.keys(INVENTORY.external_services).length;
|
||
|
||
// Tabs + categories
|
||
const tabs = document.getElementById('tabs');
|
||
const cats = Object.keys(INVENTORY.categorized_pages).sort((a,b) => INVENTORY.categorized_pages[b].length - INVENTORY.categorized_pages[a].length);
|
||
tabs.innerHTML = `<div class="tab active" onclick="filterCat('all')">Toutes (${INVENTORY.total_pages})</div>` +
|
||
cats.map(c => `<div class="tab" onclick="filterCat('${c}')">${c} (${INVENTORY.categorized_pages[c].length})</div>`).join('');
|
||
|
||
renderCategories();
|
||
}
|
||
|
||
function renderCategories() {
|
||
const container = document.getElementById('categoriesContainer');
|
||
const cats = Object.keys(INVENTORY.categorized_pages).sort((a,b) => INVENTORY.categorized_pages[b].length - INVENTORY.categorized_pages[a].length);
|
||
|
||
container.innerHTML = cats.filter(c => CURRENT_CAT === 'all' || c === CURRENT_CAT).map(c => {
|
||
const pages = INVENTORY.categorized_pages[c].filter(p => !SEARCH || p.toLowerCase().includes(SEARCH.toLowerCase()));
|
||
if (pages.length === 0 && SEARCH) return '';
|
||
return `
|
||
<div class="section">
|
||
<div class="section-title">📂 ${c} <span class="section-count">${pages.length}</span></div>
|
||
<div class="grid">
|
||
${pages.map(p => {
|
||
const url = p.startsWith('http') ? p : (p.startsWith('/') ? p : '/' + p);
|
||
// Build meta lookup key matching worker's key convention
|
||
let pathKey = null;
|
||
let displayPath = p;
|
||
const mW = p.match(/^https?:\/\/wevads\.weval-consulting\.com\/(.+)$/);
|
||
const mE = p.match(/^https?:\/\/ethica\.wevup\.app\/(.+)$/);
|
||
const mM = p.match(/^https?:\/\/(?:www\.)?weval-consulting\.com\/(.+)$/);
|
||
if (mW) { pathKey = 'wevads/' + mW[1]; displayPath = mW[1]; }
|
||
else if (mE) { pathKey = 'ethica/' + mE[1]; displayPath = mE[1]; }
|
||
else if (mM) { pathKey = mM[1]; displayPath = mM[1]; }
|
||
else if (!p.startsWith('http')) { pathKey = p.replace(/^\//, ''); displayPath = pathKey; }
|
||
// else: external 3rd party → pathKey stays null
|
||
const isExt = !pathKey;
|
||
const meta = (window.WEM_META && pathKey && window.WEM_META[pathKey]) || {};
|
||
const title = meta.title || displayPath.replace(/\.(html|php|md|json)$/,'').replace(/[\/\-_]/g, ' ').substring(0, 60);
|
||
const http = meta.http || 0;
|
||
const mtime = meta.mtime || '';
|
||
const thumbUrl = isExt ? '' : ('/api/wem-screen-thumb.php?path=' + encodeURIComponent(pathKey));
|
||
const badgeClass = http >= 200 && http < 300 ? 'b-ok' : (http >= 300 && http < 400 ? 'b-redir' : (http >= 400 ? 'b-err' : 'b-unk'));
|
||
const badgeText = http > 0 ? http : '...';
|
||
const thumbHtml = isExt
|
||
? '<div class="thumb-ext"><span>EXT</span></div>'
|
||
: `<img class="thumb-img" loading="lazy" src="${thumbUrl}" alt="" onerror="this.classList.add('thumb-missing')">`;
|
||
return `<a class="page-tile" href="${url}" target="_blank" title="${title} (${p})">
|
||
<div class="thumb-wrap">
|
||
${thumbHtml}
|
||
<span class="http-badge ${badgeClass}">${badgeText}</span>
|
||
</div>
|
||
<div class="tile-meta">
|
||
<div class="tile-title">${title}</div>
|
||
<div class="tile-path">${displayPath.length > 42 ? displayPath.substring(0,40)+'…' : displayPath}${mtime ? ' · ' + mtime : ''}</div>
|
||
</div>
|
||
</a>`;
|
||
}).join('')}
|
||
</div>
|
||
</div>`;
|
||
}).join('');
|
||
}
|
||
|
||
function filterCat(c) {
|
||
CURRENT_CAT = c;
|
||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||
event.target.classList.add('active');
|
||
renderCategories();
|
||
}
|
||
|
||
function weviaChat(msg) {
|
||
window.open('/wevia-master.html?q=' + encodeURIComponent(msg), '_blank');
|
||
}
|
||
|
||
document.getElementById('search').addEventListener('input', (e) => {
|
||
SEARCH = e.target.value;
|
||
renderCategories();
|
||
});
|
||
|
||
function renderWiki(filter = '') {
|
||
const wg = document.getElementById('wikiGrid');
|
||
if (!wg || !INVENTORY.wiki_pages) return;
|
||
const pages = INVENTORY.wiki_pages.filter(p => !filter || p.toLowerCase().includes(filter.toLowerCase()));
|
||
wg.innerHTML = pages.slice(0, 300).map(p => {
|
||
const name = p.replace(/\.json$/,'').replace(/^-/,'').replace(/-/g,' / ').substring(0, 70);
|
||
const url = p.endsWith('.md') ? '/wiki/' + p : '/wiki/view/' + encodeURIComponent(p);
|
||
return `<a class="page-card" href="${url}" target="_blank"><span class="name">${name}</span><span class="arrow">›</span></a>`;
|
||
}).join('');
|
||
document.getElementById('wikiCount').textContent = pages.length + ' / ' + INVENTORY.total_wiki;
|
||
}
|
||
|
||
function renderVault() {
|
||
const vg = document.getElementById('vaultGrid');
|
||
if (!vg || !INVENTORY.vault_pages) return;
|
||
vg.innerHTML = INVENTORY.vault_pages.map(p => {
|
||
const name = p.replace(/\.(md|json)$/,'').substring(0, 70);
|
||
return `<div class="page-card" onclick="weviaChat('show vault ${p}')"><span class="name">${name}</span><span class="arrow">›</span></div>`;
|
||
}).join('');
|
||
document.getElementById('vaultCount').textContent = INVENTORY.vault_pages.length + ' / ' + INVENTORY.total_vault;
|
||
}
|
||
|
||
// Wire wiki search
|
||
setTimeout(() => {
|
||
const ws = document.getElementById('wikiSearch');
|
||
if (ws) ws.addEventListener('input', e => renderWiki(e.target.value));
|
||
renderWiki();
|
||
renderVault();
|
||
}, 500);
|
||
|
||
loadData();
|
||
// Refresh health every 60s
|
||
setInterval(() => { checkHealth(); }, 60000);
|
||
</script>
|
||
|
||
<!-- LIVE KPI INJECTION — 17avr2026 — AJOUT ONLY -->
|
||
<script>
|
||
(function liveKPI(){
|
||
fetch('/api/em-live-kpi.php').then(r=>r.json()).then(d=>{
|
||
const el=id=>document.getElementById(id);
|
||
if(el('statPages'))el('statPages').textContent=d.assets.html_pages;
|
||
if(el('statApis'))el('statApis').textContent=d.assets.php_apis;
|
||
if(el('statWiki'))el('statWiki').textContent=d.assets.wiki_entries;
|
||
if(el('statVault'))el('statVault').textContent=(d.assets.vault_doctrines||0)+(d.assets.vault_sessions||0)+(d.assets.vault_decisions||0);
|
||
if(el('statSvc'))el('statSvc').textContent=d.services.filter(s=>s.status==='UP').length;
|
||
if(el('statIntents'))el('statIntents').textContent=d.tools.total;
|
||
if(el('statGrand'))el('statGrand').textContent=d.grand_total;
|
||
if(el('statHealth')){el('statHealth').textContent=d.health.pct+'%';el('statHealth').style.color=d.health.pct===100?'#4ade80':'#f59e0b';}
|
||
if(document.getElementById('liveKpiPanel'))return;
|
||
var p=document.createElement('div');p.id='liveKpiPanel';
|
||
p.style.cssText='margin:12px 24px;display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px;';
|
||
var cards=[
|
||
{i:'\u2699',t:'S204 Server',v:d.s204.load+' load | '+(d.s204.ram_free_mb/1024).toFixed(1)+'GB free',s:d.s204.disk_pct+' disk | '+d.s204.fpm_workers+' FPM | '+d.s204.docker_containers+' Docker | '+d.s204.cpu_cores+' CPU',c:'#3b82f6'},
|
||
{i:'\ud83d\udda5',t:'S95 WEVADS',v:d.s95.status+(d.s95.load?' | Load '+d.s95.load:''),s:d.s95.disk_pct?'Disk '+d.s95.disk_pct+(d.s95.ram_free_mb?' | '+Math.round(d.s95.ram_free_mb/1024)+'GB free':''):'',c:d.s95.status==='UP'?'#10b981':'#ef4444'},
|
||
{i:'\ud83e\udde0',t:'Sovereign IA',v:d.sovereign.active+'/'+d.sovereign.total+' providers | '+d.sovereign.cost,s:'Primary: '+d.sovereign.primary,c:'#8b5cf6'},
|
||
{i:'\ud83c\udfe5',t:'Ethica HCP',v:d.ethica.total_hcps.toLocaleString()+' HCPs',s:d.ethica.with_email.toLocaleString()+' emails ('+d.ethica.pct_email+'%) | Gap: '+d.ethica.gap_email.toLocaleString(),c:'#ec4899'},
|
||
{i:'\ud83d\udd0a',t:'Whisper STT',v:d.whisper.binary,s:'Model: '+d.whisper.model,c:d.whisper.binary==='COMPILED'?'#10b981':'#6b7280'},
|
||
{i:'\ud83d\udcbe',t:'Git',v:d.git.status,s:(d.git.head||'').substring(0,35),c:d.git.status==='CLEAN'?'#10b981':'#f59e0b'},
|
||
{i:'\u2705',t:'NonReg Playwright',v:d.nonreg.passed+'/'+d.nonreg.total,s:d.nonreg.score+' — 6\u03c3',c:'#10b981'},
|
||
{i:'\ud83d\udd17',t:'Services Live',v:d.services.filter(s=>s.status==='UP').length+'/'+d.services.length+' UP',s:d.services.map(s=>s.name.split(' ')[0]+':'+(s.status==='UP'?'\u2705':'\u274c')).join(' '),c:'#06b6d4'}
|
||
];
|
||
cards.forEach(function(c){
|
||
var dv=document.createElement('div');
|
||
dv.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid '+c.c+';cursor:pointer;transition:transform .2s;';
|
||
dv.onmouseenter=function(){this.style.transform='translateY(-2px)';};
|
||
dv.onmouseleave=function(){this.style.transform='none';};
|
||
dv.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">'+c.i+'</span><span style="color:'+c.c+';font-weight:600;font-size:13px">'+c.t+'</span></div><div style="color:#e2e8f0;font-size:15px;font-weight:600">'+c.v+'</div><div style="color:#94a3b8;font-size:11px;margin-top:4px">'+c.s+'</div>';
|
||
p.appendChild(dv);
|
||
});
|
||
var pmta=document.createElement('div');
|
||
pmta.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #f97316;';
|
||
pmta.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">\ud83d\udce7</span><span style="color:#f97316;font-weight:600;font-size:13px">PMTA ECS</span></div><div style="color:#e2e8f0;font-size:13px">'+d.pmta.map(function(x){return x.name+': '+(x.status==='UP'?'\u2705':'\u26a0\ufe0f');}).join(' | ')+'</div>';
|
||
p.appendChild(pmta);
|
||
var dk=document.createElement('div');
|
||
dk.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #06b6d4;grid-column:span 2;';
|
||
dk.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px"><span style="font-size:18px">\ud83d\udc33</span><span style="color:#06b6d4;font-weight:600;font-size:13px">Docker ('+d.docker.length+')</span></div><div style="display:flex;flex-wrap:wrap;gap:4px">'+d.docker.map(function(c){return '<span style="background:rgba(6,182,212,0.15);color:#67e8f9;padding:2px 8px;border-radius:6px;font-size:11px">'+c.name+'</span>';}).join('')+'</div>';
|
||
p.appendChild(dk);
|
||
if(d.ethica.by_country&&d.ethica.by_country.length){
|
||
var eth=document.createElement('div');
|
||
eth.style.cssText='background:rgba(15,20,40,0.7);border:1px solid rgba(255,255,255,0.08);border-radius:12px;padding:14px;border-left:3px solid #ec4899;grid-column:span 2;';
|
||
eth.innerHTML='<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px"><span style="font-size:18px">\ud83c\udf0d</span><span style="color:#ec4899;font-weight:600;font-size:13px">Ethica par pays</span></div><div style="display:flex;gap:20px;flex-wrap:wrap">'+d.ethica.by_country.map(function(c){return '<div style="text-align:center"><div style="color:#e2e8f0;font-size:18px;font-weight:700">'+parseInt(c.t).toLocaleString()+'</div><div style="color:#94a3b8;font-size:11px">'+c.country+' ('+parseInt(c.e).toLocaleString()+' emails)</div></div>';}).join('')+'</div>';
|
||
p.appendChild(eth);
|
||
}
|
||
var ts=document.createElement('div');
|
||
ts.style.cssText='grid-column:1/-1;text-align:right;color:#475569;font-size:10px;padding:4px 0;';
|
||
ts.textContent='Live KPI \u2014 '+d.ts+' \u2014 '+d.elapsed_ms+'ms';
|
||
p.appendChild(ts);
|
||
var h=document.querySelector('.stat-bar')||document.querySelector('.hero-stats');
|
||
if(h&&h.parentNode)h.parentNode.insertBefore(p,h.nextSibling);
|
||
else{var sb=document.querySelector('.search-bar')||document.querySelector('input[type=search]');if(sb)sb.parentNode.insertBefore(p,sb);else document.body.appendChild(p);}
|
||
}).catch(function(e){console.warn('LiveKPI:',e);});
|
||
setTimeout(liveKPI,60000);
|
||
})();
|
||
</script>
|
||
<script src="/api/em-live-inject.js"></script>
|
||
<!-- WTP-GAP-FILL-V1 (doctrine 90-v2 gap-fill showcase, 18avr 2026) -->
|
||
<style>
|
||
.wtp-gapfill-banner{position:fixed;bottom:0;left:0;right:0;z-index:99999;background:linear-gradient(90deg,#05060a,#0b0d15 20%,#181d2e 50%,#0b0d15 80%,#05060a);border-top:2px solid #14b8a6;color:#e2e8f0;padding:10px 16px;font-family:Inter,system-ui,-apple-system,sans-serif;font-size:11.5px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;box-shadow:0 -10px 30px rgba(20,184,166,.28)}
|
||
.wtp-gapfill-banner a{color:#5eead4;text-decoration:none;font-weight:600;transition:color .15s}
|
||
.wtp-gapfill-banner a:hover{color:#22d3ee}
|
||
.wtp-gapfill-banner .pill{padding:2px 9px;background:rgba(99,102,241,.14);color:#a5b4fc;border-radius:10px;font-size:10.5px;font-family:JetBrains Mono,monospace;font-weight:600}
|
||
.wtp-gapfill-banner .pill.new{background:rgba(20,184,166,.22);color:#5eead4}
|
||
.wtp-gapfill-banner .pill.hot{background:rgba(236,72,153,.22);color:#f472b6}
|
||
.wtp-gapfill-banner .close{margin-left:auto;cursor:pointer;color:#64748b;padding:0 8px;font-size:16px;line-height:1;border:1px solid #334155;border-radius:4px}
|
||
.wtp-gapfill-banner .close:hover{color:#e2e8f0;border-color:#64748b}
|
||
.wtp-gapfill-banner.hidden{display:none}
|
||
@media(max-width:768px){.wtp-gapfill-banner{font-size:10px;padding:7px 10px;gap:8px}}
|
||
</style>
|
||
<div class="wtp-gapfill-banner" id="wtpGapFillBanner">
|
||
<span>🎯 <strong>WEVAL Agents Gap-Fill ERP</strong></span>
|
||
<span class="pill hot">45 gaps</span>
|
||
<span class="pill">SAP · Oracle · NetSuite · Dynamics</span>
|
||
<span class="pill new">🆕 Meeting Rooms</span>
|
||
<span class="pill new">🆕 Lean 6 Sigma</span>
|
||
<span id="wtp-gfb-metrics" class="pill">— chargement —</span>
|
||
<a href="/weval-technology-platform.html">→ WTP Portal (16 mod)</a>
|
||
<a href="/enterprise-model.html">Enterprise Model</a>
|
||
<a href="/api/weval-agents-gap-fill-manifest.json" target="_blank">📋 Manifest</a>
|
||
<span class="close" onclick="document.getElementById("wtpGapFillBanner").classList.add("hidden");localStorage.setItem("wtpGapFillHidden","1")">×</span>
|
||
</div>
|
||
<script>
|
||
(async()=>{
|
||
if(localStorage.getItem("wtpGapFillHidden")==="1"){document.getElementById("wtpGapFillBanner").classList.add("hidden");return;}
|
||
try{
|
||
const r=await fetch("/api/source-of-truth.json?t="+Date.now());
|
||
const d=await r.json();
|
||
const el=document.getElementById("wtp-gfb-metrics");
|
||
if(el)el.textContent=(d.ethica_total||"?")+" HCPs · "+(d.nonreg||"?")+" · "+(d.providers_count||"?")+" IA · "+(d.agents_count||"?")+" agents · "+(d.docker_running||"?")+" 🐳";
|
||
}catch(e){}
|
||
})();
|
||
</script>
|
||
|
||
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
|
||
<script>
|
||
(function(){
|
||
if (window.__opusUniversalDrill) return; window.__opusUniversalDrill = true;
|
||
var d = document;
|
||
var m = d.createElement('div');
|
||
m.id = 'opus-udrill';
|
||
m.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;z-index:99995;padding:20px;cursor:pointer';
|
||
var inner = d.createElement('div');
|
||
inner.id = 'opus-udrill-in';
|
||
inner.style.cssText = 'max-width:900px;width:100%;max-height:90vh;overflow:auto;background:#0b0d15;border:1px solid rgba(99,102,241,0.35);border-radius:14px;padding:28px;cursor:default;box-shadow:0 20px 60px rgba(0,0,0,0.6);color:#e2e8f0;font:14px/1.55 Inter,system-ui,sans-serif';
|
||
inner.addEventListener('click', function(e){ e.stopPropagation(); });
|
||
m.appendChild(inner);
|
||
m.addEventListener('click', function(){ m.style.display='none'; });
|
||
d.addEventListener('keydown', function(e){ if(e.key==='Escape') m.style.display='none'; });
|
||
(d.body || d.documentElement).appendChild(m);
|
||
|
||
function openCard(card) {
|
||
// Clone card content + show close btn + increase font-size
|
||
var html = '<div style="display:flex;justify-content:flex-end;margin-bottom:14px"><button id="opus-udrill-close" style="padding:6px 14px;background:#171b2a;border:1px solid rgba(99,102,241,0.25);color:#e2e8f0;border-radius:8px;cursor:pointer;font-size:12px">✕ Fermer (Esc)</button></div>';
|
||
html += '<div style="transform-origin:top left;font-size:1.05em">' + card.outerHTML + '</div>';
|
||
inner.innerHTML = html;
|
||
d.getElementById('opus-udrill-close').onclick = function(){ m.style.display='none'; };
|
||
m.style.display = 'flex';
|
||
}
|
||
|
||
function wire(root) {
|
||
var sels = '.card,[class*="card"],.kpi,[class*="kpi"],.stat,[class*="stat"],.tile,[class*="tile"],.metric,[class*="metric"],.widget,[class*="widget"]';
|
||
var cards = root.querySelectorAll(sels);
|
||
for (var i = 0; i < cards.length; i++) {
|
||
var c = cards[i];
|
||
if (c.__opusWired) continue;
|
||
if (c.closest('button, a, input, select, textarea, #opus-udrill')) continue;
|
||
var r = c.getBoundingClientRect();
|
||
if (r.width < 60 || r.height < 40) continue;
|
||
c.__opusWired = true;
|
||
c.style.cursor = 'pointer';
|
||
c.setAttribute('role','button');
|
||
c.setAttribute('tabindex','0');
|
||
c.addEventListener('click', function(ev){
|
||
// If a more-specific drill is already active (e.g. pp-card custom), let it handle
|
||
if (ev.target.closest('[data-pp-id]') && window.__opusDrillInit) return;
|
||
if (ev.target.closest('a,button,input,select')) return;
|
||
ev.preventDefault(); ev.stopPropagation();
|
||
openCard(this);
|
||
});
|
||
c.addEventListener('keydown', function(ev){ if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();openCard(this);} });
|
||
}
|
||
}
|
||
|
||
// Initial + mutation observer
|
||
var initRun = function(){ wire(d.body || d.documentElement); };
|
||
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', initRun);
|
||
else initRun();
|
||
var mo = new MutationObserver(function(muts){
|
||
var newCard = false;
|
||
for (var i=0;i<muts.length;i++) if (muts[i].addedNodes.length) { newCard = true; break; }
|
||
if (newCard) initRun();
|
||
});
|
||
mo.observe(d.body || d.documentElement, {childList:true, subtree:true});
|
||
})();
|
||
</script>
|
||
<!-- === OPUS UNIVERSAL DRILL-DOWN END === -->
|
||
|
||
</body>
|
||
</html>
|