@enigmax/dashboard 0.1.42 → 0.1.44
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 +85 -16
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -252,6 +252,8 @@
|
|
|
252
252
|
.aicons { display: inline-flex; gap: 5px; align-items: center; vertical-align: middle; margin-left: 6px; }
|
|
253
253
|
.aicon { display: inline-flex; align-items: center; color: var(--muted); }
|
|
254
254
|
.aicon svg { width: 14px; height: 14px; display: block; }
|
|
255
|
+
/* Brand icon + label, reused by the savings breakdowns and the recent-compressions table. */
|
|
256
|
+
.srctag { display: inline-flex; align-items: center; gap: 7px; }
|
|
255
257
|
.set-ic { vertical-align: middle; margin-right: 7px; }
|
|
256
258
|
/* List settings (guard block/allow globs, custom secret patterns): chips + add input. A list row spans the full grid width. */
|
|
257
259
|
.set-row.list { grid-column: 1 / -1; align-items: stretch; flex-direction: column; }
|
|
@@ -352,6 +354,19 @@
|
|
|
352
354
|
/* Usage view provider/account pickers. */
|
|
353
355
|
.useg-pick { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); }
|
|
354
356
|
.useg-pick select { width: auto; min-width: 170px; }
|
|
357
|
+
/* Icon dropdown: a native <select> can't render icons in its <option>s, so the provider
|
|
358
|
+
picker is a custom listbox - the logo shows on the button AND inside every option. */
|
|
359
|
+
.dd { position: relative; }
|
|
360
|
+
.dd-btn { display: inline-flex; align-items: center; gap: 8px; min-width: 170px; background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px 10px; font-size: 12px; font-family: var(--mono); cursor: pointer; }
|
|
361
|
+
.dd-btn:hover { border-color: #2f3744; }
|
|
362
|
+
.dd-btn .dd-label { flex: 1; text-align: left; }
|
|
363
|
+
.dd-btn .dd-caret { width: 13px; height: 13px; flex: 0 0 auto; color: var(--muted); }
|
|
364
|
+
.dd-ic { display: inline-flex; align-items: center; }
|
|
365
|
+
.dd-ic svg { width: 14px; height: 14px; display: block; }
|
|
366
|
+
.dd-menu { position: absolute; top: calc(100% + 4px); left: 0; min-width: 100%; margin: 0; padding: 4px; list-style: none; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow-2); z-index: 50; }
|
|
367
|
+
.dd-opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 6px; font-size: 12px; color: var(--text); white-space: nowrap; cursor: pointer; }
|
|
368
|
+
.dd-opt:hover, .dd-opt:focus { background: var(--surface2); outline: none; }
|
|
369
|
+
.dd-opt.sel { color: var(--accent); }
|
|
355
370
|
/* Icon picker: clickable app cards (a native <select> can't render SVG in options). */
|
|
356
371
|
.modal-picker { display: flex; flex-direction: column; gap: 8px; margin-top: 14px; }
|
|
357
372
|
.pick-card {
|
|
@@ -505,7 +520,12 @@
|
|
|
505
520
|
</div>
|
|
506
521
|
</div>
|
|
507
522
|
<div class="useg-controls" style="display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin-bottom:16px">
|
|
508
|
-
<
|
|
523
|
+
<div class="useg-pick"><span>Provider</span>
|
|
524
|
+
<div class="dd" id="uProviderDD">
|
|
525
|
+
<button type="button" class="dd-btn" aria-haspopup="listbox" aria-expanded="false"><span class="dd-ic"></span><span class="dd-label"></span><svg class="dd-caret" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"></polyline></svg></button>
|
|
526
|
+
<ul class="dd-menu" role="listbox" aria-label="Provider" hidden></ul>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
509
529
|
<label class="useg-pick" id="uAccountWrap"><span>Account</span><select id="uAccount" class="modal-input"></select></label>
|
|
510
530
|
</div>
|
|
511
531
|
<div class="panel" id="usagePanel" style="display:none">
|
|
@@ -616,8 +636,8 @@
|
|
|
616
636
|
<div class="set-grid">
|
|
617
637
|
<div class="set-row">
|
|
618
638
|
<div class="set-meta"><div class="set-name">Preferred port</div><div class="set-hint" id="dashPortHint">loopback only; never network-facing</div></div>
|
|
619
|
-
<div class="set-ctl" style="
|
|
620
|
-
<input id="dashPortInput" type="number" min="0" max="65535" step="1" class="
|
|
639
|
+
<div class="set-ctl ladd" style="align-items:center">
|
|
640
|
+
<input id="dashPortInput" type="number" min="0" max="65535" step="1" class="linput" style="flex:none;width:120px" inputmode="numeric" />
|
|
621
641
|
<button id="dashPortSave" type="button" class="toggle on">Save</button>
|
|
622
642
|
</div>
|
|
623
643
|
</div>
|
|
@@ -1644,7 +1664,7 @@ export {
|
|
|
1644
1664
|
function prettyType(k) { return TYPE_LABELS[k] || String(k).replace(/\b\w/g, (c) => c.toUpperCase()); }
|
|
1645
1665
|
|
|
1646
1666
|
// One bar per key (source or type), sorted by tokens saved, with an optional $ column.
|
|
1647
|
-
function renderBreakdown(el, map, label, money) {
|
|
1667
|
+
function renderBreakdown(el, map, label, money, iconFn) {
|
|
1648
1668
|
const entries = Object.entries(map || {}).sort((a, b) => (b[1].tokensSaved || 0) - (a[1].tokensSaved || 0));
|
|
1649
1669
|
if (!entries.length) {
|
|
1650
1670
|
el.innerHTML = '<div class="empty" style="padding:20px 0">No data yet - this fills in as compressions are recorded.</div>';
|
|
@@ -1654,7 +1674,7 @@ export {
|
|
|
1654
1674
|
el.innerHTML = entries.map(([k, v]) => {
|
|
1655
1675
|
const saved = v.tokensSaved || 0, pct = Math.round(saved / max * 100);
|
|
1656
1676
|
const m = money ? '<span class="money">' + fmtUsd(moneyOf(k, saved)) + '</span>' : '';
|
|
1657
|
-
return '<div class="bar-row"><span class="name" style="width:140px">' + label(k)
|
|
1677
|
+
return '<div class="bar-row"><span class="name" style="width:140px">' + iconLabel(iconFn ? iconFn(k) : "", label(k))
|
|
1658
1678
|
+ '</span><div class="bar-track"><div class="bar-fill" style="width:' + pct + '%;background:var(--accent)"></div></div>'
|
|
1659
1679
|
+ '<span class="num">' + fmt(saved) + '</span>' + m
|
|
1660
1680
|
+ '<span class="foot" style="width:64px;text-align:right;margin:0">' + fmt(v.calls || 0) + ' calls</span></div>';
|
|
@@ -1676,8 +1696,9 @@ export {
|
|
|
1676
1696
|
+ '<th class="r">Before</th><th class="r">After</th><th class="r">Saved</th></tr></thead><tbody>'
|
|
1677
1697
|
+ rows.map((p) => {
|
|
1678
1698
|
const saved = savedOf(p), pct = p.b ? Math.round(saved / p.b * 100) : 0;
|
|
1699
|
+
const src = p.s || "unknown";
|
|
1679
1700
|
return '<tr><td>' + new Date(p.t).toLocaleString() + '</td><td>' + prettyType(p.c || "unknown")
|
|
1680
|
-
+ '</td><td>' + prettySource(
|
|
1701
|
+
+ '</td><td>' + iconLabel(sourceSvg(src), prettySource(src)) + '</td><td class="r">' + fmt(p.b || 0)
|
|
1681
1702
|
+ '</td><td class="r">' + fmt(p.a || 0) + '</td><td class="r good">' + fmt(saved) + " (" + pct + "%)</td></tr>";
|
|
1682
1703
|
}).join("") + "</tbody></table>";
|
|
1683
1704
|
}
|
|
@@ -1851,10 +1872,17 @@ export {
|
|
|
1851
1872
|
el.onclick = null; el.style.cursor = ""; el.title = ""; // clear any prior incident affordance
|
|
1852
1873
|
let ind = "unknown", desc = p.label + " status unavailable";
|
|
1853
1874
|
try {
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1875
|
+
// Prefer the same-origin server proxy: the browser's direct cross-origin fetch is
|
|
1876
|
+
// unreliable in the field (blockers/MITM proxies/captive portals returned a stub
|
|
1877
|
+
// 200 -> a bogus "Status unknown"). Fall back to direct if the server predates it.
|
|
1878
|
+
let s = null;
|
|
1879
|
+
try {
|
|
1880
|
+
const rp = await fetch("/api/provider-status?provider=" + encodeURIComponent(usageProvider), { cache: "no-store" });
|
|
1881
|
+
if (rp.ok) { const dp = await rp.json(); if (!dp.error) s = dp; }
|
|
1882
|
+
} catch { /* fall through to the direct statuspage */ }
|
|
1883
|
+
if (!s) { const d = await (await fetch(p.status, { cache: "no-store" })).json(); s = d.status || {}; }
|
|
1884
|
+
ind = s.indicator || "unknown";
|
|
1885
|
+
desc = s.description || "Status unknown";
|
|
1858
1886
|
} catch { ind = "unknown"; }
|
|
1859
1887
|
// Claude Code only: overlay the community incident signal. It elevates an otherwise
|
|
1860
1888
|
// green/unknown pill to a warning; an official major/critical statuspage state wins.
|
|
@@ -1876,12 +1904,44 @@ export {
|
|
|
1876
1904
|
try { await fetch("/api/plan", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key, value: Number(value) }) }); refreshAll(true); }
|
|
1877
1905
|
catch { /* leave as-is */ }
|
|
1878
1906
|
}
|
|
1907
|
+
// Accessible icon dropdown (a native <select> can't render icons in its <option>s). The
|
|
1908
|
+
// button shows the selected entry's icon + label; the listbox shows every entry with its
|
|
1909
|
+
// icon. entries: [{ value, label, icon }]; getVal()/onPick(value) bind it to state.
|
|
1910
|
+
function iconDropdown(rootId, entries, getVal, onPick) {
|
|
1911
|
+
const root = $(rootId), btn = root.querySelector(".dd-btn"), menu = root.querySelector(".dd-menu");
|
|
1912
|
+
const btnIc = btn.querySelector(".dd-ic"), btnLabel = btn.querySelector(".dd-label");
|
|
1913
|
+
const opts = () => [...menu.querySelectorAll(".dd-opt")];
|
|
1914
|
+
const render = () => {
|
|
1915
|
+
const cur = getVal(), e = entries.find((x) => x.value === cur) || entries[0];
|
|
1916
|
+
btnIc.innerHTML = e.icon || ""; btnLabel.textContent = e.label;
|
|
1917
|
+
menu.innerHTML = entries.map((x) => '<li class="dd-opt' + (x.value === cur ? " sel" : "")
|
|
1918
|
+
+ '" role="option" tabindex="-1" data-val="' + esc(x.value) + '" aria-selected="' + (x.value === cur)
|
|
1919
|
+
+ '"><span class="dd-ic">' + (x.icon || "") + "</span><span>" + esc(x.label) + "</span></li>").join("");
|
|
1920
|
+
};
|
|
1921
|
+
const close = () => { menu.hidden = true; btn.setAttribute("aria-expanded", "false"); };
|
|
1922
|
+
const open = () => { menu.hidden = false; btn.setAttribute("aria-expanded", "true"); (menu.querySelector(".sel") || opts()[0]) && (menu.querySelector(".sel") || opts()[0]).focus(); };
|
|
1923
|
+
const pick = (li) => { close(); btn.focus(); if (li && li.dataset.val) onPick(li.dataset.val); };
|
|
1924
|
+
btn.addEventListener("click", (e) => { e.stopPropagation(); menu.hidden ? open() : close(); });
|
|
1925
|
+
btn.addEventListener("keydown", (e) => { if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") { e.preventDefault(); open(); } });
|
|
1926
|
+
menu.addEventListener("click", (e) => pick(e.target.closest(".dd-opt")));
|
|
1927
|
+
menu.addEventListener("keydown", (e) => {
|
|
1928
|
+
const list = opts(), i = list.indexOf(document.activeElement);
|
|
1929
|
+
if (e.key === "Escape") { e.preventDefault(); close(); btn.focus(); }
|
|
1930
|
+
else if (e.key === "ArrowDown") { e.preventDefault(); (list[i + 1] || list[0]).focus(); }
|
|
1931
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); (list[i - 1] || list[list.length - 1]).focus(); }
|
|
1932
|
+
else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); pick(document.activeElement); }
|
|
1933
|
+
});
|
|
1934
|
+
document.addEventListener("click", close);
|
|
1935
|
+
render();
|
|
1936
|
+
return { render };
|
|
1937
|
+
}
|
|
1938
|
+
let providerDD = null;
|
|
1879
1939
|
function wireUsage() {
|
|
1880
|
-
//
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1940
|
+
// The provider picker is a custom icon dropdown; the account one stays a native select.
|
|
1941
|
+
providerDD = iconDropdown("uProviderDD",
|
|
1942
|
+
Object.entries(USAGE_PROVIDERS).map(([k, v]) => ({ value: k, label: v.label, icon: agentSvg(k) })),
|
|
1943
|
+
() => usageProvider,
|
|
1944
|
+
(val) => { if (val === usageProvider) return; usageProvider = val; usageAccount = "__all__"; applyUsageView(); loadProviderStatus(); });
|
|
1885
1945
|
$("uAccount").addEventListener("change", () => { usageAccount = $("uAccount").value; applyUsageView(); });
|
|
1886
1946
|
const el = $("uWindows");
|
|
1887
1947
|
el.addEventListener("click", (e) => {
|
|
@@ -1992,6 +2052,7 @@ export {
|
|
|
1992
2052
|
// measurable local store; the others show an honest "no local usage" panel instead.
|
|
1993
2053
|
function applyUsageView() {
|
|
1994
2054
|
const p = USAGE_PROVIDERS[usageProvider] || USAGE_PROVIDERS.claude;
|
|
2055
|
+
if (providerDD) providerDD.render();
|
|
1995
2056
|
$("usageTitle").textContent = p.label + " Usage";
|
|
1996
2057
|
const link = $("usageViewLink");
|
|
1997
2058
|
link.href = p.viewHref; link.textContent = p.viewLabel + " ->";
|
|
@@ -2201,7 +2262,7 @@ export {
|
|
|
2201
2262
|
$("removed").textContent = fmt(saved) + " (" + rate + "%)";
|
|
2202
2263
|
$("reduction").textContent = before ? (100 - rate) + "% of input sent" : "-";
|
|
2203
2264
|
$("ratio").textContent = after ? "~" + (before / after).toFixed(1) + ":1" : "-";
|
|
2204
|
-
renderBreakdown($("sources"), s.bySource, prettySource, true);
|
|
2265
|
+
renderBreakdown($("sources"), s.bySource, prettySource, true, sourceSvg);
|
|
2205
2266
|
renderBreakdown($("types"), s.byType, prettyType, false);
|
|
2206
2267
|
|
|
2207
2268
|
renderUsage(data.usage);
|
|
@@ -2688,6 +2749,14 @@ export {
|
|
|
2688
2749
|
const AGENT_NAME_BY_LABEL = { "Claude Code": "claude", "OpenAI Codex": "codex", "OpenCode": "opencode" };
|
|
2689
2750
|
const agentSvg = (name) => AGENT_ICONS[name] || "";
|
|
2690
2751
|
|
|
2752
|
+
// Compression sources -> a brand icon. Reuses the app icons; the CLI source is enigma itself
|
|
2753
|
+
// (the monochrome cube mark, recoloured to currentColor so it sits uniformly in the tables).
|
|
2754
|
+
const ENIGMA_ICON = '<svg viewBox="0 0 2187.36 2500.37" fill="currentColor" aria-hidden="true"><path d="m2152.75 605.14c-2.56-1.46-5.13-2.93-8.06-4.76q-254.23-145.51-507.76-291.38-266.32-152.69-532.28-305.34l-6.23-3.66-6.59 2.56a202.53 202.53 0 0 0 -29.28 13.55q-231.73 135.63-463.1 270.89c-191.83 112.4-384.03 224.42-574.39 336.44-23.8 11-23.07 35.88-22.7 50.89 0 3.29.37 7 0 10.62q-.55 426.12-1.46 852.24l-.74 316.3c-1.46 19 7 35.15 21.24 41.74q330 187.24 660.05 375.23l389.15 221.54c8.79 5.49 17.57 8.42 25.63 8.42a29.12 29.12 0 0 0 18.3-6.23l564.47-325.13c160.35-92.26 320.7-184.88 479.94-276.4 17.94-8.05 28.56-26 26.36-42.83l1.83-1197.1c2.56-27.46-20.5-40.27-34.41-47.59m-1058.32 75.41 464.19 270.17-463.46 267.28-157.79-89.33q-155.94-88.41-311.9-176.45l469-271.64m527.52 233.57-504.1-293.24c-13.91-9.88-32.21-9.88-45.39-1.1l-510.73 295.77-463.83-262.12 323.62-189.26q336.62-197.15 673.6-393.55 309.15 178.47 618.68 355.47 188.91 108.74 377.8 217.09l-469.69 270.91m-1555.86-205.74 461.63 261.38 9.52 576.59c-1.1 17.93 7.69 34 21.6 40.27q153.19 88.38 307.19 176.45l197 113.12-.36 538.51-998.78-568.16zm525.7 297.62 471.88 266.88-.73 530.46-462.36-265.78zm534.85 870.56 197.32-113.12c101-58.21 202.45-116 304.22-174.63l2.93-1.83c5.49-3.29 12.81-7.69 17.93-15l2.93-4v-4.76c-1.09-127-1.09-256.63-.73-381.83 0-70.29.37-140.58 0-210.87l472.25-272.36-.73 464.56-1.1 678.36q-316.29 181.76-633 364.25l-362.79 209 .73-537.78m0-72.86.37-530.82 464.93-268v531.92q-232.29 132.89-465.3 266.87"/></svg>';
|
|
2755
|
+
const SOURCE_ICON_KEY = { "claude-code": "claude", "claude": "claude", "claude-desktop": "claude", "opencode": "opencode", "codex": "codex", "codex-cli": "codex" };
|
|
2756
|
+
function sourceSvg(k) { return k === "cli" ? ENIGMA_ICON : (AGENT_ICONS[SOURCE_ICON_KEY[k]] || ""); }
|
|
2757
|
+
// Brand icon + text in one inline tag; the icon is omitted when there is none (e.g. content types).
|
|
2758
|
+
function iconLabel(ic, text) { return '<span class="srctag">' + (ic ? '<span class="aicon">' + ic + '</span>' : '') + esc(text) + '</span>'; }
|
|
2759
|
+
|
|
2691
2760
|
function skillRow(s) {
|
|
2692
2761
|
const ver = s.version ? '<span class="tag">v' + esc(s.version) + "</span>" : "";
|
|
2693
2762
|
// Last-edited date (from skill.json), shown relative via the vendored <relative-time>.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
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": [
|