feat: add server-side device deletion

- add portal API and UI action to delete non-default devices
- protect the configured portal default device from deletion
- document that trip/history pages still scope to the single DEVICE_NAME

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 18:09:15 -04:00
co-authored by Copilot
parent 690cf0a5d4
commit 675bffb4a4
6 changed files with 78 additions and 0 deletions
+33
View File
@@ -7,6 +7,7 @@ const createDeviceButton = document.getElementById('create-device-button');
const newDeviceName = document.getElementById('new-device-name');
const rotateKeyButton = document.getElementById('rotate-key-button');
const requestSyncButton = document.getElementById('request-sync-button');
const deleteDeviceButton = document.getElementById('delete-device-button');
const copyCredentialsButton = document.getElementById('copy-credentials-button');
const selectedSummary = {
@@ -64,6 +65,7 @@ function setFormEnabled(enabled) {
}
rotateKeyButton.disabled = !enabled;
requestSyncButton.disabled = !enabled;
deleteDeviceButton.disabled = !enabled;
updateDirtyState();
}
@@ -239,6 +241,36 @@ async function requestSync() {
}
}
async function deleteDevice() {
if (selectedDeviceId === null) return;
const device = devices.find(candidate => candidate.id === selectedDeviceId);
if (!device) return;
if (!window.confirm(`Delete device "${device.name}" and all of its server-side trips, tracking logs, and dashcam files?`)) {
return;
}
hideMessage();
deleteDeviceButton.disabled = true;
try {
const response = await fetch(`/api/v1/portal/devices/${selectedDeviceId}`, {
method: 'DELETE',
});
const payload = await response.json();
if (!response.ok) {
throw new Error(payload.error || 'Could not delete device.');
}
latestApiKey = '';
credentialFields.apiKey.value = '';
copyCredentialsButton.disabled = true;
selectedDeviceId = null;
await loadDevices();
showMessage('Device deleted from the server.', 'success');
} catch (error) {
showMessage(error.message, 'danger');
} finally {
deleteDeviceButton.disabled = selectedDeviceId === null;
}
}
async function saveConfig(event) {
event.preventDefault();
if (selectedDeviceId === null) return;
@@ -284,6 +316,7 @@ form.addEventListener('submit', saveConfig);
createDeviceButton.addEventListener('click', createDevice);
rotateKeyButton.addEventListener('click', rotateKey);
requestSyncButton.addEventListener('click', requestSync);
deleteDeviceButton.addEventListener('click', deleteDevice);
copyCredentialsButton.addEventListener('click', () => {
copyCredentials().catch(error => showMessage(`Could not copy credentials: ${error.message}`, 'danger'));
});