Multi-host phase 1: API tokens, auto TLS with pinned fingerprints, hub-side node manager + reverse proxy (WebSockets included), machine switcher in the rail, Machines page
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
fc42675e9f
commit
7dcdb5dc98
@@ -0,0 +1,41 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { MachineStatus } from './api'
|
||||
|
||||
// The host picker at the top of the rail. The hub is always first; managed machines follow
|
||||
// with their live status. Picking one re-points the whole console at it.
|
||||
export default function MachineSwitcher({ hub, current, machines, onChange, onManage }: {
|
||||
hub: string; current: string | null; machines: MachineStatus[]; onChange: (name: string | null) => void; onManage: () => void
|
||||
}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const box = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
const h = (e: MouseEvent) => { if (box.current && !box.current.contains(e.target as Node)) setOpen(false) }
|
||||
window.addEventListener('click', h)
|
||||
return () => window.removeEventListener('click', h)
|
||||
}, [])
|
||||
const cur = current ? machines.find((m) => m.name === current) : null
|
||||
const dotFor = (m?: MachineStatus | null) => (!m ? 'ok' : m.online ? 'ok' : 'bad')
|
||||
return (
|
||||
<div className="mswitch" ref={box}>
|
||||
<button className="mswitch-btn" onClick={() => setOpen(!open)} title="switch machine">
|
||||
<span className={'dot ' + (current ? dotFor(cur) : 'ok')} />
|
||||
<span className="mname">{current ?? hub}</span>
|
||||
<span className="muted small">{current ? (cur?.online ? 'remote' : 'offline') : 'this host'}</span>
|
||||
<span className="chev">▾</span>
|
||||
</button>
|
||||
{open && (
|
||||
<div className="mmenu">
|
||||
<button className={'mitem' + (!current ? ' on' : '')} onClick={() => { setOpen(false); onChange(null) }}>
|
||||
<span className="dot ok" /><span className="mname">{hub}</span><span className="muted small">this host</span>
|
||||
</button>
|
||||
{machines.map((m) => (
|
||||
<button key={m.name} className={'mitem' + (current === m.name ? ' on' : '')} onClick={() => { setOpen(false); onChange(m.name) }} title={m.error || m.url}>
|
||||
<span className={'dot ' + dotFor(m)} /><span className="mname">{m.name}</span><span className="muted small">{m.online ? m.load : 'offline'}</span>
|
||||
</button>
|
||||
))}
|
||||
<button className="mitem manage" onClick={() => { setOpen(false); onManage() }}>+ manage machines…</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user