Remove HTML-to-Markdown parser and replace with comprehensive professional documentation generator featuring detailed guides for health indicators, architecture, features, notifications, playbooks, API reference, troubleshooting, and best practices
This commit is contained in:
@@ -4,13 +4,12 @@ Utilitaires pour l'API Homelab Automation.
|
||||
|
||||
from app.utils.ssh_utils import find_ssh_private_key, run_ssh_command, bootstrap_host
|
||||
from app.utils.pdf_generator import markdown_to_pdf_bytes
|
||||
from app.utils.markdown_parser import HelpHtmlToMarkdownParser, build_help_markdown
|
||||
from app.utils.markdown_parser import build_help_markdown
|
||||
|
||||
__all__ = [
|
||||
"find_ssh_private_key",
|
||||
"run_ssh_command",
|
||||
"bootstrap_host",
|
||||
"markdown_to_pdf_bytes",
|
||||
"HelpHtmlToMarkdownParser",
|
||||
"build_help_markdown",
|
||||
]
|
||||
|
||||
+298
-253
@@ -1,287 +1,332 @@
|
||||
"""
|
||||
Parser HTML vers Markdown pour la documentation d'aide.
|
||||
Générateur de documentation Markdown pour l'aide Homelab Automation.
|
||||
"""
|
||||
|
||||
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.
|
||||
"""Génère le contenu Markdown d'aide professionnel.
|
||||
|
||||
Args:
|
||||
html_path: Chemin vers le fichier HTML source
|
||||
html_content: Contenu HTML direct (prioritaire sur html_path)
|
||||
html_path: Non utilisé (conservé pour compatibilité)
|
||||
html_content: Non utilisé (conservé pour compatibilité)
|
||||
|
||||
Returns:
|
||||
Contenu Markdown formaté
|
||||
Contenu Markdown formaté professionnellement
|
||||
"""
|
||||
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()
|
||||
return _get_professional_help_markdown()
|
||||
|
||||
|
||||
def _get_default_help_markdown() -> str:
|
||||
"""Retourne le contenu d'aide par défaut."""
|
||||
return """# 📚 Documentation Homelab Automation
|
||||
def _get_professional_help_markdown() -> str:
|
||||
"""Retourne la documentation d'aide complète et professionnelle."""
|
||||
return """# Homelab Automation Dashboard
|
||||
|
||||
## 🎯 Introduction
|
||||
## Guide d'Utilisation
|
||||
|
||||
Bienvenue dans le **Homelab Automation Dashboard**, une plateforme complète pour gérer votre infrastructure homelab avec Ansible.
|
||||
Bienvenue dans le guide officiel de votre **Homelab Automation Dashboard** !
|
||||
Découvrez comment gérer et automatiser efficacement votre infrastructure grâce à cette solution puissante et centralisée.
|
||||
|
||||
## 🖥️ 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
|
||||
## Table des Matières
|
||||
|
||||
## 🖧 Gestion des Hôtes
|
||||
1. [Démarrage Rapide](#démarrage-rapide)
|
||||
2. [Indicateurs de Santé des Hosts](#indicateurs-de-santé-des-hosts)
|
||||
3. [Architecture de la Solution](#architecture-de-la-solution)
|
||||
4. [Fonctionnalités par Section](#fonctionnalités-par-section)
|
||||
5. [Système de Notifications](#système-de-notifications-ntfy)
|
||||
6. [Playbooks Ansible Disponibles](#playbooks-ansible-disponibles)
|
||||
7. [Référence API](#référence-api)
|
||||
8. [Dépannage](#dépannage)
|
||||
9. [Raccourcis et Astuces](#raccourcis-et-astuces)
|
||||
|
||||
### 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
|
||||
## Démarrage Rapide
|
||||
|
||||
## 📋 Playbooks
|
||||
### Étape 1 : Ajouter vos Hosts
|
||||
|
||||
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
|
||||
Commencez par ajouter vos serveurs dans la section **Hosts**. Chaque host nécessite :
|
||||
- Un nom unique
|
||||
- Une adresse IP
|
||||
- Un système d'exploitation
|
||||
|
||||
## ⏰ Schedules
|
||||
### Étape 2 : Bootstrap Ansible
|
||||
|
||||
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
|
||||
Exécutez le **Bootstrap** sur chaque host pour configurer :
|
||||
- L'accès SSH avec clé publique
|
||||
- L'utilisateur d'automatisation
|
||||
- Les prérequis Ansible (Python, sudo)
|
||||
|
||||
## 🔔 Notifications
|
||||
### Étape 3 : Automatiser
|
||||
|
||||
Les notifications sont envoyées via ntfy:
|
||||
- Démarrage/arrêt de l'application
|
||||
- Succès/échec des tâches
|
||||
- Bootstrap réussi/échoué
|
||||
Utilisez les **Actions Rapides** ou exécutez des playbooks personnalisés pour automatiser vos tâches récurrentes.
|
||||
|
||||
## 🔑 API
|
||||
---
|
||||
|
||||
## Indicateurs de Santé des Hosts
|
||||
|
||||
Chaque host affiche un indicateur visuel de santé représenté par des barres colorées. Cet indicateur combine plusieurs facteurs pour évaluer l'état global de votre serveur.
|
||||
|
||||
### Comprendre l'Indicateur
|
||||
|
||||
| Niveau | Barres | Description |
|
||||
|--------|--------|-------------|
|
||||
| **Excellent** | 5 barres vertes | Host en ligne, bootstrap OK, vérifié récemment |
|
||||
| **Bon** | 3-4 barres jaunes | Host fonctionnel mais certains aspects à améliorer |
|
||||
| **Moyen** | 2 barres oranges | Attention requise - vérification recommandée |
|
||||
| **Faible** | 1 barre rouge | Host hors ligne ou non configuré |
|
||||
|
||||
### Facteurs de Calcul du Score
|
||||
|
||||
| Facteur | Points | Description |
|
||||
|---------|--------|-------------|
|
||||
| Statut en ligne | +2 | Le host répond aux requêtes réseau |
|
||||
| Bootstrap Ansible OK | +1 | SSH et prérequis Ansible configurés |
|
||||
| Vérifié récemment (<1h) | +2 | Dernière vérification il y a moins d'une heure |
|
||||
| Vérifié aujourd'hui | +1 | Dernière vérification dans les 24 dernières heures |
|
||||
|
||||
> **Astuce :** Exécutez régulièrement un `Health Check` pour maintenir un score de santé élevé.
|
||||
|
||||
### Statuts Bootstrap Ansible
|
||||
|
||||
- **Ansible Ready** : Le host est entièrement configuré pour Ansible. L'utilisateur automation existe, la clé SSH est déployée et sudo est configuré sans mot de passe.
|
||||
- **Non configuré** : Le bootstrap n'a pas encore été exécuté sur ce host. Cliquez sur le bouton **Bootstrap** pour configurer l'accès Ansible.
|
||||
|
||||
#### Que signifie "Jamais vérifié" ?
|
||||
|
||||
Ce message apparaît lorsqu'aucun Health Check n'a été exécuté sur le host depuis son ajout. Le système ne peut pas déterminer l'état réel du serveur. Lancez un Health Check pour mettre à jour le statut et obtenir un score de santé précis.
|
||||
|
||||
---
|
||||
|
||||
## Architecture de la Solution
|
||||
|
||||
### Stack Technologique
|
||||
|
||||
| Composant | Technologie | Description |
|
||||
|-----------|-------------|-------------|
|
||||
| **Backend** | FastAPI (Python) | API REST haute performance |
|
||||
| **Automation** | Ansible | Gestion de configuration |
|
||||
| **Frontend** | HTML/CSS/JS + TailwindCSS | Interface web moderne |
|
||||
| **Déploiement** | Docker & Docker Compose | Conteneurisation |
|
||||
| **Temps réel** | WebSocket | Mises à jour live |
|
||||
|
||||
### Structure des Fichiers
|
||||
|
||||
L'API REST est accessible avec une clé API:
|
||||
```
|
||||
Header: X-API-Key: votre-cle-api
|
||||
homelab-automation/
|
||||
├── app/
|
||||
│ ├── routes/ # Routes API FastAPI
|
||||
│ ├── models/ # Modèles SQLAlchemy
|
||||
│ ├── schemas/ # Schémas Pydantic
|
||||
│ ├── services/ # Logique métier
|
||||
│ ├── index.html # Interface web
|
||||
│ └── main.js # Logique frontend
|
||||
├── ansible/
|
||||
│ ├── inventory/
|
||||
│ │ ├── hosts.yml # Inventaire des hosts
|
||||
│ │ └── group_vars/ # Variables par groupe
|
||||
│ └── playbooks/ # Playbooks Ansible
|
||||
├── alembic/ # Migrations de base de données
|
||||
├── data/ # Base de données SQLite
|
||||
├── tasks_logs/ # Logs des tâches
|
||||
├── docker-compose.yml
|
||||
└── Dockerfile
|
||||
```
|
||||
|
||||
---
|
||||
*Généré par Homelab Automation Dashboard*
|
||||
|
||||
## Fonctionnalités par Section
|
||||
|
||||
### Dashboard
|
||||
|
||||
Vue d'ensemble centralisée de votre infrastructure homelab.
|
||||
|
||||
- **Métriques en temps réel** : État des hosts (Online/Offline), statistiques des tâches (Succès/Échec)
|
||||
- **Actions Rapides** : Accès immédiat aux opérations courantes (Mise à jour globale, Health Check général)
|
||||
- **Aperçu des Hosts** : Liste condensée avec indicateurs de statut et OS pour une surveillance rapide
|
||||
- **Notifications** : Alertes visuelles sur les dernières activités importantes
|
||||
|
||||
### Hosts
|
||||
|
||||
Gestion complète du cycle de vie de vos serveurs.
|
||||
|
||||
- **Inventaire** : Ajout, modification et suppression de hosts avec détection d'OS
|
||||
- **Bootstrap Ansible** : Préparation automatique des serveurs (User, Clés SSH, Sudo)
|
||||
- **Indicateurs de Santé** : Score de santé détaillé basé sur la connectivité et la configuration
|
||||
- **Actions Individuelles** : Exécution de playbooks spécifiques (Upgrade, Backup, Reboot) sur un host
|
||||
- **Détails Avancés** : Vue détaillée avec historique des tâches et logs spécifiques au host
|
||||
|
||||
### Playbooks
|
||||
|
||||
Bibliothèque et exécution de playbooks Ansible personnalisés.
|
||||
|
||||
- **Catalogue** : Liste de tous les playbooks disponibles dans votre répertoire
|
||||
- **Exécution Ciblée** : Lancement de playbooks sur des hosts spécifiques ou des groupes
|
||||
- **Logs en Direct** : Suivi temps réel de l'exécution Ansible (console output)
|
||||
- **Historique** : Accès rapide aux résultats des exécutions précédentes
|
||||
|
||||
### Tasks
|
||||
|
||||
Traçabilité complète de toutes les opérations d'automatisation.
|
||||
|
||||
- **Suivi d'État** : Visualisation instantanée (En cours, Succès, Échec)
|
||||
- **Filtrage Avancé** : Recherche par statut, par date ou par type d'action
|
||||
- **Logs Détaillés** : Accès aux sorties standard et d'erreur pour le débogage
|
||||
- **Auto-refresh** : Mise à jour automatique des tâches en cours d'exécution
|
||||
|
||||
### Schedules
|
||||
|
||||
Planification automatisée des tâches récurrentes.
|
||||
|
||||
- **Planification Cron** : Configuration flexible de la fréquence d'exécution
|
||||
- **Tâches Récurrentes** : Backups quotidiens, Mises à jour hebdomadaires, Health Checks horaires
|
||||
- **Ciblage** : Définition des hosts ou groupes cibles pour chaque planification
|
||||
- **Gestion** : Activation, désactivation ou modification des planifications existantes
|
||||
|
||||
### Logs
|
||||
|
||||
Journal technique des événements système.
|
||||
|
||||
- **Streaming WebSocket** : Arrivée des logs en temps réel sans rechargement de page
|
||||
- **Niveaux de Log** : Distinction claire entre Info, Warning et Error
|
||||
- **Export** : Possibilité de télécharger les logs pour analyse externe
|
||||
- **Rétention** : Gestion de l'historique et nettoyage des logs anciens
|
||||
|
||||
### Alertes
|
||||
|
||||
Centre de messages pour les événements importants.
|
||||
|
||||
- **Suivi** : Consultez les alertes récentes (succès/échec, changements d'état)
|
||||
- **Lecture** : Les alertes peuvent être marquées comme lues pour garder une boîte de réception propre
|
||||
- **Notifications** : Certaines alertes peuvent déclencher des notifications ntfy (si activé)
|
||||
|
||||
### Configuration
|
||||
|
||||
Paramètres de l'application et intégrations.
|
||||
|
||||
- **Paramètres applicatifs** : Options persistées (ex: collecte des métriques)
|
||||
- **Notifications** : Configuration et test du service ntfy
|
||||
- **Sécurité** : Gestion du compte utilisateur (mot de passe) via l'écran utilisateur
|
||||
|
||||
---
|
||||
|
||||
## Système de Notifications (ntfy)
|
||||
|
||||
Restez informé de l'état de votre infrastructure grâce au système de notifications intégré basé sur **ntfy**.
|
||||
|
||||
### Canaux et Configuration
|
||||
|
||||
Les notifications sont envoyées via le service ntfy, permettant de recevoir des alertes push sur mobile et desktop.
|
||||
|
||||
- **Push Mobile** : Via l'application ntfy (Android/iOS)
|
||||
- **Web Push** : Notifications navigateur sur desktop
|
||||
- **Priorité** : Gestion des niveaux d'urgence (Low à High)
|
||||
|
||||
### Types d'Alertes
|
||||
|
||||
Vous recevez des notifications pour les événements critiques :
|
||||
|
||||
- ✅ Succès des Backups
|
||||
- ❌ Échecs de Tâches
|
||||
- ⚠️ Changements de Santé Host
|
||||
- 🛠️ Fin de Bootstrap
|
||||
|
||||
---
|
||||
|
||||
## Playbooks Ansible Disponibles
|
||||
|
||||
### bootstrap-host.yml
|
||||
|
||||
Configure un nouveau host pour Ansible : création utilisateur, clé SSH, sudo sans mot de passe.
|
||||
|
||||
> **Note :** Requis avant toute autre opération
|
||||
|
||||
### health-check.yml
|
||||
|
||||
Vérifie l'état de santé : CPU, RAM, disque, services critiques.
|
||||
|
||||
> **Note :** Exécution rapide, non destructif
|
||||
|
||||
### system-upgrade.yml
|
||||
|
||||
Met à jour tous les paquets système (apt/yum/dnf selon l'OS).
|
||||
|
||||
> **Attention :** Peut nécessiter un redémarrage
|
||||
|
||||
### backup-config.yml
|
||||
|
||||
Sauvegarde les fichiers de configuration importants (/etc, configs apps).
|
||||
|
||||
> **Note :** Stockage local ou distant
|
||||
|
||||
---
|
||||
|
||||
## Référence API
|
||||
|
||||
L'API REST est accessible sur le port configuré. Authentification via header `Authorization: Bearer <token>`.
|
||||
|
||||
| Endpoint | Méthode | Description |
|
||||
|----------|---------|-------------|
|
||||
| `/api/hosts` | GET | Liste tous les hosts |
|
||||
| `/api/hosts` | POST | Ajoute un nouveau host |
|
||||
| `/api/hosts/{id}` | PUT | Modifie un host existant |
|
||||
| `/api/hosts/{id}` | DELETE | Supprime un host |
|
||||
| `/api/tasks/logs` | GET | Récupère les logs de tâches |
|
||||
| `/api/ansible/playbooks` | GET | Liste les playbooks disponibles |
|
||||
| `/api/ansible/execute` | POST | Exécute un playbook |
|
||||
| `/api/schedules` | GET | Liste les planifications |
|
||||
| `/api/schedules` | POST | Crée une planification |
|
||||
| `/api/metrics` | GET | Métriques du dashboard |
|
||||
| `/api/auth/login` | POST | Authentification utilisateur |
|
||||
| `/api/auth/me` | GET | Informations utilisateur courant |
|
||||
|
||||
---
|
||||
|
||||
## Dépannage
|
||||
|
||||
### Le bootstrap échoue avec "Permission denied"
|
||||
|
||||
**Cause :** Les identifiants SSH fournis sont incorrects ou l'utilisateur n'a pas les droits sudo.
|
||||
|
||||
**Solution :** Vérifiez le nom d'utilisateur et mot de passe. Assurez-vous que l'utilisateur peut exécuter `sudo` sur le host cible.
|
||||
|
||||
### Les hosts apparaissent "offline" alors qu'ils sont accessibles
|
||||
|
||||
**Cause :** Le health check n'a pas été exécuté ou la clé SSH n'est pas configurée.
|
||||
|
||||
**Solution :** Exécutez le bootstrap si ce n'est pas fait, puis lancez un Health Check.
|
||||
|
||||
### Les tâches restent bloquées "En cours"
|
||||
|
||||
**Cause :** Le processus Ansible peut être bloqué ou le host ne répond plus.
|
||||
|
||||
**Solution :** Vérifiez la connectivité réseau. Consultez les logs système pour plus de détails. Redémarrez le conteneur Docker si nécessaire.
|
||||
|
||||
### L'interface ne se met pas à jour en temps réel
|
||||
|
||||
**Cause :** La connexion WebSocket est interrompue.
|
||||
|
||||
**Solution :** Rafraîchissez la page. Vérifiez que le port WebSocket n'est pas bloqué par un firewall ou proxy.
|
||||
|
||||
---
|
||||
|
||||
## Raccourcis et Astuces
|
||||
|
||||
### Navigation
|
||||
|
||||
- Cliquez sur le logo pour revenir au Dashboard
|
||||
- Utilisez les onglets du menu pour naviguer
|
||||
- Le thème clair/sombre est persistant
|
||||
|
||||
### Productivité
|
||||
|
||||
- Filtrez les hosts par groupe pour des actions groupées
|
||||
- Utilisez les filtres de date pour retrouver des tâches
|
||||
- Exportez les logs avant de les effacer
|
||||
|
||||
---
|
||||
|
||||
*Documentation générée par Homelab Automation Dashboard v1.0*
|
||||
"""
|
||||
|
||||
@@ -197,6 +197,29 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
|
||||
bulletIndent=10,
|
||||
)
|
||||
|
||||
quote_style = ParagraphStyle(
|
||||
'CustomQuote',
|
||||
parent=body_style,
|
||||
leftIndent=20,
|
||||
borderColor=colors.HexColor('#7c3aed'),
|
||||
borderWidth=2,
|
||||
borderPadding=8,
|
||||
backColor=colors.HexColor('#f5f3ff'),
|
||||
textColor=colors.HexColor('#5b21b6'),
|
||||
fontSize=9,
|
||||
spaceBefore=8,
|
||||
spaceAfter=8,
|
||||
)
|
||||
|
||||
h4_style = ParagraphStyle(
|
||||
'CustomH4',
|
||||
parent=styles['Heading4'],
|
||||
fontSize=11,
|
||||
spaceBefore=8,
|
||||
spaceAfter=4,
|
||||
textColor=colors.HexColor('#374151'),
|
||||
)
|
||||
|
||||
# Parser le Markdown
|
||||
elements = []
|
||||
elements.append(Paragraph(title, title_style))
|
||||
@@ -272,7 +295,7 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
|
||||
elements.append(Paragraph(display_text, h3_style))
|
||||
elif line.startswith('#### '):
|
||||
text = line[5:].strip()
|
||||
elements.append(Paragraph(f"<b>{text}</b>", body_style))
|
||||
elements.append(Paragraph(text, h4_style))
|
||||
|
||||
# Listes à puces
|
||||
elif line.strip().startswith('- ') or line.strip().startswith('* '):
|
||||
@@ -291,6 +314,14 @@ def markdown_to_pdf_bytes(markdown_content: str, title: str = "Document") -> byt
|
||||
text = re.sub(r'`(.+?)`', r'<font face="Courier">\1</font>', text)
|
||||
elements.append(Paragraph(f"{num}. {text}", bullet_style))
|
||||
|
||||
# Blockquotes (citations)
|
||||
elif line.strip().startswith('> '):
|
||||
text = line.strip()[2:]
|
||||
# Convertir le markdown basique
|
||||
text = re.sub(r'\*\*(.+?)\*\*', r'<b>\1</b>', text)
|
||||
text = re.sub(r'`(.+?)`', r'<font face="Courier">\1</font>', text)
|
||||
elements.append(Paragraph(text, quote_style))
|
||||
|
||||
# Ligne horizontale
|
||||
elif line.strip() in ['---', '***', '___']:
|
||||
elements.append(Spacer(1, 10))
|
||||
|
||||
Reference in New Issue
Block a user