diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/app.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 57 |
1 files changed, 56 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 18ce83a..cf33d72 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -2587,10 +2587,25 @@ function VideoPlayer({ entry, transportRef, gekRef, onClose }) { videoRef.current.removeEventListener('seeking', onSeeking); } if (transport) { + // Tell the node first: dropping the handlers only makes us deaf, and a + // stream nobody is listening to still occupies a transcode slot. + transport.stopStream(); transport.onStreamInit = null; transport.onStreamData = null; transport.onStreamEnd = null; } + // The queue can hold several megabytes of decrypted video. + queueRef.current = []; + const ms = msRef.current; + if (ms && ms.readyState === 'open') { + try { ms.endOfStream(); } catch { /* already ended */ } + } + if (blobUrlRef.current) { + URL.revokeObjectURL(blobUrlRef.current); + blobUrlRef.current = null; + } + sbRef.current = null; + msRef.current = null; }; }, [entry, flushQueue]); @@ -3056,6 +3071,7 @@ function AdminPage({ token }) { const [logEvent, setLogEvent] = useState(''); const [logOffset, setLogOffset] = useState(0); const [blocklist, setBlocklist] = useState([]); + const [nodes, setNodes] = useState([]); const [detailUser, setDetailUser] = useState(null); const [error, setError] = useState(''); @@ -3105,6 +3121,10 @@ function AdminPage({ token }) { if (tab === 'stats') loadStats(); else if (tab === 'users') loadUsers(userSearch); else if (tab === 'groups') loadGroups(); + else if (tab === 'nodes') { + hubFetch('/v1/admin/nodes', { token }) + .then(d => setNodes(d.nodes || [])).catch(e => setError(e.message)); + } else if (tab === 'logs') { setLogOffset(0); loadLogs(logEvent, 0); } else if (tab === 'blocklist') loadBlocklist(); }, [tab]); @@ -3157,7 +3177,7 @@ function AdminPage({ token }) { } catch (e) { setError(e.message); } }, [token]); - const TABS = ['stats', 'users', 'groups', 'logs', 'blocklist']; + const TABS = ['stats', 'users', 'groups', 'nodes', 'logs', 'blocklist']; return html` <div> @@ -3267,6 +3287,41 @@ function AdminPage({ token }) { </table> `} + ${tab === 'nodes' && html` + <p class="settings-hint" style="margin-bottom:10px"> + ${t('admin.nodes_hint')} + </p> + <table class="admin-table"> + <thead><tr> + <th>${t('admin.col_username')}</th> + <th>${t('admin.col_observed_ip')}</th> + <th>${t('admin.col_hint')}</th> + <th>${t('admin.col_last_seen')}</th> + <th>${t('admin.col_status')}</th> + </tr></thead> + <tbody> + ${nodes.length === 0 && html` + <tr><td colspan="5" class="admin-empty">${t('admin.no_nodes')}</td></tr> + `} + ${nodes.map(n => html` + <tr key=${n.id}> + <td>${n.username || n.user_id.slice(0, 8)}</td> + <td style="font-family:monospace">${n.observed_ip || '—'}</td> + <td style="font-family:monospace;color:var(--text-dim)"> + ${n.endpoint_hint || '—'} + </td> + <td>${n.last_seen ? new Date(n.last_seen).toLocaleString() : '—'}</td> + <td> + <span class="badge ${n.online ? 'badge-ok' : ''}"> + ${n.online ? t('admin.node_online') : t('admin.node_offline')} + </span> + </td> + </tr> + `)} + </tbody> + </table> + `} + ${tab === 'logs' && html` <div class="admin-toolbar"> <select class="admin-select" value=${logEvent} onChange=${e => { |