@enigmax/dashboard 0.1.33 → 0.1.35

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.
Files changed (2) hide show
  1. package/assets/index.html +78 -1
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -4,6 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Enigma - Dashboard</title>
7
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjE4Ny4zNiAyNTAwLjM3IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxwYXRoIGQ9Im0xMDk0Ljc2IDY0MiA1MDkuNCAzMDcuMjYtNTA5LjQgMjkwLjc0LTUzMS4zNy0yOTAuNzR6IiBmaWxsPSIjZWEyNTllIi8+PHBhdGggZD0ibTU2My4zOSA5NjAgNTMxLjM3IDI5MC4xOC0yLjc0IDU5MS44Mi01MjguNjMtMjgwLjQ2eiIgZmlsbD0iIzAxNGM5OSIvPjxwYXRoIGQ9Im0xMDk0Ljc2IDEyNTAuMTggNTE1LjQtMjkwLjE4IDE0LjUxIDYwMS41NC01MjIuNTEgMjkxLjU5eiIgZmlsbD0iIzA4YjBmOSIvPjxnIGZpbGw9IiMyZjJmMmYiPjxwYXRoIGQ9Im0yMTUyLjc1IDYwNS4xNGMtMi41Ni0xLjQ2LTUuMTMtMi45My04LjA2LTQuNzZxLTI1NC4yMy0xNDUuNTEtNTA3Ljc2LTI5MS4zOC0yNjYuMzItMTUyLjY5LTUzMi4yOC0zMDUuMzRsLTYuMjMtMy42Ni02LjU5IDIuNTZhMjAyLjUzIDIwMi41MyAwIDAgMCAtMjkuMjggMTMuNTVxLTIzMS43MyAxMzUuNjMtNDYzLjEgMjcwLjg5Yy0xOTEuODMgMTEyLjQtMzg0LjAzIDIyNC40Mi01NzQuMzkgMzM2LjQ0LTIzLjggMTEtMjMuMDcgMzUuODgtMjIuNyA1MC44OSAwIDMuMjkuMzcgNyAwIDEwLjYycS0uNTUgNDI2LjEyLTEuNDYgODUyLjI0bC0uNzQgMzE2LjNjLTEuNDYgMTkgNyAzNS4xNSAyMS4yNCA0MS43NHEzMzAgMTg3LjI0IDY2MC4wNSAzNzUuMjNsMzg5LjE1IDIyMS41NGM4Ljc5IDUuNDkgMTcuNTcgOC40MiAyNS42MyA4LjQyYTI5LjEyIDI5LjEyIDAgMCAwIDE4LjMtNi4yM2w1NjQuNDctMzI1LjEzYzE2MC4zNS05Mi4yNiAzMjAuNy0xODQuODggNDc5Ljk0LTI3Ni40IDE3Ljk0LTguMDUgMjguNTYtMjYgMjYuMzYtNDIuODNsMS44My0xMTk3LjFjMi41Ni0yNy40Ni0yMC41LTQwLjI3LTM0LjQxLTQ3LjU5bS0xMDU4LjMyIDc1LjQxIDQ2NC4xOSAyNzAuMTctNDYzLjQ2IDI2Ny4yOC0xNTcuNzktODkuMzNxLTE1NS45NC04OC40MS0zMTEuOS0xNzYuNDVsNDY5LTI3MS42NG01MjcuNTIgMjMzLjU3LTUwNC4xLTI5My4yNGMtMTMuOTEtOS44OC0zMi4yMS05Ljg4LTQ1LjM5LTEuMWwtNTEwLjczIDI5NS43Ny00NjMuODMtMjYyLjEyIDMyMy42Mi0xODkuMjZxMzM2LjYyLTE5Ny4xNSA2NzMuNi0zOTMuNTUgMzA5LjE1IDE3OC40NyA2MTguNjggMzU1LjQ3IDE4OC45MSAxMDguNzQgMzc3LjggMjE3LjA5bC00NjkuNjkgMjcwLjkxbS0xNTU1Ljg2LTIwNS43NCA0NjEuNjMgMjYxLjM4IDkuNTIgNTc2LjU5Yy0xLjEgMTcuOTMgNy42OSAzNCAyMS42IDQwLjI3cTE1My4xOSA4OC4zOCAzMDcuMTkgMTc2LjQ1bDE5NyAxMTMuMTItLjM2IDUzOC41MS05OTguNzgtNTY4LjE2em01MjUuNyAyOTcuNjIgNDcxLjg4IDI2Ni44OC0uNzMgNTMwLjQ2LTQ2Mi4zNi0yNjUuNzh6bTUzNC44NSA4NzAuNTYgMTk3LjMyLTExMy4xMmMxMDEtNTguMjEgMjAyLjQ1LTExNiAzMDQuMjItMTc0LjYzbDIuOTMtMS44M2M1LjQ5LTMuMjkgMTIuODEtNy42OSAxNy45My0xNWwyLjkzLTR2LTQuNzZjLTEuMDktMTI3LTEuMDktMjU2LjYzLS43My0zODEuODMgMC03MC4yOS4zNy0xNDAuNTggMC0yMTAuODdsNDcyLjI1LTI3Mi4zNi0uNzMgNDY0LjU2LTEuMSA2NzguMzZxLTMxNi4yOSAxODEuNzYtNjMzIDM2NC4yNWwtMzYyLjc5IDIwOSAuNzMtNTM3Ljc4bTAtNzIuODYuMzctNTMwLjgyIDQ2NC45My0yNjh2NTMxLjkycS0yMzIuMjkgMTMyLjg5LTQ2NS4zIDI2Ni44NyIvPjxwYXRoIGQ9Im0xMDgwLjg1IDEzMy4yNmMtMTMuNTUgMS40Ni0yNS42MyAxMC4yNS0zMS44NSAyMy4wNi02LjIyIDEzLjU0LTUuNDkgMjguMTkgMi4yIDM5LjE3IDcuMzIgMTEuNzEgMjAuODYgMTguNjcgMzUuNTEgMTguNjdhNzIuODUgNzIuODUgMCAwIDAgOC40Mi0uNzNjMTcuMi0yLjkzIDMwLTE0LjY1IDMyLjk0LTI5LjY1YTM4LjM0IDM4LjM0IDAgMCAwIC05LjUxLTM3LjM1Yy05LjUyLTEwLjYxLTI0LjE3LTE1Ljc0LTM3LjcxLTEzLjE3bTI2LjcyIDE3NC42MmMtMTQuMjctNy42OS0zMS4xMS03LjMyLTQzLjU2IDEuMS0xMSA2LjIyLTE4LjMgMTcuOTMtMTkuNzcgMzEuNDgtMS4xIDEzLjU0IDQuMzkgMjYgMTMuMTggMzNhNDAuMTcgNDAuMTcgMCAwIDAgMjguMTkgMTEgNDUuNjggNDUuNjggMCAwIDAgMTguMy00YzE1Ljc0LTYuOTUgMjUuMjYtMjEuMjMgMjQuOS0zNi4yNC43My0xNC42NC03LjMyLTI4LjU1LTIxLjI0LTM2LjI0bTEuODMgMTcxLjY5Yy0xMy45MS04Ljc4LTMxLjExLTkuMTUtNDQuMjktMS40Ni0xMi4wOCA2LjIyLTE5Ljc3IDE3Ljk0LTIwLjg3IDMyLjIxczUuMTMgMjguMTkgMTUuNzQgMzUuMTVhNDEuMjkgNDEuMjkgMCAwIDAgMjYgOC43OCA0NS44OSA0NS44OSAwIDAgMCAxOC4zMS00YzE0LjY0LTYuNTkgMjMuNzktMTkgMjQuNTMtMzMuMzIgMS4xLTE1LTYuMjMtMjguOTItMTkuNDEtMzcuMzRtLTY3OS44MyAxMTIxLjJjLTE0LjI3IDQtMjUuMjYgMTUuNzQtMjguNTUgMzEuMTItMy4zIDE1Ljc0IDIuMTkgMzEuNDggMTMuMTggMzkuOWEzNy4wOCAzNy4wOCAwIDAgMCAyNC44OSA5LjUyIDM4LjY3IDM4LjY3IDAgMCAwIDkuMTUtMS4xYzEzLjE4LTIuOTIgMjQuMTctMTIuMDggMjguMTktMjMuMDYgNy0xNC4yOCA0LjQtMzEuNDgtNi45NS00NC4zLTEwLjI1LTEyLjA4LTI1LjYzLTE2LjQ3LTM5LjkxLTEyLjA4bTEyOTIuMjkgODAuOTFhMzQuNjMgMzQuNjMgMCAwIDAgMTAuMjUtMS40N2MxMy4xOC0zLjI5IDIzLjQzLTEyLjQ0IDI2LjcyLTIyLjY5IDcuMzItMTQuNjUgNC4zOS0zMi4yMi03LTQ1LjQtMTEtMTIuMDgtMjYuNzItMTYuNDctNDEtMTEuNzEtMTMuOTEgNC0yNC41MiAxNi4xLTI3LjQ1IDMxLjQ4LTIuOTMgMTUuNzQgMi41NiAzMS4xMiAxMy4xOCAzOS41NCA2LjU5IDcgMTUuNzQgMTAuMjUgMjUuMjYgMTAuMjVtLTE0MzggNS40OWMtMTQuNjQgNS4xMi0yNC41MyAxOC4zLTI2IDM0Ljc4LTEuNDcgMTYuODQgNi41OSAzMi41OCAxOS40IDM5LjlhMzcuOCAzNy44IDAgMCAwIDE5Ljc3IDUuODYgNDIuMjIgNDIuMjIgMCAwIDAgMTcuMjEtNGMxMi44MS01Ljg2IDIxLjYtMTcuNTcgMjIuNjktMjkuMjkgMy4zLTE0LjI3LTIuNTYtMzAuMzgtMTQuNjQtNDAuMjctMTEuMzUtOS44OC0yNS42My0xMi40NC0zOC40NC03bTE1ODAgODAuMTdhMzkuMTkgMzkuMTkgMCAwIDAgMTQuMjctMi41NmMxMy45Mi01LjEzIDIzLjgtMTcuMjEgMjUuMjYtMjkuMjkgNC0xNC4yNy0uNzMtMjkuNjUtMTIuNDQtNDAuMjctMTEtMTAuMjUtMjYuMzYtMTMuNTQtMzkuNTQtOC40Mi0xMy41NCA0Ljc2LTIzLjggMTYuODQtMjYuMzYgMzIuMjItMi41NiAxNS43NCAzLjMgMzEuMTIgMTQuMjggMzkuNTRhNDAuMTcgNDAuMTcgMCAwIDAgMjQuNTMgOC43OG0tMTY4OS44NSA4LjA2Yy0xMi40NS03LTI2LjczLTctMzguNDQgMC0xMi44MiA3LTIwLjg3IDIwLjg2LTIwLjg3IDM2LjYgMCAxNi4xMSA4Ljc5IDMwIDIxLjIzIDM2LjI1YTM0LjczIDM0LjczIDAgMCAwIDE3Ljk0IDQuNzYgNDEuNDggNDEuNDggMCAwIDAgMjItNi41OWMxMy4xNy04LjQyIDIwLjUtMjMuNDMgMTktMzYuMjUuMzctMTQuMjctNy42OS0yNy40NS0yMC44Ni0zNC43N20xODA4LjkxIDY4LjVhMzUuODEgMzUuODEgMCAwIDAgMjMuOCA5LjE2IDM4LjczIDM4LjczIDAgMCAwIDE1LjM4LTMuM2MxNC4yNy02LjIyIDI0LjE2LTE5LjQgMjQuODktMzIuMjEgMi45My0xNS43NS00LjM5LTMxLjg1LTE4LjMtNDEtMTIuODItOC43OC0yOC41Ni05LjE1LTQxLTEuMDlhNDEuNSA0MS41IDAgMCAwIC0yMC4xMyAzNGMtLjczIDE0LjI4IDUuNDkgMjcuODIgMTUuMzcgMzQuNDEiLz48L2c+PC9zdmc+">
7
8
  <style>
