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/depotfige 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 - 🧬 Code lié — branches, PRs et commits référant
#nsur la carte (badge⑂) et dans le panneau détail (section « Code lié ») - 🔀 PR fusionnée → Terminé — le webhook
pull_requestretrouve les issues liées (références#n, brancheissue-n-*), les ferme et déplace leurs cartes, journalisé - 🗣️ Standup automatique —
/standup: fermé/déplacé hier, focus du jour, blocages (QA/Révision qui vieillissent), copiable d'un clic ; ajout optionnel au briefing (KANBANTE_BRIEFING_INCLUDE_STANDUP=1) - ⚙️ Règles d'automatisation — moteur déclaratif
KANBANTE_RULES(JSON) évalué toutes les 15 min : conditions colonne/âge/étiquette/WIP → étiquette, notification ntfy, tête de colonne ; jamais de fermeture par règle ; page/rules - 🌿 Branche depuis la carte — le panneau détail crée
issue-<n>-<slug>dans Gitea depuis la branche par défaut, déplace la carte en « En cours » et journalise ; la PR qui en naîtra sera reconnue automatiquement - 🩺 Santé du flux —
/health: temps médian par colonne (du journal), colonne la plus lente, et cartes zombies (aucune activité depuisKANBANTE_ZOMBIE_DAYS) avec actions rapides (commenter, remettre au backlog, fermer) - 📥 Mode Triage —
/triage: le backlog se vide carte par carte, plein cadre, au clavier (1-5colonnes,Létiquettes,As'assigner,Xfermer,Entréesuivante,Zannuler), 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), endpointPOST /api/captureproté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 (sourcegitea). Une seule étiquettekanban:*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
/briefinget envoi ntfy planifié - 🤖 Serveur MCP — 7 outils en lecture seule (boards, focus, recherche, métriques) exposés en JSON-RPC sur
/mcppour 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
# 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é)
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
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)
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_BRIEFING_INCLUDE_STANDUP |
0 |
1 = le standup automatique est ajouté au briefing ntfy du matin |
KANBANTE_RULES |
(vide) | Règles d'automatisation en JSON (voir /rules) : conditions colonne/âge/étiquette/WIP → add_label / notify / move_top |
KANBANTE_ZOMBIE_DAYS |
30 |
Jours sans activité après lesquels une carte ouverte est « zombie » sur /health |
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 poseKANBANTE_REQUIRE_AUTH=1pour é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-Tokenou champcsrf_token). - Aucun secret n'est versionné : le jeton passe uniquement par
l'environnement / le fichier
.envlocal (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 |
| GET | /api/issues/{o}/{r}/{n}/code |
Branches, PRs et commits liés à la carte |
| GET | /standup · /api/standup |
Standup automatique (hier / aujourd'hui / blocages) |
| GET | /rules · POST /api/rules/run |
Règles d'automatisation : état, dernière évaluation, lancement manuel |
| POST | /api/issues/{o}/{r}/{n}/branch |
Créer la branche de la carte (issue-<n>-<slug> ou nom libre validé) et la passer en « En cours » |
| GET | /health · /health/{o}/{r} |
Santé du flux : goulots (temps médian par colonne) et cartes zombies, global ou par projet |
| 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) :
{
"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
pip install -e ".[dev]"
pytest
La CI (.gitea/workflows/ci.yml) lance la même suite à chaque push.
Licence
MIT — voir LICENSE. Historique des versions : CHANGELOG.md.