ON_OFF Phase 0-3: service-controller (.13:8443, token-gated, containers+systemd-user, audit log) + portal toggle cards (live status, per-user who, failed token, RAM) — full E2E verified
This commit is contained in:
@@ -35,6 +35,17 @@
|
||||
{% if tool.admin %}<span class="tag-chip tag-chip-admin" title="Admins only">Admin</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if tool.togglable and tool.service_id %}
|
||||
<div class="svc-strip" data-svc="{{ tool.service_id }}"
|
||||
data-ram="{{ tool.ram_mb or '' }}" data-name="{{ tool.name }}"
|
||||
onclick="event.preventDefault();event.stopPropagation();">
|
||||
<span class="svc-state" data-svc-state>
|
||||
<span class="svc-dot" data-svc-dot></span><span data-svc-label>…</span>
|
||||
</span>
|
||||
<span class="svc-ram" data-svc-ram></span>
|
||||
<button type="button" class="button button-utility button-sm" data-svc-btn disabled>…</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@@ -90,5 +101,62 @@
|
||||
syncToggle(sec);
|
||||
});
|
||||
});
|
||||
|
||||
// --- ON/OFF strips (live state from /api/services via host-side controller) ---
|
||||
var svcEls = document.querySelectorAll("[data-svc]");
|
||||
var svcState = {}; // id -> latest controller state
|
||||
var svcPollers = {}; // id -> timer handle
|
||||
if (svcEls.length) {
|
||||
function normState(s) {
|
||||
var st = (s && s.state) || "unknown";
|
||||
if (st === "active") st = "running";
|
||||
if (st === "inactive") st = "stopped";
|
||||
return st;
|
||||
}
|
||||
function paint(id, showPoll) {
|
||||
var strip = document.querySelector('[data-svc="' + id + '"]');
|
||||
if (!strip) return;
|
||||
var st = normState(svcState[id]);
|
||||
var btn = strip.querySelector("[data-svc-btn]");
|
||||
var lab = strip.querySelector("[data-svc-label]");
|
||||
var dot = strip.querySelector("[data-svc-dot]");
|
||||
var ram = strip.querySelector("[data-svc-ram]");
|
||||
var can = svcState[id] && svcState[id].can_toggle;
|
||||
var transitional = st === "starting" || st === "stopping";
|
||||
if (ram && svcState[id] && svcState[id].ram_mb) ram.textContent = "~" + svcState[id].ram_mb + "MB";
|
||||
dot.className = "svc-dot svc-" + (st === "running" ? "on" : st === "stopped" ? "off" : st === "failed" ? "fail" : "trans");
|
||||
if (lab) lab.textContent = st === "running" ? "Running" : st === "stopped" ? "Stopped" : st === "failed" ? (svcState[id].error || "Failed") : (st[0].toUpperCase() + st.slice(1));
|
||||
if (btn) {
|
||||
btn.disabled = !can || transitional;
|
||||
if (transitional) btn.textContent = st === "starting" ? "Starting…" : "Stopping…";
|
||||
else if (can) btn.textContent = st === "running" ? "Stop" : "Start";
|
||||
else btn.textContent = st === "running" ? "On" : "Off";
|
||||
}
|
||||
// keep polling only while a toggle is in flight
|
||||
var poll = /starting|stopping/.test(st);
|
||||
if (poll && !svcPollers[id]) svcPollers[id] = setInterval(function () { refresh(); }, 2500);
|
||||
if (!poll && svcPollers[id]) { clearInterval(svcPollers[id]); delete svcPollers[id]; }
|
||||
}
|
||||
function refresh() {
|
||||
fetch('/api/services').then(function (r) { return r.json(); }).then(function (d) {
|
||||
(d.services || []).forEach(function (s) { svcState[s.id] = s; paint(s.id); });
|
||||
}).catch(function () {});
|
||||
}
|
||||
document.querySelectorAll("[data-svc]").forEach(function (strip) {
|
||||
var id = strip.getAttribute("data-svc");
|
||||
var btn = strip.querySelector("[data-svc-btn]");
|
||||
if (btn) btn.addEventListener("click", function (e) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
var st = normState(svcState[id]);
|
||||
var action = st === "running" ? "stop" : "start";
|
||||
btn.disabled = true; btn.textContent = action === "start" ? "Starting…" : "Stopping…";
|
||||
fetch('/service/' + id + '/' + action, { method: 'POST' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) { if (d.ok) { svcState[id] = Object.assign({}, svcState[id], { state: d.state || (action === 'start' ? 'running' : 'stopped') }); paint(id); refresh(); } })
|
||||
.catch(function () { btn.disabled = false; refresh(); });
|
||||
});
|
||||
});
|
||||
refresh();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
Reference in New Issue
Block a user