diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-30 22:36:56 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-30 22:36:56 +0200 |
| commit | 2d6c276734fd19f80edf84cdb95f91a929df6365 (patch) | |
| tree | 41a697eb9c048218e0d800cbcb7fa39f4ede2b1d /packages/meshbay-hub/src/meshbay_hub/static/node-page.js | |
| parent | 07e6e4271ea3a45e9cd364eb6ec05801a653d9e8 (diff) | |
| download | meshbay-2d6c276734fd19f80edf84cdb95f91a929df6365.tar.gz | |
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 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/node-page.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/node-page.js | 81 |
1 files changed, 81 insertions, 0 deletions
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 }) { </div> </div> `} + + ${editIce && html` + <div class="node-group"> + <span class="settings-heading">${t('node.ice_interfaces')}</span> + <p class="node-hint">${editIce.length === 0 + ? t('node.ice_auto_hint') + : t('node.ice_manual_hint')}</p> + ${editIce.length > 0 && html` + <div class="stun-server-list"> + ${editIce.map((name, idx) => html` + <div class="stun-server-entry" key=${name + idx}> + <span class="stun-server-url">${name}</span> + <div class="stun-server-actions"> + <button class="btn btn-small btn-danger btn-icon" + onClick=${() => removeIceInterface(idx)} + title=${t('node.ice_remove')}>✕</button> + </div> + </div> + `)} + </div> + `} + <div class="stun-add-row"> + <input type="text" placeholder=${t('node.ice_add_placeholder')} + value=${iceInput} + onInput=${e => setIceInput(e.target.value)} + onKeyDown=${e => { if (e.key === 'Enter') addIceInterface(); }} /> + <button class="btn btn-small btn-secondary" onClick=${addIceInterface}> + ${t('node.ice_add')}</button> + </div> + <div class="node-settings-actions"> + <button class="btn btn-primary btn-small" disabled=${savingIce || busy} + onClick=${saveIce}> + ${savingIce ? t('node.ice_saving') : t('node.ice_save')}</button> + ${editIce.length > 0 && html` + <button class="btn btn-small btn-secondary" disabled=${savingIce || busy} + onClick=${resetIceAuto}> + ${t('node.ice_reset_auto')}</button> + `} + </div> + </div> + `} </div> `; } |