Compare commits

...
37 Commits
Author SHA1 Message Date
bruno b42a81018c release: changelog 2.82.8
CI / security (push) Failing after 0s
CI / lint (push) Failing after 1m59s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-30 18:36:43 -04:00
bruno 5c85763de1 release: version 2.82.8
CI / security (push) Failing after 0s
CI / lint (push) Failing after 1m56s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-30 18:36:39 -04:00
bruno 2aacb60102 Merge pull request 'secrets: afficher/masquer + copier la valeur dans le panneau détail' (#57) from secrets-afficher-copier into main
CI / lint (push) Failing after 1s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-30 18:36:07 -04:00
bruno c4247b8bbc secrets: afficher/masquer + copier la valeur, revelation gestionnaire auditee (og/COFFRE-contrat-distribution.md)
CI / lint (pull_request) Failing after 1s
CI / security (pull_request) Failing after 0s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-30 18:34:21 -04:00
bruno c29bafeccc secrets: afficher/masquer + copier la valeur, revelation gestionnaire auditee (og/test_coffre_secrets.py) 2026-10-30 18:34:18 -04:00
bruno fe4bed91d7 secrets: afficher/masquer + copier la valeur, revelation gestionnaire auditee (og/en.json) 2026-10-30 18:34:15 -04:00
bruno 3abf908ca4 secrets: afficher/masquer + copier la valeur, revelation gestionnaire auditee (og/fr.json) 2026-10-30 18:34:12 -04:00
bruno 9c8e8f54dc secrets: afficher/masquer + copier la valeur, revelation gestionnaire auditee (og/coffres.js) 2026-10-30 18:34:09 -04:00
bruno 1e59781d77 secrets: afficher/masquer + copier la valeur, revelation gestionnaire auditee (og/coffres.py) 2026-10-30 18:34:07 -04:00
bruno ead3393211 Merge pull request 'ObsiGate 2.82.7 : barre v0.1.11 — marque monochrome, anneau avatar' (#56) from barre-v0111-2-82-7 into main
CI / lint (push) Failing after 0s
CI / security (push) Failing after 1s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-29 23:10:14 -04:00
bruno 48beb692dc ObsiGate 2.82.7 : étoile Spark monochrome pour la marque de la barre
CI / lint (pull_request) Failing after 0s
CI / security (pull_request) Failing after 0s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-29 23:10:00 -04:00
bruno fe4b189697 ObsiGate 2.82.7 : barre v0.1.11, marque monochrome + anneau avatar (tests/test_draco_barre.py) 2026-10-29 23:09:59 -04:00
bruno c1e79d07e4 ObsiGate 2.82.7 : barre v0.1.11, marque monochrome + anneau avatar (CHANGELOG.md) 2026-10-29 23:09:57 -04:00
bruno 5de962b2c6 ObsiGate 2.82.7 : barre v0.1.11, marque monochrome + anneau avatar (VERSION) 2026-10-29 23:09:55 -04:00
bruno bc150bf225 ObsiGate 2.82.7 : barre v0.1.11, marque monochrome + anneau avatar (frontend/draco-barre.css) 2026-10-29 23:09:53 -04:00
bruno 31b8a86861 ObsiGate 2.82.7 : barre v0.1.11, marque monochrome + anneau avatar (frontend/recherche.html) 2026-10-29 23:09:52 -04:00
bruno dda60313f0 ObsiGate 2.82.7 : barre v0.1.11, marque monochrome + anneau avatar (frontend/index.html) 2026-10-29 23:09:50 -04:00
bruno fdf33c0f54 Merge pull request 'ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône' (#55) from barre-logos-2-82-6 into main
CI / lint (push) Failing after 0s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-29 22:56:31 -04:00
bruno 92bfd8f0ef ObsiGate 2.82.6 : avatar Spark flottant (commons v0.1.10)
CI / lint (pull_request) Failing after 0s
CI / security (pull_request) Failing after 0s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-29 22:56:13 -04:00
bruno 64973a578b ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône (tests/test_draco_barre.py) 2026-10-29 22:56:11 -04:00
bruno fb5414efbb ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône (CHANGELOG.md) 2026-10-29 22:56:09 -04:00
bruno 174cd1c3e7 ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône (VERSION) 2026-10-29 22:56:07 -04:00
bruno ee6748c261 ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône (frontend/draco-barre.css) 2026-10-29 22:56:06 -04:00
bruno dd0b12213f ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône (frontend/recherche.html) 2026-10-29 22:56:04 -04:00
bruno 6195f252d9 ObsiGate 2.82.6 : barre v0.1.10 — rouge Spark, marque logo, avatar flottant, Alertes icône (frontend/index.html) 2026-10-29 22:56:02 -04:00
bruno 397a60663c Merge pull request 'ObsiGate 2.82.5 : les 5 logos dans le menu et la barre commune' (#54) from barre-logos-2-82-5 into main
CI / lint (push) Failing after 1s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-29 22:22:58 -04:00
bruno d792714f4f ObsiGate 2.82.5 : logo fourni spark.png pour la barre et le menu
CI / lint (pull_request) Failing after 1s
CI / security (pull_request) Failing after 0s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
2026-10-29 22:22:41 -04:00
bruno 65edbc9e6c ObsiGate 2.82.5 : logo fourni kanbante.png pour la barre et le menu 2026-10-29 22:22:39 -04:00
bruno 95df928035 ObsiGate 2.82.5 : logo fourni tiktak.svg pour la barre et le menu 2026-10-29 22:22:38 -04:00
bruno 78ac84e1cc ObsiGate 2.82.5 : logo fourni flowdeck.png pour la barre et le menu 2026-10-29 22:22:37 -04:00
bruno 550bcb5cc8 ObsiGate 2.82.5 : logo fourni obsigate.png pour la barre et le menu 2026-10-29 22:22:35 -04:00
bruno 44baa0a273 ObsiGate 2.82.5 : logos barre-apps v0.1.9 dans le menu et la barre (CHANGELOG.md) 2026-10-29 22:22:34 -04:00
bruno 78021d6549 ObsiGate 2.82.5 : logos barre-apps v0.1.9 dans le menu et la barre (VERSION) 2026-10-29 22:22:32 -04:00
bruno 72d8ce39d6 ObsiGate 2.82.5 : logos barre-apps v0.1.9 dans le menu et la barre (frontend/draco-barre.css) 2026-10-29 22:22:30 -04:00
bruno 14dd77094d ObsiGate 2.82.5 : logos barre-apps v0.1.9 dans le menu et la barre (frontend/draco-barre.js) 2026-10-29 22:22:29 -04:00
bruno 031879889c ObsiGate 2.82.5 : logos barre-apps v0.1.9 dans le menu et la barre (frontend/recherche.html) 2026-10-29 22:22:27 -04:00
bruno a5d2e0d085 ObsiGate 2.82.5 : logos barre-apps v0.1.9 dans le menu et la barre (frontend/index.html) 2026-10-29 22:22:25 -04:00
20 changed files with 315 additions and 107 deletions
+23 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.82.4**.
> [Unreleased](#unreleased). La dernière version livrée est **2.82.8**.
---
@@ -14,6 +14,28 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.82.8] — 2026-10-30
- **Panneau détail d'un secret : Afficher/Masquer + Copier la valeur** (PR #57) : la valeur reste masquée (••••••) par défaut ; Afficher la révèle en place, Masquer l'efface (effacée aussi dès que le détail change). « Copier la valeur » copie au presse-papiers sans afficher. Le bouton « Copier » existant ne copiait que la référence.
- **Nouvel endpoint `POST /api/coffres/{slug}/secrets/{nom}/reveler`** : session + gestion du coffre exigées (propriétaire, ou admin sur coffre d'instance) ; un lecteur par octroi utilisateur reçoit 403. Chaque révélation est auditée (`revelation_manuelle`) et met à jour la dernière utilisation. Ce n'est pas une distribution : aucun octroi créé, aucun consommateur ne reçoit la valeur.
- **Suppression de la « Copie manuelle à usage unique »** par consommateur temporaire : elle échouait systématiquement (aucun octroi pour le consommateur temporaire, donc 403 octroi_absent) ; « Copier la valeur » la remplace.
- **Tests** : `tests/test_coffre_secrets.py` 44/44 (5 nouveaux TestRevelation).
## [2.82.7] — 2026-10-29
- **Barre commune (dracodev-commons v0.1.11 vendangée)** : la marque de la barre n'est plus le logo rouge — c'est l'étoile Spark monochrome (`frontend/img/brand-spark.png`, servi en `/static/img/brand-spark.png`, 26 px), blanche sur barre foncée et noire sur barre claire : ObsiGate pose déjà `data-theme` sur `<html>` (sombre par défaut, mode persisté), donc la règle vendangée `[data-theme="dark"] .draco-bar .draco-brand-mark img { filter: none; }` s'applique directement et aucune surcharge propre n'est nécessaire. L'onglet Console Spark (et son entrée de menu, qui le clone) GARDE le logo rouge. Le bouton avatar Spark flottant bas-centre gagne un anneau rouge `#c00000` (bordure 3 px, `var(--agent)`), via le CSS vendangé.
- **Pages concernées** : la barre est rendue en ligne dans `frontend/index.html` et dans sa copie de `frontend/recherche.html` — les deux sont portées à la v0.1.11 ; `frontend/draco-barre.css` remplacé verbatim par la v0.1.11, `frontend/draco-barre.js` inchangé (identique à commons, lui-même inchangé en v0.1.11). Le reste de l'intégration est inchangé. Précision : sur `/recherche`, copie statique, le bouton flottant y est visuel seulement, comme au lot précédent — l'ouverture du panneau vit dans la SPA.
## [2.82.6] — 2026-10-29
- **Barre commune (dracodev-commons v0.1.10 vendangée)** : le rose Spark `#c2437e` disparaît de la barre — `--agent` passe au rouge Spark `#c00000` (échantillonné dans le logo Spark), l'onglet Console Spark passe à `--app #c00000`, et la marque ✦ devient le logo Spark (26 px). Le bouton « Spark » rose de la barre est supprimé, remplacé par le bouton avatar Spark flottant bas-centre (cercle personnage, `frontend/img/avatar-spark.png`, servi en `/static/img/avatar-spark.png`, `data-act="spark"` → panneau Spark de l'hôte, comme l'ancien bouton). Le bouton Alertes n'affiche plus que la cloche (pastille conservée), donc une barre plus étroite.
- **Pages concernées** : la barre est rendue en ligne dans `frontend/index.html` et dans sa copie de `frontend/recherche.html` — les deux sont portées à la v0.1.10 ; `frontend/draco-barre.css` remplacé verbatim par la v0.1.10, `frontend/draco-barre.js` inchangé (identique à commons v0.1.9, lui-même inchangé en v0.1.10). Le reste de l'intégration (URL, `DRACO_URLS`, actions, compteurs) est inchangé. Précision : sur `/recherche`, copie statique, le panneau Spark n'est pas rejoué (comme avant) — le bouton flottant y est visuel, l'ouverture du panneau vit dans la SPA.
## [2.82.5] — 2026-10-29
- **Barre commune (dracodev-commons v0.1.9 vendangée)** : les 5 tuiles des onglets — 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 dans `frontend/img/logos/` (copie de `ui/barre-apps/logos/` de commons, PNG 180 px), servis en `/static/img/logos/` ; l'entrée « Console Spark » du menu clone l'icône de l'onglet agent (`draco-barre.js`) ; onglet agent passé à `--app #d21f2c`. Les glyphes texte ▤/▦/✓/▥/✦ disparaissent des onglets (marque ✦, bouton Spark et rappel d'en-tête inchangés).
- **Pages concernées** : la barre est rendue en ligne dans `frontend/index.html` et dans sa copie de `frontend/recherche.html` — les deux sont portées à la v0.1.9, `draco-barre.js` et `draco-barre.css` remplacés verbatim (les fichiers précédents étaient identiques à l'octet près à ceux de commons avant la v0.1.9, aucune adaptation locale perdue). Le reste de l'intégration (URL, `DRACO_URLS`, actions, compteurs) est inchangé.
## [2.82.4] — 2026-10-28
- **Recherche (PR #52)** : texte parasite du mécanisme de thème retiré du haut de la page `/recherche` (lignes 28-43 de `frontend/recherche.html`, hors commentaire).
+1 -1
View File
@@ -1 +1 @@
2.82.4
2.82.8
+52
View File
@@ -12,6 +12,14 @@ Règles d'or, appliquées côté serveur à chaque requête :
- Seul ``POST /api/coffres/distribuer`` rend une valeur, et uniquement à
un consommateur authentifié par jeton (hash + jti, jamais la session
utilisateur), avec un octroi actif et un contexte obligatoire.
- Seule exception, voulue : ``POST /api/coffres/{slug}/secrets/{nom}/reveler``
rend la valeur au gestionnaire du coffre authentifié par sa session
(propriétaire, ou admin sur coffre d'instance — jamais un simple
lecteur par octroi utilisateur), pour l'affichage et la copie manuelle
dans le panneau détail. Chaque révélation est auditée
(``revelation_manuelle``) et met à jour la dernière utilisation ; la
valeur n'est jamais journalisée et le frontend l'efface dès qu'elle
est masquée ou que le détail change.
- Autorisations (spec §6) : le propriétaire seul sur son coffre
personnel ; l'admin gère les coffres d'instance, les consommateurs et
la KEK, jamais le coffre personnel d'autrui ; l'ACL « * » des vaults
@@ -622,6 +630,50 @@ async def api_rotate_secret(
return _metadata_secret(coffre, secret)
@router.post("/api/coffres/{slug}/secrets/{nom}/reveler")
async def api_reveler_secret(
slug: str, nom: str, current_user=Depends(require_auth)
):
"""Révélation manuelle pour le panneau détail (afficher / copier).
Session utilisateur + gestion du coffre exigées : un lecteur par
octroi utilisateur voit les métadonnées mais ne révèle jamais la
valeur. Chaque appel est audité et touche la dernière utilisation.
"""
_exiger_actives()
coffre = _coffre_or_404(slug)
_exiger_gerer(current_user, coffre)
secret = _secret_or_404(coffre, nom)
try:
valeur = distribuer_valeur(coffre, secret)
except SecretExpireError:
_audit(_username(current_user), "revelation_refusee",
coffre=coffre["slug"],
secret_ref=f"coffre:{coffre['slug']}/{secret['nom']}",
resultat="secret_expire")
raise HTTPException(status_code=410, detail="secret_expire") from None
except KEKAbsenteError:
_audit(_username(current_user), "revelation_refusee",
coffre=coffre["slug"],
secret_ref=f"coffre:{coffre['slug']}/{secret['nom']}",
resultat="kek_absente")
raise HTTPException(status_code=503, detail="kek_absente") from None
except CryptoIndisponibleError:
_audit(_username(current_user), "revelation_refusee",
coffre=coffre["slug"],
secret_ref=f"coffre:{coffre['slug']}/{secret['nom']}",
resultat="crypto_indisponible")
raise HTTPException(status_code=503,
detail="crypto_indisponible") from None
toucher_utilisation(secret["id"])
_audit(_username(current_user), "revelation_manuelle",
coffre=coffre["slug"],
secret_ref=f"coffre:{coffre['slug']}/{secret['nom']}",
resultat="ok")
return {"reference": f"coffre:{coffre['slug']}/{secret['nom']}",
"valeur": valeur, "version": secret["version_courante"]}
@router.delete("/api/coffres/{slug}/secrets/{nom}")
async def api_delete_secret(
slug: str, nom: str, current_user=Depends(require_auth)
+10
View File
@@ -73,6 +73,16 @@ configurée (sinon 503 `kek_absente`).
SSO : une session utilisateur, même admin, ne distribue jamais
(401 `consommateur_invalide`). Il n'existe pas d'endpoint de
distribution par session (voir §6).
- Révélation manuelle (panneau détail, hors distribution) : `POST
/api/coffres/{slug}/secrets/{nom}/reveler` rend la valeur au
gestionnaire du coffre authentifié par sa session (propriétaire,
ou admin sur coffre d'instance), pour l'afficher ou la copier.
Ce n'est pas une distribution : aucun octroi n'est créé, aucun
consommateur ne reçoit la valeur, chaque révélation est auditée
(`revelation_manuelle`) et met à jour la dernière utilisation.
Un lecteur par octroi utilisateur (métadonnées visibles) reçoit
403. L'ancienne copie manuelle par consommateur temporaire a
été supprimée (elle échouait : 403 octroi_absent).
- Ajouter une app = enregistrer un consommateur + créer des octrois,
**sans modification du coffre** et sans toucher à ses secrets.
+36 -11
View File
@@ -7,6 +7,11 @@
menu des applications) ; l'avatar est un vrai <button>.
Correctif fusion #70 (SANS bump) : bouton trois points et menu par
défaut supprimés ; le menu de la marque gagne « Console Spark ».
v0.1.10 : rose --agent → rouge Spark #c00000 ; marque = logo Spark ;
bouton Spark supprimé (remplacé par l'avatar flottant bas-centre) ;
Alertes icône seule.
v0.1.11 : marque = étoile Spark monochrome (blanche barre foncée,
noire barre claire) ; anneau rouge #c00000 autour de l'avatar flottant.
Jetons repris du prototype validé Console v7.2.
Thème : clair par défaut, sombre via [data-theme="dark"] sur <html>
ou prefers-color-scheme si l'app intégratrice ne force rien. */
@@ -14,7 +19,7 @@
.draco-bar {
--bg: #f2f2f0; --surface: #ffffff; --surface-2: #e9eae6; --surface-3: #f6f6f3;
--ink: #1b1d1a; --ink-2: #5c615a; --ink-3: #787d75; --line: #dedfd9;
--agent: #c2437e; --danger: #cf4444;
--agent: #c00000; --danger: #cf4444;
position: relative;
height: 60px; box-sizing: border-box;
display: grid; grid-template-columns: auto minmax(200px, 520px) auto;
@@ -37,7 +42,15 @@
.draco-brand { display: flex; align-items: center; gap: 7px; color: var(--ink); text-decoration: none; font-weight: 800; cursor: pointer; border-radius: 10px; padding: 4px 6px; margin: -4px -6px; }
.draco-brand:hover { background: var(--surface-2); }
.draco-brand:focus-visible { outline: 2px solid var(--agent); outline-offset: 2px; }
.draco-brand-mark { color: var(--agent); font-size: 24px; line-height: 1; }
.draco-brand-mark { line-height: 0; }
/* Marque monochrome v0.1.11 : étoile blanche extraite du logo rouge,
fond transparent — noire sur barre claire (filtre), blanche sur
barre foncée (filtre annulé par les règles thème ci-dessous). */
.draco-brand-mark img { width: 26px; height: 26px; object-fit: cover; display: block; filter: brightness(0); }
@media (prefers-color-scheme: dark) {
.draco-bar .draco-brand-mark img { filter: none; }
}
[data-theme="dark"] .draco-bar .draco-brand-mark img { filter: none; }
/* Menu des applications — construit par le JS depuis les onglets */
.draco-apps-menu {
@@ -94,6 +107,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); }
@@ -131,18 +151,23 @@
/* Alertes — la cloche ne disparaît JAMAIS (correctif v7.2) */
.draco-alertes {
position: relative; display: flex; align-items: center; gap: 7px; height: 38px;
padding: 0 11px; border-radius: 10px; border: 1px solid var(--line);
position: relative; display: flex; align-items: center; height: 38px;
padding: 0 9px; border-radius: 10px; border: 1px solid var(--line);
background: transparent; color: var(--ink); font: inherit; cursor: pointer;
}
/* Spark */
.draco-spark {
position: relative; display: flex; align-items: center; gap: 7px; height: 38px;
padding: 0 14px; border: 0; border-radius: 10px; cursor: pointer;
background: var(--agent); color: #fff; font: inherit; font-weight: 700;
/* Bouton avatar Spark flottant — bas-centre, ouvre le panneau Spark
de l'hôte via data-act="spark" (délégation globale existante).
La console Spark ne l'inclut pas : son fragment est propre. */
.draco-spark-flottant {
position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%);
z-index: 65; width: 64px; height: 64px; padding: 0; border: 0;
border-radius: 50%; background: transparent; cursor: pointer;
box-shadow: 0 8px 28px rgb(0 0 0 / .45);
}
.draco-spark .draco-badge { border-color: var(--agent); }
.draco-spark-flottant img { width: 64px; height: 64px; border-radius: 50%; object-fit: cover; display: block; box-sizing: border-box; border: 3px solid var(--agent); }
.draco-spark-flottant:hover { transform: translateX(-50%) scale(1.05); }
.draco-spark-flottant:focus-visible { outline: 2px solid var(--agent); outline-offset: 3px; }
.draco-icon-btn {
width: 38px; height: 38px; border-radius: 10px; border: 1px solid transparent;
@@ -169,7 +194,7 @@
.draco-bar { grid-template-columns: auto minmax(200px, 440px) auto; }
}
@media (max-width: 1200px) {
.draco-search-txt, .draco-search kbd, .draco-alertes-txt, .draco-spark-txt { display: none; }
.draco-search-txt, .draco-search kbd { display: none; }
.draco-search { width: 38px; padding: 0; justify-content: center; }
.draco-bar { grid-template-columns: auto auto 1fr; }
.draco-bar-droite { justify-content: flex-end; }
+16 -5
View File
@@ -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";
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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

+8
View File
@@ -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

+24 -12
View File
@@ -268,10 +268,26 @@
<!-- Main Application (wrapped for show/hide) -->
<!-- Barre d'apps commune dracodev — Fusion #215 (ACC-1.1).
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.6
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.11
(ui/barre-apps/) : ne pas modifier la structure ici ; toute
évolution passe par une nouvelle version de commons.
CSS/JS : /static/draco-barre.css, /static/draco-barre.js.
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) ; l'entrée « Console Spark »
du menu clone l'icône de l'onglet agent (draco-barre.js).
v0.1.10 (2026-10-29) : --agent rose → rouge Spark #c00000, marque ✦
→ logo Spark 26 px, bouton Spark rose supprimé (remplacé par
l'avatar flottant bas-centre /static/img/avatar-spark.png,
data-act="spark" → panneau Spark), Alertes = cloche seule.
v0.1.11 (2026-10-29) : marque → étoile Spark monochrome
/static/img/brand-spark.png 26 px (blanche sur barre foncée
via [data-theme="dark"] d'ObsiGate, noire sur barre claire
via le filtre brightness(0) du CSS vendangé — aucune
surcharge propre nécessaire, la barre suit déjà
data-theme) ; onglet agent GARDANT le logo rouge ;
anneau rouge #c00000 (bordure 3 px) autour de l'avatar
flottant, via le CSS vendangé.
Chantier 5 (fusion #69/#71, sans bump) : 5e onglet Console Spark
(data-app="agent", défaut prod ; la réécriture DRACO_URLS
couvre la clé "agent" comme les autres onglets), après Kanbante ;
@@ -280,29 +296,29 @@
<div class="draco-bar-zone draco-bar-gauche">
<button type="button" class="draco-icon-btn" data-act="rail" aria-label="Barre latérale">☰</button>
<a class="draco-brand" href="/" data-act="apps" aria-haspopup="true" aria-expanded="false" aria-label="dracodev — changer d&#39;application" title="Changer d&#39;application">
<span class="draco-brand-mark">✦</span><span class="draco-brand-name">dracodev</span>
<span class="draco-brand-mark"><img src="/static/img/brand-spark.png" alt="" width="26" height="26"></span><span class="draco-brand-name">dracodev</span>
</a>
<nav class="draco-apptabs" aria-label="Applications">
<a class="draco-apptab" data-app="obsigate" href="https://og.dracodev.net/">
<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="https://flowdeck.dracodev.net/">
<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="https://tiktak.dracodev.net/">
<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="https://kanbante.dracodev.net/">
<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="https://agent.dracodev.net/">
<span class="draco-apptab-ico" style="--app:#c2437e">✦</span>
<span class="draco-apptab-ico" style="--app:#c00000" 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>
@@ -323,17 +339,13 @@
</div>
<button type="button" class="draco-alertes" data-act="alertes" aria-label="Alertes unifiées">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
<span class="draco-alertes-txt">Alertes</span>
<span class="draco-badge" data-badge="alertes" hidden>0</span>
</button>
<button type="button" class="draco-spark" data-act="spark" aria-label="Ouvrir Spark, l'agent commun">
<span class="draco-spark-ico">✦</span><span class="draco-spark-txt">Spark</span>
<span class="draco-badge" data-badge="spark" hidden>0</span>
</button>
<button type="button" class="draco-avatar" id="draco-avatar" data-act="compte" aria-label="Compte" aria-haspopup="menu" aria-expanded="false" title="">✦</button>
</div>
</header>
<div class="draco-bar-rappel" data-act="rappel-entete" hidden>✦ dracodev — afficher l'en-tête</div>
<button type="button" class="draco-spark-flottant" data-act="spark" aria-label="Ouvrir Spark, l'agent commun"><img src="/static/img/avatar-spark.png" alt="" width="64" height="64"></button>
<script>
/* Configuration ObsiGate de la barre commune (Fusion #215).
Phase 1 : navigation entre apps et domaine mémorisé
+63 -62
View File
@@ -4,9 +4,15 @@
*
* Règles de sécurité appliquées ici (jamais assouplies) :
* - Le backend ne renvoie que des métadonnées (has_value, jamais la valeur,
* jamais un texte chiffré, jamais un nonce). Ce module n'affiche donc
* jamais de valeur de secret après enregistrement : colonne valeur =
* « •••••• » seulement, aucun bouton « afficher ».
* jamais un texte chiffré, jamais un nonce). La valeur d'un secret
* enregistré reste donc masquée (« •••••• ») par défaut ; seul le
* gestionnaire du coffre (propriétaire, ou admin sur coffre
* d'instance) peut la révéler, via POST .../secrets/{nom}/reveler
* (session + gestion, chaque révélation auditée) : bouton
* Afficher/Masquer dans le détail, ou « Copier la valeur » sans
* l'afficher. La valeur est effacée dès qu'elle est masquée ou que
* le détail change ; un simple lecteur par octroi utilisateur ne
* révèle jamais.
* - Aucune valeur ne transite par un titre d'onglet, une URL, un hash, un
* journal console ou un log. Les variables contenant une valeur sont
* effacées dès que l'opération est terminée (champ vidé, variable = null).
@@ -14,10 +20,11 @@
* consommateur (csk_…) et une KEK générée sont rendus UNE fois, dans une
* modale, avec un bouton Copier et un avertissement ; la modale fermée,
* ils sont perdus. Ce ne sont pas des valeurs de secret du coffre.
* - La copie manuelle à usage unique (admin seulement, voir
* _copieManuelle) passe par un consommateur temporaire créé puis révoqué
* immédiatement, avec distribution auditée ; il n'existe pas encore
* d'endpoint de distribution par session utilisateur côté backend.
* - L'ancienne copie manuelle à usage unique par consommateur
* temporaire a été supprimée : elle échouait systématiquement
* (aucun octroi pour le consommateur temporaire, donc 403
* octroi_absent à la distribution). Elle est remplacée par
* « Copier la valeur », qui passe par l'endpoint de révélation.
* - Phase 2 (issue #20) : « Importer l'existant » migre les clés de
* data/api_keys.json vers le coffre d'instance « ObsiGate interne »,
* en deux temps (import, puis purge confirmée). Aucune valeur ne
@@ -52,6 +59,8 @@ let _coffresCache = null;
let _coffresCacheAt = 0;
const CACHE_MS = 30000;
let _valeurAffichee = null; // valeur révélée du détail courant, sinon null
const viewState = {
coffres: [],
currentSlug: null,
@@ -354,6 +363,7 @@ function renderAlerts(host) {
function renderDetail(host) {
const s = viewState.detail;
_valeurAffichee = null;
host.innerHTML = '';
if (!s) {
host.appendChild(text('p', t('coffres.select_secret'), 'coffres-empty'));
@@ -377,7 +387,9 @@ function renderDetail(host) {
const dl = el('dl', { class: 'coffres-dl' }, []);
rows.forEach(([k, v]) => {
dl.appendChild(el('dt', {}, [document.createTextNode(k)]));
dl.appendChild(el('dd', {}, [document.createTextNode(v)]));
const dd = el('dd', {}, [document.createTextNode(v)]);
if (k === t('coffres.value')) dd.id = 'coffres-valeur';
dl.appendChild(dd);
});
host.appendChild(dl);
const actions = el('div', { class: 'coffres-actions' }, []);
@@ -386,11 +398,36 @@ function renderDetail(host) {
actions.appendChild(modalBtn(t('coffres.acces'), '', () => {
document.getElementById('coffres-acces')?.scrollIntoView({ behavior: 'smooth' });
}));
actions.appendChild(modalBtn(t('coffres.manual_copy'), '', () => copieManuelle(s)));
if (!isAdmin()) {
actions.lastChild.disabled = true;
actions.lastChild.title = t('coffres.manual_copy_admin_only');
}
const btnAfficher = modalBtn(t('coffres.show_value'), '', async () => {
if (_valeurAffichee !== null) {
_valeurAffichee = null;
document.getElementById('coffres-valeur').textContent = '••••••';
btnAfficher.textContent = t('coffres.show_value');
return;
}
let valeur;
try {
valeur = await revelerValeur(s);
} catch (e) {
showToast(e.message || t('common.error'), 'error');
return;
}
_valeurAffichee = valeur;
document.getElementById('coffres-valeur').textContent = valeur;
btnAfficher.textContent = t('coffres.hide_value');
});
actions.appendChild(btnAfficher);
actions.appendChild(modalBtn(t('coffres.copy_value'), '', async () => {
let valeur;
try {
valeur = await revelerValeur(s);
} catch (e) {
showToast(e.message || t('common.error'), 'error');
return;
}
await copyText(valeur);
valeur = null;
}));
actions.appendChild(modalBtn(t('coffres.delete'), 'danger', () => supprimerSecret(s)));
host.appendChild(actions);
@@ -819,55 +856,19 @@ function openCreateCoffreModal() {
]);
}
/** Copie manuelle à usage unique, admin seulement.
* Le backend n'expose pas (encore) de distribution par session : on passe
* par un consommateur temporaire créé, utilisé une fois, puis révoqué
* immédiatement. La valeur est copiée au presse-papiers sans jamais être
* affichée, puis la variable est effacée, même en cas d'échec. */
async function copieManuelle(s) {
if (!isAdmin()) return;
const ok = await showConfirm({
title: t('coffres.manual_copy'),
message: t('coffres.manual_copy_confirm'),
danger: true,
});
if (!ok) return;
let consommateurId = null;
let valeur = null;
try {
const created = await api('/api/coffres/consommateurs', {
method: 'POST',
body: JSON.stringify({ nom: `copie-manuelle-${me()}-${Date.now()}` }),
});
consommateurId = created.id;
const jeton = created.jeton;
const res = await fetch('/api/coffres/distribuer', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${jeton}`,
},
body: JSON.stringify({
reference: s.reference,
contexte_type: 'copie_manuelle',
contexte_id: `copie-manuelle-${Date.now()}`,
}),
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.detail || `Erreur ${res.status}`);
valeur = data.valeur;
await copyText(valeur);
} catch (e) {
showToast(e.message || t('common.error'), 'error');
} finally {
valeur = null;
if (consommateurId) {
try {
await api(`/api/coffres/consommateurs/${encodeURIComponent(consommateurId)}`, { method: 'DELETE' });
} catch { /* révocation best-effort, l'octroi reste révocable depuis Accès */ }
}
}
/** Révélation manuelle (gestionnaire du coffre seulement, auditée
* côté backend). Ne conserve rien : l'appelant affiche ou copie
* puis efface. Un lecteur par octroi utilisateur reçoit 403. */
async function revelerValeur(s) {
const ref = (s.reference || '').replace(/^coffre:/, '');
const [slug, nom] = ref.split('/');
const res = await fetch(
`/api/coffres/${encodeURIComponent(slug)}/secrets/${encodeURIComponent(nom)}/reveler`,
{ method: 'POST', credentials: 'include' },
);
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.detail || `Erreur ${res.status}`);
return data.valeur;
}
/** Phase 2 — « Importer l'existant » (admin seulement, deux temps).
+3
View File
@@ -2680,6 +2680,9 @@
"coffres.copy_failed": "Copy failed.",
"coffres.copy_generated": "Copy the generated value",
"coffres.copy_reference": "Copy",
"coffres.show_value": "Show",
"coffres.hide_value": "Hide",
"coffres.copy_value": "Copy value",
"coffres.create_coffre_title": "New vault",
"coffres.create_octroi": "Create a grant",
"coffres.create_secret_title": "New secret",
+3
View File
@@ -2680,6 +2680,9 @@
"coffres.copy_failed": "Copie impossible.",
"coffres.copy_generated": "Copier la valeur générée",
"coffres.copy_reference": "Copier",
"coffres.show_value": "Afficher",
"coffres.hide_value": "Masquer",
"coffres.copy_value": "Copier la valeur",
"coffres.create_coffre_title": "Nouveau coffre",
"coffres.create_octroi": "Créer un octroi",
"coffres.create_secret_title": "Nouveau secret",
+8 -12
View File
@@ -14,7 +14,7 @@
retomberait sur les défauts prod (les jetons sont ignorés par
le JS) — jamais d'URL inventée.
La barre ci-dessous est une copie statique du snippet figé de
index.html (commons v0.1.6 + chantier 5 fusion #69/#71, sans
index.html (commons v0.1.11 + chantier 5 fusion #69/#71, sans
bump) : les onglets sont des liens réels, le bouton central
replace le curseur dans la boîte (recherche.js,
clic data-act="palette"). Re-vendoring alignement recherche :
@@ -58,29 +58,29 @@
<div class="draco-bar-zone draco-bar-gauche">
<button type="button" class="draco-icon-btn" data-act="rail" aria-label="Barre latérale">☰</button>
<a class="draco-brand" href="/" data-act="apps" aria-haspopup="true" aria-expanded="false" aria-label="dracodev — changer d'application" title="Changer d'application">
<span class="draco-brand-mark">✦</span><span class="draco-brand-name">dracodev</span>
<span class="draco-brand-mark"><img src="/static/img/brand-spark.png" alt="" width="26" height="26"></span><span class="draco-brand-name">dracodev</span>
</a>
<nav class="draco-apptabs" aria-label="Applications">
<a class="draco-apptab" data-app="obsigate" href="{{URL_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="{{URL_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="{{URL_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="{{URL_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="{{URL_AGENT}}">
<span class="draco-apptab-ico" style="--app:#c2437e">✦</span>
<span class="draco-apptab-ico" style="--app:#c00000" 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>
@@ -101,16 +101,12 @@
</div>
<button type="button" class="draco-alertes" data-act="alertes" aria-label="Alertes unifiées">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
<span class="draco-alertes-txt">Alertes</span>
<span class="draco-badge" data-badge="alertes" hidden>0</span>
</button>
<button type="button" class="draco-spark" data-act="spark" aria-label="Ouvrir Spark, l'agent commun">
<span class="draco-spark-ico">✦</span><span class="draco-spark-txt">Spark</span>
<span class="draco-badge" data-badge="spark" hidden>0</span>
</button>
<button type="button" class="draco-avatar" id="draco-avatar" data-act="compte" aria-label="Compte" aria-haspopup="menu" aria-expanded="false" title="">✦</button>
</div>
</header>
<button type="button" class="draco-spark-flottant" data-act="spark" aria-label="Ouvrir Spark, l'agent commun"><img src="/static/img/avatar-spark.png" alt="" width="64" height="64"></button>
<div id="app" class="app-container">
<header class="header">
<div class="header-left">
+53
View File
@@ -778,3 +778,56 @@ class TestDistribuables:
# Flag désactivé : 503 comme les autres routes.
monkeypatch.setenv("OBSIGATE_SECRETS_ENABLED", "0")
assert self._lister(client, jeton).status_code == 503
# ---------------------------------------------------------------------------
# Révélation manuelle (panneau détail : Afficher / Copier la valeur)
# ---------------------------------------------------------------------------
class TestRevelation:
def test_proprietaire_revele_et_audit_sans_valeur(self, client, env_coffre):
_creer_coffre_secret(client)
resp = client.post("/api/coffres/personnel/secrets/gitea-token/reveler")
assert resp.status_code == 200, resp.text
corps = resp.json()
assert corps["valeur"] == VALEUR_TEST
assert corps["reference"] == "coffre:personnel/gitea-token"
assert corps["version"] == 1
audit = (env_coffre / "audit.log").read_text(encoding="utf-8")
assert "revelation_manuelle" in audit
assert VALEUR_TEST not in audit
def test_lecteur_octroi_utilisateur_ne_revele_pas(self, client):
_creer_coffre_secret(client)
resp = client.post("/api/coffres/personnel/octrois", json={
"cible_type": "utilisateur", "cible_id": "alice",
"secret_nom": "gitea-token"})
assert resp.status_code == 200, resp.text
_connecter(client, "alice", role="user")
# Voit les métadonnées…
assert client.get("/api/coffres/personnel/secrets").status_code == 200
# …mais ne révèle jamais la valeur.
resp = client.post("/api/coffres/personnel/secrets/gitea-token/reveler")
assert resp.status_code == 403
def test_secret_inexistant_404(self, client):
_creer_coffre_secret(client)
resp = client.post("/api/coffres/personnel/secrets/absent/reveler")
assert resp.status_code == 404
def test_secret_expire_410(self, client):
_creer_coffre_secret(client)
hier = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
resp = client.post("/api/coffres/personnel/secrets", json={
"nom": "vieux-jeton", "type": "token", "valeur": VALEUR_TEST,
"expiration": hier})
assert resp.status_code == 200, resp.text
resp = client.post("/api/coffres/personnel/secrets/vieux-jeton/reveler")
assert resp.status_code == 410
assert resp.json()["detail"] == "secret_expire"
def test_flag_desactive_503(self, client, monkeypatch):
_creer_coffre_secret(client)
monkeypatch.setenv("OBSIGATE_SECRETS_ENABLED", "0")
resp = client.post("/api/coffres/personnel/secrets/gitea-token/reveler")
assert resp.status_code == 503
+15 -3
View File
@@ -34,13 +34,25 @@ def test_palier_mobile_resserre_v012():
def test_menu_des_applications_par_la_marque():
# v0.1.3 : la marque ✦ ouvre le menu des 4 applications
# (changement d'app en mobile), logo à 24 px.
# v0.1.3 : la marque ouvre le menu des applications
# (changement d'app en mobile). v0.1.10 : la marque n'est plus
# le glyphe ✦ à 24 px mais le logo Spark en img 26 px.
# v0.1.11 : la marque n'est plus le logo rouge mais l'étoile
# Spark monochrome (brand-spark.png) ; l'onglet agent, lui,
# garde le logo rouge.
html = _index()
assert 'data-act="apps"' in html
js = (FRONTEND / "draco-barre.js").read_text(encoding="utf-8")
assert "draco-apps-menu" in js
assert "font-size: 24px" in _css()
assert '<span class="draco-brand-mark"><img src="/static/img/brand-spark.png"' in html
assert ".draco-brand-mark img" in _css()
assert "width: 26px" in _css()
assert "filter: brightness(0)" in _css()
assert '[data-theme="dark"] .draco-bar .draco-brand-mark img { filter: none; }' in _css()
assert 'data-icon="agent"><img src="/static/img/logos/spark.png"' in html
assert "border: 3px solid var(--agent)" in _css()
recherche = (FRONTEND / "recherche.html").read_text(encoding="utf-8")
assert '<span class="draco-brand-mark"><img src="/static/img/brand-spark.png"' in recherche
def test_panneau_spark_integre_218():