@enigmax/dashboard 0.1.34 → 0.1.36

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 +80 -2
  2. package/package.json +1 -1
package/assets/index.html CHANGED
@@ -340,7 +340,7 @@
340
340
  <header>
341
341
  <div class="brand">
342
342
  <div class="wordmark">Enigma<span aria-hidden="true">.</span></div>
343
- <div class="sub">Savings dashboard</div>
343
+ <div class="sub">Control panel</div>
344
344
  </div>
345
345
  <nav class="tabs">
346
346
  <a href="#/" data-view="savings" class="tab active">Savings</a>
@@ -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>
@@ -2162,6 +2181,8 @@ export {
2162
2181
 
2163
2182
  function renderSettings(cats) {
2164
2183
  const el = $("settingsBody");
2184
+ // Skill-related settings live in the Skills tab; drop them here so they aren't shown twice.
2185
+ cats = (cats || []).map((c) => ({ ...c, settings: (c.settings || []).filter((s) => SKILL_SETTING_KEYS.indexOf(s.key) === -1) })).filter((c) => c.settings.length);
2165
2186
  if (!cats.length) { el.innerHTML = '<div class="empty" style="padding:24px 0">No settings available.</div>'; return; }
2166
2187
  // One panel per category; settings laid out in a responsive 2-column grid so a
2167
2188
  // long config list stays scannable on a wide screen.
@@ -2683,7 +2704,7 @@ export {
2683
2704
 
2684
2705
  // --- hash routing between the subpages ---
2685
2706
  // Single source of truth for the subpage ids (savings is the default/fallback).
2686
- const VIEWS = ["savings", "usage", "accounts", "skills", "settings"];
2707
+ const VIEWS = ["savings", "usage", "accounts", "skills", "settings", "resources"];
2687
2708
  function route() {
2688
2709
  const h = (location.hash || "").replace(/^#\/?/, "");
2689
2710
  const v = VIEWS.includes(h) ? h : "savings";
@@ -2692,10 +2713,66 @@ export {
2692
2713
  if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
2693
2714
  if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
2694
2715
  if (v === "accounts" && !accountsLoaded) { accountsLoaded = true; loadAccounts(); }
2716
+ if (v === "resources") loadResources();
2695
2717
  // The chart was sized while its view may have been hidden; nudge it on return.
2696
2718
  if (v === "savings") { loadSystems(true); if (chart) { try { applyRange(); } catch { /* not ready */ } } }
2697
2719
  }
2698
2720
 
2721
+ // --- resources subpage (system cleanup; destructive, origin-guarded) ---
2722
+ function fmtMB(kb) { return kb >= 1048576 ? (kb / 1048576).toFixed(1) + " GB" : Math.round(kb / 1024) + " MB"; }
2723
+ function renderResources(s) {
2724
+ s = s || {};
2725
+ const mem = s.totalMem ? Math.round((s.totalMem - s.freeMem) / 1048576) + " / " + Math.round(s.totalMem / 1048576) + " MB used" : "-";
2726
+ const wsl = s.wslAvailable
2727
+ ? '<button type="button" class="btn-danger" data-res="wsl-shutdown">Shut down WSL</button>'
2728
+ : '<span class="tag">not available</span>';
2729
+ const dock = s.dockerRunning ? '<button type="button" class="btn-danger" data-res="docker-quit">Quit Docker</button>' : '<span class="tag">not running</span>';
2730
+ $("resSystem").innerHTML =
2731
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Memory</div><div class="set-hint">' + esc(mem) + "</div></div></div>"
2732
+ + '<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>"
2733
+ + '<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>";
2734
+ const ports = s.ports || [];
2735
+ $("resPorts").innerHTML = ports.length
2736
+ ? 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("")
2737
+ : '<div class="empty" style="padding:12px 0">No listening ports found.</div>';
2738
+ const procs = s.topProcesses || [];
2739
+ $("resProcs").innerHTML = procs.length
2740
+ ? 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("")
2741
+ : '<div class="empty" style="padding:12px 0">No processes found.</div>';
2742
+ }
2743
+ async function loadResources() {
2744
+ $("resNote").textContent = "";
2745
+ try { renderResources(await (await fetch("/api/resources", { cache: "no-store" })).json()); }
2746
+ catch { $("resSystem").innerHTML = '<div class="empty" style="padding:12px 0">Resources unavailable.</div>'; }
2747
+ }
2748
+ async function resAction(op, value, confirmMsg) {
2749
+ if (confirmMsg && !(await askConfirm("Confirm", confirmMsg, true))) return;
2750
+ $("resNote").textContent = "Running...";
2751
+ try {
2752
+ const out = await (await fetch("/api/resources", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op, value }) })).json();
2753
+ $("resNote").textContent = out.message || (out.ok ? "Done." : "Failed.");
2754
+ if (out.status) renderResources(out.status);
2755
+ } catch { $("resNote").textContent = "Could not reach the server."; }
2756
+ }
2757
+ function wireResources() {
2758
+ $("resRefresh").addEventListener("click", loadResources);
2759
+ $("resFreePort").addEventListener("click", () => {
2760
+ const port = Number($("resPort").value);
2761
+ if (!port) { $("resNote").textContent = "Enter a port number."; return; }
2762
+ resAction("free-port", port, "Kill whatever is listening on port " + port + "?");
2763
+ });
2764
+ $("view-resources").addEventListener("click", (e) => {
2765
+ const b = e.target.closest("button[data-res]");
2766
+ if (!b) return;
2767
+ const op = b.dataset.res, value = b.dataset.value ? Number(b.dataset.value) : undefined;
2768
+ const msg = op === "wsl-shutdown" ? "Shut down WSL? This stops every WSL distro and frees vmmemWSL."
2769
+ : op === "docker-quit" ? "Force-quit Docker Desktop and its WSL backend?"
2770
+ : op === "free-port" ? "Kill the process listening on port " + value + "?"
2771
+ : op === "kill" ? "Force-kill process " + value + "?" : "Run this action?";
2772
+ resAction(op, value, msg);
2773
+ });
2774
+ }
2775
+
2699
2776
  initChart();
2700
2777
  poll(true);
2701
2778
  loadClaudeStatus();
@@ -2708,6 +2785,7 @@ export {
2708
2785
  wireSkills();
2709
2786
  wireAccounts();
2710
2787
  wireUsage();
2788
+ wireResources();
2711
2789
  $("cfgExport").addEventListener("click", exportConfig);
2712
2790
  $("cfgImport").addEventListener("click", importConfig);
2713
2791
  $("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.36",
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": [