@enigmax/dashboard 0.1.1 → 0.1.2
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 +139 -2
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -108,6 +108,22 @@
|
|
|
108
108
|
border-radius: 7px; padding: 5px 10px; font-size: 12px; font-family: inherit; cursor: pointer;
|
|
109
109
|
}
|
|
110
110
|
.set-note { color: var(--accent); font-size: 12px; min-height: 16px; margin-top: 10px; }
|
|
111
|
+
.tag {
|
|
112
|
+
display: inline-block; font-size: 11px; color: var(--muted); background: var(--surface2);
|
|
113
|
+
border: 1px solid var(--border); border-radius: 6px; padding: 1px 7px; margin-left: 6px; vertical-align: middle;
|
|
114
|
+
}
|
|
115
|
+
.tag.ext { color: var(--accent2); }
|
|
116
|
+
.tag.warn { color: var(--accent); border-color: var(--accent); }
|
|
117
|
+
.editor {
|
|
118
|
+
width: 100%; min-height: 360px; resize: vertical; background: var(--surface2); color: var(--text);
|
|
119
|
+
border: 1px solid var(--border); border-radius: 8px; padding: 12px;
|
|
120
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;
|
|
121
|
+
}
|
|
122
|
+
.btn-danger {
|
|
123
|
+
background: var(--surface2); color: var(--accent2); border: 1px solid var(--border);
|
|
124
|
+
border-radius: 7px; padding: 5px 14px; font-size: 12px; cursor: pointer; font-family: inherit;
|
|
125
|
+
}
|
|
126
|
+
.btn-danger:hover { border-color: var(--accent2); }
|
|
111
127
|
@media (max-width: 720px) { .grid { grid-template-columns: repeat(2, 1fr); } header { gap: 12px; } }
|
|
112
128
|
</style>
|
|
113
129
|
</head>
|
|
@@ -119,6 +135,7 @@
|
|
|
119
135
|
</div>
|
|
120
136
|
<nav class="tabs">
|
|
121
137
|
<a href="#/" data-view="savings" class="tab active">Savings</a>
|
|
138
|
+
<a href="#/skills" data-view="skills" class="tab">Skills</a>
|
|
122
139
|
<a href="#/settings" data-view="settings" class="tab">Settings</a>
|
|
123
140
|
</nav>
|
|
124
141
|
<div class="nav">
|
|
@@ -233,6 +250,28 @@
|
|
|
233
250
|
</div>
|
|
234
251
|
</main>
|
|
235
252
|
|
|
253
|
+
<section id="view-skills" style="display:none">
|
|
254
|
+
<div class="page-head" style="display:flex;align-items:flex-start;gap:16px">
|
|
255
|
+
<div style="flex:1">
|
|
256
|
+
<h1>Skills</h1>
|
|
257
|
+
<p>The skills installed in your agents - enigma's own and any external ones you added. Edit a skill's content, disable/enable or remove it, and check whether enigma's skills are up to date.</p>
|
|
258
|
+
</div>
|
|
259
|
+
<button id="skillsCheck" type="button" class="toggle on" style="white-space:nowrap">Check for updates</button>
|
|
260
|
+
</div>
|
|
261
|
+
<div id="skillEditor" class="panel" style="display:none">
|
|
262
|
+
<div class="panel-head"><h2>Editing <span id="seName"></span> <small id="seWarn"></small></h2></div>
|
|
263
|
+
<textarea id="seText" class="editor" spellcheck="false"></textarea>
|
|
264
|
+
<div style="margin-top:10px;display:flex;gap:8px">
|
|
265
|
+
<button id="seSave" type="button" class="toggle on">Save</button>
|
|
266
|
+
<button id="seCancel" type="button" class="toggle">Cancel</button>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="panel">
|
|
270
|
+
<div id="skillsBody"><div class="empty" style="padding:24px 0">Loading skills...</div></div>
|
|
271
|
+
<div id="skillsNote" class="set-note"></div>
|
|
272
|
+
</div>
|
|
273
|
+
</section>
|
|
274
|
+
|
|
236
275
|
<section id="view-settings" style="display:none">
|
|
237
276
|
<div class="page-head">
|
|
238
277
|
<h1>Settings</h1>
|
|
@@ -656,13 +695,110 @@
|
|
|
656
695
|
} catch { $("settingsBody").innerHTML = '<div class="empty" style="padding:24px 0">Settings unavailable.</div>'; }
|
|
657
696
|
}
|
|
658
697
|
|
|
659
|
-
// ---
|
|
698
|
+
// --- skills subpage (lists enigma + external skills over /api/skills) ---
|
|
699
|
+
function skillRow(s) {
|
|
700
|
+
const ver = s.version ? '<span class="tag">v' + esc(s.version) + "</span>" : "";
|
|
701
|
+
const where = s.agents && s.agents.length ? '<span class="tag">' + s.agents.map(esc).join(", ") + "</span>"
|
|
702
|
+
: (s.discarded ? '<span class="tag">disabled</span>' : '<span class="tag">not deployed</span>');
|
|
703
|
+
const ext = s.source === "external" ? '<span class="tag ext">external</span>' : "";
|
|
704
|
+
const upd = s.source !== "enigma" ? ""
|
|
705
|
+
: s.update === "update" ? '<span class="tag warn">update available</span>'
|
|
706
|
+
: s.update === "modified" ? '<span class="tag ext">modified</span>' : "";
|
|
707
|
+
const edit = '<button type="button" class="toggle" data-name="' + esc(s.name) + '" data-action="edit" data-enigma="' + (s.source === "enigma" ? 1 : 0) + '">Edit</button>';
|
|
708
|
+
const main = s.source === "enigma"
|
|
709
|
+
? '<button type="button" class="toggle' + (s.discarded ? "" : " on") + '" data-name="' + esc(s.name)
|
|
710
|
+
+ '" data-action="' + (s.discarded ? "enable" : "disable") + '">' + (s.discarded ? "Disabled" : "Enabled") + "</button>"
|
|
711
|
+
: '<button type="button" class="btn-danger" data-name="' + esc(s.name) + '" data-action="remove">Remove</button>';
|
|
712
|
+
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + upd + where + "</div>"
|
|
713
|
+
+ '<div class="set-hint">' + esc(s.description || "") + '</div></div><div class="set-ctl" style="display:flex;gap:8px">' + edit + main + "</div></div>";
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function renderSkills(skills) {
|
|
717
|
+
const enigma = skills.filter((s) => s.source === "enigma");
|
|
718
|
+
const ext = skills.filter((s) => s.source === "external");
|
|
719
|
+
const section = (title, blurb, list, emptyText) => '<div class="set-cat"><div class="set-cat-h">' + title + "</div>"
|
|
720
|
+
+ '<div class="set-cat-b">' + blurb + "</div>"
|
|
721
|
+
+ (list.length ? list.map(skillRow).join("") : '<div class="empty" style="padding:16px 0">' + emptyText + "</div>") + "</div>";
|
|
722
|
+
$("skillsBody").innerHTML =
|
|
723
|
+
section("Enigma skills", "Distributed and kept current by enigma. Disable to remove from your agents; enable to restore.", enigma, "No enigma skills.")
|
|
724
|
+
+ 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.");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
async function postSkill(name, action) {
|
|
728
|
+
$("skillsNote").textContent = "Working...";
|
|
729
|
+
try {
|
|
730
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action }) });
|
|
731
|
+
const out = await res.json();
|
|
732
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not " + action + " " + name + ": " + (out.error || "error"); return; }
|
|
733
|
+
if (out.skills) renderSkills(out.skills);
|
|
734
|
+
$("skillsNote").textContent = out.note || ("Done: " + name);
|
|
735
|
+
} catch { $("skillsNote").textContent = "Could not reach the server to change " + name + "."; }
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Inline SKILL.md editor (one skill at a time).
|
|
739
|
+
let editing = null;
|
|
740
|
+
async function openEditor(name, isEnigma) {
|
|
741
|
+
$("skillsNote").textContent = "Loading " + name + "...";
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, action: "read" }) });
|
|
744
|
+
const out = await res.json();
|
|
745
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not open " + name + ": " + (out.error || "error"); return; }
|
|
746
|
+
editing = name;
|
|
747
|
+
$("seName").textContent = name;
|
|
748
|
+
$("seWarn").textContent = isEnigma ? "managed by enigma - local edits revert on the next update/sync" : "external skill";
|
|
749
|
+
$("seText").value = out.content || "";
|
|
750
|
+
$("skillEditor").style.display = "";
|
|
751
|
+
$("skillsNote").textContent = "";
|
|
752
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
753
|
+
$("seText").focus();
|
|
754
|
+
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
755
|
+
}
|
|
756
|
+
function closeEditor() { editing = null; $("skillEditor").style.display = "none"; }
|
|
757
|
+
async function saveEditor() {
|
|
758
|
+
if (!editing) return;
|
|
759
|
+
$("skillsNote").textContent = "Saving " + editing + "...";
|
|
760
|
+
try {
|
|
761
|
+
const res = await fetch("/api/skills", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editing, action: "save", content: $("seText").value }) });
|
|
762
|
+
const out = await res.json();
|
|
763
|
+
if (!out.ok) { $("skillsNote").textContent = "Could not save " + editing + ": " + (out.error || "error"); return; }
|
|
764
|
+
if (out.skills) renderSkills(out.skills);
|
|
765
|
+
$("skillsNote").textContent = out.note || "Saved.";
|
|
766
|
+
closeEditor();
|
|
767
|
+
} catch { $("skillsNote").textContent = "Could not reach the server."; }
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function wireSkills() {
|
|
771
|
+
$("skillsBody").addEventListener("click", (e) => {
|
|
772
|
+
const b = e.target.closest("button[data-action]");
|
|
773
|
+
if (!b) return;
|
|
774
|
+
const { name, action } = b.dataset;
|
|
775
|
+
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
776
|
+
if (action === "remove" && !confirm('Remove the external skill "' + name + '" from all agents? This deletes it from disk.')) return;
|
|
777
|
+
postSkill(name, action);
|
|
778
|
+
});
|
|
779
|
+
$("seSave").addEventListener("click", saveEditor);
|
|
780
|
+
$("seCancel").addEventListener("click", closeEditor);
|
|
781
|
+
$("skillsCheck").addEventListener("click", () => postSkill("*", "check-updates"));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
let skillsLoaded = false;
|
|
785
|
+
async function loadSkills() {
|
|
786
|
+
try {
|
|
787
|
+
const res = await fetch("/api/skills", { cache: "no-store" });
|
|
788
|
+
renderSkills((await res.json()).skills || []);
|
|
789
|
+
} catch { $("skillsBody").innerHTML = '<div class="empty" style="padding:24px 0">Skills unavailable.</div>'; }
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// --- hash routing between the Savings, Skills and Settings subpages ---
|
|
660
793
|
function route() {
|
|
661
|
-
const
|
|
794
|
+
const h = (location.hash || "").replace(/^#\/?/, "");
|
|
795
|
+
const v = (h === "settings" || h === "skills") ? h : "savings";
|
|
662
796
|
$("view-savings").style.display = v === "savings" ? "" : "none";
|
|
797
|
+
$("view-skills").style.display = v === "skills" ? "" : "none";
|
|
663
798
|
$("view-settings").style.display = v === "settings" ? "" : "none";
|
|
664
799
|
document.querySelectorAll(".tab").forEach((t) => t.classList.toggle("active", t.dataset.view === v));
|
|
665
800
|
if (v === "settings" && !settingsLoaded) { settingsLoaded = true; loadSettings(); }
|
|
801
|
+
if (v === "skills" && !skillsLoaded) { skillsLoaded = true; loadSkills(); }
|
|
666
802
|
// The chart was sized while its view may have been hidden; nudge it on return.
|
|
667
803
|
if (v === "savings" && chart) { try { applyRange(); } catch { /* not ready */ } }
|
|
668
804
|
}
|
|
@@ -672,6 +808,7 @@
|
|
|
672
808
|
setInterval(poll, POLL_MS);
|
|
673
809
|
document.addEventListener("visibilitychange", () => { if (!document.hidden) poll(); });
|
|
674
810
|
wireSettings();
|
|
811
|
+
wireSkills();
|
|
675
812
|
window.addEventListener("hashchange", route);
|
|
676
813
|
route();
|
|
677
814
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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": [
|