From 2b204ddb5e350d9f4fce929470212d7e175eabf3 Mon Sep 17 00:00:00 2001 From: Jules Date: Thu, 4 Jun 2026 20:38:48 +0200 Subject: [PATCH] =?UTF-8?q?Admin=20:=20statut=20d=C3=A9taill=C3=A9=20impri?= =?UTF-8?q?mante=20+=20message=20erreur=20CUPS=20traduit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GET /api/imprimante/statut-detail : statut + dernière erreur log - Bouton évacuer retourne le vrai message (format papier, bourrage...) - Bloc d'erreur affiché dans l'onglet Matériel si problème détecté Co-Authored-By: Claude Sonnet 4.6 --- backend/main.py | 71 ++++++++++++++++++++++++++++++++++++++++++-- frontend/index.html | 5 +++- frontend/js/admin.js | 26 ++++++++++++++++ 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/backend/main.py b/backend/main.py index 55625c9..691ef6a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -505,15 +505,65 @@ async def api_imprimantes(): return lister_imprimantes() +@app.get("/api/imprimante/statut-detail") +async def api_statut_imprimante(): + """Retourne le statut détaillé CUPS + dernière erreur du log.""" + import subprocess + config = charger_config() + nom = config.get("impression", {}).get("imprimante", "Mitsubishi") + # Statut lpstat + r = subprocess.run(["lpstat", "-p", nom], capture_output=True, text=True) + statut_ligne = r.stdout.strip() + # Dernière erreur dans error_log + try: + r2 = subprocess.run( + ["sudo", "grep", f"\\[{nom}\\]\\|Job.*cancel\\|media.*match\\|jam\\|paper", + "/var/log/cups/error_log"], + capture_output=True, text=True + ) + lignes = [l for l in r2.stdout.splitlines() if "error_log" not in l] + derniere_erreur = lignes[-1] if lignes else "" + except Exception: + derniere_erreur = "" + return { + "statut": statut_ligne, + "derniere_erreur": derniere_erreur, + "imprimante": nom, + } + + @app.post("/api/imprimante/evacuer") async def api_evacuer_bourrage(): - """Annule tous les jobs et réactive l'imprimante pour débloquer un bourrage.""" + """Annule tous les jobs, réactive l'imprimante, retourne le dernier message d'erreur.""" import subprocess config = charger_config() nom = config.get("impression", {}).get("imprimante", "Mitsubishi") subprocess.run(["sudo", "cancel", "-a", nom], capture_output=True) subprocess.run(["sudo", "cupsenable", nom], capture_output=True) - return {"succes": True, "message": f"Jobs annulés, {nom} réactivée"} + # Lire la dernière erreur CUPS pour informer l'utilisateur + try: + r = subprocess.run( + ["sudo", "tail", "-50", "/var/log/cups/error_log"], + capture_output=True, text=True + ) + erreurs = [l for l in r.stdout.splitlines() + if any(k in l.lower() for k in ["media", "match", "jam", "paper", "cancel", "error"]) + and "createprofile" not in l.lower()] + derniere = erreurs[-1] if erreurs else "" + except Exception: + derniere = "" + + if "media does not match" in derniere.lower(): + msg = ("Format papier incorrect — vérifiez que le format configuré " + "correspond à la cassette dans l'imprimante") + elif "jam" in derniere.lower(): + msg = "Bourrage papier — retirez le papier bloqué physiquement" + elif derniere: + msg = f"Jobs annulés. Dernière erreur : {derniere.split(']')[-1].strip()}" + else: + msg = f"Jobs annulés, {nom} réactivée" + + return {"succes": True, "message": msg} @app.post("/api/imprimer") @@ -800,8 +850,23 @@ async def _lancer_photostation(): # Fermer Chromium (la boucle kiosk attend le flag avant de le relancer) await asyncio.create_subprocess_exec("pkill", "chromium") await asyncio.sleep(2) + import subprocess as _sp env = {**__import__("os").environ, "DISPLAY": ":0"} - cmd = f"source {PHOTOSTATION_DIR}/venv/bin/activate && python {PHOTOSTATION_DIR}/src/main.py --kiosk" + # Récupérer les variables de session X/dbus depuis la session LightDM + try: + r = _sp.run(["grep", "-z", "DISPLAY\|XAUTHORITY\|DBUS_SESSION", + f"/proc/{__import__('os').getpid()}/environ"], + capture_output=True) + for kv in r.stdout.split(b"\x00"): + if b"=" in kv: + k, v = kv.decode(errors="ignore").split("=", 1) + if k in ("DISPLAY", "XAUTHORITY", "DBUS_SESSION_BUS_ADDRESS"): + env[k] = v + except Exception: + pass + log_path = "/tmp/photostation.log" + cmd = (f"source {PHOTOSTATION_DIR}/venv/bin/activate && " + f"python {PHOTOSTATION_DIR}/src/main.py --kiosk >> {log_path} 2>&1") proc = await asyncio.create_subprocess_exec("bash", "-c", cmd, env=env) await proc.wait() FLAG_PHOTOSTATION.unlink(missing_ok=True) diff --git a/frontend/index.html b/frontend/index.html index b5d681e..fe8c736 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -396,7 +396,10 @@
- + +
+ diff --git a/frontend/js/admin.js b/frontend/js/admin.js index e8e61c1..96348c9 100644 --- a/frontend/js/admin.js +++ b/frontend/js/admin.js @@ -57,6 +57,7 @@ async function chargerMateriel() { // Imprimantes await rafraichirImprimantes(); + rafraichirStatutImprimante(); if (imp.imprimante) document.getElementById('admin-imprimante').value = imp.imprimante; if (imp.format) document.getElementById('admin-format-papier').value = imp.format; const fmt = (imp.format || '15x20').replace('-2up', ''); @@ -94,6 +95,31 @@ function _getOrientations() { async function evacuerBourrage() { const r = await apiPost('/api/imprimante/evacuer', {}); afficherStatut(r.message || 'Imprimante réactivée', r.succes ? 'succes' : 'erreur'); + const el = document.getElementById('imprimante-erreur-msg'); + const bloc = document.getElementById('imprimante-statut-detail'); + if (r.message && r.message !== 'Imprimante réactivée') { + el.textContent = '⚠ ' + r.message; + bloc.style.display = 'block'; + } else { + bloc.style.display = 'none'; + } +} + +async function rafraichirStatutImprimante() { + const r = await apiGet('/api/imprimante/statut-detail').catch(() => null); + if (!r) return; + const el = document.getElementById('imprimante-erreur-msg'); + const bloc = document.getElementById('imprimante-statut-detail'); + if (r.derniere_erreur) { + let msg = r.derniere_erreur; + if (msg.includes('media does not match')) msg = '⚠ Format papier incorrect — vérifiez que le format configuré correspond à la cassette chargée dans l\'imprimante'; + else if (msg.includes('jam')) msg = '⚠ Bourrage papier détecté'; + else msg = '⚠ ' + msg.split(']').pop().trim(); + el.textContent = msg; + bloc.style.display = 'block'; + } else { + bloc.style.display = 'none'; + } } async function sauvegarderMateriel() {