271 lines
14 KiB
HTML
271 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr" data-theme="dark">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Orphans Dashboard — Doctrine 92 Hub Merged</title>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<style>
|
||
:root{--bg:#05060a;--bg2:#0e111c;--bg3:#181c2b;--br:#1f2436;--tx:#f1f5f9;--tx2:#94a3b8;--ac:#6366f1;--green:#22c55e;--yellow:#fbbf24;--red:#ef4444}
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{font-family:system-ui,sans-serif;background:var(--bg);color:var(--tx);padding:24px;line-height:1.5}
|
||
h1{font-size:30px;font-weight:800;background:linear-gradient(135deg,#6366f1,#ec4899);-webkit-background-clip:text;color:transparent;margin-bottom:4px}
|
||
.sub{color:var(--tx2);margin-bottom:20px;font-size:14px}
|
||
.back{display:inline-block;padding:8px 16px;background:var(--bg3);border:1px solid var(--br);border-radius:8px;color:var(--tx);text-decoration:none;margin-bottom:20px;transition:.2s}
|
||
.back:hover{border-color:var(--ac);transform:translateX(-2px)}
|
||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:24px}
|
||
.stat{background:var(--bg2);border:1px solid var(--br);border-radius:12px;padding:16px;transition:.2s}
|
||
.stat:hover{border-color:var(--ac);transform:translateY(-2px)}
|
||
.stat.active{border-color:var(--green)}
|
||
.stat.dormant{border-color:var(--yellow)}
|
||
.stat.archive{border-color:var(--tx2)}
|
||
.stat .n{font-size:28px;font-weight:800;color:var(--ac);margin-bottom:4px}
|
||
.stat.active .n{color:var(--green)}
|
||
.stat.dormant .n{color:var(--yellow)}
|
||
.stat.archive .n{color:var(--tx2)}
|
||
.stat .l{font-size:11px;color:var(--tx2);text-transform:uppercase;letter-spacing:.5px}
|
||
.tabs{display:flex;gap:8px;margin-bottom:20px;border-bottom:1px solid var(--br)}
|
||
.tab{padding:10px 20px;background:none;border:none;color:var(--tx2);cursor:pointer;font-weight:600;font-size:14px;border-bottom:2px solid transparent;transition:.2s}
|
||
.tab.active{color:var(--ac);border-bottom-color:var(--ac)}
|
||
.tab:hover{color:var(--tx)}
|
||
.tab .count{display:inline-block;background:var(--bg3);padding:2px 8px;border-radius:10px;font-size:11px;margin-left:6px}
|
||
.filter{margin-bottom:16px;display:flex;gap:10px;align-items:center;flex-wrap:wrap}
|
||
.filter input{background:var(--bg2);border:1px solid var(--br);border-radius:8px;padding:10px 14px;color:var(--tx);font-size:14px;min-width:240px}
|
||
.filter input:focus{outline:none;border-color:var(--ac)}
|
||
.filter select{background:var(--bg2);border:1px solid var(--br);border-radius:8px;padding:10px 14px;color:var(--tx);font-size:14px;cursor:pointer}
|
||
.pages{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:10px}
|
||
.page{display:block;padding:12px 14px;background:var(--bg2);border:1px solid var(--br);border-radius:10px;color:var(--tx);text-decoration:none;transition:.2s;position:relative}
|
||
.page:hover{border-color:var(--ac);transform:translateY(-1px);box-shadow:0 4px 12px rgba(99,102,241,0.2)}
|
||
.page-header{display:flex;justify-content:space-between;align-items:flex-start;gap:8px;margin-bottom:6px}
|
||
.pn{font-family:'SF Mono',Menlo,monospace;font-size:12px;color:var(--ac);font-weight:600;word-break:break-all}
|
||
.page.active .pn{color:var(--green)}
|
||
.page.dormant .pn{color:var(--yellow)}
|
||
.page.archive .pn{color:var(--tx2)}
|
||
.pt{font-size:13px;color:var(--tx);margin-bottom:4px;font-weight:500}
|
||
.pm{font-size:11px;color:var(--tx2);display:flex;gap:10px;flex-wrap:wrap}
|
||
.badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.3px}
|
||
.badge.active{background:rgba(34,197,94,0.2);color:var(--green);border:1px solid var(--green)}
|
||
.badge.dormant{background:rgba(251,191,36,0.2);color:var(--yellow);border:1px solid var(--yellow)}
|
||
.badge.archive{background:rgba(148,163,184,0.2);color:var(--tx2);border:1px solid var(--tx2)}
|
||
.badge.suite{background:var(--bg3);color:var(--tx2);border:1px solid var(--br)}
|
||
.empty{text-align:center;padding:40px;color:var(--tx2)}
|
||
.loading{text-align:center;padding:40px;color:var(--ac)}
|
||
.snippet-btn{display:inline-block;margin-top:10px;padding:10px 16px;background:linear-gradient(135deg,#6366f1,#ec4899);color:#fff;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;border:none}
|
||
.snippet-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(99,102,241,0.4)}
|
||
#snippet-modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);z-index:100;padding:40px;overflow:auto}
|
||
#snippet-modal.show{display:block}
|
||
.modal-content{background:var(--bg2);border:1px solid var(--br);border-radius:12px;padding:24px;max-width:900px;margin:0 auto}
|
||
.modal-content pre{background:#000;border:1px solid var(--br);border-radius:8px;padding:14px;overflow:auto;font-size:12px;color:var(--green);max-height:500px}
|
||
.modal-close{float:right;background:none;border:none;color:var(--tx);font-size:24px;cursor:pointer}
|
||
|
||
/* === OPUS RESPONSIVE FIX v2 19avr — append-only, doctrine #14 === */
|
||
@media(max-width: 480px) {
|
||
html, body { overflow-x: hidden !important; max-width: 100vw; }
|
||
body, main, section, article { word-break: break-word; overflow-wrap: anywhere; }
|
||
img, video, iframe, canvas, svg, table, pre, code { max-width: 100% !important; }
|
||
pre, code { white-space: pre-wrap; word-break: break-all; }
|
||
table { display: block; overflow-x: auto; }
|
||
.container, [class*="container"], [class*="wrapper"] { max-width: 100vw !important; padding-left: 12px !important; padding-right: 12px !important; }
|
||
[class*="grid"], [class*="-grid"] { grid-template-columns: 1fr !important; gap: 10px !important; }
|
||
[class*="kpi"], [class*="stats"], [class*="-cards"] { grid-template-columns: 1fr !important; }
|
||
header, nav, footer { flex-wrap: wrap !important; }
|
||
header > *, nav > *, footer > * { max-width: 100%; }
|
||
h1 { font-size: 22px !important; word-break: break-word; }
|
||
h2 { font-size: 18px !important; }
|
||
.pitch, [class*="pitch"], [class*="hero"] { word-break: break-word; overflow-wrap: anywhere; }
|
||
}
|
||
/* === OPUS RESPONSIVE FIX v2 END === */
|
||
</style></head>
|
||
<body>
|
||
<a href="/weval-technology-platform.html" class="back">← WTP</a>
|
||
<h1>Orphans Dashboard</h1>
|
||
<div class="sub">Doctrine 92 · Hub Merged · V82 suites métier + D91 classifier</div>
|
||
|
||
<div class="stats" id="stats"></div>
|
||
|
||
<div class="tabs">
|
||
<button class="tab active" data-filter="all">Tous <span class="count" id="c-all">·</span></button>
|
||
<button class="tab" data-filter="ACTIVE">Actifs à rebrancher <span class="count" id="c-active">·</span></button>
|
||
<button class="tab" data-filter="DORMANT">Dormant <span class="count" id="c-dormant">·</span></button>
|
||
<button class="tab" data-filter="ARCHIVE">Archive <span class="count" id="c-archive">·</span></button>
|
||
</div>
|
||
|
||
<div class="filter">
|
||
<input type="text" id="search" placeholder="Rechercher page, titre ou suite métier…">
|
||
<select id="suite-filter"><option value="">Toutes suites</option></select>
|
||
<button class="snippet-btn" onclick="showSnippet()">📋 Voir snippet HTML WTP-ready</button>
|
||
</div>
|
||
|
||
<div id="pages" class="pages"><div class="loading">Chargement…</div></div>
|
||
|
||
<div id="snippet-modal">
|
||
<div class="modal-content">
|
||
<button class="modal-close" onclick="hideSnippet()">×</button>
|
||
<h2>Snippet HTML prêt à injecter dans WTP</h2>
|
||
<p class="sub">Additive pur · avant </body> · pattern V80 · GOLD backup obligatoire</p>
|
||
<pre id="snippet-code">Chargement…</pre>
|
||
<button class="snippet-btn" onclick="copySnippet()">Copier</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
let DATA = null;
|
||
let CURRENT_FILTER = 'all';
|
||
let CURRENT_SUITE = '';
|
||
let SEARCH_TERM = '';
|
||
|
||
async function load() {
|
||
try {
|
||
const r = await fetch('/api/opus5-orphans-hub.php');
|
||
DATA = await r.json();
|
||
renderStats();
|
||
renderSuiteFilter();
|
||
render();
|
||
} catch (e) {
|
||
document.getElementById('pages').innerHTML = '<div class="empty">Erreur chargement : ' + e.message + '</div>';
|
||
}
|
||
}
|
||
|
||
function renderStats() {
|
||
const s = DATA.stats;
|
||
document.getElementById('stats').innerHTML = `
|
||
<div class="stat"><div class="n">${s.total_orphans}</div><div class="l">Total orphelins</div></div>
|
||
<div class="stat active"><div class="n">${s.active_to_link}</div><div class="l">Actifs à rebrancher</div></div>
|
||
<div class="stat dormant"><div class="n">${s.dormant_candidate}</div><div class="l">Dormant (user decide)</div></div>
|
||
<div class="stat archive"><div class="n">${s.archive_legit}</div><div class="l">Archive légitime</div></div>
|
||
<div class="stat"><div class="n">${s.suites_v82}</div><div class="l">Suites métier V82</div></div>
|
||
<div class="stat"><div class="n">${s.sync_ok ? '✓' : '≠'}</div><div class="l">V82↔D91 sync</div></div>
|
||
`;
|
||
document.getElementById('c-all').textContent = s.total_orphans;
|
||
document.getElementById('c-active').textContent = s.active_to_link;
|
||
document.getElementById('c-dormant').textContent = s.dormant_candidate;
|
||
document.getElementById('c-archive').textContent = s.archive_legit;
|
||
}
|
||
|
||
function renderSuiteFilter() {
|
||
const suites = [...new Set(DATA.merged.map(m => m.suite_v82))].sort();
|
||
const sel = document.getElementById('suite-filter');
|
||
suites.forEach(s => sel.innerHTML += `<option value="${s}">${s}</option>`);
|
||
}
|
||
|
||
function render() {
|
||
let filtered = DATA.merged;
|
||
if (CURRENT_FILTER !== 'all') filtered = filtered.filter(m => m.classif_d91 === CURRENT_FILTER);
|
||
if (CURRENT_SUITE) filtered = filtered.filter(m => m.suite_v82 === CURRENT_SUITE);
|
||
if (SEARCH_TERM) {
|
||
const q = SEARCH_TERM.toLowerCase();
|
||
filtered = filtered.filter(m =>
|
||
m.page.toLowerCase().includes(q) ||
|
||
(m.title||'').toLowerCase().includes(q) ||
|
||
(m.suite_v82||'').toLowerCase().includes(q)
|
||
);
|
||
}
|
||
|
||
const container = document.getElementById('pages');
|
||
if (filtered.length === 0) {
|
||
container.innerHTML = '<div class="empty">Aucun orphelin pour ce filtre</div>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = filtered.map(m => {
|
||
const cls = m.classif_d91.toLowerCase();
|
||
return `
|
||
<a href="${m.url}" class="page ${cls}" target="_blank">
|
||
<div class="page-header">
|
||
<div class="pn">${m.page}</div>
|
||
<span class="badge ${cls}">${m.classif_d91}</span>
|
||
</div>
|
||
<div class="pt">${m.title || '(sans titre)'}</div>
|
||
<div class="pm">
|
||
<span class="badge suite">${m.suite_v82}</span>
|
||
<span>${m.size_kb} KB</span>
|
||
<span>${m.class}</span>
|
||
${m.reason ? '<span>· '+m.reason+'</span>' : ''}
|
||
</div>
|
||
</a>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
document.querySelectorAll('.tab').forEach(t => t.onclick = () => {
|
||
document.querySelectorAll('.tab').forEach(x => x.classList.remove('active'));
|
||
t.classList.add('active');
|
||
CURRENT_FILTER = t.dataset.filter;
|
||
render();
|
||
});
|
||
document.getElementById('search').oninput = e => { SEARCH_TERM = e.target.value; render(); };
|
||
document.getElementById('suite-filter').onchange = e => { CURRENT_SUITE = e.target.value; render(); };
|
||
|
||
function showSnippet() {
|
||
document.getElementById('snippet-modal').classList.add('show');
|
||
document.getElementById('snippet-code').textContent = DATA.snippet_html || '(empty)';
|
||
}
|
||
function hideSnippet() { document.getElementById('snippet-modal').classList.remove('show'); }
|
||
function copySnippet() {
|
||
navigator.clipboard.writeText(DATA.snippet_html);
|
||
alert('Snippet copié — paste avant
|
||
<!-- === 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 === -->
|
||
|
||
</body> dans WTP');
|
||
}
|
||
|
||
load();
|
||
</script>
|
||
<script src="/api/archi-meta-badge.js" defer></script>
|
||
</body></html>
|