Updates the esp32 template to use eventsocket

This commit is contained in:
Rune Harlyk
2024-04-23 19:35:29 +02:00
committed by Rune Harlyk
parent 7e521235f4
commit 5148891fc4
31 changed files with 366 additions and 1809 deletions
-1
View File
@@ -1,3 +1,2 @@
export { default as fileService } from './file-service';
export { default as socketService } from './socket-service';
export { default as resultService } from './result-service';
-95
View File
@@ -1,95 +0,0 @@
import { isConnected, socketData } from '$lib/stores';
import { Result, Ok } from '$lib/utilities';
import { resultService } from '$lib/services';
import { type WebSocketJsonMsg } from '$lib/models';
import type { Writable } from 'svelte/store';
type WebsocketOutData = string | ArrayBufferLike | Blob | ArrayBufferView;
// TODO
/**
* MOVE THE store to a store.ts file
*
* Make an object on the class that encapsulate all the stores
*
* Make the handle message function look up the type and set the value, to simplify the code
*/
class SocketService {
private socket!: WebSocket;
constructor() {}
public connect(url: string): void {
if (typeof WebSocket === 'undefined') return;
this.socket = new WebSocket(url);
this.socket.binaryType = 'arraybuffer';
this.socket.onopen = () => this.handleConnected();
this.socket.onclose = () => this.handleDisconnected();
this.socket.onmessage = (event: MessageEvent) =>
resultService.handleResult(this.handleMessage(event), 'SocketService');
this.socket.onerror = (error: Event) => console.log(error);
}
public send(data: WebsocketOutData): Result<void, string> {
if (typeof WebSocket === 'undefined') return Result.err('The connection is not open');
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(data);
return Ok.void();
}
return Result.err('The connection is not open');
}
public addPublisher(store: Writable<WebsocketOutData>, type?: string) {
const publish = (data: WebsocketOutData) =>
this.send(type ? JSON.stringify({ type, data }) : data);
store.subscribe(publish);
}
private handleConnected(): void {
isConnected.set(true);
}
private handleDisconnected(): void {
isConnected.set(false);
}
private getJsonFromMessage(msg: string): Result<WebSocketJsonMsg, string> {
try {
return Result.ok(JSON.parse(msg) as WebSocketJsonMsg);
} catch (error) {
return Result.err('Failed to parse socket message', error);
}
}
private handleBufferMessage(buffer: ArrayBuffer): Result<void, string> {
console.log(buffer);
return Ok.void();
}
private handleMessage(event: MessageEvent): Result<void, string> {
if (event.data instanceof ArrayBuffer) {
return this.handleBufferMessage(event.data);
}
let msgRes = this.getJsonFromMessage(event.data);
if (msgRes.isErr()) {
return msgRes;
}
const msg = msgRes.inner;
if (msg.type === 'log') {
socketData.logs.update((entries) => {
entries.push(msg.data);
return entries;
});
return Ok.void();
} else if (msg.data && msg.type in socketData) {
socketData[msg.type].set(msg.data);
return Ok.void();
}
return Result.err(`Got invalid msg: ${JSON.stringify(msg)}`);
}
}
export default new SocketService();
+19 -11
View File
@@ -1,3 +1,4 @@
import { type Analytics } from '$lib/types/models';
import { writable } from 'svelte/store';
let analytics_data = {
@@ -11,23 +12,30 @@ let analytics_data = {
core_temp: <number[]>[]
};
const maxAnalyticsData = 1000; // roughly 33 Minutes of data at 1 update per 2 seconds
function createAnalytics() {
const { subscribe, set, update } = writable(analytics_data);
const { subscribe, update } = writable(analytics_data);
return {
subscribe,
addData: (data: string) => {
const content = JSON.parse(data);
addData: (content: Analytics) => {
update((analytics_data) => ({
...analytics_data,
uptime: [...analytics_data.uptime, content.uptime],
free_heap: [...analytics_data.free_heap, content.free_heap / 1000],
total_heap: [...analytics_data.total_heap, content.total_heap / 1000],
min_free_heap: [...analytics_data.min_free_heap, content.min_free_heap / 1000],
max_alloc_heap: [...analytics_data.max_alloc_heap, content.max_alloc_heap / 1000],
fs_used: [...analytics_data.fs_used, content.fs_used / 1000],
fs_total: [...analytics_data.fs_total, content.fs_total / 1000],
core_temp: [...analytics_data.core_temp, content.core_temp]
uptime: [...analytics_data.uptime, content.uptime].slice(-maxAnalyticsData),
free_heap: [...analytics_data.free_heap, content.free_heap / 1000].slice(-maxAnalyticsData),
total_heap: [...analytics_data.total_heap, content.total_heap / 1000].slice(
-maxAnalyticsData
),
min_free_heap: [...analytics_data.min_free_heap, content.min_free_heap / 1000].slice(
-maxAnalyticsData
),
max_alloc_heap: [...analytics_data.max_alloc_heap, content.max_alloc_heap / 1000].slice(
-maxAnalyticsData
),
fs_used: [...analytics_data.fs_used, content.fs_used / 1000].slice(-maxAnalyticsData),
fs_total: [...analytics_data.fs_total, content.fs_total / 1000].slice(-maxAnalyticsData),
core_temp: [...analytics_data.core_temp, content.core_temp].slice(-maxAnalyticsData)
}));
}
};
+1
View File
@@ -1,3 +1,4 @@
export * from './socket-store';
export * from './logging-store';
export * from './model-store';
export * from './socket';
-1
View File
@@ -1,7 +1,6 @@
import { writable, type Writable } from 'svelte/store';
import { type angles } from '$lib/models';
export const isConnected = writable(false);
export const servoAnglesOut: Writable<number[]> = writable([
0, 45, -90, 0, 45, -90, 0, 45, -90, 0, 45, -90
]);
+115
View File
@@ -0,0 +1,115 @@
import { writable } from 'svelte/store';
function createWebSocket() {
let listeners = new Map<string, Set<(data?: unknown) => void>>();
const { subscribe, set } = writable(false);
const socketEvents = ['open', 'close', 'error', 'message', 'unresponsive'] as const;
type SocketEvent = (typeof socketEvents)[number];
let unresponsiveTimeoutId: number;
let reconnectTimeoutId: number;
let ws: WebSocket;
let socketUrl: string | URL;
function init(url: string | URL) {
socketUrl = url;
connect();
}
function disconnect(reason: SocketEvent, event?: Event) {
ws.close();
set(false);
clearTimeout(unresponsiveTimeoutId);
clearTimeout(reconnectTimeoutId);
listeners.get(reason)?.forEach((listener) => listener(event));
reconnectTimeoutId = setTimeout(connect, 1000);
}
function connect() {
ws = new WebSocket(socketUrl);
ws.onopen = (ev) => {
set(true);
clearTimeout(reconnectTimeoutId);
listeners.get('open')?.forEach((listener) => listener(ev));
for (const event of listeners.keys()) {
if (socketEvents.includes(event as SocketEvent)) continue;
sendEvent('subscribe', event);
}
};
ws.onmessage = (message) => {
resetUnresponsiveCheck();
let data = message.data;
if (data instanceof ArrayBuffer) {
listeners.get('binary')?.forEach((listener) => listener(data));
return;
}
listeners.get('message')?.forEach((listener) => listener(data));
try {
data = JSON.parse(message.data);
} catch (error) {
listeners.get('error')?.forEach((listener) => listener(error));
return;
}
listeners.get('json')?.forEach((listener) => listener(data));
const [event, payload] = data;
if (event) listeners.get(event)?.forEach((listener) => listener(payload));
};
ws.onerror = (ev) => disconnect('error', ev);
ws.onclose = (ev) => disconnect('close', ev);
}
function unsubscribe(event: string, listener?: (data: any) => void) {
let eventListeners = listeners.get(event);
if (!eventListeners) return;
if (!eventListeners.size) {
sendEvent('unsubscribe', event);
}
if (listener) {
eventListeners?.delete(listener);
} else {
listeners.delete(event);
}
}
function resetUnresponsiveCheck() {
clearTimeout(unresponsiveTimeoutId);
unresponsiveTimeoutId = setTimeout(() => disconnect('unresponsive'), 2000);
}
function send(msg: unknown) {
if (!ws || ws.readyState !== WebSocket.OPEN) return;
ws.send(JSON.stringify(msg));
}
function sendEvent(event: string, data: unknown) {
send({ event, data });
}
return {
subscribe,
send,
sendEvent,
init,
on: <T>(event: string, listener: (data: T) => void): (() => void) => {
let eventListeners = listeners.get(event);
if (!eventListeners) {
if (!socketEvents.includes(event as SocketEvent)) {
sendEvent('subscribe', event);
}
eventListeners = new Set();
listeners.set(event, eventListeners);
}
eventListeners.add(listener as (data: any) => void);
return () => {
unsubscribe(event, listener);
};
},
off: (event: string, listener?: (data: any) => void) => {
unsubscribe(event, listener);
}
};
}
export const socket = createWebSocket();
+7 -8
View File
@@ -1,7 +1,6 @@
import { writable } from 'svelte/store';
let telemetry_data = {
serverAvailable: true,
rssi: {
rssi: 0,
disconnected: true
@@ -24,25 +23,25 @@ function createTelemetry() {
subscribe,
setRSSI: (data: string) => {
if (!isNaN(Number(data))) {
update((telemerty_data) => ({
...telemerty_data,
update((telemetry_data) => ({
...telemetry_data,
rssi: { rssi: Number(data), disconnected: false }
}));
} else {
update((telemerty_data) => ({ ...telemerty_data, rssi: { rssi: 0, disconnected: true } }));
update((telemetry_data) => ({ ...telemetry_data, rssi: { rssi: 0, disconnected: true } }));
}
},
setBattery: (data: string) => {
const content = JSON.parse(data);
update((telemerty_data) => ({
...telemerty_data,
update((telemetry_data) => ({
...telemetry_data,
battery: { soc: content.soc, charging: content.charging }
}));
},
setDownloadOTA: (data: string) => {
const content = JSON.parse(data);
update((telemerty_data) => ({
...telemerty_data,
update((telemetry_data) => ({
...telemetry_data,
download_ota: { status: content.status, progress: content.progress, error: content.error }
}));
}
+113
View File
@@ -0,0 +1,113 @@
export type WifiStatus = {
status: number;
local_ip: string;
mac_address: string;
rssi: number;
ssid: string;
bssid: string;
channel: number;
subnet_mask: string;
gateway_ip: string;
dns_ip_1: string;
dns_ip_2?: string;
};
export type WifiSettings = {
hostname: string;
priority_RSSI: boolean;
wifi_networks: networkItem[];
};
export type KnownNetworkItem = {
ssid: string;
password: string;
static_ip_config: boolean;
local_ip?: string;
subnet_mask?: string;
gateway_ip?: string;
dns_ip_1?: string;
dns_ip_2?: string;
};
export type NetworkItem = {
rssi: number;
ssid: string;
bssid: string;
channel: number;
encryption_type: number;
};
export type ApStatus = {
status: number;
ip_address: string;
mac_address: string;
station_num: number;
};
export type ApSettings = {
provision_mode: number;
ssid: string;
password: string;
channel: number;
ssid_hidden: boolean;
max_clients: number;
local_ip: string;
gateway_ip: string;
subnet_mask: string;
};
export type LightState = {
led_on: boolean;
};
export type BrokerSettings = {
mqtt_path: string;
name: string;
unique_id: string;
};
export type NTPStatus = {
status: number;
utc_time: string;
local_time: string;
server: string;
uptime: number;
};
export type NTPSettings = {
enabled: boolean;
server: string;
tz_label: string;
tz_format: string;
};
export type Analytics = {
max_alloc_heap: number;
psram_size: number;
free_psram: number;
free_heap: number;
total_heap: number;
min_free_heap: number;
core_temp: number;
fs_total: number;
fs_used: number;
uptime: number;
};
export type StaticSystemInformation = {
esp_platform: string;
firmware_version: string;
cpu_freq_mhz: number;
cpu_type: string;
cpu_rev: number;
cpu_cores: number;
sketch_size: number;
free_sketch_space: number;
sdk_version: string;
arduino_version: string;
flash_chip_size: number;
flash_chip_speed: number;
cpu_reset_reason: string;
};
export type SystemInformation = Analytics & StaticSystemInformation;
+61 -112
View File
@@ -14,65 +14,57 @@
import Menu from './menu.svelte';
import Statusbar from './statusbar.svelte';
import Login from './login.svelte';
import { get, type Writable } from 'svelte/store';
import { isConnected, mode, outControllerData, servoAngles, servoAnglesOut, socketData } from '$lib/stores';
import { throttler } from '$lib/utilities';
import { mode, outControllerData, servoAnglesOut, socket } from '$lib/stores';
import type { Analytics } from '$lib/types/models';
export let data: LayoutData;
type WebsocketOutData = string | ArrayBufferLike | Blob | ArrayBufferView;
onMount(async () => {
if ($user.bearer_token !== '') {
await validateUser($user);
}
connectToEventSource();
connectToSocket()
addPublisher(outControllerData, "controller")
addPublisher(mode as unknown as Writable<WebsocketOutData>, "mode")
addPublisher(servoAnglesOut as unknown as Writable<WebsocketOutData>, "angles")
});
const connectToSocket = () => {
const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : '';
socket.init(`ws://${window.location.host}/ws/events${ws_token}`);
socket = new WebSocket('ws://' + $page.url.host + '/ws' + ws_token);
socket.onopen = (event) => isConnected.set(true);
socket.onclose = (event) => {
isConnected.set(false)
notifications.error('Websocket disconnected', 5000);
};
socket.onmessage = ((event: MessageEvent) => {
const message = JSON.parse(event.data);
if (message.type === 'log') {
socketData.logs.update((entries) => {
entries.push(message.data);
return entries;
});
} else if (message.data && message.type in socketData) {
const store = socketData[message.type as keyof typeof socketData];
if (JSON.stringify(get(store)) !== JSON.stringify(message.data))
store.set(message.data);
}
});
}
addEventListeners();
const addPublisher = (store: Writable<WebsocketOutData>, type?: string) => {
const publish = (data: WebsocketOutData) => {
if (socket.readyState === WebSocket.OPEN)
throttle.throttle(
() => socket.send(type ? JSON.stringify({ type, data }) : data),
100);
}
store.subscribe(publish);
}
onDestroy(() => {
disconnectEventSource();
socket?.close();
outControllerData.subscribe((data) => socket.sendEvent("input", data));
mode.subscribe((data) => socket.sendEvent("mode", data));
servoAnglesOut.subscribe((data) => socket.sendEvent("angles", data));
});
async function validateUser(userdata: userProfile) {
onDestroy(() => {
removeEventListeners();
});
const addEventListeners = () => {
socket.on('open', handleOpen);
socket.on('close', handleClose);
socket.on('error', handleError);
socket.on('rssi', handleNetworkStatus);
socket.on('infoToast', handleInfoToast);
socket.on('successToast', handleSuccessToast);
socket.on('warningToast', handleWarningToast);
socket.on('errorToast', handleErrorToast);
if ($page.data.features.analytics) socket.on('analytics', handleAnalytics);
if ($page.data.features.battery) socket.on('battery', handleBattery);
if ($page.data.features.download_firmware) socket.on('otastatus', handleOAT);
};
const removeEventListeners = () => {
socket.off('analytics', handleAnalytics);
socket.off('open', handleOpen);
socket.off('close', handleClose);
socket.off('rssi', handleNetworkStatus);
socket.off('infoToast', handleInfoToast);
socket.off('successToast', handleSuccessToast);
socket.off('warningToast', handleWarningToast);
socket.off('errorToast', handleErrorToast);
socket.off('battery', handleBattery);
socket.off('otastatus', handleOAT);
};
async function validateUser(userdata: userProfile) {
try {
const response = await fetch('/rest/verifyAuthorization', {
method: 'GET',
@@ -89,74 +81,31 @@
}
}
let menuOpen = false;
let throttle = new throttler();
const handleOpen = () => {
notifications.success('Connection to device established', 5000);
};
let socket: WebSocket
let eventSourceUrl = '/events';
let eventSource: EventSource;
let unresponsiveTimeoutId: number;
function connectToEventSource() {
eventSource = new EventSource(eventSourceUrl);
eventSource.addEventListener('open', () => {
notifications.success('Connection to device established', 5000);
telemetry.setRSSI('found'); // Update store and flag as server being available again
});
eventSource.addEventListener('rssi', (event) => {
telemetry.setRSSI(event.data);
resetUnresponsiveCheck();
});
eventSource.addEventListener('error', (event) => {
reconnectEventSource();
});
eventSource.addEventListener('infoToast', (event) => {
notifications.info(event.data, 5000);
});
eventSource.addEventListener('successToast', (event) => {
notifications.success(event.data, 5000);
});
eventSource.addEventListener('warningToast', (event) => {
notifications.warning(event.data, 5000);
});
eventSource.addEventListener('errorToast', (event) => {
notifications.error(event.data, 5000);
});
eventSource.addEventListener('battery', (event) => {
telemetry.setBattery(event.data);
});
eventSource.addEventListener('download_ota', (event) => {
telemetry.setDownloadOTA(event.data);
});
eventSource.addEventListener('analytics', (event) => {
analytics.addData(event.data);
});
}
function disconnectEventSource() {
clearTimeout(unresponsiveTimeoutId);
eventSource?.close();
}
function reconnectEventSource() {
const handleClose = () => {
notifications.error('Connection to device lost', 5000);
disconnectEventSource();
connectToEventSource();
}
telemetry.setRSSI('lost');
};
function resetUnresponsiveCheck() {
clearTimeout(unresponsiveTimeoutId);
unresponsiveTimeoutId = setTimeout(() => reconnectEventSource(), 2000);
}
const handleError = (data: any) => console.error(data);
const handleInfoToast = (data: string) => notifications.info(data, 5000);
const handleWarningToast = (data: string) => notifications.warning(data, 5000);
const handleErrorToast = (data: string) => notifications.error(data, 5000);
const handleSuccessToast = (data: string) => notifications.success(data, 5000);
const handleAnalytics = (data: Analytics) => analytics.addData(data);
const handleNetworkStatus = (data: string) => telemetry.setRSSI(data);
const handleBattery = (data: string) => telemetry.setBattery(data);
const handleOAT = (data: string) => telemetry.setDownloadOTA(data);
let menuOpen = false;
</script>
+1 -1
View File
@@ -96,7 +96,7 @@
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('Security settings updated.', 3000);
notifications.success('MQTT settings updated.', 3000);
mqttSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
@@ -8,12 +8,7 @@
import Spinner from '$lib/components/Spinner.svelte';
import MQTT from '~icons/tabler/topology-star-3';
import Info from '~icons/tabler/info-circle';
type BrokerSettings = {
mqtt_path: string;
name: string;
unique_id: string;
};
import type { BrokerSettings } from '$lib/types/models';
let brokerSettings: BrokerSettings;
+1 -16
View File
@@ -8,28 +8,13 @@
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications';
import type { TimeZones } from './timezones';
import { TIME_ZONES } from './timezones';
import NTP from '~icons/tabler/clock-check';
import Server from '~icons/tabler/server';
import Clock from '~icons/tabler/clock';
import UTC from '~icons/tabler/clock-pin';
import Stopwatch from '~icons/tabler/24-hours';
type NTPStatus = {
status: number;
utc_time: string;
local_time: string;
server: string;
uptime: number;
};
type NTPSettings = {
enabled: boolean;
server: string;
tz_label: string;
tz_format: string;
};
import type { NTPSettings, NTPStatus } from '$lib/types/models';
let ntpSettings: NTPSettings;
let ntpStatus: NTPStatus;
+2 -2
View File
@@ -1,9 +1,9 @@
<script lang="ts">
import Controls from './Controls.svelte';
import { isConnected } from '$lib/stores';
import { socket } from '$lib/stores';
</script>
<div>
{#if $isConnected}
{#if $socket}
<Controls />
<slot/>
{:else}
+1 -1
View File
@@ -2,7 +2,7 @@
import nipplejs from 'nipplejs';
import { onMount } from 'svelte';
import { capitalize, throttler, toInt8 } from '$lib/utilities';
import { input, outControllerData, mode, modes, type Modes, ModesEnum } from '$lib/stores';
import { input, outControllerData, mode, modes, type Modes, ModesEnum, socket } from '$lib/stores';
import type { vector } from '$lib/models';
let throttle = new throttler();
-13
View File
@@ -1,13 +0,0 @@
<script lang="ts">
import type { PageData } from '../$types';
import Demo from './Demo.svelte';
export let data: PageData;
</script>
<div
class="mx-0 my-1 flex flex-col space-y-4
sm:mx-8 sm:my-8"
>
<Demo />
</div>
-7
View File
@@ -1,7 +0,0 @@
import type { PageLoad } from './$types';
export const load = (async ({ fetch }) => {
return {
title: 'Demo App'
};
}) satisfies PageLoad;
-83
View File
@@ -1,83 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { user } from '$lib/stores/user';
import { servoAngles } from '$lib/stores';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import Light from '~icons/tabler/bulb';
async function getActuatorState() {
try {
const response = await fetch('/rest/actuators', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
const actuators = await response.json();
servoAngles.set(actuators.state);
} catch (error) {
console.error('Error:', error);
}
return;
}
const ws_token = $page.data.features.security ? '?access_token=' + $user.bearer_token : '';
const socket = new WebSocket('ws://' + $page.url.host + '/ws' + ws_token);
socket.onopen = (event) => {
// socket.send('Hello');
};
socket.addEventListener('close', (event) => {
const closeCode = event.code;
const closeReason = event.reason;
console.log('WebSocket closed with code:', closeCode);
console.log('Close reason:', closeReason);
notifications.error('Websocket disconnected', 5000);
});
socket.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type != 'id') {
servoAngles.set(message.state);
}
};
onDestroy(() => socket.close());
onMount(() => getActuatorState());
function updateAngle(index: number, value: number) {
servoAngles.update(($servoAngles) => {
$servoAngles[index] = value;
socket.send(JSON.stringify({ state: $servoAngles }));
return $servoAngles;
});
}
</script>
<SettingsCard collapsible={false}>
<Light slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
<span slot="title">Light State</span>
<div class="w-full">
<h1 class="text-xl font-semibold">Websocket Example</h1>
<div class="form-control">
<div class="w-full flex justify-between">
{#each $servoAngles as angle, index}
<input
type="number"
class="input w-12"
id={`angle-${index}`}
value={angle}
on:input={(event) => updateAngle(index, parseFloat(event.target?.value))}
/>
{/each}
</div>
</div>
</div>
</SettingsCard>
-15
View File
@@ -1,15 +0,0 @@
<script lang="ts">
import type { PageData } from '../$types';
import MQTT from './MQTT.svelte';
import MqttConfig from './MQTTConfig.svelte';
export let data: PageData;
</script>
<div
class="mx-0 my-1 flex flex-col space-y-4
sm:mx-8 sm:my-8"
>
<MQTT />
<MqttConfig />
</div>
-7
View File
@@ -1,7 +0,0 @@
import type { PageLoad } from './$types';
export const load = (async () => {
return {
title: "MQTT"
};
}) satisfies PageLoad;
-304
View File
@@ -1,304 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import InputPassword from '$lib/components/InputPassword.svelte';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications';
import Spinner from '$lib/components/Spinner.svelte';
import Collapsible from '$lib/components/Collapsible.svelte';
import MQTT from '~icons/tabler/topology-star-3';
import Client from '~icons/tabler/robot';
type MQTTStatus = {
enabled: boolean;
connected: boolean;
client_id: string;
last_error: string;
};
type MQTTSettings = {
enabled: boolean;
uri: string;
username: string;
password: string;
client_id: string;
keep_alive: number;
clean_session: boolean;
};
let mqttSettings: MQTTSettings;
let mqttStatus: MQTTStatus;
let formField: any;
async function getMQTTStatus() {
try {
const response = await fetch('/rest/mqttStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
mqttStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
return mqttStatus;
}
async function getMQTTSettings() {
try {
const response = await fetch('/rest/mqttSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
mqttSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return mqttSettings;
}
const interval = setInterval(async () => {
getMQTTStatus();
}, 5000);
onDestroy(() => clearInterval(interval));
onMount(() => {
if (!$page.data.features.security || $user.admin) {
getMQTTSettings();
}
});
let formErrors = {
host: false,
port: false,
keep_alive: false,
topic_length: false
};
async function postMQTTSettings(data: MQTTSettings) {
try {
const response = await fetch('/rest/mqttSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('Security settings updated.', 3000);
mqttSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
return;
}
function handleSubmitMQTT() {
let valid = true;
// Validate Server URI
const regexExpURL =
/(([-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4})|(\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b))(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
if (!regexExpURL.test(mqttSettings.uri)) {
valid = false;
formErrors.host = true;
} else {
formErrors.host = false;
}
// Validate if port is a number and within the right range
let keepalive = Number(mqttSettings.keep_alive);
if (1 <= keepalive && keepalive <= 600) {
formErrors.keep_alive = false;
} else {
formErrors.keep_alive = true;
valid = false;
}
// Submit JSON to REST API
if (valid) {
postMQTTSettings(mqttSettings);
//alert('Form Valid');
}
}
</script>
<SettingsCard collapsible={false}>
<MQTT slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
<span slot="title">MQTT</span>
<div class="w-full overflow-x-auto">
{#await getMQTTStatus()}
<Spinner />
{:then nothing}
<div
class="flex w-full flex-col space-y-1"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div
class="mask mask-hexagon h-auto w-10 {mqttStatus.connected === true
? 'bg-success'
: 'bg-error'}"
>
<MQTT
class="h-auto w-full scale-75 {mqttStatus.connected === true
? 'text-success-content'
: 'text-error-content'}"
/>
</div>
<div>
<div class="font-bold">Status</div>
<div class="text-sm opacity-75">
{#if mqttStatus.connected}
Connected
{:else if !mqttStatus.enabled}
MQTT Disabled
{:else}
{mqttStatus.last_error}
{/if}
</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">
<Client class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Client ID</div>
<div class="text-sm opacity-75">
{mqttStatus.client_id}
</div>
</div>
</div>
</div>
{/await}
</div>
{#if !$page.data.features.security || $user.admin}
<Collapsible open={false} class="shadow-lg" on:closed={getMQTTSettings}>
<span slot="title">Change MQTT Settings</span>
<form on:submit|preventDefault={handleSubmitMQTT} novalidate bind:this={formField}>
<div class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2">
<!-- Enable -->
<label class="label inline-flex cursor-pointer content-end justify-start gap-4">
<input
type="checkbox"
bind:checked={mqttSettings.enabled}
class="checkbox checkbox-primary"
/>
<span>Enable MQTT</span>
</label>
<div class="hidden sm:block" />
<!-- URI -->
<div class="sm:col-span-2">
<label class="label" for="host">
<span class="label-text text-md">URI</span>
</label>
<input
type="text"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.host
? 'border-error border-2'
: ''}"
bind:value={mqttSettings.uri}
id="host"
min="3"
max="64"
required
/>
<label class="label" for="host">
<span class="label-text-alt text-error {formErrors.host ? '' : 'hidden'}"
>Must be a valid URI</span
>
</label>
</div>
<!-- Username -->
<div>
<label class="label" for="user">
<span class="label-text text-md">Username</span>
</label>
<input
type="text"
class="input input-bordered w-full"
bind:value={mqttSettings.username}
id="user"
/>
</div>
<!-- Password -->
<div>
<label class="label" for="pwd">
<span class="label-text text-md">Password</span>
</label>
<InputPassword bind:value={mqttSettings.password} id="pwd" />
</div>
<!-- Client ID -->
<div>
<label class="label" for="clientid">
<span class="label-text text-md">Client ID</span>
</label>
<input
type="text"
class="input input-bordered w-full"
bind:value={mqttSettings.client_id}
id="clientid"
/>
</div>
<!-- Keep Alive -->
<div>
<label class="label" for="keepalive">
<span class="label-text text-md">Keep Alive</span>
</label>
<label for="keepalive" class="input-group">
<input
type="number"
min="1"
max="600"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.keep_alive
? 'border-error border-2'
: ''}"
bind:value={mqttSettings.keep_alive}
id="keepalive"
required
/>
<span>Seconds</span>
</label>
<label for="keepalive" class="label"
><span class="label-text-alt text-error {formErrors.keep_alive ? '' : 'hidden'}"
>Must be between 1 and 600 seconds</span
></label
>
</div>
<!-- Clean Session -->
<label class="label inline-flex cursor-pointer content-end justify-start gap-4">
<input
type="checkbox"
bind:checked={mqttSettings.clean_session}
class="checkbox checkbox-primary"
/>
<span class="">Clean Session?</span>
</label>
</div>
<div class="divider mb-2 mt-0" />
<div class="mx-4 flex flex-wrap justify-end gap-2">
<button class="btn btn-primary" type="submit">Apply Settings</button>
</div>
</form>
</Collapsible>
{/if}
</SettingsCard>
-192
View File
@@ -1,192 +0,0 @@
<script lang="ts">
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications';
import Spinner from '$lib/components/Spinner.svelte';
import MQTT from '~icons/tabler/topology-star-3';
import Info from '~icons/tabler/info-circle';
type BrokerSettings = {
mqtt_path: string;
name: string;
unique_id: string;
};
let brokerSettings: BrokerSettings;
let formField: any;
async function getBrokerSettings() {
try {
const response = await fetch('/rest/brokerSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
brokerSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
}
let formErrors = {
uid: false,
path: false,
name: false
};
async function postBrokerSettings() {
try {
const response = await fetch('/rest/brokerSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(brokerSettings)
});
if (response.status == 200) {
notifications.success('Broker settings updated.', 3000);
brokerSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
return;
}
function handleSubmitBroker() {
let valid = true;
// Validate unique ID
if (brokerSettings.unique_id.length < 3 || brokerSettings.unique_id.length > 32) {
valid = false;
formErrors.uid = true;
} else {
formErrors.uid = false;
}
// Validate name
if (brokerSettings.name.length < 3 || brokerSettings.name.length > 32) {
valid = false;
formErrors.name = true;
} else {
formErrors.name = false;
}
// Validate MQTT Path
if (brokerSettings.mqtt_path.length > 64) {
valid = false;
formErrors.path = true;
} else {
formErrors.path = false;
}
// Submit JSON to REST API
if (valid) {
postBrokerSettings();
//alert('Form Valid');
}
}
</script>
<SettingsCard collapsible={true} open={false}>
<MQTT slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
<span slot="title">MQTT Broker Settings</span>
<div class="w-full overflow-x-auto">
{#await getBrokerSettings()}
<Spinner />
{:then nothing}
<form
on:submit|preventDefault={handleSubmitBroker}
novalidate
bind:this={formField}
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
<div class="alert alert-info my-2 shadow-lg">
<Info class="h-6 w-6 flex-shrink-0 stroke-current" />
<span
>The LED is controllable via MQTT with the demo project designed to work with Home
Assistant's auto discovery feature.</span
>
</div>
<div class="grid w-full grid-cols-1 content-center gap-x-4 px-4">
<div>
<label class="label" for="uid">
<span class="label-text text-md">Unique ID</span>
</label>
<input
type="text"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.uid
? 'border-error border-2'
: ''}"
bind:value={brokerSettings.unique_id}
id="uid"
min="3"
max="32"
required
/>
<label class="label" for="uid">
<span class="label-text-alt text-error {formErrors.uid ? '' : 'hidden'}"
>Unique ID must be between 3 and 32 characters long</span
>
</label>
</div>
<div>
<label class="label" for="name">
<span class="label-text text-md">Name</span>
</label>
<input
type="text"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.name
? 'border-error border-2'
: ''}"
bind:value={brokerSettings.name}
id="name"
min="3"
max="32"
required
/>
<label class="label" for="name">
<span class="label-text-alt text-error {formErrors.name ? '' : 'hidden'}"
>Name must be between 3 and 32 characters long</span
>
</label>
</div>
<div>
<label class="label" for="path">
<span class="label-text text-md">MQTT Path</span>
</label>
<input
type="text"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.path
? 'border-error border-2'
: ''}"
bind:value={brokerSettings.mqtt_path}
id="path"
min="0"
max="64"
required
/>
<label class="label" for="path">
<span class="label-text-alt text-error {formErrors.path ? '' : 'hidden'}"
>MQTT path is limited to 64 characters</span
>
</label>
</div>
</div>
<div class="divider mb-2 mt-0" />
<div class="mx-4 flex flex-wrap justify-end gap-2">
<button class="btn btn-primary" type="submit">Apply Settings</button>
</div>
</form>
{/await}
</div>
</SettingsCard>
-13
View File
@@ -1,13 +0,0 @@
<script lang="ts">
import type { PageData } from '../$types';
import NTP from './NTP.svelte';
export let data: PageData;
</script>
<div
class="mx-0 my-1 flex flex-col space-y-4
sm:mx-8 sm:my-8"
>
<NTP />
</div>
-7
View File
@@ -1,7 +0,0 @@
import type { PageLoad } from './$types';
export const load = (async () => {
return {
title: 'NTP'
};
}) satisfies PageLoad;
-303
View File
@@ -1,303 +0,0 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import Collapsible from '$lib/components/Collapsible.svelte';
import Spinner from '$lib/components/Spinner.svelte';
import { user } from '$lib/stores/user';
import { page } from '$app/stores';
import { notifications } from '$lib/components/toasts/notifications';
import type { TimeZones } from './timezones';
import { TIME_ZONES } from './timezones';
import NTP from '~icons/tabler/clock-check';
import Server from '~icons/tabler/server';
import Clock from '~icons/tabler/clock';
import UTC from '~icons/tabler/clock-pin';
import Stopwatch from '~icons/tabler/24-hours';
type NTPStatus = {
status: number;
utc_time: string;
local_time: string;
server: string;
uptime: number;
};
type NTPSettings = {
enabled: boolean;
server: string;
tz_label: string;
tz_format: string;
};
let ntpSettings: NTPSettings;
let ntpStatus: NTPStatus;
async function getNTPStatus() {
try {
const response = await fetch('/rest/ntpStatus', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
ntpStatus = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
}
async function getNTPSettings() {
try {
const response = await fetch('/rest/ntpSettings', {
method: 'GET',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
}
});
ntpSettings = await response.json();
} catch (error) {
console.error('Error:', error);
}
return;
}
const interval = setInterval(async () => {
getNTPStatus();
}, 5000);
onDestroy(() => clearInterval(interval));
onMount(() => {
if (!$page.data.features.security || $user.admin) {
getNTPSettings();
}
});
let formField: any;
let formErrors = {
server: false
};
async function postNTPSettings(data: NTPSettings) {
try {
const response = await fetch('/rest/ntpSettings', {
method: 'POST',
headers: {
Authorization: $page.data.features.security ? 'Bearer ' + $user.bearer_token : 'Basic',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
if (response.status == 200) {
notifications.success('Security settings updated.', 3000);
ntpSettings = await response.json();
} else {
notifications.error('User not authorized.', 3000);
}
} catch (error) {
console.error('Error:', error);
}
}
function handleSubmitNTP() {
let valid = true;
// Validate Server
// RegEx for IPv4
const regexExpIPv4 =
/\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b/;
const regexExpURL =
/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/i;
if (!regexExpURL.test(ntpSettings.server) && !regexExpIPv4.test(ntpSettings.server)) {
valid = false;
formErrors.server = true;
} else {
formErrors.server = false;
}
ntpSettings.tz_format = TIME_ZONES[ntpSettings.tz_label];
// Submit JSON to REST API
if (valid) {
postNTPSettings(ntpSettings);
//alert('Form Valid');
}
}
function convertSeconds(seconds: number) {
// Calculate the number of seconds, minutes, hours, and days
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
let days = Math.floor(hours / 24);
// Calculate the remaining hours, minutes, and seconds
hours = hours % 24;
minutes = minutes % 60;
seconds = seconds % 60;
// Create the formatted string
let result = '';
if (days > 0) {
result += days + ' day' + (days > 1 ? 's' : '') + ' ';
}
if (hours > 0) {
result += hours + ' hour' + (hours > 1 ? 's' : '') + ' ';
}
if (minutes > 0) {
result += minutes + ' minute' + (minutes > 1 ? 's' : '') + ' ';
}
result += seconds + ' second' + (seconds > 1 ? 's' : '');
return result;
}
</script>
<SettingsCard collapsible={false}>
<Clock slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
<span slot="title">Network Time</span>
<div class="w-full overflow-x-auto">
{#await getNTPStatus()}
<Spinner />
{:then nothing}
<div
class="flex w-full flex-col space-y-1"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
<div
class="mask mask-hexagon h-auto w-10 {ntpStatus.status === 1
? 'bg-success'
: 'bg-error'}"
>
<NTP
class="h-auto w-full scale-75 {ntpStatus.status === 1
? 'text-success-content'
: 'text-error-content'}"
/>
</div>
<div>
<div class="font-bold">Status</div>
<div class="text-sm opacity-75">
{ntpStatus.status === 1 ? 'Active' : 'Inactive'}
</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">
<Server class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">NTP Server</div>
<div class="text-sm opacity-75">
{ntpStatus.server}
</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">
<Clock class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Local Time</div>
<div class="text-sm opacity-75">
{new Intl.DateTimeFormat('en-GB', {
dateStyle: 'long',
timeStyle: 'long'
}).format(new Date(ntpStatus.local_time))}
</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">
<UTC class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">UTC Time</div>
<div class="text-sm opacity-75">
{new Intl.DateTimeFormat('en-GB', {
dateStyle: 'long',
timeStyle: 'long',
timeZone: 'UTC'
}).format(new Date(ntpStatus.utc_time))}
</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">
<Stopwatch class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Uptime</div>
<div class="text-sm opacity-75">
{convertSeconds(ntpStatus.uptime)}
</div>
</div>
</div>
</div>
{/await}
</div>
{#if !$page.data.features.security || $user.admin}
<Collapsible open={false} class="shadow-lg" on:closed={getNTPSettings}>
<span slot="title">Change NTP Settings</span>
<form
class="form-control w-full"
on:submit|preventDefault={handleSubmitNTP}
novalidate
bind:this={formField}
>
<label class="label cursor-pointer justify-start gap-4">
<input
type="checkbox"
bind:checked={ntpSettings.enabled}
class="checkbox checkbox-primary"
/>
<span class="">Enable NTP</span>
</label>
<label class="label" for="server">
<span class="label-text text-md">Server</span>
</label>
<input
type="text"
min="3"
max="64"
class="input input-bordered invalid:border-error w-full invalid:border-2 {formErrors.server
? 'border-error border-2'
: ''}"
bind:value={ntpSettings.server}
id="server"
required
/>
<label class="label" for="subnet">
<span class="label-text-alt text-error {formErrors.server ? '' : 'hidden'}"
>Must be a valid IPv4 address or URL</span
>
</label>
<label class="label" for="tz">
<span class="label-text text-md">Pick Time Zone</span>
</label>
<select class="select select-bordered" bind:value={ntpSettings.tz_label} id="tz">
{#each Object.entries(TIME_ZONES) as [tz_label, tz_format]}
<option value={tz_label}>{tz_label}</option>
{/each}
</select>
<div class="mt-6 place-self-end">
<button class="btn btn-primary" type="submit">Apply Settings</button>
</div>
</form>
</Collapsible>
{/if}
</SettingsCard>
-466
View File
@@ -1,466 +0,0 @@
export type TimeZones = {
[name: string]: string
};
export const TIME_ZONES: TimeZones = {
"Africa/Abidjan": "GMT0",
"Africa/Accra": "GMT0",
"Africa/Addis_Ababa": "EAT-3",
"Africa/Algiers": "CET-1",
"Africa/Asmara": "EAT-3",
"Africa/Bamako": "GMT0",
"Africa/Bangui": "WAT-1",
"Africa/Banjul": "GMT0",
"Africa/Bissau": "GMT0",
"Africa/Blantyre": "CAT-2",
"Africa/Brazzaville": "WAT-1",
"Africa/Bujumbura": "CAT-2",
"Africa/Cairo": "EET-2",
"Africa/Casablanca": "UNK-1",
"Africa/Ceuta": "CET-1CEST,M3.5.0,M10.5.0/3",
"Africa/Conakry": "GMT0",
"Africa/Dakar": "GMT0",
"Africa/Dar_es_Salaam": "EAT-3",
"Africa/Djibouti": "EAT-3",
"Africa/Douala": "WAT-1",
"Africa/El_Aaiun": "UNK-1",
"Africa/Freetown": "GMT0",
"Africa/Gaborone": "CAT-2",
"Africa/Harare": "CAT-2",
"Africa/Johannesburg": "SAST-2",
"Africa/Juba": "EAT-3",
"Africa/Kampala": "EAT-3",
"Africa/Khartoum": "CAT-2",
"Africa/Kigali": "CAT-2",
"Africa/Kinshasa": "WAT-1",
"Africa/Lagos": "WAT-1",
"Africa/Libreville": "WAT-1",
"Africa/Lome": "GMT0",
"Africa/Luanda": "WAT-1",
"Africa/Lubumbashi": "CAT-2",
"Africa/Lusaka": "CAT-2",
"Africa/Malabo": "WAT-1",
"Africa/Maputo": "CAT-2",
"Africa/Maseru": "SAST-2",
"Africa/Mbabane": "SAST-2",
"Africa/Mogadishu": "EAT-3",
"Africa/Monrovia": "GMT0",
"Africa/Nairobi": "EAT-3",
"Africa/Ndjamena": "WAT-1",
"Africa/Niamey": "WAT-1",
"Africa/Nouakchott": "GMT0",
"Africa/Ouagadougou": "GMT0",
"Africa/Porto-Novo": "WAT-1",
"Africa/Sao_Tome": "GMT0",
"Africa/Tripoli": "EET-2",
"Africa/Tunis": "CET-1",
"Africa/Windhoek": "CAT-2",
"America/Adak": "HST10HDT,M3.2.0,M11.1.0",
"America/Anchorage": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Anguilla": "AST4",
"America/Antigua": "AST4",
"America/Araguaina": "UNK3",
"America/Argentina/Buenos_Aires": "UNK3",
"America/Argentina/Catamarca": "UNK3",
"America/Argentina/Cordoba": "UNK3",
"America/Argentina/Jujuy": "UNK3",
"America/Argentina/La_Rioja": "UNK3",
"America/Argentina/Mendoza": "UNK3",
"America/Argentina/Rio_Gallegos": "UNK3",
"America/Argentina/Salta": "UNK3",
"America/Argentina/San_Juan": "UNK3",
"America/Argentina/San_Luis": "UNK3",
"America/Argentina/Tucuman": "UNK3",
"America/Argentina/Ushuaia": "UNK3",
"America/Aruba": "AST4",
"America/Asuncion": "UNK4UNK,M10.1.0/0,M3.4.0/0",
"America/Atikokan": "EST5",
"America/Bahia": "UNK3",
"America/Bahia_Banderas": "CST6CDT,M4.1.0,M10.5.0",
"America/Barbados": "AST4",
"America/Belem": "UNK3",
"America/Belize": "CST6",
"America/Blanc-Sablon": "AST4",
"America/Boa_Vista": "UNK4",
"America/Bogota": "UNK5",
"America/Boise": "MST7MDT,M3.2.0,M11.1.0",
"America/Cambridge_Bay": "MST7MDT,M3.2.0,M11.1.0",
"America/Campo_Grande": "UNK4",
"America/Cancun": "EST5",
"America/Caracas": "UNK4",
"America/Cayenne": "UNK3",
"America/Cayman": "EST5",
"America/Chicago": "CST6CDT,M3.2.0,M11.1.0",
"America/Chihuahua": "MST7MDT,M4.1.0,M10.5.0",
"America/Costa_Rica": "CST6",
"America/Creston": "MST7",
"America/Cuiaba": "UNK4",
"America/Curacao": "AST4",
"America/Danmarkshavn": "GMT0",
"America/Dawson": "MST7",
"America/Dawson_Creek": "MST7",
"America/Denver": "MST7MDT,M3.2.0,M11.1.0",
"America/Detroit": "EST5EDT,M3.2.0,M11.1.0",
"America/Dominica": "AST4",
"America/Edmonton": "MST7MDT,M3.2.0,M11.1.0",
"America/Eirunepe": "UNK5",
"America/El_Salvador": "CST6",
"America/Fort_Nelson": "MST7",
"America/Fortaleza": "UNK3",
"America/Glace_Bay": "AST4ADT,M3.2.0,M11.1.0",
"America/Godthab": "UNK3UNK,M3.5.0/-2,M10.5.0/-1",
"America/Goose_Bay": "AST4ADT,M3.2.0,M11.1.0",
"America/Grand_Turk": "EST5EDT,M3.2.0,M11.1.0",
"America/Grenada": "AST4",
"America/Guadeloupe": "AST4",
"America/Guatemala": "CST6",
"America/Guayaquil": "UNK5",
"America/Guyana": "UNK4",
"America/Halifax": "AST4ADT,M3.2.0,M11.1.0",
"America/Havana": "CST5CDT,M3.2.0/0,M11.1.0/1",
"America/Hermosillo": "MST7",
"America/Indiana/Indianapolis": "EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Knox": "CST6CDT,M3.2.0,M11.1.0",
"America/Indiana/Marengo": "EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Petersburg": "EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Tell_City": "CST6CDT,M3.2.0,M11.1.0",
"America/Indiana/Vevay": "EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Vincennes": "EST5EDT,M3.2.0,M11.1.0",
"America/Indiana/Winamac": "EST5EDT,M3.2.0,M11.1.0",
"America/Inuvik": "MST7MDT,M3.2.0,M11.1.0",
"America/Iqaluit": "EST5EDT,M3.2.0,M11.1.0",
"America/Jamaica": "EST5",
"America/Juneau": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Kentucky/Louisville": "EST5EDT,M3.2.0,M11.1.0",
"America/Kentucky/Monticello": "EST5EDT,M3.2.0,M11.1.0",
"America/Kralendijk": "AST4",
"America/La_Paz": "UNK4",
"America/Lima": "UNK5",
"America/Los_Angeles": "PST8PDT,M3.2.0,M11.1.0",
"America/Lower_Princes": "AST4",
"America/Maceio": "UNK3",
"America/Managua": "CST6",
"America/Manaus": "UNK4",
"America/Marigot": "AST4",
"America/Martinique": "AST4",
"America/Matamoros": "CST6CDT,M3.2.0,M11.1.0",
"America/Mazatlan": "MST7MDT,M4.1.0,M10.5.0",
"America/Menominee": "CST6CDT,M3.2.0,M11.1.0",
"America/Merida": "CST6CDT,M4.1.0,M10.5.0",
"America/Metlakatla": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Mexico_City": "CST6CDT,M4.1.0,M10.5.0",
"America/Miquelon": "UNK3UNK,M3.2.0,M11.1.0",
"America/Moncton": "AST4ADT,M3.2.0,M11.1.0",
"America/Monterrey": "CST6CDT,M4.1.0,M10.5.0",
"America/Montevideo": "UNK3",
"America/Montreal": "EST5EDT,M3.2.0,M11.1.0",
"America/Montserrat": "AST4",
"America/Nassau": "EST5EDT,M3.2.0,M11.1.0",
"America/New_York": "EST5EDT,M3.2.0,M11.1.0",
"America/Nipigon": "EST5EDT,M3.2.0,M11.1.0",
"America/Nome": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Noronha": "UNK2",
"America/North_Dakota/Beulah": "CST6CDT,M3.2.0,M11.1.0",
"America/North_Dakota/Center": "CST6CDT,M3.2.0,M11.1.0",
"America/North_Dakota/New_Salem": "CST6CDT,M3.2.0,M11.1.0",
"America/Ojinaga": "MST7MDT,M3.2.0,M11.1.0",
"America/Panama": "EST5",
"America/Pangnirtung": "EST5EDT,M3.2.0,M11.1.0",
"America/Paramaribo": "UNK3",
"America/Phoenix": "MST7",
"America/Port-au-Prince": "EST5EDT,M3.2.0,M11.1.0",
"America/Port_of_Spain": "AST4",
"America/Porto_Velho": "UNK4",
"America/Puerto_Rico": "AST4",
"America/Punta_Arenas": "UNK3",
"America/Rainy_River": "CST6CDT,M3.2.0,M11.1.0",
"America/Rankin_Inlet": "CST6CDT,M3.2.0,M11.1.0",
"America/Recife": "UNK3",
"America/Regina": "CST6",
"America/Resolute": "CST6CDT,M3.2.0,M11.1.0",
"America/Rio_Branco": "UNK5",
"America/Santarem": "UNK3",
"America/Santiago": "UNK4UNK,M9.1.6/24,M4.1.6/24",
"America/Santo_Domingo": "AST4",
"America/Sao_Paulo": "UNK3",
"America/Scoresbysund": "UNK1UNK,M3.5.0/0,M10.5.0/1",
"America/Sitka": "AKST9AKDT,M3.2.0,M11.1.0",
"America/St_Barthelemy": "AST4",
"America/St_Johns": "NST3:30NDT,M3.2.0,M11.1.0",
"America/St_Kitts": "AST4",
"America/St_Lucia": "AST4",
"America/St_Thomas": "AST4",
"America/St_Vincent": "AST4",
"America/Swift_Current": "CST6",
"America/Tegucigalpa": "CST6",
"America/Thule": "AST4ADT,M3.2.0,M11.1.0",
"America/Thunder_Bay": "EST5EDT,M3.2.0,M11.1.0",
"America/Tijuana": "PST8PDT,M3.2.0,M11.1.0",
"America/Toronto": "EST5EDT,M3.2.0,M11.1.0",
"America/Tortola": "AST4",
"America/Vancouver": "PST8PDT,M3.2.0,M11.1.0",
"America/Whitehorse": "MST7",
"America/Winnipeg": "CST6CDT,M3.2.0,M11.1.0",
"America/Yakutat": "AKST9AKDT,M3.2.0,M11.1.0",
"America/Yellowknife": "MST7MDT,M3.2.0,M11.1.0",
"Antarctica/Casey": "UNK-8",
"Antarctica/Davis": "UNK-7",
"Antarctica/DumontDUrville": "UNK-10",
"Antarctica/Macquarie": "UNK-11",
"Antarctica/Mawson": "UNK-5",
"Antarctica/McMurdo": "NZST-12NZDT,M9.5.0,M4.1.0/3",
"Antarctica/Palmer": "UNK3",
"Antarctica/Rothera": "UNK3",
"Antarctica/Syowa": "UNK-3",
"Antarctica/Troll": "UNK0UNK-2,M3.5.0/1,M10.5.0/3",
"Antarctica/Vostok": "UNK-6",
"Arctic/Longyearbyen": "CET-1CEST,M3.5.0,M10.5.0/3",
"Asia/Aden": "UNK-3",
"Asia/Almaty": "UNK-6",
"Asia/Amman": "EET-2EEST,M3.5.4/24,M10.5.5/1",
"Asia/Anadyr": "UNK-12",
"Asia/Aqtau": "UNK-5",
"Asia/Aqtobe": "UNK-5",
"Asia/Ashgabat": "UNK-5",
"Asia/Atyrau": "UNK-5",
"Asia/Baghdad": "UNK-3",
"Asia/Bahrain": "UNK-3",
"Asia/Baku": "UNK-4",
"Asia/Bangkok": "UNK-7",
"Asia/Barnaul": "UNK-7",
"Asia/Beirut": "EET-2EEST,M3.5.0/0,M10.5.0/0",
"Asia/Bishkek": "UNK-6",
"Asia/Brunei": "UNK-8",
"Asia/Chita": "UNK-9",
"Asia/Choibalsan": "UNK-8",
"Asia/Colombo": "UNK-5:30",
"Asia/Damascus": "EET-2EEST,M3.5.5/0,M10.5.5/0",
"Asia/Dhaka": "UNK-6",
"Asia/Dili": "UNK-9",
"Asia/Dubai": "UNK-4",
"Asia/Dushanbe": "UNK-5",
"Asia/Famagusta": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Asia/Gaza": "EET-2EEST,M3.5.5/0,M10.5.6/1",
"Asia/Hebron": "EET-2EEST,M3.5.5/0,M10.5.6/1",
"Asia/Ho_Chi_Minh": "UNK-7",
"Asia/Hong_Kong": "HKT-8",
"Asia/Hovd": "UNK-7",
"Asia/Irkutsk": "UNK-8",
"Asia/Jakarta": "WIB-7",
"Asia/Jayapura": "WIT-9",
"Asia/Jerusalem": "IST-2IDT,M3.4.4/26,M10.5.0",
"Asia/Kabul": "UNK-4:30",
"Asia/Kamchatka": "UNK-12",
"Asia/Karachi": "PKT-5",
"Asia/Kathmandu": "UNK-5:45",
"Asia/Khandyga": "UNK-9",
"Asia/Kolkata": "IST-5:30",
"Asia/Krasnoyarsk": "UNK-7",
"Asia/Kuala_Lumpur": "UNK-8",
"Asia/Kuching": "UNK-8",
"Asia/Kuwait": "UNK-3",
"Asia/Macau": "CST-8",
"Asia/Magadan": "UNK-11",
"Asia/Makassar": "WITA-8",
"Asia/Manila": "PST-8",
"Asia/Muscat": "UNK-4",
"Asia/Nicosia": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Asia/Novokuznetsk": "UNK-7",
"Asia/Novosibirsk": "UNK-7",
"Asia/Omsk": "UNK-6",
"Asia/Oral": "UNK-5",
"Asia/Phnom_Penh": "UNK-7",
"Asia/Pontianak": "WIB-7",
"Asia/Pyongyang": "KST-9",
"Asia/Qatar": "UNK-3",
"Asia/Qyzylorda": "UNK-5",
"Asia/Riyadh": "UNK-3",
"Asia/Sakhalin": "UNK-11",
"Asia/Samarkand": "UNK-5",
"Asia/Seoul": "KST-9",
"Asia/Shanghai": "CST-8",
"Asia/Singapore": "UNK-8",
"Asia/Srednekolymsk": "UNK-11",
"Asia/Taipei": "CST-8",
"Asia/Tashkent": "UNK-5",
"Asia/Tbilisi": "UNK-4",
"Asia/Tehran": "UNK-3:30UNK,J79/24,J263/24",
"Asia/Thimphu": "UNK-6",
"Asia/Tokyo": "JST-9",
"Asia/Tomsk": "UNK-7",
"Asia/Ulaanbaatar": "UNK-8",
"Asia/Urumqi": "UNK-6",
"Asia/Ust-Nera": "UNK-10",
"Asia/Vientiane": "UNK-7",
"Asia/Vladivostok": "UNK-10",
"Asia/Yakutsk": "UNK-9",
"Asia/Yangon": "UNK-6:30",
"Asia/Yekaterinburg": "UNK-5",
"Asia/Yerevan": "UNK-4",
"Atlantic/Azores": "UNK1UNK,M3.5.0/0,M10.5.0/1",
"Atlantic/Bermuda": "AST4ADT,M3.2.0,M11.1.0",
"Atlantic/Canary": "WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Cape_Verde": "UNK1",
"Atlantic/Faroe": "WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Madeira": "WET0WEST,M3.5.0/1,M10.5.0",
"Atlantic/Reykjavik": "GMT0",
"Atlantic/South_Georgia": "UNK2",
"Atlantic/St_Helena": "GMT0",
"Atlantic/Stanley": "UNK3",
"Australia/Adelaide": "ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Brisbane": "AEST-10",
"Australia/Broken_Hill": "ACST-9:30ACDT,M10.1.0,M4.1.0/3",
"Australia/Currie": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Darwin": "ACST-9:30",
"Australia/Eucla": "UNK-8:45",
"Australia/Hobart": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Lindeman": "AEST-10",
"Australia/Lord_Howe": "UNK-10:30UNK-11,M10.1.0,M4.1.0",
"Australia/Melbourne": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Australia/Perth": "AWST-8",
"Australia/Sydney": "AEST-10AEDT,M10.1.0,M4.1.0/3",
"Etc/GMT": "GMT0",
"Etc/GMT+0": "GMT0",
"Etc/GMT+1": "UNK1",
"Etc/GMT+10": "UNK10",
"Etc/GMT+11": "UNK11",
"Etc/GMT+12": "UNK12",
"Etc/GMT+2": "UNK2",
"Etc/GMT+3": "UNK3",
"Etc/GMT+4": "UNK4",
"Etc/GMT+5": "UNK5",
"Etc/GMT+6": "UNK6",
"Etc/GMT+7": "UNK7",
"Etc/GMT+8": "UNK8",
"Etc/GMT+9": "UNK9",
"Etc/GMT-0": "GMT0",
"Etc/GMT-1": "UNK-1",
"Etc/GMT-10": "UNK-10",
"Etc/GMT-11": "UNK-11",
"Etc/GMT-12": "UNK-12",
"Etc/GMT-13": "UNK-13",
"Etc/GMT-14": "UNK-14",
"Etc/GMT-2": "UNK-2",
"Etc/GMT-3": "UNK-3",
"Etc/GMT-4": "UNK-4",
"Etc/GMT-5": "UNK-5",
"Etc/GMT-6": "UNK-6",
"Etc/GMT-7": "UNK-7",
"Etc/GMT-8": "UNK-8",
"Etc/GMT-9": "UNK-9",
"Etc/GMT0": "GMT0",
"Etc/Greenwich": "GMT0",
"Etc/UCT": "UTC0",
"Etc/UTC": "UTC0",
"Etc/Universal": "UTC0",
"Etc/Zulu": "UTC0",
"Europe/Amsterdam": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Andorra": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Astrakhan": "UNK-4",
"Europe/Athens": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Belgrade": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Berlin": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Bratislava": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Brussels": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Bucharest": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Budapest": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Busingen": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Chisinau": "EET-2EEST,M3.5.0,M10.5.0/3",
"Europe/Copenhagen": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Dublin": "IST-1GMT0,M10.5.0,M3.5.0/1",
"Europe/Gibraltar": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Guernsey": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Helsinki": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Isle_of_Man": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Istanbul": "UNK-3",
"Europe/Jersey": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Kaliningrad": "EET-2",
"Europe/Kiev": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Kirov": "UNK-3",
"Europe/Lisbon": "WET0WEST,M3.5.0/1,M10.5.0",
"Europe/Ljubljana": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/London": "GMT0BST,M3.5.0/1,M10.5.0",
"Europe/Luxembourg": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Madrid": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Malta": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Mariehamn": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Minsk": "UNK-3",
"Europe/Monaco": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Moscow": "MSK-3",
"Europe/Oslo": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Paris": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Podgorica": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Prague": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Riga": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Rome": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Samara": "UNK-4",
"Europe/San_Marino": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Sarajevo": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Saratov": "UNK-4",
"Europe/Simferopol": "MSK-3",
"Europe/Skopje": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Sofia": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Stockholm": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Tallinn": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Tirane": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Ulyanovsk": "UNK-4",
"Europe/Uzhgorod": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Vaduz": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vatican": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vienna": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Vilnius": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Volgograd": "UNK-4",
"Europe/Warsaw": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Zagreb": "CET-1CEST,M3.5.0,M10.5.0/3",
"Europe/Zaporozhye": "EET-2EEST,M3.5.0/3,M10.5.0/4",
"Europe/Zurich": "CET-1CEST,M3.5.0,M10.5.0/3",
"Indian/Antananarivo": "EAT-3",
"Indian/Chagos": "UNK-6",
"Indian/Christmas": "UNK-7",
"Indian/Cocos": "UNK-6:30",
"Indian/Comoro": "EAT-3",
"Indian/Kerguelen": "UNK-5",
"Indian/Mahe": "UNK-4",
"Indian/Maldives": "UNK-5",
"Indian/Mauritius": "UNK-4",
"Indian/Mayotte": "EAT-3",
"Indian/Reunion": "UNK-4",
"Pacific/Apia": "UNK-13UNK,M9.5.0/3,M4.1.0/4",
"Pacific/Auckland": "NZST-12NZDT,M9.5.0,M4.1.0/3",
"Pacific/Bougainville": "UNK-11",
"Pacific/Chatham": "UNK-12:45UNK,M9.5.0/2:45,M4.1.0/3:45",
"Pacific/Chuuk": "UNK-10",
"Pacific/Easter": "UNK6UNK,M9.1.6/22,M4.1.6/22",
"Pacific/Efate": "UNK-11",
"Pacific/Enderbury": "UNK-13",
"Pacific/Fakaofo": "UNK-13",
"Pacific/Fiji": "UNK-12UNK,M11.2.0,M1.2.3/99",
"Pacific/Funafuti": "UNK-12",
"Pacific/Galapagos": "UNK6",
"Pacific/Gambier": "UNK9",
"Pacific/Guadalcanal": "UNK-11",
"Pacific/Guam": "ChST-10",
"Pacific/Honolulu": "HST10",
"Pacific/Kiritimati": "UNK-14",
"Pacific/Kosrae": "UNK-11",
"Pacific/Kwajalein": "UNK-12",
"Pacific/Majuro": "UNK-12",
"Pacific/Marquesas": "UNK9:30",
"Pacific/Midway": "SST11",
"Pacific/Nauru": "UNK-12",
"Pacific/Niue": "UNK11",
"Pacific/Norfolk": "UNK-11UNK,M10.1.0,M4.1.0/3",
"Pacific/Noumea": "UNK-11",
"Pacific/Pago_Pago": "SST11",
"Pacific/Palau": "UNK-9",
"Pacific/Pitcairn": "UNK8",
"Pacific/Pohnpei": "UNK-11",
"Pacific/Port_Moresby": "UNK-10",
"Pacific/Rarotonga": "UNK10",
"Pacific/Saipan": "ChST-10",
"Pacific/Tahiti": "UNK10",
"Pacific/Tarawa": "UNK-12",
"Pacific/Tongatapu": "UNK-13",
"Pacific/Wake": "UNK-12",
"Pacific/Wallis": "UNK-12"
};
+1 -1
View File
@@ -39,7 +39,7 @@
}
</script>
<div class="navbar bg-base-300 sticky top-0 z-10 h-12 min-h-fit drop-shadow-lg lg:h-16">
<div class="navbar bg-base-300 sticky top-0 z-10 h-12 min-h-fit drop-shadow-lg lg:h-16 gap-2">
<div class="flex-1">
<!-- Page Hamburger Icon here -->
<label for="main-menu" class="btn btn-ghost btn-circle btn-sm drawer-button"
@@ -24,33 +24,10 @@
import Health from '~icons/tabler/stethoscope';
import Stopwatch from '~icons/tabler/24-hours';
import SDK from '~icons/tabler/sdk';
import type { SystemInformation, Analytics } from '$lib/types/models';
import { socket } from '$lib/stores/socket';
type SystemStatus = {
esp_platform: string;
firmware_version: string;
max_alloc_heap: number;
psram_size: number;
free_psram: number;
cpu_freq_mhz: number;
cpu_type: string;
cpu_rev: number;
cpu_cores: number;
free_heap: number;
min_free_heap: number;
sketch_size: number;
free_sketch_space: number;
sdk_version: string;
arduino_version: string;
flash_chip_size: number;
flash_chip_speed: number;
fs_total: number;
fs_used: number;
core_temp: number;
cpu_reset_reason: string;
uptime: number;
};
let systemStatus: SystemStatus;
let systemInformation: SystemInformation;
async function getSystemStatus() {
try {
@@ -61,20 +38,19 @@
'Content-Type': 'application/json'
}
});
systemStatus = await response.json();
systemInformation = await response.json();
} catch (error) {
console.log('Error:', error);
}
return systemStatus;
return systemInformation;
}
const interval = setInterval(async () => {
getSystemStatus();
}, 5000);
onMount(() => socket.on('analytics', handleSystemData));
onMount(() => getSystemStatus());
onDestroy(() => socket.off('analytics', handleSystemData));
onDestroy(() => clearInterval(interval));
const handleSystemData = (data: Analytics) =>
(systemInformation = { ...systemInformation, ...data });
async function postRestart() {
const response = await fetch('/rest/restart', {
@@ -195,7 +171,7 @@
<div>
<div class="font-bold">Chip</div>
<div class="text-sm opacity-75">
{systemStatus.cpu_type} Rev {systemStatus.cpu_rev}
{systemInformation.cpu_type} Rev {systemInformation.cpu_rev}
</div>
</div>
</div>
@@ -207,7 +183,7 @@
<div>
<div class="font-bold">SDK Version</div>
<div class="text-sm opacity-75">
ESP-IDF {systemStatus.sdk_version} / Arduino {systemStatus.arduino_version}
ESP-IDF {systemInformation.sdk_version} / Arduino {systemInformation.arduino_version}
</div>
</div>
</div>
@@ -219,7 +195,7 @@
<div>
<div class="font-bold">Firmware Version</div>
<div class="text-sm opacity-75">
{systemStatus.firmware_version}
{systemInformation.firmware_version}
</div>
</div>
</div>
@@ -231,7 +207,7 @@
<div>
<div class="font-bold">CPU Frequency</div>
<div class="text-sm opacity-75">
{systemStatus.cpu_freq_mhz} MHz {systemStatus.cpu_cores == 2
{systemInformation.cpu_freq_mhz} MHz {systemInformation.cpu_cores == 2
? 'Dual Core'
: 'Single Core'}
</div>
@@ -245,7 +221,7 @@
<div>
<div class="font-bold">Heap (Free / Max Alloc)</div>
<div class="text-sm opacity-75">
{systemStatus.free_heap.toLocaleString('en-US')} / {systemStatus.max_alloc_heap.toLocaleString(
{systemInformation.free_heap.toLocaleString('en-US')} / {systemInformation.max_alloc_heap.toLocaleString(
'en-US'
)} bytes
</div>
@@ -259,7 +235,7 @@
<div>
<div class="font-bold">PSRAM (Size / Free)</div>
<div class="text-sm opacity-75">
{systemStatus.psram_size.toLocaleString('en-US')} / {systemStatus.psram_size.toLocaleString(
{systemInformation.psram_size.toLocaleString('en-US')} / {systemInformation.psram_size.toLocaleString(
'en-US'
)} bytes
</div>
@@ -274,13 +250,16 @@
<div class="font-bold">Sketch (Used / Free)</div>
<div class="flex flex-wrap justify-start gap-1 text-sm opacity-75">
<span>
{((systemStatus.sketch_size / systemStatus.free_sketch_space) * 100).toFixed(1)} % of
{(systemStatus.free_sketch_space / 1000000).toLocaleString('en-US')} MB used
{(
(systemInformation.sketch_size / systemInformation.free_sketch_space) *
100
).toFixed(1)} % of
{(systemInformation.free_sketch_space / 1000000).toLocaleString('en-US')} MB used
</span>
<span>
({(
(systemStatus.free_sketch_space - systemStatus.sketch_size) /
(systemInformation.free_sketch_space - systemInformation.sketch_size) /
1000000
).toLocaleString('en-US')} MB free)
</span>
@@ -295,8 +274,8 @@
<div>
<div class="font-bold">Flash Chip (Size / Speed)</div>
<div class="text-sm opacity-75">
{(systemStatus.flash_chip_size / 1000000).toLocaleString('en-US')} MB / {(
systemStatus.flash_chip_speed / 1000000
{(systemInformation.flash_chip_size / 1000000).toLocaleString('en-US')} MB / {(
systemInformation.flash_chip_speed / 1000000
).toLocaleString('en-US')} MHz
</div>
</div>
@@ -310,15 +289,16 @@
<div class="font-bold">File System (Used / Total)</div>
<div class="flex flex-wrap justify-start gap-1 text-sm opacity-75">
<span
>{((systemStatus.fs_used / systemStatus.fs_total) * 100).toFixed(1)} % of {(
systemStatus.fs_total / 1000000
>{((systemInformation.fs_used / systemInformation.fs_total) * 100).toFixed(1)} % of {(
systemInformation.fs_total / 1000000
).toLocaleString('en-US')} MB used</span
>
<span
>({((systemStatus.fs_total - systemStatus.fs_used) / 1000000).toLocaleString(
'en-US'
)}
>({(
(systemInformation.fs_total - systemInformation.fs_used) /
1000000
).toLocaleString('en-US')}
MB free)</span
>
</div>
@@ -332,7 +312,9 @@
<div>
<div class="font-bold">Core Temperature</div>
<div class="text-sm opacity-75">
{systemStatus.core_temp == 53.33 ? 'NaN' : systemStatus.core_temp.toFixed(2) + ' °C'}
{systemInformation.core_temp == 53.33
? 'NaN'
: systemInformation.core_temp.toFixed(2) + ' °C'}
</div>
</div>
</div>
@@ -344,7 +326,7 @@
<div>
<div class="font-bold">Uptime</div>
<div class="text-sm opacity-75">
{convertSeconds(systemStatus.uptime)}
{convertSeconds(systemInformation.uptime)}
</div>
</div>
</div>
@@ -356,31 +338,7 @@
<div>
<div class="font-bold">Reset Reason</div>
<div class="text-sm opacity-75">
{systemStatus.cpu_reset_reason}
</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">
<Stopwatch class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Uptime</div>
<div class="text-sm opacity-75">
{convertSeconds(systemStatus.uptime)}
</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">
<Power class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Reset Reason</div>
<div class="text-sm opacity-75">
{systemStatus.cpu_reset_reason}
{systemInformation.cpu_reset_reason}
</div>
</div>
</div>
+1 -19
View File
@@ -13,25 +13,7 @@
import MAC from '~icons/tabler/dna-2';
import Home from '~icons/tabler/home';
import Devices from '~icons/tabler/devices';
type ApStatus = {
status: number;
ip_address: string;
mac_address: string;
station_num: number;
};
type ApSettings = {
provision_mode: number;
ssid: string;
password: string;
channel: number;
ssid_hidden: boolean;
max_clients: number;
local_ip: string;
gateway_ip: string;
subnet_mask: string;
};
import type { ApSettings, ApStatus } from '$lib/types/models';
let apSettings: ApSettings;
let apStatus: ApStatus;
+3 -10
View File
@@ -10,6 +10,7 @@
import Reload from '~icons/tabler/reload';
import { onMount, onDestroy } from 'svelte';
import RssiIndicator from '$lib/components/RSSIIndicator.svelte';
import type { NetworkItem } from '$lib/types/models';
// provided by <Modals />
export let isOpen: boolean;
@@ -27,15 +28,7 @@
'WAPI PSK'
];
type networkItem = {
rssi: number;
ssid: string;
bssid: string;
channel: number;
encryption_type: number;
};
let listOfNetworks: networkItem[] = [];
let listOfNetworks: NetworkItem[] = [];
let scanActive = false;
@@ -103,7 +96,7 @@
use:focusTrap
>
<div
class="bg-base-100 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
class="bg-base-100 shadow-secondary/30 rounded-box pointer-events-auto flex max-h-full min-w-fit max-w-md flex-col justify-between p-4 shadow-lg"
>
<h2 class="text-base-content text-start text-2xl font-bold">Scan Networks</h2>
<div class="divider my-2" />
+3 -33
View File
@@ -32,39 +32,9 @@
import Cancel from '~icons/tabler/x';
import Check from '~icons/tabler/check';
import InfoDialog from '$lib/components/InfoDialog.svelte';
import type { KnownNetworkItem, WifiSettings, WifiStatus } from '$lib/types/models';
type WifiStatus = {
status: number;
local_ip: string;
mac_address: string;
rssi: number;
ssid: string;
bssid: string;
channel: number;
subnet_mask: string;
gateway_ip: string;
dns_ip_1: string;
dns_ip_2?: string;
};
type WifiSettings = {
hostname: string;
priority_RSSI: boolean;
wifi_networks: networkItem[];
};
type networkItem = {
ssid: string;
password: string;
static_ip_config: boolean;
local_ip?: string;
subnet_mask?: string;
gateway_ip?: string;
dns_ip_1?: string;
dns_ip_2?: string;
};
let networkEditable: networkItem = {
let networkEditable: KnownNetworkItem = {
ssid: '',
password: '',
static_ip_config: false,
@@ -81,7 +51,7 @@
let wifiStatus: WifiStatus;
let wifiSettings: WifiSettings;
let dndNetworkList: networkItem[] = [];
let dndNetworkList: KnownNetworkItem[] = [];
let showWifiDetails = false;