Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
444 lines
16 KiB
Markdown
444 lines
16 KiB
Markdown
# 🎯 Ansible Playbook Editor - Design & Architecture
|
||
|
||
## Résumé Exécutif
|
||
|
||
Refonte complète de l'éditeur de playbooks Ansible pour offrir une **expérience IDE-like** avec:
|
||
- Éditeur CodeMirror 6 avec coloration syntaxique Ansible/YAML avancée
|
||
- Intégration ansible-lint non-bloquante avec feedback temps réel
|
||
- Score de qualité et historique par playbook
|
||
- UX professionnelle inspirée de VS Code/GitHub Actions
|
||
|
||
---
|
||
|
||
## 1. Vision UX Globale
|
||
|
||
### Philosophie Design
|
||
- **Clarté avant tout**: Interface sobre, focalisée sur le code
|
||
- **Feedback intelligent**: Guidage proactif sans interruption du flux
|
||
- **Progressive disclosure**: Complexité révélée au bon moment
|
||
- **Performance**: Réactivité < 100ms pour les interactions
|
||
|
||
### Principes UX
|
||
| Principe | Application |
|
||
|----------|-------------|
|
||
| **Non-bloquant** | Lint en arrière-plan, édition jamais interrompue |
|
||
| **Contextuel** | Erreurs affichées à leur emplacement exact |
|
||
| **Actionnable** | Chaque diagnostic propose une correction |
|
||
| **Discret** | Pas de popup intrusif, indicateurs subtils |
|
||
|
||
---
|
||
|
||
## 2. Design de l'Éditeur
|
||
|
||
### 2.1 Architecture de l'Éditeur
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ 📄 playbook-name.yml [Quality: 92/100 ✓] │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ ┌──────┬────────────────────────────────────────────────────┐ │
|
||
│ │ LINT │ ERRORS (2) WARNINGS (3) INFO (1) │ │
|
||
│ ├──────┴────────────────────────────────────────────────────┤ │
|
||
│ │ │ │
|
||
│ │ [Gutter] [Code Editor Area] │ │
|
||
│ │ ⚠ 1 --- │ │
|
||
│ │ 2 - name: Deploy application │ │
|
||
│ │ 3 hosts: all │ │
|
||
│ │ ⛔ 4 become: yes │ │
|
||
│ │ ╭──────────────────────────────────────────╮ │ │
|
||
│ │ │ ⛔ risky-file-permissions: Missing mode │ │ │
|
||
│ │ │ for file operation │ │ │
|
||
│ │ │ 📖 Learn more | ✨ Auto-fix │ │ │
|
||
│ │ ╰──────────────────────────────────────────╯ │ │
|
||
│ │ 5 vars: │ │
|
||
│ │ 6 category: deploy │ │
|
||
│ │ │ │
|
||
│ └────────────────────────────────────────────────────────────┘ │
|
||
├─────────────────────────────────────────────────────────────────┤
|
||
│ [✓ YAML Valid] [⚠ 5 issues] [Ln 4, Col 5] [Annuler] [Save] │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 Coloration Syntaxique Ansible
|
||
|
||
#### Tokens à highlighter
|
||
| Type | Exemples | Couleur (thème sombre) |
|
||
|------|----------|------------------------|
|
||
| **Clés Ansible** | `hosts`, `tasks`, `vars`, `handlers`, `become` | `#c792ea` (violet) |
|
||
| **Modules** | `ansible.builtin.copy`, `community.docker.docker_container` | `#82aaff` (bleu) |
|
||
| **Variables Jinja** | `{{ var }}`, `{% if %}` | `#c3e88d` (vert) |
|
||
| **Valeurs booléennes** | `true`, `false`, `yes`, `no` | `#f78c6c` (orange) |
|
||
| **Commentaires** | `# comment` | `#546e7a` (gris) |
|
||
| **Strings** | `"text"`, `'text'` | `#c3e88d` (vert clair) |
|
||
| **Nombres** | `123`, `0.5` | `#f78c6c` (orange) |
|
||
| **Ancres YAML** | `&anchor`, `*reference` | `#89ddff` (cyan) |
|
||
|
||
### 2.3 Fonctionnalités Éditeur
|
||
|
||
1. **Auto-indentation YAML** (2 espaces)
|
||
2. **Bracket matching** pour `{{ }}`, `{% %}`, `[]`, `{}`
|
||
3. **Fold/Unfold** des blocs YAML
|
||
4. **Minimap** optionnelle
|
||
5. **Search & Replace** avec regex
|
||
6. **Multi-curseur**
|
||
|
||
---
|
||
|
||
## 3. Intégration ansible-lint
|
||
|
||
### 3.1 Bouton Lint Intelligent
|
||
|
||
#### États visuels
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ État │ Icône │ Couleur │ Animation │
|
||
├─────────────┼───────────┼────────────┼────────────────────┤
|
||
│ Idle │ 🔍 │ Gris │ Aucune │
|
||
│ Running │ ⟳ │ Bleu │ Rotation │
|
||
│ Success │ ✓ │ Vert │ Pulse subtil │
|
||
│ Warnings │ ⚠ (3) │ Jaune │ Aucune │
|
||
│ Errors │ ⛔ (2) │ Rouge │ Aucune │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 Affichage des Résultats
|
||
|
||
#### Gutter Markers
|
||
- Icône colorée dans la marge gauche à la ligne concernée
|
||
- Survol = tooltip avec résumé
|
||
- Clic = panneau détaillé
|
||
|
||
#### Inline Diagnostics
|
||
```yaml
|
||
- name: Copy config
|
||
ansible.builtin.copy:
|
||
src: config.yml
|
||
dest: /etc/app/config.yml
|
||
# ⛔ risky-file-permissions: File permissions unset or incorrect
|
||
# 💡 Ajouter: mode: '0644'
|
||
```
|
||
|
||
#### Panneau Problèmes (optionnel)
|
||
Liste scrollable de tous les problèmes, filtrable par sévérité.
|
||
|
||
### 3.3 Workflow Utilisateur
|
||
|
||
```
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||
│ Ouvrir │────▶│ Éditer │────▶│ Lint │
|
||
│ playbook │ │ contenu │ │ (auto/btn) │
|
||
└──────────────┘ └──────────────┘ └──────┬───────┘
|
||
│
|
||
┌────────────────────────────────────────┤
|
||
▼ ▼
|
||
┌──────────────┐ ┌──────────────┐
|
||
│ Afficher │ │ Sauvegarder │
|
||
│ résultats │ │ (possible) │
|
||
└──────────────┘ └──────┬───────┘
|
||
│
|
||
▼
|
||
┌──────────────┐
|
||
│ Exécuter │
|
||
│ (bloqué si │
|
||
│ erreurs*) │
|
||
└──────────────┘
|
||
* Configurable
|
||
```
|
||
|
||
### 3.4 Comportement Non-Bloquant
|
||
|
||
| Action | Lint bloque ? | Sauvegarde bloque ? | Exécution bloque ? |
|
||
|--------|---------------|---------------------|-------------------|
|
||
| Lint en cours | ❌ | ❌ | ❌ |
|
||
| Warnings | ❌ | ❌ | ❌ |
|
||
| Errors | ❌ | ❌ | ⚠️ Avertissement |
|
||
|
||
---
|
||
|
||
## 4. Architecture Technique
|
||
|
||
### 4.1 Backend - API ansible-lint
|
||
|
||
#### Endpoint
|
||
```
|
||
POST /api/playbooks/{filename}/lint
|
||
```
|
||
|
||
#### Request
|
||
```json
|
||
{
|
||
"content": "---\n- name: My playbook\n hosts: all\n...",
|
||
"rules": ["yaml", "risky-file-permissions"], // optionnel
|
||
"skip_rules": ["no-changed-when"] // optionnel
|
||
}
|
||
```
|
||
|
||
#### Response
|
||
```json
|
||
{
|
||
"success": true,
|
||
"execution_time_ms": 342,
|
||
"summary": {
|
||
"total": 5,
|
||
"errors": 2,
|
||
"warnings": 2,
|
||
"info": 1
|
||
},
|
||
"quality_score": 72,
|
||
"issues": [
|
||
{
|
||
"rule_id": "risky-file-permissions",
|
||
"severity": "error",
|
||
"message": "File permissions unset or incorrect",
|
||
"filename": "playbook.yml",
|
||
"line": 12,
|
||
"column": 5,
|
||
"context": " ansible.builtin.copy:",
|
||
"help_url": "https://ansible-lint.readthedocs.io/rules/risky-file-permissions/",
|
||
"fix_suggestion": "Add 'mode: \"0644\"' parameter"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### Implémentation Backend
|
||
```python
|
||
# app/routes/lint.py
|
||
|
||
import asyncio
|
||
import json
|
||
import tempfile
|
||
from pathlib import Path
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel
|
||
|
||
router = APIRouter()
|
||
|
||
class LintRequest(BaseModel):
|
||
content: str
|
||
rules: list[str] | None = None
|
||
skip_rules: list[str] | None = None
|
||
|
||
class LintIssue(BaseModel):
|
||
rule_id: str
|
||
severity: str # error, warning, info
|
||
message: str
|
||
line: int
|
||
column: int
|
||
context: str | None = None
|
||
help_url: str | None = None
|
||
fix_suggestion: str | None = None
|
||
|
||
class LintResponse(BaseModel):
|
||
success: bool
|
||
execution_time_ms: int
|
||
summary: dict
|
||
quality_score: int
|
||
issues: list[LintIssue]
|
||
|
||
@router.post("/{filename}/lint")
|
||
async def lint_playbook(filename: str, request: LintRequest):
|
||
"""Exécute ansible-lint sur le contenu d'un playbook."""
|
||
|
||
start_time = asyncio.get_event_loop().time()
|
||
|
||
# Créer fichier temporaire
|
||
with tempfile.NamedTemporaryFile(
|
||
mode='w', suffix='.yml', delete=False
|
||
) as f:
|
||
f.write(request.content)
|
||
temp_path = f.name
|
||
|
||
try:
|
||
# Construire commande ansible-lint
|
||
cmd = [
|
||
'ansible-lint',
|
||
'--format', 'json',
|
||
'--nocolor',
|
||
temp_path
|
||
]
|
||
|
||
if request.skip_rules:
|
||
for rule in request.skip_rules:
|
||
cmd.extend(['--skip-list', rule])
|
||
|
||
# Exécuter
|
||
process = await asyncio.create_subprocess_exec(
|
||
*cmd,
|
||
stdout=asyncio.subprocess.PIPE,
|
||
stderr=asyncio.subprocess.PIPE
|
||
)
|
||
stdout, stderr = await process.communicate()
|
||
|
||
# Parser JSON
|
||
issues = parse_ansible_lint_output(stdout.decode(), temp_path, filename)
|
||
|
||
# Calculer score
|
||
score = calculate_quality_score(issues, len(request.content.split('\n')))
|
||
|
||
execution_time = int((asyncio.get_event_loop().time() - start_time) * 1000)
|
||
|
||
return LintResponse(
|
||
success=True,
|
||
execution_time_ms=execution_time,
|
||
summary={
|
||
"total": len(issues),
|
||
"errors": sum(1 for i in issues if i.severity == "error"),
|
||
"warnings": sum(1 for i in issues if i.severity == "warning"),
|
||
"info": sum(1 for i in issues if i.severity == "info")
|
||
},
|
||
quality_score=score,
|
||
issues=issues
|
||
)
|
||
finally:
|
||
Path(temp_path).unlink(missing_ok=True)
|
||
```
|
||
|
||
### 4.2 Frontend - Composants
|
||
|
||
#### Structure des composants
|
||
```
|
||
PlaybookEditor/
|
||
├── EditorContainer # Container principal
|
||
├── CodeMirrorEditor # Instance CodeMirror 6
|
||
├── LintButton # Bouton avec états
|
||
├── LintResultsPanel # Panneau latéral/bas
|
||
├── GutterMarkers # Marqueurs dans la marge
|
||
├── InlineDiagnostics # Diagnostics inline
|
||
├── QualityBadge # Score de qualité
|
||
└── StatusBar # Barre de statut
|
||
```
|
||
|
||
#### Gestion d'état
|
||
```javascript
|
||
const editorState = {
|
||
// Contenu
|
||
content: '',
|
||
originalContent: '',
|
||
isDirty: false,
|
||
|
||
// Lint
|
||
lintStatus: 'idle', // idle | running | success | warnings | errors
|
||
lintResults: null,
|
||
lastLintTime: null,
|
||
|
||
// Qualité
|
||
qualityScore: null,
|
||
|
||
// UI
|
||
showProblemsPanel: false,
|
||
activeIssueIndex: null,
|
||
|
||
// Config
|
||
lintOnSave: true,
|
||
blockExecutionOnErrors: true
|
||
};
|
||
```
|
||
|
||
#### Stratégie de Performance
|
||
|
||
1. **Debounce**: Lint auto après 1500ms d'inactivité
|
||
2. **Abort Controller**: Annuler lint précédent si nouvelle édition
|
||
3. **Virtual Scrolling**: Pour longs playbooks (>1000 lignes)
|
||
4. **Web Worker**: Parser YAML en background (optionnel)
|
||
|
||
---
|
||
|
||
## 5. Idées Bonus à Forte Valeur
|
||
|
||
### 5.1 📊 Quality Score Dashboard
|
||
Score 0-100 basé sur:
|
||
- Nombre d'erreurs (-20 pts/erreur)
|
||
- Nombre de warnings (-5 pts/warning)
|
||
- Bonnes pratiques suivies (+5 pts)
|
||
- Historique (trend: amélioration ou dégradation)
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ Quality Score: 87/100 ↑ +5 │
|
||
│ ████████████████████░░░░ │
|
||
│ │
|
||
│ ✓ No deprecated modules │
|
||
│ ✓ FQCN used consistently │
|
||
│ ⚠ 2 missing task names │
|
||
│ ⚠ 1 risky permission │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
### 5.2 🔧 Auto-Fix Suggestions
|
||
Pour certaines règles, proposer des corrections automatiques:
|
||
- `no-free-form`: Convertir en syntaxe structurée
|
||
- `risky-file-permissions`: Ajouter `mode: '0644'`
|
||
- `fqcn`: Convertir `copy` → `ansible.builtin.copy`
|
||
- `yaml[truthy]`: Convertir `yes` → `true`
|
||
|
||
### 5.3 📈 Lint History par Playbook
|
||
Stocker l'historique des scores pour chaque playbook:
|
||
```json
|
||
{
|
||
"playbook": "deploy-app.yml",
|
||
"history": [
|
||
{"date": "2025-01-15", "score": 65, "errors": 3, "warnings": 5},
|
||
{"date": "2025-01-16", "score": 78, "errors": 1, "warnings": 3},
|
||
{"date": "2025-01-17", "score": 92, "errors": 0, "warnings": 1}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 5.4 💡 Assistant Bonnes Pratiques
|
||
Suggestions proactives basées sur le contexte:
|
||
- "Cette tâche modifie des fichiers. Ajoutez un handler notify."
|
||
- "Utilisez become_user au lieu de become pour plus de sécurité."
|
||
- "Préférez ansible.builtin.command à shell pour les commandes simples."
|
||
|
||
### 5.5 🔄 Pré-validation Commit
|
||
Hook optionnel pour valider avant git commit (documentation fournie).
|
||
|
||
---
|
||
|
||
## 6. Plan d'Implémentation
|
||
|
||
### Phase 1: Backend Lint API ✅
|
||
- [x] Endpoint `/api/playbooks/{filename}/lint`
|
||
- [x] Parsing JSON ansible-lint
|
||
- [x] Calcul quality score
|
||
- [x] Suggestions de correction par règle
|
||
- [x] URLs de documentation
|
||
|
||
### Phase 2: Éditeur CodeMirror 6 ✅
|
||
- [x] Intégration CDN CodeMirror 6 (avec fallback textarea)
|
||
- [x] Configuration YAML + thème personnalisé
|
||
- [x] Coloration Ansible avancée
|
||
|
||
### Phase 3: UI ansible-lint ✅
|
||
- [x] LintButton avec états (idle, running, success, warnings, errors)
|
||
- [x] Panneau problèmes avec navigation vers ligne
|
||
- [x] Badge de qualité coloré
|
||
- [x] Onglets Éditeur / Problèmes
|
||
|
||
### Phase 4: Bonus ✅
|
||
- [x] Quality Score (0-100) avec bonus/malus
|
||
- [x] Suggestions de fix par règle
|
||
- [x] Liens vers documentation ansible-lint
|
||
|
||
### Tests ✅
|
||
- [x] 24 tests unitaires (100% passing)
|
||
- [x] Tests parsing JSON/texte
|
||
- [x] Tests endpoint API
|
||
- [x] Tests calcul score qualité
|
||
|
||
---
|
||
|
||
## 7. Résumé Pitch Produit
|
||
|
||
> **Éditeur Ansible IDE-like pour votre Homelab**
|
||
>
|
||
> Passez de "ça marche peut-être" à "je suis sûr que c'est correct" avec notre éditeur de playbooks intelligent.
|
||
>
|
||
> ✨ **Coloration syntaxique Ansible** - Modules, variables Jinja, clés YAML
|
||
> 🔍 **ansible-lint intégré** - Feedback en temps réel, sans bloquer
|
||
> 📊 **Score de qualité** - Suivez l'évolution de vos playbooks
|
||
> ⚡ **Non-bloquant** - Éditez, sauvegardez, le lint tourne en arrière-plan
|
||
> 🎯 **Actionnable** - Chaque erreur avec explication et suggestion de fix
|
||
|