Files
html/token-health-dashboard.html
Opus cf8108658d feat(chrome-cdp): wave 308 - 8/8 CDP live + doctrine no-overlap
- chrome-profile-launch.sh: CDP port mapping 9222-9229 per profile
- chrome-profile-launch.sh: --remote-debugging-port ajoute + address 0.0.0.0
- chrome-profile-launch.sh: PID extraction direct pgrep (no tmpfile)
- chrome-profile-launch.sh: JSON output include cdp_port + cdp_listening verify
- api/cdp-status.php NEW: proxy 8 CDP ports + bypass CORS 127.0.0.1
- vnc-picker.html: toast-stack BR->BL (doctrine zero overlap)
- vnc-picker.html: live polling 5s via /api/cdp-status.php
- vnc-picker.html: summary badge CDP LIVE x/8 coverage %
- 8/8 Chrome profiles running (openai/anthropic/google/deepseek/mistral/poe/perplexity/hf)
- 49 chrome processes active with CDP ports 9222-9229 listening
- Doctrine 308 wired: CDP port mapping + status proxy centralise
- GOLD: gold_vnc_picker_toast_fix + gold_chrome_launch_w308 + gold_vnc_picker_live_status_w308
2026-04-24 11:26:27 +02:00

205 lines
9.1 KiB
HTML

