wrolpi/app/src/api/controller.js
Roland 0d2c13e4e7 Controller: move hotspot settings to controller.yaml with WiFi device dropdown.
Hotspot was reported as "not supported" on x86 machines (Ubuntu/Debian PCs)
because the Controller only watched the hardcoded default wlan0 device, while
predictable interface naming produces names like wlp2s0. The Settings page
saved hotspot_device to wrolpi.yaml, which the Controller never read.

The Controller now owns hotspot settings in controller.yaml:

* New endpoints: GET/POST /api/hotspot/settings, GET /api/hotspot/devices
  (WiFi interfaces discovered via nmcli).
* Legacy hotspot_* values in wrolpi.yaml are migrated into controller.yaml
  once on startup, unless controller.yaml already sets them.
* The Hotspot SSID/password/device fields moved from the Settings page to the
  Control page next to the Hotspot toggle; the device is now a dropdown of
  the WiFi devices found on the machine.
* The Controller fallback UI gets the same hotspot settings form for parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 09:03:58 -06:00

252 lines
6.7 KiB
JavaScript

/**
* Controller API client for WROLPi.
*
* These functions call the Controller service endpoints.
* Controller handles system-level operations that need to work
* even when the main API is down.
*/
// Controller endpoints are served under /controller/api
const CONTROLLER_BASE = '/controller/api';
/**
* Fetch with error handling for Controller endpoints.
*/
async function controllerFetch(endpoint, options = {}) {
const url = `${CONTROLLER_BASE}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
const error = await response.json().catch(() => ({detail: response.statusText}));
throw new Error(error.detail || 'Controller request failed');
}
return response.json();
}
// --- Status Endpoints ---
/**
* Get all system stats from the Controller.
* Returns CPU, memory, load, drives, network, power, iostat, processes,
* disk_bandwidth along with system info (dockerized, is_rpi, hotspot, throttle).
*/
export async function getControllerStats() {
return controllerFetch('/stats');
}
export async function getCpuStatus() {
return controllerFetch('/status/cpu');
}
export async function getMemoryStatus() {
return controllerFetch('/status/memory');
}
export async function getLoadStatus() {
return controllerFetch('/status/load');
}
export async function getDrivesStatus() {
return controllerFetch('/status/drives');
}
export async function getPrimaryDriveStatus() {
return controllerFetch('/status/primary-drive');
}
export async function getNetworkStatus() {
return controllerFetch('/status/network');
}
export async function getPowerStatus() {
return controllerFetch('/status/power');
}
// --- Admin Endpoints ---
export async function getHotspotStatus() {
return controllerFetch('/hotspot/status');
}
export async function getHotspotDevices() {
return controllerFetch('/hotspot/devices');
}
export async function getHotspotSettings() {
return controllerFetch('/hotspot/settings');
}
export async function updateHotspotSettings(settings) {
return controllerFetch('/hotspot/settings', {method: 'POST', body: JSON.stringify(settings)});
}
export async function enableHotspot() {
return controllerFetch('/hotspot/enable', {method: 'POST'});
}
export async function disableHotspot() {
return controllerFetch('/hotspot/disable', {method: 'POST'});
}
export async function getThrottleStatus() {
return controllerFetch('/throttle/status');
}
export async function enableThrottle() {
return controllerFetch('/throttle/enable', {method: 'POST'});
}
export async function disableThrottle() {
return controllerFetch('/throttle/disable', {method: 'POST'});
}
export async function getBluetoothStatus() {
return controllerFetch('/bluetooth/status');
}
export async function enableBluetooth() {
return controllerFetch('/bluetooth/enable', {method: 'POST'});
}
export async function disableBluetooth() {
return controllerFetch('/bluetooth/disable', {method: 'POST'});
}
// --- Samba Endpoints ---
export async function getSambaStatus() {
return controllerFetch('/samba/status');
}
export async function addSambaShare(name, path, readOnly = true, comment = '') {
return controllerFetch('/samba/shares', {
method: 'POST',
body: JSON.stringify({name, path, read_only: readOnly, comment}),
});
}
export async function removeSambaShare(shareName) {
return controllerFetch(`/samba/shares/${encodeURIComponent(shareName)}`, {method: 'DELETE'});
}
export async function shutdownSystem() {
return controllerFetch('/shutdown', {method: 'POST'});
}
export async function rebootSystem() {
return controllerFetch('/reboot', {method: 'POST'});
}
export async function restartServices() {
return controllerFetch('/restart', {method: 'POST'});
}
// --- Service Endpoints ---
export async function getServices() {
return controllerFetch('/services');
}
export async function getServiceStatus(serviceName) {
return controllerFetch(`/services/${serviceName}`);
}
export async function startService(serviceName) {
return controllerFetch(`/services/${serviceName}/start`, {method: 'POST'});
}
export async function stopService(serviceName) {
return controllerFetch(`/services/${serviceName}/stop`, {method: 'POST'});
}
export async function restartService(serviceName) {
return controllerFetch(`/services/${serviceName}/restart`, {method: 'POST'});
}
export async function enableService(serviceName) {
return controllerFetch(`/services/${serviceName}/enable`, {method: 'POST'});
}
export async function disableService(serviceName) {
return controllerFetch(`/services/${serviceName}/disable`, {method: 'POST'});
}
export async function getServiceLogs(serviceName, lines = 100) {
return controllerFetch(`/services/${serviceName}/logs?lines=${lines}`);
}
// --- Disk Endpoints ---
export async function getDisks() {
return controllerFetch('/disks');
}
export async function getMounts() {
return controllerFetch('/disks/mounts');
}
export async function mountDisk(device, mountPoint, fstype = null, options = 'defaults', persist = false, forceShadowed = false) {
return controllerFetch('/disks/mount', {
method: 'POST',
body: JSON.stringify({
device,
mount_point: mountPoint,
fstype,
options,
persist,
force_shadowed: forceShadowed,
}),
});
}
export async function unmountDisk(mountPoint, lazy = false) {
return controllerFetch('/disks/unmount', {
method: 'POST',
body: JSON.stringify({mount_point: mountPoint, lazy}),
});
}
export async function detectWrolpiDrives() {
return controllerFetch('/disks/detect-wrolpi');
}
export async function getSmartStatus() {
return controllerFetch('/disks/smart');
}
// --- Fstab Endpoints ---
export async function getFstabEntries() {
return controllerFetch('/disks/fstab');
}
export async function addFstabEntry(device, mountPoint, fstype, options = 'defaults,nofail,x-systemd.device-timeout=10s') {
return controllerFetch('/disks/fstab', {
method: 'POST',
body: JSON.stringify({device, mount_point: mountPoint, fstype, options}),
});
}
export async function removeFstabEntry(mountPoint) {
const encodedPath = encodeURIComponent(mountPoint.slice(1));
return controllerFetch(`/disks/fstab/${encodedPath}`, {method: 'DELETE'});
}
// --- Health Check ---
export async function healthCheck() {
return controllerFetch('/health');
}
// --- Controller Info ---
export async function getControllerInfo() {
return controllerFetch('/info');
}