Skip to content

Commit a92ce2c

Browse files
committed
refactor(core): Mise en place de l'architecture multi-moteur (Strategy Pattern)
1 parent 2e064cf commit a92ce2c

File tree

5 files changed

+198
-65
lines changed

5 files changed

+198
-65
lines changed
5.38 KB
Binary file not shown.
6.09 KB
Binary file not shown.

lsit.py

Lines changed: 8 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
#!/usr/bin/env python3
2-
import subprocess
32
import argparse
43
import json
54
import os
65
import threading
76
import logging
8-
from datetime import datetime
97
from flask import Flask, render_template, jsonify
8+
from moteur_base import choisir_moteur
109

1110
dossier_script = os.path.dirname(os.path.realpath(__file__))
1211
chemin_changelog = os.path.join(dossier_script, "CHANGELOG.md")
@@ -28,64 +27,8 @@
2827
parser.add_argument("--serve", action="store_true", help="Lance directement le tableau de bord web sur le port 8080")
2928
args = parser.parse_args()
3029

31-
32-
def collecter_donnees():
33-
with open("/etc/hostname") as f:
34-
hostname = f.read().strip()
35-
36-
ram_info = ""
37-
with open("/proc/meminfo") as f:
38-
for ligne in f:
39-
if "MemTotal" in ligne:
40-
ram_info = ligne.strip()
41-
42-
cpu_info = "Inconnu"
43-
with open("/proc/cpuinfo", "r") as f:
44-
for ligne in f:
45-
if "model name" in ligne:
46-
cpu_info = ligne.split(":")[1].strip()
47-
break
48-
49-
cmd_ps = subprocess.run(["ps", "aux"], capture_output=True, text=True)
50-
processus_actifs = cmd_ps.stdout
51-
52-
cmd_tree = subprocess.run(["tree", "-L", "2", "/home/vagrant"], capture_output=True, text=True)
53-
arborescence = cmd_tree.stdout
54-
55-
cmd_ports = subprocess.run(["ss", "-tuln"], capture_output=True, text=True)
56-
ports_ouverts = cmd_ports.stdout
57-
58-
cmd_disk = subprocess.run(["df", "-h"], capture_output=True, text=True)
59-
espace_disque = cmd_disk.stdout
60-
61-
cmd_uptime = subprocess.run(["uptime"], capture_output=True, text=True)
62-
uptime_raw = cmd_uptime.stdout.strip()
63-
parties = uptime_raw.split("load average:")
64-
uptime_info = parties[0].strip().rstrip(",") if len(parties) > 0 else "Inconnu"
65-
load_average = parties[1].strip() if len(parties) > 1 else "Inconnu"
66-
67-
utilisateurs_sudo = "Aucun"
68-
with open("/etc/group", "r") as f:
69-
for ligne in f:
70-
if ligne.startswith("sudo:"):
71-
utilisateurs_sudo = ligne.strip()
72-
break
73-
74-
date_audit = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
75-
76-
return {
77-
"date": date_audit,
78-
"machine": hostname,
79-
"ram": ram_info,
80-
"cpu": cpu_info,
81-
"processus": processus_actifs,
82-
"arborescence": arborescence,
83-
"securite_ports": ports_ouverts,
84-
"securite_sudoers": utilisateurs_sudo,
85-
"stockage": espace_disque,
86-
"uptime": uptime_info,
87-
"load_average": load_average
88-
}
30+
# Auto-détection de l'OS et initialisation du moteur approprié
31+
moteur_actif = choisir_moteur()
8932

9033

9134
def afficher_menu():
@@ -144,7 +87,7 @@ def mode_serve() -> None:
14487

