This commit is contained in:
5
api/business-kpi-dashboard.php
Normal file
5
api/business-kpi-dashboard.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
// V93 alias /api/business-kpi-dashboard.php -> /business-kpi-dashboard.php
|
||||
// Root cause: clients call /api/ path but file at root
|
||||
header('X-V93-alias: root->api');
|
||||
require_once __DIR__ . '/../business-kpi-dashboard.php';
|
||||
11
api/v83-business-kpi-dashboard-data.php
Normal file
11
api/v83-business-kpi-dashboard-data.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
// V93 alias to serve the latest JSON cache as PHP response
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('X-V93-source: v83-business-kpi-latest.json');
|
||||
$cache = __DIR__ . '/v83-business-kpi-latest.json';
|
||||
if (file_exists($cache)) {
|
||||
readfile($cache);
|
||||
} else {
|
||||
echo json_encode(['ok'=>false,'note'=>'cache not yet populated','ts'=>date('c')]);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-20T12:13:44+00:00",
|
||||
"ts": "2026-04-20T12:14:13+00:00",
|
||||
"summary": {
|
||||
"total_categories": 7,
|
||||
"total_kpis": 56,
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
<?php
|
||||
// V92 alias: visual-management-data -> visual-management-live (fix Primary script unknown error)
|
||||
require_once __DIR__ . '/visual-management-live.php';
|
||||
// V93 cached wrapper - fixes 8sec psql queries
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('X-V93-cache: 60s-wrapper');
|
||||
$cache = '/tmp/visual-management-data.cache.json';
|
||||
if (file_exists($cache) && (time() - filemtime($cache)) < 60) {
|
||||
header('X-V93-cache-hit: 1');
|
||||
readfile($cache);
|
||||
exit;
|
||||
}
|
||||
ob_start();
|
||||
require __DIR__ . '/visual-management-live.php';
|
||||
$body = ob_get_clean();
|
||||
@file_put_contents($cache, $body);
|
||||
header('X-V93-cache-hit: 0');
|
||||
echo $body;
|
||||
|
||||
@@ -59,11 +59,11 @@ h1{background:linear-gradient(90deg,#f59e0b,#ef4444);-webkit-background-clip:tex
|
||||
|
||||
<div class="col">
|
||||
<h2>📊 Volumes Business Live</h2>
|
||||
<div class="metric"><span class="label">Ethica HCPs</span><span class="val recent" id="ethica">—</span></div>
|
||||
<div class="metric"><span class="label">Office 365</span><span class="val recent" id="office">—</span></div>
|
||||
<div class="metric"><span class="label">Inbox accts</span><span class="val recent" id="inbox">—</span></div>
|
||||
<div class="metric"><span class="label">ADS accts</span><span class="val recent" id="ads">—</span></div>
|
||||
<div class="metric"><span class="label">Weval leads</span><span class="val recent" id="weval">—</span></div>
|
||||
<div class="metric"><span class="label">Ethica HCPs</span><span class="val recent" id="ethica" data-k="ethica" data-intent="ethica live" onclick="v93Drill(this)" style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px">—</span></div>
|
||||
<div class="metric"><span class="label">Office 365</span><span class="val recent" id="office" data-k="office" data-intent="office 365 accounts" onclick="v93Drill(this)" style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px">—</span></div>
|
||||
<div class="metric"><span class="label">Inbox accts</span><span class="val recent" id="inbox" data-k="inbox" data-intent="inbox accounts" onclick="v93Drill(this)" style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px">—</span></div>
|
||||
<div class="metric"><span class="label">ADS accts</span><span class="val recent" id="ads" data-k="ads" data-intent="ads accounts" onclick="v93Drill(this)" style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px">—</span></div>
|
||||
<div class="metric"><span class="label">Weval leads</span><span class="val recent" id="weval" data-k="weval" data-intent="weval leads count" onclick="v93Drill(this)" style="cursor:pointer;text-decoration:underline dotted;text-underline-offset:3px">—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
175
crm-dashboard-live.html.GOLD-V93-20260420-1413
Normal file
175
crm-dashboard-live.html.GOLD-V93-20260420-1413
Normal file
@@ -0,0 +1,175 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CRM Dual Dashboard — WEVAL</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0a0e27;color:#e4e8f7;margin:0;padding:20px}
|
||||
.container{max-width:1400px;margin:0 auto}
|
||||
h1{background:linear-gradient(90deg,#f59e0b,#ef4444);-webkit-background-clip:text;-webkit-text-fill-color:transparent;font-size:36px;margin-bottom:4px}
|
||||
.subtitle{color:#9ca8d3;margin-bottom:24px}
|
||||
.live-pulse{display:inline-block;width:8px;height:8px;background:#f59e0b;border-radius:50%;animation:pulse 2s infinite;margin-right:6px}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
|
||||
.alert{background:rgba(245,158,11,0.15);border:1px solid #f59e0b;border-radius:8px;padding:16px;margin-bottom:24px}
|
||||
.alert strong{color:#f59e0b}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-bottom:24px}
|
||||
.col{background:rgba(20,25,51,0.6);border:1px solid #263161;border-radius:12px;padding:20px}
|
||||
.col h2{margin:0 0 16px;color:#c084fc;font-size:18px}
|
||||
.metric{display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-bottom:1px solid rgba(38,49,97,0.3)}
|
||||
.metric:last-child{border-bottom:0}
|
||||
.metric .label{color:#9ca8d3;font-size:13px}
|
||||
.metric .val{font-size:22px;font-weight:700;color:#6ba3ff}
|
||||
.stale{color:#ef4444 !important}
|
||||
.recent{color:#10b981 !important}
|
||||
.doctrine-link{background:#1e3a8a;color:#fff;padding:8px 14px;border-radius:6px;text-decoration:none;font-size:12px;display:inline-block}
|
||||
.doctrine-link:hover{background:#2950a7}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>CRM Dual Dashboard</h1>
|
||||
<div class="subtitle">
|
||||
<span class="live-pulse"></span> Twenty (active) + Legacy (stale) — auto-refresh 30s
|
||||
</div>
|
||||
|
||||
<div class="alert">
|
||||
<strong>⚠ Doctrine 55 CRM STALENESS</strong> — Le job <code>send_contacts_merge</code> est stoppé depuis 2026-02-19 (2+ mois sans activité CRM). Pipeline Twenty inactif.
|
||||
<a href="/api/wiki/doctrine-55-crm-staleness.md" class="doctrine-link">Lire doctrine complète →</a>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col">
|
||||
<h2>🆕 Twenty CRM (Pipeline Actif)</h2>
|
||||
<div class="metric"><span class="label">Deals</span><span class="val" id="t-deals">—</span></div>
|
||||
<div class="metric"><span class="label">Contacts</span><span class="val" id="t-contacts">—</span></div>
|
||||
<div class="metric"><span class="label">Companies</span><span class="val" id="t-companies">—</span></div>
|
||||
<div class="metric"><span class="label">Leads</span><span class="val" id="t-leads">—</span></div>
|
||||
<div class="metric"><span class="label">Last deal</span><span class="val" id="t-last-deal">—</span></div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<h2>📦 CRM Legacy (Stale)</h2>
|
||||
<div class="metric"><span class="label">CRM Contacts (vue)</span><span class="val stale" id="l-contacts">—</span></div>
|
||||
<div class="metric"><span class="label">Send contacts (base)</span><span class="val stale" id="l-send">—</span></div>
|
||||
<div class="metric"><span class="label">Last merge</span><span class="val stale" id="l-last-merge">—</span></div>
|
||||
<div class="metric"><span class="label">Days since last</span><span class="val stale" id="l-days">—</span></div>
|
||||
<div class="metric"><span class="label">Verdict</span><span class="val" id="l-verdict">—</span></div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<h2>📊 Volumes Business Live</h2>
|
||||
<div class="metric"><span class="label">Ethica HCPs</span><span class="val recent" id="ethica">—</span></div>
|
||||
<div class="metric"><span class="label">Office 365</span><span class="val recent" id="office">—</span></div>
|
||||
<div class="metric"><span class="label">Inbox accts</span><span class="val recent" id="inbox">—</span></div>
|
||||
<div class="metric"><span class="label">ADS accts</span><span class="val recent" id="ads">—</span></div>
|
||||
<div class="metric"><span class="label">Weval leads</span><span class="val recent" id="weval">—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="last-refresh" style="color:#9ca8d3;font-size:11px;font-style:italic;margin-top:16px">Initialisation...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function fetchLive() {
|
||||
const fmt = n => typeof n === 'number' ? n.toLocaleString('fr-FR') : (n || '—');
|
||||
try {
|
||||
const crm = await fetch('/api/crm-audit-live.php').then(r => r.json());
|
||||
const office = await fetch('/api/office-admins.php?action=status').then(r => r.json());
|
||||
|
||||
document.getElementById('t-deals').textContent = fmt(crm.twenty_deals);
|
||||
document.getElementById('t-contacts').textContent = fmt(40);
|
||||
document.getElementById('t-companies').textContent = fmt(2107);
|
||||
document.getElementById('t-leads').textContent = fmt(1921);
|
||||
document.getElementById('t-last-deal').textContent = crm.twenty_last_deal || '2026-03-27';
|
||||
|
||||
document.getElementById('l-contacts').textContent = fmt(crm.crm_contacts);
|
||||
document.getElementById('l-send').textContent = fmt(crm.send_contacts);
|
||||
document.getElementById('l-last-merge').textContent = '2026-02-19';
|
||||
const daysSince = Math.floor((Date.now() - new Date('2026-02-19').getTime()) / 86400000);
|
||||
document.getElementById('l-days').textContent = daysSince + ' jours';
|
||||
document.getElementById('l-verdict').textContent = crm.verdict;
|
||||
document.getElementById('l-verdict').className = crm.verdict === 'OK' ? 'val recent' : 'val stale';
|
||||
|
||||
document.getElementById('ethica').textContent = fmt(crm.ethica_hcps);
|
||||
document.getElementById('office').textContent = fmt(office.total);
|
||||
document.getElementById('inbox').textContent = fmt(148186);
|
||||
document.getElementById('ads').textContent = fmt(17);
|
||||
document.getElementById('weval').textContent = fmt(1921);
|
||||
|
||||
document.getElementById('last-refresh').textContent = `MAJ: ${new Date().toLocaleTimeString('fr-FR')}`;
|
||||
} catch (e) {
|
||||
document.getElementById('last-refresh').textContent = 'Erreur: ' + e.message;
|
||||
}
|
||||
}
|
||||
fetchLive();
|
||||
setInterval(fetchLive, 30000);
|
||||
</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 === -->
|
||||
|
||||
<script src="/api/archi-meta-badge.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -199,14 +199,14 @@ async function load() {
|
||||
function renderStats() {
|
||||
const s = ALL_DATA.summary;
|
||||
document.getElementById('stats').innerHTML = `
|
||||
<div class="stat s-ac"><div class="st-l">Total tools</div><div class="st-v">${s.total_tools}</div><div class="st-s">OSS integrated in stack</div></div>
|
||||
<div class="stat s-gn"><div class="st-l">Wired</div><div class="st-v">${s.wired_tools}/${s.total_tools}</div><div class="st-s">All connected to WEVIA</div></div>
|
||||
<div class="stat s-cy"><div class="st-l">Total skills</div><div class="st-v">${s.total_skills.toLocaleString()}</div><div class="st-s">Across 72 tools</div></div>
|
||||
<div class="stat s-bl"><div class="st-l">Injected in RAG</div><div class="st-v">${s.injected_skills.toLocaleString()}</div><div class="st-s">Qdrant vectors live</div></div>
|
||||
<div class="stat s-pk"><div class="st-l">Coverage</div><div class="st-v">${s.coverage_pct}%</div><div class="st-s">Injected / Total</div></div>
|
||||
<div class="stat s-gn"><div class="st-l">With Docker</div><div class="st-v">${s.with_docker}</div><div class="st-s">Production-ready</div></div>
|
||||
<div class="stat s-ac"><div class="st-l">With README</div><div class="st-v">${s.with_readme}</div><div class="st-s">Documented</div></div>
|
||||
<div class="stat s-cy"><div class="st-l">Production</div><div class="st-v">${s.production_count}</div><div class="st-s">Docker + wired live</div></div>
|
||||
<div class="stat s-ac" data-k="total_tools" data-intent="v77 oss discovery enriched" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">Total tools</div><div class="st-v">${s.total_tools}</div><div class="st-s">OSS integrated in stack</div></div>
|
||||
<div class="stat s-gn" data-k="wired" data-intent="oss wired tools list" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">Wired</div><div class="st-v">${s.wired_tools}/${s.total_tools}</div><div class="st-s">All connected to WEVIA</div></div>
|
||||
<div class="stat s-cy" data-k="total_skills" data-intent="oss 20126 skills breakdown" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">Total skills</div><div class="st-v">${s.total_skills.toLocaleString()}</div><div class="st-s">Across 72 tools</div></div>
|
||||
<div class="stat s-bl" data-k="injected_in_rag" data-intent="qdrant injection status" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">Injected in RAG</div><div class="st-v">${s.injected_skills.toLocaleString()}</div><div class="st-s">Qdrant vectors live</div></div>
|
||||
<div class="stat s-pk" data-k="coverage" data-intent="oss coverage analysis" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">Coverage</div><div class="st-v">${s.coverage_pct}%</div><div class="st-s">Injected / Total</div></div>
|
||||
<div class="stat s-gn" data-k="with_docker" data-intent="oss production tools" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">With Docker</div><div class="st-v">${s.with_docker}</div><div class="st-s">Production-ready</div></div>
|
||||
<div class="stat s-ac" data-k="with_readme" data-intent="oss documented tools" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">With README</div><div class="st-v">${s.with_readme}</div><div class="st-s">Documented</div></div>
|
||||
<div class="stat s-cy" data-k="production" data-intent="oss production live" onclick="v93Drill(this)" style="cursor:pointer"<div class="st-l">Production</div><div class="st-v">${s.production_count}</div><div class="st-s">Docker + wired live</div></div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
321
oss-discovery-v77.html.GOLD-V93-20260420-1413
Normal file
321
oss-discovery-v77.html.GOLD-V93-20260420-1413
Normal file
@@ -0,0 +1,321 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>WEVAL — OSS Discovery V77 Drill-down</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--bg:#0a0e17;--bg2:#111827;--bg3:#1a2234;--bg4:#243049;
|
||||
--bd:#1e293b;--bd2:#334155;--wh:#f1f5f9;--mu:#64748b;--mu2:#94a3b8;
|
||||
--ac:#f59e0b;--ac2:#fbbf24;--gn:#22c55e;--gn2:#4ade80;
|
||||
--bl:#3b82f6;--bl2:#60a5fa;--cy:#22d3ee;--rd:#ef4444;--or:#f97316;
|
||||
--pk:#ec4899;--vi:#8b5cf6;
|
||||
--r1:6px;--r2:10px;--r3:14px;
|
||||
--font:'Plus Jakarta Sans',sans-serif;--mono:'JetBrains Mono',monospace;
|
||||
}
|
||||
body{background:var(--bg);color:var(--wh);font-family:var(--font);overflow-x:hidden}
|
||||
a{color:var(--cy);text-decoration:none}a:hover{text-decoration:underline}
|
||||
|
||||
.hdr{background:linear-gradient(135deg,#0f172a 0%,#1a1040 50%,#0f172a 100%);border-bottom:1px solid var(--bd);padding:20px 32px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px}
|
||||
.hdr-left{display:flex;align-items:center;gap:16px}
|
||||
.hdr-logo{width:42px;height:42px;background:linear-gradient(135deg,var(--ac),var(--or));border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:20px;font-weight:800;color:#000}
|
||||
.hdr h1{font-size:22px;font-weight:800;letter-spacing:-.5px}
|
||||
.hdr h1 span{color:var(--ac);font-weight:900}
|
||||
.hdr-sub{font-size:11px;color:var(--mu);margin-top:2px;font-family:var(--mono)}
|
||||
.btn{padding:8px 18px;border-radius:var(--r1);border:1px solid var(--bd2);background:var(--bg3);color:var(--wh);font-size:12px;font-weight:600;cursor:pointer;transition:.2s;font-family:var(--font);display:inline-flex;align-items:center;gap:6px;text-decoration:none}
|
||||
.btn:hover{background:var(--bg4);border-color:var(--ac);text-decoration:none}
|
||||
.btn-ac{background:linear-gradient(135deg,var(--ac),var(--or));color:#000;border:none}
|
||||
|
||||
.main{max-width:1560px;margin:0 auto;padding:24px}
|
||||
|
||||
/* KPI stats row */
|
||||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-bottom:24px}
|
||||
.stat{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r2);padding:18px 20px;position:relative;overflow:hidden;transition:.3s;cursor:pointer}
|
||||
.stat:hover{border-color:var(--ac);transform:translateY(-3px);box-shadow:0 8px 24px rgba(245,158,11,.15)}
|
||||
.stat::after{content:'';position:absolute;top:0;left:0;right:0;height:3px}
|
||||
.stat.s-ac::after{background:linear-gradient(90deg,var(--ac),var(--or))}
|
||||
.stat.s-gn::after{background:linear-gradient(90deg,var(--gn),var(--cy))}
|
||||
.stat.s-bl::after{background:linear-gradient(90deg,var(--bl),var(--vi))}
|
||||
.stat.s-pk::after{background:linear-gradient(90deg,var(--pk),var(--vi))}
|
||||
.stat.s-cy::after{background:linear-gradient(90deg,var(--cy),var(--bl))}
|
||||
.st-l{font-size:10px;color:var(--mu);text-transform:uppercase;letter-spacing:.7px;font-weight:700}
|
||||
.st-v{font-size:28px;font-weight:800;margin:6px 0;font-family:var(--mono);line-height:1}
|
||||
.st-s{font-size:10px;color:var(--mu2);margin-top:4px}
|
||||
|
||||
/* Categories drill-down */
|
||||
.section-title{font-size:16px;font-weight:800;margin:28px 0 14px;display:flex;align-items:center;gap:10px;color:var(--ac2)}
|
||||
.section-title::before{content:'';display:inline-block;width:4px;height:18px;background:linear-gradient(180deg,var(--ac),var(--or));border-radius:2px}
|
||||
|
||||
.cats{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px}
|
||||
.cat{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r2);padding:16px 18px;cursor:pointer;transition:.25s;position:relative;overflow:hidden}
|
||||
.cat:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.4)}
|
||||
.cat-strip{position:absolute;top:0;left:0;right:0;height:3px}
|
||||
.cat-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
|
||||
.cat-emoji{font-size:22px}
|
||||
.cat-name{font-size:14px;font-weight:700}
|
||||
.cat-count{margin-left:auto;font-family:var(--mono);font-weight:700;font-size:13px}
|
||||
.cat-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--bd)}
|
||||
.cat-stat{font-size:10px;color:var(--mu2)}
|
||||
.cat-stat b{display:block;font-family:var(--mono);font-size:14px;color:var(--wh);margin-bottom:2px}
|
||||
.cat-cov{margin-top:10px}
|
||||
.cat-cov-bar{height:6px;background:var(--bg);border-radius:3px;overflow:hidden}
|
||||
.cat-cov-fill{height:100%;background:linear-gradient(90deg,var(--gn),var(--cy));transition:width .8s ease}
|
||||
.cat-cov-label{display:flex;justify-content:space-between;font-size:10px;color:var(--mu);margin-top:4px}
|
||||
|
||||
/* Tool drill-down detail */
|
||||
.drill{margin-top:14px;padding-top:14px;border-top:1px dashed var(--bd);display:none}
|
||||
.cat.open .drill{display:block}
|
||||
.tool-row{display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:var(--r1);transition:.2s;font-size:11px}
|
||||
.tool-row:hover{background:var(--bg3)}
|
||||
.tool-name{flex:1;font-family:var(--mono);font-weight:600}
|
||||
.tool-badges{display:flex;gap:4px;flex-shrink:0}
|
||||
.tool-badge{font-size:9px;padding:2px 6px;border-radius:10px;font-family:var(--mono);font-weight:600}
|
||||
.tb-files{background:rgba(59,130,246,.15);color:var(--bl2)}
|
||||
.tb-docker{background:rgba(34,211,238,.15);color:var(--cy)}
|
||||
.tb-readme{background:rgba(34,197,94,.15);color:var(--gn2)}
|
||||
.tb-wired{background:rgba(245,158,11,.15);color:var(--ac2)}
|
||||
|
||||
/* Skills explorer */
|
||||
.explorer{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r2);margin-top:14px;padding:18px 20px}
|
||||
.explorer-head{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:14px}
|
||||
.search-wrap{display:flex;align-items:center;gap:8px;background:var(--bg);border:1px solid var(--bd2);border-radius:var(--r1);padding:8px 12px;flex:1;max-width:420px}
|
||||
.search-wrap input{flex:1;background:none;border:none;color:var(--wh);font-family:var(--mono);font-size:12px;outline:none}
|
||||
.filter-pills{display:flex;gap:6px;flex-wrap:wrap}
|
||||
.pill{padding:5px 12px;border-radius:20px;border:1px solid var(--bd2);background:var(--bg3);color:var(--mu2);font-size:10px;font-weight:700;cursor:pointer;transition:.2s}
|
||||
.pill:hover{border-color:var(--ac);color:var(--wh)}
|
||||
.pill.active{background:var(--ac);color:#000;border-color:var(--ac)}
|
||||
|
||||
.skills-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;max-height:400px;overflow-y:auto;padding-right:8px}
|
||||
.skill-item{background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r1);padding:10px 12px;font-size:11px;transition:.2s;cursor:pointer}
|
||||
.skill-item:hover{border-color:var(--cy);background:var(--bg4);transform:translateY(-1px)}
|
||||
.skill-name{font-family:var(--mono);font-weight:600;margin-bottom:3px}
|
||||
.skill-cat{font-size:9px;color:var(--mu);text-transform:uppercase}
|
||||
|
||||
/* Production OSS */
|
||||
.prod-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:10px;margin-top:10px}
|
||||
.prod-item{background:var(--bg2);border:1px solid var(--gn);border-radius:var(--r1);padding:12px 14px;position:relative}
|
||||
.prod-item::before{content:'';position:absolute;top:6px;right:6px;width:8px;height:8px;background:var(--gn);border-radius:50%;box-shadow:0 0 8px var(--gn);animation:pulse 2s ease-in-out infinite}
|
||||
@keyframes pulse{50%{opacity:.4}}
|
||||
.prod-name{font-family:var(--mono);font-weight:700;font-size:12px;margin-bottom:2px}
|
||||
.prod-info{font-size:10px;color:var(--mu)}
|
||||
|
||||
.loading{text-align:center;padding:40px;color:var(--mu)}
|
||||
.footer-n{font-size:10px;color:var(--mu);text-align:center;margin-top:32px;padding-top:14px;border-top:1px solid var(--bd)}
|
||||
|
||||
@media(max-width:768px){
|
||||
.stats{grid-template-columns:repeat(2,1fr)}
|
||||
.main{padding:14px}
|
||||
.hdr{padding:14px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="hdr">
|
||||
<div class="hdr-left">
|
||||
<div class="hdr-logo">OSS</div>
|
||||
<div>
|
||||
<h1>WEVAL <span>OSS Discovery</span> V77</h1>
|
||||
<div class="hdr-sub">72 tools · 6 178 skills · drill-down par catégorie · Enterprise Model UX</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<a class="btn" href="/weval-technology-platform.html">← WTP</a>
|
||||
<a class="btn" href="/enterprise-model.html">🏢 Enterprise Model</a>
|
||||
<a class="btn btn-ac" href="/agents-archi.html">🤖 Agents Archi</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<!-- KPI Summary -->
|
||||
<div class="stats" id="stats"><div class="loading">Loading...</div></div>
|
||||
|
||||
<!-- Categories drill-down -->
|
||||
<div class="section-title">📦 Catégories (cliquer pour drill-down)</div>
|
||||
<div class="cats" id="cats"></div>
|
||||
|
||||
<!-- Skills Explorer -->
|
||||
<div class="section-title" style="margin-top:32px">🔎 Skills Explorer · 6 178 skills</div>
|
||||
<div class="explorer">
|
||||
<div class="explorer-head">
|
||||
<div class="search-wrap">
|
||||
<span>🔍</span>
|
||||
<input type="text" id="search" placeholder="Search skills by name or category...">
|
||||
</div>
|
||||
<div class="filter-pills" id="filter-pills">
|
||||
<div class="pill active" data-cat="all">All</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="skills-grid" id="skills-grid">
|
||||
<div class="loading">Loading skills...</div>
|
||||
</div>
|
||||
<div style="font-size:10px;color:var(--mu);margin-top:12px;text-align:center">
|
||||
Affichage de <b id="skills-count">0</b> skills · 694 injected in Qdrant vectors (coverage 11.2%)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Production OSS -->
|
||||
<div class="section-title" style="margin-top:32px">🟢 Production OSS (Docker déployé live)</div>
|
||||
<div class="prod-grid" id="prod-grid"></div>
|
||||
|
||||
<div class="footer-n">
|
||||
V77 OSS Discovery enriched · Style Enterprise Model · Doctrine #60 UX premium · Cross-linked with WTP+Enterprise+Archi
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Sample skills list from category names (real skills stored in Qdrant, sample here for UX)
|
||||
const SAMPLE_SKILLS_PER_CAT = {
|
||||
llm_core: ['gpt-wrapper','claude-bridge','mistral-adapter','ollama-local','vllm-server','text-generation-webui','prompt-optimizer','token-counter','llm-proxy','fallback-cascade'],
|
||||
agents: ['dspy-agent','crewai-swarm','langgraph-flow','autogen-team','superclaude-framework','claude-skills','agent-router','mcp-server','tool-dispatcher','skill-injector','task-planner','reasoning-agent','retrieval-agent','code-agent'],
|
||||
automation: ['n8n-workflow','activepieces-flow','temporal-pipeline','cron-scheduler','webhook-dispatch','event-router'],
|
||||
observability: ['grafana-dash','prometheus-scrape','loki-logs','langfuse-trace','uptime-kuma','opentelemetry-tracer'],
|
||||
dev_tools: ['claude-code','vscode-ext','docker-compose','git-hooks','jetbrains-plugin','eslint-config','prettier-setup','typescript-types','pytest-runner','jest-suite'],
|
||||
rag_vector: ['qdrant-client','milvus-index','pinecone-wrapper','weaviate-client','chroma-store','embeddings-batch','semantic-search','hybrid-rerank'],
|
||||
security: ['crowdsec-rules','nuclei-templates','keycloak-sso','vault-secrets','fail2ban-config','auth-middleware'],
|
||||
weval_own: ['wevia-master','wevia-brain','weval-l99','weval-nonreg','wevads-arsenal','ethica-pipeline','paperclip-db','deerflow','mirofish','blade-ops','sentinel-lite','dynamic-resolver']
|
||||
};
|
||||
|
||||
let ALL_DATA = null;
|
||||
let FILTER_CAT = 'all';
|
||||
let SEARCH = '';
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
const r = await fetch('/api/v77-oss-discovery-enriched.php?t=' + Date.now());
|
||||
ALL_DATA = await r.json();
|
||||
renderStats();
|
||||
renderCategories();
|
||||
renderSkills();
|
||||
renderProduction();
|
||||
} catch(e) {
|
||||
document.getElementById('stats').innerHTML = '<div class="loading" style="color:#ef4444">Error loading: ' + e.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderStats() {
|
||||
const s = ALL_DATA.summary;
|
||||
document.getElementById('stats').innerHTML = `
|
||||
<div class="stat s-ac"><div class="st-l">Total tools</div><div class="st-v">${s.total_tools}</div><div class="st-s">OSS integrated in stack</div></div>
|
||||
<div class="stat s-gn"><div class="st-l">Wired</div><div class="st-v">${s.wired_tools}/${s.total_tools}</div><div class="st-s">All connected to WEVIA</div></div>
|
||||
<div class="stat s-cy"><div class="st-l">Total skills</div><div class="st-v">${s.total_skills.toLocaleString()}</div><div class="st-s">Across 72 tools</div></div>
|
||||
<div class="stat s-bl"><div class="st-l">Injected in RAG</div><div class="st-v">${s.injected_skills.toLocaleString()}</div><div class="st-s">Qdrant vectors live</div></div>
|
||||
<div class="stat s-pk"><div class="st-l">Coverage</div><div class="st-v">${s.coverage_pct}%</div><div class="st-s">Injected / Total</div></div>
|
||||
<div class="stat s-gn"><div class="st-l">With Docker</div><div class="st-v">${s.with_docker}</div><div class="st-s">Production-ready</div></div>
|
||||
<div class="stat s-ac"><div class="st-l">With README</div><div class="st-v">${s.with_readme}</div><div class="st-s">Documented</div></div>
|
||||
<div class="stat s-cy"><div class="st-l">Production</div><div class="st-v">${s.production_count}</div><div class="st-s">Docker + wired live</div></div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderCategories() {
|
||||
const pills = document.getElementById('filter-pills');
|
||||
const cats = document.getElementById('cats');
|
||||
cats.innerHTML = '';
|
||||
// Add filter pills
|
||||
const existingPills = pills.querySelectorAll('.pill:not([data-cat="all"])');
|
||||
existingPills.forEach(p => p.remove());
|
||||
|
||||
ALL_DATA.categories.forEach((c, i) => {
|
||||
// Category card
|
||||
const el = document.createElement('div');
|
||||
el.className = 'cat';
|
||||
el.dataset.catIdx = i;
|
||||
el.innerHTML = `
|
||||
<div class="cat-strip" style="background:${c.color}"></div>
|
||||
<div class="cat-head">
|
||||
<div class="cat-emoji">${c.emoji}</div>
|
||||
<div class="cat-name">${c.label}</div>
|
||||
<div class="cat-count" style="color:${c.color}">${c.count}</div>
|
||||
</div>
|
||||
<div class="cat-stats">
|
||||
<div class="cat-stat"><b>${c.total_files}</b>files</div>
|
||||
<div class="cat-stat"><b>${c.est_skills.toLocaleString()}</b>skills est.</div>
|
||||
<div class="cat-stat"><b>${c.est_injected}</b>injected</div>
|
||||
</div>
|
||||
<div class="cat-cov">
|
||||
<div class="cat-cov-bar"><div class="cat-cov-fill" style="width:${c.coverage_pct}%;background:linear-gradient(90deg,${c.color},var(--cy))"></div></div>
|
||||
<div class="cat-cov-label"><span>Coverage</span><span>${c.coverage_pct}%</span></div>
|
||||
</div>
|
||||
<div class="drill">
|
||||
<div style="font-size:10px;color:var(--mu);text-transform:uppercase;margin-bottom:6px;font-weight:700">Tools (${c.tools.length})</div>
|
||||
${c.tools.map(t => `
|
||||
<div class="tool-row">
|
||||
<div class="tool-name">${t.name}</div>
|
||||
<div class="tool-badges">
|
||||
<span class="tool-badge tb-files">${t.files}f</span>
|
||||
${t.has_docker ? '<span class="tool-badge tb-docker">🐳</span>' : ''}
|
||||
${t.has_readme ? '<span class="tool-badge tb-readme">📖</span>' : ''}
|
||||
${t.wired ? '<span class="tool-badge tb-wired">⚡</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
el.onclick = (ev) => {
|
||||
if (ev.target.closest('.tool-row')) return;
|
||||
el.classList.toggle('open');
|
||||
};
|
||||
cats.appendChild(el);
|
||||
|
||||
// Filter pill
|
||||
const p = document.createElement('div');
|
||||
p.className = 'pill';
|
||||
p.dataset.cat = Object.keys({llm_core:1,agents:1,automation:1,observability:1,dev_tools:1,rag_vector:1,security:1,weval_own:1})[i];
|
||||
p.textContent = c.emoji + ' ' + c.label;
|
||||
p.onclick = () => {
|
||||
document.querySelectorAll('.pill').forEach(x => x.classList.remove('active'));
|
||||
p.classList.add('active');
|
||||
FILTER_CAT = p.dataset.cat;
|
||||
renderSkills();
|
||||
};
|
||||
pills.appendChild(p);
|
||||
});
|
||||
}
|
||||
|
||||
function renderSkills() {
|
||||
const grid = document.getElementById('skills-grid');
|
||||
const catKeys = ['llm_core','agents','automation','observability','dev_tools','rag_vector','security','weval_own'];
|
||||
let items = [];
|
||||
catKeys.forEach((k,i) => {
|
||||
(SAMPLE_SKILLS_PER_CAT[k]||[]).forEach(s => {
|
||||
items.push({ name: s, cat: k, catLabel: ALL_DATA.categories[i]?.label || k, color: ALL_DATA.categories[i]?.color || '#64748b' });
|
||||
});
|
||||
});
|
||||
// Filter
|
||||
if (FILTER_CAT !== 'all') items = items.filter(x => x.cat === FILTER_CAT);
|
||||
if (SEARCH) items = items.filter(x => x.name.toLowerCase().includes(SEARCH) || x.catLabel.toLowerCase().includes(SEARCH));
|
||||
|
||||
document.getElementById('skills-count').textContent = items.length;
|
||||
grid.innerHTML = items.map(x => `
|
||||
<div class="skill-item" style="border-left:3px solid ${x.color}">
|
||||
<div class="skill-name">${x.name}</div>
|
||||
<div class="skill-cat">${x.catLabel}</div>
|
||||
</div>
|
||||
`).join('') || '<div class="loading">No skills match filter</div>';
|
||||
}
|
||||
|
||||
function renderProduction() {
|
||||
const grid = document.getElementById('prod-grid');
|
||||
const CAT_EMOJI = {llm_core:'🧠',agents:'🤖',automation:'⚡',observability:'👁️',dev_tools:'🛠️',rag_vector:'🔍',security:'🛡️',weval_own:'👑'};
|
||||
grid.innerHTML = ALL_DATA.production_tools.map(t => `
|
||||
<div class="prod-item">
|
||||
<div class="prod-name">${CAT_EMOJI[t.category]||'📦'} ${t.name}</div>
|
||||
<div class="prod-info">${t.files} files · ${t.category.replace('_',' ')}</div>
|
||||
</div>
|
||||
`).join('') || '<div class="loading">No production tools</div>';
|
||||
}
|
||||
|
||||
document.getElementById('search').addEventListener('input', e => {
|
||||
SEARCH = e.target.value.toLowerCase();
|
||||
renderSkills();
|
||||
});
|
||||
|
||||
load();
|
||||
setInterval(load, 60000);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user