commit f3370cde1daac8a08d2d289f23ec91d5befdd1ee Author: 7ka1 <7ka1@noreply.localhost> Date: Tue Apr 21 14:48:19 2026 +0000 Ajouter 7Disk.py diff --git a/7Disk.py b/7Disk.py new file mode 100644 index 0000000..bc0cd3b --- /dev/null +++ b/7Disk.py @@ -0,0 +1,232 @@ +import customtkinter as ctk +import psutil +import subprocess +import shutil +import threading +import json +import os + +class SevenDiskApp(ctk.CTk): + def __init__(self): + super().__init__() + + # Configuration de la fenêtre principale + self.title("7Disk - Moniteur de Stockage Pro") + self.geometry("900x600") + self.minsize(800, 500) + ctk.set_appearance_mode("System") + ctk.set_default_color_theme("blue") + + # Configuration de la grille (Sidebar + Contenu principal) + self.grid_rowconfigure(0, weight=1) + self.grid_columnconfigure(1, weight=1) + + # --- BARRE LATÉRALE (SIDEBAR) --- + self.sidebar_frame = ctk.CTkFrame(self, width=200, corner_radius=0) + self.sidebar_frame.grid(row=0, column=0, sticky="nsew") + self.sidebar_frame.grid_rowconfigure(4, weight=1) + + self.logo_label = ctk.CTkLabel(self.sidebar_frame, text="7Disk ⚡", font=ctk.CTkFont(size=24, weight="bold")) + self.logo_label.grid(row=0, column=0, padx=20, pady=(20, 30)) + + self.btn_tab_overview = ctk.CTkButton(self.sidebar_frame, text="📊 Vue d'ensemble", fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"), anchor="w", command=lambda: self.select_tab("overview")) + self.btn_tab_overview.grid(row=1, column=0, padx=20, pady=10, sticky="ew") + + self.btn_tab_smart = ctk.CTkButton(self.sidebar_frame, text="🩺 Santé S.M.A.R.T.", fg_color="transparent", text_color=("gray10", "gray90"), hover_color=("gray70", "gray30"), anchor="w", command=lambda: self.select_tab("smart")) + self.btn_tab_smart.grid(row=2, column=0, padx=20, pady=10, sticky="ew") + + self.appearance_mode_label = ctk.CTkLabel(self.sidebar_frame, text="Thème:", anchor="w") + self.appearance_mode_label.grid(row=5, column=0, padx=20, pady=(10, 0)) + self.appearance_mode_menu = ctk.CTkOptionMenu(self.sidebar_frame, values=["System", "Dark", "Light"], command=self.change_appearance_mode) + self.appearance_mode_menu.grid(row=6, column=0, padx=20, pady=(10, 20)) + + # --- ZONE DE CONTENU PRINCIPAL --- + self.main_frame = ctk.CTkFrame(self, corner_radius=10, fg_color="transparent") + self.main_frame.grid(row=0, column=1, padx=20, pady=20, sticky="nsew") + self.main_frame.grid_rowconfigure(1, weight=1) + self.main_frame.grid_columnconfigure(0, weight=1) + + # Création des différentes vues + self.create_overview_tab() + self.create_smart_tab() + + # Sélection de l'onglet par défaut + self.select_tab("overview") + + def change_appearance_mode(self, new_appearance_mode: str): + ctk.set_appearance_mode(new_appearance_mode) + + def select_tab(self, tab_name): + # Cache toutes les frames + self.frame_overview.grid_forget() + self.frame_smart.grid_forget() + + # Réinitialise les couleurs des boutons + self.btn_tab_overview.configure(fg_color="transparent") + self.btn_tab_smart.configure(fg_color="transparent") + + # Affiche la frame demandée + if tab_name == "overview": + self.frame_overview.grid(row=0, column=0, sticky="nsew") + self.btn_tab_overview.configure(fg_color=("gray75", "gray25")) + self.load_overview_data() + elif tab_name == "smart": + self.frame_smart.grid(row=0, column=0, sticky="nsew") + self.btn_tab_smart.configure(fg_color=("gray75", "gray25")) + + # ========================================== + # ONGLET 1 : VUE D'ENSEMBLE (Partitions) + # ========================================== + def create_overview_tab(self): + self.frame_overview = ctk.CTkScrollableFrame(self.main_frame, fg_color="transparent") + self.frame_overview.grid_columnconfigure(0, weight=1) + + self.lbl_overview_title = ctk.CTkLabel(self.frame_overview, text="Stockage Local", font=ctk.CTkFont(size=20, weight="bold")) + self.lbl_overview_title.grid(row=0, column=0, sticky="w", pady=(0, 20)) + + self.partitions_frame = ctk.CTkFrame(self.frame_overview, fg_color="transparent") + self.partitions_frame.grid(row=1, column=0, sticky="nsew") + self.partitions_frame.grid_columnconfigure(0, weight=1) + + def load_overview_data(self): + # Nettoyage de l'affichage existant + for widget in self.partitions_frame.winfo_children(): + widget.destroy() + + partitions = psutil.disk_partitions(all=False) + row_idx = 0 + + for p in partitions: + # Ignorer les systèmes de fichiers virtuels et les snaps de Linux + if p.fstype == "" or "snap" in p.mountpoint or "boot" in p.mountpoint: + continue + + try: + usage = psutil.disk_usage(p.mountpoint) + total_gb = usage.total // (2**30) + used_gb = usage.used // (2**30) + free_gb = usage.free // (2**30) + percent = usage.percent + + card = ctk.CTkFrame(self.partitions_frame, corner_radius=10) + card.grid(row=row_idx, column=0, sticky="ew", pady=10) + card.grid_columnconfigure(1, weight=1) + + lbl_mount = ctk.CTkLabel(card, text=f"Point de montage : {p.mountpoint} ({p.fstype})", font=ctk.CTkFont(weight="bold")) + lbl_mount.grid(row=0, column=0, columnspan=2, sticky="w", padx=15, pady=(10, 5)) + + lbl_info = ctk.CTkLabel(card, text=f"{used_gb} Go utilisés sur {total_gb} Go | Libre: {free_gb} Go") + lbl_info.grid(row=1, column=0, sticky="w", padx=15, pady=0) + + lbl_percent = ctk.CTkLabel(card, text=f"{percent}%", font=ctk.CTkFont(weight="bold")) + lbl_percent.grid(row=1, column=1, sticky="e", padx=15, pady=0) + + prog = ctk.CTkProgressBar(card) + prog.grid(row=2, column=0, columnspan=2, sticky="ew", padx=15, pady=(5, 15)) + prog.set(percent / 100) + + if percent > 90: + prog.configure(progress_color="#e74c3c") # Rouge + elif percent > 75: + prog.configure(progress_color="#f39c12") # Orange + else: + prog.configure(progress_color="#2ecc71") # Vert + + row_idx += 1 + except PermissionError: + continue + + # ========================================== + # ONGLET 2 : SANTÉ S.M.A.R.T (Physique) + # ========================================== + def create_smart_tab(self): + self.frame_smart = ctk.CTkFrame(self.main_frame, fg_color="transparent") + self.frame_smart.grid_columnconfigure(0, weight=1) + self.frame_smart.grid_rowconfigure(2, weight=1) + + self.lbl_smart_title = ctk.CTkLabel(self.frame_smart, text="Diagnostic Matériel (S.M.A.R.T.)", font=ctk.CTkFont(size=20, weight="bold")) + self.lbl_smart_title.grid(row=0, column=0, sticky="w", pady=(0, 10)) + + # Contrôles du haut + self.controls_frame = ctk.CTkFrame(self.frame_smart, fg_color="transparent") + self.controls_frame.grid(row=1, column=0, sticky="ew", pady=10) + + self.disk_selector = ctk.CTkOptionMenu(self.controls_frame, values=["/dev/nvme0n1", "/dev/sda", "/dev/sdb"]) + self.disk_selector.pack(side="left", padx=(0, 10)) + + self.btn_scan = ctk.CTkButton(self.controls_frame, text="🔍 Lancer le Scan Profond", command=self.start_smart_scan) + self.btn_scan.pack(side="left") + + self.lbl_status = ctk.CTkLabel(self.controls_frame, text="", text_color="gray") + self.lbl_status.pack(side="left", padx=10) + + # Zone d'affichage des résultats + self.textbox_smart = ctk.CTkTextbox(self.frame_smart, font=ctk.CTkFont(family="monospace", size=12)) + self.textbox_smart.grid(row=2, column=0, sticky="nsew", pady=10) + self.textbox_smart.insert("0.0", "Sélectionnez un disque et lancez le scan.\nLes droits administrateurs vous seront demandés via une fenêtre Zorin OS.") + self.textbox_smart.configure(state="disabled") + + def start_smart_scan(self): + disk = self.disk_selector.get() + self.btn_scan.configure(state="disabled") + self.lbl_status.configure(text="Analyse en cours, veuillez patienter...") + self.textbox_smart.configure(state="normal") + self.textbox_smart.delete("0.0", "end") + self.textbox_smart.configure(state="disabled") + + # Lancement de l'analyse dans un Thread séparé pour ne pas figer l'interface + threading.Thread(target=self.fetch_smart_data, args=(disk,), daemon=True).start() + + def fetch_smart_data(self, disk_path): + try: + # Utilisation de -j pour récupérer du JSON, beaucoup plus propre à traiter + result = subprocess.run(['pkexec', 'smartctl', '-a', '-j', disk_path], capture_output=True, text=True) + + if result.returncode == 126 or result.returncode == 127: + output_text = "❌ Erreur : Droits administrateur refusés ou smartctl non trouvé." + else: + try: + data = json.loads(result.stdout) + + model = data.get("model_name", "Inconnu") + serial = data.get("serial_number", "Inconnu") + passed = data.get("smart_status", {}).get("passed", False) + temp = data.get("temperature", {}).get("current", "N/A") + + status_icon = "✅ BON" if passed else "❌ DÉFAILLANT" + + output_text = f"=== RAPPPORT 7DISK : {disk_path} ===\n\n" + output_text += f"Modèle : {model}\n" + output_text += f"N° Série : {serial}\n" + output_text += f"Statut S.M.A.R.T: {status_icon}\n" + output_text += f"Température : {temp}°C\n\n" + output_text += "--- Détails Techniques Bruts ---\n" + + # Ajout des informations brutes formatées + if "nvme_smart_health_information_log" in data: + for key, val in data["nvme_smart_health_information_log"].items(): + output_text += f"{key}: {val}\n" + elif "ata_smart_attributes" in data: + output_text += "ID Attribut Valeur\n" + for attr in data["ata_smart_attributes"].get("table", []): + output_text += f"{attr['id']:<4} {attr['name']:<30} {attr['raw']['value']}\n" + + except json.JSONDecodeError: + output_text = f"⚠️ Résultat brut (Impossible de parser le JSON):\n\n{result.stdout}" + + except Exception as e: + output_text = f"❌ Erreur inattendue : {e}" + + # Mise à jour de l'interface graphique (doit être fait via mainloop, d'où self.after) + self.after(0, self.update_smart_ui, output_text) + + def update_smart_ui(self, text): + self.textbox_smart.configure(state="normal") + self.textbox_smart.insert("0.0", text) + self.textbox_smart.configure(state="disabled") + self.lbl_status.configure(text="Analyse terminée.") + self.btn_scan.configure(state="normal") + +if __name__ == "__main__": + app = SevenDiskApp() + app.mainloop() \ No newline at end of file