Fix the blank VPN tab, and stop one page taking the console with it
On a machine with wireguard-tools installed but no tunnel up — core, which is the machine the console opens on — the tunnel list was a nil slice, which is null in JSON, and the page took its length. That throws, React unmounts the whole tree, and what is left is a blank window with nothing to go on. Two fixes, because either alone leaves the failure possible. The parse returns an empty list rather than nil, with a test that pins the marshalled shape; and the page treats a missing list as an empty one, so a node still running an older build cannot blank the console either. And the reason a small mistake had such a large blast radius: nothing caught it. Each page now renders inside a boundary, so a fault costs that page and says what it was, while the rail, the switcher and every other tab carry on. The terminals are deliberately left outside it — they hold live WebSockets, and remounting them to recover from somebody else's error would drop the sessions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
acb83d3a4a
commit
32fdbf169a
+12
-7
@@ -48,14 +48,15 @@ function Peer({ p, server }: { p: VPNPeer; server: boolean }) {
|
||||
|
||||
function Tunnel({ t }: { t: VPNTunnel }) {
|
||||
const server = t.kind === 'server'
|
||||
const online = t.peers.filter((p) => p.online).length
|
||||
const peers = t.peers ?? []
|
||||
const online = peers.filter((p) => p.online).length
|
||||
return (
|
||||
<>
|
||||
<div className="dash-bar">
|
||||
<h2 className="section">{t.name}</h2>
|
||||
<span className="badge">{server ? 'devices dial in' : t.kind === 'client' ? 'this machine dials out' : 'tunnel'}</span>
|
||||
<span className="spacer" />
|
||||
<span className="muted small">{server ? `${online} of ${t.peers.length} online` : t.exit ? `via ${t.exit}` : ''}</span>
|
||||
<span className="muted small">{server ? `${online} of ${peers.length} online` : t.exit ? `via ${t.exit}` : ''}</span>
|
||||
</div>
|
||||
<section className="card">
|
||||
<div className="props">
|
||||
@@ -66,8 +67,8 @@ function Tunnel({ t }: { t: VPNTunnel }) {
|
||||
{t.fwmark && <div><span className="k">Firewall mark</span><span className="mono">{t.fwmark}</span></div>}
|
||||
</div>
|
||||
</section>
|
||||
{t.peers.map((p) => <Peer key={p.public_key} p={p} server={server} />)}
|
||||
{t.peers.length === 0 && <section className="card"><div className="empty">No peers are configured on this tunnel.</div></section>}
|
||||
{peers.map((p) => <Peer key={p.public_key} p={p} server={server} />)}
|
||||
{peers.length === 0 && <section className="card"><div className="empty">No peers are configured on this tunnel.</div></section>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -88,9 +89,13 @@ export default function VPN() {
|
||||
|
||||
if (!d) return <div className="dash">{err ? <div className="notice">{err === 'not found' ? "This machine's Ham Lab is older than the VPN tab. Update it from Machines." : err}</div> : <Loading label="Reading the tunnels…" />}</div>
|
||||
|
||||
// A machine running an older build answers with null rather than an empty list, and taking
|
||||
// the length of that is what a blank page is made of.
|
||||
const tunnels = d.tunnels ?? []
|
||||
|
||||
return (
|
||||
<div className="dash net">
|
||||
{d.tunnels.length === 0 && (
|
||||
{tunnels.length === 0 && (
|
||||
<>
|
||||
<div className="dash-bar"><h2 className="section">VPN</h2></div>
|
||||
<section className="card"><div className="empty">{d.note || 'Nothing here.'}</div>
|
||||
@@ -100,8 +105,8 @@ export default function VPN() {
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
{d.tunnels.map((t) => <Tunnel key={t.name} t={t} />)}
|
||||
{d.tunnels.length > 0 && (
|
||||
{tunnels.map((t) => <Tunnel key={t.name} t={t} />)}
|
||||
{tunnels.length > 0 && (
|
||||
<div className="muted small method" style={{ marginTop: 12 }}>
|
||||
Read from the kernel with <span className="mono">wg show</span>; names come from the configuration beside it.
|
||||
Online means a handshake in the last three minutes — WireGuard is silent when idle, so there is no connection to be in.
|
||||
|
||||
Reference in New Issue
Block a user