diff --git a/packages/secubox-release/menu.d/590-releases.json b/packages/secubox-release/menu.d/590-releases.json new file mode 100644 index 00000000..bcc8a5ba --- /dev/null +++ b/packages/secubox-release/menu.d/590-releases.json @@ -0,0 +1,10 @@ +{ + "id": "releases", + "name": "Releases", + "icon": "🚀", + "path": "/releases/", + "category": "mesh", + "order": 590, + "description": "Rings apt (draft/internal/published) — évolutions, promote/demote, assignation box×ring", + "requires": ["secubox-annuaire"] +} diff --git a/packages/secubox-release/nginx/releases.conf b/packages/secubox-release/nginx/releases.conf new file mode 100644 index 00000000..372af630 --- /dev/null +++ b/packages/secubox-release/nginx/releases.conf @@ -0,0 +1,17 @@ +# /etc/nginx/secubox.d/releases.conf — Installed by secubox-release. +# Static "Releases" operator panel (evolutions×ring, box×ring assign) + +# API proxy to the standalone secubox-release.service over +# /run/secubox/release.sock (no aggregator dependency). + +location /releases/ { + alias /usr/share/secubox/www/releases/; + index index.html; + try_files $uri $uri/ /releases/index.html; +} + +location /api/v1/release/ { + rewrite ^/api/v1/release/(.*)$ /$1 break; + proxy_pass http://unix:/run/secubox/release.sock; + include /etc/nginx/snippets/secubox-proxy.conf; + proxy_intercept_errors on; +} diff --git a/packages/secubox-release/tests/test_menu.py b/packages/secubox-release/tests/test_menu.py new file mode 100644 index 00000000..17636c14 --- /dev/null +++ b/packages/secubox-release/tests/test_menu.py @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# Source-Disclosed License — All rights reserved except as expressly granted. +# See LICENCE-CMSD-1.0.md for terms. +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent + + +VALID_CATEGORIES = {"auth", "wall", "boot", "mind", "root", "mesh"} + + +def test_menu_is_valid_json_with_releases_path(): + m = json.loads((ROOT / "menu.d" / "590-releases.json").read_text()) + blob = json.dumps(m) + assert "/releases" in blob + assert isinstance(m.get("name"), str) and m.get("name") + assert m.get("category") in VALID_CATEGORIES + + +def test_panel_uses_sbx_token_and_sidebar_js(): + html = (ROOT / "www" / "releases" / "index.html").read_text() + assert "sbx_token" in html + assert "/shared/sidebar.js" in html + + +def test_panel_has_no_inline_onclick_or_innerhtml(): + html = (ROOT / "www" / "releases" / "index.html").read_text() + assert "onclick=" not in html + assert "innerHTML" not in html + + +def test_panel_has_promote_demote_assign_hooks(): + html = (ROOT / "www" / "releases" / "index.html").read_text() + # promote/demote rows are built per-evolution via createElement + dataset + # (no innerHTML allowed), so the hook shows as a dataset.act assignment; + # assign/publish are static buttons with a literal data-act attribute. + assert "dataset.act = 'promote'" in html + assert "dataset.act = 'demote'" in html + assert 'data-act="assign"' in html + assert 'data-act="publish"' in html diff --git a/packages/secubox-release/www/releases/index.html b/packages/secubox-release/www/releases/index.html new file mode 100644 index 00000000..f8c93faf --- /dev/null +++ b/packages/secubox-release/www/releases/index.html @@ -0,0 +1,251 @@ + + + + +SecuBox — Releases + + + + +
+

🚀 Releases

+ + +
+

Évolutions × rings

+ + + +
ÉvolutionRingActions
+ +

Publier une évolution

+
+ +
+
+ + +
+
+ +
+

Box Ă— ring

+

Assigner un ring

+
+ + + +
+
+
+ + +