Gitea 1.27.3 renvoie "pull_request": null sur les issues ordinaires: _is_issue testait la présence de la clé et classait chaque vraie issue comme un pull request. Board, Focus et briefing affichaient zéro carte contre un vrai Gitea (le dashboard, basé sur les compteurs de dépôts, semblait normal, d'où le signalement « la nouvelle issue n'apparaît pas »). Le filtre teste maintenant la valeur. Vérifié en réel: board bruno/flowdeck 0 -> 6 cartes.
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 → 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 révision, tous projets confondus (
/focus) - 🔍 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 |
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 + En révision, tous projets actifs |
| 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.