Ajout log dans interface

Passe a la version 0.2
This commit is contained in:
2026-03-24 13:17:28 +01:00
parent ba1cf1ea25
commit fe56e563f3
2 changed files with 94 additions and 15 deletions

Binary file not shown.

View File

@@ -5,7 +5,7 @@ Accès lecture seule uniquement (fetch/pull/checkout, jamais de push).
Tous les chemins sont relatifs à l'emplacement de l'exécutable.
"""
VERSION = "0.1"
VERSION = "0.2"
import subprocess
import sys
@@ -460,9 +460,13 @@ class App(tk.Tk):
date_label = ttk.Label(self, text=datetime.now().strftime(" %d/%m/%Y %H:%M:%S"), style="Status.TLabel")
date_label.pack(anchor="w", padx=15)
# Panneau principal (PanedWindow vertical : cartes en haut, log en bas)
paned = ttk.PanedWindow(self, orient="vertical")
paned.pack(fill="both", expand=True, padx=15, pady=10)
# Zone scrollable pour les repos
container = ttk.Frame(self)
container.pack(fill="both", expand=True, padx=15, pady=10)
container = ttk.Frame(paned)
paned.add(container, weight=3)
self.canvas = tk.Canvas(container, bg=bg, highlightthickness=0)
scrollbar = ttk.Scrollbar(container, orient="vertical", command=self.canvas.yview)
@@ -478,6 +482,35 @@ class App(tk.Tk):
# Scroll avec la molette
self.canvas.bind_all("<MouseWheel>", lambda e: self.canvas.yview_scroll(int(-1 * (e.delta / 120)), "units"))
# Panneau de log en bas
log_frame = tk.Frame(paned, bg="#181825")
paned.add(log_frame, weight=1)
log_header = tk.Frame(log_frame, bg="#181825")
log_header.pack(fill="x", padx=8, pady=(6, 2))
tk.Label(log_header, text="Journal des operations", bg="#181825", fg="#a6adc8", font=("Segoe UI", 9, "bold")).pack(side="left")
tk.Button(log_header, text="Effacer", bg="#313244", fg="#cdd6f4", bd=0, font=("Segoe UI", 8),
command=self._clear_log_panel, activebackground="#45475a", activeforeground="#cdd6f4").pack(side="right")
self.log_text = tk.Text(log_frame, bg="#11111b", fg="#cdd6f4", font=("Consolas", 9),
height=8, bd=0, highlightthickness=0, wrap="word",
state="disabled", padx=8, pady=4)
log_scroll = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
self.log_text.configure(yscrollcommand=log_scroll.set)
self.log_text.pack(side="left", fill="both", expand=True, padx=(8, 0), pady=(0, 8))
log_scroll.pack(side="right", fill="y", padx=(0, 8), pady=(0, 8))
# Tags couleur pour le log GUI
self.log_text.tag_configure("info", foreground="#cdd6f4")
self.log_text.tag_configure("success", foreground="#a6e3a1")
self.log_text.tag_configure("warning", foreground="#f9e2af")
self.log_text.tag_configure("error", foreground="#f38ba8")
self.log_text.tag_configure("file_add", foreground="#a6e3a1")
self.log_text.tag_configure("file_mod", foreground="#f9e2af")
self.log_text.tag_configure("file_del", foreground="#f38ba8")
self.log_text.tag_configure("dim", foreground="#6c7086")
# Boutons en bas
btn_frame = ttk.Frame(self)
btn_frame.pack(fill="x", padx=15, pady=(0, 15))
@@ -504,6 +537,26 @@ class App(tk.Tk):
threading.Thread(target=work, daemon=True).start()
def _log_gui(self, message, tag="info"):
"""Ajoute une ligne dans le panneau de log."""
def _append():
self.log_text.configure(state="normal")
timestamp = datetime.now().strftime("%H:%M:%S")
self.log_text.insert("end", f"[{timestamp}] ", "dim")
self.log_text.insert("end", f"{message}\n", tag)
self.log_text.see("end")
self.log_text.configure(state="disabled")
# Appel thread-safe
if threading.current_thread() is threading.main_thread():
_append()
else:
self.after(0, _append)
def _clear_log_panel(self):
self.log_text.configure(state="normal")
self.log_text.delete("1.0", "end")
self.log_text.configure(state="disabled")
def _handle_self_update(self, needs_update, info):
"""Gère le résultat de l'auto-update."""
if needs_update:
@@ -540,6 +593,7 @@ class App(tk.Tk):
self.btn_refresh.state(["disabled"])
self.btn_update_all.state(["disabled"])
self.status_label.configure(text="Verification en cours...")
self._log_gui("Verification des depots...", "info")
self.repos_config = load_repos()
if not self.repos_config:
@@ -573,6 +627,7 @@ class App(tk.Tk):
total = len(self.repo_results)
up = sum(1 for r in self.repo_results if r["up_to_date"])
log.info(f"Resultat: {up}/{total} depots a jour")
self._log_gui(f"Verification terminee : {up}/{total} depots a jour", "success" if up == total else "warning")
self.status_label.configure(text=f"{up}/{total} depots a jour")
self.btn_refresh.state(["!disabled"])
@@ -678,7 +733,9 @@ class App(tk.Tk):
def _do_update(self, res):
"""Met à jour un dépôt (pull + restore)."""
log.info(f"[{res['name']}] MAJ unitaire demandee")
name = res["name"]
log.info(f"[{name}] MAJ unitaire demandee")
self._log_gui(f"[{name}] Mise a jour en cours...", "info")
if "_btn" in res:
res["_btn"].state(["disabled"])
@@ -688,41 +745,60 @@ class App(tk.Tk):
local_path = res["local_path"]
branch = res["branch"]
tag_map = {"A": "file_add", "M": "file_mod", "D": "file_del", "R": "file_mod"}
if res["commits"]:
log.info(f"[{res['name']}] Pull de {len(res['commits'])} commit(s)...")
log.info(f"[{name}] Pull de {len(res['commits'])} commit(s)...")
self._log_gui(f"[{name}] Telechargement de {len(res['commits'])} commit(s)...", "info")
ok, out, err = do_pull(local_path, branch)
if ok:
msg = f"Pull OK : {len(res['commits'])} commits telecharges."
log.info(f"[{res['name']}] {msg}")
log.info(f"[{name}] {msg}")
self._log_gui(f"[{name}] {msg}", "success")
# Logger chaque fichier distant
for f in res.get("files", []):
tag = tag_map.get(f["status_char"], "info")
self._log_gui(f" [{f['status']:>9}] {f['file']}", tag)
messages.append(msg)
else:
msg = f"Erreur pull : {err}"
log.error(f"[{res['name']}] {msg}")
log.error(f"[{name}] {msg}")
self._log_gui(f"[{name}] {msg}", "error")
messages.append(msg)
success = False
if res["local_changes"]:
log.info(f"[{res['name']}] Restauration de {len(res['local_changes'])} fichier(s)...")
log.info(f"[{name}] Restauration de {len(res['local_changes'])} fichier(s)...")
self._log_gui(f"[{name}] Restauration de {len(res['local_changes'])} fichier(s)...", "info")
ok, err = do_restore(local_path)
if ok:
msg = f"Restauration OK : {len(res['local_changes'])} fichiers restaures."
log.info(f"[{res['name']}] {msg}")
log.info(f"[{name}] {msg}")
self._log_gui(f"[{name}] {msg}", "success")
# Logger chaque fichier restauré
for f in res["local_changes"]:
tag = tag_map.get(f["status_char"], "info")
self._log_gui(f" [Restaure] {f['file']}", tag)
messages.append(msg)
else:
msg = f"Erreur restauration : {err}"
log.error(f"[{res['name']}] {msg}")
log.error(f"[{name}] {msg}")
self._log_gui(f"[{name}] {msg}", "error")
messages.append(msg)
success = False
status = "SUCCES" if success else "ECHEC"
log.info(f"[{res['name']}] MAJ unitaire terminee - {status}")
log.info(f"[{name}] MAJ unitaire terminee - {status}")
self._log_gui(f"[{name}] Termine - {status}", "success" if success else "error")
self.after(0, lambda: self._show_update_result(res, messages, success))
threading.Thread(target=work, daemon=True).start()
def _do_clone(self, res):
"""Clone un dépôt."""
log.info(f"[{res['name']}] Clonage demande - {res['url']}")
name = res["name"]
log.info(f"[{name}] Clonage demande - {res['url']}")
self._log_gui(f"[{name}] Clonage en cours depuis {res['url']}...", "info")
if "_btn" in res:
res["_btn"].state(["disabled"])
@@ -731,11 +807,13 @@ class App(tk.Tk):
def work():
ok, err = do_clone(repo)
if ok:
msg = f"Depot '{res['name']}' clone avec succes !"
log.info(f"[{res['name']}] {msg}")
msg = f"Depot '{name}' clone avec succes !"
log.info(f"[{name}] {msg}")
self._log_gui(f"[{name}] {msg}", "success")
else:
msg = f"Erreur de clonage : {err}"
log.error(f"[{res['name']}] {msg}")
log.error(f"[{name}] {msg}")
self._log_gui(f"[{name}] {msg}", "error")
self.after(0, lambda: messagebox.showinfo("Clonage", msg))
self.after(100, self._start_check)
@@ -750,6 +828,7 @@ class App(tk.Tk):
"""Met à jour tous les dépôts qui ont des MAJ."""
to_update = [r for r in self.repo_results if not r["up_to_date"] and not r.get("error") and not r.get("needs_clone")]
log.info(f"MAJ globale demandee - {len(to_update)} depot(s) a mettre a jour")
self._log_gui(f"MAJ globale : {len(to_update)} depot(s) a mettre a jour", "warning")
for res in to_update:
self._do_update(res)