diff --git a/GitUpdateChecker.exe b/GitUpdateChecker.exe index d14ccad..46df93b 100644 Binary files a/GitUpdateChecker.exe and b/GitUpdateChecker.exe differ diff --git a/git_updater.py b/git_updater.py index 053d32d..7e0f38c 100644 --- a/git_updater.py +++ b/git_updater.py @@ -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("", 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)