Let the media server card show the whole mesh, not just this host

A partner's Emby URL is http://localhost:8096 — true there, meaningless here —
so nobody queries a partner's media server directly. Each host reports its own
over the SSH call remote_arr_cache_writer.sh already makes, which keeps its API
keys on it and costs no extra connection. Local is the default: opening the tab
to check your own server should not first make you read past a partner's.
A partner with no cache is stated as not collected rather than omitted, because
an empty mesh view and a partner that is down look identical otherwise.
This commit is contained in:
Gmer4Lfe
2026-08-14 19:30:23 -04:00
parent 24710af156
commit 04a0dcb39a
4 changed files with 100 additions and 6 deletions
+44 -5
View File
@@ -399,9 +399,41 @@ function _dur(s) {
// long it has been up. A server answering happily at 145% CPU is a different situation from one
// answering happily at 8%, and neither half says so alone.
let _vvMsFilter = 'all';
// Local by default. The mesh view is the deliberate act of asking about the whole estate; opening
// the tab to check your own server should not first make you read past a partner's.
let _vvMsScope = 'local';
function _mediaServersSection(servers) {
if (!servers || !servers.length) return '';
function _mediaServersSection(nodes) {
if (!nodes || !nodes.length) return '';
const me = nodes.filter(n => n.local);
const others = nodes.filter(n => !n.local);
const inScope = _vvMsScope === 'mesh' ? nodes : me;
// Flattened with their host attached, so a card can say which machine it is on once more than
// one is shown. Without that a mesh view is two Embys and no way to tell them apart.
const servers = [];
for (const n of inScope) for (const s of (n.servers || [])) {
servers.push({ ...s, _host: n.host, _hostName: n.name, _local: !!n.local, _age: n.cache_age });
}
// Only offered when there is a partner to combine with. On a single-host install the control
// would be two words for the same view.
const scopeTabs = others.length
? `<div style="display:flex;gap:4px;margin-left:auto;">` +
[['local', 'This host'], ['mesh', 'Mesh']].map(([k, lbl]) =>
`<button class="vv-arr-mstab${_vvMsScope === k ? ' on' : ''}" data-msscope="${vvEscAttr(k)}">${vvEscHtml(lbl)}</button>`
).join('') + `</div>`
: '';
// A partner that has never been collected is stated, not omitted — an empty mesh view and a
// partner that is simply down look identical otherwise.
const missing = _vvMsScope === 'mesh'
? others.filter(n => n.cache_miss).map(n =>
`<div style="grid-column:1/-1;font-size:10px;color:#3f3f3f;padding:2px 0;">
${vvEscHtml(n.name)} — not collected yet; the partner reports its own media servers over SSH every 2 hours
</div>`).join('')
: '';
const types = [...new Set(servers.map(s => s.type))];
// The filter only earns its space when there is something to filter. One server means the
@@ -436,6 +468,9 @@ function _mediaServersSection(servers) {
<span><span class="vv-arr-dot" style="background:${dot}"></span>
<span class="vv-arr-ver">${vvEscHtml(up ? (s.version || 'online') : 'offline')}</span></span>
</div>
${_vvMsScope === 'mesh' ? `<div style="font-size:9px;color:#3a3a3a;margin:-6px 0 8px;">
${vvEscHtml(s._hostName || s._host || '')}${s._local ? '' : ' · cached' + (s._age != null ? ' ' + _relTime(Math.floor(Date.now()/1000) - s._age) : '')}
</div>` : ''}
${up ? `
<div class="vv-arr-stats">
<div class="vv-arr-stat"><div class="vv-arr-stat-n${s.sessions ? '' : ' dim'}">${_n(s.sessions)}</div><div class="vv-arr-stat-l">Streams</div></div>
@@ -455,9 +490,12 @@ function _mediaServersSection(servers) {
}).join('');
return `<div style="grid-column:1/-1;">
<div style="font-size:11px;font-weight:700;color:#555;text-transform:uppercase;letter-spacing:.07em;margin-bottom:8px;">Media servers</div>
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
<span style="font-size:11px;font-weight:700;color:#555;text-transform:uppercase;letter-spacing:.07em;">Media servers</span>
${scopeTabs}
</div>
${tabs}
<div class="vv-arr-cards">${cards}</div>
<div class="vv-arr-cards">${cards}${missing}</div>
</div>`;
}
@@ -467,7 +505,8 @@ function _mediaServersSection(servers) {
document.addEventListener('click', ev => {
const b = ev.target.closest('.vv-arr-mstab');
if (!b) return;
_vvMsFilter = b.dataset.mstab || 'all';
if (b.dataset.msscope) _vvMsScope = b.dataset.msscope;
else _vvMsFilter = b.dataset.mstab || 'all';
if (_vvArrsLast) _render(_vvArrsLast);
});