260 lines
16 KiB
Markdown
260 lines
16 KiB
Markdown
# Kanbante
|
||
|
||
**Kanban project manager integrated with Gitea** — lightweight, self-hosted, dark theme.
|
||
|
||
## Features
|
||
|
||
- 📋 **Dashboard** — tous tes projets Gitea en un coup d'œil (actifs / inactifs / archivés — le vrai drapeau Gitea)
|
||
- 🗂️ **Kanban Board** — colonnes Backlog → À faire → En cours → **QA** → Révision → Terminé, ordre des cartes persisté
|
||
- 🎯 **Drag & Drop** — glisse les issues entre colonnes ; en cas d'échec, la carte est replacée
|
||
- 🔗 **Gitea Sync bidirectionnelle** — « Terminé » ferme l'issue dans Gitea, la ressortir la rouvre ; l'état local est réconcilié à chaque chargement
|
||
- ➕ **Création d'issues** depuis le board, filtres texte + étiquettes, cartes liées à Gitea
|
||
- ⚡ **Temps réel** — un webhook Gitea invalide le cache et pousse un événement SSE : le board ouvert se met à jour tout seul (en douceur, sans interrompre une saisie ni un drag)
|
||
- 🏷️ **Visible dans Gitea** — les colonnes intermédiaires sont reflétées par des étiquettes `kanban:*` sur les issues, et un board peut se filtrer **par milestone** (échéance + progression façon sprint)
|
||
- 🗂️ **Panneau détail** — clic sur une carte : corps markdown, commentaires, et réponse postée dans Gitea sans quitter le board
|
||
- 📰 **Journal d'événements** — chaque mouvement est horodaté : fil d'activité (`/activity`), métriques de débit et de temps de cycle (`/api/metrics`)
|
||
- ⏳ **Vieillissement + limites WIP** — badge d'ancienneté par colonne et limites souples configurables
|
||
- 🎯 **Vue Focus** — tout ce qui est En cours / en QA / En révision, tous projets confondus (`/focus`)
|
||
- 🖥️ **Wallboard** — mode TV plein écran (`/wall`) : rotation automatique entre les projets actifs, cartes en vol et compteurs en très grands caractères, rafraîchi en direct ; `?repo=Proprietaire/depot` fige un seul projet
|
||
- 🌅 **Focus du jour** — `/today` : jusqu'à 3 cartes épinglées (bouton 📌 sur les cartes), candidates de l'encours, reprises d'hier non terminées, récapitulatif du soir ; intégrées au briefing
|
||
- 🔮 **Prévisions** — débit réel du journal (fermetures/semaine), date d'épuisement du backlog et ETA des milestones en fourchette honnête, sur le dashboard, le bandeau milestone et `/forecast` (sparkline SVG)
|
||
- 📖 **Récit de sprint** — le bilan markdown d'un milestone (livré, cycles, plus lentes, reporté) généré depuis le journal, page `/sprint/...` + copie/téléchargement en un clic
|
||
- 📥 **Mode Triage** — `/triage` : le backlog se vide carte par carte, plein cadre, au clavier (`1-5` colonnes, `L` étiquettes, `A` s'assigner, `X` fermer, `Entrée` suivante, `Z` annuler), progression et résumé de session ; décisions via les mêmes API que le board
|
||
- 🔄 **Board dynamique** — aucun refresh manuel : toute modification (autre session, Gitea, capture) est diffusée en SSE et appliquée **en place** avec animation des cartes (`/api/board/{owner}/{repo}` en JSON + fragments rendus serveur, diff DOM/FLIP) ; repli par sondage de 45 s sans webhooks ; drag local, filtres et panneau détail préservés
|
||
- ⌨️ **Palette de commandes** — `Ctrl+K` (ou le bouton ⌨️) sur toutes les pages : actions, projets et recherche d'issues globale, 100 % clavier
|
||
- 🧠 **Digest hebdo → ObsiGate** — résumé markdown de la semaine (journal + focus) composé automatiquement, **livré seulement sur approbation** (page `/digest`) vers le serveur MCP d'ObsiGate (client MCP intégré, outil et gabarit d'arguments configurables) ou un webhook générique
|
||
- ⚡ **Capture éclair** — page `/capture` (formulaire), endpoint `POST /api/capture` protégé par un jeton dédié, et écoute ntfy : un message publié sur le topic de capture devient une issue dans le dépôt par défaut
|
||
- 🔀 **Réconciliation par étiquettes** — si l'étiquette `kanban:*` d'une issue change hors de Kanbante (dans Gitea, par un autre outil), le board adopte la colonne de l'étiquette au chargement et le journalise (source `gitea`). Une seule étiquette `kanban:*` fait foi ; aucune ou plusieurs = l'état local gagne
|
||
- 🔍 **Recherche globale + Mes issues** — chercher dans toutes les issues de tous les projets (`/search`), et voir ce qui vous est assigné (`/my`)
|
||
- 📊 **Briefing quotidien** — encours, vieillissement, milestones proches : page `/briefing` et envoi **ntfy** planifié
|
||
- 🤖 **Serveur MCP** — 7 outils en lecture seule (boards, focus, recherche, métriques) exposés en JSON-RPC sur `/mcp` pour vos agents
|
||
- 🏷️ **Étiquettes gérables depuis le board** — créer / supprimer les étiquettes du dépôt, les appliquer aux issues depuis le panneau détail
|
||
- 📝 **Notes de projet** — panneau repliable, largeur ajustable, sauvegarde automatique
|
||
- 📝 **Notes** — bloc-notes par projet
|
||
- 🔐 **Authentification** Basic Auth intégrée + protection CSRF (voir Sécurité)
|
||
- 🌙 **Dark theme** — conçu pour les longues sessions
|
||
- 🐳 **Docker** — un seul conteneur, utilisateur non-root, healthcheck
|
||
- 📴 **Aucun CDN** — les bibliothèques JS sont vendorées dans `static/vendor/`, l'app fonctionne hors-ligne
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
# 1. Installer les dépendances
|
||
cd kanbante
|
||
pip install -e .
|
||
|
||
# 2. Configurer les variables d'environnement
|
||
export GITEA_URL=https://git.dracodev.net
|
||
export GITEA_TOKEN=ton_token_gitea
|
||
# Recommandé dès que le service dépasse localhost :
|
||
export KANBANTE_AUTH_USER=ton_nom
|
||
export KANBANTE_AUTH_PASSWORD=ton_mot_de_passe
|
||
|
||
# 3. Lancer
|
||
uvicorn app.main:app --reload --port 8080
|
||
```
|
||
|
||
Ouvrir http://localhost:8080
|
||
|
||
## Docker
|
||
|
||
### Avec docker-compose (recommandé)
|
||
|
||
```bash
|
||
cat > .env <<'EOF'
|
||
GITEA_TOKEN=ton_token
|
||
KANBANTE_AUTH_USER=ton_nom
|
||
KANBANTE_AUTH_PASSWORD=ton_mot_de_passe
|
||
EOF
|
||
docker compose up -d
|
||
```
|
||
|
||
La base SQLite vit dans le volume `kanbante_data` (monté sur `/data`,
|
||
chemin `KANBANTE_DB_PATH`) : les positions de cartes et les notes
|
||
survivent aux recréations du conteneur.
|
||
|
||
### Avec docker uniquement
|
||
|
||
```bash
|
||
docker build -t kanbante:latest .
|
||
docker volume create kanbante_data
|
||
docker run -d \
|
||
--name kanbante \
|
||
-p 8080:8080 \
|
||
-v kanbante_data:/data \
|
||
-e GITEA_URL=https://git.dracodev.net \
|
||
-e GITEA_TOKEN=ton_token \
|
||
-e KANBANTE_AUTH_USER=ton_nom \
|
||
-e KANBANTE_AUTH_PASSWORD=ton_mot_de_passe \
|
||
--restart unless-stopped \
|
||
kanbante:latest
|
||
```
|
||
|
||
### Déploiement sur Deepin 25 (Debian Bookworm)
|
||
|
||
```bash
|
||
sudo apt install -y docker.io docker-compose docker-buildx
|
||
sudo usermod -aG docker $USER
|
||
sudo systemctl enable --now docker
|
||
# Builder et lancer (voir section "Avec docker uniquement")
|
||
```
|
||
|
||
## Configuration
|
||
|
||
| Variable | Défaut | Rôle |
|
||
|---|---|---|
|
||
| `GITEA_URL` | `https://git.dracodev.net` | Instance Gitea |
|
||
| `GITEA_TOKEN` | *(vide)* | Jeton API Gitea — **portée conseillée : lecture + écriture des issues, sur un compte dédié, pas un jeton admin**. Sans jeton : contenu public seulement |
|
||
| `KANBANTE_DB_PATH` | `~/.kanbante/kanbante.db` | Base SQLite locale (en Docker : `/data/kanbante.db`) |
|
||
| `KANBANTE_AUTH_USER` / `KANBANTE_AUTH_PASSWORD` | *(vide)* | Active la Basic Auth dès que les deux sont définis |
|
||
| `KANBANTE_REQUIRE_AUTH` | `0` | `1` = l'app refuse de servir (503) si l'auth n'est pas configurée |
|
||
| `KANBANTE_ACTIVE_WINDOW_DAYS` | `365` | Un dépôt non archivé mis à jour depuis moins de N jours est « actif » |
|
||
| `KANBANTE_CACHE_TTL` | `30` | Secondes de cache des listes Gitea (dépôts, issues) |
|
||
| `KANBANTE_MAX_NOTE_CHARS` | `20000` | Taille maximale d'une note de projet |
|
||
| `KANBANTE_SYNC_GITEA` | `1` | `0` = les déplacements ne ferment/rouvrent plus les issues Gitea |
|
||
| `KANBANTE_WIP_LIMITS` | *(vide)* | Limites souples par colonne, ex. `in_progress:5,review:3` (affichage `n/limite`, rouge en dépassement, jamais bloquant) |
|
||
| `KANBANTE_AGING_DAYS` | `7` | Jours dans la même colonne avant le badge « ⏳ » sur une carte (rouge au double) |
|
||
| `KANBANTE_WEBHOOK_SECRET` | *(vide)* | Secret partagé du webhook Gitea ; si défini, la signature HMAC `X-Gitea-Signature` est exigée |
|
||
| `KANBANTE_SYNC_LABELS` | `1` | `0` = ne plus refléter les colonnes intermédiaires en étiquettes `kanban:*` dans Gitea |
|
||
| `KANBANTE_NTFY_URL` | *(vide)* | Base du serveur ntfy (ex. `https://ntfy.sh` ou auto-hébergé) pour le briefing |
|
||
| `KANBANTE_NTFY_TOPIC` | *(vide)* | Topic ntfy du briefing (URL + topic requis pour activer l'envoi) |
|
||
| `KANBANTE_NTFY_TOKEN` | *(vide)* | Jeton Bearer optionnel pour un topic protégé |
|
||
| `KANBANTE_BRIEFING_TIME` | *(vide)* | Heure d'envoi quotidien du briefing, `HH:MM` heure du serveur ; vide = pas d'envoi automatique |
|
||
| `KANBANTE_CAPTURE_REPO` | *(vide)* | Dépôt par défaut de la capture éclair (`Proprietaire/depot`) |
|
||
| `KANBANTE_CAPTURE_TOKEN` | *(vide)* | Jeton dédié de `POST /api/capture` ; vide = endpoint désactivé (503) |
|
||
| `KANBANTE_CAPTURE_NTFY_TOPIC` | *(vide)* | Topic ntfy écouté : chaque message devient une issue dans `KANBANTE_CAPTURE_REPO` |
|
||
| `KANBANTE_DIGEST_TIME` / `KANBANTE_DIGEST_WEEKDAY` | *(vide)* / `0` | Composition hebdo du digest (`HH:MM`, 0 = lundi) ; notification ntfy, jamais d'envoi automatique |
|
||
| `KANBANTE_OBSIGATE_MCP_URL` / `_TOKEN` / `_TOOL` / `_ARGS` | *(vide)* | Serveur MCP d'ObsiGate : URL, jeton optionnel, outil de création de note, gabarit JSON d'arguments (`{title}`, `{content}`) |
|
||
| `KANBANTE_DIGEST_WEBHOOK_URL` | *(vide)* | Webhook générique recevant le digest en JSON `{title, markdown, week}` |
|
||
|
||
## Sécurité
|
||
|
||
- Sans `KANBANTE_AUTH_*`, **quiconque atteint le port 8080 voit tous les
|
||
dépôts visibles par le jeton et peut modifier les boards** — l'app
|
||
l'annonce par un avertissement au démarrage. Définis les identifiants,
|
||
ou mets Kanbante derrière un reverse proxy authentifiant, et pose
|
||
`KANBANTE_REQUIRE_AUTH=1` pour échouer fermé.
|
||
- Tous les POST exigent un jeton CSRF (cookie double-submit posé par les
|
||
pages) : les appels API directs doivent d'abord charger une page pour
|
||
obtenir le cookie, puis envoyer le jeton (en-tête `X-CSRF-Token` ou
|
||
champ `csrf_token`).
|
||
- Aucun secret n'est versionné : le jeton passe uniquement par
|
||
l'environnement / le fichier `.env` local (ignoré par git).
|
||
|
||
## Architecture
|
||
|
||
```
|
||
kanbante/
|
||
├── app/
|
||
│ ├── main.py # FastAPI app (routes, auth, CSRF, lifespan)
|
||
│ ├── config.py # Réglages par variables d'environnement
|
||
│ ├── gitea_client.py # Client API Gitea asynchrone, paginé, avec cache
|
||
│ ├── models.py # SQLite (état du board local + migrations)
|
||
│ └── templates/ # Jinja2 (dashboard, board, focus, error)
|
||
├── static/
|
||
│ ├── css/style.css # Design system dark theme
|
||
│ ├── js/board.js # Drag & drop, filtres, création d'issues
|
||
│ └── vendor/ # HTMX, Alpine, SortableJS (vendorés, aucun CDN)
|
||
├── tests/ # Suite pytest
|
||
├── Dockerfile
|
||
├── docker-compose.yml
|
||
└── README.md
|
||
```
|
||
|
||
## API
|
||
|
||
| Méthode | Route | Description |
|
||
|---------|-------|-------------|
|
||
| GET | `/` | Dashboard tous les projets |
|
||
| GET | `/board/{owner}/{repo}` | Kanban board |
|
||
| GET | `/focus` | En cours + QA + En révision, tous projets actifs |
|
||
| GET | `/wall` | Wallboard plein écran (rotation des projets actifs ; `?repo=` pour un seul projet) |
|
||
| GET | `/capture` | Page de capture éclair (formulaire + statut + instructions) |
|
||
| GET | `/api/palette` | Données de la palette Ctrl+K (projets + recherche d'issues `?q=`) |
|
||
| GET | `/api/board/{owner}/{repo}` | Board en JSON + fragments HTML des cartes (base du board dynamique, `?milestone=` accepté) |
|
||
| GET | `/triage` · `/triage/{owner}/{repo}` | Mode Triage : sélecteur de projet puis session clavier sur le backlog |
|
||
| GET | `/today` | Focus du jour : les 3 cartes épinglées, candidates, reprises d'hier |
|
||
| POST | `/api/today/pin` · `/api/today/unpin` | Épingler / retirer une carte du Focus du jour (max 3) |
|
||
| GET | `/forecast` | Prévisions : débit hebdo, ETA backlog et milestones par projet |
|
||
| GET | `/sprint/{owner}/{repo}` · `/api/sprint/{owner}/{repo}` | Récit de sprint d'un milestone (`?milestone=`), page rendue ou JSON markdown |
|
||
| POST | `/api/issues/{o}/{r}/{n}/assign-self` | S'assigner une issue (utilisateur du jeton) |
|
||
| GET | `/digest` | Digest hebdo : aperçu, statut des cibles, approbation d'envoi |
|
||
| POST | `/digest/send` | Livrer le digest (cible `obsigate` ou `webhook`) — approbation explicite |
|
||
| POST | `/api/capture` | Capture machine-à-machine (jeton `X-Capture-Token`, JSON ou texte : 1re ligne = titre) |
|
||
| GET | `/activity` | Fil d'activité (journal d'événements, tous projets) |
|
||
| POST | `/api/move` | Déplacer une issue (ordre persisté, synchro Gitea) |
|
||
| POST | `/api/issues` | Créer une issue Gitea depuis le board |
|
||
| GET | `/api/issues/{owner}/{repo}/{n}` | Détail d'une issue + commentaires (panneau) |
|
||
| POST | `/api/issues/{owner}/{repo}/{n}/comments` | Commenter l'issue dans Gitea |
|
||
| GET | `/api/events` | Derniers événements du journal (`?repo=`, `?limit=`) |
|
||
| GET | `/api/metrics` | Débit + temps de cycle moyen (`?repo=`, `?window=`) |
|
||
| POST | `/webhooks/gitea` | Réception des webhooks Gitea (HMAC si secret défini) |
|
||
| GET | `/api/stream` | Flux SSE des événements Gitea (rafraîchissement live) |
|
||
| GET | `/board/{o}/{r}?milestone=` | Board filtré par milestone (titre ou id) |
|
||
| GET | `/search?q=` | Recherche globale d'issues (tous projets, tous états) |
|
||
| GET | `/my` | Issues ouvertes assignées à l'utilisateur du jeton |
|
||
| GET | `/briefing` | Briefing quotidien (page lisible + statut ntfy) |
|
||
| GET | `/api/briefing` | Briefing en JSON + texte formaté |
|
||
| POST | `/api/briefing/send` | Envoyer le briefing sur ntfy maintenant |
|
||
| GET/POST | `/mcp` | Serveur MCP lecture seule (JSON-RPC 2.0) |
|
||
| GET/POST | `/api/repos/{o}/{r}/labels` | Lister / créer les étiquettes du dépôt |
|
||
| DELETE | `/api/repos/{o}/{r}/labels/{id}` | Supprimer une étiquette du dépôt |
|
||
| POST | `/api/issues/{o}/{r}/{n}/labels` | Ajouter / retirer une étiquette sur une issue |
|
||
|
||
## Brancher un client MCP
|
||
|
||
Kanbante expose un serveur MCP **en lecture seule** (aucune écriture :
|
||
les lectures d'abord). Configuration type (transport HTTP) :
|
||
|
||
```json
|
||
{
|
||
"mcpServers": {
|
||
"kanbante": {
|
||
"url": "https://<votre-hôte-kanbante>/mcp"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Outils : `list_projects`, `get_board`, `get_issue`, `get_focus`,
|
||
`search_issues`, `get_activity`, `get_metrics`. Si la Basic Auth
|
||
Kanbante est active, le client doit envoyer l'en-tête
|
||
`Authorization` correspondant.
|
||
|
||
## Activer le temps réel (webhook Gitea)
|
||
|
||
Dans chaque dépôt (ou au niveau d'une organisation) : **Réglages →
|
||
Webhooks → Ajouter un webhook → Gitea**, avec :
|
||
|
||
- **URL cible** : `https://<votre-hôte-kanbante>/webhooks/gitea`
|
||
- **Type de contenu** : `application/json`
|
||
- **Secret** : la même valeur que `KANBANTE_WEBHOOK_SECRET`
|
||
- **Déclencheur** : « Événements d'issue » (et « Commentaire d'issue »
|
||
si vous voulez aussi le rafraîchissement sur commentaires externes)
|
||
|
||
Dès lors, fermer ou rouvrir une issue dans Gitea met à jour les boards
|
||
Kanbante ouverts, sans rechargement manuel.
|
||
| POST | `/api/notes` | Sauvegarder des notes |
|
||
| GET | `/api/stats` | Statistiques JSON |
|
||
| GET | `/api/projects` | Projets JSON (actifs / inactifs / archivés) |
|
||
| GET | `/api/health` | Liveness (aucune dépendance) |
|
||
| GET | `/api/readiness` | Readiness : teste SQLite + Gitea (503 si KO) |
|
||
|
||
Les POST répondent avec des codes explicites : `400` entrée invalide,
|
||
`403` jeton CSRF manquant/invalide, `413` note trop longue, `502` Gitea
|
||
injoignable.
|
||
|
||
## Tests
|
||
|
||
```bash
|
||
pip install -e ".[dev]"
|
||
pytest
|
||
```
|
||
|
||
La CI (`.gitea/workflows/ci.yml`) lance la même suite à chaque push.
|
||
|
||
## Licence
|
||
|
||
MIT — voir [LICENSE](LICENSE). Historique des versions :
|
||
[CHANGELOG.md](CHANGELOG.md).
|