From ac4583d80ed5e0d3c8eebb2625286cb18b088b6a Mon Sep 17 00:00:00 2001 From: DTTerastar Date: Mon, 9 Sep 2024 09:40:22 -0400 Subject: [PATCH 1/3] Settings ui and service --- Controllers/SettingsController.cs | 75 ++++++++++++ src/Controllers/SettingsController.cs | 75 ++++++++++++ src/ui/src/lib/GlobalSettings.svelte | 155 ++++++++++++++++++++++++ src/ui/src/lib/stores.ts | 27 +++++ src/ui/src/lib/types.ts | 31 ++++- src/ui/src/routes/+layout.svelte | 1 + src/ui/src/routes/settings/+page.svelte | 14 +++ 7 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 Controllers/SettingsController.cs create mode 100644 src/Controllers/SettingsController.cs create mode 100644 src/ui/src/lib/GlobalSettings.svelte create mode 100644 src/ui/src/routes/settings/+page.svelte diff --git a/Controllers/SettingsController.cs b/Controllers/SettingsController.cs new file mode 100644 index 00000000..975c8e53 --- /dev/null +++ b/Controllers/SettingsController.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Mvc; +using System.IO; +using System.Text.Json; + +[ApiController] +[Route("api/settings")] +public class SettingsController : ControllerBase +{ + private const string SettingsFilePath = "settings.json"; + + [HttpGet] + public ActionResult GetSettings() + { + if (!System.IO.File.Exists(SettingsFilePath)) + { + return new Settings(); + } + + var json = System.IO.File.ReadAllText(SettingsFilePath); + return JsonSerializer.Deserialize(json); + } + + [HttpPost] + public IActionResult SaveSettings([FromBody] Settings settings) + { + var json = JsonSerializer.Serialize(settings); + System.IO.File.WriteAllText(SettingsFilePath, json); + return Ok(); + } +} + +public class Settings +{ + public Updating Updating { get; set; } + public Scanning Scanning { get; set; } + public Counting Counting { get; set; } + public Filtering Filtering { get; set; } + public Calibration Calibration { get; set; } +} + +public class Updating +{ + public bool AutoUpdate { get; set; } + public bool PreRelease { get; set; } +} + +public class Scanning +{ + public int? ForgetAfterMs { get; set; } +} + +public class Counting +{ + public string IdPrefixes { get; set; } + public double? StartCountingDistance { get; set; } + public double? StopCountingDistance { get; set; } + public int? IncludeDevicesAge { get; set; } +} + +public class Filtering +{ + public string IncludeIds { get; set; } + public string ExcludeIds { get; set; } + public double? MaxReportDistance { get; set; } + public double? EarlyReportDistance { get; set; } + public int? SkipReportAge { get; set; } +} + +public class Calibration +{ + public int? RssiAt1m { get; set; } + public int? RssiAdjustment { get; set; } + public double? AbsorptionFactor { get; set; } + public int? IBeaconRssiAt1m { get; set; } +} \ No newline at end of file diff --git a/src/Controllers/SettingsController.cs b/src/Controllers/SettingsController.cs new file mode 100644 index 00000000..3a8cc51c --- /dev/null +++ b/src/Controllers/SettingsController.cs @@ -0,0 +1,75 @@ +using Microsoft.AspNetCore.Mvc; +using System.IO; +using System.Text.Json; + +[ApiController] +[Route("api/settings")] +public class SettingsController : ControllerBase +{ + private const string SettingsFilePath = "settings.json"; + + [HttpGet] + public ActionResult GetSettings() + { + if (!System.IO.File.Exists(SettingsFilePath)) + { + return new Settings(); + } + + var json = System.IO.File.ReadAllText(SettingsFilePath); + return JsonSerializer.Deserialize(json); + } + + [HttpPost] + public IActionResult SaveSettings([FromBody] Settings settings) + { + var json = JsonSerializer.Serialize(settings); + System.IO.File.WriteAllText(SettingsFilePath, json); + return Ok(); + } +} + +public class Settings +{ + public UpdatingSettings Updating { get; set; } = new UpdatingSettings(); + public ScanningSettings Scanning { get; set; } = new ScanningSettings(); + public CountingSettings Counting { get; set; } = new CountingSettings(); + public FilteringSettings Filtering { get; set; } = new FilteringSettings(); + public CalibrationSettings Calibration { get; set; } = new CalibrationSettings(); +} + +public class UpdatingSettings +{ + public bool? AutoUpdate { get; set; } + public bool? PreRelease { get; set; } +} + +public class ScanningSettings +{ + public int? ForgetAfterMs { get; set; } +} + +public class CountingSettings +{ + public string? IdPrefixes { get; set; } + public double? StartCountingDistance { get; set; } + public double? StopCountingDistance { get; set; } + public int? IncludeDevicesAge { get; set; } +} + +public class FilteringSettings +{ + public string? IncludeIds { get; set; } + public string? ExcludeIds { get; set; } + public double? MaxReportDistance { get; set; } + public double? EarlyReportDistance { get; set; } + public int? SkipReportAge { get; set; } +} + +public class CalibrationSettings +{ + public int? RssiAt1m { get; set; } + public int? RssiAdjustment { get; set; } + public double? AbsorptionFactor { get; set; } + public int? IBeaconRssiAt1m { get; set; } +} \ No newline at end of file diff --git a/src/ui/src/lib/GlobalSettings.svelte b/src/ui/src/lib/GlobalSettings.svelte new file mode 100644 index 00000000..8e3aecb1 --- /dev/null +++ b/src/ui/src/lib/GlobalSettings.svelte @@ -0,0 +1,155 @@ + + +{#if loading} +
+
+ +

Loading settings...

+
+
+{:else if error} +
+

Error: {error}

+
+{:else if $settings} +
+
+

Updating

+
+
+ + +
+
+ + +
+
+
+ +
+

Scanning

+
+ +
+
+ +
+

Counting

+
+ + + + + + + +
+
+ +
+

Filtering

+
+ + + + + + + + + +
+
+ +
+

Calibration

+
+ + + + + + + +
+
+ +
+ +
+
+{:else} +
+

No settings available.

+
+{/if} \ No newline at end of file diff --git a/src/ui/src/lib/stores.ts b/src/ui/src/lib/stores.ts index 2a59607e..18cc1f3a 100644 --- a/src/ui/src/lib/stores.ts +++ b/src/ui/src/lib/stores.ts @@ -147,3 +147,30 @@ export const calibration = readable({matrix: {}}, function clearInterval(interval); }; }); + +export const settings = (() => { + const { subscribe, set, update } = writable(null); + + return { + subscribe, + set, + update, + load: async () => { + const response = await fetch(`${base}/api/settings`); + if (!response.ok) throw new Error("Something went wrong loading settings (error="+response.status+" "+response.statusText+")"); + const data = await response.json(); + set(data); + }, + save: async (newSettings: Settings) => { + const response = await fetch(`${base}/api/settings`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newSettings), + }); + const data = await response.json(); + set(data); + }, + }; +})(); \ No newline at end of file diff --git a/src/ui/src/lib/types.ts b/src/ui/src/lib/types.ts index dd073a7a..daf92321 100644 --- a/src/ui/src/lib/types.ts +++ b/src/ui/src/lib/types.ts @@ -187,6 +187,35 @@ export interface CalibrationResponse { matrix: CalibrationMatrix; } +export type NodeSettings = { + updating: { + autoUpdate: boolean; + preRelease: boolean; + }; + scanning: { + forgetAfterMs: number | null; + }; + counting: { + idPrefixes: string | null; + startCountingDistance: number | null; + stopCountingDistance: number | null; + includeDevicesAge: number | null; + }; + filtering: { + includeIds: string | null; + excludeIds: string | null; + maxReportDistance: number | null; + earlyReportDistance: number | null; + skipReportAge: number | null; + }; + calibration: { + rssiAt1m: number | null; + rssiAdjustment: number | null; + absorptionFactor: number | null; + iBeaconRssiAt1m: number | null; + }; +}; + export function isNode(d: Device | Node | null): d is Node { return (d as Node)?.telemetry !== undefined; -} +} \ No newline at end of file diff --git a/src/ui/src/routes/+layout.svelte b/src/ui/src/routes/+layout.svelte index 25e81f09..42f41f00 100644 --- a/src/ui/src/routes/+layout.svelte +++ b/src/ui/src/routes/+layout.svelte @@ -26,6 +26,7 @@ { href: '/devices', name: 'devices', icon: devices, alt: 'Devices' }, { href: '/nodes', name: 'nodes', icon: nodes, alt: 'Nodes' }, { href: '/calibration', name: 'calibration', icon: calibration, alt: 'Calibration' }, + { href: '/settings', name: 'settings', icon: settings, alt: 'Settings' } ]; diff --git a/src/ui/src/routes/settings/+page.svelte b/src/ui/src/routes/settings/+page.svelte new file mode 100644 index 00000000..a77dcab0 --- /dev/null +++ b/src/ui/src/routes/settings/+page.svelte @@ -0,0 +1,14 @@ + + + + ESPresense Companion: Settings + + +
+