<!DOCTYPE html>
<html lang="fr"><head><meta charset="UTF-8"><title>Token Health Dashboard · WEVAL</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>
*{margin:0;padding:0;box-sizing:border-box;font-family:-apple-system,'Segoe UI',sans-serif}
body{background:#060d1a;color:#e2e8f0;min-height:100vh;padding:20px}
.container{max-width:1200px;margin:0 auto}
h1{font-family:'Orbitron',sans-serif;background:linear-gradient(135deg,#06b6d4,#a855f7);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;
font-size:2rem;margin-bottom:8px;letter-spacing:1px}
.subtitle{color:#94a3b8;margin-bottom:24px;font-size:0.9rem}
.hero{background:linear-gradient(135deg,rgba(6,182,212,0.08),rgba(168,85,247,0.08));
border:1px solid rgba(6,182,212,0.25);border-radius:16px;padding:24px;margin-bottom:24px;
backdrop-filter:blur(12px)}
.hero-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:16px}
.stat{text-align:center}
.stat-val{font-family:'Orbitron',sans-serif;font-size:2.5rem;font-weight:900;line-height:1}
.stat-val.danger{color:#ef4444}
.stat-val.warn{color:#f59e0b}
.stat-val.success{color:#22c55e}
.stat-lbl{font-size:0.7rem;color:#64748b;text-transform:uppercase;letter-spacing:1.5px;margin-top:6px}
.providers{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;margin-bottom:24px}
.provider{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.15);
border-radius:12px;padding:14px 16px;transition:all 0.2s}
.provider.ok{border-left:3px solid #22c55e}
.provider.expired{border-left:3px solid #ef4444;background:rgba(239,68,68,0.05)}
.provider-name{font-weight:700;font-size:0.95rem;display:flex;justify-content:space-between;align-items:center}
.badge{padding:2px 8px;border-radius:10px;font-size:0.65rem;font-weight:700;text-transform:uppercase}
.badge.ok{background:rgba(34,197,94,0.15);color:#22c55e}
.badge.expired{background:rgba(239,68,68,0.15);color:#ef4444}
.provider-meta{font-size:0.75rem;color:#94a3b8;margin-top:6px}
.provider-action{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}
.btn{padding:4px 10px;border-radius:6px;font-size:0.7rem;font-weight:700;cursor:pointer;
text-decoration:none;border:none;transition:all 0.15s}
.btn-primary{background:linear-gradient(135deg,#06b6d4,#8b5cf6);color:#fff}
.btn-secondary{background:rgba(100,116,139,0.15);color:#94a3b8;border:1px solid rgba(100,116,139,0.25)}
.btn:hover{transform:translateY(-1px);opacity:0.9}
.runbook{background:rgba(15,23,42,0.8);border:1px solid rgba(100,116,139,0.15);
border-radius:12px;padding:20px;margin-top:24px}
.runbook h2{color:#06b6d4;font-size:1.2rem;margin-bottom:14px;
border-left:3px solid #06b6d4;padding-left:10px}
.runbook-step{padding:12px;margin:10px 0;background:rgba(11,13,21,0.6);border-radius:8px;border-left:3px solid rgba(168,85,247,0.4)}
.runbook-step strong{color:#c084fc}
code{background:#0f0f15;color:#22c55e;padding:2px 6px;border-radius:4px;font-size:0.85em}
</style><!-- DOCTRINE-60-UX-ENRICH cerebras-qwen-235b 20260424-110933 --><style id="doctrine60-ux-token-health-dashboard">
body::before {
content: '';
position: fixed;
width: 100%;
height: 100%;
background: radial-gradient(ellipse at center, rgba(0,0,0,0.12) 0%, rgba(0,0,0,0) 70%);
z-index: -1;
pointer-events: none;
}
.card, .btn, .kpi, .panel {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.enter-stagger {
opacity: 1;
transform: translateY(0);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.pulse, .active, .live-indicator, .online {
animation: pulse 3s ease-in-out infinite;
}
.card:hover {
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
border-color: var(--accent);
}
.modal, .chat, .speech, .overlay {
backdrop-filter: blur(12px);
}
</style>
</head><body>
<div class="container">
<h1>🔑 Token Health Dashboard</h1>
<p class="subtitle">Monitoring centralisé des credentials API providers · cascade souveraine WEVIA</p>
<section class="hero">
<div class="hero-stats" id="hero-stats">
<div class="stat"><div class="stat-val warn" id="stat-health"></div><div class="stat-lbl">Health</div></div>
<div class="stat"><div class="stat-val success" id="stat-ok"></div><div class="stat-lbl">Actifs</div></div>
<div class="stat"><div class="stat-val danger" id="stat-exp"></div><div class="stat-lbl">Expirés</div></div>
<div class="stat"><div class="stat-val" id="stat-total"></div><div class="stat-lbl">Total</div></div>
</div>
</section>
<h2 style="color:#06b6d4;margin-bottom:14px;border-left:3px solid #06b6d4;padding-left:10px">Providers Status</h2>
<div class="providers" id="providers-grid"><div style="color:#64748b">Chargement live...</div></div>
<section class="runbook">
<h2>📘 Runbook Rotation Tokens (manuel ou via Selenium blueprint)</h2>
<div class="runbook-step">
<strong>Groq</strong> · dashboard: <code>https://console.groq.com/keys</code><br>
1. Se connecter avec yacineutt@gmail.com<br>
2. Créer nouvelle API key · label: <code>weval-prod-2026</code><br>
3. Copier dans <code>/etc/weval/secrets.env</code><code>GROQ_API_KEY=gsk_...</code><br>
4. Reload: <code>sudo systemctl reload php8.5-fpm</code>
</div>
<div class="runbook-step">
<strong>SambaNova</strong> · dashboard: <code>https://cloud.sambanova.ai/</code><br>
1. Se connecter avec yacineutt@gmail.com<br>
2. Settings → API Keys → Generate new<br>
3. Update <code>SAMBANOVA_API_KEY=...</code> dans secrets.env
</div>
<div class="runbook-step">
<strong>Alibaba (DashScope)</strong> · dashboard: <code>https://dashscope.console.aliyun.com/</code><br>
1. Login Chinese account<br>
2. API Key Management → Create new<br>
3. Update <code>ALIBABA_API_KEY=sk-...</code> dans secrets.env
</div>
<div class="runbook-step">
<strong>GitHub PAT</strong> · dashboard: <code>https://github.com/settings/tokens?type=beta</code><br>
1. Regenerate → copy new <code>ghp_...</code><br>
2. Update <code>GITHUB_PAT=ghp_...</code> dans secrets.env<br>
3. Test: <code>sudo git push origin main</code>
</div>
<div class="runbook-step">
<strong>Blueprint Selenium automatisé</strong> · voir <code>/opt/obsidian-vault/doctrines/tips-6-mois-cracked.md</code><br>
Script Python + Selenium Grid Docker + Chrome + YacineUTT session sync
</div>
</section>
<section style="margin-top:20px;text-align:center;color:#64748b;font-size:0.85rem">
<button class="btn btn-primary" onclick="location.reload()">🔄 Refresh</button>
<a href="/wiki.html" class="btn btn-secondary">📘 Wiki</a>
<a href="/wtp-udock-coverage.html" class="btn btn-secondary">📊 WTP Dashboard</a>
</section>
</div>
<script>
fetch('/api/wevia-autonomy-status.json', {cache:'no-store'})
.then(r => r.json())
.then(d => {
const alerts = d.alerts || [];
const expiredList = alerts.filter(a => a.msg && /expir/i.test(a.msg)).map(a => {
const m = a.msg.match(/Token\s+(\w+)/i);
return m ? m[1].toLowerCase() : null;
}).filter(Boolean);
const KNOWN = ['cerebras','groq','deepseek','mistral','together','openrouter','sambanova',
'alibaba','gemini','nvidia-nim','cohere','huggingface','replicate','zhipu',
'anthropic','github','whatsapp'];
const health = Math.round((KNOWN.length - expiredList.length) / KNOWN.length * 100);
document.getElementById('stat-health').textContent = health + '%';
document.getElementById('stat-ok').textContent = KNOWN.length - expiredList.length;
document.getElementById('stat-exp').textContent = expiredList.length;
document.getElementById('stat-total').textContent = KNOWN.length;
const grid = document.getElementById('providers-grid');
grid.innerHTML = '';
KNOWN.forEach(p => {
const isExpired = expiredList.includes(p);
const card = document.createElement('div');
card.className = 'provider ' + (isExpired ? 'expired' : 'ok');
card.innerHTML = `
<div class="provider-name">
<span>${p}</span>
<span class="badge ${isExpired?'expired':'ok'}">${isExpired?'EXPIRED':'OK'}</span>
</div>
<div class="provider-meta">${isExpired ? '⚠️ Rotation requise' : '✅ Active'}</div>
${isExpired ? '<div class="provider-action"><button class="btn btn-primary" onclick="alert(\'Voir runbook dans cette page pour '+p+'\')">Rotate</button></div>' : ''}
`;
grid.appendChild(card);
});
})
.catch(e => {
document.getElementById('providers-grid').innerHTML = '<div style="color:#ef4444">Erreur: ' + e.message + '</div>';
});
</script>
<!-- WTP_UDOCK_V1 (Opus 21-avr t39) --><script src=\/wtp-unified-dock.js\ defer></script>
<script src="/opus-antioverlap-doctrine.js?v=1776806662" defer></script>
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-token-health-dashboard">
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
setTimeout(() => {
entry.target.classList.add('enter-stagger');
}, index * 80);
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.card, .btn, .kpi, .panel').forEach(el => observer.observe(el));
</script>
</body></html>