diff --git a/app/src/api/controller.js b/app/src/api/controller.js index acfe6585..ed7522dd 100644 --- a/app/src/api/controller.js +++ b/app/src/api/controller.js @@ -79,6 +79,11 @@ export async function getHotspotDevices() { return controllerFetch('/hotspot/devices'); } +export async function getHotspotProtocols(device) { + const query = device ? `?device=${encodeURIComponent(device)}` : ''; + return controllerFetch(`/hotspot/protocols${query}`); +} + export async function getHotspotSettings() { return controllerFetch('/hotspot/settings'); } diff --git a/app/src/components/admin/ControllerPage.js b/app/src/components/admin/ControllerPage.js index 17714785..f6d7d5a1 100644 --- a/app/src/components/admin/ControllerPage.js +++ b/app/src/components/admin/ControllerPage.js @@ -12,6 +12,7 @@ import { getDisks, getFstabEntries, getHotspotDevices, + getHotspotProtocols, getHotspotSettings, getSambaStatus, getServiceLogs, @@ -1410,10 +1411,17 @@ function SambaSection() { } +// Human labels for the hotspot protocols reported by the Controller. +const hotspotProtocolLabels = { + wpa2: 'WPA2 (most compatible)', + wpa3: 'WPA3', +}; + function HotspotSettingsForm() { const dockerized = useDockerized(); const [devices, setDevices] = React.useState([]); - const [form, setForm] = React.useState({device: '', ssid: '', password: ''}); + const [protocols, setProtocols] = React.useState([]); + const [form, setForm] = React.useState({device: '', ssid: '', password: '', protocol: ''}); const [loading, setLoading] = React.useState(true); const [saving, setSaving] = React.useState(false); const [qrOpen, setQrOpen] = React.useState(false); @@ -1422,7 +1430,12 @@ function HotspotSettingsForm() { const fetchSettings = async () => { try { const [settings, devicesResponse] = await Promise.all([getHotspotSettings(), getHotspotDevices()]); - setForm({device: settings.device, ssid: settings.ssid, password: settings.password}); + setForm({ + device: settings.device, + ssid: settings.ssid, + password: settings.password, + protocol: settings.protocol, + }); setDevices(devicesResponse.devices || []); } catch (e) { console.error('Failed to fetch hotspot settings', e); @@ -1433,6 +1446,23 @@ function HotspotSettingsForm() { fetchSettings(); }, []); + // The supported protocols depend on the selected device's hardware. + React.useEffect(() => { + if (!form.device) { + return; + } + const fetchProtocols = async () => { + try { + const response = await getHotspotProtocols(form.device); + setProtocols(response.protocols || []); + } catch (e) { + console.error('Failed to fetch hotspot protocols', e); + setProtocols([]); + } + }; + fetchProtocols(); + }, [form.device]); + const handleSave = async () => { setSaving(true); try { @@ -1461,6 +1491,10 @@ function HotspotSettingsForm() { const deviceOptions = [...new Set([...devices, form.device].filter(Boolean))] .map(device => ({key: device, value: device, text: device})); + // Keep the saved protocol selectable even if the device does not report it. + const protocolOptions = [...new Set([...protocols, form.protocol].filter(Boolean))] + .map(protocol => ({key: protocol, value: protocol, text: hotspotProtocolLabels[protocol] || protocol})); + // Special string which allows a mobile device to connect to a specific Wi-Fi. // The WPA QR format requires backslash-escaping of \ ; , " in the SSID and password. const escapeWifi = (s) => s.replace(/([\\;,"])/g, '\\$1'); @@ -1494,6 +1528,15 @@ function HotspotSettingsForm() { disabled={dockerized || saving} onChange={(e, {value}) => setForm({...form, device: value})} /> + setForm({...form, protocol: value})} + />