Inline service information into the table to make it more accessible
This commit is contained in:
parent
bd6a503241
commit
a3b5bcf2c5
6 changed files with 1779 additions and 12 deletions
1716
opencode_session_service_detail_inline_2026-08-30.txt
Normal file
1716
opencode_session_service_detail_inline_2026-08-30.txt
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -176,11 +176,20 @@ button:hover { border-color: var(--accent); }
|
||||||
.dot-busy { background: var(--busy); }
|
.dot-busy { background: var(--busy); }
|
||||||
.dot-sleep { background: var(--accent); }
|
.dot-sleep { background: var(--accent); }
|
||||||
|
|
||||||
.svc-name { cursor: pointer; color: var(--accent); }
|
.svc-row { cursor: pointer; }
|
||||||
|
.svc-name { color: var(--accent); }
|
||||||
.svc-name:hover { text-decoration: underline; }
|
.svc-name:hover { text-decoration: underline; }
|
||||||
|
.svc-caret {
|
||||||
.detail-pane { margin-top: 4px; }
|
display: inline-block;
|
||||||
.detail-pane:empty { display: none; }
|
margin-right: 6px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 10px;
|
||||||
|
transition: transform .15s;
|
||||||
|
}
|
||||||
|
.svc-row.open .svc-caret { transform: rotate(90deg); color: var(--accent); }
|
||||||
|
.svc-detail-row[hidden] { display: none; }
|
||||||
|
.svc-detail-row td { background: #12161d; padding: 10px 12px; white-space: normal; }
|
||||||
|
.table tbody tr.svc-detail-row:hover { background: #12161d; }
|
||||||
.detail-inner {
|
.detail-inner {
|
||||||
background: var(--bg-panel);
|
background: var(--bg-panel);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
|
||||||
|
|
@ -180,12 +180,53 @@
|
||||||
htmx.trigger(form, "submit");
|
htmx.trigger(form, "submit");
|
||||||
});
|
});
|
||||||
|
|
||||||
// keyboard: Enter/Space on service names
|
// ---------- services accordion ----------
|
||||||
|
const openSvc = new Set();
|
||||||
|
|
||||||
|
function loadSvcDetail(unit, target) {
|
||||||
|
htmx.ajax("GET", "/api/services/" + encodeURIComponent(unit) + "/detail", { target });
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSvcRow(row) {
|
||||||
|
const detailRow = row.nextElementSibling;
|
||||||
|
if (!detailRow || !detailRow.classList.contains("svc-detail-row")) return;
|
||||||
|
const unit = row.dataset.unit;
|
||||||
|
if (openSvc.has(unit)) {
|
||||||
|
openSvc.delete(unit);
|
||||||
|
row.classList.remove("open");
|
||||||
|
detailRow.hidden = true;
|
||||||
|
} else {
|
||||||
|
openSvc.add(unit);
|
||||||
|
row.classList.add("open");
|
||||||
|
detailRow.hidden = false;
|
||||||
|
loadSvcDetail(unit, detailRow.querySelector(".svc-detail"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.addEventListener("click", (e) => {
|
||||||
|
const row = e.target.closest("#services tr.svc-row");
|
||||||
|
if (!row || e.target.closest("button")) return;
|
||||||
|
toggleSvcRow(row);
|
||||||
|
});
|
||||||
|
|
||||||
document.body.addEventListener("keydown", (e) => {
|
document.body.addEventListener("keydown", (e) => {
|
||||||
const el = e.target.closest(".svc-name");
|
const el = e.target.closest(".svc-name");
|
||||||
if (el && (e.key === "Enter" || e.key === " ")) {
|
if (el && (e.key === "Enter" || e.key === " ")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
htmx.trigger(el, "click");
|
toggleSvcRow(el.closest("tr.svc-row"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.addEventListener("htmx:afterSwap", (e) => {
|
||||||
|
if (e.target.id !== "services" || !openSvc.size) return;
|
||||||
|
for (const row of e.target.querySelectorAll("tr.svc-row")) {
|
||||||
|
const unit = row.dataset.unit;
|
||||||
|
if (!openSvc.has(unit)) continue;
|
||||||
|
const detailRow = row.nextElementSibling;
|
||||||
|
if (!detailRow || !detailRow.classList.contains("svc-detail-row")) continue;
|
||||||
|
row.classList.add("open");
|
||||||
|
detailRow.hidden = false;
|
||||||
|
loadSvcDetail(unit, detailRow.querySelector(".svc-detail"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,7 @@
|
||||||
</select>
|
</select>
|
||||||
<button type="submit">filter</button>
|
<button type="submit">filter</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="load, every 5s" hx-swap="outerHTML" hx-include="#svc-filters"></div>
|
<div id="services" class="panel" hx-get="/api/services" hx-trigger="load, every 15s" hx-swap="outerHTML" hx-include="#svc-filters"></div>
|
||||||
<div id="service-detail" class="detail-pane"></div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="tab-plugins" class="tab hidden">
|
<section id="tab-plugins" class="tab hidden">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<div class="detail-inner">
|
<div class="detail-inner">
|
||||||
<h3 class="mono">{{ unit }}</h3>
|
|
||||||
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
||||||
<div class="kv">
|
<div class="kv">
|
||||||
<div><span class="muted">state:</span> {{ props.get("ActiveState", "?") }} ({{ props.get("SubState", "?") }})</div>
|
<div><span class="muted">state:</span> {{ props.get("ActiveState", "?") }} ({{ props.get("SubState", "?") }})</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<div id="services" class="panel" hx-get="/api/services" hx-trigger="every 5s" hx-swap="outerHTML" hx-include="#svc-filters">
|
<div id="services" class="panel" hx-get="/api/services" hx-trigger="every 15s" hx-swap="outerHTML" hx-include="#svc-filters">
|
||||||
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
{% if error %}<div class="alert">{{ error }}</div>{% endif %}
|
||||||
<div class="muted small">system: {{ state }} · {{ units | length }} units</div>
|
<div class="muted small">system: {{ state }} · {{ units | length }} units</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
|
|
@ -27,9 +27,9 @@
|
||||||
{% if u.sub in ("running", "exited") and u.active == "active" %}{% set dot = "dot-run" %}{% endif %}
|
{% if u.sub in ("running", "exited") and u.active == "active" %}{% set dot = "dot-run" %}{% endif %}
|
||||||
{% if u.active == "failed" %}{% set dot = "dot-failed" %}{% endif %}
|
{% if u.active == "failed" %}{% set dot = "dot-failed" %}{% endif %}
|
||||||
{% if u.sub in ("activating", "deactivating", "reloading") %}{% set dot = "dot-busy" %}{% endif %}
|
{% if u.sub in ("activating", "deactivating", "reloading") %}{% set dot = "dot-busy" %}{% endif %}
|
||||||
<tr>
|
<tr class="svc-row" data-unit="{{ u.name }}">
|
||||||
<td><span class="dot {{ dot }}"></span></td>
|
<td><span class="dot {{ dot }}"></span></td>
|
||||||
<td class="mono svc-name" hx-get="/api/services/{{ u.name }}/detail" hx-target="#service-detail" hx-swap="innerHTML" role="button" tabindex="0" title="click for details">{{ u.name }}</td>
|
<td class="mono svc-name" role="button" tabindex="0" title="click for details"><span class="svc-caret">▸</span>{{ u.name }}</td>
|
||||||
<td class="muted cell-clip" title="{{ u.desc }}">{{ u.desc }}</td>
|
<td class="muted cell-clip" title="{{ u.desc }}">{{ u.desc }}</td>
|
||||||
<td class="muted">{{ u.active }}{% if u.sub and u.sub not in ("running", "dead", "exited") %} <span class="small">({{ u.sub }})</span>{% endif %}</td>
|
<td class="muted">{{ u.active }}{% if u.sub and u.sub not in ("running", "dead", "exited") %} <span class="small">({{ u.sub }})</span>{% endif %}</td>
|
||||||
<td class="muted">{{ u.enabled or "—" }}</td>
|
<td class="muted">{{ u.enabled or "—" }}</td>
|
||||||
|
|
@ -43,6 +43,9 @@
|
||||||
<button class="btn" hx-post="/api/services/{{ u.name }}/action" hx-vals='{"action":"{% if u.enabled == "enabled" or u.enabled == "indirect" or u.enabled == "static" %}disable{% else %}enable{% endif %}"}' hx-include="#svc-filters" hx-target="#services" hx-swap="outerHTML">{% if u.enabled == "enabled" or u.enabled == "indirect" or u.enabled == "static" %}disable{% else %}enable{% endif %}</button>
|
<button class="btn" hx-post="/api/services/{{ u.name }}/action" hx-vals='{"action":"{% if u.enabled == "enabled" or u.enabled == "indirect" or u.enabled == "static" %}disable{% else %}enable{% endif %}"}' hx-include="#svc-filters" hx-target="#services" hx-swap="outerHTML">{% if u.enabled == "enabled" or u.enabled == "indirect" or u.enabled == "static" %}disable{% else %}enable{% endif %}</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr class="svc-detail-row" hidden data-unit="{{ u.name }}">
|
||||||
|
<td colspan="6"><div class="svc-detail"></div></td>
|
||||||
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="6" class="muted">no units match</td></tr>
|
<tr><td colspan="6" class="muted">no units match</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue