- 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
205 lines
9.1 KiB
HTML
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>
|