diff --git a/src/Controllers/DeviceController.cs b/src/Controllers/DeviceController.cs index 2f922128..42a74896 100644 --- a/src/Controllers/DeviceController.cs +++ b/src/Controllers/DeviceController.cs @@ -19,14 +19,20 @@ public DeviceController(ILogger logger, DeviceSettingsStore de _state = state; } + [HttpGet("{id}/details")] + public async Task>> Details(string id) + { + if (_state.Devices.TryGetValue(id, out var device)) + return device.GetDetails().ToList(); + return new List>(); + } + [HttpGet("{id}/settings")] - public DeviceSettingsDetails Get(string id) + public Task Get(string id) { - var deviceSettings = _deviceSettingsStore.Get(id); - var details = new List>(); - if (deviceSettings?.Id != null && _state.Devices.TryGetValue(deviceSettings.Id, out var device)) - details.AddRange(device.GetDetails()); - return new DeviceSettingsDetails(deviceSettings ?? new DeviceSettings { Id = id, OriginalId = id }, details); + var settings = _deviceSettingsStore.Get(id); + settings ??= new DeviceSettings { OriginalId = id, Id = id }; + return Task.FromResult(settings); } [HttpPut("{id}/settings")] @@ -35,6 +41,4 @@ public async Task Set(string id, [FromBody] DeviceSettings value) await _deviceSettingsStore.Set(id, value); } } - - public readonly record struct DeviceSettingsDetails(DeviceSettings? settings, IList> details); } diff --git a/src/ui/src/lib/NodeActions.svelte b/src/ui/src/lib/NodeActions.svelte index 4a092f7d..c3ab6ee7 100644 --- a/src/ui/src/lib/NodeActions.svelte +++ b/src/ui/src/lib/NodeActions.svelte @@ -5,18 +5,18 @@ import { getModalStore, getToastStore, type ToastSettings } from '@skeletonlabs/skeleton'; import { updateMethod, flavor, version, artifact, flavorNames } from '$lib/firmware'; import Firmware from '$lib/modals/Firmware.svelte'; + import { restartNode, updateNodeSelf } from '$lib/node'; const modalStore = getModalStore(); const toastStore = getToastStore(); async function onRestart(i: Node) { try { - var response = await fetch(`${base}/api/node/${i.id}/restart`, { method: 'POST' }); - if (response.status != 200) throw new Error(response.statusText); + await restartNode(i.id); toastStore.trigger({ message: i.name + ' asked to reboot', background: 'variant-filled-primary' }); } catch (e) { console.log(e); - toastStore.trigger({ message: e, background: 'variant-filled-error' }); + toastStore.trigger({ message: e instanceof Error ? e.message : String(e), background: 'variant-filled-error' }); } } @@ -44,12 +44,8 @@ }); } else { if (i) { - fetch(`${base}/api/node/${i.id}/update`, { - method: 'POST', - body: '' - }) - .then((response) => { - if (response.status != 200) throw new Error(response.statusText); + updateNodeSelf(i.id) + .then(() => { const t: ToastSettings = { message: (i.name ?? i.id) + ' asked to update itself', background: 'variant-filled-primary' }; toastStore.trigger(t); }) diff --git a/src/ui/src/lib/NodeSettings.svelte b/src/ui/src/lib/NodeSettings.svelte index 32d72df6..ffa71c31 100644 --- a/src/ui/src/lib/NodeSettings.svelte +++ b/src/ui/src/lib/NodeSettings.svelte @@ -1,31 +1,22 @@ @@ -62,7 +53,7 @@ Max Distance - + diff --git a/src/ui/src/lib/device.ts b/src/ui/src/lib/device.ts new file mode 100644 index 00000000..a6d50b1b --- /dev/null +++ b/src/ui/src/lib/device.ts @@ -0,0 +1,14 @@ +import { base } from '$app/paths'; +import type { DeviceSetting, DeviceDetail } from './types'; + +export async function fetchDeviceSettings(fetch, id: string): Promise { + const response = await fetch(`${base}/api/device/${id}/settings`); + if (!response.ok) throw new Error("Something went wrong loading device details (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} + +export async function fetchDeviceDetails(id: string): Promise { + const response = await fetch(`${base}/api/device/${id}/details`); + if (!response.ok) throw new Error("Something went wrong loading device details (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} diff --git a/src/ui/src/lib/node.ts b/src/ui/src/lib/node.ts new file mode 100644 index 00000000..7bc8db8e --- /dev/null +++ b/src/ui/src/lib/node.ts @@ -0,0 +1,47 @@ +import { base } from '$app/paths'; +import type { Settings, Node, NodeSetting } from './types'; + +export async function loadSettings(id: string): Promise { + const response = await fetch(`${base}/api/node/${id}/settings`); + if (!response.ok) throw new Error("Something went wrong loading settings (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} + +export async function saveSettings(newSettings: Settings): Promise { + const response = await fetch(`${base}/api/node/${newSettings.id}/settings`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newSettings), + }); + if (!response.ok) throw new Error("Something went wrong loading settings (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} + +export async function restartNode(nodeId: string): Promise { + const response = await fetch(`${base}/api/node/${nodeId}/restart`, { method: 'POST' }); + if (!response.ok) throw new Error(response.statusText); +} + +export async function updateNodeSelf(nodeId: string): Promise { + const response = await fetch(`${base}/api/node/${nodeId}/update`, { method: 'POST' }); + if (!response.ok) throw new Error(response.statusText); +} + +export async function saveNodeSettings(nodeId: string, settings: NodeSetting): Promise { + const response = await fetch(`${base}/api/node/${nodeId}/settings`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(settings) + }); + if (!response.ok) throw new Error(response.statusText); +} + +export async function fetchNode(nodeId: string): Promise { + const response = await fetch(`${base}/api/node/${nodeId}/settings`); + if (!response.ok) throw new Error(response.statusText); + return await response.json(); +} \ No newline at end of file diff --git a/src/ui/src/lib/state.ts b/src/ui/src/lib/state.ts new file mode 100644 index 00000000..82ca3016 --- /dev/null +++ b/src/ui/src/lib/state.ts @@ -0,0 +1,26 @@ +import { base } from '$app/paths'; +import type { Config, CalibrationData, Device } from './types'; + +export async function fetchConfig(): Promise { + const response = await fetch(`${base}/api/state/config`); + if (!response.ok) throw new Error("Something went wrong loading config (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} + +export async function fetchCalibrationState(): Promise { + const response = await fetch(`${base}/api/state/calibration`); + if (!response.ok) throw new Error("Something went wrong loading calibration state (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} + +export async function fetchDeviceState(): Promise { + const response = await fetch(`${base}/api/state/devices`); + if (!response.ok) throw new Error("Something went wrong loading device state (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} + +export async function fetchNodeState(includeTele: boolean = true): Promise { + const response = await fetch(`${base}/api/state/nodes?includeTele=${includeTele}`); + if (!response.ok) throw new Error("Something went wrong loading node state (error="+response.status+" "+response.statusText+")"); + return await response.json(); +} \ No newline at end of file diff --git a/src/ui/src/lib/stores.ts b/src/ui/src/lib/stores.ts index 32f0ade3..3b93b07e 100644 --- a/src/ui/src/lib/stores.ts +++ b/src/ui/src/lib/stores.ts @@ -1,6 +1,8 @@ import { readable, writable, derived } from 'svelte/store'; import { base } from '$app/paths'; import type { Device, Config, Node, Settings, CalibrationData } from './types'; +import { loadSettings, saveSettings } from './node'; +import { fetchConfig, fetchCalibrationState, fetchDeviceState, fetchNodeState } from './state'; export const showAll: SvelteStore = writable(false); export const config = writable(); @@ -37,8 +39,8 @@ let socket: WebSocket; export const history = writable(['/']); async function getConfig() { - const response = await fetch(`${base}/api/state/config`); - config.set(await response.json()); + const data = await fetchConfig(); + config.set(data); } getConfig(); @@ -112,8 +114,7 @@ export const nodes = readable([], function start(set) { const interval = setInterval(() => { if (outstanding) return; outstanding = true; - fetch(`${base}/api/state/nodes?includeTele=true`) - .then((d) => d.json()) + fetchNodeState() .then((r) => { outstanding = false; errors = 0; @@ -133,15 +134,12 @@ export const nodes = readable([], function start(set) { export const calibration = readable({ matrix: {} }, function start(set) { async function fetchAndSet() { - const response = await fetch(`${base}/api/state/calibration`); - var data = await response.json(); + const data = await fetchCalibrationState(); set(data); } fetchAndSet(); - const interval = setInterval(() => { - fetchAndSet(); - }, 1000); + const interval = setInterval(fetchAndSet, 1000); return function stop() { clearInterval(interval); @@ -156,20 +154,11 @@ export const settings = (() => { set, update, load: async () => { - const response = await fetch(`${base}/api/node/*/settings`); - if (!response.ok) throw new Error("Something went wrong loading settings (error="+response.status+" "+response.statusText+")"); - const data = await response.json(); + const data = await loadSettings("*"); set(data); }, save: async (newSettings: Settings) => { - const response = await fetch(`${base}/api/node/*/settings`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(newSettings), - }); - const data = await response.json(); + const data = await saveSettings(newSettings); set(data); }, }; diff --git a/src/ui/src/lib/types.ts b/src/ui/src/lib/types.ts index 55d0d3f7..d7571214 100644 --- a/src/ui/src/lib/types.ts +++ b/src/ui/src/lib/types.ts @@ -55,14 +55,7 @@ export interface Config { devices: Device[]; } -export type NodeSetting = { - id: string | null; - name: string | null; - absorption: number | null; - rx_adj_rssi: number | null; - tx_ref_rssi: number | null; - max_distance: number | null; -}; +export type DeviceDetail = Array<{ key: string; value: string }>; export type DeviceSetting = { originalId: string; @@ -139,7 +132,9 @@ export interface CalibrationData { } } -export type Settings = { +export type NodeSetting = { + id: string | null; + name: string | null; updating: { autoUpdate: boolean; preRelease: boolean; @@ -163,7 +158,7 @@ export type Settings = { calibration: { rssiAt1m: number | null; rssiAdjustment: number | null; - absorptionFactor: number | null; + absorption: number | null; iBeaconRssiAt1m: number | null; }; }; diff --git a/src/ui/src/routes/devices/[id]/+page.svelte b/src/ui/src/routes/devices/[id]/+page.svelte index dd6ce578..052dd381 100644 --- a/src/ui/src/routes/devices/[id]/+page.svelte +++ b/src/ui/src/routes/devices/[id]/+page.svelte @@ -2,22 +2,27 @@ import { base } from '$app/paths'; import { devices } from '$lib/stores'; import { readable } from 'svelte/store'; - import type { DeviceSetting } from '$lib/types'; + import type { DeviceSetting, DeviceDetail } from '$lib/types'; import { Accordion, AccordionItem } from '@skeletonlabs/skeleton'; + import { fetchDeviceDetails } from '$lib/device'; import Map from '$lib/Map.svelte'; import DeviceDetailTabs from '$lib/DeviceDetailTabs.svelte'; import DeviceSettings from '$lib/DeviceSettings.svelte'; export let tab = 'map'; - export let data: { settings?: DeviceSetting } = {}; - $: device = $devices.find((d) => d.id === data.settings?.id); + export let data: { id: string, settings: DeviceSetting }; + $: device = $devices.find((d) => d.id === data.id); - export const deviceDetails = readable([], (set) => { + export const deviceDetails = readable([], (set) => { async function fetchAndSet() { try { - const response = await fetch(`${base}/api/device/${data.settings?.id}`); - const result = await response.json(); + const id = data.id; + if (!id) { + console.error('No device id'); + return; + } + const result = await fetchDeviceDetails(id); set(result.details); } catch (ex) { console.error(ex); diff --git a/src/ui/src/routes/devices/[id]/+page.ts b/src/ui/src/routes/devices/[id]/+page.ts index 6743de77..0ac0c4b3 100644 --- a/src/ui/src/routes/devices/[id]/+page.ts +++ b/src/ui/src/routes/devices/[id]/+page.ts @@ -1,13 +1,16 @@ -import { base } from '$app/paths'; +import { error } from '@sveltejs/kit'; +import type { PageLoad } from './$types'; +import { fetchDeviceSettings } from '$lib/device' -export async function load({ fetch, params }) { - return await fetch(`${base}/api/device/${params.id}`) - .then((response) => { - if (response.status != 200) throw new Error(response.statusText); - var data = response.json(); - return data; - }) - .catch((e) => { - return { settings: { originalId: params.id, id: null, name: null, 'rssi@1m': null, error: e } }; - }); -} +export const load: PageLoad = async ({ fetch, params }) => { + if (!params.id) { + throw error(400, 'No device id'); + } + try { + var settings = fetchDeviceSettings(fetch, params.id); + return { id: params.id, settings: settings }; + } + catch (e) { + return { settings: { originalId: params.id, id: null, name: null, 'rssi@1m': null, error: e } }; + } +}; diff --git a/src/ui/src/routes/nodes/[id]/+page.svelte b/src/ui/src/routes/nodes/[id]/+page.svelte index d50bd5c4..67520027 100644 --- a/src/ui/src/routes/nodes/[id]/+page.svelte +++ b/src/ui/src/routes/nodes/[id]/+page.svelte @@ -1,72 +1,27 @@ - - ESPresense Companion: Map - - - - -
-
- {#if floorId !== 'settings'} - - {/if} - {#if floorId === 'settings'} - - {/if} -
-
- - - -

Details

-
- - {#if $nodeDetails} - {#each $nodeDetails as d} - - {/each} - {/if} - -
-
-
-
+{#if error} +

{error}

+{:else if node} +

{node.name ?? node.id}

+ +{:else} +

Loading...

+{/if} diff --git a/src/ui/src/routes/nodes/[id]/+page.ts b/src/ui/src/routes/nodes/[id]/+page.ts index dfb014c9..df7c64bb 100644 --- a/src/ui/src/routes/nodes/[id]/+page.ts +++ b/src/ui/src/routes/nodes/[id]/+page.ts @@ -1,7 +1,7 @@ import { base } from '$app/paths'; export async function load({ fetch, params }) { - return await fetch(`${base}/api/node/${params.id}`) + return await fetch(`${base}/api/node/${params.id}/settings`) .then((response) => { if (response.status != 200) throw new Error(response.statusText); var data = response.json();