288 lines
8.9 KiB
Python
288 lines
8.9 KiB
Python
"""
|
|
Parser HTML vers Markdown pour la documentation d'aide.
|
|
"""
|
|
|
|
import re
|
|
from html.parser import HTMLParser
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
|
|
|
|
class HelpHtmlToMarkdownParser(HTMLParser):
|
|
"""Parser pour convertir le HTML de documentation en Markdown."""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.markdown_lines: List[str] = []
|
|
self.current_text = ""
|
|
self.in_code = False
|
|
self.in_pre = False
|
|
self.in_list = False
|
|
self.list_type = "ul"
|
|
self.list_level = 0
|
|
self.in_table = False
|
|
self.table_row: List[str] = []
|
|
self.in_th = False
|
|
self.ignore_content = False
|
|
self.tag_stack: List[str] = []
|
|
|
|
def handle_starttag(self, tag: str, attrs: List[Tuple[str, str]]):
|
|
self.tag_stack.append(tag)
|
|
attrs_dict = dict(attrs)
|
|
|
|
if tag in ["script", "style", "nav", "footer"]:
|
|
self.ignore_content = True
|
|
return
|
|
|
|
if tag == "h1":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n# ")
|
|
elif tag == "h2":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n## ")
|
|
elif tag == "h3":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n### ")
|
|
elif tag == "h4":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n#### ")
|
|
elif tag == "p":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "br":
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "strong" or tag == "b":
|
|
self.current_text += "**"
|
|
elif tag == "em" or tag == "i":
|
|
self.current_text += "*"
|
|
elif tag == "code":
|
|
if not self.in_pre:
|
|
self.current_text += "`"
|
|
self.in_code = True
|
|
elif tag == "pre":
|
|
self._flush_text()
|
|
self.in_pre = True
|
|
self.markdown_lines.append("\n```\n")
|
|
elif tag == "ul":
|
|
self._flush_text()
|
|
self.in_list = True
|
|
self.list_type = "ul"
|
|
self.list_level += 1
|
|
elif tag == "ol":
|
|
self._flush_text()
|
|
self.in_list = True
|
|
self.list_type = "ol"
|
|
self.list_level += 1
|
|
elif tag == "li":
|
|
self._flush_text()
|
|
indent = " " * (self.list_level - 1)
|
|
if self.list_type == "ul":
|
|
self.markdown_lines.append(f"\n{indent}- ")
|
|
else:
|
|
self.markdown_lines.append(f"\n{indent}1. ")
|
|
elif tag == "a":
|
|
href = attrs_dict.get("href", "")
|
|
self.current_text += "["
|
|
self._href_pending = href
|
|
elif tag == "table":
|
|
self._flush_text()
|
|
self.in_table = True
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "tr":
|
|
self.table_row = []
|
|
elif tag == "th":
|
|
self.in_th = True
|
|
elif tag == "td":
|
|
pass
|
|
elif tag == "hr":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n---\n")
|
|
elif tag == "blockquote":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n> ")
|
|
|
|
def handle_endtag(self, tag: str):
|
|
if self.tag_stack and self.tag_stack[-1] == tag:
|
|
self.tag_stack.pop()
|
|
|
|
if tag in ["script", "style", "nav", "footer"]:
|
|
self.ignore_content = False
|
|
return
|
|
|
|
if tag in ["h1", "h2", "h3", "h4"]:
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "p":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "strong" or tag == "b":
|
|
self.current_text += "**"
|
|
elif tag == "em" or tag == "i":
|
|
self.current_text += "*"
|
|
elif tag == "code":
|
|
if not self.in_pre:
|
|
self.current_text += "`"
|
|
self.in_code = False
|
|
elif tag == "pre":
|
|
self._flush_text()
|
|
self.in_pre = False
|
|
self.markdown_lines.append("```\n")
|
|
elif tag == "ul" or tag == "ol":
|
|
self._flush_text()
|
|
self.list_level -= 1
|
|
if self.list_level == 0:
|
|
self.in_list = False
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "li":
|
|
self._flush_text()
|
|
elif tag == "a":
|
|
href = getattr(self, "_href_pending", "")
|
|
self.current_text += f"]({href})"
|
|
self._href_pending = ""
|
|
elif tag == "tr":
|
|
if self.table_row:
|
|
self.markdown_lines.append("| " + " | ".join(self.table_row) + " |\n")
|
|
if self.in_th:
|
|
# Ajouter la ligne de séparation après les en-têtes
|
|
self.markdown_lines.append("|" + "|".join(["---"] * len(self.table_row)) + "|\n")
|
|
self.in_th = False
|
|
elif tag == "th" or tag == "td":
|
|
self._flush_text()
|
|
self.table_row.append(self.current_text.strip())
|
|
self.current_text = ""
|
|
elif tag == "table":
|
|
self.in_table = False
|
|
self.markdown_lines.append("\n")
|
|
elif tag == "blockquote":
|
|
self._flush_text()
|
|
self.markdown_lines.append("\n")
|
|
|
|
def handle_data(self, data: str):
|
|
if self.ignore_content:
|
|
return
|
|
|
|
if self.in_pre:
|
|
self.markdown_lines.append(data)
|
|
else:
|
|
# Normaliser les espaces
|
|
text = " ".join(data.split())
|
|
if text:
|
|
self.current_text += text
|
|
|
|
def _flush_text(self):
|
|
if self.current_text.strip():
|
|
self.markdown_lines.append(self.current_text.strip())
|
|
self.current_text = ""
|
|
|
|
def get_markdown(self) -> str:
|
|
self._flush_text()
|
|
content = "".join(self.markdown_lines)
|
|
# Nettoyer les lignes vides multiples
|
|
content = re.sub(r'\n{3,}', '\n\n', content)
|
|
return content.strip()
|
|
|
|
|
|
def build_help_markdown(html_path: Path = None, html_content: str = None) -> str:
|
|
"""Construit le contenu Markdown d'aide depuis un fichier HTML.
|
|
|
|
Args:
|
|
html_path: Chemin vers le fichier HTML source
|
|
html_content: Contenu HTML direct (prioritaire sur html_path)
|
|
|
|
Returns:
|
|
Contenu Markdown formaté
|
|
"""
|
|
if html_content:
|
|
content = html_content
|
|
elif html_path and html_path.exists():
|
|
content = html_path.read_text(encoding='utf-8')
|
|
else:
|
|
return _get_default_help_markdown()
|
|
|
|
# Extraire uniquement la section d'aide si présente
|
|
help_section_match = re.search(
|
|
r'<section[^>]*id=["\']help["\'][^>]*>(.*?)</section>',
|
|
content,
|
|
re.DOTALL | re.IGNORECASE
|
|
)
|
|
|
|
if help_section_match:
|
|
help_html = help_section_match.group(1)
|
|
else:
|
|
# Utiliser tout le body si pas de section help
|
|
body_match = re.search(r'<body[^>]*>(.*?)</body>', content, re.DOTALL | re.IGNORECASE)
|
|
help_html = body_match.group(1) if body_match else content
|
|
|
|
# Parser le HTML
|
|
parser = HelpHtmlToMarkdownParser()
|
|
parser.feed(help_html)
|
|
|
|
return parser.get_markdown()
|
|
|
|
|
|
def _get_default_help_markdown() -> str:
|
|
"""Retourne le contenu d'aide par défaut."""
|
|
return """# 📚 Documentation Homelab Automation
|
|
|
|
## 🎯 Introduction
|
|
|
|
Bienvenue dans le **Homelab Automation Dashboard**, une plateforme complète pour gérer votre infrastructure homelab avec Ansible.
|
|
|
|
## 🖥️ Dashboard
|
|
|
|
Le dashboard principal affiche:
|
|
- **Hôtes en ligne**: Nombre d'hôtes actuellement accessibles
|
|
- **Tâches**: Nombre total de tâches exécutées
|
|
- **Taux de succès**: Pourcentage de tâches réussies
|
|
- **Uptime**: Disponibilité globale du système
|
|
|
|
## 🖧 Gestion des Hôtes
|
|
|
|
### Ajouter un hôte
|
|
1. Cliquez sur "Nouvel hôte"
|
|
2. Renseignez le nom et l'adresse IP
|
|
3. Sélectionnez le groupe d'environnement
|
|
4. Choisissez les groupes de rôles
|
|
5. Cliquez sur "Ajouter"
|
|
|
|
### Bootstrap
|
|
Le bootstrap configure un hôte pour Ansible:
|
|
- Création de l'utilisateur d'automatisation
|
|
- Configuration des clés SSH
|
|
- Installation de sudo et Python3
|
|
|
|
## 📋 Playbooks
|
|
|
|
Les playbooks Ansible sont organisés par catégorie:
|
|
- **Général**: Playbooks de base
|
|
- **Maintenance**: Mises à jour, redémarrages
|
|
- **Sécurité**: Configurations de sécurité
|
|
- **Monitoring**: Surveillance et alertes
|
|
|
|
## ⏰ Schedules
|
|
|
|
Planifiez l'exécution automatique de playbooks:
|
|
- **Une fois**: Exécution unique à une date/heure
|
|
- **Quotidien**: Tous les jours à une heure fixe
|
|
- **Hebdomadaire**: Certains jours de la semaine
|
|
- **Mensuel**: Un jour spécifique du mois
|
|
- **Personnalisé**: Expression cron
|
|
|
|
## 🔔 Notifications
|
|
|
|
Les notifications sont envoyées via ntfy:
|
|
- Démarrage/arrêt de l'application
|
|
- Succès/échec des tâches
|
|
- Bootstrap réussi/échoué
|
|
|
|
## 🔑 API
|
|
|
|
L'API REST est accessible avec une clé API:
|
|
```
|
|
Header: X-API-Key: votre-cle-api
|
|
```
|
|
|
|
---
|
|
*Généré par Homelab Automation Dashboard*
|
|
"""
|