Ajouter 7Disk.py
This commit is contained in:
232
7Disk.py
Normal file
232
7Disk.py
Normal file
@@ -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()
|
||||
Reference in New Issue
Block a user