Files
7DIsk/7Disk.py
2026-04-21 14:48:19 +00:00

232 lines
11 KiB
Python

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()