@enigmax/dashboard 0.1.34 → 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 +77 -1
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -348,6 +348,7 @@
348
348
  <a href="#/accounts" data-view="accounts" class="tab">Accounts</a>
349
349
  <a href="#/skills" data-view="skills" class="tab">Skills</a>
350
350
  <a href="#/settings" data-view="settings" class="tab">Settings</a>
351
+ <a href="#/resources" data-view="resources" class="tab">Resources</a>
351
352
  </nav>
352
353
  <div class="nav">
353
354
  <span class="live"><span id="dot" class="dot stale"></span><span id="updated">Connecting...</span></span>
@@ -566,6 +567,24 @@
566
567
  <div id="settingsBody"><div class="empty" style="padding:24px 0">Loading settings...</div></div>
567
568
  </section>
568
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
+
569
588
  <footer>
570
589
  Polling pauses automatically when this tab is hidden, and the server caches each snapshot - so an open dashboard costs almost nothing.
571
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>
@@ -2683,7 +2702,7 @@ export {
2683
2702
 
2684
2703
  // --- hash routing between the subpages ---
2685
2704
  // Single source of truth for the subpage ids (savings is the default/fallback).
2686
- const VIEWS = ["savings", "usage", "accounts", "skills", "settings"];
2705
+ const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources"];
2687
2706
  function route() {
2688
2707
  const h = (location.hash || "").replace(/^#\/?/, "");
2689
2708
  const v = VIEWS.includes(h) ? h : "savings";
@@ -2692,10 +2711,66 @@ export {
2692
2711
  if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
2693
2712
  if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
2694
2713
  if (v === "accounts" && !accountsLoaded) { accountsLoaded = true; loadAccounts(); }
2714
+ if (v === "resources") loadResources();
2695
2715
  // The chart was sized while its view may have been hidden; nudge it on return.
2696
2716
  if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
2697
2717
  }
2698
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
+
2699
2774
  initChart();
2700
2775
  poll(true);
2701
2776
  loadClaudeStatus();
@@ -2708,6 +2783,7 @@ export {
2708
2783
  wireSkills();
2709
2784
  wireAccounts();
2710
2785
  wireUsage();
2786
+ wireResources();
2711
2787
  $("cfgExport").addEventListener("click", exportConfig);
2712
2788
  $("cfgImport").addEventListener("click", importConfig);
2713
2789
  $("updateBtn").addEventListener("click", runUpdate);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enigmax/dashboard",
3
- "version": "0.1.34",
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": [