import customtkinter as ctk from tkinter import filedialog, messagebox import os import threading import subprocess import urllib.request import time import datetime import glob import hashlib import string import secrets import json import platform import shutil from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler # ========================================== # CONFIGURATION GLOBALE # ========================================== UPDATE_URL = "https://git.7ka1.com/7ka1/7LnA_Antivirus_Linux_Free_ClamAV_Based/raw/branch/main/7lna.py" QUARANTINE_DIR = os.path.expanduser("~/.7lna_quarantine") WATCH_FOLDER = os.path.expanduser("~/Téléchargements") HISTORY_FILE = os.path.expanduser("~/.7lna_history.json") ctk.set_appearance_mode("dark") ctk.set_default_color_theme("blue") # ========================================== # UTILITAIRES SYSTÈME # ========================================== def send_desktop_notification(title, message, is_critical=False): try: urgency = 'critical' if is_critical else 'normal' subprocess.Popen(['notify-send', '-u', urgency, '-a', '7LnA Security', title, message]) except Exception: pass # ========================================== # GESTIONNAIRE DU BOUCLIER TEMPS RÉEL # ========================================== class RealTimeShieldHandler(FileSystemEventHandler): def __init__(self, app_instance): self.app = app_instance def on_created(self, event): if not event.is_directory: time.sleep(1.5) self.app.trigger_realtime_scan(event.src_path) # ========================================== # APPLICATION PRINCIPALE # ========================================== class Antivirus7LnA(ctk.CTk): def __init__(self): super().__init__() self.title("7LnA Security Suite - V8 Enterprise Edition") self.geometry("1250x850") self.minsize(1000, 700) os.makedirs(QUARANTINE_DIR, exist_ok=True) if not os.path.exists(HISTORY_FILE): with open(HISTORY_FILE, "w") as f: json.dump([], f) self.shield_observer = None self.shield_active = False self.check_dependencies() self.setup_ui() # Lancement de la surveillance USB en arrière-plan threading.Thread(target=self.monitor_usb_drives, daemon=True).start() def check_dependencies(self): try: subprocess.run(['clamscan', '--version'], capture_output=True, check=True) self.clamav_installed = True except (subprocess.CalledProcessError, FileNotFoundError): self.clamav_installed = False def setup_ui(self): self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) # --- BARRE LATÉRALE --- self.sidebar = ctk.CTkFrame(self, width=260, corner_radius=0, fg_color="#111827") self.sidebar.grid(row=0, column=0, sticky="nsew") self.sidebar.grid_rowconfigure(10, weight=1) ctk.CTkLabel(self.sidebar, text="🛡️ 7LnA Sec", font=ctk.CTkFont(size=30, weight="bold"), text_color="#3B82F6").grid(row=0, column=0, padx=20, pady=(30, 20)) self.btn_dash = self.create_nav_button("📊 Tableau de Bord", 1, "dashboard") self.btn_scan = self.create_nav_button("🔍 Scanner Manuel", 2, "scanner") self.btn_shield = self.create_nav_button("⚡ Bouclier Actif", 3, "shield") self.btn_audit = self.create_nav_button("⚙️ Audit Réseau", 4, "audit") self.btn_tools = self.create_nav_button("🧰 Outils Avancés", 5, "tools") self.btn_quarantine = self.create_nav_button("📦 Quarantaine", 6, "quarantine") self.btn_schedule = self.create_nav_button("📅 Planification", 7, "schedule") # NOUVEAU self.btn_history = self.create_nav_button("📜 Rapports", 8, "history") # NOUVEAU self.btn_update = self.create_nav_button("🔄 Mise à jour", 9, "update") self.version_label = ctk.CTkLabel(self.sidebar, text="v8.0 - Enterprise", text_color="#6B7280", font=ctk.CTkFont(weight="bold")) self.version_label.grid(row=10, column=0, pady=20, sticky="s") # --- CONTENEUR DES VUES --- self.views = {} self.init_dashboard_view() self.init_scanner_view() self.init_realtime_view() self.init_audit_view() self.init_tools_view() self.init_quarantine_view() self.init_schedule_view() # NOUVEAU self.init_history_view() # NOUVEAU self.init_update_view() self.select_view("dashboard") def create_nav_button(self, text, row, view_name): btn = ctk.CTkButton(self.sidebar, text=text, anchor="w", fg_color="transparent", text_color="#D1D5DB", hover_color="#1F2937", font=ctk.CTkFont(size=15), height=40, command=lambda: self.select_view(view_name)) btn.grid(row=row, column=0, padx=20, pady=5, sticky="ew") return btn def select_view(self, view_name): for view in self.views.values(): view.grid_forget() if view_name in self.views: self.views[view_name].grid(row=0, column=1, sticky="nsew", padx=30, pady=30) if view_name == "quarantine": self.refresh_quarantine_list() if view_name == "history": self.refresh_history_list() # --- UTILITAIRES DE CONSOLE --- def setup_console_tags(self, console): console.tag_config("danger", foreground="#EF4444") console.tag_config("success", foreground="#10B981") console.tag_config("warning", foreground="#F59E0B") console.tag_config("info", foreground="#3B82F6") def get_time_prefix(self): return datetime.datetime.now().strftime("[%H:%M:%S] ") def log_threat_to_history(self, filepath, threat_name): try: with open(HISTORY_FILE, "r") as f: data = json.load(f) data.append({ "date": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "file": filepath, "threat": threat_name }) with open(HISTORY_FILE, "w") as f: json.dump(data, f, indent=4) except Exception as e: print("Erreur d'historique:", e) # --- NOUVEAU : AUTO-DETECT USB --- def monitor_usb_drives(self): media_dir = f"/media/{os.getlogin()}" if not os.path.exists(media_dir): return known_mounts = set(os.listdir(media_dir)) while True: time.sleep(3) try: current_mounts = set(os.listdir(media_dir)) new_mounts = current_mounts - known_mounts for mount in new_mounts: full_path = os.path.join(media_dir, mount) self.after(0, self.prompt_usb_scan, full_path, mount) known_mounts = current_mounts except Exception: pass def prompt_usb_scan(self, path, name): send_desktop_notification("USB Détectée", f"Disque {name} branché.") if messagebox.askyesno("Protection USB", f"Nouveau périphérique USB détecté :\n{name}\n\nVoulez-vous lancer une analyse antivirus dessus ?"): self.select_view("scanner") threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start() # --- VUE : TABLEAU DE BORD --- def init_dashboard_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["dashboard"] = frame ctk.CTkLabel(frame, text="État du Système", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) status_card = ctk.CTkFrame(frame, fg_color="#064E3B" if self.clamav_installed else "#7F1D1D", corner_radius=15) status_card.pack(fill="x", pady=10, ipady=20) status_text = "Moteur ClamAV Opérationnel" if self.clamav_installed else "Moteur ClamAV Introuvable" ctk.CTkLabel(status_card, text=f"{'✅' if self.clamav_installed else '❌'} {status_text}", font=ctk.CTkFont(size=24, weight="bold"), text_color="white").pack(expand=True) sys_frame = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) sys_frame.pack(fill="x", pady=10, ipady=10) sys_info = f"🖥️ OS : {platform.system()} {platform.release()} | 👤 Compte : {os.getlogin()}" ctk.CTkLabel(sys_frame, text=sys_info, font=ctk.CTkFont(size=16, weight="bold")).pack(padx=20, pady=10, anchor="w") # --- VUE : SCANNER MANUEL --- def init_scanner_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(4, weight=1) frame.grid_columnconfigure((0, 1, 2), weight=1) self.views["scanner"] = frame ctk.CTkLabel(frame, text="Analyse Profonde", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, columnspan=3, sticky="w", pady=(0, 20)) # Ligne 1 des boutons self.btn_scan_f = ctk.CTkButton(frame, text="📄 Analyser Fichier", command=lambda: self.start_manual_scan(is_dir=False), height=45, fg_color="#2563EB", hover_color="#1D4ED8") self.btn_scan_f.grid(row=1, column=0, padx=(0, 5), pady=5, sticky="ew") self.btn_scan_d = ctk.CTkButton(frame, text="📁 Analyser Dossier", command=lambda: self.start_manual_scan(is_dir=True), height=45, fg_color="#4F46E5", hover_color="#4338CA") self.btn_scan_d.grid(row=1, column=1, padx=(5, 5), pady=5, sticky="ew") self.btn_db_update = ctk.CTkButton(frame, text="🔄 MaJ Signatures (freshclam)", command=self.update_virus_db, height=45, fg_color="#059669", hover_color="#047857") self.btn_db_update.grid(row=1, column=2, padx=(5, 0), pady=5, sticky="ew") # NOUVEAU : Scanner Rootkit self.btn_rootkit = ctk.CTkButton(frame, text="🕵️ Chasse aux Rootkits (rkhunter)", command=self.run_rootkit_scan, height=45, fg_color="#7C3AED", hover_color="#6D28D9") self.btn_rootkit.grid(row=2, column=0, columnspan=3, pady=(5, 10), sticky="ew") self.scan_progress = ctk.CTkProgressBar(frame, mode="indeterminate", height=10) self.scan_progress.grid(row=3, column=0, columnspan=3, pady=(10, 0), sticky="ew") self.scan_progress.set(0) self.scan_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.scan_console.grid(row=4, column=0, columnspan=3, pady=20, sticky="nsew") self.setup_console_tags(self.scan_console) self.scan_console.insert("end", f"{self.get_time_prefix()}[*] Moteur de détection V8 prêt...\n", "info") def run_rootkit_scan(self): threading.Thread(target=self._exec_rootkit, daemon=True).start() def _exec_rootkit(self): self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de l'analyse Rootkit (Requiert l'authentification)...\n", "info") self.scan_progress.start() try: # pkexec permet de demander le mot de passe proprement en interface graphique Linux process = subprocess.Popen(['pkexec', 'rkhunter', '-c', '--sk', '--report-warnings-only'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) for line in process.stdout: self.scan_console.insert("end", line) self.scan_console.see("end") process.wait() self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Analyse Rootkit terminée.\n", "success") except FileNotFoundError: self.scan_console.insert("end", f"{self.get_time_prefix()}[-] rkhunter n'est pas installé. (sudo apt install rkhunter)\n", "warning") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: self.scan_progress.stop() self.scan_console.see("end") def update_virus_db(self): threading.Thread(target=self._run_freshclam, daemon=True).start() def _run_freshclam(self): self.scan_progress.start() try: process = subprocess.Popen(['freshclam'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) for line in process.stdout: self.scan_console.insert("end", line) self.scan_console.see("end") process.wait() self.scan_console.insert("end", f"{self.get_time_prefix()}[+] Signatures à jour.\n", "success") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: self.scan_progress.stop() def start_manual_scan(self, is_dir): path = filedialog.askdirectory() if is_dir else filedialog.askopenfilename() if path: threading.Thread(target=self.run_clamav_scan, args=(path, is_dir, self.scan_console), daemon=True).start() # --- VUE : BOUCLIER TEMPS RÉEL --- def init_realtime_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["shield"] = frame header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) ctk.CTkLabel(header, text="⚡ Bouclier Actif", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") self.btn_toggle_shield = ctk.CTkButton(header, text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857", command=self.toggle_shield, height=45, font=ctk.CTkFont(weight="bold")) self.btn_toggle_shield.pack(side="right") self.rt_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.rt_console.grid(row=2, column=0, sticky="nsew") self.setup_console_tags(self.rt_console) def toggle_shield(self): if not self.shield_active: self.shield_observer = Observer() self.shield_observer.schedule(RealTimeShieldHandler(self), WATCH_FOLDER, recursive=False) self.shield_observer.start() self.shield_active = True self.btn_toggle_shield.configure(text="Arrêter le Bouclier", fg_color="#DC2626", hover_color="#B91C1C") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[+] BOUCLIER ARMÉ : {WATCH_FOLDER}\n", "success") else: self.shield_observer.stop() self.shield_active = False self.btn_toggle_shield.configure(text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[-] Bouclier désactivé.\n", "warning") def trigger_realtime_scan(self, filepath): self.rt_console.insert("end", f"\n{self.get_time_prefix()}⚡ Nouveau fichier : {os.path.basename(filepath)}\n", "info") threading.Thread(target=self.run_clamav_scan, args=(filepath, False, self.rt_console), daemon=True).start() # --- MOTEUR CLAMAV --- def run_clamav_scan(self, path, is_dir, console): if not self.clamav_installed: return console.insert("end", f"{self.get_time_prefix()}[*] Analyse : {path}\n") if console == self.scan_console: self.scan_progress.start() try: cmd = ['clamscan', '-i', '--no-summary', f'--move={QUARANTINE_DIR}'] if is_dir: cmd.append('-r') cmd.append(path) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) infected = 0 for line in process.stdout: clean_line = line.strip() if "FOUND" in clean_line: threat_details = clean_line.split(":") threat_name = threat_details[-1].replace("FOUND", "").strip() if len(threat_details) > 1 else "Malware Inconnu" console.insert("end", f"{self.get_time_prefix()}☠️ MENACE DÉTECTÉE : {clean_line}\n", "danger") infected += 1 self.log_threat_to_history(path, threat_name) # Enregistrement dans l'historique elif clean_line: console.insert("end", f" -> {clean_line}\n") console.see("end") process.wait() if infected == 0: console.insert("end", f"{self.get_time_prefix()}[+] Fichier propre.\n", "success") else: send_desktop_notification("🚨 VIRUS NEUTRALISÉ", f"{infected} menace(s) trouvée(s) et isolée(s).", is_critical=True) except Exception as e: console.insert("end", f"{self.get_time_prefix()}❌ Erreur Moteur : {e}\n", "danger") finally: if console == self.scan_console: self.scan_progress.stop() console.see("end") # --- VUE : AUDIT & PARE-FEU --- def init_audit_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(2, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["audit"] = frame ctk.CTkLabel(frame, text="Audit Réseau & Pare-feu", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) btn_frame = ctk.CTkFrame(frame, fg_color="transparent") btn_frame.grid(row=1, column=0, sticky="ew", pady=(0, 10)) ctk.CTkButton(btn_frame, text="Lancer l'Audit Processus", command=self.run_audit_thread, fg_color="#D97706", hover_color="#B45309", height=40).pack(side="left", padx=(0, 10)) ctk.CTkButton(btn_frame, text="Vérifier Statut UFW", command=self.check_firewall, fg_color="#4B5563", hover_color="#374151", height=40).pack(side="left") self.audit_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.audit_console.grid(row=2, column=0, sticky="nsew") self.setup_console_tags(self.audit_console) def run_audit_thread(self): self.audit_console.delete("0.0", "end") threading.Thread(target=self.perform_audit, daemon=True).start() def perform_audit(self): self.audit_console.insert("end", f"{self.get_time_prefix()}[*] Recherche de connexions fantômes (Ports ouverts)...\n", "info") try: self.audit_console.insert("end", subprocess.check_output(['ss', '-tuln'], text=True)) except: pass self.audit_console.insert("end", f"\n{self.get_time_prefix()}[*] Top 10 Processus (Charge CPU)...\n", "info") try: res = subprocess.check_output(['ps', '-eo', 'pid,user,%cpu,%mem,cmd', '--sort=-%cpu'], text=True) self.audit_console.insert("end", "\n".join(res.split('\n')[:11]) + "\n") except: pass def check_firewall(self): self.audit_console.delete("0.0", "end") try: res = subprocess.check_output(['systemctl', 'is-active', 'ufw'], text=True).strip() if res == "active": self.audit_console.insert("end", f"{self.get_time_prefix()}✅ PARE-FEU UFW ACTIF.\n", "success") else: self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\n", "danger") except: self.audit_console.insert("end", f"{self.get_time_prefix()}[-] Impossible de déterminer l'état de UFW.\n", "warning") # --- VUE : OUTILS AVANCÉS (Avec Nettoyeur) --- def init_tools_view(self): frame = ctk.CTkScrollableFrame(self, fg_color="transparent") self.views["tools"] = frame ctk.CTkLabel(frame, text="Boîte à Outils", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) # NOUVEAU : Nettoyeur Système clean_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) clean_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(clean_card, text="🧹 Nettoyeur Système", font=ctk.CTkFont(size=18, weight="bold"), text_color="#10B981").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkLabel(clean_card, text="Vide la corbeille, les fichiers temporaires et le cache de l'utilisateur pour libérer de l'espace.", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) ctk.CTkButton(clean_card, text="Nettoyer l'ordinateur", fg_color="#059669", hover_color="#047857", command=self.run_cleaner).pack(anchor="w", padx=20) # Destructeur de Fichiers shred_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) shred_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(shred_card, text="🔥 Destructeur de Fichiers (File Shredder)", font=ctk.CTkFont(size=18, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20, pady=(10,0)) ctk.CTkLabel(shred_card, text="Écrase le fichier avec des données aléatoires (3 passes) pour empêcher sa récupération.", text_color="#9CA3AF").pack(anchor="w", padx=20, pady=(5, 10)) ctk.CTkButton(shred_card, text="Détruire un fichier à jamais", fg_color="#DC2626", hover_color="#991B1B", command=self.run_shredder).pack(anchor="w", padx=20) # Calculateur de Hash hash_card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) hash_card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(hash_card, text="🧬 Extracteur d'Empreintes (Hash)", font=ctk.CTkFont(size=18, weight="bold"), text_color="#3B82F6").pack(anchor="w", padx=20, pady=(10,0)) self.hash_result_var = ctk.StringVar(value="Aucun fichier sélectionné.") ctk.CTkButton(hash_card, text="Sélectionner un fichier", fg_color="#2563EB", hover_color="#1D4ED8", command=self.calculate_hash).pack(anchor="w", padx=20, pady=(0, 10)) ctk.CTkEntry(hash_card, textvariable=self.hash_result_var, state="readonly", width=500, fg_color="#111827").pack(anchor="w", padx=20) def run_cleaner(self): freed_space = 0 paths_to_clean = [os.path.expanduser("~/.local/share/Trash/files"), os.path.expanduser("~/.cache/thumbnails")] for path in paths_to_clean: if os.path.exists(path): for item in os.listdir(path): item_path = os.path.join(path, item) try: freed_space += os.path.getsize(item_path) if os.path.isfile(item_path): os.remove(item_path) elif os.path.isdir(item_path): shutil.rmtree(item_path) except: pass freed_mb = freed_space / (1024 * 1024) messagebox.showinfo("Nettoyage Terminé", f"Le nettoyage est terminé.\nEspace libéré : {freed_mb:.2f} MB") def run_shredder(self): filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ LE FICHIER À DÉTRUIRE") if not filepath: return if messagebox.askyesno("DANGER", f"Détruire DÉFINITIVEMENT ?\n\n{filepath}"): try: file_size = os.path.getsize(filepath) for _ in range(3): with open(filepath, "ba+", buffering=0) as f: f.seek(0) f.write(os.urandom(file_size)) os.remove(filepath) messagebox.showinfo("Succès", "Fichier détruit.") except Exception as e: messagebox.showerror("Erreur", str(e)) def calculate_hash(self): filepath = filedialog.askopenfilename() if not filepath: return try: sha256_hash = hashlib.sha256() with open(filepath, "rb") as f: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) self.hash_result_var.set(f"SHA-256: {sha256_hash.hexdigest()}") except: self.hash_result_var.set("Erreur de lecture.") # --- NOUVEAU : VUE PLANIFICATION (CRON) --- def init_schedule_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["schedule"] = frame ctk.CTkLabel(frame, text="Planification de Scans", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) ctk.CTkLabel(frame, text="Automatisez l'analyse de votre système via les tâches planifiées Linux (Cron).", text_color="#9CA3AF").pack(anchor="w", pady=(0, 20)) card = ctk.CTkFrame(frame, fg_color="#1F2937", corner_radius=10) card.pack(fill="x", pady=10, ipady=15) ctk.CTkLabel(card, text="Programmer un scan automatique de ~/Téléchargements", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=10) ctk.CTkButton(card, text="Activer le Scan Quotidien (12h00)", fg_color="#2563EB", hover_color="#1D4ED8", command=self.setup_cronjob).pack(pady=10) ctk.CTkButton(card, text="Désactiver les scans programmés", fg_color="#4B5563", hover_color="#374151", command=self.remove_cronjob).pack(pady=10) def setup_cronjob(self): cron_command = f"0 12 * * * /usr/bin/clamscan -r --move={QUARANTINE_DIR} {WATCH_FOLDER} > /dev/null 2>&1" try: # Récupère l'ancien crontab, enlève nos anciennes tâches, ajoute la nouvelle os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}"; echo "{cron_command}") | crontab -') messagebox.showinfo("Succès", "Scan automatique configuré tous les jours à 12h00.") except Exception as e: messagebox.showerror("Erreur", "Impossible de configurer Cron.") def remove_cronjob(self): try: os.system(f'(crontab -l 2>/dev/null | grep -v "clamscan -r --move={QUARANTINE_DIR}") | crontab -') messagebox.showinfo("Succès", "Planification désactivée.") except: pass # --- NOUVEAU : VUE HISTORIQUE --- def init_history_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(1, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["history"] = frame header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) ctk.CTkLabel(header, text="📜 Registre des Menaces", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") self.history_console = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.history_console.grid(row=1, column=0, sticky="nsew") def refresh_history_list(self): self.history_console.delete("0.0", "end") try: with open(HISTORY_FILE, "r") as f: data = json.load(f) if not data: self.history_console.insert("end", "\n ✅ Historique vierge. Aucun virus n'a été détecté sur cette machine.\n") else: for entry in reversed(data): # Du plus récent au plus ancien self.history_console.insert("end", f"[{entry['date']}] ☠️ {entry['threat']}\n -> Fichier : {entry['file']}\n\n") except: self.history_console.insert("end", "Impossible de lire l'historique.") # --- VUE : QUARANTAINE --- def init_quarantine_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") frame.grid_rowconfigure(1, weight=1) frame.grid_columnconfigure(0, weight=1) self.views["quarantine"] = frame header = ctk.CTkFrame(frame, fg_color="transparent") header.grid(row=0, column=0, sticky="ew", pady=(0, 20)) ctk.CTkLabel(header, text="📦 Quarantaine", font=ctk.CTkFont(size=34, weight="bold")).pack(side="left") ctk.CTkButton(header, text="🗑️ Vider", fg_color="#DC2626", hover_color="#991B1B", command=self.empty_quarantine, height=40).pack(side="right") self.quarantine_listbox = ctk.CTkTextbox(frame, font=ctk.CTkFont(family="Consolas", size=14), fg_color="#111827", corner_radius=10) self.quarantine_listbox.grid(row=1, column=0, sticky="nsew") def refresh_quarantine_list(self): self.quarantine_listbox.delete("0.0", "end") files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) if not files: self.quarantine_listbox.insert("end", "\n ✅ Aucun fichier malveillant isolé.\n") else: self.quarantine_listbox.insert("end", f" ⚠️ {len(files)} menace(s) :\n\n") for f in files: size_kb = os.path.getsize(f) / 1024 self.quarantine_listbox.insert("end", f" -> {os.path.basename(f)} ({size_kb:.1f} KB)\n") def empty_quarantine(self): files = glob.glob(os.path.join(QUARANTINE_DIR, "*")) if not files: return if messagebox.askyesno("Purger", "Supprimer définitivement les virus isolés ?"): for f in files: try: os.remove(f) except: pass self.refresh_quarantine_list() # --- VUE : MISE À JOUR (OTA) --- def init_update_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["update"] = frame ctk.CTkLabel(frame, text="Mise à jour Cloud", font=ctk.CTkFont(size=34, weight="bold")).pack(anchor="w", pady=(0, 20)) self.btn_update_soft = ctk.CTkButton(frame, text="⬇️ Mettre à jour", command=self.run_software_update, fg_color="#8B5CF6", hover_color="#7C3AED", height=45) self.btn_update_soft.pack(anchor="w") self.update_console = ctk.CTkTextbox(frame, height=200, font=ctk.CTkFont(family="Consolas", size=13), fg_color="#111827", corner_radius=10) self.update_console.pack(fill="x", pady=20) self.setup_console_tags(self.update_console) def run_software_update(self): self.btn_update_soft.configure(state="disabled") self.update_console.insert("end", f"{self.get_time_prefix()}[*] Négociation avec serveur...\n", "info") try: current_file_path = os.path.abspath(__file__) urllib.request.urlretrieve(UPDATE_URL, current_file_path) self.update_console.insert("end", f"{self.get_time_prefix()}[+] Code source mis à jour !\n", "success") messagebox.showinfo("Succès", "Redémarrage requis.") self.destroy() except Exception as e: self.update_console.insert("end", f"{self.get_time_prefix()}[-] Échec : {e}\n", "danger") self.btn_update_soft.configure(state="normal") if __name__ == "__main__": app = Antivirus7LnA() app.mainloop()