diff options
Diffstat (limited to 'packages/meshbay-hub/src')
13 files changed, 303 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js index 86258d6..604be3c 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -743,4 +743,18 @@ export default { 'wizard.done_message': 'Ihre Gruppe ist bereit. Ihr Node hostet sie und die Verschlüsselung ist eingerichtet.', 'wizard.go_to_group': 'Zur Gruppe', 'wizard.finish_later': 'Einrichtung später abschließen', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js index 5d2725c..a58619e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -722,4 +722,18 @@ export default { 'node.setting_unit_minutes': 'min', 'node.setting_on': 'On', 'node.setting_off': 'Off', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js index 7080677..bcb0b93 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -739,4 +739,18 @@ export default { 'wizard.done_message': 'Su grupo está listo. Su node lo aloja y el cifrado está configurado.', 'wizard.go_to_group': 'Ir al grupo', 'wizard.finish_later': 'Finalizar configuración más tarde', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js index eee4292..2e3e4d6 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -755,4 +755,18 @@ export default { 'wizard.done_message': 'Votre groupe est prêt. Votre node l\'héberge et le chiffrement est configuré.', 'wizard.go_to_group': 'Aller au groupe', 'wizard.finish_later': 'Terminer la configuration plus tard', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js index 1031fed..dc1d16f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -753,4 +753,18 @@ export default { 'wizard.done_message': 'Il suo gruppo è pronto. Il suo node lo ospita e la cifratura è configurata.', 'wizard.go_to_group': 'Vai al gruppo', 'wizard.finish_later': 'Completa la configurazione in seguito', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js index 39863b1..90a5f24 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -737,4 +737,18 @@ export default { 'wizard.done_message': 'グループの準備ができました。node がホストし、暗号化が設定されています。', 'wizard.go_to_group': 'グループを開く', 'wizard.finish_later': 'セットアップを後で完了', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js index 21d14d7..3347087 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -755,4 +755,18 @@ export default { 'wizard.done_message': 'Uw groep is klaar. Uw node host de groep en de versleuteling is ingesteld.', 'wizard.go_to_group': 'Naar groep', 'wizard.finish_later': 'Later voltooien', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js index d6ca6af..499a54f 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -782,4 +782,18 @@ export default { 'wizard.done_message': 'Grupa jest gotowa. Node ją hostuje, a szyfrowanie jest skonfigurowane.', 'wizard.go_to_group': 'Przejdź do grupy', 'wizard.finish_later': 'Dokończ konfigurację później', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js index 5e6dd42..03a9f6e 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js @@ -740,4 +740,18 @@ export default { 'wizard.done_message': 'Seu grupo está pronto. Seu node o está hospedando e a criptografia está configurada.', 'wizard.go_to_group': 'Ir para o grupo', 'wizard.finish_later': 'Finalizar configuração depois', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js index da97162..8004868 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js @@ -724,4 +724,18 @@ export default { 'wizard.done_message': '您的群组已就绪。您的 node 正在托管它,加密已设置完成。', 'wizard.go_to_group': '前往群组', 'wizard.finish_later': '稍后完成设置', + 'node.stun_servers': 'STUN Servers', + 'node.stun_hint': 'Servers used for WebRTC NAT traversal (ICE candidate gathering).', + 'node.stun_add': 'Add', + 'node.stun_add_placeholder': 'stun:host:port', + 'node.stun_remove': 'Remove', + 'node.stun_move_up': 'Move up', + 'node.stun_move_down': 'Move down', + 'node.stun_save': 'Save', + 'node.stun_saving': 'Saving…', + 'node.stun_saved': 'STUN servers saved.', + 'node.stun_reset': 'Reset to defaults', + 'node.stun_empty': 'No STUN servers configured. NAT traversal will not work.', + 'node.stun_duplicate': 'This server is already in the list.', + 'node.stun_invalid': 'STUN URL must start with stun:', }; 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 9a6db7d..87a9815 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/node-page.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/node-page.js @@ -102,6 +102,9 @@ export function NodePage({ groups }) { const [nodeSettings, setNodeSettings] = useState(null); const [editSettings, setEditSettings] = useState(null); const [savingSettings, setSavingSettings] = useState(false); + const [editStun, setEditStun] = useState(null); + const [savingStun, setSavingStun] = useState(false); + const [stunInput, setStunInput] = useState(''); const [operatorPaired, setOperatorPaired] = useState(false); const [pairBusy, setPairBusy] = useState(false); const [pairStatus, setPairStatus] = useState(''); @@ -131,6 +134,7 @@ export function NodePage({ groups }) { useEffect(() => { if (nodeSettings && !editSettings) setEditSettings({ ...nodeSettings }); + if (nodeSettings && !editStun) setEditStun([...(nodeSettings.stun_servers || [])]); }, [nodeSettings]); const refresh = useCallback(async () => { @@ -291,6 +295,61 @@ export function NodePage({ groups }) { } }, [editSettings]); + const saveStun = useCallback(async () => { + if (!editStun) return; + setSavingStun(true); + setActionMsg(''); + try { + await nodeCall('PUT', '/api/node-settings', { stun_servers: editStun }); + setNodeSettings(s => s ? { ...s, stun_servers: [...editStun] } : s); + setActionMsg(t('node.stun_saved')); + } catch (err) { + setActionMsg(platform.bridgeMessage(err)); + } finally { + setSavingStun(false); + } + }, [editStun]); + + const addStunServer = useCallback(() => { + const url = stunInput.trim(); + if (!url) return; + if (!url.startsWith('stun:')) { + setActionMsg(t('node.stun_invalid')); + return; + } + if (editStun && editStun.includes(url)) { + setActionMsg(t('node.stun_duplicate')); + return; + } + setEditStun(s => [...(s || []), url]); + setStunInput(''); + setActionMsg(''); + }, [stunInput, editStun]); + + const removeStunServer = useCallback((idx) => { + setEditStun(s => s.filter((_, i) => i !== idx)); + }, []); + + const moveStunServer = useCallback((idx, dir) => { + setEditStun(s => { + const a = [...s]; + const target = idx + dir; + if (target < 0 || target >= a.length) return a; + [a[idx], a[target]] = [a[target], a[idx]]; + return a; + }); + }, []); + + const resetStunDefaults = useCallback(() => { + setEditStun([ + 'stun:stun.l.google.com:19302', + 'stun:stun1.l.google.com:19302', + 'stun:stun.cloudflare.com:3478', + 'stun:stun.services.mozilla.com:3478', + ]); + setActionMsg(''); + }, []); + const doPairOperator = useCallback(async () => { setPairBusy(true); setPairStatus(''); @@ -559,6 +618,51 @@ export function NodePage({ groups }) { </div> </div> `} + + ${editStun && html` + <div class="node-group"> + <span class="settings-heading">${t('node.stun_servers')}</span> + <p class="node-hint">${t('node.stun_hint')}</p> + ${editStun.length === 0 ? html` + <p class="node-hint">${t('node.stun_empty')}</p> + ` : html` + <div class="stun-server-list"> + ${editStun.map((url, idx) => html` + <div class="stun-server-entry" key=${url + idx}> + <span class="stun-server-url">${url}</span> + <div class="stun-server-actions"> + <button class="btn btn-small btn-icon" disabled=${idx === 0} + onClick=${() => moveStunServer(idx, -1)} + title=${t('node.stun_move_up')}>▲</button> + <button class="btn btn-small btn-icon" disabled=${idx === editStun.length - 1} + onClick=${() => moveStunServer(idx, 1)} + title=${t('node.stun_move_down')}>▼</button> + <button class="btn btn-small btn-danger btn-icon" + onClick=${() => removeStunServer(idx)} + title=${t('node.stun_remove')}>✕</button> + </div> + </div> + `)} + </div> + `} + <div class="stun-add-row"> + <input type="text" placeholder=${t('node.stun_add_placeholder')} + value=${stunInput} + onInput=${e => setStunInput(e.target.value)} + onKeyDown=${e => { if (e.key === 'Enter') addStunServer(); }} /> + <button class="btn btn-small btn-secondary" onClick=${addStunServer}> + ${t('node.stun_add')}</button> + </div> + <div class="node-settings-actions"> + <button class="btn btn-primary btn-small" disabled=${savingStun || busy} + onClick=${saveStun}> + ${savingStun ? t('node.stun_saving') : t('node.stun_save')}</button> + <button class="btn btn-small btn-secondary" disabled=${savingStun || busy} + onClick=${resetStunDefaults}> + ${t('node.stun_reset')}</button> + </div> + </div> + `} </div> `; } diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css index 2523c1a..e217cc8 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/style.css +++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css @@ -2708,6 +2708,59 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } } .node-settings-actions { margin-top: 12px; + display: flex; + gap: 8px; +} + +/* ── STUN server list ─────────────────────────────────────────────────────── */ +.stun-server-list { + display: flex; + flex-direction: column; + gap: 0; + margin-top: 8px; +} +.stun-server-entry { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 6px 0; + border-bottom: 1px solid var(--border); +} +.stun-server-url { + font-family: monospace; + font-size: 0.85em; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.stun-server-actions { + display: flex; + gap: 2px; + flex-shrink: 0; +} +.btn-icon { + min-width: 28px; + padding: 2px 6px; + font-size: 0.75em; + line-height: 1; +} +.stun-add-row { + display: flex; + gap: 6px; + margin-top: 10px; +} +.stun-add-row input { + flex: 1; + padding: 6px 10px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg-base); + color: var(--text); + font-family: monospace; + font-size: 0.85em; } /* ── Create Group Wizard ──────────────────────────────────────────────────── */ diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js index b60bd93..286bc9d 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js @@ -321,7 +321,12 @@ class MeshBayTransport { this._newNodeBundle = null; this._joinError = null; this._pc = new RTCPeerConnection({ - iceServers: [{ urls: 'stun:stun.l.google.com:19302' }], + iceServers: [ + { urls: 'stun:stun.l.google.com:19302' }, + { urls: 'stun:stun1.l.google.com:19302' }, + { urls: 'stun:stun.cloudflare.com:3478' }, + { urls: 'stun:stun.services.mozilla.com:3478' }, + ], }); this._channel = this._pc.createDataChannel('mnp', { ordered: true }); |