Merge pull request #358 from naturallaw777/arena/019fadc2-sovran-systemsos

Improve service version details and naming
This commit is contained in:
Sovran Systems
2026-07-29 07:18:55 -05:00
committed by GitHub
5 changed files with 110 additions and 41 deletions
+9
View File
@@ -2576,6 +2576,15 @@ def _get_service_version(unit: str) -> str | None:
) )
if result.returncode == 0 and result.stdout.strip(): if result.returncode == 0 and result.stdout.strip():
m = _NIX_STORE_VERSION_RE.search(result.stdout) m = _NIX_STORE_VERSION_RE.search(result.stdout)
# Some wrapped systemd commands contain an escaped or nested
# store path that the strict package-path expression misses. Fall
# back to scanning each store path segment for the version token.
if not m:
for store_path in re.findall(r"/nix/store/[a-z0-9]{32}-[^\\s/\\\"]+", result.stdout):
candidates = re.findall(r"(?:^|-)(\\d+\\.\\d+(?:\\.\\d+)*(?:[+-][a-zA-Z0-9]+(?:\\.[a-zA-Z0-9]+)*)?)$", store_path)
if candidates:
m = type("_VersionMatch", (), {"group": lambda self, n: candidates[-1]})()
break
if m: if m:
ver = m.group(1) ver = m.group(1)
# Strip a single trailing period (defensive; shouldn't appear in store paths) # Strip a single trailing period (defensive; shouldn't appear in store paths)
@@ -256,6 +256,80 @@
color: var(--text-primary); color: var(--text-primary);
} }
/* Version lives in the detail modal so compact tiles stay visually stable. */
.svc-detail-version-card {
display: flex;
align-items: center;
gap: 12px;
margin: -6px 0 20px;
padding: 12px 14px;
border: 1px solid rgba(109, 191, 139, 0.28);
border-radius: 10px;
background: linear-gradient(135deg, rgba(109, 191, 139, 0.12), rgba(109, 191, 139, 0.04));
}
.svc-detail-version-icon {
display: grid;
place-items: center;
width: 30px;
height: 30px;
border-radius: 8px;
background: rgba(109, 191, 139, 0.18);
color: var(--accent-color);
font-size: 1.35rem;
font-weight: 700;
}
.svc-detail-version-label {
color: var(--text-dim);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.svc-detail-version-copy {
min-width: 0;
}
.svc-detail-version-state {
display: inline-block;
margin-left: 6px;
padding: 2px 6px;
border-radius: 999px;
color: var(--accent-color);
background: rgba(109, 191, 139, 0.14);
font-size: 0.62rem;
letter-spacing: 0.04em;
}
.svc-detail-version-card--unavailable,
.svc-detail-version-card--not-applicable {
border-color: rgba(229, 165, 10, 0.3);
background: rgba(229, 165, 10, 0.07);
}
.svc-detail-version-card--unavailable .svc-detail-version-icon,
.svc-detail-version-card--not-applicable .svc-detail-version-icon {
color: var(--yellow);
background: rgba(229, 165, 10, 0.14);
}
.svc-detail-version-card--unavailable .svc-detail-version-state,
.svc-detail-version-card--not-applicable .svc-detail-version-state {
color: var(--yellow);
background: rgba(229, 165, 10, 0.14);
}
.svc-detail-version-value {
margin-top: 2px;
color: var(--text-primary);
font-family: 'JetBrains Mono', 'Fira Code', monospace;
font-size: 0.95rem;
font-weight: 700;
word-break: break-word;
}
/* ── Service detail: Domain ──────────────────────────────────────── */ /* ── Service detail: Domain ──────────────────────────────────────── */
.svc-detail-domain-value { .svc-detail-domain-value {
@@ -659,6 +659,29 @@ async function openServiceDetailModal(unit, name, icon) {
'</div>' + '</div>' +
'</div>'); '</div>');
// Keep package versions in the detail modal rather than the compact tile.
// The API uses `version` for regular services and `bitcoin_version` for
// older Bitcoin payloads, so accept both while the backend is upgraded.
var serviceVersion = data.version || data.bitcoin_version || '';
// These services are wrappers, setup helpers, or application stacks rather
// than a single versioned daemon represented by the tile's systemd unit.
var versionNotApplicable = [
'phpfpm-wordpress.service', 'phpfpm-nextcloud.service',
'albyhub.service', 'nwc-wallets.service',
'zeus-connect-setup.service', 'sparrow-autoconnect.service'
].indexOf(unit) !== -1;
var versionState = serviceVersion ? 'detected' : (versionNotApplicable ? 'not-applicable' : 'unavailable');
var versionText = serviceVersion || (versionNotApplicable
? 'This service does not expose a single package version'
: (effectiveEnabled ? 'Version could not be detected' : 'Start the service to detect its version'));
var versionBadge = serviceVersion ? 'Detected' : (versionNotApplicable ? 'Not applicable' : 'Not detected');
addSetup('<div class="svc-detail-version-card svc-detail-version-card--' + versionState + '">' +
'<div class="svc-detail-version-icon">⌁</div>' +
'<div class="svc-detail-version-copy"><div class="svc-detail-version-label">Installed version ' +
'<span class="svc-detail-version-state">' + versionBadge + '</span></div>' +
'<div class="svc-detail-version-value">' + escHtml(versionText) + '</div></div>' +
'</div>');
// Section B2: BIP-110 live status (bip110 tile only) // Section B2: BIP-110 live status (bip110 tile only)
if (icon === 'bip110' && data.bip110) { if (icon === 'bip110' && data.bip110) {
var bip110 = data.bip110; var bip110 = data.bip110;
+3 -40
View File
@@ -156,13 +156,10 @@ function buildTile(svc) {
var pct = Math.round((svc.sync_progress || 0) * 100); var pct = Math.round((svc.sync_progress || 0) * 100);
var id = tileId(svc); var id = tileId(svc);
var eta = _calcBtcEta(id, svc.sync_progress || 0); var eta = _calcBtcEta(id, svc.sync_progress || 0);
var ver = svc.version || svc.bitcoin_version || '';
var versionLabel = ver ? '<div class="tile-version">' + escHtml(ver) + '</div>' : '';
tile.innerHTML = tile.innerHTML =
'<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'">' + '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'">' +
'<div class="tile-icon-fallback" style="display:none">?</div>' + '<div class="tile-icon-fallback" style="display:none">?</div>' +
'<div class="tile-name">' + escHtml(svc.name) + '</div>' + '<div class="tile-name">' + escHtml(svc.name) + '</div>' +
versionLabel +
'<div class="tile-sync-container">' + '<div class="tile-sync-container">' +
'<div class="tile-sync-label">\u23F3 Syncing Timechain</div>' + '<div class="tile-sync-label">\u23F3 Syncing Timechain</div>' +
'<div class="tile-sync-bar-row">' + '<div class="tile-sync-bar-row">' +
@@ -178,10 +175,8 @@ function buildTile(svc) {
return tile; return tile;
} }
var ver = svc.version || svc.bitcoin_version || '';
var versionLabel = ver ? '<div class="tile-version">' + escHtml(ver) + '</div>' : '';
var bip110Badge = (svc.icon === 'bip110') ? _renderBip110Badge(svc.bip110) : ''; var bip110Badge = (svc.icon === 'bip110') ? _renderBip110Badge(svc.bip110) : '';
tile.innerHTML = '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tile-icon-fallback" style="display:none">?</div><div class="tile-name">' + escHtml(svc.name) + '</div>' + versionLabel + bip110Badge + '<div class="tile-status"><span class="status-dot ' + sc + '"></span><span class="status-text">' + st + '</span></div>'; tile.innerHTML = '<img class="tile-icon" src="/static/icons/' + escHtml(svc.icon) + '.svg" alt="' + escHtml(svc.name) + '" onerror="this.style.display=\'none\';this.nextElementSibling.style.display=\'flex\'"><div class="tile-icon-fallback" style="display:none">?</div><div class="tile-name">' + escHtml(svc.name) + '</div>' + bip110Badge + '<div class="tile-status"><span class="status-dot ' + sc + '"></span><span class="status-text">' + st + '</span></div>';
tile.style.cursor = "pointer"; tile.style.cursor = "pointer";
tile.addEventListener("click", function() { tile.addEventListener("click", function() {
@@ -235,22 +230,6 @@ function updateTiles(services) {
if (fill) fill.style.width = pct + "%"; if (fill) fill.style.width = pct + "%";
if (pctEl) pctEl.textContent = pct + "%"; if (pctEl) pctEl.textContent = pct + "%";
if (etaEl) etaEl.textContent = etaText; if (etaEl) etaEl.textContent = etaText;
// Update or insert version label
var syncVer = svc.version || svc.bitcoin_version || '';
if (syncVer) {
var syncVerEl = tile.querySelector(".tile-version");
if (syncVerEl) {
syncVerEl.textContent = syncVer;
} else {
var syncNameEl = tile.querySelector(".tile-name");
if (syncNameEl) {
var newSyncVerEl = document.createElement("div");
newSyncVerEl.className = "tile-version";
newSyncVerEl.textContent = syncVer;
syncNameEl.insertAdjacentElement("afterend", newSyncVerEl);
}
}
}
} else { } else {
// IBD finished or not syncing — if tile had sync layout rebuild it normally // IBD finished or not syncing — if tile had sync layout rebuild it normally
if (tile.querySelector(".tile-sync-container")) { if (tile.querySelector(".tile-sync-container")) {
@@ -265,22 +244,6 @@ function updateTiles(services) {
var text = tile.querySelector(".status-text"); var text = tile.querySelector(".status-text");
if (dot) dot.className = "status-dot " + sc; if (dot) dot.className = "status-dot " + sc;
if (text) text.textContent = st; if (text) text.textContent = st;
// Update or insert version label for all service tiles
var tileVer = svc.version || svc.bitcoin_version || '';
if (tileVer) {
var verEl = tile.querySelector(".tile-version");
if (verEl) {
verEl.textContent = tileVer;
} else {
var nameEl = tile.querySelector(".tile-name");
if (nameEl) {
var newVerEl = document.createElement("div");
newVerEl.className = "tile-version";
newVerEl.textContent = tileVer;
nameEl.insertAdjacentElement("afterend", newVerEl);
}
}
}
// Update BIP-110 badge for bip110 tiles // Update BIP-110 badge for bip110 tiles
if (svc.icon === 'bip110') { if (svc.icon === 'bip110') {
var badgeHtml = _renderBip110Badge(svc.bip110); var badgeHtml = _renderBip110Badge(svc.bip110);
@@ -290,8 +253,8 @@ function updateTiles(services) {
var newBadge = _firstElementFromHtml(badgeHtml); var newBadge = _firstElementFromHtml(badgeHtml);
if (newBadge) { badgeEl.replaceWith(newBadge); } else { badgeEl.remove(); } if (newBadge) { badgeEl.replaceWith(newBadge); } else { badgeEl.remove(); }
} else if (badgeHtml) { } else if (badgeHtml) {
// Insert badge after version label (or after tile-name if no version) // Insert badge after the service name
var anchorEl = tile.querySelector(".tile-version") || tile.querySelector(".tile-name"); var anchorEl = tile.querySelector(".tile-name");
if (anchorEl) { if (anchorEl) {
var newBadgeEl = _firstElementFromHtml(badgeHtml); var newBadgeEl = _firstElementFromHtml(badgeHtml);
if (newBadgeEl) anchorEl.insertAdjacentElement("afterend", newBadgeEl); if (newBadgeEl) anchorEl.insertAdjacentElement("afterend", newBadgeEl);
+1 -1
View File
@@ -79,7 +79,7 @@ let
{ label = "Test Username"; file = "/var/lib/secrets/matrix-test-username"; } { label = "Test Username"; file = "/var/lib/secrets/matrix-test-username"; }
{ label = "Test Password"; file = "/var/lib/secrets/matrix-test-password"; } { label = "Test Password"; file = "/var/lib/secrets/matrix-test-password"; }
]; } ]; }
{ name = "Element-Call"; unit = "livekit.service"; type = "system"; icon = "element-calling"; enabled = cfg.features.element-calling; category = "communication"; credentials = []; } { name = "LiveKit (Element Call)"; unit = "livekit.service"; type = "system"; icon = "element-calling"; enabled = cfg.features.element-calling; category = "communication"; credentials = []; }
] ]
# ── Self-Hosted Apps (server+desktop only) ───────────────── # ── Self-Hosted Apps (server+desktop only) ─────────────────
++ lib.optionals cfg.roles.server_plus_desktop [ ++ lib.optionals cfg.roles.server_plus_desktop [