Resilience : ecran pause, reconnexion WS auto, watchdog systemd, WiFi monitor
- WebSocket : reconnexion avec backoff exponentiel au lieu de reload page - Ecran pause avec tasse de cafe quand le backend est injoignable - Endpoint /api/health pour monitoring externe - Watchdog systemd (sd_notify READY=1 + WATCHDOG=1 toutes les 10s) - Service systemd durci (Type=notify, WatchdogSec=30, KillMode, limites) - WiFi watchdog : script + timer systemd, verifie la gateway toutes les 60s - Destinations : fallback url_tunnel (WireGuard) en priorite Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -118,9 +118,34 @@ function allerA(ecran) {
|
||||
chargerGalerie();
|
||||
} else if (ecran === 'admin') {
|
||||
chargerAdmin();
|
||||
demarrerTimeoutAdmin();
|
||||
} else {
|
||||
arreterTimeoutAdmin();
|
||||
}
|
||||
}
|
||||
|
||||
let _adminTimeoutId = null;
|
||||
const ADMIN_TIMEOUT_MS = 30000;
|
||||
|
||||
function demarrerTimeoutAdmin() {
|
||||
arreterTimeoutAdmin();
|
||||
_adminTimeoutId = setTimeout(() => { allerA('accueil'); }, ADMIN_TIMEOUT_MS);
|
||||
const ecranAdmin = document.getElementById('ecran-admin');
|
||||
ecranAdmin.removeEventListener('pointerdown', _resetAdminTimeout);
|
||||
ecranAdmin.addEventListener('pointerdown', _resetAdminTimeout);
|
||||
}
|
||||
|
||||
function _resetAdminTimeout() {
|
||||
if (_adminTimeoutId) {
|
||||
clearTimeout(_adminTimeoutId);
|
||||
_adminTimeoutId = setTimeout(() => { allerA('accueil'); }, ADMIN_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
function arreterTimeoutAdmin() {
|
||||
if (_adminTimeoutId) { clearTimeout(_adminTimeoutId); _adminTimeoutId = null; }
|
||||
}
|
||||
|
||||
function recommencer() {
|
||||
photosSession = [];
|
||||
photoFinale = null;
|
||||
|
||||
@@ -48,13 +48,19 @@ function lancerPreview() {
|
||||
if (previewActif) return;
|
||||
previewActif = true;
|
||||
afficherErreurPreview(false);
|
||||
// Flou le temps que la 1ere frame fraiche arrive (le miroir DSLR doit remonter
|
||||
// apres la capture precedente) : evite l'impression d'image figee sur l'ancienne photo.
|
||||
document.getElementById('img-preview')?.classList.add('preview-chargement');
|
||||
wsEnvoyer({ type: 'preview_start' });
|
||||
resetPreviewTimeout();
|
||||
}
|
||||
|
||||
function demarrerEclairageLive() {
|
||||
if (!previewActif) {
|
||||
previewActif = true;
|
||||
wsEnvoyer({ type: 'preview_start' });
|
||||
resetPreviewTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
function arreterPreview() {
|
||||
if (!previewActif) return;
|
||||
previewActif = false;
|
||||
@@ -81,6 +87,10 @@ wsOnMessage('preview', (msg) => {
|
||||
};
|
||||
img.src = msg.image;
|
||||
}
|
||||
const adminPrev = document.getElementById('eclairage-preview');
|
||||
if (adminPrev && document.getElementById('panneau-eclairage')?.classList.contains('actif')) {
|
||||
adminPrev.src = msg.image;
|
||||
}
|
||||
afficherErreurPreview(false);
|
||||
resetPreviewTimeout();
|
||||
});
|
||||
@@ -175,33 +185,32 @@ wsOnMessage('camera_ok', () => {
|
||||
|
||||
wsOnMessage('eclairage', (msg) => {
|
||||
if (captureEnCours) return;
|
||||
let el = document.getElementById('indicateur-eclairage');
|
||||
if (!el) {
|
||||
el = document.createElement('div');
|
||||
el.id = 'indicateur-eclairage';
|
||||
el.style.cssText = 'position:fixed;top:12px;left:12px;padding:8px 16px;border-radius:12px;font-size:.9rem;font-weight:600;z-index:90;transition:all .3s;pointer-events:none;display:flex;align-items:center;gap:8px;';
|
||||
document.body.appendChild(el);
|
||||
// Admin panel eclairage
|
||||
const scoreBox = document.getElementById('eclairage-score-box');
|
||||
const actionEl = document.getElementById('eclairage-action');
|
||||
const visagesEl = document.getElementById('eclairage-visages');
|
||||
if (scoreBox) {
|
||||
const score = msg.score >= 0 ? msg.score : '--';
|
||||
scoreBox.textContent = score;
|
||||
if (msg.action === 'allumer') {
|
||||
scoreBox.style.background = '#dc2626'; scoreBox.style.color = '#fff';
|
||||
actionEl.textContent = 'ALLUMER LA LUMIERE';
|
||||
actionEl.style.color = '#f87171';
|
||||
} else if (msg.action === 'attention') {
|
||||
scoreBox.style.background = '#d97706'; scoreBox.style.color = '#fff';
|
||||
actionEl.textContent = 'Eclairage faible';
|
||||
actionEl.style.color = '#fbbf24';
|
||||
} else if (msg.action === 'ok') {
|
||||
scoreBox.style.background = '#16a34a'; scoreBox.style.color = '#fff';
|
||||
actionEl.textContent = 'Eclairage OK';
|
||||
actionEl.style.color = '#4ade80';
|
||||
} else {
|
||||
scoreBox.style.background = '#222'; scoreBox.style.color = '#888';
|
||||
actionEl.textContent = 'Aucun visage';
|
||||
actionEl.style.color = '#888';
|
||||
}
|
||||
visagesEl.textContent = msg.visages > 0 ? msg.visages + ' visage' + (msg.visages > 1 ? 's' : '') + ' detecte' + (msg.visages > 1 ? 's' : '') : '';
|
||||
}
|
||||
const score = msg.score >= 0 ? msg.score : '?';
|
||||
const faces = msg.visages || 0;
|
||||
if (msg.action === 'allumer') {
|
||||
el.style.background = 'rgba(239,68,68,.9)';
|
||||
el.style.color = '#fff';
|
||||
el.innerHTML = `☀️ ALLUMER <span style="opacity:.7;font-size:.8rem">${score}/100 · ${faces} visage${faces > 1 ? 's' : ''}</span>`;
|
||||
} else if (msg.action === 'attention') {
|
||||
el.style.background = 'rgba(245,158,11,.85)';
|
||||
el.style.color = '#fff';
|
||||
el.innerHTML = `⚠️ Faible <span style="opacity:.7;font-size:.8rem">${score}/100 · ${faces} visage${faces > 1 ? 's' : ''}</span>`;
|
||||
} else if (msg.action === 'ok') {
|
||||
el.style.background = 'rgba(34,197,94,.7)';
|
||||
el.style.color = '#fff';
|
||||
el.innerHTML = `✓ <span style="opacity:.7;font-size:.8rem">${score}/100 · ${faces} visage${faces > 1 ? 's' : ''}</span>`;
|
||||
} else if (msg.action === 'no_face') {
|
||||
el.style.background = 'rgba(100,100,120,.6)';
|
||||
el.style.color = '#ccc';
|
||||
el.innerHTML = `<span style="font-size:.8rem">Aucun visage</span>`;
|
||||
}
|
||||
el.style.display = '';
|
||||
});
|
||||
|
||||
// --- Capture ---
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
/* Communication WebSocket avec le backend */
|
||||
/* Communication WebSocket avec le backend — reconnexion résiliente */
|
||||
|
||||
let ws = null;
|
||||
let wsReconnectTimer = null;
|
||||
let _wsReconnectDelay = 1000;
|
||||
let _wsWasConnected = false;
|
||||
const wsCallbacks = {};
|
||||
|
||||
function wsConnecter() {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
ws = new WebSocket(`${proto}//${location.host}/ws`);
|
||||
try { ws = new WebSocket(`${proto}//${location.host}/ws`); } catch(e) { _wsScheduleReconnect(); return; }
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket connecte');
|
||||
if (wsReconnectTimer) {
|
||||
clearInterval(wsReconnectTimer);
|
||||
wsReconnectTimer = null;
|
||||
_wsReconnectDelay = 1000;
|
||||
if (_wsWasConnected) {
|
||||
_masquerPause();
|
||||
// Recharger la config pour resynchroniser l'état
|
||||
if (typeof chargerConfig === 'function') chargerConfig();
|
||||
}
|
||||
_wsWasConnected = true;
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const msg = JSON.parse(event.data);
|
||||
const cbs = wsCallbacks[msg.type];
|
||||
if (cbs) cbs.forEach(cb => cb(msg));
|
||||
try {
|
||||
const msg = JSON.parse(event.data);
|
||||
const cbs = wsCallbacks[msg.type];
|
||||
if (cbs) cbs.forEach(cb => cb(msg));
|
||||
} catch(e) {}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket deconnecte, rechargement dans 5s...');
|
||||
setTimeout(() => { location.reload(); }, 5000);
|
||||
console.log('WebSocket deconnecte');
|
||||
ws = null;
|
||||
_afficherPause();
|
||||
_wsScheduleReconnect();
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
@@ -32,6 +40,26 @@ function wsConnecter() {
|
||||
};
|
||||
}
|
||||
|
||||
function _wsScheduleReconnect() {
|
||||
const delay = Math.min(_wsReconnectDelay, 10000);
|
||||
_wsReconnectDelay = Math.min(_wsReconnectDelay * 1.5, 10000);
|
||||
setTimeout(wsConnecter, delay);
|
||||
}
|
||||
|
||||
function _afficherPause() {
|
||||
const el = document.getElementById('ecran-pause');
|
||||
if (el) el.style.display = 'flex';
|
||||
}
|
||||
|
||||
function _masquerPause() {
|
||||
const el = document.getElementById('ecran-pause');
|
||||
if (el) { el.style.display = 'none'; }
|
||||
// Relancer le preview si on était sur l'accueil
|
||||
if (typeof lancerPreview === 'function' && document.getElementById('ecran-accueil')?.classList.contains('actif')) {
|
||||
lancerPreview();
|
||||
}
|
||||
}
|
||||
|
||||
function wsEnvoyer(msg) {
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify(msg));
|
||||
|
||||
Reference in New Issue
Block a user