Settings

+

These settings will be applied to every node, including new nodes.

+ + +
From b67f6f1f468ff9f98f8a14c979c233fe18551e7f Mon Sep 17 00:00:00 2001 From: Darrell Turner Date: Mon, 9 Sep 2024 23:10:04 -0400 Subject: [PATCH 2/3] Fix C# Code --- src/Controllers/DeviceController.cs | 4 +- src/Controllers/NodeController.cs | 10 ++-- src/Controllers/SettingsController.cs | 75 --------------------------- src/Services/NodeSettingsStore.cs | 6 +-- src/ui/src/lib/stores.ts | 4 +- 5 files changed, 12 insertions(+), 87 deletions(-) delete mode 100644 src/Controllers/SettingsController.cs diff --git a/src/Controllers/DeviceController.cs b/src/Controllers/DeviceController.cs index a4673b06..2f922128 100644 --- a/src/Controllers/DeviceController.cs +++ b/src/Controllers/DeviceController.cs @@ -19,7 +19,7 @@ public DeviceController(ILogger logger, DeviceSettingsStore de _state = state; } - [HttpGet("{id}")] + [HttpGet("{id}/settings")] public DeviceSettingsDetails Get(string id) { var deviceSettings = _deviceSettingsStore.Get(id); @@ -29,7 +29,7 @@ public DeviceSettingsDetails Get(string id) return new DeviceSettingsDetails(deviceSettings ?? new DeviceSettings { Id = id, OriginalId = id }, details); } - [HttpPut("{id}")] + [HttpPut("{id}/settings")] public async Task Set(string id, [FromBody] DeviceSettings value) { await _deviceSettingsStore.Set(id, value); diff --git a/src/Controllers/NodeController.cs b/src/Controllers/NodeController.cs index a3b2c2f2..655fb504 100644 --- a/src/Controllers/NodeController.cs +++ b/src/Controllers/NodeController.cs @@ -9,18 +9,18 @@ namespace ESPresense.Controllers; [ApiController] public class NodeController(NodeSettingsStore nodeSettingsStore, State state) : ControllerBase { - [HttpGet("{id}")] + [HttpGet("{id}/settings")] public NodeSettingsDetails Get(string id) { var nodeSettings = nodeSettingsStore.Get(id); var details = new List>(); if (nodeSettings?.Id != null && state.Nodes.TryGetValue(id, out var node)) details.AddRange(node.GetDetails()); - return new NodeSettingsDetails(nodeSettings ?? new NodeSettings(id), details); + return new NodeSettingsDetails(nodeSettings ?? new Models.NodeSettings(id), details); } - [HttpPut("{id}")] - public Task Set(string id, [FromBody] NodeSettings ds) + [HttpPut("{id}/settings")] + public Task Set(string id, [FromBody] Models.NodeSettings ds) { Log.Information("Set {id} {@ds}", id, ds); return nodeSettingsStore.Set(id, ds); @@ -38,5 +38,5 @@ public async Task Restart(string id) await nodeSettingsStore.Restart(id); } - public readonly record struct NodeSettingsDetails(NodeSettings? settings, IList> details); + public readonly record struct NodeSettingsDetails(Models.NodeSettings? settings, IList> details); } \ No newline at end of file diff --git a/src/Controllers/SettingsController.cs b/src/Controllers/SettingsController.cs deleted file mode 100644 index 3a8cc51c..00000000 --- a/src/Controllers/SettingsController.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.IO; -using System.Text.Json; - -[ApiController] -[Route("api/settings")] -public class SettingsController : ControllerBase -{ - private const string SettingsFilePath = "settings.json"; - - [HttpGet] - public ActionResult GetSettings() - { - if (!System.IO.File.Exists(SettingsFilePath)) - { - return new Settings(); - } - - var json = System.IO.File.ReadAllText(SettingsFilePath); - return JsonSerializer.Deserialize(json); - } - - [HttpPost] - public IActionResult SaveSettings([FromBody] Settings settings) - { - var json = JsonSerializer.Serialize(settings); - System.IO.File.WriteAllText(SettingsFilePath, json); - return Ok(); - } -} - -public class Settings -{ - public UpdatingSettings Updating { get; set; } = new UpdatingSettings(); - public ScanningSettings Scanning { get; set; } = new ScanningSettings(); - public CountingSettings Counting { get; set; } = new CountingSettings(); - public FilteringSettings Filtering { get; set; } = new FilteringSettings(); - public CalibrationSettings Calibration { get; set; } = new CalibrationSettings(); -} - -public class UpdatingSettings -{ - public bool? AutoUpdate { get; set; } - public bool? PreRelease { get; set; } -} - -public class ScanningSettings -{ - public int? ForgetAfterMs { get; set; } -} - -public class CountingSettings -{ - public string? IdPrefixes { get; set; } - public double? StartCountingDistance { get; set; } - public double? StopCountingDistance { get; set; } - public int? IncludeDevicesAge { get; set; } -} - -public class FilteringSettings -{ - public string? IncludeIds { get; set; } - public string? ExcludeIds { get; set; } - public double? MaxReportDistance { get; set; } - public double? EarlyReportDistance { get; set; } - public int? SkipReportAge { get; set; } -} - -public class CalibrationSettings -{ - public int? RssiAt1m { get; set; } - public int? RssiAdjustment { get; set; } - public double? AbsorptionFactor { get; set; } - public int? IBeaconRssiAt1m { get; set; } -} \ No newline at end of file diff --git a/src/Services/NodeSettingsStore.cs b/src/Services/NodeSettingsStore.cs index 5712f301..873bc5aa 100644 --- a/src/Services/NodeSettingsStore.cs +++ b/src/Services/NodeSettingsStore.cs @@ -21,12 +21,12 @@ private static bool ParseBool(string value) } private readonly ConcurrentDictionary _storeById = new(); - public NodeSettings Get(string id) + public Models.NodeSettings Get(string id) { - return _storeById.TryGetValue(id, out var ns) ? ns.Clone() : new NodeSettings(id); + return _storeById.TryGetValue(id, out var ns) ? ns.Clone() : new Models.NodeSettings(id); } - public async Task Set(string id, NodeSettings ds) + public async Task Set(string id, Models.NodeSettings ns) { var retain = id == "*"; var old = Get(id); diff --git a/src/ui/src/lib/stores.ts b/src/ui/src/lib/stores.ts index 18cc1f3a..27ace365 100644 --- a/src/ui/src/lib/stores.ts +++ b/src/ui/src/lib/stores.ts @@ -156,13 +156,13 @@ export const settings = (() => { set, update, load: async () => { - const response = await fetch(`${base}/api/settings`); + 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(); set(data); }, save: async (newSettings: Settings) => { - const response = await fetch(`${base}/api/settings`, { + const response = await fetch(`${base}/api/node/*/settings`, { method: 'POST', headers: { 'Content-Type': 'application/json', From b25f70311ca3402c0e7d8b7751885d638ff8d1d6 Mon Sep 17 00:00:00 2001 From: DTTerastar Date: Wed, 11 Sep 2024 17:49:05 -0400 Subject: [PATCH 3/3] WIP --- src/Controllers/DeviceController.cs | 20 +++-- src/ui/src/lib/NodeActions.svelte | 14 ++-- src/ui/src/lib/NodeSettings.svelte | 31 +++----- src/ui/src/lib/device.ts | 14 ++++ src/ui/src/lib/node.ts | 47 ++++++++++++ src/ui/src/lib/state.ts | 26 +++++++ src/ui/src/lib/stores.ts | 31 +++----- src/ui/src/lib/types.ts | 6 +- src/ui/src/routes/devices/[id]/+page.svelte | 17 +++-- src/ui/src/routes/devices/[id]/+page.ts | 27 ++++--- src/ui/src/routes/nodes/[id]/+page.svelte | 83 +++++---------------- src/ui/src/routes/nodes/[id]/+page.ts | 2 +- 12 files changed, 175 insertions(+), 143 deletions(-) create mode 100644 src/ui/src/lib/device.ts create mode 100644 src/ui/src/lib/node.ts create mode 100644 src/ui/src/lib/state.ts 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 27ace365..939c6329 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, CalibrationResponse } from './types'; +import type { Device, Config, Node, NodeSettings, CalibrationResponse } 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 daf92321..e058e52e 100644 --- a/src/ui/src/lib/types.ts +++ b/src/ui/src/lib/types.ts @@ -187,7 +187,9 @@ export interface CalibrationResponse { matrix: CalibrationMatrix; } -export type NodeSettings = { +export type NodeSetting = { + id: string | null; + name: string | null; updating: { autoUpdate: boolean; preRelease: boolean; @@ -211,7 +213,7 @@ export type NodeSettings = { 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 08d8ab78..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();