Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc5431308c | ||
|
|
7d31af8ec2 | ||
|
|
2e73802f5f | ||
|
|
46b876eea8 | ||
|
|
2431bb6059 | ||
|
|
ac2f0c8cfc | ||
|
|
58ad5d9164 | ||
|
|
91fad4eba3 | ||
|
|
7995dbe9d2 | ||
|
|
0e3e6ec197 | ||
|
|
d6ea617ed7 | ||
|
|
f7c2ea0d34 | ||
|
|
d16666be85 | ||
|
|
fb4ce38403 | ||
|
|
34253c7b3f | ||
|
|
4490a2ed70 |
@@ -1,3 +1,13 @@
|
||||
## v7.84.7 (2026-10-29) — Barre commune : les 5 logos dans le menu et la barre
|
||||
|
||||
- **Barre d'apps v0.1.9 vendangée (dracodev-commons)** : `static/js/vendor/draco-barre.js` et `static/css/draco-barre.css` remplacés verbatim par la v0.1.9 (les fichiers vendangés précédents étaient identiques à l'octet près à ceux de commons avant la v0.1.9, aucune adaptation locale n'est perdue) ; fragment `app/templates/_draco_bar.html` porté à la v0.1.9, le reste de son intégration Flowdeck (URL `draco_urls()`, `onAction`, nonce CSP) est inchangé.
|
||||
- **Icônes** : les 5 tuiles de la barre — et donc du menu de la marque, qui les clone — sont désormais les logos fournis, tels quels, jamais redessinés : ObsiGate (arbre), Flowdeck (cartes), TikTak (logo officiel SVG), Kanbante (cycle vert), Console Spark (étoile rouge validée). Fichiers servis depuis `static/img/logos/` (copie de `ui/barre-apps/logos/` de commons, PNG 180 px) ; l'entrée « Console Spark » du menu clone l'icône de l'onglet agent. Les glyphes texte ▤/▦/✓/▥/✦ disparaissent des onglets (la marque ✦ et le bouton Spark gardent leur ✦).
|
||||
- **Cache** : le bump 7.84.6 → 7.84.7 change aussi `asset_version`, donc les navigateurs ne garderont pas les anciennes icônes.
|
||||
|
||||
## v7.84.6 (2026-10-28) — Recherche console : CORS Flowdeck sans doublons (PR #41)
|
||||
|
||||
- **CORS recherche (PR #41)** : le middleware de recherche remplace désormais les en-têtes CORS existants au lieu de les empiler — le CORSMiddleware global posait déjà Allow-Credentials: true, l'ancien middleware ajoutait les siens par-dessus (« true, true » rejeté par le navigateur) ; l'onglet Flowdeck de la recherche console agent-dev n'est plus injoignable.
|
||||
|
||||
## v7.84.5 (2026-10-28) — Corrections console/recherche : texte parasite, onglets, CORS console
|
||||
|
||||
- **Barre (PR #37)** : suppression du texte parasite « Chantier 5 » rendu au-dessus du header.
|
||||
|
||||
@@ -193,7 +193,32 @@ class RechercheCorsMiddleware:
|
||||
if message["type"] == "http.response.start":
|
||||
cors = cors_headers_for(origin, request_host=request_host)
|
||||
if cors is not None:
|
||||
message["headers"] = list(message.get("headers", [])) + [
|
||||
# Remplacer, pas empiler (défaut 3b, 2026-10-28) :
|
||||
# le CORSMiddleware global de l'application pose
|
||||
# déjà Access-Control-Allow-Credentials: true sur
|
||||
# TOUTES les réponses (comportement Starlette avec
|
||||
# allow_credentials=True, même pour une origine
|
||||
# hors allowlist et hors /api/search) ; empiler les
|
||||
# en-têtes de recherche donnait, sur /api/search
|
||||
# depuis agent-dev, Allow-Credentials en double
|
||||
# (« true, true » ≠ « true » pour le contrôle CORS
|
||||
# du navigateur → fetch rejeté → « injoignable
|
||||
# (CORS ou réseau) ») et, depuis une sœur
|
||||
# (origine déjà dans la allowlist globale),
|
||||
# Allow-Origin en double (rejet certain). Les
|
||||
# hôtes TikTak/Kanbante, sans ce cumul, répondent
|
||||
# avec des en-têtes uniques — d'où l'échec
|
||||
# Flowdeck seul. Une origine non autorisée
|
||||
# traverse sans modification (comportement
|
||||
# antérieur préservé : le seul en-tête global
|
||||
# reste, sans Allow-Origin, donc bloqué).
|
||||
restants = [
|
||||
(k, v) for k, v in message.get("headers", [])
|
||||
if k.decode("latin-1").lower() not in (
|
||||
"access-control-allow-origin",
|
||||
"access-control-allow-credentials",
|
||||
"vary")]
|
||||
message["headers"] = restants + [
|
||||
(k.lower().encode("latin-1"), v.encode("latin-1"))
|
||||
for k, v in cors.items()
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{# Barre d'apps commune dracodev — Fusion ACC-1.1 (issue #17).
|
||||
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.6
|
||||
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.9
|
||||
(ui/barre-apps/) : ne pas modifier la structure ici ; toute évolution
|
||||
passe par une nouvelle version de commons.
|
||||
CSS/JS : /static/css/draco-barre.css, /static/js/vendor/draco-barre.js.
|
||||
@@ -8,7 +8,12 @@
|
||||
par défaut, noms en -dev déclarés dans le .env de l'environnement).
|
||||
5e onglet Console Spark (data-app="agent", draco_urls().agent),
|
||||
après Kanbante ; l'entrée « Console Spark » du menu de la marque
|
||||
est conservée (menu construit par draco-barre.js depuis les onglets). #}
|
||||
est conservée (menu construit par draco-barre.js depuis les onglets).
|
||||
Icônes logos v0.1.9 (2026-10-29) : les 5 tuiles sont les logos
|
||||
fournis, tels quels, servis depuis /static/img/logos/ (copie de
|
||||
ui/barre-apps/logos/ de dracodev-commons) — jamais de glyphe
|
||||
texte, jamais de redessin ; l'entrée « Console Spark » du menu
|
||||
clone l'icône de l'onglet agent (draco-barre.js). #}
|
||||
<link rel="stylesheet" href="/static/css/draco-alertes.css?v={{ asset_version }}">
|
||||
<header class="draco-bar" data-current="flowdeck">
|
||||
<div class="draco-bar-zone draco-bar-gauche">
|
||||
@@ -18,25 +23,25 @@
|
||||
</a>
|
||||
<nav class="draco-apptabs" aria-label="Applications">
|
||||
<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" data-icon="obsigate"><img src="/static/img/logos/obsigate.png" alt="ObsiGate" width="30" height="30"></span>
|
||||
<span class="draco-apptab-txt"><strong>ObsiGate</strong><small>Notes</small></span>
|
||||
</a>
|
||||
<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" data-icon="flowdeck"><img src="/static/img/logos/flowdeck.png" alt="Flowdeck" width="30" height="30"></span>
|
||||
<span class="draco-apptab-txt"><strong>Flowdeck</strong><small>Projets</small></span>
|
||||
</a>
|
||||
<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" data-icon="tiktak"><img src="/static/img/logos/tiktak.svg" alt="TikTak" width="30" height="30"></span>
|
||||
<span class="draco-apptab-txt"><strong>TikTak</strong><small>Tâches</small></span>
|
||||
<span class="draco-badge" data-badge="tiktak" hidden>0</span>
|
||||
</a>
|
||||
<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" data-icon="kanbante"><img src="/static/img/logos/kanbante.png" alt="Kanbante" width="30" height="30"></span>
|
||||
<span class="draco-apptab-txt"><strong>Kanbante</strong><small>Kanban</small></span>
|
||||
<span class="draco-badge" data-badge="kanbante" hidden>0</span>
|
||||
</a>
|
||||
<a class="draco-apptab" data-app="agent" href="{{ draco_urls().agent }}/">
|
||||
<span class="draco-apptab-ico" style="--app:#c2437e">✦</span>
|
||||
<span class="draco-apptab-ico" style="--app:#d21f2c" data-icon="agent"><img src="/static/img/logos/spark.png" alt="Console Spark" width="30" height="30"></span>
|
||||
<span class="draco-apptab-txt"><strong>Console Spark</strong><small>Agent commun</small></span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
@@ -94,6 +94,13 @@
|
||||
width: 30px; height: 30px; flex: none; display: grid; place-items: center;
|
||||
border-radius: 9px; background: var(--app); color: #fff; font-size: 15px;
|
||||
}
|
||||
/* Icônes logos (2026-10-29, corrigé le même jour) : l'image du logo
|
||||
* fourni, telle quelle, remplit la tuile 30 px — jamais de glyphe
|
||||
* texte, jamais de redessin. Le fond de la tuile est transparent :
|
||||
* les logos PNG portent déjà leur fond (dégradé), le SVG TikTak
|
||||
* reste sur le fond de la barre. */
|
||||
.draco-apptab-ico { background: transparent; }
|
||||
.draco-apptab-ico img { width: 30px; height: 30px; display: block; border-radius: 9px; object-fit: cover; }
|
||||
.draco-apptab-txt { display: none; flex-direction: column; line-height: 1.15; }
|
||||
.draco-apptab-txt strong { font-size: 14px; font-weight: 700; }
|
||||
.draco-apptab-txt small { font-size: 11.5px; color: var(--ink-2); }
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
||||
<!-- TikTak — deux arcs (les deux temps), coche-flèche. Bleu #0076fb, orange #fc750c.
|
||||
Géométrie mesurée sur le visuel validé par Bruno (variante B). -->
|
||||
<path d="M 38.2 101.1 A 84 84 0 0 1 201.8 101.1" fill="none" stroke="#0076fb" stroke-width="28" stroke-linecap="round"/>
|
||||
<path d="M 201.5 140.3 A 84 84 0 0 1 38.5 140.3" fill="none" stroke="#fc750c" stroke-width="28" stroke-linecap="round"/>
|
||||
<polyline points="80,129 106,146 163,84" fill="none" stroke="#0076fb" stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M 128 80 L 163 84 L 167 122" fill="none" stroke="#0076fb" stroke-width="22" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 760 B |
Vendored
+16
-5
@@ -369,7 +369,10 @@
|
||||
* de l'agent commun (URL agent résolue — explicite > -dev >
|
||||
* défaut prod ; aucun hôte spark-dev inventé). data-app="agent"
|
||||
* l'intègre à refreshMenuHrefs/applyUrls/setCurrent comme les
|
||||
* autres items ; l'icône ✦ reprend la couleur agent du CSS. */
|
||||
* autres items ; son icône CLONE celle de l'onglet agent
|
||||
* (2026-10-29, logos barre-apps, corrigé le même jour) : le logo
|
||||
* fourni, tel quel, jamais redessiné — le CSS --agent (rose) des
|
||||
* autres éléments de la barre n'est pas changé ici. */
|
||||
var sep = document.createElement("div");
|
||||
sep.className = "draco-menu-sep";
|
||||
sep.setAttribute("aria-hidden", "true");
|
||||
@@ -379,10 +382,18 @@
|
||||
sparkItem.href = resolvedUrls.agent || "#";
|
||||
sparkItem.dataset.app = "agent";
|
||||
sparkItem.setAttribute("role", "menuitem");
|
||||
var sparkIco = document.createElement("span");
|
||||
sparkIco.className = "draco-apptab-ico";
|
||||
sparkIco.setAttribute("style", "--app:#c2437e");
|
||||
sparkIco.textContent = "✦";
|
||||
var sparkIco;
|
||||
var agentTab = document.querySelector('.draco-apptab[data-app="agent"]');
|
||||
var agentIco = agentTab ? agentTab.querySelector(".draco-apptab-ico") : null;
|
||||
if (agentIco) {
|
||||
sparkIco = agentIco.cloneNode(true);
|
||||
} else {
|
||||
sparkIco = document.createElement("span");
|
||||
sparkIco.className = "draco-apptab-ico";
|
||||
sparkIco.setAttribute("style", "--app:#d21f2c");
|
||||
sparkIco.setAttribute("data-icon", "agent");
|
||||
sparkIco.textContent = "✦";
|
||||
}
|
||||
sparkItem.appendChild(sparkIco);
|
||||
var sparkTxt = document.createElement("span");
|
||||
sparkTxt.className = "draco-apps-item-txt";
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"""Défaut 3b — /api/search Flowdeck depuis la console agent-dev.
|
||||
|
||||
Cause prouvée en déployé (flowdeck-dev, 2026-10-28) : sur une réponse
|
||||
GET /api/search, le CORSMiddleware global pose déjà
|
||||
``Access-Control-Allow-Credentials: true`` (comportement Starlette
|
||||
avec ``allow_credentials=True``, même hors allowlist), et l'ancien
|
||||
middleware de recherche EMPILAIT ses en-têtes par-dessus — d'où, pour
|
||||
l'origine agent-dev, ``Allow-Credentials`` en double (« true, true »
|
||||
≠ « true » pour le contrôle CORS du navigateur → fetch rejeté →
|
||||
« injoignable (CORS ou réseau) » dans la console, alors que TikTak et
|
||||
Kanbante, sans ce cumul, répondent avec des en-têtes uniques) et,
|
||||
pour une origine sœur déjà dans la allowlist globale, ``Allow-Origin``
|
||||
en double (rejet certain). Le correctif : le middleware de recherche
|
||||
REMPLACE les en-têtes CORS existants au lieu de les empiler (même
|
||||
patron que app/recherche_cors.py de la console).
|
||||
|
||||
Ces tests pilotent le middleware en ASGI pur, avec une application
|
||||
interne qui imite le CORSMiddleware global (un en-tête
|
||||
Allow-Credentials déjà posé, et, pour une sœur, Allow-Origin aussi).
|
||||
``sister_origins`` est figé sur les listes officielles (les fonctions
|
||||
pures sont déjà couvertes par tests/test_recherche.py) pour garder le
|
||||
test hermétique (aucune résolution draco_urls, aucune base).
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from app.middleware import recherche_cors as rc
|
||||
|
||||
ORIGINES = rc.PROD_ORIGINS | rc.DEV_ORIGINS | rc.AGENT_ORIGINS
|
||||
|
||||
|
||||
def _lancer(middleware, scope):
|
||||
"""Exécute le middleware ASGI et retourne les messages envoyés."""
|
||||
recus = []
|
||||
|
||||
async def recevoir():
|
||||
return {"type": "http.request", "body": b"", "more_body": False}
|
||||
|
||||
async def envoyer(message):
|
||||
recus.append(message)
|
||||
|
||||
asyncio.run(middleware(scope, recevoir, envoyer))
|
||||
return recus
|
||||
|
||||
|
||||
def _application_interne(entetes):
|
||||
async def appli(scope, recevoir, envoyer):
|
||||
await envoyer({"type": "http.response.start", "status": 401,
|
||||
"headers": list(entetes)})
|
||||
await envoyer({"type": "http.response.body", "body": b"{}"})
|
||||
return appli
|
||||
|
||||
|
||||
def _scope(methode, origine):
|
||||
return {"type": "http", "method": methode, "path": "/api/search",
|
||||
"headers": [(b"origin", origine.encode("latin-1")),
|
||||
(b"host", b"flowdeck-dev.dracodev.net")]}
|
||||
|
||||
|
||||
def _compter(entetes, nom):
|
||||
return sum(1 for k, _v in entetes if k == nom.encode("latin-1"))
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _origines_figees(monkeypatch):
|
||||
monkeypatch.setattr(rc, "sister_origins", lambda *a, **k: ORIGINES)
|
||||
|
||||
|
||||
def test_get_agent_dev_un_seul_jeu_d_entetes():
|
||||
"""Cas du défaut : globale (ACAC seul) + recherche → un seul jeu."""
|
||||
interne = _application_interne([
|
||||
(b"content-type", b"application/json"),
|
||||
(b"access-control-allow-credentials", b"true")])
|
||||
recus = _lancer(rc.RechercheCorsMiddleware(interne),
|
||||
_scope("GET", "https://agent-dev.dracodev.net"))
|
||||
entetes = recus[0]["headers"]
|
||||
assert _compter(entetes, "access-control-allow-credentials") == 1
|
||||
assert _compter(entetes, "access-control-allow-origin") == 1
|
||||
assert _compter(entetes, "vary") == 1
|
||||
assert (b"access-control-allow-origin",
|
||||
b"https://agent-dev.dracodev.net") in entetes
|
||||
assert (b"content-type", b"application/json") in entetes
|
||||
assert recus[0]["status"] == 401 # le statut traverse inchangé
|
||||
|
||||
|
||||
def test_get_origine_soeur_pas_d_allow_origin_double():
|
||||
"""Sœur déjà dans la allowlist globale : pas de doublon non plus."""
|
||||
interne = _application_interne([
|
||||
(b"access-control-allow-origin", b"https://og-dev.dracodev.net"),
|
||||
(b"access-control-allow-credentials", b"true"),
|
||||
(b"vary", b"Origin")])
|
||||
recus = _lancer(rc.RechercheCorsMiddleware(interne),
|
||||
_scope("GET", "https://og-dev.dracodev.net"))
|
||||
entetes = recus[0]["headers"]
|
||||
assert _compter(entetes, "access-control-allow-origin") == 1
|
||||
assert _compter(entetes, "access-control-allow-credentials") == 1
|
||||
assert _compter(entetes, "vary") == 1
|
||||
|
||||
|
||||
def test_origine_non_autorisee_traverse_sans_modification():
|
||||
"""Une origine refusée ne reçoit aucun Allow-Origin ajouté."""
|
||||
interne = _application_interne([
|
||||
(b"access-control-allow-credentials", b"true")])
|
||||
recus = _lancer(rc.RechercheCorsMiddleware(interne),
|
||||
_scope("GET", "https://example.com"))
|
||||
entetes = recus[0]["headers"]
|
||||
assert _compter(entetes, "access-control-allow-origin") == 0
|
||||
assert entetes == [(b"access-control-allow-credentials", b"true")]
|
||||
|
||||
|
||||
def test_preflight_agent_dev_toujours_unique():
|
||||
"""Le preflight (court-circuit avant l'interne) reste propre."""
|
||||
recus = _lancer(rc.RechercheCorsMiddleware(_application_interne([])),
|
||||
_scope("OPTIONS", "https://agent-dev.dracodev.net"))
|
||||
entetes = recus[0]["headers"]
|
||||
assert recus[0]["status"] == 204
|
||||
assert _compter(entetes, "access-control-allow-origin") == 1
|
||||
assert _compter(entetes, "access-control-allow-credentials") == 1
|
||||
Reference in New Issue
Block a user