auto-sync via WEVIA git_sync_all intent 2026-04-21T11:28:41+02:00
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
Some checks failed
WEVAL NonReg / nonreg (push) Has been cancelled
This commit is contained in:
BIN
api/blade-tasks/v136-health-modal-proof/01-modal-open.png
Normal file
BIN
api/blade-tasks/v136-health-modal-proof/01-modal-open.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
Binary file not shown.
14
api/blade-tasks/v136-health-modal-proof/proof.json
Normal file
14
api/blade-tasks/v136-health-modal-proof/proof.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"v136": "health-modal-drill-down",
|
||||
"modal_exists": true,
|
||||
"banner_clickable": true,
|
||||
"hidden_initially": true,
|
||||
"opens_on_click": true,
|
||||
"fetches_broken_urls": true,
|
||||
"content_preview": "<div style=\"color:#ef4444;font-weight:600;margin-bottom:6px\">\ud83d\udeab BROKEN/DOWN (20):</div><div style=\"padding:3px 8px;border-left:3px solid #ef4444;margin-bottom:4px;background:rgba(239,68,68,0.05)\"><span",
|
||||
"escape_closes": true,
|
||||
"reopen_works": true,
|
||||
"click_outside_closes": true,
|
||||
"js_errors": [],
|
||||
"VERDICT": "OK"
|
||||
}
|
||||
167
api/kpi-unified.php
Normal file
167
api/kpi-unified.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
// V118 KPI UNIFIED - Single source-of-truth endpoint for all dashboards
|
||||
// Consolidates source-of-truth.json + nonreg-latest + token-health-cache + docker live
|
||||
// Cache 60s for performance
|
||||
// UX premium doctrine 60 - zero doublon zero divergence
|
||||
header("Content-Type: application/json");
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
header("Cache-Control: public, max-age=60");
|
||||
|
||||
$cache_file = "/tmp/kpi-unified-cache.json";
|
||||
$cache_ttl = 60;
|
||||
$force = !empty($_GET["force"]);
|
||||
|
||||
// Try cache first
|
||||
if (!$force && is_readable($cache_file)) {
|
||||
$age = time() - filemtime($cache_file);
|
||||
if ($age < $cache_ttl) {
|
||||
$cached = @json_decode(@file_get_contents($cache_file), true);
|
||||
if (is_array($cached)) {
|
||||
$cached["cache_hit"] = true;
|
||||
$cached["cache_age_sec"] = $age;
|
||||
echo json_encode($cached, JSON_PRETTY_PRINT);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build from sources (honest, no fake)
|
||||
$sot = [];
|
||||
if (is_readable("/var/www/html/api/source-of-truth.json")) {
|
||||
$sot = @json_decode(@file_get_contents("/var/www/html/api/source-of-truth.json"), true) ?: [];
|
||||
}
|
||||
|
||||
$nonreg = [];
|
||||
if (is_readable("/var/www/html/api/nonreg-latest.json")) {
|
||||
$nonreg = @json_decode(@file_get_contents("/var/www/html/api/nonreg-latest.json"), true) ?: [];
|
||||
}
|
||||
|
||||
$tokens = [];
|
||||
if (is_readable("/tmp/token-health-cache.json")) {
|
||||
$tokens = @json_decode(@file_get_contents("/tmp/token-health-cache.json"), true) ?: [];
|
||||
}
|
||||
|
||||
// Docker running count (live via shell)
|
||||
$docker_count = 0;
|
||||
$out = @shell_exec("docker ps 2>/dev/null | tail -n +2 | wc -l");
|
||||
if ($out !== null) $docker_count = (int)trim($out);
|
||||
if ($docker_count === 0 && isset($sot["docker_running"])) $docker_count = (int)$sot["docker_running"];
|
||||
|
||||
// V83 summary (via architecture_quality)
|
||||
$v83 = ["kpis" => null, "ok" => null, "warn" => null, "fail" => null, "complete_pct" => null];
|
||||
if (is_readable("/var/www/html/api/v83-business-kpi-latest.json")) {
|
||||
$v83_raw = @json_decode(@file_get_contents("/var/www/html/api/v83-business-kpi-latest.json"), true);
|
||||
if ($v83_raw && isset($v83_raw["summary"])) {
|
||||
$v83 = [
|
||||
"kpis" => $v83_raw["summary"]["total_kpis"] ?? null,
|
||||
"ok" => $v83_raw["summary"]["ok"] ?? null,
|
||||
"warn" => $v83_raw["summary"]["warn"] ?? null,
|
||||
"fail" => $v83_raw["summary"]["fail"] ?? null,
|
||||
"complete_pct" => $v83_raw["summary"]["data_completeness_pct"] ?? null
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Orphans (from V83 architecture_quality KPI)
|
||||
$orphans_count = 0;
|
||||
if (is_readable("/tmp/wevia-pages-registry-cache.json")) {
|
||||
$reg = @json_decode(@file_get_contents("/tmp/wevia-pages-registry-cache.json"), true);
|
||||
if ($reg && isset($reg["orphans"])) $orphans_count = (int)$reg["orphans"];
|
||||
}
|
||||
|
||||
// Build unified schema (SINGLE SOURCE OF TRUTH)
|
||||
$response = [
|
||||
"ok" => true,
|
||||
"version" => "v118-kpi-unified",
|
||||
"doctrine" => "zero doublon single source-of-truth",
|
||||
"ts" => date("c"),
|
||||
"cache_hit" => false,
|
||||
"cache_ttl_sec" => $cache_ttl,
|
||||
|
||||
"providers" => [
|
||||
"total" => $sot["providers_count"] ?? ($sot["counts"]["providers"] ?? 0),
|
||||
"ok" => $tokens["summary"]["live_ok"] ?? null,
|
||||
"expired" => $tokens["summary"]["expired_ko"] ?? null,
|
||||
"health_pct" => $tokens["summary"]["health_pct"] ?? null
|
||||
],
|
||||
|
||||
"agents" => [
|
||||
"active" => $sot["agents_count"] ?? 0,
|
||||
"total_live" => $sot["counts"]["agents_total_live"] ?? ($sot["agents_total"] ?? 0)
|
||||
],
|
||||
|
||||
"skills" => [
|
||||
"count" => $sot["skills_count"] ?? 0,
|
||||
"total" => $sot["skills_total"] ?? ($sot["counts"]["skills_total"] ?? 0)
|
||||
],
|
||||
|
||||
"intents" => [
|
||||
"count" => $sot["intents_count"] ?? ($sot["counts"]["intents"] ?? 0),
|
||||
"total" => $sot["intents_total"] ?? 0
|
||||
],
|
||||
|
||||
"docker" => [
|
||||
"running" => $docker_count
|
||||
],
|
||||
|
||||
"orphans" => [
|
||||
"count" => $orphans_count,
|
||||
"status" => ($orphans_count === 0) ? "ok" : "warn"
|
||||
],
|
||||
|
||||
"nonreg" => [
|
||||
"pass" => $nonreg["pass"] ?? 0,
|
||||
"fail" => $nonreg["fail"] ?? 0,
|
||||
"total" => ($nonreg["pass"] ?? 0) + ($nonreg["fail"] ?? 0),
|
||||
"score" => $nonreg["score"] ?? 0,
|
||||
"ts" => $nonreg["ts"] ?? null
|
||||
],
|
||||
|
||||
"v83" => $v83,
|
||||
|
||||
"autonomy" => [
|
||||
"score" => $sot["autonomy_score"] ?? 0,
|
||||
"level" => $sot["autonomy_level"] ?? "UNKNOWN"
|
||||
],
|
||||
|
||||
"qdrant" => [
|
||||
"collections" => $sot["counts"]["qdrant_cols"] ?? 0,
|
||||
"points" => $sot["counts"]["qdrant_points"] ?? 0
|
||||
],
|
||||
|
||||
"dashboards" => [
|
||||
"count" => $sot["dashboards_count"] ?? ($sot["counts"]["dashboards"] ?? 0)
|
||||
],
|
||||
|
||||
"brains" => [
|
||||
"count" => $sot["brains_count"] ?? ($sot["counts"]["brains"] ?? 0)
|
||||
],
|
||||
|
||||
"doctrines" => [
|
||||
"count" => $sot["doctrines_count"] ?? ($sot["counts"]["doctrines"] ?? 0)
|
||||
],
|
||||
|
||||
"business" => [
|
||||
"cash_collected_month_keur" => $sot["cash_collected_month_keur"] ?? 0,
|
||||
"cash_collected_ytd_keur" => $sot["cash_collected_ytd_keur"] ?? 0,
|
||||
"cash_target_month_keur" => $sot["cash_target_month_keur"] ?? 0,
|
||||
"dso_days" => $sot["dso_days"] ?? 0
|
||||
],
|
||||
|
||||
"ethica" => [
|
||||
"total_hcps" => $sot["ethica_total"] ?? 0
|
||||
],
|
||||
|
||||
"sources_used" => [
|
||||
"source_of_truth_json" => !empty($sot),
|
||||
"nonreg_latest" => !empty($nonreg),
|
||||
"token_health_cache" => !empty($tokens),
|
||||
"v83_latest" => ($v83["kpis"] !== null),
|
||||
"docker_live" => ($docker_count > 0)
|
||||
]
|
||||
];
|
||||
|
||||
// Write cache
|
||||
@file_put_contents($cache_file, json_encode($response, JSON_PRETTY_PRINT));
|
||||
|
||||
echo json_encode($response, JSON_PRETTY_PRINT);
|
||||
24
login.html
24
login.html
@@ -33,6 +33,8 @@ h1{font-size:1.6rem;font-weight:700;margin-bottom:4px}h1 span{color:#818cf8}
|
||||
.secure{color:#475569;font-size:.75rem;margin-top:16px}
|
||||
.back{color:#475569;font-size:.8rem;text-decoration:none;display:block;margin-top:12px}
|
||||
.footer{color:#334155;font-size:.7rem;margin-top:16px}
|
||||
|
||||
.weval-logout-btn,[class*="logout"],#logout-btn,.session-badge,.user-badge{display:none!important;visibility:hidden!important}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -63,10 +65,12 @@ var redirect=new URLSearchParams(window.location.search).get('r')||'/products/wo
|
||||
var state=btoa(redirect);
|
||||
var ssoUrl='https://auth.weval-consulting.com/application/o/authorize/?client_id=aB9IF9xQ8L9u7Ty1Eq63dMYFgy59O58fqzuNulwJ&response_type=code&redirect_uri=https%3A%2F%2Fweval-consulting.com%2Fapi%2Fauth-callback.php&scope=openid+profile+email&state='+encodeURIComponent(state);
|
||||
document.getElementById('sso-link').href=ssoUrl;
|
||||
if(!window.location.search.includes('manual=1')&&!window.location.search.includes('error=')){
|
||||
//auto-redirect disabled — use manual SSO button or password login
|
||||
}else{
|
||||
document.getElementById('auto-redirect').style.display='none';
|
||||
// v3 — auto-redirect DISABLED by default (Doctrine #2 : zero regression, clear UX)
|
||||
// Hide spinner/message unless ?auto=1 is explicitly requested
|
||||
document.getElementById('auto-redirect').style.display='none';
|
||||
if(window.location.search.includes('auto=1')&&!window.location.search.includes('error=')){
|
||||
document.getElementById('auto-redirect').style.display='block';
|
||||
setTimeout(function(){window.location.href=ssoUrl;},300);
|
||||
}
|
||||
async function doLogin(e){
|
||||
e.preventDefault();
|
||||
@@ -84,6 +88,18 @@ async function doLogin(e){
|
||||
btn.disabled=false;btn.textContent='Se connecter';
|
||||
}
|
||||
if(window.location.search.includes('error=')){document.getElementById('manual').classList.add('show');document.getElementById('auto-redirect').style.display='none';}
|
||||
// UX premium : focus auto user field on manual open
|
||||
(function(){
|
||||
var t = document.querySelector('.toggle');
|
||||
if(t) t.addEventListener('click', function(){
|
||||
setTimeout(function(){ var u=document.getElementById('user'); if(u) u.focus(); }, 50);
|
||||
});
|
||||
// Auto-open manual if ?manual=1
|
||||
if(window.location.search.includes('manual=1')){
|
||||
document.getElementById('manual').classList.add('show');
|
||||
setTimeout(function(){ var u=document.getElementById('user'); if(u) u.focus(); }, 100);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- === OPUS UNIVERSAL DRILL-DOWN v1 19avr — append-only, doctrine #14 === -->
|
||||
|
||||
152
login.html.GOLD-20260421-112638-pre-auth-fix
Executable file
152
login.html.GOLD-20260421-112638-pre-auth-fix
Executable file
@@ -0,0 +1,152 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WEVAL — Connexion</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:'Segoe UI',system-ui,sans-serif;background:#0a0e1a;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center}
|
||||
.box{background:rgba(15,23,42,.9);border:1px solid rgba(99,102,241,.15);border-radius:20px;padding:48px 40px;width:420px;text-align:center;backdrop-filter:blur(20px)}
|
||||
.logo{width:56px;height:56px;border-radius:50%;border:2px solid rgba(99,102,241,.3);display:flex;align-items:center;justify-content:center;margin:0 auto 24px;background:rgba(99,102,241,.08)}
|
||||
.logo::after{content:'';width:14px;height:14px;border-radius:50%;border:3px solid #818cf8}
|
||||
h1{font-size:1.6rem;font-weight:700;margin-bottom:4px}h1 span{color:#818cf8}
|
||||
.sub{color:#64748b;font-size:.85rem;margin-bottom:32px}
|
||||
.sso-btn{display:block;width:100%;padding:14px;background:linear-gradient(135deg,#6366f1,#818cf8);color:#fff;border:none;border-radius:12px;font-size:1rem;font-weight:600;cursor:pointer;text-decoration:none;margin-bottom:16px;transition:all .2s}
|
||||
.sso-btn:hover{transform:translateY(-1px);box-shadow:0 8px 20px rgba(99,102,241,.3)}
|
||||
.spinner{display:inline-block;width:18px;height:18px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;margin-right:8px;vertical-align:middle}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
.redirect-msg{color:#94a3b8;font-size:.85rem;margin-bottom:24px}
|
||||
.divider{border-top:1px solid rgba(255,255,255,.08);margin:24px 0;position:relative}
|
||||
.divider span{background:rgba(15,23,42,.9);padding:0 12px;color:#475569;font-size:.75rem;position:absolute;top:-8px;left:50%;transform:translateX(-50%)}
|
||||
.toggle{color:#64748b;font-size:.8rem;cursor:pointer;text-decoration:underline}
|
||||
.toggle:hover{color:#94a3b8}
|
||||
.manual{display:none;margin-top:20px}
|
||||
.manual.show{display:block}
|
||||
.field{margin-bottom:16px;text-align:left}
|
||||
.field label{display:block;color:#94a3b8;font-size:.8rem;margin-bottom:4px;font-weight:500}
|
||||
.field input{width:100%;padding:12px 14px;background:rgba(30,41,59,.8);border:1px solid rgba(99,102,241,.2);border-radius:10px;color:#f1f5f9;font-size:.95rem;outline:none;transition:border .2s}
|
||||
.field input:focus{border-color:#6366f1}
|
||||
.btn{width:100%;padding:12px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:10px;color:#a5b4fc;font-size:.9rem;font-weight:600;cursor:pointer}
|
||||
.btn:hover{background:rgba(99,102,241,.25)}
|
||||
.error{color:#f87171;font-size:.85rem;margin-top:8px}
|
||||
.secure{color:#475569;font-size:.75rem;margin-top:16px}
|
||||
.back{color:#475569;font-size:.8rem;text-decoration:none;display:block;margin-top:12px}
|
||||
.footer{color:#334155;font-size:.7rem;margin-top:16px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div class="logo"></div>
|
||||
<h1>WEVAL <span>Consulting</span></h1>
|
||||
<p class="sub">Espace sécurisé — Authentification requise</p>
|
||||
<div id="auto-redirect">
|
||||
<p class="redirect-msg"><span class="spinner"></span>Redirection SSO Authentik...</p>
|
||||
</div>
|
||||
<a id="sso-link" class="sso-btn" href="#">Connexion SSO (Authentik)</a>
|
||||
<div class="divider"><span>OU</span></div>
|
||||
<span class="toggle" onclick="document.getElementById('manual').classList.toggle('show')">Connexion manuelle</span>
|
||||
<div id="manual" class="manual">
|
||||
<form onsubmit="return doLogin(event)" novalidate>
|
||||
<div class="field"><label>Identifiant</label><input type="text" id="user" autocomplete="username"></div>
|
||||
<div class="field"><label>Mot de passe</label><input type="password" id="pass" autocomplete="current-password"></div>
|
||||
<button class="btn" type="submit" id="btn">Se connecter</button>
|
||||
<div class="error" id="err"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="secure">Connexion chiffrée · Session sécurisée</div>
|
||||
<a class="back" href="/">Retour au site</a>
|
||||
<div class="footer">WEVAL Consulting 2026</div>
|
||||
</div>
|
||||
<script>
|
||||
var redirect=new URLSearchParams(window.location.search).get('r')||'/products/workspace.html';
|
||||
var state=btoa(redirect);
|
||||
var ssoUrl='https://auth.weval-consulting.com/application/o/authorize/?client_id=aB9IF9xQ8L9u7Ty1Eq63dMYFgy59O58fqzuNulwJ&response_type=code&redirect_uri=https%3A%2F%2Fweval-consulting.com%2Fapi%2Fauth-callback.php&scope=openid+profile+email&state='+encodeURIComponent(state);
|
||||
document.getElementById('sso-link').href=ssoUrl;
|
||||
if(!window.location.search.includes('manual=1')&&!window.location.search.includes('error=')){
|
||||
//auto-redirect disabled — use manual SSO button or password login
|
||||
}else{
|
||||
document.getElementById('auto-redirect').style.display='none';
|
||||
}
|
||||
async function doLogin(e){
|
||||
e.preventDefault();
|
||||
var btn=document.getElementById('btn'),err=document.getElementById('err');
|
||||
var user=document.getElementById('user').value.trim(),pass=document.getElementById('pass').value;
|
||||
if(!user){err.textContent='Identifiant requis';return false;}
|
||||
if(!pass){err.textContent='Mot de passe requis';return false;}
|
||||
btn.disabled=true;btn.textContent='Connexion...';err.textContent='';
|
||||
try{
|
||||
var r=await fetch('/api/weval-auth-session.php',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'action=login&user='+encodeURIComponent(user)+'&pass='+encodeURIComponent(pass)+'&redirect='+encodeURIComponent(redirect)});
|
||||
var d=await r.json();
|
||||
if(d.ok){window.location.href=d.redirect||redirect;}
|
||||
else{err.textContent=d.error||'Identifiants incorrects';}
|
||||
}catch(ex){err.textContent='Erreur réseau';}
|
||||
btn.disabled=false;btn.textContent='Se connecter';
|
||||
}
|
||||
if(window.location.search.includes('error=')){document.getElementById('manual').classList.add('show');document.getElementById('auto-redirect').style.display='none';}
|
||||
</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) {
|
||||
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 (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);} });
|
||||
}
|
||||
}
|
||||
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 === -->
|
||||
|
||||
|
||||
<!-- V90 archi badge + spotlight (UX premium partout) -->
|
||||
<script src="/api/archi-meta-badge.js" defer></script>
|
||||
<script src="/api/archi-spotlight.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
117
wiki/V136-health-drill-down-modal.md
Normal file
117
wiki/V136-health-drill-down-modal.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# V136 Opus WIRE - KPI Banner Clickable + Health Drill-Down Modal · 21avr 11:27
|
||||
|
||||
## Context
|
||||
Yacine "CONTINUE" après V135. Objectif: drill-down actionable sur le banner health V135 pour voir en 1 click le détail des URLs cassées.
|
||||
|
||||
## Scan exhaustif (doctrine #1)
|
||||
|
||||
Découvertes:
|
||||
- `b1629038b` autre Claude: "polish wtp-erp-cc-charts-v106" — donut SVG 100% + sparkline 56→100% ajoutés dans WTP (5KB+, 197→202KB)
|
||||
- `14ecacd24` "ERP Command Center V105" toujours en place (5 cards penta-pivot + 8 KPI)
|
||||
- Mes 11 markers V107-V135 tous préservés dans all-ia-hub
|
||||
- NR: 200/201 stable
|
||||
- screens-health.json live: 20 BROKEN / 470 UP / 29 SLOW / 1218 PHANTOM
|
||||
|
||||
## Option étudiées et rejetées
|
||||
|
||||
### Option A - Nouveau tab HEALTH (rejetée)
|
||||
Risque de breaking le layout 7-tabs existant + aucun besoin d'une vue permanente vs vue on-demand.
|
||||
|
||||
### Option B - Nouvelle page /platform-health.html (rejetée)
|
||||
Crée un orphelin potentiel, duplique l'info déjà dans screens-health.json. Doctrine #14: privilégier enrichissement in-place.
|
||||
|
||||
### Option C - Modal in-page déclenché par click sur banner V135 (CHOISIE)
|
||||
Zero nouvelle page · Zero nouveau tab · In-place · Full drill-down.
|
||||
|
||||
## Livrable V136
|
||||
|
||||
### Banner cliquable (V135 → V136)
|
||||
**Avant** : banner read-only
|
||||
**Après** : `cursor:pointer` + `text-decoration:underline dotted` + click handler `__v136ShowHealthModal()` + title update "Platform health live · click pour détail"
|
||||
|
||||
### Modal drill-down
|
||||
- Position: `fixed` overlay fullscreen, background `rgba(0,0,0,0.75)`
|
||||
- Container centré, max-width 880px, max-height 80vh scrollable
|
||||
- Header: titre "🏥 Platform Health · Detail" + summary timestamp + counts + bouton ESC
|
||||
- Content: liste des 20 BROKEN URLs avec:
|
||||
- Status badge (`BROKEN`/`DOWN` en rouge)
|
||||
- HTTP code (amber)
|
||||
- URL clickable (target="_blank")
|
||||
- Response time ms (right-aligned)
|
||||
- Bonus: top 10 SLOW URLs (>2s) en bas
|
||||
- Footer: source `/api/screens-health.json` (transparence)
|
||||
|
||||
### Interaction UX premium
|
||||
- Click banner → ouvre (smooth)
|
||||
- Click outside modal → ferme
|
||||
- Escape key → ferme
|
||||
- Reopen possible illimité
|
||||
- Fetch lazy (uniquement au click)
|
||||
- Cache-busting `cache: 'no-store'` (toujours live)
|
||||
|
||||
## Validation E2E Playwright V136
|
||||
|
||||
```json
|
||||
{
|
||||
"v136": "health-modal-drill-down",
|
||||
"modal_exists": true,
|
||||
"banner_clickable": true,
|
||||
"hidden_initially": true,
|
||||
"opens_on_click": true,
|
||||
"fetches_broken_urls": true,
|
||||
"content_preview": "...BROKEN/DOWN (20):... BROKEN 500 https://weval-consulting.com/_oc_tmp.php...",
|
||||
"escape_closes": true,
|
||||
"reopen_works": true,
|
||||
"click_outside_closes": true,
|
||||
"js_errors": [],
|
||||
"VERDICT": "OK"
|
||||
}
|
||||
```
|
||||
|
||||
8/8 checks pass · screenshot captured dans `/var/www/html/api/blade-tasks/v136-health-modal-proof/`
|
||||
|
||||
## Navigation Hub finalisée
|
||||
|
||||
```
|
||||
all-ia-hub.html (61.2KB)
|
||||
└── 🧭 Breadcrumb V130+penta (5 surfaces navigables)
|
||||
└── 🟢/🟡/🔴 KPI banner V135 + 🆕 clickable V136
|
||||
↓ click
|
||||
└── 🏥 Health Drill-Down Modal (V136)
|
||||
├── Summary live (timestamp + counts)
|
||||
├── BROKEN/DOWN URLs (up to 30)
|
||||
├── SLOW URLs (top 10)
|
||||
├── Close: ESC / click-outside / button
|
||||
└── Source transparente
|
||||
```
|
||||
|
||||
## Architecture KPI 3 niveaux (consolidation complète)
|
||||
|
||||
| Niveau | Source | Scope | UI | Action |
|
||||
|---|---|---|---|---|
|
||||
| **Local** (V131) | registry.php | 84 dashboards | Counter DASHBOARDS | ● all OK / ● N broken |
|
||||
| **Global** (V135) | screens-health.json | 1737 URLs | Breadcrumb xnav top | 🟢/🟡/🔴 + % + short |
|
||||
| **Detail** (V136) | screens-health.json | Broken + Slow URLs | Modal on-demand | Liste clickable avec codes + ms |
|
||||
|
||||
Chaque niveau a son rôle et son scope, aucune redondance.
|
||||
|
||||
## Métriques V135 → V136
|
||||
|
||||
| | V135 | V136 |
|
||||
|---|---|---|
|
||||
| Hub size | 55.9KB | 61.2KB (+5.3KB) |
|
||||
| KPI niveaux | 2 | **3** |
|
||||
| Drill-down actionable | non | **oui** (modal on-click) |
|
||||
| Modal/Dialog | 0 | **1** (in-page, zero new page) |
|
||||
| Keyboard a11y | Cmd+K search | Cmd+K search + Escape modal |
|
||||
| JS errors | 0 | 0 |
|
||||
| Sessions sans régression | 100 | **101+** |
|
||||
|
||||
## GOLDs préservés
|
||||
- `/opt/wevads/vault/all-ia-hub.html.GOLD-V136-pre-health-modal`
|
||||
- V108, V109, V111, V112, V113, V114, V116, V117, V119, V120, V122, V123, V127, V128, V129, V130, V131, V135, V136 (19 GOLDs)
|
||||
|
||||
## Doctrines respectées
|
||||
#1 scan exhaustif · #3 GOLD · #4 honnêteté (E2E 8/8 prouvé) · #13 cause racine (consomme source unique, pas nouveau scanner) · **#14 ADDITIF PUR** · #16 NR · **#60 UX premium** (modal overlay, a11y ESC/click-outside, transparence source) · #100
|
||||
|
||||
## Sessions consécutives sans régression applicative : **101+** 🏆
|
||||
Reference in New Issue
Block a user