@enigmax/dashboard 0.1.46 → 0.1.47
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 +92 -17
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -363,6 +363,9 @@
|
|
|
363
363
|
border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 12px; font-size: 13px; font-family: var(--mono);
|
|
364
364
|
}
|
|
365
365
|
.modal-input:focus { outline: none; border-color: var(--accent); }
|
|
366
|
+
.modal-fl { display: block; margin-top: 14px; margin-bottom: 5px; font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
|
|
367
|
+
.modal-fl .modal-opt { text-transform: none; letter-spacing: 0; opacity: .75; }
|
|
368
|
+
.modal-fl + .modal-input { margin-top: 0; }
|
|
366
369
|
/* Usage view provider/account pickers. */
|
|
367
370
|
.useg-pick { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--muted); }
|
|
368
371
|
.useg-pick select { width: auto; min-width: 170px; }
|
|
@@ -3208,21 +3211,74 @@ export {
|
|
|
3208
3211
|
catch { projectsCache = []; }
|
|
3209
3212
|
populateScopeSelector();
|
|
3210
3213
|
}
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3214
|
+
function baseName(p) { return (p || "").trim().replace(/[\\/]+$/, "").split(/[\\/]/).pop() || ""; }
|
|
3215
|
+
function openCreateProject() {
|
|
3216
|
+
const back = document.createElement("div");
|
|
3217
|
+
back.className = "modal-back";
|
|
3218
|
+
back.innerHTML =
|
|
3219
|
+
'<div class="modal" role="dialog" aria-modal="true">'
|
|
3220
|
+
+ '<div class="modal-title">Create new project</div>'
|
|
3221
|
+
+ '<label class="modal-fl">Path</label><input class="modal-input" data-f="path" placeholder="/home/you/project" autocomplete="off" spellcheck="false">'
|
|
3222
|
+
+ '<div class="modal-err" data-e="path"></div>'
|
|
3223
|
+
+ '<label class="modal-fl">Name</label><input class="modal-input" data-f="name" placeholder="project name" autocomplete="off" spellcheck="false">'
|
|
3224
|
+
+ '<div class="modal-err" data-e="name"></div>'
|
|
3225
|
+
+ '<label class="modal-fl">Description <span class="modal-opt">(optional)</span></label><input class="modal-input" data-f="desc" placeholder="what this project is" autocomplete="off">'
|
|
3226
|
+
+ '<div class="modal-acts"><button type="button" class="toggle modal-cancel">Cancel</button><button type="button" class="toggle on modal-ok" disabled>Create</button></div></div>';
|
|
3227
|
+
document.body.appendChild(back);
|
|
3228
|
+
requestAnimationFrame(() => back.classList.add("show"));
|
|
3229
|
+
const $f = (s) => back.querySelector(s);
|
|
3230
|
+
const pathInp = $f('[data-f="path"]'), nameInp = $f('[data-f="name"]'), descInp = $f('[data-f="desc"]');
|
|
3231
|
+
const pathErr = $f('[data-e="path"]'), nameErr = $f('[data-e="name"]'), okBtn = $f(".modal-ok");
|
|
3232
|
+
let nameEdited = false, pathOk = false, nameOk = false, reqId = 0, debounce;
|
|
3233
|
+
let done = false;
|
|
3234
|
+
const close = () => { if (done) return; done = true; back.classList.remove("show"); setTimeout(() => back.remove(), 200); };
|
|
3235
|
+
const refreshOk = () => { okBtn.disabled = !(pathOk && nameOk); };
|
|
3236
|
+
// Name: required + not a duplicate (checked live against the loaded list; the server re-checks on add).
|
|
3237
|
+
const checkName = () => {
|
|
3238
|
+
const nm = nameInp.value.trim();
|
|
3239
|
+
if (!nm) { nameErr.textContent = "Enter a name."; nameOk = false; }
|
|
3240
|
+
else if (projectsCache.some((p) => p.label.toLowerCase() === nm.toLowerCase())) { nameErr.textContent = "A project with this name already exists."; nameOk = false; }
|
|
3241
|
+
else { nameErr.textContent = ""; nameOk = true; }
|
|
3242
|
+
refreshOk();
|
|
3243
|
+
};
|
|
3244
|
+
// Path: client format check, then a debounced server check (exists on disk + not already a project).
|
|
3245
|
+
const validatePath = async () => {
|
|
3246
|
+
const myId = ++reqId, path = pathInp.value.trim();
|
|
3247
|
+
try {
|
|
3248
|
+
const out = await (await fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op: "validate", path }) })).json();
|
|
3249
|
+
if (myId !== reqId) return; // a newer keystroke superseded this
|
|
3250
|
+
pathErr.textContent = out.pathError || ""; pathOk = !out.pathError; refreshOk();
|
|
3251
|
+
} catch { /* keep the client-side state */ }
|
|
3252
|
+
};
|
|
3253
|
+
const onPath = () => {
|
|
3254
|
+
const path = pathInp.value.trim();
|
|
3255
|
+
if (!nameEdited) { nameInp.value = baseName(path); checkName(); }
|
|
3256
|
+
if (!path) { pathErr.textContent = "Enter a path."; pathOk = false; refreshOk(); return; }
|
|
3257
|
+
if (!/^(\/|[A-Za-z]:[\\/]|\\\\)/.test(path)) { pathErr.textContent = "Enter an absolute path (e.g. /home/you/app or C:\\path)."; pathOk = false; refreshOk(); return; }
|
|
3258
|
+
pathErr.textContent = "Checking..."; pathOk = false; refreshOk();
|
|
3259
|
+
clearTimeout(debounce); debounce = setTimeout(validatePath, 300);
|
|
3260
|
+
};
|
|
3261
|
+
pathInp.addEventListener("input", onPath);
|
|
3262
|
+
nameInp.addEventListener("input", () => { nameEdited = true; checkName(); });
|
|
3263
|
+
back.querySelector(".modal-cancel").addEventListener("click", close);
|
|
3264
|
+
back.addEventListener("mousedown", (e) => { if (e.target === back) close(); });
|
|
3265
|
+
back.addEventListener("keydown", (e) => { if (e.key === "Escape") { e.preventDefault(); close(); } else if (e.key === "Enter" && !okBtn.disabled) { e.preventDefault(); okBtn.click(); } });
|
|
3266
|
+
okBtn.addEventListener("click", async () => {
|
|
3267
|
+
if (okBtn.disabled) return;
|
|
3268
|
+
const before = new Set(projectsCache.map((p) => p.path));
|
|
3269
|
+
let out;
|
|
3270
|
+
try { out = await (await fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op: "add", path: pathInp.value.trim(), name: nameInp.value.trim(), description: descInp.value.trim() }) })).json(); }
|
|
3271
|
+
catch { pathErr.textContent = "Could not reach the server."; return; }
|
|
3272
|
+
if (!out.ok) { pathErr.textContent = out.error || "Could not add the project."; pathOk = false; refreshOk(); return; }
|
|
3273
|
+
projectsCache = out.projects || projectsCache;
|
|
3274
|
+
const added = projectsCache.find((p) => !before.has(p.path)) || projectsCache[projectsCache.length - 1];
|
|
3275
|
+
currentScope = added ? added.path : "global";
|
|
3276
|
+
try { localStorage.setItem("enigmaScope", currentScope); } catch { /* */ }
|
|
3277
|
+
populateScopeSelector();
|
|
3278
|
+
close();
|
|
3279
|
+
location.hash = "#/settings"; route();
|
|
3280
|
+
});
|
|
3281
|
+
pathInp.focus();
|
|
3226
3282
|
}
|
|
3227
3283
|
function wireScope() {
|
|
3228
3284
|
const sel = $("scopeSel");
|
|
@@ -3262,13 +3318,31 @@ export {
|
|
|
3262
3318
|
const hooks = d.hooks ? '<span class="tag">installed</span>' : (d.isGitRepo ? '<button type="button" class="toggle on" data-proj-act="hooks">Install git hooks</button>' : '<span class="tag">not a git repo</span>');
|
|
3263
3319
|
const gate = d.gate ? '<span class="tag">initialized</span> <button type="button" class="toggle" data-proj-act="gate-eject">Eject</button>' : '<button type="button" class="toggle on" data-proj-act="gate-init">Initialize gate</button>';
|
|
3264
3320
|
const cfg = (d.config || []).map(projConfigRow).join("") || '<div class="empty" style="padding:8px 0">No project settings.</div>';
|
|
3321
|
+
const descLine = d.description ? " · " + esc(d.description) : "";
|
|
3265
3322
|
el.innerHTML =
|
|
3266
|
-
'<div class="scope-banner">Editing <b>' + esc(d.label) + "</b> · " + esc(d.path) + " · " + agentsLine + "</div>"
|
|
3323
|
+
'<div class="scope-banner">Editing <b>' + esc(d.label) + "</b> · " + esc(d.path) + descLine + " · " + agentsLine + "</div>"
|
|
3267
3324
|
+ '<div class="panel"><div class="set-cat-h">Setup</div><div class="set-grid">'
|
|
3268
3325
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Git hooks</div><div class="set-hint">commit secret guard for this repo</div></div><div class="set-ctl">' + hooks + "</div></div>"
|
|
3269
3326
|
+ '<div class="set-row"><div class="set-meta"><div class="set-name">AI quality gate</div><div class="set-hint">validate changes through the gate pipeline before push (needs the gate feature enabled)</div></div><div class="set-ctl">' + gate + "</div></div></div></div>"
|
|
3270
3327
|
+ '<div class="panel"><div class="set-cat-h">Project config</div><div class="set-cat-b">applies only to this project (its .enigma.json). Settings not shown here are global-only - switch Scope to Global for those.</div><div class="set-grid">' + cfg + "</div></div>"
|
|
3271
|
-
+ '<div class="panel"><div class="set-cat-h">Manage</div><div class="set-grid"
|
|
3328
|
+
+ '<div class="panel"><div class="set-cat-h">Manage</div><div class="set-grid">'
|
|
3329
|
+
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Name</div><div class="set-hint">display name for this project (must be unique)</div></div><div class="set-ctl"><input class="linput" data-proj-name value="' + esc(d.label) + '" spellcheck="false" style="width:200px"></div></div>'
|
|
3330
|
+
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Description</div><div class="set-hint">optional note shown in the header</div></div><div class="set-ctl ladd" style="align-items:center"><input class="linput" data-proj-desc value="' + esc(d.description || "") + '" placeholder="optional" style="flex:none;width:240px"><button type="button" class="toggle on" data-proj-save-meta>Save</button></div></div>'
|
|
3331
|
+
+ '<div class="set-row"><div class="set-meta"><div class="set-name">Forget project</div><div class="set-hint">remove it from the dashboard list - its files are not touched</div></div><div class="set-ctl"><button type="button" class="toggle" data-proj-forget>Forget</button></div></div>'
|
|
3332
|
+
+ "</div></div>";
|
|
3333
|
+
}
|
|
3334
|
+
async function saveProjectMeta() {
|
|
3335
|
+
if (!isProjectScope()) return;
|
|
3336
|
+
const nameInp = document.querySelector("[data-proj-name]"), descInp = document.querySelector("[data-proj-desc]");
|
|
3337
|
+
const name = nameInp ? nameInp.value.trim() : undefined, description = descInp ? descInp.value.trim() : "";
|
|
3338
|
+
try {
|
|
3339
|
+
const out = await (await fetch("/api/projects", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op: "update", path: currentScope, name, description }) })).json();
|
|
3340
|
+
if (!out.ok) { setNote("settingsNote", out.error || "Could not save.", "err"); return; }
|
|
3341
|
+
projectsCache = out.projects || projectsCache;
|
|
3342
|
+
populateScopeSelector(); // reflect the renamed project in the selector
|
|
3343
|
+
setNote("settingsNote", "Saved.", "ok");
|
|
3344
|
+
loadProjectSettings();
|
|
3345
|
+
} catch { setNote("settingsNote", "Could not reach the server.", "err"); }
|
|
3272
3346
|
}
|
|
3273
3347
|
async function loadProjectSkills() {
|
|
3274
3348
|
const el = $("skillsBody");
|
|
@@ -3314,6 +3388,7 @@ export {
|
|
|
3314
3388
|
if (cb) { projAction({ op: "config-set", key: cb.dataset.projCfgBool, value: cb.dataset.on !== "1" }); return; }
|
|
3315
3389
|
const rs = e.target.closest("[data-proj-cfg-reset]");
|
|
3316
3390
|
if (rs) { projAction({ op: "config-unset", key: rs.dataset.projCfgReset }); return; }
|
|
3391
|
+
if (e.target.closest("[data-proj-save-meta]")) { saveProjectMeta(); return; }
|
|
3317
3392
|
if (e.target.closest("[data-proj-forget]")) { forgetProject(); return; }
|
|
3318
3393
|
const act = e.target.closest("[data-proj-act]");
|
|
3319
3394
|
if (act) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
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": [
|