fix: dégradation gracieuse Chart.js CDN, WS cleanup, guards null

- C1: guard typeof Chart === 'undefined' dans initChart(), handler onerror
  sur le tag <script> CDN, message fallback "Graphique indisponible"
- C1: guards if(!chartInst) return dans ajouterPointChart(), chargerHistorique(),
  traiterMessageWS(), mettreAJourCouleursFond()
- I1: nettoyage ws.close() en début de connecterWS() avant toute reconnexion
- I3: guard if(!chartInst) au début du .then() de chargerHistorique()
- I4: tooltip backgroundColor remplacé par cssVar('--bg-3') (plus hardcodé)
- M2: sec = sec || 0 dans fmtUptime(), b = b || 0 dans fmtRam()
- M3: emoji ☀️ remplacé par ☼ (U+263C, caractère texte pur)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 16:58:42 +02:00
parent d9d2db8b5e
commit f75d35ae5b
+28 -8
View File
@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js" onerror="(function(){var w=document.querySelector('.chart-wrap');if(w){w.innerHTML='<div style=&quot;display:flex;align-items:center;justify-content:center;height:100%;color:var(--err);font-family:var(--font-terminal);font-size:13px;&quot;>Graphique indisponible (Chart.js non chargé)</div>';}}())"></script>
<style>
/* ── Variables thème dark ── */
:root[data-theme="dark"]{
@@ -422,6 +422,7 @@ var statusTimer = null;
UTILITAIRES
────────────────────────────────────────────────────────── */
function fmtUptime(sec){
sec = sec || 0;
var h = Math.floor(sec/3600);
var m = Math.floor((sec%3600)/60);
var s = sec%60;
@@ -434,6 +435,7 @@ function fmtHeure(ts){
}
function fmtRam(b){
b = b || 0;
if(b>=1024)return (b/1024).toFixed(0)+' ko';
return b+' o';
}
@@ -450,7 +452,7 @@ btnTheme.addEventListener('click',function(){
var html = document.documentElement;
var th = html.getAttribute('data-theme')==='dark'?'light':'dark';
html.setAttribute('data-theme',th);
btnTheme.textContent = th==='dark'?'☾':'☀️';
btnTheme.textContent = th==='dark'?'☾':'';
if(chartInst) mettreAJourCouleursFond();
localStorage.setItem('theme',th);
});
@@ -459,11 +461,12 @@ btnTheme.addEventListener('click',function(){
var th = localStorage.getItem('theme');
if(th){
document.documentElement.setAttribute('data-theme',th);
btnTheme.textContent = th==='dark'?'☾':'☀️';
btnTheme.textContent = th==='dark'?'☾':'';
}
})();
function mettreAJourCouleursFond(){
if(!chartInst) return;
var bg = cssVar('--bg-3');
var ink3 = cssVar('--ink-3');
var border1 = cssVar('--border-1');
@@ -479,6 +482,11 @@ function mettreAJourCouleursFond(){
CHART.JS
────────────────────────────────────────────────────────── */
function initChart(noms){
if(typeof Chart === 'undefined'){
var w = document.querySelector('.chart-wrap');
if(w) w.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--err);font-family:var(--font-terminal);font-size:13px;">Graphique indisponible (Chart.js non chargé)</div>';
return;
}
var ctx = document.getElementById('chartTemp').getContext('2d');
var datasets = noms.map(function(n,i){
return {
@@ -511,7 +519,7 @@ function initChart(noms){
}
},
tooltip:{
backgroundColor:'rgba(42,35,29,.95)',
backgroundColor:cssVar('--bg-3'),
titleColor:cssVar('--ink-2'),
bodyColor:cssVar('--ink-1'),
borderColor:cssVar('--border-2'),
@@ -549,6 +557,7 @@ function initChart(noms){
function ajouterPointChart(label,valeurs){
/* valeurs = [v0, v1, v2] — null si erreur */
if(!chartInst) return;
var d = chartInst.data;
d.labels.push(label);
for(var i=0;i<3;i++){
@@ -565,6 +574,7 @@ function chargerHistorique(){
fetch('/api/history')
.then(function(r){return r.json();})
.then(function(hist){
if(!chartInst) return;
if(!hist||!hist.length)return;
var d = chartInst.data;
d.labels=[];
@@ -673,6 +683,10 @@ function setLedWs(etat){
}
function connecterWS(){
if(ws && ws.readyState !== WebSocket.CLOSED && ws.readyState !== WebSocket.CLOSING){
ws.onclose = null;
ws.close();
}
var proto = location.protocol==='https:'?'wss':'ws';
var url = proto+'://'+location.host+'/ws';
setLedWs('warn');
@@ -707,10 +721,16 @@ function traiterMessageWS(data){
// Mise à jour des vrais noms au premier message
if(premierMessage){
premierMessage=false;
data.sondes.forEach(function(s,i){
if(s.nom){ nomsReels[i]=s.nom; chartInst.data.datasets[i].label=s.nom; }
});
chartInst.update('none');
if(chartInst){
data.sondes.forEach(function(s,i){
if(s.nom){ nomsReels[i]=s.nom; chartInst.data.datasets[i].label=s.nom; }
});
chartInst.update('none');
} else {
data.sondes.forEach(function(s,i){
if(s.nom) nomsReels[i]=s.nom;
});
}
}
// Sondes