diff --git a/7lna.py b/7lna.py index 0fec627..1b4a2de 100644 --- a/7lna.py +++ b/7lna.py @@ -37,13 +37,9 @@ def send_desktop_notification(title, message, is_critical=False): 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) @@ -61,8 +57,7 @@ class Antivirus7LnA(ctk.CTk): os.makedirs(QUARANTINE_DIR, exist_ok=True) if not os.path.exists(HISTORY_FILE): - with open(HISTORY_FILE, "w") as f: - json.dump([], f) + with open(HISTORY_FILE, "w") as f: json.dump([], f) self.shield_observer = None self.shield_active = False @@ -70,14 +65,13 @@ class Antivirus7LnA(ctk.CTk): 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): + except: self.clamav_installed = False def setup_ui(self): @@ -97,14 +91,13 @@ class Antivirus7LnA(ctk.CTk): 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_schedule = self.create_nav_button("📅 Planification", 7, "schedule") + self.btn_history = self.create_nav_button("📜 Rapports", 8, "history") 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 = ctk.CTkLabel(self.sidebar, text="v8.1 - 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() @@ -112,8 +105,8 @@ class Antivirus7LnA(ctk.CTk): self.init_audit_view() self.init_tools_view() self.init_quarantine_view() - self.init_schedule_view() # NOUVEAU - self.init_history_view() # NOUVEAU + self.init_schedule_view() + self.init_history_view() self.init_update_view() self.select_view("dashboard") @@ -126,14 +119,12 @@ class Antivirus7LnA(ctk.CTk): return btn def select_view(self, view_name): - for view in self.views.values(): - view.grid_forget() + 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") @@ -146,19 +137,14 @@ class Antivirus7LnA(ctk.CTk): 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 - }) + 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) + except: pass - # --- NOUVEAU : AUTO-DETECT USB --- + # --- 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) @@ -166,14 +152,13 @@ class Antivirus7LnA(ctk.CTk): 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) + self.after(0, self.prompt_usb_scan, os.path.join(media_dir, mount), mount) known_mounts = current_mounts - except Exception: pass + except: 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 ?"): + if messagebox.askyesno("Protection USB", f"Nouveau périphérique USB détecté :\n{name}\n\nVoulez-vous l'analyser ?"): self.select_view("scanner") threading.Thread(target=self.run_clamav_scan, args=(path, True, self.scan_console), daemon=True).start() @@ -202,17 +187,15 @@ class Antivirus7LnA(ctk.CTk): 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 = ctk.CTkButton(frame, text="🔄 MaJ Signatures", 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") @@ -225,22 +208,57 @@ class Antivirus7LnA(ctk.CTk): 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") + # ----- CORRECTION DU ROOTKIT (Mot de passe intégré) ----- def run_rootkit_scan(self): - threading.Thread(target=self._exec_rootkit, daemon=True).start() + # Création d'une fenêtre pop-up pour demander le mot de passe sans dépendre du système + dialog = ctk.CTkToplevel(self) + dialog.title("Sécurité Administrateur") + dialog.geometry("400x220") + dialog.attributes("-topmost", True) + dialog.grab_set() # Empêche de cliquer ailleurs + + ctk.CTkLabel(dialog, text="⚠️ Privilèges Requis", font=ctk.CTkFont(size=20, weight="bold"), text_color="#F59E0B").pack(pady=(20, 5)) + ctk.CTkLabel(dialog, text="L'analyse anti-rootkit nécessite les droits sudo.\nEntrez votre mot de passe session :", justify="center").pack(pady=5) + + pwd_entry = ctk.CTkEntry(dialog, show="*", width=250) + pwd_entry.pack(pady=10) + pwd_entry.focus() + + def on_submit(event=None): + pwd = pwd_entry.get() + dialog.destroy() + if pwd: + threading.Thread(target=self._exec_rootkit, args=(pwd,), daemon=True).start() + + ctk.CTkButton(dialog, text="Lancer l'analyse", command=on_submit, fg_color="#DC2626", hover_color="#991B1B").pack(pady=10) + dialog.bind("", on_submit) - 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") + def _exec_rootkit(self, pwd): + self.scan_console.insert("end", f"\n{self.get_time_prefix()}[*] Lancement de rkhunter (cela peut prendre du temps)...\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) + # -S permet de passer le mot de passe via l'entrée standard + cmd = ['sudo', '-S', 'rkhunter', '-c', '--sk', '--report-warnings-only'] + process = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) + + # Envoi du mot de passe + process.stdin.write(pwd + '\n') + process.stdin.flush() + + for line in iter(process.stdout.readline, ''): + if "incorrect password" in line.lower() or "try again" in line.lower(): + self.scan_console.insert("end", f"{self.get_time_prefix()}[-] Mot de passe refusé par le système.\n", "danger") + process.terminate() + break + else: + 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") + if process.returncode in [0, 1]: + 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") + self.scan_console.insert("end", f"{self.get_time_prefix()}[-] rkhunter n'est pas installé. (Lancez le nouveau script install.sh)\n", "warning") except Exception as e: self.scan_console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: @@ -259,10 +277,8 @@ class Antivirus7LnA(ctk.CTk): 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() + except: pass + finally: self.scan_progress.stop() def start_manual_scan(self, is_dir): path = filedialog.askdirectory() if is_dir else filedialog.askopenfilename() @@ -274,14 +290,11 @@ class Antivirus7LnA(ctk.CTk): 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 = ctk.CTkButton(header, text="Démarrer la Surveillance", fg_color="#059669", hover_color="#047857", command=self.toggle_shield, height=45) 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) @@ -297,7 +310,7 @@ class Antivirus7LnA(ctk.CTk): 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.btn_toggle_shield.configure(text="Démarrer la Surveillance", fg_color="#059669") self.rt_console.insert("end", f"\n{self.get_time_prefix()}[-] Bouclier désactivé.\n", "warning") def trigger_realtime_scan(self, filepath): @@ -309,34 +322,25 @@ class Antivirus7LnA(ctk.CTk): 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" - + threat_name = clean_line.split(":")[-1].replace("FOUND", "").strip() 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") + self.log_threat_to_history(path, threat_name) + 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") + 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).", is_critical=True) + except Exception as e: console.insert("end", f"{self.get_time_prefix()}❌ Erreur : {e}\n", "danger") finally: if console == self.scan_console: self.scan_progress.stop() console.see("end") @@ -350,81 +354,61 @@ class Antivirus7LnA(ctk.CTk): 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") + ctk.CTkButton(btn_frame, text="Lancer l'Audit", 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 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") + self.audit_console.insert("end", "\n" + "\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") + if res == "active": self.audit_console.insert("end", f"{self.get_time_prefix()}✅ PARE-FEU UFW ACTIF.\n") + else: self.audit_console.insert("end", f"{self.get_time_prefix()}⚠️ PARE-FEU INACTIF.\n") + except: pass - # --- VUE : OUTILS AVANCÉS (Avec Nettoyeur) --- + # --- VUE : OUTILS AVANCÉS --- 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) + ctk.CTkButton(clean_card, text="Vider le Cache et Corbeille", fg_color="#059669", hover_color="#047857", command=self.run_cleaner).pack(anchor="w", padx=20, pady=10) - # 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) + ctk.CTkLabel(shred_card, text="🔥 Destructeur de Fichiers", font=ctk.CTkFont(size=18, weight="bold"), text_color="#EF4444").pack(anchor="w", padx=20, pady=(10,0)) + ctk.CTkButton(shred_card, text="Détruire un fichier", fg_color="#DC2626", hover_color="#991B1B", command=self.run_shredder).pack(anchor="w", padx=20, pady=10) 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") + messagebox.showinfo("Succès", "Nettoyage terminé.") def run_shredder(self): - filepath = filedialog.askopenfilename(title="SÉLECTIONNEZ LE FICHIER À DÉTRUIRE") + filepath = filedialog.askopenfilename() if not filepath: return if messagebox.askyesno("DANGER", f"Détruire DÉFINITIVEMENT ?\n\n{filepath}"): try: @@ -435,40 +419,43 @@ class Antivirus7LnA(ctk.CTk): 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)) + except: pass - 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) --- + # ----- CORRECTION PLANIFICATION (Sélecteurs d'Heure) ----- 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)) + ctk.CTkLabel(frame, text="Automatisez l'analyse via les tâches 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) + ctk.CTkLabel(card, text="Programmer un scan automatique (Dossier Téléchargements)", font=ctk.CTkFont(size=16, weight="bold")).pack(pady=(10, 5)) + + time_frame = ctk.CTkFrame(card, fg_color="transparent") + time_frame.pack(pady=(5, 15)) + + ctk.CTkLabel(time_frame, text="Heure d'exécution :").pack(side="left", padx=5) + self.cron_h_var = ctk.StringVar(value="12") + self.cron_m_var = ctk.StringVar(value="00") + + # Menus déroulants élégants pour le choix de l'heure + ctk.CTkComboBox(time_frame, values=[f"{i:02d}" for i in range(24)], variable=self.cron_h_var, width=70).pack(side="left", padx=5) + ctk.CTkLabel(time_frame, text="h").pack(side="left") + ctk.CTkComboBox(time_frame, values=["00", "15", "30", "45"], variable=self.cron_m_var, width=70).pack(side="left", padx=5) + + ctk.CTkButton(card, text="✅ Activer le Scan Quotidien", fg_color="#2563EB", hover_color="#1D4ED8", command=self.setup_cronjob).pack(pady=5) + ctk.CTkButton(card, text="❌ Désactiver la planification", fg_color="#4B5563", hover_color="#374151", command=self.remove_cronjob).pack(pady=5) def setup_cronjob(self): - cron_command = f"0 12 * * * /usr/bin/clamscan -r --move={QUARANTINE_DIR} {WATCH_FOLDER} > /dev/null 2>&1" + h = self.cron_h_var.get() + m = self.cron_m_var.get() + cron_command = f"{int(m)} {int(h)} * * * /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.") + messagebox.showinfo("Succès", f"Scan configuré tous les jours à {h}:{m}.") + except: messagebox.showerror("Erreur", "Impossible de configurer Cron.") def remove_cronjob(self): try: @@ -476,17 +463,13 @@ class Antivirus7LnA(ctk.CTk): messagebox.showinfo("Succès", "Planification désactivée.") except: pass - # --- NOUVEAU : VUE HISTORIQUE --- + # --- 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") - + ctk.CTkLabel(frame, text="📜 Registre des Menaces", font=ctk.CTkFont(size=34, weight="bold")).grid(row=0, column=0, sticky="w", pady=(0, 20)) 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") @@ -494,12 +477,11 @@ class Antivirus7LnA(ctk.CTk): 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") + if not data: self.history_console.insert("end", "\n ✅ Historique vierge.\n") else: - for entry in reversed(data): # Du plus récent au plus ancien + for entry in reversed(data): 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.") + except: pass # --- VUE : QUARANTAINE --- def init_quarantine_view(self): @@ -519,21 +501,17 @@ class Antivirus7LnA(ctk.CTk): 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") + for f in files: self.quarantine_listbox.insert("end", f" -> {os.path.basename(f)}\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 ?"): + if files and 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) --- + # --- VUE : MISE À JOUR --- def init_update_view(self): frame = ctk.CTkFrame(self, fg_color="transparent") self.views["update"] = frame @@ -542,19 +520,17 @@ class Antivirus7LnA(ctk.CTk): 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") + self.update_console.insert("end", "[*] Négociation avec serveur...\n") 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") + urllib.request.urlretrieve(UPDATE_URL, os.path.abspath(__file__)) + self.update_console.insert("end", "[+] Mise à jour réussie !\n") 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") + except: + self.update_console.insert("end", "[-] Échec.\n") self.btn_update_soft.configure(state="normal") if __name__ == "__main__":