diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e638ab..434322a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.86.0**. +> [Unreleased](#unreleased). La dernière version livrée est **2.86.1**. --- @@ -14,6 +14,12 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.86.1] — 2026-11-06 + +- **Barre commune v0.6.0 — entrée « Administration » du menu compte** (fusion #142, demande de Bruno) : barre re-vendangée **verbatim** depuis dracodev-commons (PR commons #14) — `frontend/draco-barre.js` et `frontend/draco-barre.css`. Le menu compte gagne un cinquième lien Hub, « Administration » → `/admin` (le Hub redirige vers `/admin/utilisateurs`), après le séparateur et avant « Déconnexion », à **visibilité conditionnelle** : l'entrée n'apparaît que si l'hôte affirme que le compte courant est administrateur de suite (`DRACO_BAR.setAdmin`). Branchement hôte : `frontend/index.html` affirme le statut depuis la réponse de `/api/auth/me` déjà chargée pour l'avatar — `setAdmin(u.role === "admin")`. Le rôle global du compte chez ObsiGate est la définition même de l'administrateur de suite (c'est lui que la garde `/admin/*` du Hub vérifie, via ce même endpoint) : ObsiGate le connaît nativement. Tout autre rôle, ou échec de l'appel : aucune affirmation, l'entrée reste masquée. Garde : `tests/test_draco_barre.py` (`test_barre_v060_entree_administration`). + +--- + ## [2.86.0] — 2026-11-06 - **BUG-122 — Déconnexion globale navigable : `GET /sso/logout`** (fusion #141) : le bouton « Déconnexion » du menu compte de la barre commune ne faisait rien dans les applications sœurs — la barre relaie l'action à l'hôte, et la déconnexion globale d'ObsiGate (ACC-3.1, #216) n'était appelable qu'en same-origin depuis l'interface d'ObsiGate (`POST /api/auth/logout`). Nouvelle route `GET /sso/logout?return_to=`, symétrique de `/sso/authorize` : elle exécute la déconnexion globale — logique extraite dans `apply_global_logout()` (`backend/auth/router.py`), désormais partagée par les deux entrées (jetons natifs refresh/access révoqués, `jti` du cookie `draco_session` révoqué, cookie effacé sur le domaine parent) — puis redirige vers le `return_to` validé par la liste blanche existante (`validate_return_to` : https + `*.dracodev.net`, repli « / »). Idempotente sans session. Les applications sœurs branchent leur action `deconnexion` sur cette route (leur session locale est détruite d'abord chez celles qui en ont une) — et la barre d'ObsiGate elle-même aussi (`frontend/index.html`, retour à sa racine). Tests : `tests/test_draco_session.py` (+3 : révocation et redirection, liste blanche, idempotence), `tests/test_draco_barre.py` (+1 : branchement de la barre d'ObsiGate). Fiche : `docs/features/deconnexion-globale-bug-122.md`. diff --git a/README.fr.md b/README.fr.md index ed82e32..c247819 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. -[![Version](https://img.shields.io/badge/Version-2.86.0-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.86.1-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.86.0). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.86.1). --- -*Projet : ObsiGate | Version : 2.86.0 | Dernière mise à jour : Septembre 2026* +*Projet : ObsiGate | Version : 2.86.1 | Dernière mise à jour : Septembre 2026* diff --git a/README.md b/README.md index 146d194..39739b8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. -[![Version](https://img.shields.io/badge/Version-2.86.0-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.86.1-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.86.0). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.86.1). --- -*Project: ObsiGate | Version: 2.86.0 | Last updated: September 2026* +*Project: ObsiGate | Version: 2.86.1 | Last updated: September 2026* diff --git a/VERSION b/VERSION index c994171..32ea999 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.86.0 +2.86.1 diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index 529be6c..46934a7 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.86.0" +version = "2.86.1" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 683d432..456f1bb 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.86.0" +version = "2.86.1" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index e40f5a5..05d710e 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.86.0", + "version": "2.86.1", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 2fa3bf8..f8d5c8b 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.86.0 | **Dernière mise à jour :** 2026-11-06 +> **Version :** 2.86.1 | **Dernière mise à jour :** 2026-11-06 > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > vers les fonctionnalités livrées. > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** diff --git a/frontend/draco-barre.css b/frontend/draco-barre.css index 55ab916..431f81a 100644 --- a/frontend/draco-barre.css +++ b/frontend/draco-barre.css @@ -101,6 +101,11 @@ text-decoration: none; cursor: pointer; } .draco-menu-item:hover, .draco-menu-item:focus-visible { background: var(--surface-2); } +/* Item conditionnel masqué (v0.6.0 : « Administration » tant que + l'hôte n'a pas affirmé le statut d'administrateur) — le + display:flex ci-dessus écraserait l'attribut hidden sans cette + règle, même patron que .draco-compte-menu[hidden]. */ +.draco-menu-item[hidden] { display: none; } /* Icônes monoline du menu compte (v0.4.0) : appendues après le libellé dans le DOM, replacées à gauche par order: -1. */ .draco-menu-icone { diff --git a/frontend/draco-barre.js b/frontend/draco-barre.js index 7b66b81..ff7e3d1 100644 --- a/frontend/draco-barre.js +++ b/frontend/draco-barre.js @@ -42,6 +42,22 @@ * l'ouverture par le mécanisme data-theme des hôtes, avec cache * local court et repli intégral sur le comportement actuel de * l'hôte si le Hub ne répond pas. Voir « Thème commun » plus bas. + * v0.6.0 (demande de Bruno, 2026-11-06) : le menu compte gagne un + * cinquième lien Hub, « Administration » → /admin (le Hub y + * redirige vers /admin/utilisateurs), placé après le séparateur, + * avant « Déconnexion », avec son icône monoline (curseurs de + * réglage). AFFICHAGE CONDITIONNEL : l'entrée n'apparaît que si + * l'hôte AFFIRME que le compte courant est administrateur de + * suite — cfg.admin === true à la configuration, ou + * DRACO_BAR.setAdmin(true) quand l'hôte l'apprend après coup + * (patron setUrls). L'hôte ne passe le drapeau que s'il l'a établi + * depuis SA source d'autorité (rôle global du compte chez + * ObsiGate, niveau du registre des rôles du Hub pour son app…) ; + * à défaut de certitude, pas de drapeau et l'entrée reste masquée. + * La barre ne devine jamais : le jeton draco ne porte aucun rôle + * (claims minimaux, par design). La vraie protection reste la + * garde serveur du Hub (/admin/*), pas ce masquage d'affichage. + * Voir le README, « Administration » — contrat de visibilité. * * Aucun gestionnaire inline : un seul écouteur délégué sur le document * (patron validé par le prototype — les onclick du HTML généré sont @@ -54,6 +70,11 @@ * counts: { tiktak: 3, kanbante: 3, alertes: 3, spark: 0 }, * user: "Bruno", // nom affiché (v0.1.6) ; * // ou { display_name: "Bruno", username: "bruno" } + * admin: true, // (v0.6.0, optionnel) + * // administrateur de suite ÉTABLI par l'hôte : démasque + * // l'entrée « Administration » du menu compte. Absent ou + * // non-strictement-true → entrée masquée. Peut aussi être + * // posé après coup via DRACO_BAR.setAdmin(true). * urls: { // bases des applications * obsigate: "https://og.dracodev.net/", * flowdeck: "https://flowdeck.dracodev.net/", @@ -653,6 +674,18 @@ ["path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }], ["circle", { cx: "12", cy: "7", r: "4" }] ], + /* Curseurs de réglage (trois axes, trois boutons) — v0.6.0. */ + "admin": [ + ["line", { x1: "4", y1: "21", x2: "4", y2: "14" }], + ["line", { x1: "4", y1: "10", x2: "4", y2: "3" }], + ["line", { x1: "12", y1: "21", x2: "12", y2: "12" }], + ["line", { x1: "12", y1: "8", x2: "12", y2: "3" }], + ["line", { x1: "20", y1: "21", x2: "20", y2: "16" }], + ["line", { x1: "20", y1: "12", x2: "20", y2: "3" }], + ["line", { x1: "1", y1: "14", x2: "7", y2: "14" }], + ["line", { x1: "9", y1: "8", x2: "15", y2: "8" }], + ["line", { x1: "17", y1: "16", x2: "23", y2: "16" }] + ], /* Flèche sortant d'un cadre (porte). */ "deconnexion": [ ["path", { d: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" }], @@ -687,6 +720,15 @@ return span; } + /* Visibilité de l'entrée « Administration » (v0.6.0) : vraie + * seulement si l'hôte l'a AFFIRMÉE — cfg.admin === true à la + * configuration, ou DRACO_BAR.setAdmin(true) après coup. La + * barre ne déduit jamais ce statut (le jeton draco ne porte + * aucun rôle) ; à défaut, l'entrée reste masquée et la garde + * serveur du Hub reste la vraie protection. */ + var compteAdmin = cfg.admin === true; + var adminItem = null; + /* Menu compte (v0.1.6 ; v0.3.0 pour les liens) — en-tête * d'identité, puis les liens du Hub (Télécharger, À Propos, * Aides, Profil — vrais , même patron que le menu des @@ -719,6 +761,22 @@ sep.className = "draco-menu-sep"; sep.setAttribute("aria-hidden", "true"); compteMenu.appendChild(sep); + /* « Administration » (v0.6.0) : cinquième lien Hub + * (/admin, où le Hub redirige vers /admin/utilisateurs). + * data-hub-link="admin" : son href est resynchronisé par + * refreshCompteHrefs / setUrls comme les quatre autres liens. + * Toujours construit ; masqué tant que l'hôte n'a pas affirmé + * le statut d'administrateur (compteAdmin). */ + var adm = document.createElement("a"); + adm.className = "draco-menu-item"; + adm.href = hubHref("admin"); + adm.setAttribute("data-hub-link", "admin"); + adm.setAttribute("role", "menuitem"); + adm.textContent = "Administration"; + adm.appendChild(menuIcone("admin")); + adm.hidden = !compteAdmin; + compteMenu.appendChild(adm); + adminItem = adm; var b = document.createElement("button"); b.type = "button"; b.className = "draco-menu-item"; @@ -734,6 +792,7 @@ var head = compteMenu.querySelector(".draco-compte-nom"); var name = displayName(); if (head) head.textContent = name || "Compte"; + if (adminItem) adminItem.hidden = !compteAdmin; refreshCompteHrefs(); } function toggleCompteMenu(force) { @@ -835,6 +894,14 @@ window.DRACO_BAR.urls = resolvedUrls; applyUrls(resolvedUrls); refreshCompteHrefs(); + }, + /* Administration (v0.6.0) : l'hôte pose le drapeau quand il a + * ÉTABLI que le compte courant est administrateur de suite + * (sa source d'autorité — voir l'en-tête et le README). Toute + * valeur autre que le booléen true masque l'entrée. */ + setAdmin: function (v) { + compteAdmin = v === true; + if (adminItem) adminItem.hidden = !compteAdmin; } }; diff --git a/frontend/index.html b/frontend/index.html index f5d5127..214f4ce 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -439,6 +439,17 @@ var av = document.getElementById("draco-avatar"); if (av) { av.textContent = nom.charAt(0).toUpperCase(); av.title = nom; } } + /* Barre v0.6.0 (commons) : entrée « Administration » + du menu compte. Le rôle global du compte chez + ObsiGate EST la définition de l'administrateur + de suite (c'est lui que la garde /admin/* du Hub + vérifie, via ce même /api/auth/me) — ObsiGate le + connaît nativement, il l'affirme à la barre. + Tout autre rôle (ou échec) : pas d'affirmation, + l'entrée reste masquée. */ + if (u && window.DRACO_BAR && window.DRACO_BAR.setAdmin) { + window.DRACO_BAR.setAdmin(u.role === "admin"); + } }).catch(function () { /* avatar générique conservé */ }); }); diff --git a/frontend/js/auth.js b/frontend/js/auth.js index 21e9072..6464168 100644 --- a/frontend/js/auth.js +++ b/frontend/js/auth.js @@ -386,6 +386,12 @@ const AuthManager = { const section = document.getElementById("sidebar-user"); if (!user || !this._authEnabled) { if (section) section.hidden = true; + // Barre commune v0.6.0 (fusion #142) : sans identité + // établie, l'entrée « Administration » du menu compte + // reste masquée. + if (window.DRACO_BAR && window.DRACO_BAR.setAdmin) { + window.DRACO_BAR.setAdmin(false); + } return; } if (!section) return; @@ -427,6 +433,19 @@ const AuthManager = { }); } + // Barre commune v0.6.0 (fusion #142) : l'entrée + // « Administration » du menu compte suit le rôle natif du + // compte — la définition même de l'administrateur de suite + // (garde /admin/* du Hub), même source que la rangée + // d'administration native ci-dessous. renderUserSection() + // est appelée par showApp() pour toutes les sessions + // établies (connexion, restauration, renouvellement + // silencieux) ; le fetch d'amorçage d'index.html ne couvre + // que les sessions déjà présentes au chargement de la page. + if (window.DRACO_BAR && window.DRACO_BAR.setAdmin) { + window.DRACO_BAR.setAdmin(user.role === "admin"); + } + const adminRow = document.getElementById("admin-menu-row"); if (adminRow) { if (user.role === "admin") { diff --git a/package.json b/package.json index 7d90d49..cf98e05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.86.0", + "version": "2.86.1", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "main": "patch.js", "directories": { diff --git a/tests/test_draco_barre.py b/tests/test_draco_barre.py index 4f1e9ea..fc5b918 100644 --- a/tests/test_draco_barre.py +++ b/tests/test_draco_barre.py @@ -101,6 +101,38 @@ def test_barre_v030_liens_hub_menu_compte(): assert "v0.3.0" in html +def test_barre_v060_entree_administration(): + # Barre v0.6.0 (dracodev-commons, demande de Bruno du + # 2026-11-06) : entrée « Administration » du menu compte — + # lien Hub /admin à visibilité conditionnelle. Le JS + # vendangé la construit (data-hub-link="admin"), masquée tant + # que l'hôte n'affirme pas le statut d'administrateur + # (cfg.admin / DRACO_BAR.setAdmin) ; le CSS porte la règle + # [hidden] qui la masque malgré le display:flex des items. + # Branchement hôte : index.html affirme le statut depuis le + # rôle global de /api/auth/me — la définition même de + # l'administrateur de suite (garde /admin/* du Hub). + js = (FRONTEND / "draco-barre.js").read_text(encoding="utf-8") + assert 'data-hub-link", "admin"' in js + assert '"Administration"' in js + assert "setAdmin" in js + assert "compteAdmin = cfg.admin === true" in js + css = _css() + assert ".draco-menu-item[hidden]" in css + html = _index() + assert 'window.DRACO_BAR.setAdmin(u.role === "admin")' in html + # Le fetch d'amorçage d'index.html tourne avant toute + # connexion : le branchement qui couvre la connexion par le + # formulaire vit dans renderUserSection() (js/auth.js — le + # frontend servi est en modules ES ; le app.js monolithique + # à la racine n'est plus chargé), appelée par showApp() pour + # toutes les sessions établies, au même endroit que la + # rangée d'administration native. + auth_js = (FRONTEND / "js" / "auth.js").read_text(encoding="utf-8") + assert "window.DRACO_BAR.setAdmin(user.role === \"admin\")" in auth_js + assert "window.DRACO_BAR.setAdmin(false)" in auth_js + + def test_panneau_spark_integre_218(): # #218 : le bouton Spark de la barre ouvre le panneau de l'agent # commun (mêmes assets partagés que TikTak / Flowdeck / Kanbante).