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:
@@ -505,15 +505,65 @@ async def api_imprimantes():
|
|||||||
return lister_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")
|
@app.post("/api/imprimante/evacuer")
|
||||||
async def api_evacuer_bourrage():
|
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
|
import subprocess
|
||||||
config = charger_config()
|
config = charger_config()
|
||||||
nom = config.get("impression", {}).get("imprimante", "Mitsubishi")
|
nom = config.get("impression", {}).get("imprimante", "Mitsubishi")
|
||||||
subprocess.run(["sudo", "cancel", "-a", nom], capture_output=True)
|
subprocess.run(["sudo", "cancel", "-a", nom], capture_output=True)
|
||||||
subprocess.run(["sudo", "cupsenable", 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")
|
@app.post("/api/imprimer")
|
||||||
@@ -800,8 +850,23 @@ async def _lancer_photostation():
|
|||||||
# Fermer Chromium (la boucle kiosk attend le flag avant de le relancer)
|
# Fermer Chromium (la boucle kiosk attend le flag avant de le relancer)
|
||||||
await asyncio.create_subprocess_exec("pkill", "chromium")
|
await asyncio.create_subprocess_exec("pkill", "chromium")
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
|
import subprocess as _sp
|
||||||
env = {**__import__("os").environ, "DISPLAY": ":0"}
|
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)
|
proc = await asyncio.create_subprocess_exec("bash", "-c", cmd, env=env)
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
FLAG_PHOTOSTATION.unlink(missing_ok=True)
|
FLAG_PHOTOSTATION.unlink(missing_ok=True)
|
||||||
|
|||||||
@@ -396,7 +396,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<button class="btn-action" onclick="sauvegarderMateriel()">Sauvegarder</button>
|
<button class="btn-action" onclick="sauvegarderMateriel()">Sauvegarder</button>
|
||||||
<div class="champ" style="margin-top:1rem">
|
<div class="champ" style="margin-top:1rem">
|
||||||
<button class="btn-danger" onclick="evacuerBourrage()">⚠ Évacuer bourrage papier</button>
|
<button class="btn-danger" onclick="evacuerBourrage()">⚠ Évacuer / Réinitialiser imprimante</button>
|
||||||
|
</div>
|
||||||
|
<div id="imprimante-statut-detail" class="champ" style="display:none">
|
||||||
|
<div id="imprimante-erreur-msg" style="background:rgba(244,67,54,0.1);border-radius:8px;padding:0.8rem;color:#f44336;font-size:0.85rem;margin-top:0.5rem"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ async function chargerMateriel() {
|
|||||||
|
|
||||||
// Imprimantes
|
// Imprimantes
|
||||||
await rafraichirImprimantes();
|
await rafraichirImprimantes();
|
||||||
|
rafraichirStatutImprimante();
|
||||||
if (imp.imprimante) document.getElementById('admin-imprimante').value = imp.imprimante;
|
if (imp.imprimante) document.getElementById('admin-imprimante').value = imp.imprimante;
|
||||||
if (imp.format) document.getElementById('admin-format-papier').value = imp.format;
|
if (imp.format) document.getElementById('admin-format-papier').value = imp.format;
|
||||||
const fmt = (imp.format || '15x20').replace('-2up', '');
|
const fmt = (imp.format || '15x20').replace('-2up', '');
|
||||||
@@ -94,6 +95,31 @@ function _getOrientations() {
|
|||||||
async function evacuerBourrage() {
|
async function evacuerBourrage() {
|
||||||
const r = await apiPost('/api/imprimante/evacuer', {});
|
const r = await apiPost('/api/imprimante/evacuer', {});
|
||||||
afficherStatut(r.message || 'Imprimante réactivée', r.succes ? 'succes' : 'erreur');
|
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() {
|
async function sauvegarderMateriel() {
|
||||||
|
|||||||
Reference in New Issue
Block a user