14588
@app.route('/')
14689
def dashboard():
147-
donnees = collecter_donnees()
90+
donnees = moteur_actif.collecter_donnees()
14891
return render_template('dashboard.html',
14992
date_audit=donnees["date"],
15093
hostname=donnees["machine"],
@@ -161,7 +104,7 @@ def dashboard():
161104

162105
@app.route('/api/donnees')
163106
def api_donnees():
164-
donnees = collecter_donnees()
107+
donnees = moteur_actif.collecter_donnees()
165108
donnees["version_lsit"] = version_lsit
166109
return jsonify(donnees)
167110

@@ -189,11 +132,11 @@ def main():
189132
break
190133
elif choix == "1":
191134
print("\nCollecte des données en cours...")
192-
donnees = collecter_donnees()
135+
donnees = moteur_actif.collecter_donnees()
193136
mode_txt(donnees)
194137
elif choix == "2":
195138
print("\nCollecte des données en cours...")
196-
donnees = collecter_donnees()
139+
donnees = moteur_actif.collecter_donnees()
197140
mode_json(donnees)
198141
elif choix == "3":
199142
print("\nDémarrage du serveur web...")
@@ -206,7 +149,7 @@ def main():
206149
if args.serve:
207150
mode_serve()
208151
else:
209-
donnees = collecter_donnees()
152+
donnees = moteur_actif.collecter_donnees()
210153
if args.format == "json":
211154
mode_json(donnees)
212155
else:

moteur_base.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env python3
2+
3+
import platform
4+
from abc import ABC, abstractmethod
5+
from datetime import datetime
6+
from typing import Dict
7+
8+
9+
class MoteurBase(ABC):
10+
"""
11+
Classe abstraite définissant le contrat que tout moteur OS doit respecter.
12+
Impossible à instancier directement - les enfants (Linux, FreeBSD)
13+
DOIVENT implémenter toutes les méthodes @abstractmethod.
14+
"""
15+
16+
@abstractmethod
17+
def obtenir_hostname(self) -> str:
18+
"""Retourne le nom de la machine."""
19+
pass
20+
21+
@abstractmethod
22+
def obtenir_ram(self) -> str:
23+
"""Retourne les informations sur la mémoire totale."""
24+
pass
25+
26+
@abstractmethod
27+
def obtenir_cpu(self) -> str:
28+
"""Retourne le modèle du processeur."""
29+
pass
30+
31+
@abstractmethod
32+
def obtenir_processus(self) -> str:
33+
"""Retourne la liste des processus actifs."""
34+
pass
35+
36+
@abstractmethod
37+
def obtenir_arborescence(self, chemin: str, profondeur: int = 2) -> str:
38+
"""Retourne l'arborescence d'un dossier."""
39+
pass
40+
41+
@abstractmethod
42+
def obtenir_ports_ouverts(self) -> str:
43+
"""Retourne la liste des ports ouverts."""
44+
pass
45+
46+
@abstractmethod
47+
def obtenir_sudoers(self) -> str:
48+
"""Retourne les utilisateurs ayant des droits sudo/wheel."""
49+
pass
50+
51+
@abstractmethod
52+
def obtenir_stockage(self) -> str:
53+
"""Retourne l'espace disque."""
54+
pass
55+
56+
@abstractmethod
57+
def obtenir_uptime(self) -> str:
58+
"""Retourne le temps de fonctionnement."""
59+
pass
60+
61+
@abstractmethod
62+
def obtenir_load_average(self) -> str:
63+
"""Retourne la charge système."""
64+
pass
65+
66+
# Méthode NON abstraite (commune à tous les OS)
67+
def obtenir_date_audit(self) -> str:
68+
"""Retourne la date actuelle - identique sur tous les OS."""
69+
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
70+
71+
def collecter_donnees(self) -> Dict[str, str]:
72+
"""Collecte toutes les données et retourne un dictionnaire."""
73+
return {
74+
"date": self.obtenir_date_audit(),
75+
"machine": self.obtenir_hostname(),
76+
"ram": self.obtenir_ram(),
77+
"cpu": self.obtenir_cpu(),
78+
"processus": self.obtenir_processus(),
79+
"arborescence": self.obtenir_arborescence("/home/vagrant"),
80+
"securite_ports": self.obtenir_ports_ouverts(),
81+
"securite_sudoers": self.obtenir_sudoers(),
82+
"stockage": self.obtenir_stockage(),
83+
"uptime": self.obtenir_uptime(),
84+
"load_average": self.obtenir_load_average()
85+
}
86+
87+
88+
def choisir_moteur() -> MoteurBase:
89+
"""
90+
Routeur intelligent qui détecte l'OS et retourne le moteur approprié.
91+
Utilise platform.system() qui renvoie "Linux", "FreeBSD", "Windows", etc.
92+
"""
93+
systeme = platform.system()
94+
95+
if systeme == "Linux":
96+
from moteur_linux import MoteurLinux
97+
return MoteurLinux()
98+
99+
elif systeme == "FreeBSD":
100+
# TODO: À implémenter dans l'étape suivante
101+
# from moteur_freebsd import MoteurFreeBSD
102+
# return MoteurFreeBSD()
103+
raise NotImplementedError(
104+
f"Le moteur FreeBSD n'est pas encore implémenté. "
105+
f"OS détecté : {systeme}"
106+
)
107+
108+
else:
109+
raise NotImplementedError(
110+
f"Système d'exploitation non supporté : {systeme}. "
111+
f"Seuls Linux et FreeBSD sont pris en charge."
112+
)

moteur_linux.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
3+
import subprocess
4+
from moteur_base import MoteurBase
5+
6+
7+
class MoteurLinux(MoteurBase):
8+
"""
9+
Moteur de collecte de données pour les systèmes Linux (Debian, Ubuntu, etc.).
10+
Utilise /proc, /etc et les commandes GNU/Linux.
11+
"""
12+
13+
def obtenir_hostname(self) -> str:
14+
"""Lit le hostname depuis /etc/hostname (spécifique Linux)."""
15+
with open("/etc/hostname") as f:
16+
return f.read().strip()
17+
18+
def obtenir_ram(self) -> str:
19+
"""Lit la RAM depuis /proc/meminfo (spécifique Linux)."""
20+
with open("/proc/meminfo") as f:
21+
for ligne in f:
22+
if "MemTotal" in ligne:
23+
return ligne.strip()
24+
return "Inconnu"
25+
26+
def obtenir_cpu(self) -> str:
27+
"""Lit le modèle CPU depuis /proc/cpuinfo (spécifique Linux)."""
28+
with open("/proc/cpuinfo", "r") as f:
29+
for ligne in f:
30+
if "model name" in ligne:
31+
return ligne.split(":")[1].strip()
32+
return "Inconnu"
33+
34+
def obtenir_processus(self) -> str:
35+
"""Liste les processus avec ps aux (commande POSIX, fonctionne sur Linux)."""
36+
cmd = subprocess.run(["ps", "aux"], capture_output=True, text=True)
37+
return cmd.stdout
38+
39+
def obtenir_arborescence(self, chemin: str, profondeur: int = 2) -> str:
40+
"""Affiche l'arborescence avec tree (paquet à installer sur Linux)."""
41+
cmd = subprocess.run(
42+
["tree", "-L", str(profondeur), chemin],
43+
capture_output=True,
44+
text=True
45+
)
46+
return cmd.stdout
47+
48+
def obtenir_ports_ouverts(self) -> str:
49+
"""Liste les ports ouverts avec ss (remplaçant de netstat sur Linux)."""
50+
cmd = subprocess.run(["ss", "-tuln"], capture_output=True, text=True)
51+
return cmd.stdout
52+
53+
def obtenir_sudoers(self) -> str:
54+
"""Lit le groupe sudo depuis /etc/group (spécifique Linux)."""
55+
with open("/etc/group", "r") as f:
56+
for ligne in f:
57+
if ligne.startswith("sudo:"):
58+
return ligne.strip()
59+
return "Aucun"
60+
61+
def obtenir_stockage(self) -> str:
62+
"""Affiche l'espace disque avec df -h (commande POSIX)."""
63+
cmd = subprocess.run(["df", "-h"], capture_output=True, text=True)
64+
return cmd.stdout
65+
66+
def obtenir_uptime(self) -> str:
67+
"""Récupère l'uptime via la commande uptime."""
68+
cmd = subprocess.run(["uptime"], capture_output=True, text=True)
69+
uptime_raw = cmd.stdout.strip()
70+
parties = uptime_raw.split("load average:")
71+
return parties[0].strip().rstrip(",") if len(parties) > 0 else "Inconnu"
72+
73+
def obtenir_load_average(self) -> str:
74+
"""Récupère le load average via la commande uptime."""
75+
cmd = subprocess.run(["uptime"], capture_output=True, text=True)
76+
uptime_raw = cmd.stdout.strip()
77+
parties = uptime_raw.split("load average:")
78+
return parties[1].strip() if len(parties) > 1 else "Inconnu"

0 commit comments

Comments
 (0)