8
9
  :root {
9
10
  /* Core palette. --accent / --muted / --border are PINNED: the chart hardcodes
@@ -347,6 +348,7 @@
347
348
  <a href="#/accounts" data-view="accounts" class="tab">Accounts</a>
348
349
  <a href="#/skills" data-view="skills" class="tab">Skills</a>
349
350
  <a href="#/settings" data-view="settings" class="tab">Settings</a>
351
+ <a href="#/resources" data-view="resources" class="tab">Resources</a>
350
352
  </nav>
351
353
  <div class="nav">
352
354
  <span class="live"><span id="dot" class="dot stale"></span><span id="updated">Connecting...</span></span>
@@ -565,6 +567,24 @@
565
567
  <div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
566
568
  </section>
567
569
 
570
+ <section id="view-resources" style="display:none">
571
+ <div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
572
+ <div style="flex:1">
573
+ <h1>Resources</h1>
574
+ <p>Free up your machine: shut down WSL (the <code>vmmemWSL</code> RAM hog), quit Docker Desktop when it won't, free a stuck port, or kill any process. These actions are destructive and ask before running.</p>
575
+ </div>
576
+ <button id="resRefresh" type="button" class="iconbtn" title="Refresh" aria-label="Refresh"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg></button>
577
+ </div>
578
+ <div id="resNote" class="set-note"></div>
579
+ <div class="panel"><div class="set-cat-h">System</div><div id="resSystem" class="set-grid"></div></div>
580
+ <div class="panel">
581
+ <div class="set-cat-h">Free a port</div>
582
+ <div class="ladd" style="max-width:320px"><input id="resPort" class="linput" type="number" min="1" max="65535" placeholder="port number, e.g. 3000"><button id="resFreePort" type="button" class="toggle on">Free</button></div>
583
+ </div>
584
+ <div class="panel"><div class="set-cat-h">Listening ports</div><div id="resPorts"><div class="empty" style="padding:12px 0">Loading...</div></div></div>
585
+ <div class="panel"><div class="set-cat-h">Processes <small>by memory</small></div><div id="resProcs"><div class="empty" style="padding:12px 0">Loading...</div></div></div>
586
+ </section>
587
+
568
588
  <footer>
569
589
  Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
570
590
  Data source: <code>~/.enigma/ccr/stats.json</code> + <code>history.jsonl</code> - reset it with <code>enigma compress --clear</code>. Enigma <span id="ver"></span>
@@ -2682,7 +2702,7 @@ export {
2682
2702
 
2683
2703
  // --- hash routing between the subpages ---
2684
2704
  // Single source of truth for the subpage ids (savings is the default/fallback).
2685
- const VIEWS = ["savings", "usage", "accounts", "skills", "settings"];
2705
+ const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources"];
2686
2706
  function route() {
2687
2707
  const h = (location.hash || "").replace(/^#\/?/, "");
2688
2708
  const v = VIEWS.includes(h) ? h : "savings";
@@ -2691,10 +2711,66 @@ export {
2691
2711
  if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
2692
2712
  if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
2693
2713
  if (v === "accounts" && !accountsLoaded) { accountsLoaded = true; loadAccounts(); }
2714
+ if (v === "resources") loadResources();
2694
2715
  // The chart was sized while its view may have been hidden; nudge it on return.
2695
2716
  if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
2696
2717
  }
2697
2718
 
2719
+ // --- resources subpage (system cleanup; destructive, origin-guarded) ---
2720
+ function fmtMB(kb) { return kb >= 1048576 ? (kb / 1048576).toFixed(1) + " GB" : Math.round(kb / 1024) + " MB"; }
2721
+ function renderResources(s) {
2722
+ s = s || {};
2723
+ const mem = s.totalMem ? Math.round((s.totalMem - s.freeMem) / 1048576) + " / " + Math.round(s.totalMem / 1048576) + " MB used" : "-";
2724
+ const wsl = s.wslAvailable
2725
+ ? '<button type="button" class="btn-danger" data-res="wsl-shutdown">Shut down WSL</button>'
2726
+ : '<span class="tag">not available</span>';
2727
+ const dock = s.dockerRunning ? '<button type="button" class="btn-danger" data-res="docker-quit">Quit Docker</button>' : '<span class="tag">not running</span>';
2728
+ $("resSystem").innerHTML =
2729
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Memory</div><div class="set-hint">' + esc(mem) + "</div></div></div>"
2730
+ + '<div class="set-row"><div class="set-meta"><div class="set-name">WSL / vmmemWSL</div><div class="set-hint">' + (s.vmmemRunning ? "running - eating RAM; shut down to free it" : "not running") + '</div></div><div class="set-ctl">' + wsl + "</div></div>"
2731
+ + '<div class="set-row"><div class="set-meta"><div class="set-name">Docker Desktop</div><div class="set-hint">' + (s.dockerRunning ? "running" : "not running") + '</div></div><div class="set-ctl">' + dock + "</div></div>";
2732
+ const ports = s.ports || [];
2733
+ $("resPorts").innerHTML = ports.length
2734
+ ? ports.map((p) => '<div class="set-row"><div class="set-meta"><div class="set-name">:' + p.port + ' <span class="tag">' + esc(p.name || "pid " + p.pid) + '</span></div><div class="set-hint">pid ' + p.pid + " · " + esc(p.proto) + '</div></div><div class="set-ctl"><button type="button" class="btn-danger" data-res="free-port" data-value="' + p.port + '">Free</button></div></div>').join("")
2735
+ : '<div class="empty" style="padding:12px 0">No listening ports found.</div>';
2736
+ const procs = s.topProcesses || [];
2737
+ $("resProcs").innerHTML = procs.length
2738
+ ? procs.map((p) => '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(p.name) + ' <span class="tag">' + fmtMB(p.memKB) + '</span></div><div class="set-hint">pid ' + p.pid + '</div></div><div class="set-ctl"><button type="button" class="btn-danger" data-res="kill" data-value="' + p.pid + '">Kill</button></div></div>').join("")
2739
+ : '<div class="empty" style="padding:12px 0">No processes found.</div>';
2740
+ }
2741
+ async function loadResources() {
2742
+ $("resNote").textContent = "";
2743
+ try { renderResources(await (await fetch("/api/resources", { cache: "no-store" })).json()); }
2744
+ catch { $("resSystem").innerHTML = '<div class="empty" style="padding:12px 0">Resources unavailable.</div>'; }
2745
+ }
2746
+ async function resAction(op, value, confirmMsg) {
2747
+ if (confirmMsg && !(await askConfirm("Confirm", confirmMsg, true))) return;
2748
+ $("resNote").textContent = "Running...";
2749
+ try {
2750
+ const out = await (await fetch("/api/resources", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op, value }) })).json();
2751
+ $("resNote").textContent = out.message || (out.ok ? "Done." : "Failed.");
2752
+ if (out.status) renderResources(out.status);
2753
+ } catch { $("resNote").textContent = "Could not reach the server."; }
2754
+ }
2755
+ function wireResources() {
2756
+ $("resRefresh").addEventListener("click", loadResources);
2757
+ $("resFreePort").addEventListener("click", () => {
2758
+ const port = Number($("resPort").value);
2759
+ if (!port) { $("resNote").textContent = "Enter a port number."; return; }
2760
+ resAction("free-port", port, "Kill whatever is listening on port " + port + "?");
2761
+ });
2762
+ $("view-resources").addEventListener("click", (e) => {
2763
+ const b = e.target.closest("button[data-res]");
2764
+ if (!b) return;
2765
+ const op = b.dataset.res, value = b.dataset.value ? Number(b.dataset.value) : undefined;
2766
+ const msg = op === "wsl-shutdown" ? "Shut down WSL? This stops every WSL distro and frees vmmemWSL."
2767
+ : op === "docker-quit" ? "Force-quit Docker Desktop and its WSL backend?"
2768
+ : op === "free-port" ? "Kill the process listening on port " + value + "?"
2769
+ : op === "kill" ? "Force-kill process " + value + "?" : "Run this action?";
2770
+ resAction(op, value, msg);
2771
+ });
2772
+ }
2773
+
2698
2774
  initChart();
2699
2775
  poll(true);
2700
2776
  loadClaudeStatus();
@@ -2707,6 +2783,7 @@ export {
2707
2783
  wireSkills();
2708
2784
  wireAccounts();
2709
2785
  wireUsage();
2786
+ wireResources();
2710
2787
  $("cfgExport").addEventListener("click", exportConfig);
2711
2788
  $("cfgImport").addEventListener("click", importConfig);
2712
2789
  $("updateBtn").addEventListener("click", runUpdate);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.33",
3
+ "version": "0.1.35",
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": [