124 lines
5.6 KiB
Python
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 · 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)'">↺</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 · 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 · ' + dot + ' ' + healthPct + '% (' + up + ' UP · ' + 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 ? ' · <span style="color:#10b981">just now</span>'
|
|
: ageMin < 60 ? ' · ' + ageMin + 'min ago'
|
|
: ' · ' + Math.floor(ageMin/60) + 'h ago';
|
|
}
|
|
}
|
|
kpi.innerHTML = 'All-IA Hub · ' + dot + ' ' + healthPct + '% (' + up + ' UP · ' + 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)
|