mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 09:14:33 +00:00
feat(appstore): federated Mesh Catalog over the annuaire directory — 0.3.0 (ref #768)
This commit is contained in:
parent
9ee985bdb1
commit
8fdd95aa55
|
|
@ -156,6 +156,63 @@ async def catalog(category: Optional[str] = None, tier: Optional[str] = None,
|
|||
return {"modules": items, "count": len(items), "total": len(st), "board_tier": board_tier()}
|
||||
|
||||
|
||||
# ── Mesh catalog — federated view over the annuaire directory (#768, phase 2) ──
|
||||
# Every node holds the converged gondwana directory. We read it read-only (the
|
||||
# journal is owned by `secubox`, our own user) and join each ServiceOffer to the
|
||||
# publishing node's NodeRecord, so the appstore shows a fleet-wide catalog:
|
||||
# which service is offered/emancipated, by which node, over which channel, and
|
||||
# how to reach it. Graceful no-op when secubox-annuaire is not installed.
|
||||
|
||||
ANNUAIRE_LIB = "/usr/lib/secubox/annuaire"
|
||||
ANNUAIRE_DB = os.environ.get("ANNUAIRE_DB_PATH", "/var/lib/secubox/annuaire/journal.db")
|
||||
|
||||
|
||||
def _read_directory():
|
||||
import sys # noqa: PLC0415
|
||||
if ANNUAIRE_LIB not in sys.path:
|
||||
sys.path.insert(0, ANNUAIRE_LIB)
|
||||
try:
|
||||
from annuaire.log import Journal # noqa: PLC0415
|
||||
from annuaire.verbs import _get_nodes, _get_offers # noqa: PLC0415
|
||||
except Exception:
|
||||
return [], {}
|
||||
if not os.path.exists(ANNUAIRE_DB):
|
||||
return [], {}
|
||||
try:
|
||||
j = Journal(ANNUAIRE_DB)
|
||||
return _get_offers(j), {n["did"]: n for n in _get_nodes(j)}
|
||||
except Exception:
|
||||
return [], {}
|
||||
|
||||
|
||||
@app.get("/mesh-catalog")
|
||||
async def mesh_catalog():
|
||||
offers, nodes = _read_directory()
|
||||
items = []
|
||||
for o in offers:
|
||||
prov = o.get("provider")
|
||||
node = nodes.get(prov, {})
|
||||
scope = o.get("scope") or {}
|
||||
items.append({
|
||||
"name": o.get("name"),
|
||||
"kind": o.get("kind"),
|
||||
"endpoint": o.get("endpoint"),
|
||||
"channel": scope.get("channel"),
|
||||
"port": scope.get("port"),
|
||||
"approval_mode": o.get("approval_mode"),
|
||||
"provider_did": prov,
|
||||
"provider_node": node.get("boxname") or "?",
|
||||
"provider_mesh_ip": node.get("mesh_ip"),
|
||||
})
|
||||
items.sort(key=lambda x: (x.get("provider_node") or "", x.get("name") or ""))
|
||||
return {
|
||||
"catalog": items,
|
||||
"nodes": [{"boxname": n.get("boxname"), "mesh_ip": n.get("mesh_ip"),
|
||||
"ddns": n.get("ddns"), "did": n.get("did")} for n in nodes.values()],
|
||||
"count": len(items), "node_count": len(nodes),
|
||||
}
|
||||
|
||||
|
||||
@app.get("/module/{name}")
|
||||
async def module(name: str):
|
||||
st = compute_state()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,14 @@
|
|||
secubox-appstore (0.3.0-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* feat(#768): federated Mesh Catalog (appstore federation phase 2).
|
||||
GET /mesh-catalog reads this node's converged annuaire directory (read-only)
|
||||
and joins each ServiceOffer to the publishing node's NodeRecord, so the app
|
||||
store shows a fleet-wide view: which service is offered/emancipated, by which
|
||||
node, over which channel, and how to reach it. New "Mesh Catalog" panel in
|
||||
the UI. Graceful no-op when secubox-annuaire is absent.
|
||||
|
||||
-- Gerald Kerma <devel@cybermind.fr> Wed, 01 Jul 2026 11:00:00 +0200
|
||||
|
||||
secubox-appstore (0.2.2-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* fix(menu): install the navbar entry to /usr/share/secubox/menu.d (the
|
||||
|
|
|
|||
|
|
@ -68,6 +68,18 @@
|
|||
<span class="as-stat" id="stat">loading…</span>
|
||||
</div>
|
||||
<div class="as-grid" id="grid"></div>
|
||||
|
||||
<section id="mesh-panel" style="margin-top:24px">
|
||||
<h2 style="font-family:'Cinzel',serif;color:var(--gold-hermetic,#c9a84c);font-size:1rem;margin:0">
|
||||
🌐 Mesh Catalog <span id="mesh-stat" style="font-size:12px;color:var(--text-muted,#6b6b7a);font-family:'JetBrains Mono',monospace">—</span></h2>
|
||||
<p style="font-size:11px;color:var(--text-muted,#6b6b7a);margin:3px 0 8px;font-family:'JetBrains Mono',monospace">
|
||||
Services federated across the gondwana mesh (annuaire directory) — which node offers each, over which channel, and how to reach it.</p>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:12px">
|
||||
<thead><tr style="text-align:left;color:var(--gold-hermetic,#c9a84c)">
|
||||
<th style="padding:6px 8px">Service</th><th>Kind</th><th>Node</th><th>Channel</th><th>Reach</th></tr></thead>
|
||||
<tbody id="mesh-list"><tr><td colspan="5" style="padding:10px;color:var(--text-muted,#6b6b7a)">loading…</td></tr></tbody>
|
||||
</table>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Config / detail drawer -->
|
||||
|
|
@ -105,6 +117,25 @@
|
|||
ALL=(data&&Array.isArray(data.modules))?data.modules:[];
|
||||
document.getElementById('board-badge').textContent='tier '+((cats&&cats.board_tier)||'?');
|
||||
render();
|
||||
loadMesh();
|
||||
}
|
||||
|
||||
async function loadMesh(){
|
||||
const d=await jget('/mesh-catalog');
|
||||
const tb=document.getElementById('mesh-list'), st=document.getElementById('mesh-stat');
|
||||
const items=(d&&Array.isArray(d.catalog))?d.catalog:[];
|
||||
st.textContent=items.length?(items.length+' service(s) · '+(d.node_count||0)+' node(s)'):'no federated services';
|
||||
if(!items.length){tb.innerHTML='<tr><td colspan="5" style="padding:10px;color:var(--text-muted,#6b6b7a)">No services emancipated across the mesh yet.</td></tr>';return;}
|
||||
tb.innerHTML=items.map(s=>{
|
||||
const reach=s.endpoint?('<a href="'+esc(s.endpoint)+'" target="_blank" rel="noopener" style="color:var(--cyber-cyan,#00d4ff)">'+esc(s.endpoint)+'</a>'):'—';
|
||||
const ip=s.provider_mesh_ip?(' <span style="color:var(--text-muted,#6b6b7a)">('+esc(s.provider_mesh_ip)+')</span>'):'';
|
||||
return '<tr style="border-top:1px solid var(--line,#2a2a3a)">'
|
||||
+'<td style="padding:6px 8px">'+esc(s.name)+'</td>'
|
||||
+'<td><span style="font-size:10px;padding:1px 6px;border:1px solid var(--line,#2a2a3a);border-radius:10px;color:var(--cyber-cyan,#00d4ff)">'+esc(s.kind||'')+'</span></td>'
|
||||
+'<td style="font-family:\'JetBrains Mono\',monospace">'+esc(s.provider_node||'?')+ip+'</td>'
|
||||
+'<td>'+esc(s.channel||'—')+'</td>'
|
||||
+'<td style="font-family:\'JetBrains Mono\',monospace">'+reach+'</td></tr>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function actionsFor(m){
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user