refactor: noms publics dracodev par configuration (dev/prod)
CI / test (push) Successful in 2m22s

This commit is contained in:
Spark
2026-10-25 18:02:21 +00:00
parent 7852d860fe
commit 4a97e0d0dc
13 changed files with 236 additions and 20 deletions
+27
View File
@@ -0,0 +1,27 @@
# Kanbante — exemple d'environnement (copier en .env, jamais versionné)
# Infrastructure (un seul Gitea pour dev et prod, hors renommage)
GITEA_URL=https://git.dracodev.net
GITEA_TOKEN=
# Authentification locale
KANBANTE_AUTH_USER=
KANBANTE_AUTH_PASSWORD=
KANBANTE_REQUIRE_AUTH=0
# Noms publics dracodev — défauts = noms OFFICIELS (prod).
# Environnement dev : déclarer ici les noms en -dev, sans toucher au code.
KANBANTE_PUBLIC_URL=https://kanbante.dracodev.net
DRACO_SSO_URL=https://og.dracodev.net
DRACO_FLOWDECK_URL=https://flowdeck.dracodev.net
DRACO_TIKTAK_URL=https://tiktak.dracodev.net
AGENT_URL=https://agent.dracodev.net
# Exemples dev (à décommenter sur l'hôte dev) :
# KANBANTE_PUBLIC_URL=https://kanbante-dev.dracodev.net
# DRACO_SSO_URL=https://og-dev.dracodev.net
# DRACO_FLOWDECK_URL=https://flowdeck-dev.dracodev.net
# DRACO_TIKTAK_URL=https://tiktak-dev.dracodev.net
# AGENT_URL=https://agent-dev.dracodev.net
# Journal d'événements du service d'agents (signé HMAC)
# AGENT_EVENEMENTS_URL= # vide = dérivé de AGENT_URL (/api/v1/evenements)
AGENT_EVENEMENTS_CLE=
# Pont SSO draco_session (émis par ObsiGate ; vide = inerte)
DRACO_SESSION_SECRET=
DRACO_SSO_MACHINE_KEY=
+36
View File
@@ -1,5 +1,41 @@
# Changelog # Changelog
## 0.32.0 — 2026-10-25
### Refactor — noms publics dracodev par configuration (dev/prod)
Aucun nom public dracodev n'est plus codé en dur dans le code
vivant (gabarits, JS statique) : tout passe par `app/config.py`,
dont les défauts sont les noms **officiels** (prod). L'environnement
dev déclare ses noms en `-dev` dans son `.env`, sans toucher au code.
- **Nouvelles variables** : `KANBANTE_PUBLIC_URL` (propre URL
publique : lien de la barre + `return_to` SSO), `DRACO_FLOWDECK_URL`
et `DRACO_TIKTAK_URL` (liens de la barre commune ; le lien ObsiGate
réutilise `DRACO_SSO_URL`, déjà existant), `AGENT_URL` (panneau
Spark/console). `AGENT_EVENEMENTS_URL` se dérive désormais de
`AGENT_URL` quand il n'est pas posé explicitement (l'override
compose existant garde la priorité).
- **Barre commune** (`_draco_bar.html`) : les quatre liens d'apps
viennent de `draco_urls` injecté par `render()` ; le panneau
reçoit l'adresse du service via `window.DRACO_PANNEAU_CONFIG`
injecté avant `draco-panneau.js` (chargé avec le cache-busting
existant `?v={{ version }}`). `draco-panneau.js` ne contient plus
aucun nom : sans injection, il se déclare non configuré.
- **`login.html`** : le lien « Se connecter avec ObsiGate (SSO) »
est construit depuis la configuration (`sso_authorize_url`).
Note : ce lien SSO livré de longue date (Fusion #27) contredit le
document d'installation dev affirmant « pas de SSO par conception »
pour Kanbante — comportement existant conservé tel quel, juste
rendu configurable ; la contradiction documentaire est signalée,
pas tranchée ici.
- `git.dracodev.net` inchangé : infrastructure (un seul Gitea,
`GITEA_URL` / `KANBANTE_GITEA_URL` côté sync), hors renommage dev/prod.
- Tests barre/SSO mis en cohérence via la configuration (défauts
officiels + variantes `-dev`), `.env.example` créé, README et
`docker-compose.yml` documentent chaque variable touchée.
## 0.31.1 — 2026-10-23 ## 0.31.1 — 2026-10-23
### Corrigé — compositeur de création refait ### Corrigé — compositeur de création refait
+6
View File
@@ -111,6 +111,12 @@ sudo systemctl enable --now docker
|---|---|---| |---|---|---|
| `GITEA_URL` | `https://git.dracodev.net` | Instance Gitea | | `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 | | `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_PUBLIC_URL` | `https://kanbante.dracodev.net` | Propre URL publique de Kanbante (lien de la barre commune, `return_to` du lien SSO) — noms publics par configuration : défauts = noms officiels (prod), la dev déclare ses noms en `-dev` (ex. `https://kanbante-dev.dracodev.net`) |
| `DRACO_SSO_URL` | `https://og.dracodev.net` | Base d'ObsiGate, émettrice SSO (lien de barre ObsiGate, révocations, lien de connexion SSO) |
| `DRACO_FLOWDECK_URL` / `DRACO_TIKTAK_URL` | `https://flowdeck.dracodev.net` / `https://tiktak.dracodev.net` | Liens Flowdeck / TikTak de la barre commune |
| `AGENT_URL` | `https://agent.dracodev.net` | Base du service d'agents (panneau Spark + console, adresse injectée au JS par l'hôte) |
| `AGENT_EVENEMENTS_URL` | *(dérivé de `AGENT_URL`)* | Journal d'événements du service d'agents ; ne se pose que pour surcharger la dérivation `<AGENT_URL>/api/v1/evenements` |
| `AGENT_EVENEMENTS_CLE` | *(vide)* | Clé HMAC signant les événements émis vers ce journal ; vide = émission désactivée |
| `KANBANTE_DB_PATH` | `~/.kanbante/kanbante.db` | Base SQLite locale (en Docker : `/data/kanbante.db`) | | `KANBANTE_DB_PATH` | `~/.kanbante/kanbante.db` | Base SQLite locale (en Docker : `/data/kanbante.db`) |
| `KANBANTE_AUTH_USER` / `KANBANTE_AUTH_PASSWORD` | *(vide)* | Active l'authentification dès que les deux sont définis : écran de connexion + session signée pour les navigateurs ; l'en-tête Basic reste accepté pour les scripts | | `KANBANTE_AUTH_USER` / `KANBANTE_AUTH_PASSWORD` | *(vide)* | Active l'authentification dès que les deux sont définis : écran de connexion + session signée pour les navigateurs ; l'en-tête Basic reste accepté pour les scripts |
| `KANBANTE_REQUIRE_AUTH` | `0` | `1` = l'app refuse de servir (503) si l'auth n'est pas configurée | | `KANBANTE_REQUIRE_AUTH` | `0` | `1` = l'app refuse de servir (503) si l'auth n'est pas configurée |
+44 -5
View File
@@ -96,10 +96,32 @@ Environment variables:
KANBANTE_ZOMBIE_DAYS Days without any activity after KANBANTE_ZOMBIE_DAYS Days without any activity after
which an open card is a "zombie" which an open card is a "zombie"
on /health (issue #9). Default 30. on /health (issue #9). Default 30.
AGENT_URL Base URL of the agent service
(panneau Spark + console). Default
(official/prod name)
https://agent.dracodev.net — the dev
environment declares its ``-dev`` name
here (e.g. https://agent-dev.dracodev.net).
AGENT_EVENEMENTS_URL Journal endpoint of the agent AGENT_EVENEMENTS_URL Journal endpoint of the agent
service (Lot 2, contract ACC-0.2) service (Lot 2, contract ACC-0.2)
receiving this app's signed events. receiving this app's signed events.
Default https://agent.dracodev.net/api/v1/evenements. Default: derived from AGENT_URL
(``<AGENT_URL>/api/v1/evenements``).
KANBANTE_PUBLIC_URL This app's own public base URL
(barre commune self-link, SSO
``return_to``). Default (official)
https://kanbante.dracodev.net.
DRACO_FLOWDECK_URL Public base URL of Flowdeck for the
common app bar. Default (official)
https://flowdeck.dracodev.net.
DRACO_TIKTAK_URL Public base URL of TikTak for the
common app bar. Default (official)
https://tiktak.dracodev.net.
ObsiGate's bar link reuses DRACO_SSO_URL
(the SSO emitter IS ObsiGate).
No public dracodev name is hard-coded
outside these defaults: dev/prod differ
only by environment, never by code.
AGENT_EVENEMENTS_CLE Kanbante's own HMAC key signing the AGENT_EVENEMENTS_CLE Kanbante's own HMAC key signing the
events it emits to that journal (the events it emits to that journal (the
service knows it as the "kanbante" service knows it as the "kanbante"
@@ -113,7 +135,7 @@ from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
APP_VERSION = "0.31.1" APP_VERSION = "0.32.0"
def _env_bool(name: str, default: bool) -> bool: def _env_bool(name: str, default: bool) -> bool:
@@ -188,6 +210,10 @@ class Settings:
draco_secret_prev: str = "" draco_secret_prev: str = ""
sso_machine_key: str = "" sso_machine_key: str = ""
sso_url: str = "https://og.dracodev.net" sso_url: str = "https://og.dracodev.net"
public_url: str = "https://kanbante.dracodev.net"
flowdeck_url: str = "https://flowdeck.dracodev.net"
tiktak_url: str = "https://tiktak.dracodev.net"
agent_url: str = "https://agent.dracodev.net"
archive_suggest_days: int = 180 archive_suggest_days: int = 180
agent_evenements_url: str = "https://agent.dracodev.net/api/v1/evenements" agent_evenements_url: str = "https://agent.dracodev.net/api/v1/evenements"
agent_evenements_cle: str = "" agent_evenements_cle: str = ""
@@ -205,6 +231,9 @@ def get_settings() -> Settings:
def _env_settings() -> Settings: def _env_settings() -> Settings:
"""Build settings from the current environment only.""" """Build settings from the current environment only."""
agent_url = os.getenv(
"AGENT_URL", "https://agent.dracodev.net"
).rstrip("/")
return Settings( return Settings(
gitea_url=os.getenv("GITEA_URL", "https://git.dracodev.net").rstrip("/"), gitea_url=os.getenv("GITEA_URL", "https://git.dracodev.net").rstrip("/"),
gitea_token=os.getenv("GITEA_TOKEN", ""), gitea_token=os.getenv("GITEA_TOKEN", ""),
@@ -248,10 +277,20 @@ def _env_settings() -> Settings:
draco_secret_prev=os.getenv("DRACO_SESSION_SECRET_PREV", ""), draco_secret_prev=os.getenv("DRACO_SESSION_SECRET_PREV", ""),
sso_machine_key=os.getenv("DRACO_SSO_MACHINE_KEY", ""), sso_machine_key=os.getenv("DRACO_SSO_MACHINE_KEY", ""),
sso_url=os.getenv("DRACO_SSO_URL", "https://og.dracodev.net").rstrip("/"), sso_url=os.getenv("DRACO_SSO_URL", "https://og.dracodev.net").rstrip("/"),
public_url=os.getenv(
"KANBANTE_PUBLIC_URL", "https://kanbante.dracodev.net"
).rstrip("/"),
flowdeck_url=os.getenv(
"DRACO_FLOWDECK_URL", "https://flowdeck.dracodev.net"
).rstrip("/"),
tiktak_url=os.getenv(
"DRACO_TIKTAK_URL", "https://tiktak.dracodev.net"
).rstrip("/"),
agent_url=agent_url,
archive_suggest_days=_env_int("KANBANTE_ARCHIVE_SUGGEST_DAYS", 180), archive_suggest_days=_env_int("KANBANTE_ARCHIVE_SUGGEST_DAYS", 180),
agent_evenements_url=os.getenv( agent_evenements_url=(
"AGENT_EVENEMENTS_URL", os.getenv("AGENT_EVENEMENTS_URL")
"https://agent.dracodev.net/api/v1/evenements", or f"{agent_url}/api/v1/evenements"
).rstrip("/"), ).rstrip("/"),
agent_evenements_cle=os.getenv("AGENT_EVENEMENTS_CLE", ""), agent_evenements_cle=os.getenv("AGENT_EVENEMENTS_CLE", ""),
) )
+3 -1
View File
@@ -17,6 +17,8 @@ import logging
import httpx import httpx
from app.config import APP_VERSION
logger = logging.getLogger("kanbante.digest") logger = logging.getLogger("kanbante.digest")
MCP_PROTOCOL_VERSION = "2024-11-05" MCP_PROTOCOL_VERSION = "2024-11-05"
@@ -143,7 +145,7 @@ async def _mcp_session(client: httpx.AsyncClient, url: str, token: str) -> None:
{ {
"protocolVersion": MCP_PROTOCOL_VERSION, "protocolVersion": MCP_PROTOCOL_VERSION,
"capabilities": {}, "capabilities": {},
"clientInfo": {"name": "kanbante", "version": "0.31.1"}, "clientInfo": {"name": "kanbante", "version": APP_VERSION},
}, },
req_id=1, req_id=1,
) )
+20
View File
@@ -2145,6 +2145,26 @@ def render(
token = secrets.token_urlsafe(32) token = secrets.token_urlsafe(32)
context.setdefault("csrf_token", token) context.setdefault("csrf_token", token)
context.setdefault("version", APP_VERSION) context.setdefault("version", APP_VERSION)
# Noms publics dracodev par configuration (dev/prod, 0.32.0) :
# aucun nom public n'est codé en dur dans les gabarits ni dans le
# JS statique — tout vient de app.config (défauts = noms
# officiels/prod ; la dev déclare ses noms en ``-dev`` par .env).
_pub = get_settings()
context.setdefault(
"draco_urls",
{
"obsigate": _pub.sso_url,
"flowdeck": _pub.flowdeck_url,
"tiktak": _pub.tiktak_url,
"kanbante": _pub.public_url,
},
)
context.setdefault("agent_url", _pub.agent_url)
context.setdefault(
"sso_authorize_url",
f"{_pub.sso_url}/sso/authorize?return_to="
f"{quote(_pub.public_url + '/auth/draco', safe='')}",
)
# Fusion ACC-1 : initiale de l'opérateur pour la barre d'apps commune. # Fusion ACC-1 : initiale de l'opérateur pour la barre d'apps commune.
context.setdefault( context.setdefault(
"draco_avatar", (get_settings().auth_user or "K")[:1].upper() "draco_avatar", (get_settings().auth_user or "K")[:1].upper()
+11 -7
View File
@@ -1,7 +1,10 @@
{# Barre d'apps commune dracodev — Fusion ACC-1 (issue kanbante#28). {# Barre d'apps commune dracodev — Fusion ACC-1 (issue kanbante#28).
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.4 Snippet vendangé depuis Projets/dracodev-commons v0.1.4
(ui/barre-apps/) : ne pas modifier la structure ici ; toute évolution (ui/barre-apps/) : structure inchangée ; seule adaptation locale
passe par une nouvelle version de commons. (0.32.0, dev/prod) = les URL des apps et l'adresse du service
d'agents sont INJECTÉES par l'hôte depuis app.config
(draco_urls / agent_url, défauts = noms officiels) — aucun nom
public codé en dur dans ce gabarit ni dans draco-panneau.js.
CSS/JS : /static/css/draco-barre.css, /static/js/draco-barre.js. CSS/JS : /static/css/draco-barre.css, /static/js/draco-barre.js.
Inclus par _header.html (toutes les pages applicatives ; ni le Inclus par _header.html (toutes les pages applicatives ; ni le
wallboard ni l'écran de connexion, volontaires). #} wallboard ni l'écran de connexion, volontaires). #}
@@ -15,20 +18,20 @@
<span class="draco-brand-mark">✦</span><span class="draco-brand-name">dracodev</span> <span class="draco-brand-mark">✦</span><span class="draco-brand-name">dracodev</span>
</a> </a>
<nav class="draco-apptabs" aria-label="Applications"> <nav class="draco-apptabs" aria-label="Applications">
<a class="draco-apptab" data-app="obsigate" href="https://og.dracodev.net/"> <a class="draco-apptab" data-app="obsigate" href="{{ draco_urls.obsigate }}/">
<span class="draco-apptab-ico" style="--app:#7a5af5">▤</span> <span class="draco-apptab-ico" style="--app:#7a5af5">▤</span>
<span class="draco-apptab-txt"><strong>ObsiGate</strong><small>Notes</small></span> <span class="draco-apptab-txt"><strong>ObsiGate</strong><small>Notes</small></span>
</a> </a>
<a class="draco-apptab" data-app="flowdeck" href="https://flowdeck.dracodev.net/"> <a class="draco-apptab" data-app="flowdeck" href="{{ draco_urls.flowdeck }}/">
<span class="draco-apptab-ico" style="--app:#2f6fed">▦</span> <span class="draco-apptab-ico" style="--app:#2f6fed">▦</span>
<span class="draco-apptab-txt"><strong>Flowdeck</strong><small>Projets</small></span> <span class="draco-apptab-txt"><strong>Flowdeck</strong><small>Projets</small></span>
</a> </a>
<a class="draco-apptab" data-app="tiktak" href="https://tiktak.dracodev.net/"> <a class="draco-apptab" data-app="tiktak" href="{{ draco_urls.tiktak }}/">
<span class="draco-apptab-ico" style="--app:#e8762c">✓</span> <span class="draco-apptab-ico" style="--app:#e8762c">✓</span>
<span class="draco-apptab-txt"><strong>TikTak</strong><small>Tâches</small></span> <span class="draco-apptab-txt"><strong>TikTak</strong><small>Tâches</small></span>
<span class="draco-badge" data-badge="tiktak" hidden>0</span> <span class="draco-badge" data-badge="tiktak" hidden>0</span>
</a> </a>
<a class="draco-apptab" data-app="kanbante" href="https://kanbante.dracodev.net/"> <a class="draco-apptab" data-app="kanbante" href="{{ draco_urls.kanbante }}/">
<span class="draco-apptab-ico" style="--app:#0d9488">▥</span> <span class="draco-apptab-ico" style="--app:#0d9488">▥</span>
<span class="draco-apptab-txt"><strong>Kanbante</strong><small>Kanban</small></span> <span class="draco-apptab-txt"><strong>Kanbante</strong><small>Kanban</small></span>
<span class="draco-badge" data-badge="kanbante" hidden>0</span> <span class="draco-badge" data-badge="kanbante" hidden>0</span>
@@ -101,4 +104,5 @@
}; };
</script> </script>
<script defer src="/static/js/draco-barre.js?v={{ version }}"></script> <script defer src="/static/js/draco-barre.js?v={{ version }}"></script>
<script>window.DRACO_PANNEAU_CONFIG = { api: "{{ agent_url }}" };</script>
<script defer src="/static/js/draco-panneau.js?v={{ version }}"></script> <script defer src="/static/js/draco-panneau.js?v={{ version }}"></script>
+1 -1
View File
@@ -29,7 +29,7 @@
</label> </label>
<button type="submit" class="login-submit">Se connecter</button> <button type="submit" class="login-submit">Se connecter</button>
</form> </form>
<a class="login-sso" href="https://og.dracodev.net/sso/authorize?return_to=https%3A%2F%2Fkanbante.dracodev.net%2Fauth%2Fdraco">🧭 Se connecter avec ObsiGate (SSO)</a> <a class="login-sso" href="{{ sso_authorize_url }}">🧭 Se connecter avec ObsiGate (SSO)</a>
<p class="login-foot">Accès restreint — session de {{ 7 }} jours, déconnexion depuis le menu latéral.</p> <p class="login-foot">Accès restreint — session de {{ 7 }} jours, déconnexion depuis le menu latéral.</p>
</section> </section>
</main> </main>
+9
View File
@@ -6,6 +6,15 @@ services:
- "8080:8080" - "8080:8080"
environment: environment:
- GITEA_URL=${GITEA_URL:-https://git.dracodev.net} - GITEA_URL=${GITEA_URL:-https://git.dracodev.net}
# Noms publics dracodev (dev/prod) : défauts = noms officiels ;
# l'environnement dev déclare ses noms en -dev dans son .env.
- KANBANTE_PUBLIC_URL=${KANBANTE_PUBLIC_URL:-https://kanbante.dracodev.net}
- DRACO_SSO_URL=${DRACO_SSO_URL:-https://og.dracodev.net}
- DRACO_FLOWDECK_URL=${DRACO_FLOWDECK_URL:-https://flowdeck.dracodev.net}
- DRACO_TIKTAK_URL=${DRACO_TIKTAK_URL:-https://tiktak.dracodev.net}
- AGENT_URL=${AGENT_URL:-https://agent.dracodev.net}
- AGENT_EVENEMENTS_URL=${AGENT_EVENEMENTS_URL:-}
- AGENT_EVENEMENTS_CLE=${AGENT_EVENEMENTS_CLE:-}
- GITEA_TOKEN=${GITEA_TOKEN} - GITEA_TOKEN=${GITEA_TOKEN}
- KANBANTE_DB_PATH=/data/kanbante.db - KANBANTE_DB_PATH=/data/kanbante.db
# Strongly recommended: without these, the app is unauthenticated. # Strongly recommended: without these, the app is unauthenticated.
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "kanbante" name = "kanbante"
version = "0.31.1" version = "0.32.0"
description = "Kanban project manager integrated with Gitea" description = "Kanban project manager integrated with Gitea"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
+14 -4
View File
@@ -19,7 +19,7 @@
* (persistée), thème clair/sombre aligné sur l'app hôte * (persistée), thème clair/sombre aligné sur l'app hôte
* (html[data-theme], repli prefers-color-scheme, suivi en direct). * (html[data-theme], repli prefers-color-scheme, suivi en direct).
* Bouton « Console » (barre des discussions) : ouvre la console web * Bouton « Console » (barre des discussions) : ouvre la console web
* du même service (agent.dracodev.net) DANS la page, en surimpression * du même service (adresse injectée par l'hôte) DANS la page, en surimpression
* pleine page (iframe) qui naît à la géométrie du panneau puis * pleine page (iframe) qui naît à la géométrie du panneau puis
* s'étend à toute la fenêtre ; l'URL profonde (?disc= discussion * s'étend à toute la fenêtre ; l'URL profonde (?disc= discussion
* courante, ?app= app d'origine déduite du sous-domaine) est celle * courante, ?app= app d'origine déduite du sous-domaine) est celle
@@ -32,7 +32,11 @@
* configuré / exécuteur non configuré sont affichés tels quels. */ * configuré / exécuteur non configuré sont affichés tels quels. */
(function () { (function () {
"use strict"; "use strict";
var API = "https://agent.dracodev.net"; /* Adresse du service : injectée par l'app hôte (gabarit
* _draco_bar.html, window.DRACO_PANNEAU_CONFIG.api, issu de la
* configuration de l'hôte — AGENT_URL). Aucun nom public en dur
* ici : sans injection, le panneau se déclare non configuré. */
var API = (window.DRACO_PANNEAU_CONFIG && window.DRACO_PANNEAU_CONFIG.api) || "";
var convId = null; var convId = null;
var convKind = null; // 'principale' | 'parallele' var convKind = null; // 'principale' | 'parallele'
var convTitre = ""; var convTitre = "";
@@ -251,7 +255,7 @@
' <span class="d-titre">Discussion principale</span>' + ' <span class="d-titre">Discussion principale</span>' +
' <button type="button" class="d-btn" data-disc="liste">☰ Discussions</button>' + ' <button type="button" class="d-btn" data-disc="liste">☰ Discussions</button>' +
' <button type="button" class="d-btn" data-disc="nouvelle">+ Nouvelle</button>' + ' <button type="button" class="d-btn" data-disc="nouvelle">+ Nouvelle</button>' +
' <button type="button" class="d-btn" data-disc="console" title="Ouvrir la console Spark (agent.dracodev.net) en pleine page par-dessus l\'application, sur cette discussion">Console ⤢</button>' + ' <button type="button" class="d-btn" data-disc="console" title="Ouvrir la console Spark en pleine page par-dessus l\'application, sur cette discussion">Console ⤢</button>' +
'</div>' + '</div>' +
'<div class="draco-discussions" hidden></div>' + '<div class="draco-discussions" hidden></div>' +
'<div class="draco-panneau-outils">' + '<div class="draco-panneau-outils">' +
@@ -328,6 +332,12 @@
function verifierIdentite() { function verifierIdentite() {
if (etatConnu) return; if (etatConnu) return;
if (!API) {
majEtat("err", "Panneau Spark non configuré");
ajouterMsg("sys", "Adresse du service d'agents absente : l'app hôte ne l'a pas injectée (DRACO_PANNEAU_CONFIG / AGENT_URL).");
etatConnu = true;
return;
}
fetch(API + "/api/v1/me", { credentials: "include" }) fetch(API + "/api/v1/me", { credentials: "include" })
.then(function (r) { .then(function (r) {
if (r.status === 401) { if (r.status === 401) {
@@ -346,7 +356,7 @@
}) })
.catch(function () { .catch(function () {
majEtat("err", "Service d'agents injoignable"); majEtat("err", "Service d'agents injoignable");
ajouterMsg("sys", "Le service d'agents (agent.dracodev.net) ne répond pas. Vérifie que le conteneur dracodev-agent est démarré."); ajouterMsg("sys", "Le service d'agents (" + API + ") ne répond pas. Vérifie que le conteneur dracodev-agent est démarré.");
}); });
} }
+51 -1
View File
@@ -15,10 +15,60 @@ def test_barre_presente_sur_le_dashboard(client):
assert "draco-barre.css" in r.text assert "draco-barre.css" in r.text
assert "draco-barre.js" in r.text assert "draco-barre.js" in r.text
assert 'window.DRACO_BAR_CONFIG' in r.text assert 'window.DRACO_BAR_CONFIG' in r.text
# Les quatre applications de la barre, aux URL publiques. # Les quatre applications de la barre, aux URL publiques par
# défaut (noms officiels/prod, issus de la configuration).
for url in ("https://og.dracodev.net/", "https://flowdeck.dracodev.net/", for url in ("https://og.dracodev.net/", "https://flowdeck.dracodev.net/",
"https://tiktak.dracodev.net/", "https://kanbante.dracodev.net/"): "https://tiktak.dracodev.net/", "https://kanbante.dracodev.net/"):
assert url in r.text assert url in r.text
# L'adresse du service d'agents est injectée au panneau.
assert 'window.DRACO_PANNEAU_CONFIG = { api: "https://agent.dracodev.net" }' in r.text
def test_barre_et_panneau_urls_par_configuration(monkeypatch, fake_gitea):
# Dev/prod (0.32.0) : la dev déclare ses noms en -dev par
# l'environnement ; aucun nom public n'est codé en dur.
from fastapi.testclient import TestClient
import app.main as main
monkeypatch.setenv("DRACO_SSO_URL", "https://og-dev.dracodev.net")
monkeypatch.setenv("DRACO_FLOWDECK_URL", "https://flowdeck-dev.dracodev.net")
monkeypatch.setenv("DRACO_TIKTAK_URL", "https://tiktak-dev.dracodev.net")
monkeypatch.setenv("KANBANTE_PUBLIC_URL", "https://kanbante-dev.dracodev.net")
monkeypatch.setenv("AGENT_URL", "https://agent-dev.dracodev.net")
with TestClient(main.app) as client:
r = client.get("/")
assert r.status_code == 200
for url in ("https://og-dev.dracodev.net/",
"https://flowdeck-dev.dracodev.net/",
"https://tiktak-dev.dracodev.net/",
"https://kanbante-dev.dracodev.net/"):
assert url in r.text
assert 'api: "https://agent-dev.dracodev.net"' in r.text
# Le JS statique du panneau ne contient aucun nom en dur :
# il lit l'adresse injectée par l'hôte.
js = client.get("/static/js/draco-panneau.js").text
assert "DRACO_PANNEAU_CONFIG" in js
assert "dracodev.net" not in js
def test_config_agent_evenements_derive_de_agent_url(monkeypatch):
from app.config import _env_settings
monkeypatch.setenv("AGENT_URL", "https://agent-dev.dracodev.net")
monkeypatch.delenv("AGENT_EVENEMENTS_URL", raising=False)
s = _env_settings()
assert s.agent_url == "https://agent-dev.dracodev.net"
assert s.agent_evenements_url == (
"https://agent-dev.dracodev.net/api/v1/evenements"
)
# L'URL d'événements explicite garde la priorité (override compose).
monkeypatch.setenv(
"AGENT_EVENEMENTS_URL", "https://exemple.invalid/journal"
)
assert _env_settings().agent_evenements_url == (
"https://exemple.invalid/journal"
)
def test_statiques_de_la_barre_servis(client): def test_statiques_de_la_barre_servis(client):
+13
View File
@@ -97,7 +97,20 @@ def test_pont_inerte_sans_secret(monkeypatch, fake_gitea):
def test_bouton_sso_sur_la_page_login(client_sso): def test_bouton_sso_sur_la_page_login(client_sso):
# Défauts = noms officiels (DRACO_SSO_URL / KANBANTE_PUBLIC_URL).
r = client_sso.get("/login") r = client_sso.get("/login")
assert r.status_code == 200 assert r.status_code == 200
assert "og.dracodev.net/sso/authorize" in r.text assert "og.dracodev.net/sso/authorize" in r.text
assert "kanbante.dracodev.net%2Fauth%2Fdraco" in r.text assert "kanbante.dracodev.net%2Fauth%2Fdraco" in r.text
def test_bouton_sso_par_configuration(client_sso, monkeypatch):
# Dev : l'émettrice et le return_to suivent l'environnement.
monkeypatch.setenv("DRACO_SSO_URL", "https://og-dev.dracodev.net")
monkeypatch.setenv(
"KANBANTE_PUBLIC_URL", "https://kanbante-dev.dracodev.net"
)
r = client_sso.get("/login")
assert r.status_code == 200
assert "og-dev.dracodev.net/sso/authorize" in r.text
assert "kanbante-dev.dracodev.net%2Fauth%2Fdraco" in r.text