Files
Parseur_logs/src/scripts/mermaid.py
2025-07-23 09:39:15 +02:00

358 lines
13 KiB
Python

import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import List, Optional, Tuple
from dataclasses import dataclass
from scripts.format import FormatMain
@dataclass
class NetworkDevice:
"""Classe de base pour représenter un équipement réseau."""
name: str
macs: List[str]
interface: str
dest_mac: str
bridge: str
vitesse: str
nb_liens: str
def __post_init__(self):
"""Validation et nettoyage des données après initialisation."""
if isinstance(self.macs, str):
self.macs = [mac.strip() for mac in self.macs.split(',')]
self.bridge = self.bridge.strip()
self.vitesse = self.vitesse.strip()
self.nb_liens = self.nb_liens.strip()
@property
def macs_str(self) -> str:
"""Retourne les MACs sous forme de chaîne formatée."""
return ','.join(self.macs)
def to_formatted_string(self) -> str:
"""Retourne une représentation formatée de l'équipement."""
return f"{self.name} [{self.macs_str}] {self.interface} -> {self.dest_mac} [{self.bridge},{self.vitesse},{self.nb_liens}]"
@dataclass
class Coeur(NetworkDevice):
"""Classe représentant un équipement coeur."""
pass
@dataclass
class Switch(NetworkDevice):
"""Classe représentant un switch."""
pass
class NetworkFileParser:
"""Classe pour parser les fichiers de configuration réseau."""
ENCODINGS = ['utf-8', 'utf-16', 'utf-16-le', 'utf-16-be', 'latin-1', 'cp1252', 'iso-8859-1']
PATTERNS = [
r'(\S+)\s+\[([^\]]+)\]\s+(\S+)\s+->\s+([A-Fa-f0-9:-]+)\s*\[([^,]+),([^,]+),([^\]]+)\]',
r'(\S+)\s+\[([^\]]+)\]\s+(\S+)\s*->\s*([A-Fa-f0-9:-]+)\s*\[([^,]+),([^,]+),([^\]]+)\]',
r'(\w+)\s*\[([^\]]+)\]\s*(\w+)\s*->\s*([A-Fa-f0-9:-]+)\s*\[([^,]+),([^,]+),([^\]]+)\]'
]
def __init__(self, file_path: str):
self.file_path = Path(file_path)
def _read_file_with_encoding(self) -> Optional[str]:
"""Lit le fichier en essayant différents encodages."""
for encoding in self.ENCODINGS:
try:
return self.file_path.read_text(encoding=encoding)
except (UnicodeDecodeError, UnicodeError):
continue
except FileNotFoundError:
return None
return None
def _parse_line(self, line: str, device_class) -> Optional[NetworkDevice]:
"""Parse une ligne et retourne un objet NetworkDevice."""
line = line.strip()
if not line:
return None
for pattern in self.PATTERNS:
match = re.match(pattern, line)
if match:
return device_class(
name=match.group(1),
macs=match.group(2),
interface=match.group(3),
dest_mac=match.group(4),
bridge=match.group(5),
vitesse=match.group(6),
nb_liens=match.group(7)
)
print(f" -> Aucun pattern ne correspond à cette ligne: {line}")
return None
def parse(self) -> Tuple[List[Coeur], List[Switch]]:
"""Parse le fichier et retourne les coeurs et switches."""
content = self._read_file_with_encoding()
if content is None:
return [], []
sections = content.split('Switches:')
if len(sections) < 2:
return [], []
coeur_section = sections[0].replace('Coeur:', '').strip()
switches_section = sections[1].strip()
coeurs = []
for line in coeur_section.split('\n'):
coeur = self._parse_line(line, Coeur)
if coeur:
coeurs.append(coeur)
switches = []
for line in switches_section.split('\n'):
switch = self._parse_line(line, Switch)
if switch:
switches.append(switch)
return coeurs, switches
class NetworkDeviceGrouper:
"""Classe pour grouper les équipements réseau."""
@staticmethod
def group_coeurs_by_dest_mac(coeurs: List[Coeur]) -> List[Coeur]:
"""Groupe les coeurs par adresse MAC de destination."""
grouped = defaultdict(list)
for coeur in coeurs:
key = (coeur.dest_mac, coeur.name, coeur.macs_str,
coeur.bridge, coeur.vitesse, coeur.nb_liens)
grouped[key].append(coeur)
result = []
for key, group in grouped.items():
dest_mac, name, macs_str, bridge, vitesse, nb_liens = key
interfaces = [item.interface for item in group]
result.append(Coeur(
name=name,
macs=macs_str.split(','),
interface='-'.join(interfaces),
dest_mac=dest_mac,
bridge=bridge,
vitesse=vitesse,
nb_liens=nb_liens
))
return result
@staticmethod
def group_switches_by_local_mac(switches: List[Switch]) -> List[Switch]:
"""Groupe les switches par adresse MAC locale."""
grouped = defaultdict(list)
for switch in switches:
key = (switch.name, switch.macs_str, switch.dest_mac,
switch.bridge, switch.vitesse, switch.nb_liens)
grouped[key].append(switch)
result = []
for key, group in grouped.items():
name, macs_str, dest_mac, bridge, vitesse, nb_liens = key
interfaces = [item.interface for item in group]
result.append(Switch(
name=name,
macs=macs_str.split(','),
interface='-'.join(interfaces),
dest_mac=dest_mac,
bridge=bridge,
vitesse=vitesse,
nb_liens=nb_liens
))
return result
class MermaidDiagramGenerator:
"""Classe pour générer les diagrammes Mermaid."""
def __init__(self, coeurs: List[Coeur], switches: List[Switch]):
self.coeurs = coeurs
self.switches = switches
def _find_matching_switch(self, coeur: Coeur) -> Optional[Switch]:
"""Trouve le switch correspondant à un coeur."""
for switch in self.switches:
if coeur.dest_mac in switch.macs:
return switch
return None
def _format_bridge_label(self, bridge: str, vitesse: str) -> str:
"""Formate le label du bridge."""
bridge_formatted = bridge.replace('Bridge-Aggregation', 'BAGG')
return f"{bridge_formatted}<br/>{vitesse}"
def generate_links_diagram(self) -> str:
"""Génère un diagramme Mermaid des liaisons."""
if not self.coeurs or not self.switches:
return "Aucune donnée à afficher"
mermaid_lines = ["graph LR"]
total_devices = min(len(self.coeurs), len(self.switches))
for i, coeur in enumerate(self.coeurs):
target_switch = self._find_matching_switch(coeur)
label = self._format_bridge_label(coeur.bridge, coeur.vitesse)
if target_switch:
if i < total_devices // 2:
line = f' Coeur(("{coeur.name}")) <-->|{label}| {target_switch.name}'
else:
line = f' {target_switch.name} <-->|{label}| Coeur(("{coeur.name}"))'
mermaid_lines.append(line)
else:
line = f' Coeur(("{coeur.name}")) <-->|{label}| {coeur.dest_mac}["📄manquant {coeur.dest_mac}"]'
mermaid_lines.append(line)
mermaid_lines.append(f' class {coeur.dest_mac} error;')
return '\n'.join(mermaid_lines)
class FileWriter:
"""Classe pour l'écriture de fichiers."""
@staticmethod
def write_mermaid_diagram(mermaid_code: str, output_path: Path) -> None:
"""Sauvegarde le diagramme Mermaid."""
try:
output_path.parent.mkdir(parents=True, exist_ok=True)
content = [
"# Diagramme des liaisons Coeur-Switch\n",
"```mermaid\n",
"---\n",
"config:\n",
" theme: 'base'\n",
" themeVariables:\n",
" primaryColor: '#25bb75ff'\n",
" primaryTextColor: '#ffffff'\n",
" primaryBorderColor: '#000000ff'\n",
" lineColor: '#f82929ff'\n",
" secondaryColor: '#5e0c1aff'\n",
" tertiaryColor: '#ffffff'\n",
"---\n",
mermaid_code,
"\nclass Coeur coeur;\n",
"classDef coeur fill:#0590e6;\n",
"classDef error fill:#e64c05;\n",
"\n```\n"
]
output_path.write_text(''.join(content), encoding='utf-8')
print(f"✅ Diagramme Mermaid généré : {output_path}")
except Exception as e:
print(f"Erreur lors de la sauvegarde: {e}")
@staticmethod
def write_grouped_file(coeurs: List[Coeur], switches: List[Switch], output_path: Path) -> None:
"""Écrit les équipements groupés dans un fichier."""
try:
lines = ["Coeur:\n"]
lines.extend(f"{coeur.to_formatted_string()}\n" for coeur in coeurs)
lines.append("Switches:\n")
lines.extend(f"{switch.to_formatted_string()}\n" for switch in switches)
output_path.write_text(''.join(lines), encoding='utf-8')
except Exception as e:
print(f"Erreur lors de la sauvegarde: {e}")
@staticmethod
def write_links_file(coeurs: List[Coeur], switches: List[Switch], output_path: Path) -> None:
"""Écrit les liens entre coeurs et switches."""
try:
lines = []
for coeur in coeurs:
matching_switch = None
for switch in switches:
if coeur.dest_mac in switch.macs:
matching_switch = switch
break
if matching_switch:
coeur_info = f"{coeur.name} [{coeur.macs_str}] {coeur.interface} [{coeur.bridge},{coeur.vitesse},{coeur.nb_liens}]"
switch_info = f"{matching_switch.name} [{matching_switch.macs_str}] {matching_switch.interface} [{matching_switch.bridge},{matching_switch.vitesse},{matching_switch.nb_liens}]"
lines.append(f"{coeur_info} -> {switch_info}\n")
else:
lines.append(f"{coeur.name} [{coeur.macs_str}] {coeur.interface} -> Aucune correspondance de switch pour MAC {coeur.dest_mac}\n")
output_path.write_text(''.join(lines), encoding='utf-8')
except Exception as e:
print(f"Erreur lors de l'écriture des liens: {e}")
class NetworkAnalyzer:
"""Classe principale pour l'analyse réseau."""
def __init__(self, base_dir: Path = None):
if base_dir is None:
base_dir = Path(__file__).parent.parent
self.base_dir = Path(base_dir)
self.data_file = self.base_dir / 'data.txt'
self.output_dir = self.base_dir / 'output'
self.mermaid_file = self.output_dir / 'mermaid.md'
def analyze(self, filename: str) -> None:
"""Analyse complète du réseau."""
# Exécute l'analyse initiale
FormatMain().run_analysis(filename)
# Parse le fichier
parser = NetworkFileParser(self.data_file)
coeurs, switches = parser.parse()
if not coeurs and not switches:
print("Impossible de lire le fichier ou format incorrect")
return
# Groupe les équipements
grouper = NetworkDeviceGrouper()
grouped_coeurs = grouper.group_coeurs_by_dest_mac(coeurs)
grouped_switches = grouper.group_switches_by_local_mac(switches)
# Écrit les fichiers de sortie
writer = FileWriter()
writer.write_grouped_file(grouped_coeurs, grouped_switches, self.data_file)
writer.write_links_file(grouped_coeurs, grouped_switches, self.data_file)
# Génère le diagramme Mermaid
diagram_generator = MermaidDiagramGenerator(grouped_coeurs, grouped_switches)
mermaid_code = diagram_generator.generate_links_diagram()
writer.write_mermaid_diagram(mermaid_code, self.mermaid_file)
def main():
"""Fonction principale."""
if len(sys.argv) != 2:
print("Usage: python mermaid.py <filename>")
sys.exit(1)
analyzer = NetworkAnalyzer()
analyzer.analyze(filename=sys.argv[1])
if __name__ == "__main__":
main()