💅 Refactors system status view

This commit is contained in:
Rune Harlyk
2025-03-08 01:11:23 +01:00
parent 5daa299895
commit 382a58fc53
5 changed files with 244 additions and 300 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import SystemStatus from './SystemStatus.svelte'; import SystemStatus from './SystemStatus.svelte'
</script> </script>
<div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8"> <div class="mx-0 my-1 flex flex-col space-y-4 sm:mx-8 sm:my-8">
+3 -3
View File
@@ -1,5 +1,5 @@
import type { PageLoad } from './$types'; import type { PageLoad } from './$types'
export const load = (async () => { export const load = (async () => {
return { title: 'System Status' }; return { title: 'System Status' }
}) satisfies PageLoad; }) satisfies PageLoad
@@ -0,0 +1,10 @@
<script>
const { icon, type, label, ...rest } = $props()
const Icon = $derived(icon)
</script>
<button class="btn btn-{type} inline-flex items-center" {...rest}>
<Icon class="mr-2 h-5 w-5" />
<span>{label}</span>
</button>
@@ -0,0 +1,15 @@
<script lang="ts">
const { icon, title, description } = $props()
const Icon = $derived(icon)
</script>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none">
<Icon class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">{title}</div>
<div class="text-sm opacity-75">{description}</div>
</div>
</div>
+138 -219
View File
@@ -1,18 +1,18 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte'
import { modals } from 'svelte-modals'; import { modals } from 'svelte-modals'
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'
import SettingsCard from '$lib/components/SettingsCard.svelte'; import SettingsCard from '$lib/components/SettingsCard.svelte'
import Spinner from '$lib/components/Spinner.svelte'; import Spinner from '$lib/components/Spinner.svelte'
import { slide } from 'svelte/transition'; import { slide } from 'svelte/transition'
import { cubicOut } from 'svelte/easing'; import { cubicOut } from 'svelte/easing'
import type { SystemInformation, Analytics } from '$lib/types/models'; import type { SystemInformation, Analytics } from '$lib/types/models'
import { socket } from '$lib/stores/socket'; import { socket } from '$lib/stores/socket'
import { api } from '$lib/api'; import { api } from '$lib/api'
import { convertSeconds } from '$lib/utilities'; import { convertSeconds } from '$lib/utilities'
import { useFeatureFlags } from '$lib/stores/featureFlags'; import { useFeatureFlags } from '$lib/stores/featureFlags'
import { import {
Cancel, Cancel,
Power, Power,
@@ -30,34 +30,36 @@
Folder, Folder,
Temperature, Temperature,
Stopwatch Stopwatch
} from '$lib/components/icons'; } from '$lib/components/icons'
import StatusItem from './StatusItem.svelte'
import ActionButton from './ActionButton.svelte'
const features = useFeatureFlags(); const features = useFeatureFlags()
let systemInformation: SystemInformation = $state(); let systemInformation: SystemInformation = $state()
async function getSystemStatus() { async function getSystemStatus() {
const result = await api.get<SystemInformation>('/api/system/status'); const result = await api.get<SystemInformation>('/api/system/status')
if (result.isErr()) { if (result.isErr()) {
console.error('Error:', result.inner); console.error('Error:', result.inner)
return; return
} }
systemInformation = result.inner; systemInformation = result.inner
return systemInformation; return systemInformation
} }
const postFactoryReset = async () => await api.post('/api/system/reset'); const postFactoryReset = async () => await api.post('/api/system/reset')
const postSleep = async () => await api.post('api/sleep'); const postSleep = async () => await api.post('api/sleep')
onMount(() => socket.on('analytics', handleSystemData)); onMount(() => socket.on('analytics', handleSystemData))
onDestroy(() => socket.off('analytics', handleSystemData)); onDestroy(() => socket.off('analytics', handleSystemData))
const handleSystemData = (data: Analytics) => const handleSystemData = (data: Analytics) =>
(systemInformation = { ...systemInformation, ...data }); (systemInformation = { ...systemInformation, ...data })
const postRestart = async () => await api.post('/api/system/restart'); const postRestart = async () => await api.post('/api/system/restart')
function confirmRestart() { function confirmRestart() {
modals.open(ConfirmDialog, { modals.open(ConfirmDialog, {
@@ -68,10 +70,10 @@
confirm: { label: 'Restart', icon: Power } confirm: { label: 'Restart', icon: Power }
}, },
onConfirm: () => { onConfirm: () => {
modals.close(); modals.close()
postRestart(); postRestart()
} }
}); })
} }
function confirmReset() { function confirmReset() {
@@ -83,10 +85,10 @@
confirm: { label: 'Factory Reset', icon: FactoryReset } confirm: { label: 'Factory Reset', icon: FactoryReset }
}, },
onConfirm: () => { onConfirm: () => {
modals.close(); modals.close()
postFactoryReset(); postFactoryReset()
} }
}); })
} }
function confirmSleep() { function confirmSleep() {
@@ -98,11 +100,39 @@
confirm: { label: 'Sleep', icon: Sleep } confirm: { label: 'Sleep', icon: Sleep }
}, },
onConfirm: () => { onConfirm: () => {
modals.close(); modals.close()
postSleep(); postSleep()
} }
}); })
} }
interface ActionButtonDef {
icon: any
label: string
onClick: () => void
type?: string
condition?: () => boolean
}
const actionButtons: ActionButtonDef[] = [
{
icon: Sleep,
label: 'Sleep',
onClick: confirmSleep,
condition: () => Boolean($features.sleep)
},
{
icon: Power,
label: 'Restart',
onClick: confirmRestart
},
{
icon: FactoryReset,
label: 'Factory Reset',
onClick: confirmReset,
type: 'secondary'
}
]
</script> </script>
<SettingsCard collapsible={false}> <SettingsCard collapsible={false}>
@@ -119,209 +149,98 @@
{:then nothing} {:then nothing}
<div <div
class="flex w-full flex-col space-y-1" class="flex w-full flex-col space-y-1"
transition:slide|local={{ duration: 300, easing: cubicOut }} transition:slide|local={{ duration: 300, easing: cubicOut }}>
> <StatusItem
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> icon={CPU}
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> title="Chip"
<CPU class="text-primary-content h-auto w-full scale-75" /> description={`${systemInformation.cpu_type} Rev ${systemInformation.cpu_rev}`} />
</div>
<div>
<div class="font-bold">Chip</div>
<div class="text-sm opacity-75">
{systemInformation.cpu_type} Rev {systemInformation.cpu_rev}
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={SDK}
<SDK class="text-primary-content h-auto w-full scale-75" /> title="SDK Version"
</div> description={`ESP-IDF ${systemInformation.sdk_version} / Arduino ${systemInformation.arduino_version}`} />
<div>
<div class="font-bold">SDK Version</div>
<div class="text-sm opacity-75">
ESP-IDF {systemInformation.sdk_version} / Arduino {systemInformation.arduino_version}
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={CPP}
<CPP class="text-primary-content h-auto w-full scale-75" /> title="Firmware Version"
</div> description={systemInformation.firmware_version} />
<div>
<div class="font-bold">Firmware Version</div>
<div class="text-sm opacity-75">
{systemInformation.firmware_version}
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Speed}
<Speed class="text-primary-content h-auto w-full scale-75" /> title="CPU Frequency"
</div> description={`${systemInformation.cpu_freq_mhz} MHz ${
<div> systemInformation.cpu_cores == 2 ? 'Dual Core' : 'Single Core'
<div class="font-bold">CPU Frequency</div> }`} />
<div class="text-sm opacity-75">
{systemInformation.cpu_freq_mhz} MHz {systemInformation.cpu_cores == 2 ?
'Dual Core'
: 'Single Core'}
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Heap}
<Heap class="text-primary-content h-auto w-full scale-75" /> title="Heap (Free / Max Alloc)"
</div> description={`${systemInformation.free_heap} / ${systemInformation.max_alloc_heap} bytes`} />
<div>
<div class="font-bold">Heap (Free / Max Alloc)</div>
<div class="text-sm opacity-75">
{systemInformation.free_heap.toLocaleString('en-US')} / {systemInformation.max_alloc_heap.toLocaleString(
'en-US'
)} bytes
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Pyramid}
<Pyramid class="text-primary-content h-auto w-full scale-75" /> title="PSRAM (Size / Free)"
</div> description={`${systemInformation.psram_size} / ${systemInformation.psram_size} bytes`} />
<div>
<div class="font-bold">PSRAM (Size / Free)</div>
<div class="text-sm opacity-75">
{systemInformation.psram_size.toLocaleString('en-US')} / {systemInformation.psram_size.toLocaleString(
'en-US'
)} bytes
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Sketch}
<Sketch class="text-primary-content h-auto w-full scale-75" /> title="Sketch (Used / Free)"
</div> description={`${(
<div> (systemInformation.sketch_size / systemInformation.free_sketch_space) *
<div class="font-bold">Sketch (Used / Free)</div>
<div class="flex flex-wrap justify-start gap-1 text-sm opacity-75">
<span>
{(
(systemInformation.sketch_size /
systemInformation.free_sketch_space) *
100 100
).toFixed(1)} % of ).toFixed(1)} % of
{(systemInformation.free_sketch_space / 1000000).toLocaleString( ${systemInformation.free_sketch_space / 1000000} MB used (${
'en-US' (systemInformation.free_sketch_space - systemInformation.sketch_size) / 1000000
)} MB used } MB free)`} />
</span>
<span> <StatusItem
({( icon={Flash}
(systemInformation.free_sketch_space - title="Flash Chip (Size / Speed)"
systemInformation.sketch_size) / description={`${systemInformation.flash_chip_size / 1000000} MB / ${
1000000 systemInformation.flash_chip_speed / 1000000
).toLocaleString('en-US')} MB free) } MHz`} />
</span>
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Folder}
<Flash class="text-primary-content h-auto w-full scale-75" /> title="File System (Used / Total)"
</div> description={`${((systemInformation.fs_used / systemInformation.fs_total) * 100).toFixed(
<div> 1
<div class="font-bold">Flash Chip (Size / Speed)</div> )} % of ${systemInformation.fs_total / 1000000} MB used (${
<div class="text-sm opacity-75"> (systemInformation.fs_total - systemInformation.fs_used) / 1000000
{(systemInformation.flash_chip_size / 1000000).toLocaleString('en-US')} MB }
/ {(systemInformation.flash_chip_speed / 1000000).toLocaleString( MB free)`} />
'en-US'
)} MHz
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Temperature}
<Folder class="text-primary-content h-auto w-full scale-75" /> title="Core Temperature"
</div> description={`${
<div> systemInformation.core_temp == 53.33 ?
<div class="font-bold">File System (Used / Total)</div>
<div class="flex flex-wrap justify-start gap-1 text-sm opacity-75">
<span
>{(
(systemInformation.fs_used / systemInformation.fs_total) *
100
).toFixed(1)} % of {(
systemInformation.fs_total / 1000000
).toLocaleString('en-US')} MB used</span
>
<span
>({(
(systemInformation.fs_total - systemInformation.fs_used) /
1000000
).toLocaleString('en-US')}
MB free)</span
>
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none">
<Temperature class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Core Temperature</div>
<div class="text-sm opacity-75">
{systemInformation.core_temp == 53.33 ?
'NaN' 'NaN'
: systemInformation.core_temp.toFixed(2) + ' °C'} : systemInformation.core_temp.toFixed(2) + ' °C'
</div> }`} />
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10"> icon={Stopwatch}
<Stopwatch class="text-primary-content h-auto w-full scale-75" /> title="Uptime"
</div> description={convertSeconds(systemInformation.uptime)} />
<div>
<div class="font-bold">Uptime</div>
<div class="text-sm opacity-75">
{convertSeconds(systemInformation.uptime)}
</div>
</div>
</div>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2"> <StatusItem
<div class="mask mask-hexagon bg-primary h-auto w-10 flex-none"> icon={Power}
<Power class="text-primary-content h-auto w-full scale-75" /> title="Reset Reason"
</div> description={systemInformation.cpu_reset_reason} />
<div>
<div class="font-bold">Reset Reason</div>
<div class="text-sm opacity-75">
{systemInformation.cpu_reset_reason}
</div>
</div>
</div>
</div> </div>
{/await} {/await}
</div> </div>
<div class="mt-4 flex flex-wrap justify-end gap-2"> <div class="mt-4 flex flex-wrap justify-end gap-2">
{#if $features.sleep} {#each actionButtons as button}
<button class="btn btn-primary inline-flex items-center" onclick={confirmSleep}> {#if button.condition === undefined || button.condition()}
<Sleep class="mr-2 h-5 w-5" /><span>Sleep</span> <ActionButton
</button> on:click={button.onClick}
icon={button.icon}
label={button.label}
type={button.type || 'primary'} />
{/if} {/if}
<button class="btn btn-primary inline-flex items-center" onclick={confirmRestart}> {/each}
<Power class="mr-2 h-5 w-5" /><span>Restart</span>
</button>
<button class="btn btn-secondary inline-flex items-center" onclick={confirmReset}>
<FactoryReset class="mr-2 h-5 w-5" /><span>Factory Reset</span>
</button>
</div> </div>
</SettingsCard> </SettingsCard>