Skip to content

Commit

Permalink
Settings ui and service
Browse files Browse the repository at this point in the history
  • Loading branch information
DTTerastar committed Sep 9, 2024
1 parent 923ea14 commit 42e3315
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 4 deletions.
75 changes: 75 additions & 0 deletions Controllers/SettingsController.cs
Original file line number Diff line number Diff line change
@@ -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<Settings> GetSettings()
{
if (!System.IO.File.Exists(SettingsFilePath))
{
return new Settings();
}

var json = System.IO.File.ReadAllText(SettingsFilePath);
return JsonSerializer.Deserialize<Settings>(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; }
}
75 changes: 75 additions & 0 deletions src/Controllers/SettingsController.cs
Original file line number Diff line number Diff line change
@@ -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<Settings> GetSettings()
{
if (!System.IO.File.Exists(SettingsFilePath))
{
return new Settings();
}

var json = System.IO.File.ReadAllText(SettingsFilePath);
return JsonSerializer.Deserialize<Settings>(json);

Check warning on line 20 in src/Controllers/SettingsController.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference argument for parameter 'value' in 'ActionResult<Settings>.implicit operator ActionResult<Settings>(Settings value)'.
}

[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; }

Check warning on line 54 in src/Controllers/SettingsController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'IdPrefixes' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public double? StartCountingDistance { get; set; }
public double? StopCountingDistance { get; set; }
public int? IncludeDevicesAge { get; set; }
}

public class FilteringSettings
{
public string IncludeIds { get; set; }

Check warning on line 62 in src/Controllers/SettingsController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'IncludeIds' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string ExcludeIds { get; set; }

Check warning on line 63 in src/Controllers/SettingsController.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'ExcludeIds' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
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; }
}
38 changes: 38 additions & 0 deletions src/ui/src/lib/components/TriStateCheckbox.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
export let checked: boolean | null = false;
export let id: string;
function handleClick(event: Event) {
const cb = event.target as HTMLInputElement;
if (cb.readOnly) {
cb.checked = false;
cb.readOnly = false;
checked = false;
} else if (!cb.checked) {
cb.readOnly = true;
cb.indeterminate = true;
checked = null;
} else {
checked = true;
}
}
$: ariaChecked = checked === null ? 'mixed' : checked;
</script>

<input
type="checkbox"
class="checkbox"
{id}
on:click={handleClick}
checked={checked === true}
indeterminate={checked === null}
readOnly={checked === null}
aria-checked={ariaChecked}
/>

<style>
input[type="checkbox"]:indeterminate {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
}
</style>
1 change: 1 addition & 0 deletions src/ui/src/lib/images/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 30 additions & 3 deletions src/ui/src/lib/stores.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { readable, writable, derived } from 'svelte/store';
import { base } from '$app/paths';
import type { Device, Config, Node } from './types';
import type { Device, Config, Node, Settings } from './types';

export const showAll: SvelteStore<boolean> = writable(false);
export const config = writable<Config>();
Expand Down Expand Up @@ -54,8 +54,8 @@ export const devices = readable<Device[]>([], function start(set) {
function fetchDevices() {
fetch(`${base}/api/state/devices`)
.then((d) => d.json())
.then((r) => {
deviceMap = new Map(r.map((device) => [device.id, device]));
.then((r: Device[]) => {
deviceMap = new Map(r.map((device: Device) => [device.id, device]));
updateDevicesFromMap();
})
.catch((ex) => {
Expand Down Expand Up @@ -133,3 +133,30 @@ export const calibration = readable({}, function start(set) {
clearInterval(interval);
};
});

export const settings = (() => {
const { subscribe, set, update } = writable<Settings | null>(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);
},
};
})();
32 changes: 31 additions & 1 deletion src/ui/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,36 @@ export interface Release {
name: string;
}


export type Settings = {
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;
}
}
6 changes: 6 additions & 0 deletions src/ui/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import nodes from '$lib/images/nodes.svg';
import devices from '$lib/images/devices.svg';
import calibration from '$lib/images/calibration.svg';
import settings from '$lib/images/settings.svg';
initializeStores();
Expand Down Expand Up @@ -62,6 +63,11 @@
<span>Calibration</span>
</AppRailAnchor>

<AppRailAnchor href="{base}/settings" name="settings" selected={current == `${base}/settings`}>
<img src={settings} class="px-4" alt="Settings" />
<span>Settings</span>
</AppRailAnchor>

<svelte:fragment slot="trail">
<AppRailAnchor regionIcon="w-8" href="https://github.com/ESPresense/ESPresense-companion" target="_blank">
<img src={github} class="px-4" alt="GitHub" />
Expand Down
Loading

0 comments on commit 42e3315

Please sign in to comment.