Files
7LnA_Antivirus_Linux_Free_C…/7lna.py
2026-03-05 10:18:26 +00:00

562 lines
30 KiB
Python

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