[IMP] script todo: suivi install — fenêtre des lignes d'erreurs/avertissements
La colonne ⚠ donne le NOMBRE ; on peut désormais LIRE les lignes associées. Touche « d » (ou indice dans la barre) : ouvre une petite fenêtre modale (ErrorLinesScreen) listant, pour la VM sélectionnée, les lignes d'erreurs puis d'avertissements (numéro de ligne + texte), défilable. Échap/q ferme. - scan_log_error_lines() : mêmes détection + listes d'ignore que la suite de tests ERPLibre, mais retient les lignes (bornées à 500) avec leur numéro. - Scan à la demande à l'ouverture -> toujours à jour (marche même pendant l'installation). Validé headless : modale s'ouvre avec les lignes, se ferme à Échap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f7df2310b3
commit
59bf857558
2 changed files with 104 additions and 2 deletions
|
|
@ -199,6 +199,34 @@ _LST_IGNORE_ERROR = (
|
|||
)
|
||||
|
||||
|
||||
def scan_log_error_lines(log_path: str, cap: int = 500) -> tuple[list, list]:
|
||||
"""(lignes_erreur, lignes_avertissement) d'un log, même détection que
|
||||
scan_log_errors mais on RETIENT les lignes (bornées à `cap`) pour les
|
||||
afficher. Chaque ligne est préfixée de son numéro (1-indexé)."""
|
||||
try:
|
||||
text = Path(log_path).read_text(errors="replace")
|
||||
except OSError:
|
||||
return [], []
|
||||
errs, warns = [], []
|
||||
for i, line in enumerate(text.splitlines(), 1):
|
||||
if EXIT_MARKER in line:
|
||||
continue
|
||||
low = line.lower()
|
||||
if (
|
||||
"error" in low
|
||||
and not any(ig in line for ig in _LST_IGNORE_ERROR)
|
||||
and len(errs) < cap
|
||||
):
|
||||
errs.append(f"{i}: {line}")
|
||||
if (
|
||||
"warning" in low
|
||||
and not any(ig in line for ig in _LST_IGNORE_WARNING)
|
||||
and len(warns) < cap
|
||||
):
|
||||
warns.append(f"{i}: {line}")
|
||||
return errs, warns
|
||||
|
||||
|
||||
def scan_log_errors(log_path: str) -> tuple[int, int]:
|
||||
"""(nb_erreurs, nb_avertissements) dans un log d'installation, en
|
||||
réutilisant la détection de la suite de tests ERPLibre : sous-chaîne
|
||||
|
|
@ -454,13 +482,57 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
"""Ouvre le dashboard Textual sur un manifeste d'installation. `run_app`
|
||||
à False renvoie l'instance sans la lancer (tests headless)."""
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Horizontal
|
||||
from textual.containers import Horizontal, Vertical
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import DataTable, Footer, Header, RichLog, Static
|
||||
|
||||
manifest = json.loads(Path(manifest_path).read_text())
|
||||
started = manifest.get("started", time.time())
|
||||
vms = manifest["vms"]
|
||||
|
||||
class ErrorLinesScreen(ModalScreen):
|
||||
"""Petite fenêtre modale : liste les LIGNES d'erreurs/avertissements
|
||||
d'une VM pour les lire (défilable). Échap / q pour fermer."""
|
||||
|
||||
BINDINGS = [
|
||||
("escape", "dismiss", "Fermer"),
|
||||
("q", "dismiss", "Fermer"),
|
||||
]
|
||||
|
||||
def __init__(self, vm_name, errs, warns):
|
||||
super().__init__()
|
||||
self._vm = vm_name
|
||||
self._errs = errs
|
||||
self._warns = warns
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical(id="errbox"):
|
||||
yield Static(
|
||||
f" {self._vm} — ⚠ {len(self._errs)} "
|
||||
f"{t('errors')} · ⚡ {len(self._warns)} {t('warnings')}"
|
||||
f" ({t('Esc to close')})",
|
||||
id="errtitle",
|
||||
)
|
||||
yield RichLog(
|
||||
id="errlog", highlight=False, markup=False, wrap=True
|
||||
)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
log = self.query_one("#errlog", RichLog)
|
||||
if self._errs:
|
||||
log.write(f"── {t('errors').capitalize()} ──")
|
||||
for line in self._errs:
|
||||
log.write(line)
|
||||
if self._warns:
|
||||
log.write(f"── {t('warnings').capitalize()} ──")
|
||||
for line in self._warns:
|
||||
log.write(line)
|
||||
if not self._errs and not self._warns:
|
||||
log.write(t("No error detected."))
|
||||
|
||||
def action_dismiss(self) -> None:
|
||||
self.dismiss()
|
||||
|
||||
ICON = {
|
||||
"pending": "⏳",
|
||||
"running": "⏳",
|
||||
|
|
@ -476,6 +548,13 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
#stats { height: 1; color: $accent; }
|
||||
#statsdetail { display: none; height: auto; color: $text-muted; }
|
||||
#sshbar { height: 2; color: $text-muted; }
|
||||
ErrorLinesScreen { align: center middle; }
|
||||
#errbox {
|
||||
width: 80%; height: 70%;
|
||||
border: thick $accent; background: $surface;
|
||||
}
|
||||
#errtitle { height: 1; color: $accent; text-style: bold; }
|
||||
#errlog { height: 1fr; border: solid $accent; }
|
||||
"""
|
||||
BINDINGS = [
|
||||
("q", "quit", "Quitter (détaché)"),
|
||||
|
|
@ -483,6 +562,7 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
("w", "web", "Web (navigateur CLI)"),
|
||||
("f", "follow", "Suivre"),
|
||||
("c", "copy_log", "Copier log"),
|
||||
("d", "details", "Détails erreurs"),
|
||||
("p", "pause_all", "Pause tout"),
|
||||
("o", "resume_all", "Reprendre tout"),
|
||||
]
|
||||
|
|
@ -580,7 +660,8 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
if vm:
|
||||
bar.update(
|
||||
f" {vm['ssh']} (s = SSH · w = web :8069 · "
|
||||
"c = copier le log · Maj+glisser = sélectionner)\n"
|
||||
"c = copier le log · d = détails erreurs · "
|
||||
"Maj+glisser = sélectionner)\n"
|
||||
f" Log : {vm['log']}"
|
||||
)
|
||||
|
||||
|
|
@ -945,6 +1026,15 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
|||
title="Presse-papiers",
|
||||
)
|
||||
|
||||
def action_details(self) -> None:
|
||||
"""Ouvre une petite fenêtre avec les LIGNES d'erreurs/avertissements
|
||||
de la VM sélectionnée (scan à la demande -> toujours à jour)."""
|
||||
vm = self._vm_by_name(self._selected)
|
||||
if not vm:
|
||||
return
|
||||
errs, warns = scan_log_error_lines(vm["log"])
|
||||
self.push_screen(ErrorLinesScreen(vm["name"], errs, warns))
|
||||
|
||||
def on_click(self, event) -> None:
|
||||
# Clic sur le sommaire de stats -> déplie / replie le détail.
|
||||
w = getattr(event, "widget", None)
|
||||
|
|
|
|||
|
|
@ -1432,6 +1432,18 @@ TRANSLATIONS = {
|
|||
"fr": "Aucune erreur détectée.",
|
||||
"en": "No error detected.",
|
||||
},
|
||||
"errors": {
|
||||
"fr": "erreurs",
|
||||
"en": "errors",
|
||||
},
|
||||
"warnings": {
|
||||
"fr": "avertissements",
|
||||
"en": "warnings",
|
||||
},
|
||||
"Esc to close": {
|
||||
"fr": "Échap pour fermer",
|
||||
"en": "Esc to close",
|
||||
},
|
||||
"No running VM to pause.": {
|
||||
"fr": "Aucune VM en cours à mettre en pause.",
|
||||
"en": "No running VM to pause.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue