654 lines
27 KiB
HTML
654 lines
27 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr" data-theme="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Release Train · WEVAL Technology Platform</title>
|
||
<meta name="description" content="Release Train Dashboard - Commits, Phases, Waves, Doctrines, Intents, Coverage - Multi-Claude reconciliation live">
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root{
|
||
--bg-0:#05060a;--bg-1:#0a0c14;--bg-2:#11141f;--bg-3:#181c2b;--bg-card:#0e111c;
|
||
--border:#1f2436;--border-hover:#3a425f;
|
||
--text-0:#f1f5f9;--text-1:#cbd5e1;--text-2:#94a3b8;--text-3:#64748b;
|
||
--accent:#6366f1;--accent-hover:#818cf8;
|
||
--success:#10b981;--warning:#f59e0b;--danger:#ef4444;--info:#06b6d4;
|
||
--gold:#f6d572;--mint:#5cdb95;--violet:#a78bfa;--coral:#ff6b6b;--cyan:#4ecdc4;
|
||
--shadow-lg:0 16px 48px rgba(99,102,241,.2);
|
||
--radius:14px;--radius-sm:10px;
|
||
--trans:.18s cubic-bezier(.4,0,.2,1);
|
||
--font-sans:"Inter",-apple-system,system-ui,sans-serif;
|
||
--font-mono:"JetBrains Mono",Monaco,monospace;
|
||
}
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{
|
||
background:var(--bg-0);
|
||
color:var(--text-0);
|
||
font-family:var(--font-sans);
|
||
min-height:100vh;
|
||
background-image:
|
||
radial-gradient(ellipse at 15% 20%,rgba(99,102,241,.08) 0%,transparent 60%),
|
||
radial-gradient(ellipse at 85% 80%,rgba(6,182,212,.06) 0%,transparent 60%),
|
||
radial-gradient(ellipse at 50% 50%,rgba(167,139,250,.04) 0%,transparent 70%);
|
||
overflow-x:hidden;
|
||
}
|
||
|
||
/* ================ HEADER ================ */
|
||
header.topbar{
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
padding:14px 28px;
|
||
border-bottom:1px solid var(--border);
|
||
background:rgba(10,12,20,.85);
|
||
backdrop-filter:blur(24px);
|
||
position:sticky;top:0;z-index:100;
|
||
}
|
||
.brand{display:flex;align-items:center;gap:14px}
|
||
.brand-logo{
|
||
width:38px;height:38px;
|
||
border-radius:10px;
|
||
background:linear-gradient(135deg,var(--accent),var(--violet));
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-weight:900;font-size:18px;color:#fff;
|
||
box-shadow:0 4px 20px rgba(99,102,241,.4);
|
||
}
|
||
.brand-text{display:flex;flex-direction:column}
|
||
.brand-title{font-size:14px;font-weight:700;letter-spacing:.08em;color:var(--text-0)}
|
||
.brand-sub{font-size:10px;letter-spacing:.2em;color:var(--text-3);text-transform:uppercase;font-weight:600}
|
||
|
||
.header-crumbs{display:flex;align-items:center;gap:8px;color:var(--text-2);font-size:12px}
|
||
.header-crumbs a{color:var(--accent-hover);text-decoration:none;font-weight:500}
|
||
.header-crumbs a:hover{color:var(--text-0)}
|
||
|
||
.health-bar{display:flex;gap:18px;align-items:center}
|
||
.h-item{
|
||
display:flex;align-items:center;gap:7px;
|
||
font-size:11px;letter-spacing:.06em;
|
||
color:var(--text-2);font-weight:500;
|
||
font-family:var(--font-mono);
|
||
}
|
||
.h-dot{width:7px;height:7px;border-radius:50%;display:inline-block}
|
||
.h-dot.ok{background:var(--mint);box-shadow:0 0 8px var(--mint)}
|
||
.h-dot.warn{background:var(--gold);box-shadow:0 0 8px var(--gold)}
|
||
.h-dot.err{background:var(--coral);box-shadow:0 0 8px var(--coral)}
|
||
.h-val{color:var(--text-0);font-weight:700}
|
||
|
||
/* ================ LAYOUT ================ */
|
||
.page{
|
||
display:grid;
|
||
grid-template-columns:1fr;
|
||
max-width:1680px;
|
||
margin:0 auto;
|
||
padding:28px;
|
||
gap:24px;
|
||
}
|
||
|
||
/* PAGE HEAD */
|
||
.page-head{
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
padding-bottom:8px;
|
||
}
|
||
.page-title-wrap{display:flex;align-items:center;gap:18px}
|
||
.page-icon{
|
||
width:52px;height:52px;
|
||
border-radius:12px;
|
||
background:linear-gradient(135deg,rgba(99,102,241,.2),rgba(6,182,212,.15));
|
||
border:1px solid rgba(99,102,241,.3);
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:24px;
|
||
}
|
||
.page-title{font-size:26px;font-weight:800;letter-spacing:-.02em;color:var(--text-0)}
|
||
.page-sub{font-size:13px;color:var(--text-2);margin-top:3px;font-weight:500}
|
||
.page-actions{display:flex;gap:10px}
|
||
.btn{
|
||
padding:10px 16px;border-radius:10px;
|
||
background:var(--bg-2);border:1px solid var(--border);color:var(--text-1);
|
||
font-size:12px;font-weight:600;letter-spacing:.04em;
|
||
cursor:pointer;transition:var(--trans);
|
||
display:flex;align-items:center;gap:7px;
|
||
font-family:var(--font-sans);
|
||
}
|
||
.btn:hover{background:var(--bg-3);border-color:var(--border-hover);color:var(--text-0)}
|
||
.btn-primary{background:linear-gradient(135deg,var(--accent),var(--violet));border:none;color:#fff}
|
||
.btn-primary:hover{opacity:.9;transform:translateY(-1px)}
|
||
|
||
/* ================ STATS GRID ================ */
|
||
.stats-grid{
|
||
display:grid;
|
||
grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
|
||
gap:18px;
|
||
}
|
||
.stat-card{
|
||
background:linear-gradient(135deg,var(--bg-card),var(--bg-1));
|
||
border:1px solid var(--border);
|
||
border-radius:var(--radius);
|
||
padding:22px 24px;
|
||
position:relative;overflow:hidden;
|
||
transition:var(--trans);
|
||
}
|
||
.stat-card:hover{border-color:var(--border-hover);transform:translateY(-2px);box-shadow:var(--shadow-lg)}
|
||
.stat-card::before{
|
||
content:"";position:absolute;top:0;left:0;right:0;height:3px;
|
||
background:linear-gradient(90deg,var(--accent),var(--violet));
|
||
opacity:.5;
|
||
}
|
||
.stat-card.mint::before{background:linear-gradient(90deg,var(--mint),var(--success))}
|
||
.stat-card.gold::before{background:linear-gradient(90deg,var(--gold),var(--warning))}
|
||
.stat-card.cyan::before{background:linear-gradient(90deg,var(--cyan),var(--info))}
|
||
.stat-card.coral::before{background:linear-gradient(90deg,var(--coral),var(--danger))}
|
||
|
||
.stat-label{font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--text-3);font-weight:700;margin-bottom:10px}
|
||
.stat-value{font-size:34px;font-weight:900;letter-spacing:-.03em;color:var(--text-0);line-height:1;font-family:var(--font-mono)}
|
||
.stat-sub{font-size:11px;color:var(--text-2);margin-top:7px;font-weight:500}
|
||
.stat-delta{
|
||
display:inline-flex;align-items:center;gap:4px;
|
||
padding:2px 8px;border-radius:100px;
|
||
background:rgba(16,185,129,.1);color:var(--mint);
|
||
font-size:10px;font-weight:700;
|
||
margin-left:8px;
|
||
}
|
||
|
||
/* ================ SECTION ================ */
|
||
.section{
|
||
background:var(--bg-card);border:1px solid var(--border);
|
||
border-radius:var(--radius);overflow:hidden;
|
||
}
|
||
.section-head{
|
||
padding:18px 24px;border-bottom:1px solid var(--border);
|
||
display:flex;align-items:center;justify-content:space-between;
|
||
background:linear-gradient(180deg,rgba(99,102,241,.04),transparent);
|
||
}
|
||
.section-title{font-size:14px;font-weight:700;letter-spacing:.02em;color:var(--text-0);display:flex;align-items:center;gap:10px}
|
||
.section-title::before{content:"";width:3px;height:18px;background:var(--accent);border-radius:2px}
|
||
.section-meta{font-size:11px;color:var(--text-3);font-family:var(--font-mono)}
|
||
.section-body{padding:20px 24px}
|
||
|
||
/* ================ TWO-COL ================ */
|
||
.two-col{display:grid;grid-template-columns:1.6fr 1fr;gap:24px}
|
||
@media (max-width:1200px){.two-col{grid-template-columns:1fr}}
|
||
|
||
/* ================ TIMELINE ================ */
|
||
.timeline{position:relative;padding-left:30px}
|
||
.timeline::before{
|
||
content:"";position:absolute;left:10px;top:8px;bottom:8px;width:2px;
|
||
background:linear-gradient(180deg,var(--accent),var(--violet),transparent);
|
||
}
|
||
.tl-item{
|
||
position:relative;padding:14px 0 16px 18px;
|
||
border-bottom:1px dashed var(--border);
|
||
}
|
||
.tl-item:last-child{border-bottom:none}
|
||
.tl-item::before{
|
||
content:"";position:absolute;left:-25px;top:22px;
|
||
width:11px;height:11px;border-radius:50%;
|
||
background:var(--accent);
|
||
box-shadow:0 0 0 3px var(--bg-card),0 0 0 4px var(--accent);
|
||
}
|
||
.tl-item.milestone::before{background:var(--gold);box-shadow:0 0 0 3px var(--bg-card),0 0 0 4px var(--gold),0 0 14px var(--gold)}
|
||
.tl-item.feat::before{background:var(--mint);box-shadow:0 0 0 3px var(--bg-card),0 0 0 4px var(--mint)}
|
||
.tl-item.fix::before{background:var(--coral);box-shadow:0 0 0 3px var(--bg-card),0 0 0 4px var(--coral)}
|
||
.tl-item.sync::before{background:var(--text-3);box-shadow:0 0 0 3px var(--bg-card),0 0 0 4px var(--text-3)}
|
||
|
||
.tl-head{display:flex;align-items:center;gap:10px;margin-bottom:5px;flex-wrap:wrap}
|
||
.tl-sha{font-family:var(--font-mono);font-size:10px;color:var(--text-3);font-weight:600;background:var(--bg-2);padding:2px 7px;border-radius:4px}
|
||
.tl-ts{font-size:10px;color:var(--text-3);font-family:var(--font-mono)}
|
||
.tl-tag{font-size:9px;font-weight:700;padding:2px 7px;border-radius:100px;text-transform:uppercase;letter-spacing:.08em}
|
||
.tl-tag.phase{background:rgba(99,102,241,.15);color:var(--accent-hover);border:1px solid rgba(99,102,241,.3)}
|
||
.tl-tag.wave{background:rgba(6,182,212,.15);color:var(--cyan);border:1px solid rgba(6,182,212,.3)}
|
||
.tl-tag.doctrine{background:rgba(246,213,114,.15);color:var(--gold);border:1px solid rgba(246,213,114,.3)}
|
||
.tl-subject{font-size:12.5px;color:var(--text-1);line-height:1.5;word-break:break-word}
|
||
|
||
/* ================ CHART DONUT ================ */
|
||
.chart-wrap{padding:16px 0;display:flex;flex-direction:column;align-items:center}
|
||
.donut-row{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;width:100%;margin-top:10px}
|
||
.donut-card{background:var(--bg-2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;text-align:center}
|
||
.donut-val{font-size:28px;font-weight:900;color:var(--text-0);font-family:var(--font-mono);line-height:1}
|
||
.donut-label{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--text-3);margin-top:6px;font-weight:700}
|
||
|
||
/* ================ BAR CHART ================ */
|
||
.barchart{display:flex;align-items:flex-end;gap:6px;height:180px;padding:16px 0 8px;border-bottom:1px dashed var(--border);margin-bottom:10px}
|
||
.bar-col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px}
|
||
.bar{
|
||
width:100%;background:linear-gradient(180deg,var(--accent),var(--violet));
|
||
border-radius:4px 4px 0 0;min-height:4px;
|
||
transition:all .3s ease;cursor:pointer;
|
||
position:relative;
|
||
}
|
||
.bar:hover{opacity:.8;transform:scaleY(1.05)}
|
||
.bar-val{font-size:10px;color:var(--text-2);font-family:var(--font-mono);font-weight:700;position:absolute;top:-18px;left:50%;transform:translateX(-50%)}
|
||
.bar-label{font-size:10px;color:var(--text-3);font-family:var(--font-mono)}
|
||
|
||
/* ================ PROGRESS ================ */
|
||
.progress-wrap{margin:16px 0}
|
||
.progress-head{display:flex;justify-content:space-between;margin-bottom:8px}
|
||
.progress-name{font-size:12px;font-weight:600;color:var(--text-1)}
|
||
.progress-val{font-size:12px;font-family:var(--font-mono);color:var(--text-0);font-weight:700}
|
||
.progress-track{height:10px;background:var(--bg-2);border-radius:100px;overflow:hidden;border:1px solid var(--border)}
|
||
.progress-fill{
|
||
height:100%;
|
||
background:linear-gradient(90deg,var(--mint),var(--cyan),var(--accent));
|
||
border-radius:100px;
|
||
transition:width 1s cubic-bezier(.4,0,.2,1);
|
||
box-shadow:0 0 20px rgba(92,219,149,.4);
|
||
}
|
||
|
||
/* ================ TAGS GRID ================ */
|
||
.tags-grid{display:flex;flex-wrap:wrap;gap:8px;padding:4px 0}
|
||
.tag-chip{
|
||
padding:6px 12px;border-radius:100px;
|
||
font-size:11px;font-weight:600;font-family:var(--font-mono);
|
||
background:var(--bg-2);border:1px solid var(--border);color:var(--text-1);
|
||
display:flex;align-items:center;gap:6px;
|
||
transition:var(--trans);cursor:default;
|
||
}
|
||
.tag-chip:hover{border-color:var(--border-hover);transform:translateY(-1px)}
|
||
.tag-chip.phase{background:rgba(99,102,241,.08);border-color:rgba(99,102,241,.25);color:var(--accent-hover)}
|
||
.tag-chip.wave{background:rgba(6,182,212,.08);border-color:rgba(6,182,212,.25);color:var(--cyan)}
|
||
.tag-chip.doctrine{background:rgba(246,213,114,.08);border-color:rgba(246,213,114,.25);color:var(--gold)}
|
||
.tag-chip .count{background:rgba(255,255,255,.08);padding:1px 6px;border-radius:4px;font-size:9px;color:var(--text-0);font-weight:800}
|
||
|
||
/* ================ LEGEND ================ */
|
||
.legend{display:flex;gap:16px;margin:12px 0;font-size:11px;flex-wrap:wrap}
|
||
.leg-item{display:flex;align-items:center;gap:6px;color:var(--text-2)}
|
||
.leg-dot{width:10px;height:10px;border-radius:50%}
|
||
|
||
/* ================ FOOTER ================ */
|
||
footer{
|
||
text-align:center;padding:24px;
|
||
color:var(--text-3);font-size:11px;font-family:var(--font-mono);
|
||
border-top:1px solid var(--border);margin-top:24px;
|
||
}
|
||
footer a{color:var(--accent-hover);text-decoration:none}
|
||
|
||
/* ================ ANIMATIONS ================ */
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||
.pulse{animation:pulse 2s ease-in-out infinite}
|
||
|
||
@keyframes slideUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:none}}
|
||
.stat-card,.section{animation:slideUp .5s ease-out backwards}
|
||
.stat-card:nth-child(1){animation-delay:.05s}
|
||
.stat-card:nth-child(2){animation-delay:.1s}
|
||
.stat-card:nth-child(3){animation-delay:.15s}
|
||
.stat-card:nth-child(4){animation-delay:.2s}
|
||
.stat-card:nth-child(5){animation-delay:.25s}
|
||
.stat-card:nth-child(6){animation-delay:.3s}
|
||
|
||
@media (max-width:720px){
|
||
.page{padding:14px}
|
||
.page-head{flex-direction:column;align-items:flex-start;gap:12px}
|
||
.health-bar{display:none}
|
||
.stats-grid{grid-template-columns:repeat(2,1fr);gap:10px}
|
||
.stat-card{padding:14px}
|
||
.stat-value{font-size:24px}
|
||
}
|
||
</style>
|
||
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-154324 -->
|
||
<style id="doctrine60-ux-direct">
|
||
|
||
/* DOCTRINE-60-UX-ENRICH injected-direct */
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
.card, .kpi, .panel, .btn {
|
||
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
|
||
}
|
||
.card:hover, .kpi:hover, .panel:hover {
|
||
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
|
||
border-color: rgba(100,180,255,0.5);
|
||
}
|
||
@keyframes pulseD60 {
|
||
0%,100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.7; transform: scale(1.05); }
|
||
}
|
||
.pulse, .live-indicator, .active, .online {
|
||
animation: pulseD60 3s ease-in-out infinite;
|
||
}
|
||
.modal, .chat, .speech, .overlay {
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
}
|
||
.enter-stagger {
|
||
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
|
||
}
|
||
@keyframes enterStagD60 {
|
||
from { opacity: 0; transform: translateY(20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
|
||
/* ===== WEVIA-OVERLAP-FIX-24AVR-PAGES - reserve top-right for nginx Logout ===== */
|
||
header.topbar{padding-right:130px !important}
|
||
#weval-global-logout{top:10px !important;right:12px !important;z-index:99995 !important;opacity:0.6 !important}
|
||
#weval-global-logout:hover{opacity:1 !important}
|
||
@media (max-width:900px){header.topbar .header-crumbs{display:none !important}}
|
||
/* ===== END WEVIA-OVERLAP-FIX-24AVR-PAGES ===== */
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="topbar">
|
||
<div class="brand">
|
||
<div class="brand-logo">W</div>
|
||
<div class="brand-text">
|
||
<span class="brand-title">WEVAL Technology Platform</span>
|
||
<span class="brand-sub">Release Train · Live</span>
|
||
</div>
|
||
</div>
|
||
<div class="header-crumbs">
|
||
<a href="/weval-technology-platform.html">WTP</a>
|
||
<span>›</span>
|
||
<a href="/wevia-cockpit.html">Cockpit</a>
|
||
<span>›</span>
|
||
<span style="color:var(--text-0);font-weight:600">Release Train</span>
|
||
</div>
|
||
<div class="health-bar" id="health-bar">
|
||
<div class="h-item"><span class="h-dot ok pulse"></span><span>LIVE</span></div>
|
||
<div class="h-item">S204 · <span class="h-val" id="h-load">—</span></div>
|
||
<div class="h-item">Intents · <span class="h-val" id="h-intents">—</span></div>
|
||
<div class="h-item">Coverage · <span class="h-val" id="h-coverage">—</span></div>
|
||
<div class="h-item">Commits 24h · <span class="h-val" id="h-commits">—</span></div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="page">
|
||
|
||
<!-- PAGE HEAD -->
|
||
<div class="page-head">
|
||
<div class="page-title-wrap">
|
||
<div class="page-icon">🚂</div>
|
||
<div>
|
||
<h1 class="page-title">Release Train Dashboard</h1>
|
||
<p class="page-sub">Multi-Claude reconciliation · Phases · Waves · Doctrines · Intents · Coverage UX</p>
|
||
</div>
|
||
</div>
|
||
<div class="page-actions">
|
||
<button class="btn" onclick="loadData()">
|
||
<span id="refresh-icon">⟳</span> Refresh
|
||
</button>
|
||
<a href="/wevia-cockpit.html" class="btn btn-primary">
|
||
⚡ Cockpit
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- KPI STATS -->
|
||
<div class="stats-grid" id="stats-grid">
|
||
<div class="stat-card">
|
||
<div class="stat-label">📦 Commits 24h</div>
|
||
<div class="stat-value" id="kpi-commits">—</div>
|
||
<div class="stat-sub">auto-sync + features + fixes</div>
|
||
</div>
|
||
<div class="stat-card mint">
|
||
<div class="stat-label">🎯 Milestones</div>
|
||
<div class="stat-value" id="kpi-milestones">—</div>
|
||
<div class="stat-sub">phases + waves significatives</div>
|
||
</div>
|
||
<div class="stat-card cyan">
|
||
<div class="stat-label">🤖 Intents</div>
|
||
<div class="stat-value" id="kpi-intents">—</div>
|
||
<div class="stat-sub">dernière progression</div>
|
||
</div>
|
||
<div class="stat-card gold">
|
||
<div class="stat-label">📐 Pages UX</div>
|
||
<div class="stat-value" id="kpi-coverage">—</div>
|
||
<div class="stat-sub" id="kpi-coverage-sub">—</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-label">🏗 Phases</div>
|
||
<div class="stat-value" id="kpi-phases">—</div>
|
||
<div class="stat-sub">uniques 24h</div>
|
||
</div>
|
||
<div class="stat-card coral">
|
||
<div class="stat-label">📜 Doctrines</div>
|
||
<div class="stat-value" id="kpi-doctrines">—</div>
|
||
<div class="stat-sub">numérotées</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- COVERAGE PROGRESS -->
|
||
<div class="section">
|
||
<div class="section-head">
|
||
<div class="section-title">Coverage UX Doctrine 60</div>
|
||
<div class="section-meta" id="cov-meta">—</div>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="progress-wrap">
|
||
<div class="progress-head">
|
||
<span class="progress-name">Pages enrichies (cascade async)</span>
|
||
<span class="progress-val" id="progress-val">0%</span>
|
||
</div>
|
||
<div class="progress-track">
|
||
<div class="progress-fill" id="progress-fill" style="width:0%"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 2-COL: TIMELINE + DISTRIBUTION -->
|
||
<div class="two-col">
|
||
|
||
<!-- TIMELINE MILESTONES -->
|
||
<div class="section">
|
||
<div class="section-head">
|
||
<div class="section-title">Timeline Milestones</div>
|
||
<div class="legend">
|
||
<div class="leg-item"><span class="leg-dot" style="background:var(--gold)"></span>Milestone</div>
|
||
<div class="leg-item"><span class="leg-dot" style="background:var(--mint)"></span>Feature</div>
|
||
<div class="leg-item"><span class="leg-dot" style="background:var(--coral)"></span>Fix</div>
|
||
<div class="leg-item"><span class="leg-dot" style="background:var(--text-3)"></span>Auto-sync</div>
|
||
</div>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="timeline" id="timeline">
|
||
<div style="color:var(--text-3);text-align:center;padding:40px;font-family:var(--font-mono);font-size:12px">Chargement timeline…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- RIGHT COL -->
|
||
<div style="display:flex;flex-direction:column;gap:24px">
|
||
|
||
<!-- DONUT DISTRIBUTION -->
|
||
<div class="section">
|
||
<div class="section-head">
|
||
<div class="section-title">Répartition Commits</div>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="donut-row">
|
||
<div class="donut-card">
|
||
<div class="donut-val" id="d-feat">—</div>
|
||
<div class="donut-label" style="color:var(--mint)">Features</div>
|
||
</div>
|
||
<div class="donut-card">
|
||
<div class="donut-val" id="d-fix">—</div>
|
||
<div class="donut-label" style="color:var(--coral)">Fixes</div>
|
||
</div>
|
||
<div class="donut-card">
|
||
<div class="donut-val" id="d-sync">—</div>
|
||
<div class="donut-label" style="color:var(--text-3)">Auto-sync</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- HOURLY BARCHART -->
|
||
<div class="section">
|
||
<div class="section-head">
|
||
<div class="section-title">Distribution Horaire</div>
|
||
<div class="section-meta">24h · CET</div>
|
||
</div>
|
||
<div class="section-body">
|
||
<div class="barchart" id="barchart">
|
||
<div style="color:var(--text-3);font-size:11px;font-family:var(--font-mono);margin:auto">Chargement…</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- PHASES TAGS -->
|
||
<div class="section">
|
||
<div class="section-head">
|
||
<div class="section-title">Phases · Waves · Doctrines</div>
|
||
</div>
|
||
<div class="section-body">
|
||
<div style="font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--text-3);font-weight:700;margin-bottom:10px">PHASES</div>
|
||
<div class="tags-grid" id="tags-phases"></div>
|
||
<div style="font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--text-3);font-weight:700;margin:16px 0 10px">WAVES</div>
|
||
<div class="tags-grid" id="tags-waves"></div>
|
||
<div style="font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--text-3);font-weight:700;margin:16px 0 10px">DOCTRINES</div>
|
||
<div class="tags-grid" id="tags-doctrines"></div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<footer>
|
||
Release Train · WEVAL Technology Platform · Multi-Claude Convergence · Live Data · <span id="gen-at">—</span>
|
||
· <a href="/api/release-train-data.json" target="_blank">raw JSON</a>
|
||
</footer>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
// ============= Data Loading =============
|
||
async function loadData() {
|
||
const icon = document.getElementById("refresh-icon");
|
||
icon.style.animation = "pulse 1s infinite";
|
||
try {
|
||
const res = await fetch("/api/release-train-data.json?t=" + Date.now(), {cache:"no-store"});
|
||
const d = await res.json();
|
||
render(d);
|
||
// Health bar
|
||
document.getElementById("h-intents").textContent = d.stats.last_intent_count || "—";
|
||
document.getElementById("h-coverage").textContent = d.stats.last_coverage ? d.stats.last_coverage.pct + "%" : "—";
|
||
document.getElementById("h-commits").textContent = d.stats.total_commits_24h;
|
||
fetchLoad();
|
||
document.getElementById("gen-at").textContent = new Date(d.generated_at).toLocaleString("fr-FR");
|
||
} catch(e) {
|
||
console.error("Load fail", e);
|
||
} finally {
|
||
icon.style.animation = "";
|
||
}
|
||
}
|
||
|
||
async function fetchLoad() {
|
||
try {
|
||
const res = await fetch("/api/infra-dashboard-api.php?t=" + Date.now(), {cache:"no-store", signal:AbortSignal.timeout(5000)});
|
||
const d = await res.json();
|
||
const load = d.load_1m || d.load || null;
|
||
document.getElementById("h-load").textContent = load ? load.toFixed(1) : "—";
|
||
} catch(e) {
|
||
document.getElementById("h-load").textContent = "—";
|
||
}
|
||
}
|
||
|
||
function render(d) {
|
||
// KPIs
|
||
document.getElementById("kpi-commits").textContent = d.stats.total_commits_24h;
|
||
document.getElementById("kpi-milestones").textContent = d.stats.milestones_24h;
|
||
document.getElementById("kpi-intents").textContent = d.stats.last_intent_count;
|
||
if (d.stats.last_coverage) {
|
||
document.getElementById("kpi-coverage").textContent = d.stats.last_coverage.pct + "%";
|
||
document.getElementById("kpi-coverage-sub").textContent = `${d.stats.last_coverage.num}/${d.stats.last_coverage.total}`;
|
||
document.getElementById("cov-meta").textContent = `${d.stats.last_coverage.num} / ${d.stats.last_coverage.total} pages`;
|
||
document.getElementById("progress-val").textContent = d.stats.last_coverage.pct + "%";
|
||
setTimeout(() => {
|
||
document.getElementById("progress-fill").style.width = d.stats.last_coverage.pct + "%";
|
||
}, 200);
|
||
}
|
||
document.getElementById("kpi-phases").textContent = d.stats.unique_phases;
|
||
document.getElementById("kpi-doctrines").textContent = d.stats.unique_doctrines;
|
||
|
||
// Donut
|
||
document.getElementById("d-feat").textContent = d.stats.features;
|
||
document.getElementById("d-fix").textContent = d.stats.fixes;
|
||
document.getElementById("d-sync").textContent = d.stats.auto_sync;
|
||
|
||
// Timeline milestones
|
||
const tl = document.getElementById("timeline");
|
||
tl.innerHTML = "";
|
||
(d.milestone_commits || []).slice(0, 25).forEach(c => {
|
||
const cls = c.milestone ? "milestone" : (c.type === "fix" ? "fix" : (c.type === "auto-sync" ? "sync" : "feat"));
|
||
const tags = [];
|
||
if (c.phase) tags.push(`<span class="tl-tag phase">phase ${c.phase}</span>`);
|
||
if (c.wave) tags.push(`<span class="tl-tag wave">wave ${c.wave}</span>`);
|
||
if (c.doctrine) tags.push(`<span class="tl-tag doctrine">D${c.doctrine}</span>`);
|
||
const dt = new Date(c.ts);
|
||
const tsStr = dt.toLocaleTimeString("fr-FR",{hour:"2-digit",minute:"2-digit"});
|
||
const subjTrim = c.subject.length > 180 ? c.subject.slice(0,180) + "…" : c.subject;
|
||
tl.insertAdjacentHTML("beforeend",
|
||
`<div class="tl-item ${cls}">
|
||
<div class="tl-head">
|
||
<span class="tl-sha">${c.sha}</span>
|
||
<span class="tl-ts">${tsStr}</span>
|
||
${tags.join("")}
|
||
</div>
|
||
<div class="tl-subject">${escapeHtml(subjTrim)}</div>
|
||
</div>`
|
||
);
|
||
});
|
||
|
||
// Barchart hourly
|
||
const bc = document.getElementById("barchart");
|
||
bc.innerHTML = "";
|
||
const hh = d.hourly_distribution || {};
|
||
const max = Math.max(1, ...Object.values(hh));
|
||
const hours = Object.keys(hh).sort();
|
||
hours.forEach(h => {
|
||
const v = hh[h];
|
||
const pct = (v / max) * 100;
|
||
bc.insertAdjacentHTML("beforeend",
|
||
`<div class="bar-col" title="${h}h: ${v} commits">
|
||
<div class="bar" style="height:${pct}%"><span class="bar-val">${v}</span></div>
|
||
<div class="bar-label">${h}h</div>
|
||
</div>`);
|
||
});
|
||
|
||
// Tags
|
||
const renderTags = (id, data, cls) => {
|
||
const el = document.getElementById(id);
|
||
el.innerHTML = "";
|
||
Object.entries(data).forEach(([k, v]) => {
|
||
el.insertAdjacentHTML("beforeend",
|
||
`<div class="tag-chip ${cls}">${cls === "phase" ? "phase " : (cls === "wave" ? "wave " : "D")}${k}<span class="count">${v}</span></div>`);
|
||
});
|
||
};
|
||
renderTags("tags-phases", d.phases, "phase");
|
||
renderTags("tags-waves", d.waves, "wave");
|
||
renderTags("tags-doctrines", d.doctrines, "doctrine");
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return s.replace(/[<>&"]/g, c => ({'<':'<','>':'>','&':'&','"':'"'}[c]));
|
||
}
|
||
|
||
// Initial load + auto-refresh every 60s
|
||
loadData();
|
||
setInterval(loadData, 60000);
|
||
</script>
|
||
|
||
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
|
||
|
||
// DOCTRINE-60-UX-JS staggered entrance
|
||
(function(){
|
||
if (!('IntersectionObserver' in window)) return;
|
||
const obs = new IntersectionObserver((entries) => {
|
||
entries.forEach((e, i) => {
|
||
if (e.isIntersecting) {
|
||
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
|
||
obs.unobserve(e.target);
|
||
}
|
||
});
|
||
});
|
||
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
|
||
})();
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|