@lifeaitools/clauth 1.30.11 → 1.30.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -989,6 +989,8 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
989
989
  .supervisor-name{font-size:.82rem;color:#e2e8f0;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
990
990
  .supervisor-row-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}
991
991
  .supervisor-log{font-family:'Courier New',monospace;font-size:.7rem;color:#94a3b8;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow:auto}
992
+ .supervisor-feedback{margin-top:8px;padding:7px 9px;border-radius:6px;font-family:'Courier New',monospace;font-size:.72rem;color:#86efac;background:rgba(34,197,94,.08);border:1px solid rgba(74,222,128,.22);display:none}
993
+ .supervisor-feedback.bad{color:#fecaca;background:rgba(248,113,113,.08);border-color:rgba(248,113,113,.25)}
992
994
  </style>
993
995
  </head>
994
996
  <body>
@@ -1216,6 +1218,7 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
1216
1218
  </div>
1217
1219
  </div>
1218
1220
  <div id="supervisor-status" class="supervisor-sub">Loading supervisor state…</div>
1221
+ <div id="supervisor-feedback" class="supervisor-feedback" role="status"></div>
1219
1222
  <div class="supervisor-grid" style="margin-top:10px">
1220
1223
  <div class="supervisor-card">
1221
1224
  <h4>Plugins</h4>
@@ -1506,6 +1509,14 @@ async function supervisorJson(path, options) {
1506
1509
  return data;
1507
1510
  }
1508
1511
 
1512
+ function supervisorFeedback(text, bad) {
1513
+ const el = document.getElementById("supervisor-feedback");
1514
+ if (!el) return;
1515
+ el.textContent = text || "";
1516
+ el.className = "supervisor-feedback" + (bad ? " bad" : "");
1517
+ el.style.display = text ? "block" : "none";
1518
+ }
1519
+
1509
1520
  async function loadSupervisorCockpit() {
1510
1521
  const status = document.getElementById("supervisor-status");
1511
1522
  if (status) status.textContent = "Loading supervisor state…";
@@ -1530,9 +1541,10 @@ async function loadSupervisorCockpit() {
1530
1541
  const label = e.kind === "operation" ? (e.action + " " + JSON.stringify(e.target || {})) : (e.kind + " " + (e.plugin_id || ""));
1531
1542
  return (e.ts || e.created_at || "") + " " + label;
1532
1543
  }).join("\\n") || "No events yet.";
1533
- if (status) status.innerHTML = supervisorBadge("supervisor " + (health.status || "unknown"), "ok") + supervisorBadge("vault " + (health.vault_locked ? "locked" : "unlocked"), health.vault_locked ? "warn" : "ok") + supervisorBadge("v" + (health.clauth_version || "${VERSION}"), "");
1544
+ if (status) status.innerHTML = supervisorBadge("supervisor " + (health.status === "ok" ? "healthy" : (health.status || "unknown")), health.status === "ok" ? "ok" : "warn") + supervisorBadge("vault " + (health.vault_locked ? "locked" : "unlocked"), health.vault_locked ? "warn" : "ok") + supervisorBadge("v" + (health.clauth_version || "${VERSION}"), "");
1534
1545
  } catch (err) {
1535
1546
  if (status) status.innerHTML = supervisorBadge("supervisor unreachable", "bad") + " " + htmlEscape(err.message || err);
1547
+ supervisorFeedback("Supervisor unavailable — actions are disabled until the local control plane returns.", true);
1536
1548
  }
1537
1549
  }
1538
1550
 
@@ -1546,31 +1558,36 @@ function renderSupervisorPlugin(plugin) {
1546
1558
 
1547
1559
  function renderSupervisorSurface(surface) {
1548
1560
  const ownerKind = surface.lifecycle_owner === "clauth" ? "ok" : (surface.lifecycle_owner === "plugin" ? "warn" : "");
1549
- return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(surface.plugin_id + ":" + surface.id) + '</span>' + supervisorBadge(surface.lifecycle_owner || "unknown", ownerKind) + '</div>' +
1561
+ const stateKind = surface.state === "current" || surface.status === "healthy" ? "ok" : (surface.state === "unavailable" ? "bad" : "warn");
1562
+ return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(surface.plugin_id + ":" + surface.id) + '</span>' + supervisorBadge(surface.lifecycle_owner || "unknown", ownerKind) + supervisorBadge(surface.state || surface.status || "unknown", stateKind) + '</div>' +
1550
1563
  '<div class="supervisor-meta">' + htmlEscape(surface.destination || "—") + ' · port ' + htmlEscape(surface.port || "—") + '</div>' +
1551
1564
  '<div class="supervisor-meta">' + htmlEscape(surface.health || surface.health_url || "no health") + '</div>' +
1552
- '<div class="supervisor-row-actions">' + ["reconcile","restart","test","rollback"].map(a => '<button class="supervisor-action" onclick="runSupervisorSurface(' + jsArg(surface.id) + ',' + jsArg(a) + ')">' + a + '</button>').join("") + '</div></div>';
1565
+ '<div class="supervisor-row-actions">' + ["start","stop","restart","reconcile","test","promote","rollback"].map(a => '<button class="supervisor-action" data-supervisor-action="' + a + '" onclick="runSupervisorSurface(' + jsArg(surface.id) + ',' + jsArg(a) + ')">' + a + '</button>').join("") + '</div></div>';
1553
1566
  }
1554
1567
 
1555
1568
  async function rescanSupervisorPlugins() {
1556
1569
  try {
1557
1570
  await supervisorJson("/v1/plugins/rescan", { method: "POST", headers: writeHeaders() });
1571
+ supervisorFeedback("Plugin rescan completed.", false);
1558
1572
  await loadSupervisorCockpit();
1559
- } catch (err) { alert("Supervisor rescan failed: " + (err.message || err)); }
1573
+ } catch (err) { supervisorFeedback("Plugin rescan failed: " + (err.message || err), true); }
1560
1574
  }
1561
1575
 
1562
1576
  async function runSupervisorPlugin(id, action) {
1563
1577
  try {
1564
- await supervisorJson("/v1/plugins/" + encodeURIComponent(id) + "/" + encodeURIComponent(action), { method: "POST", headers: writeHeaders() });
1578
+ const receipt = await supervisorJson("/v1/plugins/" + encodeURIComponent(id) + "/" + encodeURIComponent(action), { method: "POST", headers: writeHeaders() });
1579
+ supervisorFeedback("Plugin " + action + " receipt " + (receipt.operationId || "recorded") + " · " + ((receipt.resulting_state && receipt.resulting_state.state) || "completed"), false);
1565
1580
  await loadSupervisorCockpit();
1566
- } catch (err) { alert("Plugin " + action + " failed: " + (err.message || err)); }
1581
+ } catch (err) { supervisorFeedback("Plugin " + action + " failed: " + (err.message || err), true); }
1567
1582
  }
