123 lines
6.6 KiB
Python
123 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""V136 - Make V135 KPI banner clickable: opens inline modal with broken URLs detail
|
|
Modal: positioned fixed, shows top 10 broken with code/url/ms, dismissible by click outside or Escape.
|
|
"""
|
|
import sys
|
|
src, dst = sys.argv[1], sys.argv[2]
|
|
with open(src, "r", encoding="utf-8") as f:
|
|
c = f.read()
|
|
|
|
if "V136-HEALTH-MODAL" in c:
|
|
print("ALREADY", file=sys.stderr)
|
|
sys.exit(0)
|
|
|
|
# Change the span to add cursor:pointer and id + click handler. It's already id=v135-kpi-live
|
|
# Wrap in a click-capable element
|
|
|
|
# Find the span line
|
|
old_span = '<span id="v135-kpi-live" style="margin-left:auto;color:var(--mu);font-size:9px" title="Platform health live">All-IA Hub · consolidation 84 dashboards</span>'
|
|
new_span = '<span id="v135-kpi-live" onclick="__v136ShowHealthModal()" style="margin-left:auto;color:var(--mu);font-size:9px;cursor:pointer;text-decoration:underline;text-decoration-style:dotted;text-decoration-color:rgba(255,255,255,0.2)" title="Platform health live · click pour détail">All-IA Hub · consolidation 84 dashboards</span>'
|
|
|
|
if old_span not in c:
|
|
print("span anchor missing", file=sys.stderr)
|
|
sys.exit(1)
|
|
c = c.replace(old_span, new_span, 1)
|
|
|
|
# Add modal HTML + JS logic. Insert modal markup at the END of the xnav div
|
|
anchor_xnav_close = '<span id="v135-kpi-live"' # modal placed before this won't work; place at body end
|
|
# Alternative: place after the xnav closing div, before the tabs
|
|
tabs_marker = '<button class="tab on" data-view="chat">CHAT MULTIAGENT</button>'
|
|
modal_html = '''<!-- V136-HEALTH-MODAL: in-page modal for broken URLs drill-down -->
|
|
<div id="v136-health-modal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);z-index:10000;align-items:center;justify-content:center;padding:24px" onclick="if(event.target.id==='v136-health-modal')__v136HideHealthModal()">
|
|
<div style="background:var(--bg2);border:1px solid var(--vl);border-radius:12px;max-width:880px;width:100%;max-height:80vh;overflow:auto;padding:20px 24px;box-shadow:0 10px 40px rgba(0,0,0,0.5)">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--bd);padding-bottom:10px;margin-bottom:14px">
|
|
<div>
|
|
<div style="font-size:14px;font-weight:600;color:var(--ac)">🏥 Platform Health · Detail</div>
|
|
<div id="v136-modal-summary" style="font-size:10px;color:var(--mu);margin-top:2px">Loading…</div>
|
|
</div>
|
|
<button onclick="__v136HideHealthModal()" style="background:transparent;border:1px solid var(--bd);color:var(--mu);border-radius:6px;padding:4px 10px;cursor:pointer;font-size:11px">ESC</button>
|
|
</div>
|
|
<div id="v136-modal-content" style="font-size:11px;color:var(--fg);font-family:monospace;line-height:1.6">Loading…</div>
|
|
<div style="border-top:1px solid var(--bd);margin-top:14px;padding-top:10px;font-size:9px;color:var(--mu)">
|
|
Source: <code>/api/screens-health.json</code> · maintenu par cron ecosystem
|
|
</div>
|
|
</div>
|
|
</div>
|
|
''' + tabs_marker
|
|
|
|
if tabs_marker in c:
|
|
c = c.replace(tabs_marker, modal_html, 1)
|
|
|
|
# Add JS functions before the V135-KPI-BANNER IIFE
|
|
js_marker = "/* V135-KPI-BANNER: load screens-health summary"
|
|
js_add = '''/* V136-HEALTH-MODAL: show/hide drill-down modal with broken URL list */
|
|
function __v136ShowHealthModal(){
|
|
const modal = document.getElementById('v136-health-modal');
|
|
const content = document.getElementById('v136-modal-content');
|
|
const summary = document.getElementById('v136-modal-summary');
|
|
if (!modal || !content) return;
|
|
modal.style.display = 'flex';
|
|
content.textContent = 'Fetching /api/screens-health.json ...';
|
|
fetch('/api/screens-health.json', {cache: 'no-store'}).then(r => r.ok ? r.json() : null).then(d => {
|
|
if (!d || !d.counts) { content.textContent = 'Pas de data health disponible.'; return; }
|
|
const c = d.counts;
|
|
const total = d.total || 0;
|
|
summary.textContent = 'Scan ' + (d.generated_at || '') + ' · Total ' + total + ' URLs · UP ' + (c.UP||0) + ' · SLOW ' + (c.SLOW||0) + ' · BROKEN ' + (c.BROKEN||0) + ' · DOWN ' + (c.DOWN||0) + ' · PHANTOM ' + (c.PHANTOM||0);
|
|
const byUrl = d.by_url || {};
|
|
const broken = [];
|
|
const slow = [];
|
|
for (const url in byUrl) {
|
|
const info = byUrl[url];
|
|
if (info.status === 'BROKEN' || info.status === 'DOWN') broken.push({url, ...info});
|
|
else if (info.status === 'SLOW') slow.push({url, ...info});
|
|
}
|
|
broken.sort((a,b) => (b.code||0) - (a.code||0));
|
|
slow.sort((a,b) => (b.ms||0) - (a.ms||0));
|
|
let html = '';
|
|
if (broken.length) {
|
|
html += '<div style="color:#ef4444;font-weight:600;margin-bottom:6px">🚫 BROKEN/DOWN (' + broken.length + '):</div>';
|
|
broken.slice(0, 30).forEach(b => {
|
|
html += '<div style="padding:3px 8px;border-left:3px solid #ef4444;margin-bottom:4px;background:rgba(239,68,68,0.05)">' +
|
|
'<span style="color:#ef4444;font-weight:600">' + (b.status||'?') + '</span> ' +
|
|
'<span style="color:#f59e0b">' + (b.code||'?') + '</span> ' +
|
|
'<a href="' + b.url + '" target="_blank" style="color:var(--ac);text-decoration:none">' + b.url + '</a> ' +
|
|
'<span style="color:var(--mu);float:right">' + (b.ms||'?') + 'ms</span>' +
|
|
'</div>';
|
|
});
|
|
} else {
|
|
html += '<div style="color:#10b981;font-weight:600">✅ No broken URLs</div>';
|
|
}
|
|
if (slow.length) {
|
|
html += '<div style="color:#f59e0b;font-weight:600;margin-top:12px;margin-bottom:6px">📤 SLOW top 10 (>2s):</div>';
|
|
slow.slice(0, 10).forEach(s => {
|
|
html += '<div style="padding:2px 8px;border-left:3px solid #f59e0b;margin-bottom:3px">' +
|
|
'<span style="color:#f59e0b">SLOW</span> ' +
|
|
'<a href="' + s.url + '" target="_blank" style="color:var(--ac);text-decoration:none">' + s.url + '</a> ' +
|
|
'<span style="color:var(--mu);float:right">' + (s.ms||'?') + 'ms</span>' +
|
|
'</div>';
|
|
});
|
|
}
|
|
content.innerHTML = html;
|
|
}).catch(err => { content.textContent = 'Erreur fetch: ' + err; });
|
|
}
|
|
function __v136HideHealthModal(){
|
|
const modal = document.getElementById('v136-health-modal');
|
|
if (modal) modal.style.display = 'none';
|
|
}
|
|
/* Escape key closes modal */
|
|
document.addEventListener('keydown', function(e){
|
|
if (e.key === 'Escape') {
|
|
const modal = document.getElementById('v136-health-modal');
|
|
if (modal && modal.style.display !== 'none') __v136HideHealthModal();
|
|
}
|
|
});
|
|
|
|
''' + js_marker
|
|
|
|
if js_marker in c:
|
|
c = c.replace(js_marker, js_add, 1)
|
|
|
|
with open(dst, "w", encoding="utf-8") as f:
|
|
f.write(c)
|
|
print(f"OK size={len(c)}", file=sys.stderr)
|