@enigmax/dashboard 0.1.22 → 0.1.25
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.
- package/assets/index.html +91 -11
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
.set-ctl { flex-shrink: 0; }
|
|
159
159
|
.toggle {
|
|
160
160
|
background: var(--surface2); color: var(--muted); border: 1px solid var(--border);
|
|
161
|
-
border-radius: var(--radius-sm); padding: 6px 16px; font-size: 12px; cursor: pointer; font-family: var(--mono); min-width: 56px;
|
|
161
|
+
border-radius: var(--radius-sm); padding: 6px 16px; font-size: 12px; cursor: pointer; font-family: var(--mono); min-width: 56px; text-decoration: none;
|
|
162
162
|
transition: color .15s, background .15s, border-color .15s;
|
|
163
163
|
}
|
|
164
164
|
.toggle:hover { color: var(--text); border-color: #2f3744; }
|
|
@@ -212,6 +212,14 @@
|
|
|
212
212
|
border-radius: 7px; padding: 5px 14px; font-size: 12px; cursor: pointer; font-family: inherit;
|
|
213
213
|
}
|
|
214
214
|
.btn-danger:hover { border-color: var(--accent2); }
|
|
215
|
+
.btn-danger.icon { display: inline-flex; align-items: center; justify-content: center; padding: 6px 9px; }
|
|
216
|
+
.btn-danger.icon:hover { color: var(--bad); border-color: var(--bad); }
|
|
217
|
+
.btn-danger svg { width: 15px; height: 15px; display: block; }
|
|
218
|
+
/* Per-agent skill toggles: one pill per app (Claude Code, Codex, OpenCode), green when the skill is on for it. */
|
|
219
|
+
.set-agents { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
|
220
|
+
.achip { font-family: var(--mono); font-size: 11px; padding: 3px 10px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface2); color: var(--muted); cursor: pointer; text-decoration: none; transition: color .15s, background .15s, border-color .15s; }
|
|
221
|
+
.achip:hover { border-color: #2f3744; color: var(--text); }
|
|
222
|
+
.achip.on { background: var(--good); color: #06210f; border-color: var(--good); font-weight: 600; }
|
|
215
223
|
/* List settings (guard block/allow globs, custom secret patterns): chips + add input. A list row spans the full grid width. */
|
|
216
224
|
.set-row.list { grid-column: 1 / -1; align-items: stretch; flex-direction: column; }
|
|
217
225
|
.lchips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
|
|
@@ -1652,8 +1660,11 @@ export {
|
|
|
1652
1660
|
} else if (w.resetsAt) {
|
|
1653
1661
|
const start = w.resetsAt - span;
|
|
1654
1662
|
barPct = Math.max(0, Math.min(100, (Date.now() - start) / span * 100));
|
|
1655
|
-
|
|
1656
|
-
|
|
1663
|
+
// No usage % available (no live rate-limit headers, no plan limit): the bar
|
|
1664
|
+
// shows window time-elapsed. Use the brand accent, not dead grey. The green/
|
|
1665
|
+
// yellow/orange/red consumption scale needs a real % (set a limit, or live data).
|
|
1666
|
+
barCol = "var(--accent)";
|
|
1667
|
+
barNote = "window " + Math.round(barPct) + "% elapsed - set a limit for usage %";
|
|
1657
1668
|
valLine = fmt(w.used || 0) + " tok";
|
|
1658
1669
|
} else {
|
|
1659
1670
|
barPct = 0; barCol = "var(--muted)"; barNote = ""; valLine = fmt(w.used || 0) + " tok";
|
|
@@ -1671,6 +1682,7 @@ export {
|
|
|
1671
1682
|
$("uWindows").innerHTML = '<div class="wgrid">'
|
|
1672
1683
|
+ windowCard(w.session, "session")
|
|
1673
1684
|
+ windowCard(w.weeklyAll, "weekly")
|
|
1685
|
+
+ (w.weeklyOpus ? windowCard(w.weeklyOpus, "weeklyOpus") : "")
|
|
1674
1686
|
+ windowCard(w.weeklySonnet, "weeklySonnet")
|
|
1675
1687
|
+ "</div>";
|
|
1676
1688
|
}
|
|
@@ -1905,11 +1917,19 @@ export {
|
|
|
1905
1917
|
// update), so the served UI may be newer than this tab - reload once so a tab you
|
|
1906
1918
|
// left open self-heals instead of showing stale markup.
|
|
1907
1919
|
let loadedBoot = null;
|
|
1920
|
+
// The UI bundle version this tab loaded against. A change means the served
|
|
1921
|
+
// @enigmax/dashboard bundle was swapped underneath us (a background update found a
|
|
1922
|
+
// newer version) - reload once so the tab picks up the new markup/scripts.
|
|
1923
|
+
let loadedUi = null;
|
|
1908
1924
|
function render(data) {
|
|
1909
1925
|
if (data.boot != null) {
|
|
1910
1926
|
if (loadedBoot === null) loadedBoot = data.boot;
|
|
1911
1927
|
else if (data.boot !== loadedBoot) { location.reload(); return; }
|
|
1912
1928
|
}
|
|
1929
|
+
if (data.ui != null) {
|
|
1930
|
+
if (loadedUi === null) loadedUi = data.ui;
|
|
1931
|
+
else if (data.ui !== loadedUi) { location.reload(); return; }
|
|
1932
|
+
}
|
|
1913
1933
|
priceOverride = Number(data.priceOverride) || 0;
|
|
1914
1934
|
speedOverride = Number(data.speedOverride) || 0;
|
|
1915
1935
|
const s = data.stats || {};
|
|
@@ -1968,6 +1988,50 @@ export {
|
|
|
1968
1988
|
}
|
|
1969
1989
|
}
|
|
1970
1990
|
|
|
1991
|
+
// --- dashboard self-update -------------------------------------------------
|
|
1992
|
+
// Where the latest published dashboard version lives: the raw package.json on the
|
|
1993
|
+
// repo's main branch (no API, no auth, no rate limit). A repo rename 404s here and is
|
|
1994
|
+
// handled like any other failure - the check simply no-ops until the URL resolves again.
|
|
1995
|
+
const DASH_PKG_URL = "https://raw.githubusercontent.com/FJRG2007/enigma/main/packages/dashboard/package.json";
|
|
1996
|
+
const UPDATE_CHECK_MS = 15 * 60 * 1000; // every 15 min; the dashboard rarely ships
|
|
1997
|
+
let updating = false; // one in-flight update at a time; reload happens via the ui-version change
|
|
1998
|
+
|
|
1999
|
+
// Numeric semver-ish compare: true when `a` is strictly newer than `b` (e.g. "0.2.0" > "0.1.9").
|
|
2000
|
+
function isNewer(a, b) {
|
|
2001
|
+
const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
|
|
2002
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
2003
|
+
const x = pa[i] || 0, y = pb[i] || 0;
|
|
2004
|
+
if (x !== y) return x > y;
|
|
2005
|
+
}
|
|
2006
|
+
return false;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// Ask GitHub whether a newer dashboard bundle was published; if so, tell the local
|
|
2010
|
+
// server to fetch it (the ui-version change in /api/stats then reloads the page).
|
|
2011
|
+
// Fully fault-tolerant: an 8s timeout plus try/catch swallow offline/timeout/parse/CORS
|
|
2012
|
+
// failures, and it simply retries on the next interval. Skipped while the tab is hidden.
|
|
2013
|
+
async function checkDashboardUpdate() {
|
|
2014
|
+
if (document.hidden || updating || loadedUi == null) return;
|
|
2015
|
+
let latest;
|
|
2016
|
+
try {
|
|
2017
|
+
const ctrl = new AbortController();
|
|
2018
|
+
const t = setTimeout(() => ctrl.abort(), 8000);
|
|
2019
|
+
let res;
|
|
2020
|
+
try { res = await fetch(DASH_PKG_URL, { cache: "no-store", signal: ctrl.signal }); }
|
|
2021
|
+
finally { clearTimeout(t); }
|
|
2022
|
+
if (!res.ok) return;
|
|
2023
|
+
latest = (await res.json()).version;
|
|
2024
|
+
} catch { return; } // offline, timeout, blocked, or bad JSON: try again next interval
|
|
2025
|
+
if (!latest || !isNewer(latest, loadedUi)) return;
|
|
2026
|
+
// A newer bundle exists: have the local server fetch it. The reload is driven by the
|
|
2027
|
+
// ui-version change in the stats poll, so an npm lag (changed:false) just retries later.
|
|
2028
|
+
updating = true;
|
|
2029
|
+
try {
|
|
2030
|
+
await fetch("/api/update", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" });
|
|
2031
|
+
} catch { /* server unreachable: retry next interval */ }
|
|
2032
|
+
finally { updating = false; }
|
|
2033
|
+
}
|
|
2034
|
+
|
|
1971
2035
|
// --- settings subpage (mirrors the TUI registry over /api/settings) ---
|
|
1972
2036
|
function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); }
|
|
1973
2037
|
|
|
@@ -2162,6 +2226,7 @@ export {
|
|
|
2162
2226
|
item("Context compression (MCP) · measured", boolPill(s.compress))
|
|
2163
2227
|
+ item("Real tool-usage stats · measured", boolPill(s.usageStats))
|
|
2164
2228
|
+ item("Claude Code proxy · experimental", boolPill(s.proxy))
|
|
2229
|
+
+ item("Live usage windows · real", boolPill(s.usageApi))
|
|
2165
2230
|
+ proxyMeasured
|
|
2166
2231
|
+ proxyLast
|
|
2167
2232
|
+ promptGuard
|
|
@@ -2278,8 +2343,18 @@ export {
|
|
|
2278
2343
|
+ (s.discarded ? "Disabled - click to enable" : "Enabled - click to disable") + '" data-name="' + esc(s.name)
|
|
2279
2344
|
+ '" data-action="' + (s.discarded ? "enable" : "disable") + '">' + (s.discarded ? "Disabled" : "Enabled") + "</button>"
|
|
2280
2345
|
: '<button type="button" class="btn-danger" data-name="' + esc(s.name) + '" data-action="remove">Remove</button>';
|
|
2346
|
+
// Per-app on/off chips (Claude Code, Codex, OpenCode...). Only for an enabled enigma
|
|
2347
|
+
// skill: when the whole skill is disabled the per-agent state is moot. data-off is the
|
|
2348
|
+
// value to SET on click (turn off if currently on, on if currently off).
|
|
2349
|
+
const agentChips = (s.source === "enigma" && !s.discarded && s.agentStates && s.agentStates.length)
|
|
2350
|
+
? '<div class="set-agents">' + s.agentStates.map((a) =>
|
|
2351
|
+
'<button type="button" class="achip' + (a.deployed ? " on" : "") + '" data-action="agent-toggle" data-name="' + esc(s.name)
|
|
2352
|
+
+ '" data-agent="' + esc(a.name) + '" data-off="' + (a.deployed ? 1 : 0) + '" title="'
|
|
2353
|
+
+ (a.deployed ? "On for " + esc(a.label) + " - click to disable" : "Off for " + esc(a.label) + " - click to enable")
|
|
2354
|
+
+ '">' + esc(a.label) + (a.deployed ? " on" : " off") + "</button>").join("") + "</div>"
|
|
2355
|
+
: "";
|
|
2281
2356
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + upd + where + "</div>"
|
|
2282
|
-
+ '<div class="set-hint">' + esc(s.description || "") + '</div
|
|
2357
|
+
+ '<div class="set-hint">' + esc(s.description || "") + "</div>" + agentChips + '</div><div class="set-ctl" style="display:flex;gap:8px">' + edit + main + "</div></div>";
|
|
2283
2358
|
}
|
|
2284
2359
|
|
|
2285
2360
|
function renderSkills(skills) {
|
|
@@ -2293,10 +2368,10 @@ export {
|
|
|
2293
2368
|
+ section("External skills", "Skills enigma did not author, found in your agents. Remove deletes the skill from every agent that has it.", ext, "No external skills found.");
|
|
2294
2369
|
}
|
|
2295
2370
|
|
|
2296
|
-
async function postSkill(name, action) {
|
|
2371
|
+
async function postSkill(name, action, content) {
|
|
2297
2372
|
$("skillsNote").textContent = "Working...";
|
|
2298
2373
|
try {
|
|
2299
|
-
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action }) });
|
|
2374
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action, content }) });
|
|
2300
2375
|
const out = await res.json();
|
|
2301
2376
|
if (!out.ok) { $("skillsNote").textContent = "Could not " + action + " " + name + ": " + (out.error || "error"); return; }
|
|
2302
2377
|
if (out.skills) renderSkills(out.skills);
|
|
@@ -2342,6 +2417,7 @@ export {
|
|
|
2342
2417
|
if (!b) return;
|
|
2343
2418
|
const { name, action } = b.dataset;
|
|
2344
2419
|
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
2420
|
+
if (action === "agent-toggle") { postSkill(name, action, { agent: b.dataset.agent, off: b.dataset.off === "1" }); return; }
|
|
2345
2421
|
if (action === "remove" && !(await askConfirm("Remove skill", 'Remove the external skill "' + name + '" from all agents? This deletes it from disk.', true))) return;
|
|
2346
2422
|
postSkill(name, action);
|
|
2347
2423
|
});
|
|
@@ -2365,14 +2441,16 @@ export {
|
|
|
2365
2441
|
try { const r = await fetch("/api/accounts", { cache: "no-store" }); renderAccounts(await r.json()); }
|
|
2366
2442
|
catch { $("accountsBody").innerHTML = '<div class="empty" style="padding:24px 0">Accounts unavailable.</div>'; }
|
|
2367
2443
|
}
|
|
2444
|
+
const TRASH_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>';
|
|
2368
2445
|
function accountRow(a) {
|
|
2369
2446
|
const badges = (a.active ? '<span class="tag meas">active</span>' : "")
|
|
2370
2447
|
+ (a.loggedIn ? '<span class="tag">' + esc(a.email || "logged in") + "</span>" : '<span class="tag warn">not logged in</span>');
|
|
2371
2448
|
const btn = (act, label, cls) => '<button type="button" class="' + (cls || "toggle") + '" data-acc="' + act + '" data-tool="' + esc(a.tool) + '" data-name="' + esc(a.name) + '">' + label + "</button>";
|
|
2372
|
-
const
|
|
2449
|
+
const removeBtn = '<button type="button" class="btn-danger icon" title="Remove" aria-label="Remove account ' + esc(a.name) + '" data-acc="remove" data-tool="' + esc(a.tool) + '" data-name="' + esc(a.name) + '">' + TRASH_ICON + "</button>";
|
|
2450
|
+
const acts = '<div class="set-ctl" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center">'
|
|
2373
2451
|
+ (a.active ? "" : btn("activate", "Use"))
|
|
2374
2452
|
+ (a.loggedIn ? "" : btn("login", "Log in..."))
|
|
2375
|
-
+ (a.removable ? btn("rename", "Rename") +
|
|
2453
|
+
+ (a.removable ? btn("rename", "Rename") + removeBtn : "")
|
|
2376
2454
|
+ "</div>";
|
|
2377
2455
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(a.name) + ' <span class="tag ext">' + esc(a.toolLabel) + "</span> " + badges
|
|
2378
2456
|
+ '</div><div class="set-hint">' + esc(a.dir) + "</div></div>" + acts + "</div>";
|
|
@@ -2385,10 +2463,10 @@ export {
|
|
|
2385
2463
|
return '<label style="font-size:12px;color:var(--muted);display:inline-flex;align-items:center;gap:6px;margin:0 12px 6px 0">' + esc(t.label)
|
|
2386
2464
|
+ ' <select class="choice" data-prof-tool="' + esc(t.name) + '" data-prof="' + esc(p.name) + '">' + opts + "</select></label>";
|
|
2387
2465
|
}).join("");
|
|
2388
|
-
const acts = '<div class="set-ctl" style="display:flex;gap:6px">'
|
|
2466
|
+
const acts = '<div class="set-ctl" style="display:flex;gap:6px;align-items:center">'
|
|
2389
2467
|
+ (p.active ? "" : '<button type="button" class="toggle" data-prof-act="activate" data-name="' + esc(p.name) + '">Use</button>')
|
|
2390
2468
|
+ '<button type="button" class="toggle" data-prof-act="rename" data-name="' + esc(p.name) + '">Rename</button>'
|
|
2391
|
-
+ '<button type="button" class="btn-danger" data-prof-act="remove" data-name="' + esc(p.name) + '">
|
|
2469
|
+
+ '<button type="button" class="btn-danger icon" title="Remove" aria-label="Remove profile ' + esc(p.name) + '" data-prof-act="remove" data-name="' + esc(p.name) + '">' + TRASH_ICON + "</button></div>";
|
|
2392
2470
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(p.name) + (p.active ? ' <span class="tag meas">active</span>' : "")
|
|
2393
2471
|
+ '</div><div class="set-hint" style="margin-top:6px">' + maps + "</div></div>" + acts + "</div>";
|
|
2394
2472
|
}
|
|
@@ -2499,9 +2577,11 @@ export {
|
|
|
2499
2577
|
initChart();
|
|
2500
2578
|
poll(true);
|
|
2501
2579
|
loadClaudeStatus();
|
|
2580
|
+
checkDashboardUpdate();
|
|
2502
2581
|
setInterval(() => refreshAll(false), POLL_MS);
|
|
2503
2582
|
setInterval(loadClaudeStatus, 60000);
|
|
2504
|
-
|
|
2583
|
+
setInterval(checkDashboardUpdate, UPDATE_CHECK_MS);
|
|
2584
|
+
document.addEventListener("visibilitychange", () => { if (!document.hidden) { refreshAll(true); loadClaudeStatus(); checkDashboardUpdate(); } });
|
|
2505
2585
|
wireSettings();
|
|
2506
2586
|
wireSkills();
|
|
2507
2587
|
wireAccounts();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|