Add phone hotspot uplink
Prefer a configured phone hotspot for outbound synchronization, preserve secure write-only credentials, and recover through home WiFi or the local Pi hotspot. Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
const form = document.getElementById('settings-form');
|
||||
const saveButtons = Array.from(document.querySelectorAll('.settings-save'));
|
||||
const message = document.getElementById('settings-message');
|
||||
const uplinkStatus = document.getElementById('uplink-status');
|
||||
const uplinkMessage = document.getElementById('uplink-message');
|
||||
const phoneSsid = document.getElementById('phone-hotspot-ssid');
|
||||
const phonePassword = document.getElementById('phone-hotspot-password');
|
||||
const phoneSave = document.getElementById('phone-hotspot-save');
|
||||
const phoneConnect = document.getElementById('phone-hotspot-connect');
|
||||
const phoneRemove = document.getElementById('phone-hotspot-remove');
|
||||
const lastPhoneAddress = document.getElementById('last-phone-address');
|
||||
const lastPhoneAddressLink = document.getElementById('last-phone-address-link');
|
||||
let initialUplinkStatusLoaded = false;
|
||||
|
||||
const fields = {
|
||||
dashcamUpload: document.getElementById('dashcam-upload-enabled'),
|
||||
@@ -26,6 +36,12 @@ function showMessage(text, kind) {
|
||||
message.hidden = false;
|
||||
}
|
||||
|
||||
function showUplinkMessage(text, kind) {
|
||||
uplinkMessage.textContent = text;
|
||||
uplinkMessage.className = `alert alert-${kind} mt-3 mb-0`;
|
||||
uplinkMessage.hidden = false;
|
||||
}
|
||||
|
||||
function setSaving(disabled) {
|
||||
saveButtons.forEach(button => {
|
||||
button.disabled = disabled;
|
||||
@@ -131,6 +147,116 @@ async function loadUploadHealth() {
|
||||
}
|
||||
}
|
||||
|
||||
function describeUplink(status) {
|
||||
const modeNames = {
|
||||
phone: 'Phone hotspot',
|
||||
local_hotspot: 'Pi local hotspot',
|
||||
wifi: status.active_connection || 'WiFi',
|
||||
disconnected: 'Disconnected',
|
||||
};
|
||||
const parts = [
|
||||
`Mode: ${modeNames[status.mode] || status.mode}.`,
|
||||
status.ip_address ? `IP: ${status.ip_address}.` : '',
|
||||
status.internet_reachable ? 'Internet reachable.' : 'Internet not verified.',
|
||||
];
|
||||
if (status.last_transition && status.last_transition.error) {
|
||||
parts.push(`Last error: ${status.last_transition.error}`);
|
||||
}
|
||||
return parts.filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
async function loadUplinkStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/uplink/status');
|
||||
const status = await response.json();
|
||||
if (!response.ok) throw new Error(status.error || `HTTP ${response.status}`);
|
||||
uplinkStatus.textContent = describeUplink(status);
|
||||
const phoneAddress = status.mode === 'phone'
|
||||
? status.ip_address
|
||||
: status.last_transition && status.last_transition.last_phone_ip;
|
||||
lastPhoneAddress.hidden = !phoneAddress;
|
||||
if (phoneAddress) {
|
||||
const url = `http://${phoneAddress}/`;
|
||||
lastPhoneAddressLink.href = url;
|
||||
lastPhoneAddressLink.textContent = url;
|
||||
}
|
||||
if (
|
||||
!initialUplinkStatusLoaded
|
||||
&& status.phone_configured
|
||||
&& !phoneSsid.value
|
||||
) {
|
||||
phoneSsid.value = status.phone_ssid;
|
||||
}
|
||||
initialUplinkStatusLoaded = true;
|
||||
phoneConnect.disabled = !status.phone_configured;
|
||||
phoneRemove.disabled = !status.phone_configured;
|
||||
} catch (error) {
|
||||
uplinkStatus.textContent = `Could not load network status: ${error.message}`;
|
||||
uplinkStatus.className = 'text-danger mb-3';
|
||||
}
|
||||
}
|
||||
|
||||
phoneSave.addEventListener('click', async () => {
|
||||
uplinkMessage.hidden = true;
|
||||
phoneSave.disabled = true;
|
||||
try {
|
||||
const response = await fetch('/api/uplink/config', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
ssid: phoneSsid.value,
|
||||
password: phonePassword.value,
|
||||
}),
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || `HTTP ${response.status}`);
|
||||
phonePassword.value = '';
|
||||
showUplinkMessage(
|
||||
'Phone hotspot saved. Select Wait for hotspot, then enable the hotspot on your phone within two minutes.',
|
||||
'success',
|
||||
);
|
||||
await loadUplinkStatus();
|
||||
} catch (error) {
|
||||
showUplinkMessage(`Could not save phone hotspot: ${error.message}`, 'danger');
|
||||
} finally {
|
||||
phoneSave.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
phoneConnect.addEventListener('click', async () => {
|
||||
phoneConnect.disabled = true;
|
||||
try {
|
||||
const response = await fetch('/api/uplink/connect', {method: 'POST'});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || `HTTP ${response.status}`);
|
||||
showUplinkMessage(
|
||||
'Waiting for the saved hotspot. Enable it on your phone within two minutes. This page may disconnect while networks switch.',
|
||||
'info',
|
||||
);
|
||||
} catch (error) {
|
||||
showUplinkMessage(`Could not request connection: ${error.message}`, 'danger');
|
||||
} finally {
|
||||
phoneConnect.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
phoneRemove.addEventListener('click', async () => {
|
||||
if (!window.confirm('Remove the saved phone hotspot from the Pi?')) return;
|
||||
phoneRemove.disabled = true;
|
||||
try {
|
||||
const response = await fetch('/api/uplink/config', {method: 'DELETE'});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || `HTTP ${response.status}`);
|
||||
phoneSsid.value = '';
|
||||
phonePassword.value = '';
|
||||
showUplinkMessage('Saved phone hotspot removed.', 'success');
|
||||
await loadUplinkStatus();
|
||||
} catch (error) {
|
||||
showUplinkMessage(`Could not remove phone hotspot: ${error.message}`, 'danger');
|
||||
phoneRemove.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
async function loadSettings() {
|
||||
setSaving(true);
|
||||
try {
|
||||
@@ -170,3 +296,5 @@ form.addEventListener('submit', async event => {
|
||||
|
||||
loadSettings();
|
||||
loadUploadHealth();
|
||||
loadUplinkStatus();
|
||||
setInterval(loadUplinkStatus, 15000);
|
||||
|
||||
Reference in New Issue
Block a user