@enigmax/dashboard 0.1.16 → 0.1.18
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 +93 -15
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -257,6 +257,35 @@
|
|
|
257
257
|
.tabs .tab { white-space: nowrap; }
|
|
258
258
|
.nav { margin-left: 0; }
|
|
259
259
|
}
|
|
260
|
+
/* Pretty scrollbars across the whole dashboard (thin, rounded, themed). */
|
|
261
|
+
* { scrollbar-width: thin; scrollbar-color: #2b3340 transparent; }
|
|
262
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
263
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
264
|
+
::-webkit-scrollbar-corner { background: transparent; }
|
|
265
|
+
::-webkit-scrollbar-thumb { background: #2b3340; border-radius: 999px; }
|
|
266
|
+
::-webkit-scrollbar-thumb:hover { background: #3a4150; }
|
|
267
|
+
/* Animated modal dialog - replaces native alert/prompt/confirm so dialogs keep the design. */
|
|
268
|
+
.modal-back {
|
|
269
|
+
position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center;
|
|
270
|
+
padding: 20px; background: rgba(4,6,10,0.62); backdrop-filter: blur(3px);
|
|
271
|
+
opacity: 0; transition: opacity .18s ease;
|
|
272
|
+
}
|
|
273
|
+
.modal-back.show { opacity: 1; }
|
|
274
|
+
.modal {
|
|
275
|
+
width: 100%; max-width: 420px; background: var(--surface); border: 1px solid var(--border);
|
|
276
|
+
border-radius: var(--radius); box-shadow: var(--shadow-2); padding: 22px;
|
|
277
|
+
transform: translateY(10px) scale(.98); opacity: 0; transition: transform .18s ease, opacity .18s ease;
|
|
278
|
+
}
|
|
279
|
+
.modal-back.show .modal { transform: none; opacity: 1; }
|
|
280
|
+
.modal-title { font-family: var(--mono); font-size: 14px; font-weight: 600; color: var(--text); }
|
|
281
|
+
.modal-msg { color: var(--muted); font-size: 13px; line-height: 1.5; margin-top: 8px; word-break: break-word; }
|
|
282
|
+
.modal-input {
|
|
283
|
+
width: 100%; margin-top: 14px; background: var(--surface2); color: var(--text);
|
|
284
|
+
border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 12px; font-size: 13px; font-family: var(--mono);
|
|
285
|
+
}
|
|
286
|
+
.modal-input:focus { outline: none; border-color: var(--accent); }
|
|
287
|
+
.modal-acts { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }
|
|
288
|
+
.modal-ok.danger { background: var(--accent2); border-color: var(--accent2); color: #2a0f08; }
|
|
260
289
|
</style>
|
|
261
290
|
</head>
|
|
262
291
|
<body>
|
|
@@ -956,6 +985,50 @@
|
|
|
956
985
|
// --- settings subpage (mirrors the TUI registry over /api/settings) ---
|
|
957
986
|
function esc(s) { return String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); }
|
|
958
987
|
|
|
988
|
+
// Capitalize a choice value for display ("on-demand" -> "On demand"), keeping the raw
|
|
989
|
+
// value as the option's value so the server still receives the exact token.
|
|
990
|
+
function prettyChoice(c) { c = String(c).replace(/[-_]/g, " "); return c.charAt(0).toUpperCase() + c.slice(1); }
|
|
991
|
+
|
|
992
|
+
// Animated modal replacing native alert/prompt/confirm (keeps the design). Returns a
|
|
993
|
+
// Promise: a prompt resolves to the entered string or null; a confirm resolves a boolean.
|
|
994
|
+
function openModal({ title, message, field, danger, okLabel }) {
|
|
995
|
+
return new Promise((resolve) => {
|
|
996
|
+
const back = document.createElement("div");
|
|
997
|
+
back.className = "modal-back";
|
|
998
|
+
let inner = "";
|
|
999
|
+
if (field && field.options) {
|
|
1000
|
+
inner = '<select class="modal-input">' + field.options.map((o) =>
|
|
1001
|
+
'<option value="' + esc(o.value) + '"' + (o.value === field.value ? " selected" : "") + ">" + esc(o.label || o.value) + "</option>").join("") + "</select>";
|
|
1002
|
+
} else if (field) {
|
|
1003
|
+
inner = '<input class="modal-input" type="text" value="' + esc(field.value || "") + '" placeholder="' + esc(field.placeholder || "") + '">';
|
|
1004
|
+
}
|
|
1005
|
+
back.innerHTML = '<div class="modal" role="dialog" aria-modal="true">'
|
|
1006
|
+
+ '<div class="modal-title">' + esc(title) + "</div>"
|
|
1007
|
+
+ (message ? '<div class="modal-msg">' + esc(message) + "</div>" : "")
|
|
1008
|
+
+ inner
|
|
1009
|
+
+ '<div class="modal-acts"><button type="button" class="toggle modal-cancel">Cancel</button>'
|
|
1010
|
+
+ '<button type="button" class="toggle on modal-ok' + (danger ? " danger" : "") + '">' + esc(okLabel || "OK") + "</button></div></div>";
|
|
1011
|
+
document.body.appendChild(back);
|
|
1012
|
+
requestAnimationFrame(() => back.classList.add("show"));
|
|
1013
|
+
const inp = back.querySelector(".modal-input");
|
|
1014
|
+
let done = false;
|
|
1015
|
+
const close = (val) => { if (done) return; done = true; back.classList.remove("show"); setTimeout(() => back.remove(), 200); resolve(val); };
|
|
1016
|
+
const ok = () => close(field ? (inp ? inp.value : true) : true);
|
|
1017
|
+
const cancel = () => close(field ? null : false);
|
|
1018
|
+
back.querySelector(".modal-ok").addEventListener("click", ok);
|
|
1019
|
+
back.querySelector(".modal-cancel").addEventListener("click", cancel);
|
|
1020
|
+
back.addEventListener("mousedown", (e) => { if (e.target === back) cancel(); });
|
|
1021
|
+
back.addEventListener("keydown", (e) => {
|
|
1022
|
+
if (e.key === "Escape") { e.preventDefault(); cancel(); }
|
|
1023
|
+
else if (e.key === "Enter" && (!inp || inp.tagName !== "TEXTAREA")) { e.preventDefault(); ok(); }
|
|
1024
|
+
});
|
|
1025
|
+
if (inp) { inp.focus(); if (inp.select) inp.select(); } else back.querySelector(".modal-ok").focus();
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
const askText = (title, value, message) => openModal({ title, message, field: { value: value || "" } }).then((v) => (v == null ? null : String(v).trim()));
|
|
1029
|
+
const askChoice = (title, options, value, message) => openModal({ title, message, field: { options, value } });
|
|
1030
|
+
const askConfirm = (title, message, danger) => openModal({ title, message, danger, okLabel: danger ? "Remove" : "OK" }).then(Boolean);
|
|
1031
|
+
|
|
959
1032
|
// Turn "(experimental)" and " · measured/experimental" label suffixes into visual badges.
|
|
960
1033
|
function labelWithBadges(text) {
|
|
961
1034
|
let name = String(text);
|
|
@@ -979,9 +1052,12 @@
|
|
|
979
1052
|
+ '<div class="set-hint">' + esc(s.hint) + (s.globalOnly ? " · global" : "") + "</div>"
|
|
980
1053
|
+ chips + adder + "</div></div>";
|
|
981
1054
|
}
|
|
982
|
-
|
|
1055
|
+
// A 2-value choice whose "off" is one of the two values is really a boolean, so
|
|
1056
|
+
// render it as a switch (like a pure bool); 3+ values stay a select with pretty labels.
|
|
1057
|
+
const asSwitch = !s.choices || (s.choices.length === 2 && s.choices.indexOf(s.offChoice) !== -1);
|
|
1058
|
+
const ctl = !asSwitch
|
|
983
1059
|
? '<select class="choice" data-skey="' + esc(s.key) + '">'
|
|
984
|
-
+ s.choices.map((c) => '<option value="' + esc(c) + '"' + (c === s.choice ? " selected" : "") + ">" + esc(c) + "</option>").join("")
|
|
1060
|
+
+ s.choices.map((c) => '<option value="' + esc(c) + '"' + (c === s.choice ? " selected" : "") + ">" + esc(prettyChoice(c)) + "</option>").join("")
|
|
985
1061
|
+ "</select>"
|
|
986
1062
|
: '<button type="button" class="toggle' + (s.value ? " on" : "") + '" data-skey="' + esc(s.key)
|
|
987
1063
|
+ '" data-kind="bool" data-on="' + (s.value ? 1 : 0) + '">' + (s.value ? "On" : "Off") + "</button>";
|
|
@@ -1216,7 +1292,8 @@
|
|
|
1216
1292
|
: s.update === "modified" ? '<span class="tag ext">modified</span>' : "";
|
|
1217
1293
|
const edit = '<button type="button" class="toggle" data-name="' + esc(s.name) + '" data-action="edit" data-enigma="' + (s.source === "enigma" ? 1 : 0) + '">Edit</button>';
|
|
1218
1294
|
const main = s.source === "enigma"
|
|
1219
|
-
? '<button type="button" class="toggle' + (s.discarded ? "" : " on") + '" data-
|
|
1295
|
+
? '<button type="button" class="toggle' + (s.discarded ? "" : " on") + '" data-kind="bool" title="'
|
|
1296
|
+
+ (s.discarded ? "Disabled - click to enable" : "Enabled - click to disable") + '" data-name="' + esc(s.name)
|
|
1220
1297
|
+ '" data-action="' + (s.discarded ? "enable" : "disable") + '">' + (s.discarded ? "Disabled" : "Enabled") + "</button>"
|
|
1221
1298
|
: '<button type="button" class="btn-danger" data-name="' + esc(s.name) + '" data-action="remove">Remove</button>';
|
|
1222
1299
|
return '<div class="set-row"><div class="set-meta"><div class="set-name">' + esc(s.name) + ext + ver + upd + where + "</div>"
|
|
@@ -1278,12 +1355,12 @@
|
|
|
1278
1355
|
}
|
|
1279
1356
|
|
|
1280
1357
|
function wireSkills() {
|
|
1281
|
-
$("skillsBody").addEventListener("click", (e) => {
|
|
1358
|
+
$("skillsBody").addEventListener("click", async (e) => {
|
|
1282
1359
|
const b = e.target.closest("button[data-action]");
|
|
1283
1360
|
if (!b) return;
|
|
1284
1361
|
const { name, action } = b.dataset;
|
|
1285
1362
|
if (action === "edit") { openEditor(name, b.dataset.enigma === "1"); return; }
|
|
1286
|
-
if (action === "remove" && !
|
|
1363
|
+
if (action === "remove" && !(await askConfirm("Remove skill", 'Remove the external skill "' + name + '" from all agents? This deletes it from disk.', true))) return;
|
|
1287
1364
|
postSkill(name, action);
|
|
1288
1365
|
});
|
|
1289
1366
|
$("seSave").addEventListener("click", saveEditor);
|
|
@@ -1351,27 +1428,28 @@
|
|
|
1351
1428
|
} catch { $("accountsNote").textContent = "Could not reach the server."; }
|
|
1352
1429
|
}
|
|
1353
1430
|
function wireAccounts() {
|
|
1354
|
-
$("accAdd").addEventListener("click", () => {
|
|
1355
|
-
const tool = accTools.length === 1 ? accTools[0].name
|
|
1431
|
+
$("accAdd").addEventListener("click", async () => {
|
|
1432
|
+
const tool = accTools.length === 1 ? accTools[0].name
|
|
1433
|
+
: await askChoice("Add account", accTools.map((t) => ({ value: t.name, label: t.label || t.name })), accTools[0] ? accTools[0].name : "", "Which tool is this account for?");
|
|
1356
1434
|
if (!tool) return;
|
|
1357
|
-
const name =
|
|
1435
|
+
const name = await askText("Add account", "", "New account name (e.g. work):");
|
|
1358
1436
|
if (name) accAction("account.add", { tool, name });
|
|
1359
1437
|
});
|
|
1360
|
-
$("profAdd").addEventListener("click", () => { const name =
|
|
1361
|
-
$("accountsBody").addEventListener("click", (e) => {
|
|
1438
|
+
$("profAdd").addEventListener("click", async () => { const name = await askText("Add profile", "", "New profile name:"); if (name) accAction("profile.add", { name }); });
|
|
1439
|
+
$("accountsBody").addEventListener("click", async (e) => {
|
|
1362
1440
|
const b = e.target.closest("button[data-acc]"); if (!b) return;
|
|
1363
1441
|
const op = b.dataset.acc, tool = b.dataset.tool, name = b.dataset.name;
|
|
1364
1442
|
if (op === "activate") accAction("account.activate", { tool, name });
|
|
1365
|
-
else if (op === "remove") { if (
|
|
1366
|
-
else if (op === "rename") { const nn =
|
|
1443
|
+
else if (op === "remove") { if (await askConfirm("Remove account", "Remove account '" + name + "' and delete its local config dir?", true)) accAction("account.remove", { tool, name }); }
|
|
1444
|
+
else if (op === "rename") { const nn = await askText("Rename account", name, "Rename '" + name + "' to:"); if (nn && nn !== name) accAction("account.rename", { tool, name, newName: nn }); }
|
|
1367
1445
|
else if (op === "login") { $("accountsNote").textContent = "To log in, run this in a terminal: enigma " + tool + " " + name; }
|
|
1368
1446
|
});
|
|
1369
|
-
$("profilesBody").addEventListener("click", (e) => {
|
|
1447
|
+
$("profilesBody").addEventListener("click", async (e) => {
|
|
1370
1448
|
const b = e.target.closest("button[data-prof-act]"); if (!b) return;
|
|
1371
1449
|
const op = b.dataset.profAct, name = b.dataset.name;
|
|
1372
1450
|
if (op === "activate") accAction("profile.activate", { name });
|
|
1373
|
-
else if (op === "remove") { if (
|
|
1374
|
-
else if (op === "rename") { const nn =
|
|
1451
|
+
else if (op === "remove") { if (await askConfirm("Remove profile", "Remove profile '" + name + "'? (its accounts are kept)", true)) accAction("profile.remove", { name }); }
|
|
1452
|
+
else if (op === "rename") { const nn = await askText("Rename profile", name, "Rename profile '" + name + "' to:"); if (nn && nn !== name) accAction("profile.rename", { name, newName: nn }); }
|
|
1375
1453
|
});
|
|
1376
1454
|
$("profilesBody").addEventListener("change", (e) => {
|
|
1377
1455
|
const sel = e.target.closest("select[data-prof-tool]"); if (!sel) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
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": [
|