2026-07-29 05:35:31 -04:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
"""Télémétrie de navigation du CLI TODO.
|
|
|
|
|
|
|
|
|
|
|
|
Enregistre les fils d'Ariane visités (« A › B › C » -> compteur) et affiche un
|
|
|
|
|
|
DIAGRAMME arborescent des fonctionnalités dans un TUI Textual, trié par usage.
|
|
|
|
|
|
|
|
|
|
|
|
- record(path) : appelé par Todo._menu_header à chaque affichage de menu ; on
|
|
|
|
|
|
dédupe les ré-affichages consécutifs pour ne compter que les TRANSITIONS.
|
|
|
|
|
|
- run_tui() : ouvre l'arbre de navigation (compteurs par menu).
|
|
|
|
|
|
"""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-07-29 05:49:13 -04:00
|
|
|
|
import ast
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
import asyncio
|
|
|
|
|
|
import glob
|
2026-07-29 05:35:31 -04:00
|
|
|
|
import json
|
|
|
|
|
|
import os
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
import shutil
|
|
|
|
|
|
import subprocess
|
2026-07-29 05:35:31 -04:00
|
|
|
|
import time
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
|
except Exception: # pragma: no cover - repli si i18n indisponible
|
|
|
|
|
|
|
|
|
|
|
|
def t(key: str) -> str:
|
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Dernier chemin enregistré : évite de recompter chaque ré-affichage du même
|
|
|
|
|
|
# menu (une commande qui revient au menu ne compte pas comme une navigation).
|
|
|
|
|
|
_LAST = [None]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _path() -> Path:
|
|
|
|
|
|
base = Path(os.path.expanduser("~/.erplibre"))
|
|
|
|
|
|
base.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
return base / "todo_telemetry.json"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load() -> dict:
|
|
|
|
|
|
try:
|
|
|
|
|
|
return json.loads(_path().read_text())
|
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
|
return {"paths": {}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _save(data: dict) -> None:
|
|
|
|
|
|
try:
|
|
|
|
|
|
_path().write_text(json.dumps(data, ensure_ascii=False, indent=2))
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def record(path: str) -> None:
|
|
|
|
|
|
"""Incrémente le compteur du menu `path`. Best-effort : ne lève jamais
|
|
|
|
|
|
(la télémétrie ne doit jamais casser la navigation)."""
|
|
|
|
|
|
if not path or path == _LAST[0]:
|
|
|
|
|
|
return
|
|
|
|
|
|
_LAST[0] = path
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = load()
|
|
|
|
|
|
paths = data.setdefault("paths", {})
|
|
|
|
|
|
paths[path] = paths.get(path, 0) + 1
|
|
|
|
|
|
data["updated"] = int(time.time())
|
|
|
|
|
|
_save(data)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reset() -> None:
|
|
|
|
|
|
_save({"paths": {}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _nested(paths: dict) -> dict:
|
|
|
|
|
|
"""{« A › B › C »: count} -> arbre {nom: {'count', 'children'}}. Chaque
|
|
|
|
|
|
menu enregistre son propre chemin, donc chaque nœud a son compteur."""
|
|
|
|
|
|
root: dict = {}
|
|
|
|
|
|
for path, count in paths.items():
|
|
|
|
|
|
cur = root
|
|
|
|
|
|
parts = path.split(" › ")
|
|
|
|
|
|
for i, name in enumerate(parts):
|
|
|
|
|
|
node = cur.setdefault(name, {"count": 0, "children": {}})
|
|
|
|
|
|
if i == len(parts) - 1:
|
|
|
|
|
|
node["count"] += count
|
|
|
|
|
|
cur = node["children"]
|
|
|
|
|
|
return root
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 05:49:13 -04:00
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
# Métadonnées de navigation DÉRIVÉES DU CODE (structure réelle des menus)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
def _str_of(node) -> str | None:
|
|
|
|
|
|
"""Chaîne d'un nœud AST : littéral, t("…") ou f-string (parties Constant)."""
|
|
|
|
|
|
if isinstance(node, ast.Constant) and isinstance(node.value, str):
|
|
|
|
|
|
return node.value
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(node, ast.Call)
|
|
|
|
|
|
and isinstance(node.func, ast.Name)
|
|
|
|
|
|
and node.func.id == "t"
|
|
|
|
|
|
and node.args
|
|
|
|
|
|
and isinstance(node.args[0], ast.Constant)
|
|
|
|
|
|
):
|
|
|
|
|
|
return node.args[0].value
|
|
|
|
|
|
if isinstance(node, ast.JoinedStr):
|
|
|
|
|
|
return "".join(
|
|
|
|
|
|
p.value
|
|
|
|
|
|
for p in node.values
|
|
|
|
|
|
if isinstance(p, ast.Constant) and isinstance(p.value, str)
|
|
|
|
|
|
)
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _choice_labels(func) -> list:
|
|
|
|
|
|
"""Libellés NUMÉROTÉS d'un menu « choices = [...] » (dans l'ordre, sections
|
|
|
|
|
|
exclues car non numérotées par fill_help_info)."""
|
|
|
|
|
|
for node in ast.walk(func):
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(node, ast.Assign)
|
|
|
|
|
|
and any(
|
|
|
|
|
|
isinstance(tg, ast.Name) and tg.id == "choices"
|
|
|
|
|
|
for tg in node.targets
|
|
|
|
|
|
)
|
|
|
|
|
|
and isinstance(node.value, ast.List)
|
|
|
|
|
|
):
|
|
|
|
|
|
labels = []
|
|
|
|
|
|
for el in node.value.elts:
|
|
|
|
|
|
if not isinstance(el, ast.Dict):
|
|
|
|
|
|
continue
|
|
|
|
|
|
for k, v in zip(el.keys, el.values):
|
|
|
|
|
|
if isinstance(k, ast.Constant) and k.value == "prompt_description":
|
|
|
|
|
|
s = _str_of(v)
|
|
|
|
|
|
if s:
|
|
|
|
|
|
labels.append(s)
|
|
|
|
|
|
return labels
|
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _dispatch(func) -> dict:
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
"""{numéro: (nom_de_méthode, kwargs)} depuis « status == "N": self.X(...) ».
|
|
|
|
|
|
Les kwargs LITTÉRAUX (ex. dry_run=True) sont capturés pour pouvoir rejouer
|
|
|
|
|
|
la commande à l'identique depuis la télémétrie."""
|
2026-07-29 05:49:13 -04:00
|
|
|
|
out = {}
|
|
|
|
|
|
for node in ast.walk(func):
|
|
|
|
|
|
if not isinstance(node, ast.If):
|
|
|
|
|
|
continue
|
|
|
|
|
|
test = node.test
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(test, ast.Compare)
|
|
|
|
|
|
and isinstance(test.left, ast.Name)
|
|
|
|
|
|
and test.left.id == "status"
|
|
|
|
|
|
and len(test.ops) == 1
|
|
|
|
|
|
and isinstance(test.ops[0], ast.Eq)
|
|
|
|
|
|
and isinstance(test.comparators[0], ast.Constant)
|
|
|
|
|
|
):
|
|
|
|
|
|
try:
|
|
|
|
|
|
num = int(test.comparators[0].value)
|
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
|
|
continue
|
|
|
|
|
|
for stmt in node.body:
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
found = None
|
2026-07-29 05:49:13 -04:00
|
|
|
|
for n in ast.walk(stmt):
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(n, ast.Call)
|
|
|
|
|
|
and isinstance(n.func, ast.Attribute)
|
|
|
|
|
|
and isinstance(n.func.value, ast.Name)
|
|
|
|
|
|
and n.func.value.id == "self"
|
|
|
|
|
|
):
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
kwargs = {
|
|
|
|
|
|
kw.arg: kw.value.value
|
|
|
|
|
|
for kw in n.keywords
|
|
|
|
|
|
if kw.arg
|
|
|
|
|
|
and isinstance(kw.value, ast.Constant)
|
|
|
|
|
|
}
|
|
|
|
|
|
found = (n.func.attr, kwargs)
|
2026-07-29 05:49:13 -04:00
|
|
|
|
break
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
if found:
|
|
|
|
|
|
out[num] = found
|
2026-07-29 05:49:13 -04:00
|
|
|
|
break
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _menu_labels(cls) -> dict:
|
|
|
|
|
|
"""{méthode: label} depuis l'attribut de classe _MENU_LABELS."""
|
|
|
|
|
|
for node in ast.walk(cls):
|
|
|
|
|
|
if (
|
|
|
|
|
|
isinstance(node, ast.Assign)
|
|
|
|
|
|
and any(
|
|
|
|
|
|
isinstance(tg, ast.Name) and tg.id == "_MENU_LABELS"
|
|
|
|
|
|
for tg in node.targets
|
|
|
|
|
|
)
|
|
|
|
|
|
and isinstance(node.value, ast.Dict)
|
|
|
|
|
|
):
|
|
|
|
|
|
return {
|
|
|
|
|
|
k.value: v.value
|
|
|
|
|
|
for k, v in zip(node.value.keys, node.value.values)
|
|
|
|
|
|
if isinstance(k, ast.Constant) and isinstance(v, ast.Constant)
|
|
|
|
|
|
}
|
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_code_tree(todo_path=None) -> dict | None:
|
|
|
|
|
|
"""Construit l'arbre des menus/commandes EN LISANT le code de todo.py
|
|
|
|
|
|
(AST). Chaque menu (méthode de _MENU_LABELS) devient un nœud ; ses branches
|
|
|
|
|
|
« status == N » deviennent des sous-menus (si la cible est un menu) ou des
|
|
|
|
|
|
commandes (feuilles). None si l'analyse échoue."""
|
|
|
|
|
|
p = Path(todo_path) if todo_path else (Path(__file__).parent / "todo.py")
|
|
|
|
|
|
try:
|
|
|
|
|
|
mod = ast.parse(p.read_text(encoding="utf-8"))
|
|
|
|
|
|
except (OSError, SyntaxError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
cls = next(
|
|
|
|
|
|
(n for n in ast.walk(mod) if isinstance(n, ast.ClassDef)), None
|
|
|
|
|
|
)
|
|
|
|
|
|
if cls is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
methods = {
|
|
|
|
|
|
n.name: n for n in cls.body if isinstance(n, ast.FunctionDef)
|
|
|
|
|
|
}
|
|
|
|
|
|
labels = _menu_labels(cls)
|
|
|
|
|
|
if "run" not in methods or not labels:
|
|
|
|
|
|
return None
|
|
|
|
|
|
seen = set()
|
|
|
|
|
|
|
|
|
|
|
|
def build(method):
|
|
|
|
|
|
node = {
|
|
|
|
|
|
"label": labels.get(method, method),
|
|
|
|
|
|
"is_menu": True,
|
|
|
|
|
|
"children": [],
|
|
|
|
|
|
}
|
|
|
|
|
|
if method in seen or method not in methods:
|
|
|
|
|
|
return node
|
|
|
|
|
|
seen.add(method)
|
|
|
|
|
|
func = methods[method]
|
|
|
|
|
|
disp = _dispatch(func)
|
|
|
|
|
|
clabels = _choice_labels(func)
|
|
|
|
|
|
for num in sorted(disp):
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
target, kwargs = disp[num]
|
2026-07-29 05:49:13 -04:00
|
|
|
|
if target in labels: # sous-menu
|
|
|
|
|
|
node["children"].append(build(target))
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
else: # commande (feuille) exécutable
|
2026-07-29 05:49:13 -04:00
|
|
|
|
lab = (
|
|
|
|
|
|
clabels[num - 1]
|
|
|
|
|
|
if 0 <= num - 1 < len(clabels)
|
|
|
|
|
|
else target.lstrip("_").replace("_", " ")
|
|
|
|
|
|
)
|
|
|
|
|
|
node["children"].append(
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
{
|
|
|
|
|
|
"label": lab,
|
|
|
|
|
|
"is_menu": False,
|
|
|
|
|
|
"children": [],
|
|
|
|
|
|
"method": target,
|
|
|
|
|
|
"kwargs": kwargs,
|
|
|
|
|
|
}
|
2026-07-29 05:49:13 -04:00
|
|
|
|
)
|
|
|
|
|
|
seen.discard(method)
|
|
|
|
|
|
return node
|
|
|
|
|
|
|
|
|
|
|
|
return build("run")
|
|
|
|
|
|
|
|
|
|
|
|
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
def _command_columns(tree, paths):
|
|
|
|
|
|
"""Colonnes du Kanban : (label, chemin, count, [commandes]) pour CHAQUE
|
|
|
|
|
|
menu qui contient des commandes directes (parcours profondeur d'abord)."""
|
|
|
|
|
|
cols = []
|
|
|
|
|
|
|
|
|
|
|
|
def walk(node, path):
|
|
|
|
|
|
cmds = [c for c in node["children"] if not c["is_menu"]]
|
|
|
|
|
|
if cmds:
|
|
|
|
|
|
cols.append((node["label"], path, paths.get(path, 0), cmds))
|
|
|
|
|
|
for c in node["children"]:
|
|
|
|
|
|
if c["is_menu"]:
|
|
|
|
|
|
walk(c, f"{path} › {c['label']}")
|
|
|
|
|
|
|
|
|
|
|
|
if tree:
|
|
|
|
|
|
walk(tree, "TODO")
|
|
|
|
|
|
return cols
|
|
|
|
|
|
|
|
|
|
|
|
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
# Télémétrie SYSTÈME (vue F2)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
def _os_id() -> str:
|
|
|
|
|
|
try:
|
|
|
|
|
|
for line in open("/etc/os-release", encoding="utf-8"):
|
|
|
|
|
|
if line.startswith("ID="):
|
|
|
|
|
|
return line.split("=", 1)[1].strip().strip('"').lower()
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sensors_install_command():
|
|
|
|
|
|
"""Commande d'installation de lm-sensors selon l'OS (nos 4 systèmes)."""
|
|
|
|
|
|
apt = ["sudo", "apt-get", "install", "-y", "lm-sensors"]
|
|
|
|
|
|
dnf = ["sudo", "dnf", "install", "-y", "lm_sensors"]
|
|
|
|
|
|
pac = ["sudo", "pacman", "-S", "--needed", "--noconfirm", "lm_sensors"]
|
|
|
|
|
|
cmd = {
|
|
|
|
|
|
"ubuntu": apt,
|
|
|
|
|
|
"debian": apt,
|
|
|
|
|
|
"linuxmint": apt,
|
|
|
|
|
|
"fedora": dnf,
|
|
|
|
|
|
"arch": pac,
|
|
|
|
|
|
}.get(_os_id())
|
|
|
|
|
|
if cmd:
|
|
|
|
|
|
return cmd
|
|
|
|
|
|
if shutil.which("apt-get"):
|
|
|
|
|
|
return apt
|
|
|
|
|
|
if shutil.which("dnf"):
|
|
|
|
|
|
return dnf
|
|
|
|
|
|
if shutil.which("pacman"):
|
|
|
|
|
|
return pac
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _first_int(path):
|
|
|
|
|
|
try:
|
|
|
|
|
|
return int(open(path).read().strip())
|
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _cpu_sample():
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open("/proc/stat") as f:
|
|
|
|
|
|
vals = [int(x) for x in f.readline().split()[1:]]
|
|
|
|
|
|
idle = vals[3] + (vals[4] if len(vals) > 4 else 0)
|
|
|
|
|
|
return idle, sum(vals)
|
|
|
|
|
|
except (OSError, ValueError, IndexError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _net_sample():
|
|
|
|
|
|
rx = tx = 0
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open("/proc/net/dev") as f:
|
|
|
|
|
|
for line in f.readlines()[2:]:
|
|
|
|
|
|
iface, _, rest = line.partition(":")
|
|
|
|
|
|
if iface.strip() in ("lo", ""):
|
|
|
|
|
|
continue
|
|
|
|
|
|
cols = rest.split()
|
|
|
|
|
|
if len(cols) >= 9:
|
|
|
|
|
|
rx += int(cols[0])
|
|
|
|
|
|
tx += int(cols[8])
|
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
return rx, tx
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _mem():
|
|
|
|
|
|
info = {}
|
|
|
|
|
|
try:
|
|
|
|
|
|
for line in open("/proc/meminfo"):
|
|
|
|
|
|
k, _, v = line.partition(":")
|
|
|
|
|
|
info[k] = int(v.split()[0]) * 1024
|
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
|
return None
|
|
|
|
|
|
total = info.get("MemTotal", 0)
|
|
|
|
|
|
avail = info.get("MemAvailable", info.get("MemFree", 0))
|
|
|
|
|
|
return total, total - avail
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _battery():
|
|
|
|
|
|
for base in glob.glob("/sys/class/power_supply/BAT*"):
|
|
|
|
|
|
cap = _first_int(os.path.join(base, "capacity"))
|
|
|
|
|
|
try:
|
|
|
|
|
|
status = open(os.path.join(base, "status")).read().strip()
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
status = ""
|
|
|
|
|
|
if cap is not None:
|
|
|
|
|
|
return cap, status
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_temperature():
|
|
|
|
|
|
"""(source, [°C]) ou None. /sys/class/thermal d'abord (sans dépendance),
|
|
|
|
|
|
puis lm-sensors si présent."""
|
|
|
|
|
|
temps = []
|
|
|
|
|
|
for zt in glob.glob("/sys/class/thermal/thermal_zone*/temp"):
|
|
|
|
|
|
v = _first_int(zt)
|
|
|
|
|
|
if v and v > 0:
|
|
|
|
|
|
temps.append(round(v / 1000.0, 1))
|
|
|
|
|
|
if temps:
|
|
|
|
|
|
return "sysfs", temps
|
|
|
|
|
|
if shutil.which("sensors"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
out = subprocess.run(
|
|
|
|
|
|
["sensors", "-u"], capture_output=True, text=True, timeout=5
|
|
|
|
|
|
).stdout
|
|
|
|
|
|
for line in out.splitlines():
|
|
|
|
|
|
line = line.strip()
|
|
|
|
|
|
if "_input:" in line:
|
|
|
|
|
|
try:
|
|
|
|
|
|
temps.append(round(float(line.split(":")[1]), 1))
|
|
|
|
|
|
except (ValueError, IndexError):
|
|
|
|
|
|
pass
|
|
|
|
|
|
temps = [x for x in temps if x > 0]
|
|
|
|
|
|
if temps:
|
|
|
|
|
|
return "sensors", temps
|
|
|
|
|
|
except (OSError, subprocess.SubprocessError):
|
|
|
|
|
|
pass
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def system_snapshot(prev, full=True):
|
|
|
|
|
|
"""Instantané des métriques système. `prev` = (cpu, net, t) pour calculer
|
|
|
|
|
|
les taux CPU/réseau par delta. `full=False` saute la température (évite le
|
|
|
|
|
|
subprocess sensors quand la vue système n'est pas affichée). Renvoie
|
|
|
|
|
|
(métriques, nouveau_prev)."""
|
|
|
|
|
|
now = time.time()
|
|
|
|
|
|
cpu, net = _cpu_sample(), _net_sample()
|
|
|
|
|
|
pcpu, pnet, pt = prev or (None, None, None)
|
|
|
|
|
|
m = {}
|
|
|
|
|
|
if cpu and pcpu and cpu[1] > pcpu[1]:
|
|
|
|
|
|
m["cpu"] = max(
|
|
|
|
|
|
0, min(100, round(100 * (1 - (cpu[0] - pcpu[0]) / (cpu[1] - pcpu[1]))))
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
m["cpu"] = None
|
|
|
|
|
|
if net and pnet and pt and now > pt:
|
|
|
|
|
|
dt = now - pt
|
|
|
|
|
|
m["net"] = ((net[0] - pnet[0]) / dt, (net[1] - pnet[1]) / dt)
|
|
|
|
|
|
else:
|
|
|
|
|
|
m["net"] = None
|
|
|
|
|
|
m["mem"] = _mem()
|
|
|
|
|
|
try:
|
|
|
|
|
|
du = shutil.disk_usage("/")
|
|
|
|
|
|
m["disk"] = (du.total, du.used, du.free)
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
m["disk"] = None
|
|
|
|
|
|
m["battery"] = _battery()
|
|
|
|
|
|
m["temp"] = read_temperature() if full else None
|
|
|
|
|
|
try:
|
|
|
|
|
|
m["uptime"] = float(open("/proc/uptime").read().split()[0])
|
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
|
m["uptime"] = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
m["load"] = os.getloadavg()
|
|
|
|
|
|
except OSError:
|
|
|
|
|
|
m["load"] = None
|
|
|
|
|
|
m["ncpu"] = os.cpu_count() or 1
|
|
|
|
|
|
return m, (cpu, net, now)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fmt_size(nbytes):
|
|
|
|
|
|
if nbytes is None:
|
|
|
|
|
|
return "-"
|
|
|
|
|
|
for unit, div in (("T", 1 << 40), ("G", 1 << 30), ("M", 1 << 20)):
|
|
|
|
|
|
if nbytes >= div:
|
|
|
|
|
|
return f"{nbytes / div:.1f}{unit}"
|
|
|
|
|
|
return f"{nbytes // 1024}K"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fmt_rate(bps):
|
|
|
|
|
|
if bps is None:
|
|
|
|
|
|
return "?"
|
|
|
|
|
|
for unit, div in (("Go/s", 1 << 30), ("Mo/s", 1 << 20), ("Ko/s", 1 << 10)):
|
|
|
|
|
|
if bps >= div:
|
|
|
|
|
|
return f"{bps / div:.1f} {unit}"
|
|
|
|
|
|
return f"{int(bps)} o/s"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _fmt_uptime(secs):
|
|
|
|
|
|
if not secs:
|
|
|
|
|
|
return "?"
|
|
|
|
|
|
secs = int(secs)
|
|
|
|
|
|
d, r = divmod(secs, 86400)
|
|
|
|
|
|
h, r = divmod(r, 3600)
|
|
|
|
|
|
mnt = r // 60
|
|
|
|
|
|
if d:
|
|
|
|
|
|
return f"{d}j {h}h"
|
|
|
|
|
|
if h:
|
|
|
|
|
|
return f"{h}h{mnt:02d}"
|
|
|
|
|
|
return f"{mnt}min"
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-29 06:09:06 -04:00
|
|
|
|
# Modes d'affichage du Kanban (F4 les fait défiler).
|
|
|
|
|
|
KANBAN_MODES = ("columns", "swimlanes", "grid")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_tui(run_app: bool = True, state: dict | None = None):
|
|
|
|
|
|
"""TUI de télémétrie : vue Arbre (issue du code) et vue Kanban (F3), la
|
|
|
|
|
|
disposition du Kanban défilant par F4 (colonnes / swimlanes / grille).
|
|
|
|
|
|
Sélectionner une COMMANDE = l'exécuter. `state` restaure la vue + le
|
|
|
|
|
|
curseur au retour. Renvoie (action|None, state) ; run_app=False -> l'app."""
|
2026-07-29 05:35:31 -04:00
|
|
|
|
from textual.app import App, ComposeResult
|
2026-07-29 06:09:06 -04:00
|
|
|
|
from textual.containers import (
|
|
|
|
|
|
Container,
|
|
|
|
|
|
Grid,
|
|
|
|
|
|
HorizontalScroll,
|
|
|
|
|
|
Vertical,
|
|
|
|
|
|
VerticalScroll,
|
|
|
|
|
|
)
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
from textual.widgets import (
|
|
|
|
|
|
Footer,
|
|
|
|
|
|
Header,
|
|
|
|
|
|
Label,
|
|
|
|
|
|
ListItem,
|
|
|
|
|
|
ListView,
|
|
|
|
|
|
Static,
|
|
|
|
|
|
Tree,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
paths = load().get("paths", {})
|
|
|
|
|
|
code_tree = build_code_tree()
|
2026-07-29 06:09:06 -04:00
|
|
|
|
columns = _command_columns(code_tree, paths) # (label, path, cnt, cmds)
|
|
|
|
|
|
state = state or {}
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
|
|
|
|
|
|
class CmdItem(ListItem):
|
2026-07-29 06:09:06 -04:00
|
|
|
|
"""Carte : commande à exécuter + son chemin (pour restaurer le curseur)."""
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
|
2026-07-29 06:09:06 -04:00
|
|
|
|
def __init__(self, label, method, kwargs, path):
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
super().__init__(Label(label))
|
|
|
|
|
|
self.cmd_method = method
|
|
|
|
|
|
self.cmd_kwargs = kwargs or {}
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self.cmd_path = path
|
2026-07-29 05:35:31 -04:00
|
|
|
|
|
|
|
|
|
|
class Telemetry(App):
|
|
|
|
|
|
CSS = """
|
|
|
|
|
|
#summary { height: 1; color: $text-muted; }
|
|
|
|
|
|
Tree { border: solid $accent; }
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
#kanban { display: none; height: 1fr; }
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
#system { display: none; height: 1fr; padding: 1 2; }
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
.kcol { width: 40; border: solid $accent; margin: 0 1 0 0; }
|
|
|
|
|
|
.ktitle { height: 1; color: $accent; }
|
2026-07-29 06:09:06 -04:00
|
|
|
|
.lane { height: auto; }
|
|
|
|
|
|
.lanetitle { height: 1; color: $secondary; }
|
|
|
|
|
|
.kgrid { grid-size: 3; grid-gutter: 1; }
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
.kbig { row-span: 3; }
|
2026-07-29 05:35:31 -04:00
|
|
|
|
"""
|
|
|
|
|
|
BINDINGS = [
|
|
|
|
|
|
("q", "quit", "Quitter"),
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
("f2", "system", "Système"),
|
2026-07-29 06:09:06 -04:00
|
|
|
|
("f3", "toggle_view", "Arbre/Kanban"),
|
|
|
|
|
|
("f4", "kanban_layout", "Disposition Kanban"),
|
2026-07-29 05:35:31 -04:00
|
|
|
|
("e", "expand_all", "Tout déplier"),
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
("i", "install_sensors", "Installer capteurs"),
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
("r", "reset", "Réinitialiser"),
|
2026-07-29 05:35:31 -04:00
|
|
|
|
]
|
|
|
|
|
|
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
def __init__(self):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self._action = None
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self._mode = state.get("mode", "tree")
|
|
|
|
|
|
self._kanban_mode = state.get("kanban_mode", "columns")
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
self._sys_prev = None
|
2026-07-29 06:09:06 -04:00
|
|
|
|
|
|
|
|
|
|
# -- helpers de construction de widgets ------------------------------ #
|
|
|
|
|
|
def _col_widget(self, label, cnt, cmds):
|
|
|
|
|
|
items = [
|
|
|
|
|
|
CmdItem(
|
|
|
|
|
|
f"· {c['label']}",
|
|
|
|
|
|
c.get("method"),
|
|
|
|
|
|
c.get("kwargs"),
|
|
|
|
|
|
c.get("path"),
|
|
|
|
|
|
)
|
|
|
|
|
|
for c in cmds
|
|
|
|
|
|
]
|
|
|
|
|
|
return VerticalScroll(
|
|
|
|
|
|
Static(f"{label} ({cnt})", classes="ktitle"),
|
|
|
|
|
|
ListView(*items),
|
|
|
|
|
|
classes="kcol",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _kanban_layout_widget(self):
|
|
|
|
|
|
cols = [
|
|
|
|
|
|
self._col_widget(label, cnt, cmds)
|
|
|
|
|
|
for (label, _p, cnt, cmds) in columns
|
|
|
|
|
|
]
|
|
|
|
|
|
if not cols:
|
|
|
|
|
|
return Static(t("No command found."))
|
|
|
|
|
|
if self._kanban_mode == "grid":
|
|
|
|
|
|
return Grid(*cols, classes="kgrid")
|
|
|
|
|
|
if self._kanban_mode == "swimlanes":
|
|
|
|
|
|
# Une rangée (swimlane) par menu de NIVEAU 1 (Execute, …).
|
|
|
|
|
|
groups, order = {}, []
|
|
|
|
|
|
for (label, path, cnt, cmds) in columns:
|
|
|
|
|
|
parts = path.split(" › ")
|
|
|
|
|
|
g = parts[1] if len(parts) > 1 else "TODO"
|
|
|
|
|
|
if g not in groups:
|
|
|
|
|
|
groups[g] = []
|
|
|
|
|
|
order.append(g)
|
|
|
|
|
|
groups[g].append((label, cnt, cmds))
|
|
|
|
|
|
lanes = []
|
|
|
|
|
|
for g in order:
|
|
|
|
|
|
lane_cols = [
|
|
|
|
|
|
self._col_widget(lb, cn, cm) for (lb, cn, cm) in groups[g]
|
|
|
|
|
|
]
|
|
|
|
|
|
lanes.append(
|
|
|
|
|
|
Vertical(
|
|
|
|
|
|
Static(f"━━ {g} ━━", classes="lanetitle"),
|
|
|
|
|
|
HorizontalScroll(*lane_cols),
|
|
|
|
|
|
classes="lane",
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
return VerticalScroll(*lanes)
|
|
|
|
|
|
return HorizontalScroll(*cols) # columns
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
|
2026-07-29 05:35:31 -04:00
|
|
|
|
def compose(self) -> ComposeResult:
|
|
|
|
|
|
yield Header(show_clock=True)
|
|
|
|
|
|
yield Static("", id="summary")
|
|
|
|
|
|
yield Tree("📍 TODO", id="nav")
|
2026-07-29 06:09:06 -04:00
|
|
|
|
yield Container(id="kanban")
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
yield Static("", id="system")
|
2026-07-29 05:35:31 -04:00
|
|
|
|
yield Footer()
|
|
|
|
|
|
|
|
|
|
|
|
def on_mount(self):
|
|
|
|
|
|
self.title = t("TODO navigation telemetry")
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
self._populate_tree()
|
|
|
|
|
|
self._update_summary()
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
# Télémétrie système rafraîchie toutes les 2 s (deltas CPU/réseau).
|
|
|
|
|
|
self.set_interval(2.0, self._tick_system)
|
2026-07-29 06:09:06 -04:00
|
|
|
|
# Restaure la vue / la disposition / le curseur au retour.
|
|
|
|
|
|
self.run_worker(self._restore())
|
|
|
|
|
|
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
def _apply_visibility(self):
|
|
|
|
|
|
self.query_one("#nav").display = self._mode == "tree"
|
|
|
|
|
|
self.query_one("#kanban").display = self._mode == "kanban"
|
|
|
|
|
|
self.query_one("#system").display = self._mode == "system"
|
|
|
|
|
|
|
2026-07-29 06:09:06 -04:00
|
|
|
|
async def _restore(self):
|
|
|
|
|
|
if self._mode == "kanban":
|
|
|
|
|
|
await self._enter_kanban()
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
elif self._mode == "tree":
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self._focus_tree_path(state.get("path"))
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
self._apply_visibility()
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self._update_summary()
|
|
|
|
|
|
|
|
|
|
|
|
# -- vue Kanban ------------------------------------------------------ #
|
|
|
|
|
|
async def _enter_kanban(self):
|
|
|
|
|
|
box = self.query_one("#kanban", Container)
|
|
|
|
|
|
await box.remove_children()
|
|
|
|
|
|
await box.mount(self._kanban_layout_widget())
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
self._apply_visibility()
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self._focus_kanban_path(state.get("path"))
|
|
|
|
|
|
|
|
|
|
|
|
def _focus_kanban_path(self, path):
|
|
|
|
|
|
if not path:
|
|
|
|
|
|
return
|
|
|
|
|
|
for lv in self.query(ListView):
|
|
|
|
|
|
for i, item in enumerate(lv.children):
|
|
|
|
|
|
if getattr(item, "cmd_path", None) == path:
|
|
|
|
|
|
lv.index = i
|
|
|
|
|
|
lv.focus()
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
def _focus_tree_path(self, path):
|
|
|
|
|
|
if not path:
|
|
|
|
|
|
return
|
|
|
|
|
|
tree = self.query_one("#nav", Tree)
|
|
|
|
|
|
|
|
|
|
|
|
def walk(node):
|
|
|
|
|
|
d = getattr(node, "data", None)
|
|
|
|
|
|
if isinstance(d, dict) and d.get("path") == path:
|
|
|
|
|
|
return node
|
|
|
|
|
|
for ch in node.children:
|
|
|
|
|
|
found = walk(ch)
|
|
|
|
|
|
if found:
|
|
|
|
|
|
return found
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
node = walk(tree.root)
|
|
|
|
|
|
if node is not None:
|
|
|
|
|
|
tree.move_cursor(node)
|
|
|
|
|
|
tree.scroll_to_node(node)
|
2026-07-29 05:35:31 -04:00
|
|
|
|
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
def _update_summary(self):
|
2026-07-29 05:35:31 -04:00
|
|
|
|
total = sum(paths.values())
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
src = t("tree from code") if code_tree else t("visited paths only")
|
2026-07-29 06:09:06 -04:00
|
|
|
|
extra = (
|
|
|
|
|
|
f" [{self._kanban_mode}]" if self._mode == "kanban" else ""
|
|
|
|
|
|
)
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
self.query_one("#summary", Static).update(
|
|
|
|
|
|
f" {total} {t('navigations')} · {len(paths)} {t('menus')} · "
|
2026-07-29 06:09:06 -04:00
|
|
|
|
f"{src} · {t('view')}: {self._mode}{extra} · "
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
f"{t('F2 system · F3/F4 views · Enter run')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# -- vue Système (F2) ----------------------------------------------- #
|
|
|
|
|
|
async def _tick_system(self):
|
|
|
|
|
|
# I/O système déportées en thread (comme le dashboard) ; on saute
|
|
|
|
|
|
# la température (subprocess sensors) tant que la vue n'est pas
|
|
|
|
|
|
# affichée.
|
|
|
|
|
|
try:
|
|
|
|
|
|
m, self._sys_prev = await asyncio.to_thread(
|
|
|
|
|
|
system_snapshot, self._sys_prev, self._mode == "system"
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
return
|
|
|
|
|
|
if self._mode == "system":
|
|
|
|
|
|
self.query_one("#system", Static).update(
|
|
|
|
|
|
self._render_system(m)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _render_system(self, m):
|
|
|
|
|
|
lines = []
|
|
|
|
|
|
load = (
|
|
|
|
|
|
" · " + t("load") + " "
|
|
|
|
|
|
+ "/".join(f"{x:.1f}" for x in m["load"])
|
|
|
|
|
|
if m["load"]
|
|
|
|
|
|
else ""
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
)
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 🖥 {t('State')} : {t('uptime')} "
|
|
|
|
|
|
f"{_fmt_uptime(m['uptime'])}{load}"
|
|
|
|
|
|
)
|
|
|
|
|
|
cpu = m["cpu"]
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" ⚙ CPU : {cpu if cpu is not None else '?'} %"
|
|
|
|
|
|
f" ({m['ncpu']} {t('cores')})"
|
|
|
|
|
|
)
|
|
|
|
|
|
if m["mem"]:
|
|
|
|
|
|
tot, used = m["mem"]
|
|
|
|
|
|
pct = int(used / tot * 100) if tot else 0
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 🧠 {t('Memory')} : {_fmt_size(used)} / "
|
|
|
|
|
|
f"{_fmt_size(tot)} ({pct} %)"
|
|
|
|
|
|
)
|
|
|
|
|
|
if m["disk"]:
|
|
|
|
|
|
tot, used, free = m["disk"]
|
|
|
|
|
|
pct = int(used / tot * 100) if tot else 0
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 💽 {t('Disk')} / : {_fmt_size(used)} / "
|
|
|
|
|
|
f"{_fmt_size(tot)} ({pct} %) · {t('free')} "
|
|
|
|
|
|
f"{_fmt_size(free)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if m["net"]:
|
|
|
|
|
|
rx, tx = m["net"]
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 🌐 {t('Network')} : ↓ {_fmt_rate(rx)} "
|
|
|
|
|
|
f"↑ {_fmt_rate(tx)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if m["battery"]:
|
|
|
|
|
|
cap, status = m["battery"]
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 🔋 {t('Battery')} : {cap} % ({status})"
|
|
|
|
|
|
)
|
|
|
|
|
|
temp = m["temp"]
|
|
|
|
|
|
if temp:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 🌡 {t('Temperature')} : {max(temp[1]):.0f}°C "
|
|
|
|
|
|
f"({t('max')})"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
lines.append(
|
|
|
|
|
|
f" 🌡 {t('Temperature')} : "
|
|
|
|
|
|
f"{t('lm-sensors absent — press i to install')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
# -- actions --------------------------------------------------------- #
|
|
|
|
|
|
def action_system(self):
|
|
|
|
|
|
self._mode = "system"
|
|
|
|
|
|
self._apply_visibility()
|
|
|
|
|
|
self.run_worker(self._tick_system()) # rafraîchit tout de suite
|
|
|
|
|
|
self._update_summary()
|
|
|
|
|
|
|
|
|
|
|
|
def action_install_sensors(self):
|
|
|
|
|
|
if self._mode != "system":
|
|
|
|
|
|
return
|
|
|
|
|
|
if read_temperature() is not None:
|
|
|
|
|
|
self.notify(t("Sensors already available."))
|
|
|
|
|
|
return
|
|
|
|
|
|
cmd = sensors_install_command()
|
|
|
|
|
|
if not cmd:
|
|
|
|
|
|
self.notify(
|
|
|
|
|
|
t("Unknown package manager for lm-sensors."),
|
|
|
|
|
|
severity="warning",
|
|
|
|
|
|
)
|
|
|
|
|
|
return
|
|
|
|
|
|
printable = " ".join(cmd)
|
|
|
|
|
|
with self.suspend():
|
|
|
|
|
|
print(f"{t('Proposed install command:')}")
|
|
|
|
|
|
print(f" {printable}")
|
|
|
|
|
|
print(" sudo sensors-detect --auto")
|
|
|
|
|
|
ans = input(
|
|
|
|
|
|
t("Install lm-sensors now? (y/N): ")
|
|
|
|
|
|
).strip().lower()
|
|
|
|
|
|
if ans in ("o", "oui", "y", "yes"):
|
|
|
|
|
|
os.system(printable + " || true")
|
|
|
|
|
|
os.system("sudo sensors-detect --auto || true")
|
2026-07-29 06:35:24 -04:00
|
|
|
|
# Rafraîchit IMMÉDIATEMENT la vue système : on ré-échantillonne
|
|
|
|
|
|
# (température incluse) et on réécrit la case pour que le message
|
|
|
|
|
|
# « installer » disparaisse si les capteurs sont désormais lisibles.
|
|
|
|
|
|
self._sys_prev = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
m, self._sys_prev = system_snapshot(self._sys_prev, full=True)
|
|
|
|
|
|
self.query_one("#system", Static).update(
|
|
|
|
|
|
self._render_system(m)
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
|
|
|
|
|
self.refresh()
|
|
|
|
|
|
if read_temperature() is not None:
|
|
|
|
|
|
self.notify(t("Sensors now available."))
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.notify(
|
|
|
|
|
|
t("Still no temperature (reboot/modprobe may be needed)."),
|
|
|
|
|
|
severity="warning",
|
|
|
|
|
|
)
|
[IMP] script todo: télémétrie — vue Système (F2) + case agrandissable (grille)
- F2 : nouvelle vue SYSTÈME (état/uptime + charge, CPU %, mémoire, disque,
réseau ↓/↑, batterie, température). Les I/O sont déportées en thread
(asyncio.to_thread) ; rafraîchissement 2 s (deltas CPU/réseau). Si la
température n'est pas lisible (ni /sys/class/thermal ni lm-sensors), on
propose d'installer lm-sensors avec la touche « i » (commande selon l'OS —
apt/dnf/pacman — affichée puis validée). read_temperature essaie
/sys/class/thermal d'abord (sans dépendance).
- Vue Kanban GRILLE : clic sur le TITRE d'une case -> elle s'AGRANDIT
(row-span, prend toute la hauteur d'une colonne) ; re-clic -> taille
normale (classe kbig).
Validé headless : vue système peuplée (mém/disque/temp), F2/F3 basculent,
clic titre en grille -> kbig on/off.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 06:22:11 -04:00
|
|
|
|
|
|
|
|
|
|
def on_click(self, event):
|
|
|
|
|
|
# Grille (mosaïque) : clic sur le TITRE d'une case -> l'agrandir
|
|
|
|
|
|
# (row-span sur toute la hauteur) ; re-clic -> taille normale.
|
|
|
|
|
|
if self._mode != "kanban" or self._kanban_mode != "grid":
|
|
|
|
|
|
return
|
|
|
|
|
|
w = getattr(event, "widget", None)
|
|
|
|
|
|
if w is None or "ktitle" not in getattr(w, "classes", ()):
|
|
|
|
|
|
return
|
|
|
|
|
|
card = w
|
|
|
|
|
|
while card is not None and "kcol" not in getattr(
|
|
|
|
|
|
card, "classes", ()
|
|
|
|
|
|
):
|
|
|
|
|
|
card = card.parent
|
|
|
|
|
|
if card is not None:
|
|
|
|
|
|
card.toggle_class("kbig")
|
|
|
|
|
|
|
|
|
|
|
|
# -- actions --------------------------------------------------------- #
|
|
|
|
|
|
async def action_toggle_view(self):
|
|
|
|
|
|
if self._mode == "tree":
|
|
|
|
|
|
self._mode = "kanban"
|
|
|
|
|
|
await self._enter_kanban()
|
|
|
|
|
|
else:
|
|
|
|
|
|
self._mode = "tree"
|
|
|
|
|
|
self._apply_visibility()
|
|
|
|
|
|
self._update_summary()
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
|
|
|
|
|
|
def _populate_tree(self):
|
2026-07-29 05:35:31 -04:00
|
|
|
|
tree = self.query_one("#nav", Tree)
|
|
|
|
|
|
tree.clear()
|
2026-07-29 05:49:13 -04:00
|
|
|
|
if code_tree is not None:
|
2026-07-29 06:09:06 -04:00
|
|
|
|
tree.root.data = {"path": "TODO"}
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
tree.root.set_label(f"📍 TODO ({paths.get('TODO', 0)})")
|
|
|
|
|
|
self._add_code(tree.root, code_tree["children"], "TODO")
|
2026-07-29 05:49:13 -04:00
|
|
|
|
else:
|
|
|
|
|
|
nested = _nested(paths)
|
|
|
|
|
|
todo = nested.get("TODO", {"count": 0, "children": nested})
|
|
|
|
|
|
tree.root.set_label(f"📍 TODO ({todo['count']})")
|
|
|
|
|
|
self._add_visited(tree.root, todo["children"])
|
2026-07-29 05:35:31 -04:00
|
|
|
|
tree.root.expand()
|
2026-07-29 05:49:13 -04:00
|
|
|
|
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
def _add_code(self, tnode, children, parent_path):
|
2026-07-29 05:49:13 -04:00
|
|
|
|
for c in children:
|
2026-07-29 06:09:06 -04:00
|
|
|
|
path = f"{parent_path} › {c['label']}"
|
2026-07-29 05:49:13 -04:00
|
|
|
|
if c["is_menu"]:
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
child = tnode.add(
|
2026-07-29 06:09:06 -04:00
|
|
|
|
f"{c['label']} ({paths.get(path, 0)})",
|
|
|
|
|
|
data={"path": path},
|
|
|
|
|
|
expand=True,
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
)
|
|
|
|
|
|
self._add_code(child, c["children"], path)
|
2026-07-29 05:49:13 -04:00
|
|
|
|
else:
|
2026-07-29 06:09:06 -04:00
|
|
|
|
# Feuille EXÉCUTABLE : méthode + chemin portés en data.
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
tnode.add_leaf(
|
|
|
|
|
|
f"· {c['label']}",
|
|
|
|
|
|
data={
|
|
|
|
|
|
"method": c.get("method"),
|
|
|
|
|
|
"kwargs": c.get("kwargs"),
|
2026-07-29 06:09:06 -04:00
|
|
|
|
"path": path,
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
},
|
|
|
|
|
|
)
|
2026-07-29 05:35:31 -04:00
|
|
|
|
|
2026-07-29 05:49:13 -04:00
|
|
|
|
def _add_visited(self, tnode, children):
|
2026-07-29 05:35:31 -04:00
|
|
|
|
for name, node in sorted(
|
|
|
|
|
|
children.items(), key=lambda kv: -kv[1]["count"]
|
|
|
|
|
|
):
|
|
|
|
|
|
child = tnode.add(f"{name} ({node['count']})", expand=True)
|
2026-07-29 05:49:13 -04:00
|
|
|
|
self._add_visited(child, node["children"])
|
2026-07-29 05:35:31 -04:00
|
|
|
|
|
2026-07-29 06:09:06 -04:00
|
|
|
|
# -- sélection / exécution ------------------------------------------- #
|
|
|
|
|
|
def _run(self, method, kwargs, path):
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
if not method:
|
|
|
|
|
|
return
|
|
|
|
|
|
self._action = (method, kwargs or {})
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self._exit_state = {
|
|
|
|
|
|
"mode": self._mode,
|
|
|
|
|
|
"kanban_mode": self._kanban_mode,
|
|
|
|
|
|
"path": path,
|
|
|
|
|
|
}
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
self.exit()
|
|
|
|
|
|
|
|
|
|
|
|
def on_tree_node_selected(self, event):
|
2026-07-29 06:09:06 -04:00
|
|
|
|
d = getattr(event.node, "data", None)
|
|
|
|
|
|
if isinstance(d, dict) and d.get("method"):
|
|
|
|
|
|
self._run(d["method"], d.get("kwargs"), d.get("path"))
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
|
|
|
|
|
|
def on_list_view_selected(self, event):
|
|
|
|
|
|
if isinstance(event.item, CmdItem):
|
2026-07-29 06:09:06 -04:00
|
|
|
|
self._run(
|
|
|
|
|
|
event.item.cmd_method,
|
|
|
|
|
|
event.item.cmd_kwargs,
|
|
|
|
|
|
event.item.cmd_path,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def action_kanban_layout(self):
|
|
|
|
|
|
if self._mode != "kanban":
|
|
|
|
|
|
return
|
|
|
|
|
|
i = KANBAN_MODES.index(self._kanban_mode)
|
|
|
|
|
|
self._kanban_mode = KANBAN_MODES[(i + 1) % len(KANBAN_MODES)]
|
|
|
|
|
|
await self._enter_kanban()
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
self._update_summary()
|
|
|
|
|
|
|
|
|
|
|
|
def action_expand_all(self):
|
|
|
|
|
|
if self._mode == "tree":
|
|
|
|
|
|
self.query_one("#nav", Tree).root.expand_all()
|
|
|
|
|
|
|
2026-07-29 05:35:31 -04:00
|
|
|
|
def action_reset(self):
|
|
|
|
|
|
reset()
|
[IMP] script todo: télémétrie — exécuter une commande + vue Kanban (F3)
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-29 05:56:50 -04:00
|
|
|
|
paths.clear()
|
|
|
|
|
|
self._populate_tree()
|
|
|
|
|
|
self._update_summary()
|
2026-07-29 05:35:31 -04:00
|
|
|
|
self.notify(t("Telemetry reset."))
|
|
|
|
|
|
|
|
|
|
|
|
app = Telemetry()
|
2026-07-29 06:09:06 -04:00
|
|
|
|
app._exit_state = state
|
2026-07-29 05:35:31 -04:00
|
|
|
|
if run_app:
|
|
|
|
|
|
app.run()
|
2026-07-29 06:09:06 -04:00
|
|
|
|
return app._action, getattr(app, "_exit_state", state)
|
2026-07-29 05:35:31 -04:00
|
|
|
|
return app
|