1568
1583
 
1569
1584
  async function runSupervisorSurface(id, action) {
1570
1585
  try {
1571
- await supervisorJson("/v1/surfaces/" + encodeURIComponent(id) + "/actions", { method: "POST", headers: writeHeaders({ "Content-Type": "application/json" }), body: JSON.stringify({ action }) });
1586
+ const receipt = await supervisorJson("/v1/surfaces/" + encodeURIComponent(id) + "/actions", { method: "POST", headers: writeHeaders({ "Content-Type": "application/json" }), body: JSON.stringify({ action }) });
1587
+ const result = receipt.resulting_state || {};
1588
+ supervisorFeedback("Surface " + action + " receipt " + (receipt.operationId || "recorded") + " · " + (result.state || "completed"), result.ok === false);
1572
1589
  await loadSupervisorCockpit();
1573
- } catch (err) { alert("Surface " + action + " failed: " + (err.message || err)); }
1590
+ } catch (err) { supervisorFeedback("Surface " + action + " failed: " + (err.message || err), true); }
1574
1591
  }
1575
1592
 
1576
1593
  // ── Unlock ──────────────────────────────────
@@ -0,0 +1,16 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here = path.dirname(fileURLToPath(import.meta.url));
8
+ const serveSource = fs.readFileSync(path.join(here, 'commands', 'serve.js'), 'utf8');
9
+
10
+ test('clauth supervisor UI exposes every lifecycle action and receipt feedback', () => {
11
+ for (const action of ['start', 'stop', 'restart', 'reconcile', 'test', 'promote', 'rollback']) {
12
+ assert.match(serveSource, new RegExp('"' + action + '"'));
13
+ }
14
+ assert.match(serveSource, /supervisorFeedback\(/);
15
+ assert.match(serveSource, /supervisor-feedback/);
16
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.30.11",
3
+ "version": "1.30.12",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {