// Egress — region groups + node table const { Modal, Drawer, Toggle, Seg, RegionPill, StatusDot, LatencyBar, useToast } = window.UI; function PageEgress({ state, dispatch }) { const { pools, regionGroups } = state; const [filter, setFilter] = React.useState("all"); const [search, setSearch] = React.useState(""); const [groupFilter, setGroupFilter] = React.useState(null); const [drawerNode, setDrawerNode] = React.useState(null); const [metaNode, setMetaNode] = React.useState(null); const [addOpen, setAddOpen] = React.useState(false); const toast = useToast(); const realPools = pools.filter(p => p.source === "static" || p.source === "subscription"); const allNodes = realPools.flatMap(p => (p.nodes || []).map(n => ({ ...n, pool: p.name, poolSource: p.source }))); const filtered = allNodes.filter(n => { if (filter === "normal" && n.residential) return false; if (filter === "residential" && !n.residential) return false; if (groupFilter) { const g = n.residential ? `${n.region}-RES` : n.region; if (g !== groupFilter) return false; } if (search) { const q = search.toLowerCase(); if (!n.name.toLowerCase().includes(q) && !n.pool.toLowerCase().includes(q) && !(n.region||"").toLowerCase().includes(q) && !(n.server||"").toLowerCase().includes(q) && !(n.alias||"").toLowerCase().includes(q)) return false; } return true; }); const visibleGroups = regionGroups.filter(g => { if (filter === "normal" && g.residential) return false; if (filter === "residential" && !g.residential) return false; return true; }); return (

Egress

Region groups assemble static and subscription nodes by their region code. Templates back up cold regions automatically.

Region groups

click to filter the table below
{groupFilter && }
{visibleGroups.map(g => { const active = groupFilter === g.code; return (
setGroupFilter(active ? null : g.code)} style={{ padding:"16px 18px", background: active ? "rgba(255,255,255,0.04)" : "var(--bg-1)", cursor:"pointer", transition:"background 200ms var(--ease)", position:"relative", }}>
{g.templateBackup && tpl}
{g.online}/{g.count}
{g.minLatency ? `${g.minLatency}ms min` : "no signal"}
); })}

Egress nodes

{filtered.length} of {allNodes.length}
setSearch(e.target.value)} style={{paddingLeft:30, width:220}}/>
{filtered.map(n => ( setDrawerNode(n)} style={{cursor:"pointer"}}> ))} {filtered.length === 0 && }
Node Pool Region Type Latency Status
{ e.stopPropagation(); dispatch({type:"toggleNode", id:n.id}); }}> {}}/>
{n.name}
{n.server || "—"}{n.port ? `:${n.port}` : ""}
{n.pool}
{n.poolSource}
{n.type}
No nodes match your filters.
setDrawerNode(null)} title={drawerNode?.name || ""}> {drawerNode && ( <>
{drawerNode.type} {drawerNode.tags?.map(t => {t})}
Pool
{drawerNode.pool}
Source
{drawerNode.poolSource}
Server
{drawerNode.server || "—"}
Port
{drawerNode.port || "—"}
Region
{drawerNode.region}
Residential
{drawerNode.residential ? "yes" : "no"}
Enabled
{ dispatch({type:"toggleNode", id:drawerNode.id}); setDrawerNode({...drawerNode, enabled: !drawerNode.enabled}); }}/>
Last latency
Fail count
{drawerNode.fail_count}
{drawerNode.alias && <>
Alias
{drawerNode.alias}
}
)} setMetaNode(null)} dispatch={dispatch} toast={toast} onSaved={(updated) => { setMetaNode(null); setDrawerNode(updated); }} /> setAddOpen(false)} pools={realPools.filter(p => p.source === "static")} dispatch={dispatch} toast={toast}/>
); } function AddStaticModal({ open, onClose, pools, dispatch, toast }) { const [form, setForm] = React.useState({ pool: pools[0]?.name || "us-fleet", name: "", type: "socks5", server: "", port: 1080, region: "US", residential: false, username: "", password: "", tags: "" }); const set = (k, v) => setForm(f => ({ ...f, [k]: v })); React.useEffect(() => { setForm(f => pools.some(p => p.name === f.pool) ? f : { ...f, pool: pools[0]?.name || "" }); }, [pools]); const submit = async () => { if (!form.name || !form.server) { toast("Name and server are required"); return; } if (!form.pool) { toast("Static pool is required"); return; } const ok = await dispatch({ type:"addStaticNode", node: { name: form.name, type: form.type, server: form.server, port: +form.port, region: form.region.toUpperCase().slice(0,2), residential: form.residential, username: form.username, password: form.password, enabled: true, tags: form.tags.split(",").map(s => s.trim()).filter(Boolean), }, pool: form.pool }); if (ok) onClose(); }; return ( }>
set("name", e.target.value)} placeholder="us-east-3"/>
set("server", e.target.value)} placeholder="proxy.example.com"/>
set("port", e.target.value)}/>
set("region", e.target.value)} maxLength={2}/>
set("residential", v)} label={form.residential ? "Residential" : "Normal"}/>
set("username", e.target.value)} placeholder="optional"/>
set("password", e.target.value)} placeholder="optional"/>
set("tags", e.target.value)} placeholder="primary, streaming"/>
); } function NodeMetadataModal({ node, onClose, dispatch, toast, onSaved }) { const [form, setForm] = React.useState({ name: "", alias: "", region: "", residential: false, tags: "" }); React.useEffect(() => { if (!node) return; setForm({ name: node.name || "", alias: node.alias || "", region: node.region || "", residential: !!node.residential, tags: (node.tags || []).join(", "), }); }, [node]); if (!node) return null; const set = (k, v) => setForm(f => ({ ...f, [k]: v })); const tags = () => form.tags.split(",").map(s => s.trim()).filter(Boolean); const metadataTags = () => { const set = new Set(tags().map(t => t.toLowerCase())); if (form.residential) set.add("residential"); if (!form.residential) set.delete("residential"); return Array.from(set); }; const serverKey = `${node.server || "—"}${node.port ? `:${node.port}` : ""}`; const submit = async () => { const region = form.region.trim().toUpperCase().slice(0, 2); if (node.poolSource === "subscription") { const ok = await dispatch({ type:"updateSubscriptionNode", payload:{ pool: node.pool, node: node.name, server: node.server || "", port: node.port || 0, region, alias: form.alias.trim(), tags: metadataTags(), }}); if (ok) onSaved({ ...node, region, alias: form.alias.trim(), tags: metadataTags(), residential: !!form.residential }); return; } const nextName = form.name.trim(); if (!nextName) { toast("Node name is required"); return; } const updatedNode = { ...node, name: nextName, region, residential: !!form.residential, tags: metadataTags(), enabled: node.enabled !== false, }; const ok = await dispatch({ type: "updateStaticNode", pool: node.pool, node: node.name, updatedNode, }); if (ok) onSaved({ ...node, ...updatedNode, id: `${node.pool}:${nextName}` }); }; return ( }>
Pool
{node.pool}
Source
{node.poolSource}
Server key
{serverKey}
{node.poolSource === "static" ? (
set("name", e.target.value)} />
) : (
set("alias", e.target.value)} placeholder="hk-streaming-primary" />
)}
set("region", e.target.value.toUpperCase().slice(0, 2))} maxLength={2} />
set("residential", v)} label={form.residential ? "Residential" : "Normal"} />
set("tags", e.target.value)} placeholder="streaming, residential" /> {node.poolSource === "subscription" &&
Subscription metadata is saved by node name and server key.
}
); } window.PageEgress = PageEgress;