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})}
+ />