246 lines
12 KiB
HTML
246 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<title>👤 Owner Actions Tracker — Yacine's Physical Actions · WEVAL</title>
|
||
<style>
|
||
*,*::before,*::after{box-sizing:border-box}
|
||
body{margin:0;background:linear-gradient(135deg,#0a0e1a 0%,#1a1f3a 100%);color:#e2e8f0;font-family:-apple-system,'Segoe UI',sans-serif;min-height:100vh;padding:20px}
|
||
.wrap{max-width:1400px;margin:0 auto}
|
||
.header{display:flex;align-items:center;justify-content:space-between;padding:20px 24px;background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.25);border-radius:12px;margin-bottom:20px}
|
||
.header h1{margin:0;font-size:22px;font-weight:600}
|
||
.header p{margin:4px 0 0;color:#94a3b8;font-size:13px}
|
||
.badges{display:flex;gap:10px}
|
||
.badge{padding:6px 14px;background:rgba(16,185,129,0.15);border:1px solid rgba(16,185,129,0.4);border-radius:20px;font-size:12px;font-weight:600;color:#10b981}
|
||
.badge.warn{background:rgba(245,158,11,0.15);border-color:rgba(245,158,11,0.4);color:#f59e0b}
|
||
.badge.crit{background:rgba(239,68,68,0.15);border-color:rgba(239,68,68,0.4);color:#ef4444}
|
||
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;margin-bottom:24px}
|
||
.stat{padding:18px 20px;background:rgba(30,41,59,0.5);border:1px solid rgba(99,102,241,0.15);border-radius:10px}
|
||
.stat-v{font-size:32px;font-weight:700;color:#22d3ee;line-height:1}
|
||
.stat-l{color:#94a3b8;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;margin-top:6px}
|
||
.items{display:grid;gap:16px}
|
||
.item{background:rgba(30,41,59,0.6);border:1px solid rgba(99,102,241,0.2);border-radius:12px;padding:20px 24px;transition:all 0.2s}
|
||
.item:hover{border-color:rgba(99,102,241,0.5);transform:translateX(4px)}
|
||
.item.critical{border-left:4px solid #ef4444}
|
||
.item.high{border-left:4px solid #f59e0b}
|
||
.item.medium{border-left:4px solid #22d3ee}
|
||
.item.low{border-left:4px solid #64748b}
|
||
.item-head{display:flex;align-items:center;gap:12px;margin-bottom:10px}
|
||
.item-icon{font-size:28px}
|
||
.item-title{flex:1;font-size:16px;font-weight:600;color:#f1f5f9}
|
||
.item-prio{padding:3px 10px;border-radius:12px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.8px}
|
||
.item-prio.critical{background:rgba(239,68,68,0.2);color:#ef4444}
|
||
.item-prio.high{background:rgba(245,158,11,0.2);color:#f59e0b}
|
||
.item-prio.medium{background:rgba(34,211,238,0.2);color:#22d3ee}
|
||
.item-prio.low{background:rgba(100,116,139,0.2);color:#94a3b8}
|
||
.item-cat{display:inline-block;margin:0 0 10px;padding:2px 8px;background:rgba(99,102,241,0.15);border-radius:6px;font-size:11px;color:#a78bfa}
|
||
.item-why{color:#cbd5e1;font-size:14px;line-height:1.5;margin:10px 0 14px;padding:12px;background:rgba(15,23,42,0.6);border-radius:8px;border-left:3px solid #a78bfa}
|
||
.item-why b{color:#f1f5f9}
|
||
.actions-list{list-style:none;padding:0;margin:10px 0}
|
||
.actions-list li{padding:8px 0 8px 28px;position:relative;color:#e2e8f0;font-size:13.5px;line-height:1.5}
|
||
.actions-list li::before{content:'▸';position:absolute;left:8px;color:#22d3ee;font-weight:700}
|
||
.item-footer{display:flex;flex-wrap:wrap;gap:16px;margin-top:14px;padding-top:12px;border-top:1px solid rgba(99,102,241,0.15);font-size:12px;color:#94a3b8}
|
||
.item-footer strong{color:#f1f5f9}
|
||
.item-footer .eta{color:#f59e0b}
|
||
.item-footer .value{color:#10b981;font-weight:600}
|
||
.cta-row{display:flex;gap:10px;margin-top:14px}
|
||
.cta{padding:8px 16px;background:rgba(99,102,241,0.2);border:1px solid rgba(99,102,241,0.4);border-radius:8px;color:#a78bfa;text-decoration:none;font-size:12px;font-weight:600;transition:all 0.2s;cursor:pointer;display:inline-block}
|
||
.cta:hover{background:rgba(99,102,241,0.35);transform:translateY(-1px)}
|
||
.cta.done{background:rgba(16,185,129,0.2);border-color:rgba(16,185,129,0.4);color:#10b981}
|
||
.cta.done:hover{background:rgba(16,185,129,0.35)}
|
||
.note{padding:10px 14px;background:rgba(16,185,129,0.08);border:1px dashed rgba(16,185,129,0.3);border-radius:8px;font-size:12px;color:#86efac;margin-top:10px}
|
||
.loading{padding:40px;text-align:center;color:#94a3b8}
|
||
.back{display:inline-block;margin-top:20px;padding:10px 18px;background:rgba(99,102,241,0.15);border:1px solid rgba(99,102,241,0.3);border-radius:8px;color:#a78bfa;text-decoration:none;font-size:13px}
|
||
.back:hover{background:rgba(99,102,241,0.3)}
|
||
.summary-box{padding:16px 20px;background:rgba(16,185,129,0.08);border:1px solid rgba(16,185,129,0.3);border-radius:10px;margin-bottom:20px;color:#86efac;font-size:13.5px;line-height:1.6}
|
||
</style>
|
||
<!-- DOCTRINE-60-UX-ENRICH direct-inject-20260424-144054 -->
|
||
<style id="doctrine60-ux-direct">
|
||
|
||
/* DOCTRINE-60-UX-ENRICH injected-direct */
|
||
body::before {
|
||
content: '';
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100vw; height: 100vh;
|
||
background: radial-gradient(circle at 50% 50%, rgba(100,180,255,0.08), transparent 60%);
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
.card, .kpi, .panel, .btn {
|
||
transition: all 0.3s cubic-bezier(0.2,0,0.1,1);
|
||
}
|
||
.card:hover, .kpi:hover, .panel:hover {
|
||
box-shadow: 0 4px 20px rgba(100,180,255,0.2);
|
||
border-color: rgba(100,180,255,0.5);
|
||
}
|
||
@keyframes pulseD60 {
|
||
0%,100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.7; transform: scale(1.05); }
|
||
}
|
||
.pulse, .live-indicator, .active, .online {
|
||
animation: pulseD60 3s ease-in-out infinite;
|
||
}
|
||
.modal, .chat, .speech, .overlay {
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
}
|
||
.enter-stagger {
|
||
animation: enterStagD60 0.5s cubic-bezier(0.2,0,0.1,1) forwards;
|
||
}
|
||
@keyframes enterStagD60 {
|
||
from { opacity: 0; transform: translateY(20px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<div class="header">
|
||
<div>
|
||
<h1>👤 Owner Actions Tracker</h1>
|
||
<p>Les seules actions qui nécessitent Yacine physiquement — tout le reste est automatisé à 100% (6σ)</p>
|
||
</div>
|
||
<div class="badges">
|
||
<span class="badge">19/19 automatable DONE</span>
|
||
<span id="blocked-count" class="badge warn">— blocked</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="summary-box">
|
||
<strong>🎯 État actuel</strong> : l'écosystème WEVAL est à <b>100% sur tout ce qui est automatable</b> (Plan 19/19 done · Risk 100% · NonReg 153/153 · Heatmap 144/144 · Qdrant 0 empty · Bias 20/20 · Alignment 10/10). Les items ci-dessous sont <b>strictement user-action-required</b> — doctrine #4 honnête.
|
||
</div>
|
||
|
||
<div class="stats" id="stats"></div>
|
||
|
||
<div id="items-container" class="loading">Loading owner actions…</div>
|
||
|
||
<a href="/weval-technology-platform.html" class="back">← Retour WTP (point d'entrée unique)</a>
|
||
</div>
|
||
|
||
<script>
|
||
async function load() {
|
||
try {
|
||
const r = await fetch('/api/wevia-owner-actions-tracker.php?t=' + Date.now());
|
||
const d = await r.json();
|
||
renderStats(d);
|
||
renderItems(d.items);
|
||
document.getElementById('blocked-count').textContent = d.total + ' blocked (Yacine-only)';
|
||
} catch(e) {
|
||
document.getElementById('items-container').innerHTML = '<div class="loading">❌ Erreur: '+e.message+'</div>';
|
||
}
|
||
}
|
||
|
||
function renderStats(d) {
|
||
const s = d.summary || {};
|
||
const byP = d.by_priority || {};
|
||
document.getElementById('stats').innerHTML = `
|
||
<div class="stat">
|
||
<div class="stat-v">${d.total}</div>
|
||
<div class="stat-l">Owner actions pending</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-v" style="color:#10b981">${s.automatable_closed || '—'}</div>
|
||
<div class="stat-l">Automatable closed</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-v" style="color:#ef4444">${byP.critical || 0}</div>
|
||
<div class="stat-l">Critical priority</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-v" style="color:#f59e0b">${byP.high || 0}</div>
|
||
<div class="stat-l">High priority</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat-v" style="color:#10b981">${(d.total_value_keur || 0)}k€</div>
|
||
<div class="stat-l">Total pipeline value</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderItems(items) {
|
||
const c = document.getElementById('items-container');
|
||
c.className = 'items';
|
||
c.innerHTML = items.map(it => {
|
||
const actions = (it.action_required || []).map(a => `<li>${escapeHtml(a)}</li>`).join('');
|
||
const prio = it.priority || 'medium';
|
||
const ctaRow = buildCTA(it);
|
||
const noteBlock = it.note ? `<div class="note">ℹ️ ${escapeHtml(it.note)}</div>` : '';
|
||
return `
|
||
<div class="item ${prio}">
|
||
<div class="item-head">
|
||
<span class="item-icon">${it.icon || '📌'}</span>
|
||
<div class="item-title">${escapeHtml(it.title || '?')}</div>
|
||
<span class="item-prio ${prio}">${prio}</span>
|
||
</div>
|
||
${it.category ? `<span class="item-cat">${escapeHtml(it.category)}</span>` : ''}
|
||
<div class="item-why"><b>Pourquoi bloqué :</b> ${escapeHtml(it.why_blocked || '')}</div>
|
||
${actions ? `<div><b style="font-size:12px;color:#94a3b8;text-transform:uppercase;letter-spacing:0.5px">Action requise :</b><ul class="actions-list">${actions}</ul></div>` : ''}
|
||
<div class="item-footer">
|
||
${it.contact ? `<span>👤 <strong>${escapeHtml(it.contact)}</strong></span>` : ''}
|
||
${it.eta_realistic ? `<span class="eta">⏱️ ETA: ${escapeHtml(it.eta_realistic)}</span>` : ''}
|
||
${it.value_keur ? `<span class="value">💰 ${it.value_keur}k€ value</span>` : ''}
|
||
</div>
|
||
${ctaRow}
|
||
${noteBlock}
|
||
</div>
|
||
`;
|
||
}).join('');
|
||
}
|
||
|
||
function buildCTA(it) {
|
||
const buttons = [];
|
||
if (it.compose_template) buttons.push(`<a href="${it.compose_template}" class="cta" target="_blank">📧 Open compose template</a>`);
|
||
if (it.id === 'act_69e53d5d5e09c') buttons.push(`<a href="https://portal.azure.com" class="cta" target="_blank">🔗 Open Azure Portal</a>`);
|
||
if (it.id === 'act_69e53d5d9aa8d') buttons.push(`<a href="https://www.ovh.com/manager" class="cta" target="_blank">🔗 Open OVH Manager</a>`);
|
||
if (it.id === 'blade_razer_wake') buttons.push(`<a href="/tasks-live-opus5.html" class="cta" target="_blank">📊 Verify Blade status</a>`);
|
||
buttons.push(`<button class="cta done" onclick="markDone('${it.id}')" title="Mark as done once action completed">✅ Mark done (once done)</button>`);
|
||
return `<div class="cta-row">${buttons.join('')}</div>`;
|
||
}
|
||
|
||
async function markDone(id) {
|
||
if (!confirm('Mark ' + id + ' as done ? (update plan)')) return;
|
||
try {
|
||
const r = await fetch('/api/wevia-v71-risk-halu-plan.php?action=plan_update&id=' + encodeURIComponent(id) + '&status=done');
|
||
const j = await r.json();
|
||
if (j.ok) {
|
||
alert('✅ Marked done');
|
||
load();
|
||
} else alert('❌ Error: ' + JSON.stringify(j));
|
||
} catch(e) { alert('❌ ' + e.message); }
|
||
}
|
||
|
||
function escapeHtml(s) {
|
||
return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||
}
|
||
|
||
load();
|
||
// Auto-refresh every 60s
|
||
setInterval(load, 60000);
|
||
</script>
|
||
|
||
<script src="/api/a11y-auto-enhancer.js" defer></script>
|
||
<!-- WTP_UDOCK_V1 (Opus 21-avr t34final) --><script src="/wtp-unified-dock.js" defer></script>
|
||
<script src="/opus-antioverlap-doctrine.js?v=1776776094" defer></script>
|
||
<!-- DOCTRINE-60-UX-JS --><script id="doctrine60-ux-js-direct">
|
||
|
||
// DOCTRINE-60-UX-JS staggered entrance
|
||
(function(){
|
||
if (!('IntersectionObserver' in window)) return;
|
||
const obs = new IntersectionObserver((entries) => {
|
||
entries.forEach((e, i) => {
|
||
if (e.isIntersecting) {
|
||
setTimeout(() => e.target.classList.add('enter-stagger'), i * 80);
|
||
obs.unobserve(e.target);
|
||
}
|
||
});
|
||
});
|
||
document.querySelectorAll('.card, .kpi, .panel').forEach(el => obs.observe(el));
|
||
})();
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|