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

# 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_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) :

{
  "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.

S
Description
📋 Kanban project manager integrated with Gitea — lightweight, self-hosted, dark theme
Readme MIT
1.4 MiB
Languages
Python 57.7%
HTML 17.1%
JavaScript 14.4%
CSS 10.6%
Dockerfile 0.2%