From 2d6c276734fd19f80edf84cdb95f91a929df6365 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Sun, 30 Aug 2026 22:36:56 +0200 Subject: feat(ui): configurable ICE interfaces in the Node page The ice_interfaces setting (auto-exclude vs explicit whitelist) is now editable from the Node page, persisted via the settings API and roster, and hot-swapped at runtime by re-installing the aioice filter. Co-Authored-By: Claude Opus 4.6 --- .../src/meshbay_hub/static/node-page.js | 81 ++++++++++++++++++++++ 1 file changed, 81 insertions(+) (limited to 'packages/meshbay-hub/src/meshbay_hub/static/node-page.js') diff --git a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js index 87a9815..82fc56e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js @@ -105,6 +105,9 @@ export function NodePage({ groups }) { const [editStun, setEditStun] = useState(null); const [savingStun, setSavingStun] = useState(false); const [stunInput, setStunInput] = useState(''); + const [editIce, setEditIce] = useState(null); + const [savingIce, setSavingIce] = useState(false); + const [iceInput, setIceInput] = useState(''); const [operatorPaired, setOperatorPaired] = useState(false); const [pairBusy, setPairBusy] = useState(false); const [pairStatus, setPairStatus] = useState(''); @@ -135,6 +138,7 @@ export function NodePage({ groups }) { useEffect(() => { if (nodeSettings && !editSettings) setEditSettings({ ...nodeSettings }); if (nodeSettings && !editStun) setEditStun([...(nodeSettings.stun_servers || [])]); + if (nodeSettings && !editIce) setEditIce([...(nodeSettings.ice_interfaces || [])]); }, [nodeSettings]); const refresh = useCallback(async () => { @@ -350,6 +354,42 @@ export function NodePage({ groups }) { setActionMsg(''); }, []); + const saveIce = useCallback(async () => { + if (!editIce) return; + setSavingIce(true); + setActionMsg(''); + try { + await nodeCall('PUT', '/api/node-settings', { ice_interfaces: editIce }); + setNodeSettings(s => s ? { ...s, ice_interfaces: [...editIce] } : s); + setActionMsg(t('node.ice_saved')); + } catch (err) { + setActionMsg(platform.bridgeMessage(err)); + } finally { + setSavingIce(false); + } + }, [editIce]); + + const addIceInterface = useCallback(() => { + const name = iceInput.trim(); + if (!name) return; + if (editIce && editIce.includes(name)) { + setActionMsg(t('node.ice_duplicate')); + return; + } + setEditIce(s => [...(s || []), name]); + setIceInput(''); + setActionMsg(''); + }, [iceInput, editIce]); + + const removeIceInterface = useCallback((idx) => { + setEditIce(s => s.filter((_, i) => i !== idx)); + }, []); + + const resetIceAuto = useCallback(() => { + setEditIce([]); + setActionMsg(''); + }, []); + const doPairOperator = useCallback(async () => { setPairBusy(true); setPairStatus(''); @@ -663,6 +703,47 @@ export function NodePage({ groups }) { `} + + ${editIce && html` +
+ ${t('node.ice_interfaces')} +

${editIce.length === 0 + ? t('node.ice_auto_hint') + : t('node.ice_manual_hint')}

+ ${editIce.length > 0 && html` +
+ ${editIce.map((name, idx) => html` +
+ ${name} +
+ +
+
+ `)} +
+ `} +
+ setIceInput(e.target.value)} + onKeyDown=${e => { if (e.key === 'Enter') addIceInterface(); }} /> + +
+
+ + ${editIce.length > 0 && html` + + `} +
+
+ `} `; } -- cgit v1.2.3