Admin : statut détaillé imprimante + message erreur CUPS traduit

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 20:38:48 +02:00
parent 3cd5525981
commit 2b204ddb5e
3 changed files with 98 additions and 4 deletions

View File

@@ -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)