Files
weval-l99/v137_refresh.py
2026-04-24 04:38:58 +02:00

124 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""V137 - Enrich V135 KPI banner with:
- 'last update Xmin ago' indicator
- Manual refresh button ↻ next to banner
- Fresh scan trigger (re-fetch screens-health.json no-cache)
- Visual feedback during refresh (spin animation)
"""
import sys
src, dst = sys.argv[1], sys.argv[2]
with open(src, "r", encoding="utf-8") as f:
c = f.read()
if "V137-REFRESH" in c:
print("ALREADY", file=sys.stderr)
sys.exit(0)
# Find the current span line and add a refresh button before it
old_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 &middot; consolidation 84 dashboards</span>'
new_block = '''<!-- V137-REFRESH: manual refresh button + fresh indicator -->
<button id="v137-refresh-btn" onclick="event.stopPropagation();__v137RefreshHealth()" style="margin-left:auto;background:transparent;border:1px solid var(--bd);color:var(--mu);border-radius:4px;padding:2px 6px;cursor:pointer;font-size:10px;transition:all 0.15s" title="Refresh health live" onmouseover="this.style.color='var(--ac)';this.style.borderColor='var(--vl)'" onmouseout="this.style.color='var(--mu)';this.style.borderColor='var(--bd)'">&#8634;</button>
<span id="v135-kpi-live" onclick="__v136ShowHealthModal()" style="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 &middot; 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_block, 1)
# Refactor V135 banner update JS to compute freshness + be reusable
# Find and replace the V135 IIFE with a named function + initial call
old_iife = """/* V135-KPI-BANNER: load screens-health summary and update banner (zero impact on UX if fetch fails) */
(function(){
const kpi = document.getElementById('v135-kpi-live');
if (!kpi) return;
fetch('/api/screens-health.json', {cache: 'no-store'}).then(r => r.ok ? r.json() : null).then(d => {
if (!d || !d.counts) return;
const c = d.counts;
const total = d.total || 0;
const up = c.UP || 0;
const broken = c.BROKEN || 0;
const down = c.DOWN || 0;
const phantom = c.PHANTOM || 0;
const active = total - phantom;
const healthPct = active ? Math.round((up / active) * 100) : 0;
const dot = (broken + down === 0) ? '\\u{1F7E2}' : (broken + down < 20 ? '\\u{1F7E1}' : '\\u{1F534}');
kpi.innerHTML = 'All-IA Hub &middot; ' + dot + ' ' + healthPct + '% (' + up + ' UP &middot; ' + broken + ' broken)';
kpi.title = 'Platform health: ' + up + ' UP / ' + broken + ' BROKEN / ' + down + ' DOWN / ' + phantom + ' phantom (total ' + total + ')';
}).catch(_ => {});
})();"""
new_named = """/* V135-KPI-BANNER (V137 refactored): named async function, reusable by V137 refresh */
async function __v135UpdateHealthBanner(){
const kpi = document.getElementById('v135-kpi-live');
if (!kpi) return;
try {
const r = await fetch('/api/screens-health.json', {cache: 'no-store'});
if (!r.ok) return;
const d = await r.json();
if (!d || !d.counts) return;
const c = d.counts;
const total = d.total || 0;
const up = c.UP || 0;
const broken = c.BROKEN || 0;
const down = c.DOWN || 0;
const phantom = c.PHANTOM || 0;
const active = total - phantom;
const healthPct = active ? Math.round((up / active) * 100) : 0;
const dot = (broken + down === 0) ? '\\u{1F7E2}' : (broken + down < 20 ? '\\u{1F7E1}' : '\\u{1F534}');
// V137: compute scan age
let ageStr = '';
if (d.generated_at) {
const scanMs = new Date(d.generated_at).getTime();
if (!isNaN(scanMs)) {
const ageMin = Math.floor((Date.now() - scanMs) / 60000);
ageStr = ageMin < 1 ? ' &middot; <span style="color:#10b981">just now</span>'
: ageMin < 60 ? ' &middot; ' + ageMin + 'min ago'
: ' &middot; ' + Math.floor(ageMin/60) + 'h ago';
}
}
kpi.innerHTML = 'All-IA Hub &middot; ' + dot + ' ' + healthPct + '% (' + up + ' UP &middot; ' + broken + ' broken)' + ageStr;
kpi.title = 'Platform health: ' + up + ' UP / ' + broken + ' BROKEN / ' + down + ' DOWN / ' + phantom + ' phantom (total ' + total + ')\\nScan: ' + (d.generated_at || 'n/a') + '\\nClick: détail · Bouton \\u21BA: refresh';
} catch (_) {}
}
/* V137-REFRESH: manual refresh with spin animation */
async function __v137RefreshHealth(){
const btn = document.getElementById('v137-refresh-btn');
if (btn) {
btn.style.animation = 'v137spin 0.8s linear infinite';
btn.style.color = 'var(--vl)';
btn.disabled = true;
}
await __v135UpdateHealthBanner();
if (btn) {
setTimeout(() => {
btn.style.animation = '';
btn.style.color = '';
btn.disabled = false;
}, 400);
}
}
/* Initial load */
__v135UpdateHealthBanner();"""
if old_iife in c:
c = c.replace(old_iife, new_named, 1)
else:
print("V135 IIFE anchor missing", file=sys.stderr)
sys.exit(1)
# Add CSS keyframe for spin animation (before </style>)
style_close = '</style>'
style_add = '''/* V137-REFRESH spin keyframe */
@keyframes v137spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
#v137-refresh-btn:hover{background:var(--bg3)}
#v137-refresh-btn:disabled{opacity:0.7;cursor:wait}
</style>'''
if style_close in c:
c = c.replace(style_close, style_add, 1)
with open(dst, "w", encoding="utf-8") as f:
f.write(c)
print(f"OK size={len(c)}", file=sys.stderr)