328 lines
22 KiB
HTML
328 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>WEVIA Meetings — Command Center</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;500;700;800&display=swap" rel="stylesheet">
|
|
<style>
|
|
*{margin:0;padding:0;box-sizing:border-box}
|
|
:root{--bg:#060a14;--card:#0d1525;--border:#1a2744;--accent:#06d6a0;--warn:#f59e0b;--danger:#ef4444;--opus:#8b5cf6;--text:#e2e8f0;--dim:#4a5568;--glass:rgba(13,21,37,0.85)}
|
|
body{font-family:'Plus Jakarta Sans',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
|
.header{padding:24px 32px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border);background:linear-gradient(135deg,#060a14,#0d1a30)}
|
|
.header h1{font-size:1.4rem;font-weight:800;background:linear-gradient(135deg,var(--accent),#3b82f6);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
|
.header .meta{color:var(--dim);font-size:0.78rem}
|
|
.live-dot{width:8px;height:8px;border-radius:50%;background:var(--accent);display:inline-block;animation:pulse 2s infinite}
|
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
|
.tabs{display:flex;gap:6px;padding:16px 32px;border-bottom:1px solid var(--border);flex-wrap:wrap}
|
|
.tab{padding:7px 18px;border-radius:16px;border:1px solid var(--border);background:transparent;color:var(--dim);cursor:pointer;font-size:0.8rem;font-family:inherit;transition:all .25s}
|
|
.tab.active{background:var(--accent);color:#000;border-color:var(--accent);font-weight:700}
|
|
.tab:hover{border-color:var(--accent)}
|
|
.main{padding:24px 32px;max-width:1400px;margin:0 auto}
|
|
.section{display:none}.section.active{display:block}
|
|
.grid-5{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin:16px 0}
|
|
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin:16px 0}
|
|
@media(max-width:800px){.grid-2{grid-template-columns:1fr}}
|
|
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:18px;position:relative;transition:transform .2s,box-shadow .2s}
|
|
.card:hover{transform:translateY(-2px);box-shadow:0 8px 24px rgba(6,214,160,.08)}
|
|
.card h3{font-size:0.95rem;font-weight:700;margin-bottom:10px;display:flex;align-items:center;gap:8px}
|
|
.card .status{position:absolute;top:14px;right:14px;font-size:0.65rem;padding:2px 8px;border-radius:8px;font-weight:700}
|
|
.status.up{background:#06d6a020;color:var(--accent)}.status.down{background:#ef444420;color:var(--danger)}.status.warn{background:#f59e0b20;color:var(--warn)}
|
|
.squad-card{border-left:3px solid var(--accent)}
|
|
.squad-card.infra{border-left-color:#3b82f6}.squad-card.dev{border-left-color:var(--accent)}.squad-card.security{border-left-color:var(--danger)}.squad-card.business{border-left-color:var(--warn)}.squad-card.ia{border-left-color:var(--opus)}
|
|
.participant{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--border)}
|
|
.participant:last-child{border:none}
|
|
.participant .avatar{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.1rem;flex-shrink:0}
|
|
.participant .info{flex:1}.participant .name{font-weight:700;font-size:0.85rem}.participant .role{color:var(--dim);font-size:0.72rem}
|
|
.btn{padding:8px 20px;border-radius:8px;border:none;font-family:inherit;font-size:0.82rem;font-weight:700;cursor:pointer;transition:all .2s}
|
|
.btn-primary{background:var(--accent);color:#000}.btn-primary:hover{background:#05c795}
|
|
.btn-secondary{background:var(--border);color:var(--text)}.btn-secondary:hover{background:#243356}
|
|
.btn-danger{background:var(--danger);color:#fff}
|
|
.actions{display:flex;gap:8px;margin-top:12px;flex-wrap:wrap}
|
|
.timeline{margin:16px 0}.timeline-item{display:flex;gap:12px;padding:10px 0;border-left:2px solid var(--border);margin-left:8px;padding-left:16px;position:relative}
|
|
.timeline-item::before{content:'';width:10px;height:10px;border-radius:50%;background:var(--accent);position:absolute;left:-6px;top:14px}
|
|
.timeline-item.warn::before{background:var(--warn)}.timeline-item.danger::before{background:var(--danger)}
|
|
.timeline-item .time{color:var(--dim);font-size:0.72rem;min-width:60px}.timeline-item .content{font-size:0.82rem;line-height:1.5}
|
|
.synthesis{background:#0a1020;border:1px solid var(--border);border-radius:8px;padding:14px;margin:10px 0;font-size:0.82rem;line-height:1.6;color:var(--dim)}
|
|
.kpi{text-align:center;padding:16px}.kpi .value{font-size:1.8rem;font-weight:800;color:var(--accent)}.kpi .label{font-size:0.72rem;color:var(--dim);margin-top:4px}
|
|
.schedule-row{display:flex;align-items:center;gap:16px;padding:10px 0;border-bottom:1px solid var(--border)}
|
|
.schedule-row .time{font-weight:700;font-size:0.9rem;min-width:60px;color:var(--accent)}
|
|
.schedule-row .type{font-size:0.72rem;padding:2px 8px;border-radius:6px;font-weight:700}
|
|
.type-daily{background:#3b82f620;color:#3b82f6}.type-weekly{background:#8b5cf620;color:var(--opus)}.type-strategy{background:#06d6a020;color:var(--accent)}
|
|
#loading{display:none;text-align:center;padding:40px;color:var(--dim)}
|
|
#loading.show{display:block}
|
|
.empty{text-align:center;padding:40px;color:var(--dim);font-size:0.85rem}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="header">
|
|
<div>
|
|
<h1>WEVIA Meetings — Command Center</h1>
|
|
<div class="meta"><span class="live-dot"></span> 5 squads · 8 agents · 32 crons · 4 meetings/semaine</div>
|
|
</div>
|
|
<div style="display:flex;gap:8px">
|
|
<button class="btn btn-primary" onclick="runDaily()">▶ Lancer Daily</button>
|
|
<button class="btn btn-secondary" onclick="runWeekly()">📊 Weekly</button>
|
|
<button class="btn btn-secondary" onclick="runStrategy()">🏛️ Stratégie</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tabs">
|
|
<div class="tab active" onclick="show('overview')">Vue d'ensemble</div>
|
|
<div class="tab" onclick="show('squads')">Squads</div>
|
|
<div class="tab" onclick="show('strategy')">Stratégie</div>
|
|
<div class="tab" onclick="show('dispatch')">Actions</div>
|
|
<div class="tab" onclick="show('schedule')">Planning</div>
|
|
<div class="tab" onclick="show('history')">Historique</div>
|
|
</div>
|
|
|
|
<div class="main">
|
|
<div id="loading"><div style="font-size:2rem;margin-bottom:8px">⏳</div>Meeting en cours...</div>
|
|
|
|
<!-- OVERVIEW -->
|
|
<div id="overview" class="section active">
|
|
<div class="grid-5">
|
|
<div class="card"><div class="kpi"><div class="value" id="kpi-squads">5</div><div class="label">Squads</div></div></div>
|
|
<div class="card"><div class="kpi"><div class="value" id="kpi-agents">8</div><div class="label">Agents autonomes</div></div></div>
|
|
<div class="card"><div class="kpi"><div class="value" id="kpi-crons">32</div><div class="label">Crons actifs</div></div></div>
|
|
<div class="card"><div class="kpi"><div class="value" id="kpi-meetings">4</div><div class="label">Meetings/semaine</div></div></div>
|
|
<div class="card"><div class="kpi"><div class="value" id="kpi-routes">313</div><div class="label">Routes chatbot</div></div></div>
|
|
</div>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<h3>📋 Dernier Daily</h3>
|
|
<div id="last-daily" class="synthesis">Aucun daily exécuté encore. Cliquez "Lancer Daily" ou attendez 09h00.</div>
|
|
</div>
|
|
<div class="card">
|
|
<h3>🏛️ Dernière Stratégie</h3>
|
|
<div id="last-strategy" class="synthesis">Aucune stratégie exécutée. Prochain: vendredi 16h00.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SQUADS -->
|
|
<div id="squads" class="section">
|
|
<div class="grid-5">
|
|
<div class="card squad-card infra" onclick="runSquad('infra')">
|
|
<h3>🏗️ INFRA</h3>
|
|
<div class="participant"><div class="avatar" style="background:#3b82f620">🧠</div><div class="info"><div class="name">CORTEX</div><div class="role">Lead · 13 checks */4h</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#3b82f620">🔍</div><div class="info"><div class="name">Gap Detector</div><div class="role">Cross-ref · */6h</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#3b82f620">🤖</div><div class="info"><div class="name">Agent Chef</div><div class="role">Auto-heal · */10</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#3b82f620">⚡</div><div class="info"><div class="name">Proactive</div><div class="role">Disk/RAM · */5</div></div></div>
|
|
<div class="actions"><button class="btn btn-primary" onclick="runSquad('infra')">▶ Run</button></div>
|
|
</div>
|
|
<div class="card squad-card dev" onclick="runSquad('dev')">
|
|
<h3>💻 DEV</h3>
|
|
<div class="participant"><div class="avatar" style="background:#06d6a020">✅</div><div class="info"><div class="name">NonReg Agent</div><div class="role">Auto-fix · */15</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#06d6a020">📸</div><div class="info"><div class="name">L99 Visual</div><div class="role">Screenshots · Playwright</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#06d6a020">🕵️</div><div class="info"><div class="name">L99 Dark</div><div class="role">Security · */8h</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#06d6a020">🧬</div><div class="info"><div class="name">Evolution</div><div class="role">Innovation · */6h</div></div></div>
|
|
<div class="actions"><button class="btn btn-primary" onclick="runSquad('dev')">▶ Run</button></div>
|
|
</div>
|
|
<div class="card squad-card security">
|
|
<h3>🔒 SECURITY</h3>
|
|
<div class="participant"><div class="avatar" style="background:#ef444420">🛡️</div><div class="info"><div class="name">Dark Tools</div><div class="role">Lead · Nuclei+Gitleaks</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#ef444420">🚫</div><div class="info"><div class="name">CrowdSec</div><div class="role">Ban IP · active</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#10b98120">🔐</div><div class="info"><div class="name">PHP Auth</div><div class="role">SSO Souverain · Cookie HMAC 30j</div></div></div>
|
|
<div class="actions"><button class="btn btn-primary" onclick="runSquad('security')">▶ Run</button></div>
|
|
</div>
|
|
<div class="card squad-card business">
|
|
<h3>💼 BUSINESS</h3>
|
|
<div class="participant"><div class="avatar" style="background:#f59e0b20">📎</div><div class="info"><div class="name">Paperclip CEO</div><div class="role">Lead · 716 agents</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#f59e0b20">🏢</div><div class="info"><div class="name">Enterprise</div><div class="role">10 modules</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#f59e0b20">💊</div><div class="info"><div class="name">Ethica</div><div class="role">157K HCPs</div></div></div>
|
|
<div class="actions"><button class="btn btn-primary" onclick="runSquad('business')">▶ Run</button></div>
|
|
</div>
|
|
<div class="card squad-card ia">
|
|
<h3>🧠 IA</h3>
|
|
<div class="participant"><div class="avatar" style="background:#8b5cf620">🦙</div><div class="info"><div class="name">Ollama</div><div class="role">9 modèles local</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#8b5cf620">📐</div><div class="info"><div class="name">Qdrant</div><div class="role">14,884 vectors</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#8b5cf620">🔬</div><div class="info"><div class="name">OSS Discovery</div><div class="role">690 repos · 585 skills</div></div></div>
|
|
<div class="actions"><button class="btn btn-primary" onclick="runSquad('ia')">▶ Run</button></div>
|
|
</div>
|
|
</div>
|
|
<div class="card" style="margin-top:16px"><h3>💬 Dernière synthèse squad</h3><div id="squad-result" class="synthesis">Sélectionnez un squad et cliquez Run.</div></div>
|
|
</div>
|
|
|
|
<!-- STRATEGY -->
|
|
<div id="strategy" class="section">
|
|
<div class="card">
|
|
<h3>🏛️ Weekly Strategy — Sommet</h3>
|
|
<div class="grid-2" style="margin:12px 0">
|
|
<div class="participant"><div class="avatar" style="background:#06d6a030;font-size:1.3rem">🧠</div><div class="info"><div class="name">WEVIA Master</div><div class="role">313 routes · 585 skills · 795 wiki</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#8b5cf630;font-size:1.3rem">♟️</div><div class="info"><div class="name">Claude Opus</div><div class="role">Architecte en chef · Contrôle · Fine-tune</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#3b82f630;font-size:1.3rem">📧</div><div class="info"><div class="name">WEVIA Life</div><div class="role">2079 emails · 598 opps · 407 risks</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#10b98130;font-size:1.3rem">⚔️</div><div class="info"><div class="name">Blade IA</div><div class="role">34 caps · GLM-5 · Sentinel v2.4</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#f59e0b30;font-size:1.3rem">🐟</div><div class="info"><div class="name">MiroFish</div><div class="role">Collab IA · :3050 + :5001</div></div></div>
|
|
<div class="participant"><div class="avatar" style="background:#ef444430;font-size:1.3rem">👑</div><div class="info"><div class="name">Agent Maître</div><div class="role">8 agents · 32 crons · Auto-heal</div></div></div>
|
|
</div>
|
|
<div class="actions"><button class="btn btn-primary" onclick="runStrategy()">🏛️ Lancer Stratégie</button></div>
|
|
<div id="strategy-result" class="synthesis" style="margin-top:12px">Prochain vendredi 16h00. Ou lancez manuellement.</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DISPATCH -->
|
|
<div id="dispatch" class="section">
|
|
<div class="card">
|
|
<h3>📡 Dispatch Actions → Agents</h3>
|
|
<div class="timeline" id="dispatch-list">
|
|
<div class="timeline-item"><div class="time">*/10</div><div class="content"><strong>Agent Chef</strong> — Restart services DOWN, heal infra</div></div>
|
|
<div class="timeline-item"><div class="time">*/6h</div><div class="content"><strong>Agent Evolution</strong> — Développer nouvelles features (Cerebras 235B)</div></div>
|
|
<div class="timeline-item"><div class="time">*/2h</div><div class="content"><strong>Agent Scanner</strong> — Enrichir wiki (12 sections)</div></div>
|
|
<div class="timeline-item"><div class="time">*/12h</div><div class="content"><strong>Agent Factory</strong> — Créer agents manquants</div></div>
|
|
<div class="timeline-item"><div class="time">*/15</div><div class="content"><strong>NonReg Agent</strong> — Détecter régressions + auto-fix</div></div>
|
|
<div class="timeline-item"><div class="time">*/4h</div><div class="content"><strong>CORTEX</strong> — Rapport global 13 checks → Mattermost</div></div>
|
|
<div class="timeline-item"><div class="time">*/6h</div><div class="content"><strong>Gap Detector</strong> — Vérifier couverture /opt/ vs fast.php</div></div>
|
|
<div class="timeline-item"><div class="time">*/6h</div><div class="content"><strong>RND Pipeline</strong> — Chercher innovations GitHub → wire → test</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SCHEDULE -->
|
|
<div id="schedule" class="section">
|
|
<div class="card">
|
|
<h3>📅 Planning Meetings</h3>
|
|
<div class="schedule-row"><div class="time">09:00</div><span class="type type-daily">DAILY</span><div style="flex:1;font-size:.85rem">Squad meetings AM — 5 squads en parallèle</div></div>
|
|
<div class="schedule-row"><div class="time">14:00</div><span class="type type-daily">DAILY</span><div style="flex:1;font-size:.85rem">Squad meetings PM — suivi + résolutions</div></div>
|
|
<div class="schedule-row"><div class="time">Lun 10h</div><span class="type type-weekly">WEEKLY</span><div style="flex:1;font-size:.85rem">Comité — Agent Chef + 5 leads de squad</div></div>
|
|
<div class="schedule-row"><div class="time">Ven 16h</div><span class="type type-strategy">STRATEGY</span><div style="flex:1;font-size:.85rem">Sommet — WEVIA + Opus + Life + Blade + MiroFish + Maître</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HISTORY -->
|
|
<div id="history" class="section">
|
|
<div class="card"><h3>📜 Historique Meetings</h3><div id="history-list" class="empty">Chargement...</div></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const API = '/api/wevia-meeting.php';
|
|
function show(id) {
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
|
|
document.getElementById(id).classList.add('active');
|
|
event.target.classList.add('active');
|
|
if (id === 'history') loadHistory();
|
|
}
|
|
async function apiCall(action, params = '') {
|
|
document.getElementById('loading').classList.add('show');
|
|
try {
|
|
const r = await fetch(`${API}?action=${action}${params}`);
|
|
/* HTML_GUARD_V2_BATCH */ const _t_d=await r.text(); let d=null; {var _q=(_t_d||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){d={error:"[HTTP "+(r.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{d=JSON.parse(_q)}catch(e){d={error:"[JSON] "+e.message}}}}
|
|
document.getElementById('loading').classList.remove('show');
|
|
return d;
|
|
} catch (e) {
|
|
document.getElementById('loading').classList.remove('show');
|
|
return { error: e.message };
|
|
}
|
|
}
|
|
async function runDaily() {
|
|
const d = await apiCall('daily');
|
|
if (d.chef_synthesis) {
|
|
document.getElementById('last-daily').textContent = d.chef_synthesis;
|
|
} else {
|
|
document.getElementById('last-daily').textContent = JSON.stringify(d, null, 2);
|
|
}
|
|
show('overview');
|
|
}
|
|
async function runWeekly() {
|
|
const d = await apiCall('weekly');
|
|
if (d.chef_synthesis) alert('Weekly terminé: ' + d.chef_synthesis.substring(0, 200));
|
|
}
|
|
async function runStrategy() {
|
|
const d = await apiCall('weekly');
|
|
document.getElementById('strategy-result').textContent = d.chef_synthesis || JSON.stringify(d);
|
|
show('strategy');
|
|
}
|
|
async function runSquad(name) {
|
|
const d = await apiCall('squad', `&squad=${name}`);
|
|
document.getElementById('squad-result').textContent = d.synthesis || JSON.stringify(d, null, 2);
|
|
}
|
|
async function loadHistory() {
|
|
const d = await apiCall('history');
|
|
const el = document.getElementById('history-list');
|
|
if (d.meetings && d.meetings.length) {
|
|
el.innerHTML = d.meetings.map(m => `<div class="schedule-row"><div class="time">${m.date}</div><div style="flex:1;font-size:.82rem">${m.file} (${Math.round(m.size/1024)}KB)</div></div>`).join('');
|
|
} else {
|
|
el.innerHTML = '<div class="empty">Aucun meeting archivé. Lancez un daily ou weekly.</div>';
|
|
}
|
|
}
|
|
// Auto-load last daily
|
|
fetch('/api/meeting-daily-latest.json').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
if(d.chef_synthesis) document.getElementById('last-daily').textContent=d.chef_synthesis;
|
|
}).catch(()=>{});
|
|
fetch('/api/meeting-weekly-latest.json').then(r=>r.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+r.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}})).then(d=>{
|
|
if(d.chef_synthesis) document.getElementById('last-strategy').textContent=d.chef_synthesis;
|
|
}).catch(()=>{});
|
|
</script>
|
|
<!-- CARTO_REMOVED -->
|
|
|
|
<!-- === 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>
|