🧼 Refactors wifi and ap to use StatusItem

This commit is contained in:
Rune Harlyk
2025-03-08 14:35:55 +01:00
committed by Rune Harlyk
parent 72f3bcfd78
commit 99660b9a23
5 changed files with 955 additions and 1161 deletions
+43
View File
@@ -0,0 +1,43 @@
<script lang="ts">
type Variant = 'success' | 'error' | 'primary' | 'info' | 'warning'
const {
icon,
title,
description = '',
variant = 'primary',
class: klass = '',
children = null
} = $props<{
icon: any
title: string
description?: string | number
variant?: Variant
class?: string
children?: () => any
}>()
const Icon = $derived(icon)
const variants: Record<Variant, [string, string]> = {
success: ['bg-success', 'text-success-content'],
error: ['bg-error', 'text-error-content'],
primary: ['bg-primary', 'text-primary-content'],
info: ['bg-info', 'text-info-content'],
warning: ['bg-warning', 'text-warning-content']
}
const variantKey: Variant = (variant as Variant) in variants ? (variant as Variant) : 'primary'
const [bgColor, textColor] = variants[variantKey]
</script>
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2 {klass}">
<div class="mask mask-hexagon {bgColor} h-auto w-10 flex-none">
<Icon class="{textColor} h-auto w-full scale-75" />
</div>
<div class="grow">
<div class="font-bold">{title}</div>
<div class="text-sm opacity-75 grow">{description}</div>
</div>
{@render children?.()}
</div>
@@ -1,15 +0,0 @@
<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>
@@ -6,12 +6,10 @@
import Spinner from '$lib/components/Spinner.svelte'
import { slide } from 'svelte/transition'
import { cubicOut } from 'svelte/easing'
import type { SystemInformation, Analytics } from '$lib/types/models'
import { socket } from '$lib/stores/socket'
import { api } from '$lib/api'
import { convertSeconds } from '$lib/utilities'
import { useFeatureFlags } from '$lib/stores/featureFlags'
import {
Cancel,
@@ -31,7 +29,7 @@
Temperature,
Stopwatch
} from '$lib/components/icons'
import StatusItem from './StatusItem.svelte'
import StatusItem from '$lib/components/StatusItem.svelte'
import ActionButton from './ActionButton.svelte'
const features = useFeatureFlags()
+104 -176
View File
@@ -1,52 +1,53 @@
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import { preventDefault } from 'svelte/legacy'
import { onMount, onDestroy } from 'svelte';
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { PasswordInput } from '$lib/components/input';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import { notifications } from '$lib/components/toasts/notifications';
import Spinner from '$lib/components/Spinner.svelte';
import type { ApSettings, ApStatus } from '$lib/types/models';
import { api } from '$lib/api';
import { useFeatureFlags } from '$lib/stores';
import { AP, Devices, Home, MAC } from '$lib/components/icons';
import { onMount, onDestroy } from 'svelte'
import { slide } from 'svelte/transition'
import { cubicOut } from 'svelte/easing'
import { PasswordInput } from '$lib/components/input'
import SettingsCard from '$lib/components/SettingsCard.svelte'
import { notifications } from '$lib/components/toasts/notifications'
import Spinner from '$lib/components/Spinner.svelte'
import type { ApSettings, ApStatus } from '$lib/types/models'
import { api } from '$lib/api'
import { useFeatureFlags } from '$lib/stores'
import { AP, Devices, Home, MAC } from '$lib/components/icons'
import StatusItem from '$lib/components/StatusItem.svelte'
const features = useFeatureFlags();
const features = useFeatureFlags()
let apSettings: ApSettings = $state();
let apStatus: ApStatus = $state();
let apSettings: ApSettings = $state()
let apStatus: ApStatus = $state()
let formField: any = $state();
let formField: any = $state()
async function getAPStatus() {
const result = await api.get<ApStatus>('/api/wifi/ap/status');
const result = await api.get<ApStatus>('/api/wifi/ap/status')
if (result.isErr()) {
console.error('Error:', result.inner);
return;
console.error('Error:', result.inner)
return
}
apStatus = result.inner;
return apStatus;
apStatus = result.inner
return apStatus
}
async function getAPSettings() {
const result = await api.get<ApSettings>('/api/wifi/ap/settings');
const result = await api.get<ApSettings>('/api/wifi/ap/settings')
if (result.isErr()) {
console.error('Error:', result.inner);
return;
console.error('Error:', result.inner)
return
}
apSettings = result.inner;
return apSettings;
apSettings = result.inner
return apSettings
}
const interval = setInterval(async () => {
getAPStatus();
}, 5000);
getAPStatus()
}, 5000)
onDestroy(() => clearInterval(interval));
onDestroy(() => clearInterval(interval))
onMount(getAPSettings);
onMount(getAPSettings)
let provisionMode = [
{
@@ -61,13 +62,13 @@
id: 2,
text: `Never`
}
];
]
let apStatusDescription = [
{ bg_color: 'bg-success', text_color: 'text-success-content', description: 'Active' },
{ bg_color: 'bg-error', text_color: 'text-error-content', description: 'Inactive' },
{ bg_color: 'bg-warning', text_color: 'text-warning-content', description: 'Lingering' }
];
type Variant = 'success' | 'error' | 'primary' | 'info' | 'warning'
let apStatusVariant: Variant[] = ['success', 'error', 'warning']
let apStatusDescription = ['Active', 'Inactive', 'Lingering']
let formErrors = $state({
ssid: false,
@@ -76,79 +77,79 @@
local_ip: false,
gateway_ip: false,
subnet_mask: false
});
})
async function postAPSettings(data: ApSettings) {
const result = await api.post<ApSettings>('/api/wifi/ap/settings', data);
const result = await api.post<ApSettings>('/api/wifi/ap/settings', data)
if (result.isErr()) {
notifications.error('User not authorized.', 3000);
console.error('Error:', result.inner);
return;
notifications.error('User not authorized.', 3000)
console.error('Error:', result.inner)
return
}
notifications.success('Access Point settings updated.', 3000);
apSettings = result.inner;
notifications.success('Access Point settings updated.', 3000)
apSettings = result.inner
}
function handleSubmitAP() {
let valid = true;
let valid = true
// Validate SSID
if (apSettings.ssid.length < 3 || apSettings.ssid.length > 32) {
valid = false;
formErrors.ssid = true;
valid = false
formErrors.ssid = true
} else {
formErrors.ssid = false;
formErrors.ssid = false
}
// Validate Channel
let channel = Number(apSettings.channel);
let channel = Number(apSettings.channel)
if (1 > channel || channel > 13) {
valid = false;
formErrors.channel = true;
valid = false
formErrors.channel = true
} else {
formErrors.channel = false;
formErrors.channel = false
}
// Validate max_clients
let maxClients = Number(apSettings.max_clients);
let maxClients = Number(apSettings.max_clients)
if (1 > maxClients || maxClients > 8) {
valid = false;
formErrors.max_clients = true;
valid = false
formErrors.max_clients = true
} else {
formErrors.max_clients = false;
formErrors.max_clients = false
}
// RegEx for IPv4
const regexExp =
/\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/;
/\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/
// Validate gateway IP
if (!regexExp.test(apSettings.gateway_ip)) {
valid = false;
formErrors.gateway_ip = true;
valid = false
formErrors.gateway_ip = true
} else {
formErrors.gateway_ip = false;
formErrors.gateway_ip = false
}
// Validate Subnet Mask
if (!regexExp.test(apSettings.subnet_mask)) {
valid = false;
formErrors.subnet_mask = true;
valid = false
formErrors.subnet_mask = true
} else {
formErrors.subnet_mask = false;
formErrors.subnet_mask = false
}
// Validate local IP
if (!regexExp.test(apSettings.local_ip)) {
valid = false;
formErrors.local_ip = true;
valid = false
formErrors.local_ip = true
} else {
formErrors.local_ip = false;
formErrors.local_ip = false
}
// Submit JSON to REST API
if (valid) {
postAPSettings(apSettings);
postAPSettings(apSettings)
}
}
</script>
@@ -166,69 +167,25 @@
{: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 {apStatusDescription[apStatus.status]
.bg_color}"
>
<AP
class="h-auto w-full scale-75 {apStatusDescription[apStatus.status]
.text_color}"
/>
</div>
<div>
<div class="font-bold">Status</div>
<div class="text-sm opacity-75">
{apStatusDescription[apStatus.status].description}
</div>
</div>
</div>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<StatusItem
icon={AP}
title="Status"
variant={apStatusVariant[apStatus.status]}
description={apStatusDescription[apStatus.status]} />
<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">
<Home class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">IP Address</div>
<div class="text-sm opacity-75">
{apStatus.ip_address}
</div>
</div>
</div>
<StatusItem icon={Home} title="IP Address" description={apStatus.ip_address} />
<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">
<MAC class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">MAC Address</div>
<div class="text-sm opacity-75">
{apStatus.mac_address}
</div>
</div>
</div>
<StatusItem icon={MAC} title="MAC Address" description={apStatus.mac_address} />
<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">
<Devices class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">AP Clients</div>
<div class="text-sm opacity-75">
{apStatus.station_num}
</div>
</div>
</div>
<StatusItem icon={Devices} title="AP Clients" description={apStatus.station_num} />
</div>
{/await}
</div>
<div class="bg-base-200 relative grid w-full max-w-2xl self-center overflow-hidden">
<div
class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium"
>
class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium">
Change AP Settings
</div>
{#await getAPSettings()}
@@ -236,14 +193,12 @@
{:then nothing}
<div
class="flex flex-col gap-2 p-0"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<form
class="grid w-full grid-cols-1 content-center gap-x-4 p-0s sm:grid-cols-2"
onsubmit={preventDefault(handleSubmitAP)}
novalidate
bind:this={formField}
>
bind:this={formField}>
<div>
<label class="label" for="apmode">
<span class="label-text">Provide Access Point ...</span>
@@ -251,8 +206,7 @@
<select
class="select select-bordered w-full"
id="apmode"
bind:value={apSettings.provision_mode}
>
bind:value={apSettings.provision_mode}>
{#each provisionMode as mode}
<option value={mode.id}>
{mode.text}
@@ -275,13 +229,10 @@
id="ssid"
min="2"
max="32"
required
/>
required />
<label class="label" for="ssid">
<span
class="label-text-alt text-error {formErrors.ssid ? '' : 'hidden'}"
>SSID must be between 2 and 32 characters long</span
>
<span class="label-text-alt text-error {formErrors.ssid ? '' : 'hidden'}"
>SSID must be between 2 and 32 characters long</span>
</label>
</div>
@@ -306,14 +257,10 @@
: ''}"
bind:value={apSettings.channel}
id="channel"
required
/>
required />
<label class="label" for="channel">
<span
class="label-text-alt text-error {formErrors.channel ? '' : (
'hidden'
)}">Must be channel 1 to 13</span
>
<span class="label-text-alt text-error {formErrors.channel ? '' : 'hidden'}"
>Must be channel 1 to 13</span>
</label>
</div>
@@ -332,14 +279,10 @@
: ''}"
bind:value={apSettings.max_clients}
id="clients"
required
/>
required />
<label class="label" for="clients">
<span
class="label-text-alt text-error {formErrors.max_clients ? '' : (
'hidden'
)}">Maximum 8 clients allowed</span
>
<span class="label-text-alt text-error {formErrors.max_clients ? '' : 'hidden'}"
>Maximum 8 clients allowed</span>
</label>
</div>
@@ -349,22 +292,18 @@
</label>
<input
type="text"
class="input input-bordered w-full {formErrors.local_ip ?
'border-error border-2'
: ''}"
class="input input-bordered w-full {formErrors.local_ip ? 'border-error border-2' : (
''
)}"
minlength="7"
maxlength="15"
size="15"
bind:value={apSettings.local_ip}
id="localIP"
required
/>
required />
<label class="label" for="localIP">
<span
class="label-text-alt text-error {formErrors.local_ip ? '' : (
'hidden'
)}">Must be a valid IPv4 address</span
>
<span class="label-text-alt text-error {formErrors.local_ip ? '' : 'hidden'}"
>Must be a valid IPv4 address</span>
</label>
</div>
@@ -374,22 +313,17 @@
</label>
<input
type="text"
class="input input-bordered w-full {formErrors.gateway_ip ?
'border-error border-2'
class="input input-bordered w-full {formErrors.gateway_ip ? 'border-error border-2'
: ''}"
minlength="7"
maxlength="15"
size="15"
bind:value={apSettings.gateway_ip}
id="gateway"
required
/>
required />
<label class="label" for="gateway">
<span
class="label-text-alt text-error {formErrors.gateway_ip ? '' : (
'hidden'
)}">Must be a valid IPv4 address</span
>
<span class="label-text-alt text-error {formErrors.gateway_ip ? '' : 'hidden'}"
>Must be a valid IPv4 address</span>
</label>
</div>
<div>
@@ -398,22 +332,17 @@
</label>
<input
type="text"
class="input input-bordered w-full {formErrors.subnet_mask ?
'border-error border-2'
class="input input-bordered w-full {formErrors.subnet_mask ? 'border-error border-2'
: ''}"
minlength="7"
maxlength="15"
size="15"
bind:value={apSettings.subnet_mask}
id="subnet"
required
/>
required />
<label class="label" for="subnet">
<span
class="label-text-alt text-error {formErrors.subnet_mask ? '' : (
'hidden'
)}">Must be a valid IPv4 address</span
>
<span class="label-text-alt text-error {formErrors.subnet_mask ? '' : 'hidden'}"
>Must be a valid IPv4 address</span>
</label>
</div>
@@ -421,8 +350,7 @@
<input
type="checkbox"
bind:checked={apSettings.ssid_hidden}
class="checkbox checkbox-primary"
/>
class="checkbox checkbox-primary" />
<span class="">Hide SSID</span>
</label>
+174 -334
View File
@@ -1,19 +1,19 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import { modals } from 'svelte-modals';
import { slide } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { notifications } from '$lib/components/toasts/notifications';
import DragDropList, { VerticalDropZone, reorder, type DropEvent } from 'svelte-dnd-list';
import SettingsCard from '$lib/components/SettingsCard.svelte';
import { PasswordInput } from '$lib/components/input';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import ScanNetworks from './Scan.svelte';
import Spinner from '$lib/components/Spinner.svelte';
import InfoDialog from '$lib/components/InfoDialog.svelte';
import type { KnownNetworkItem, WifiSettings, WifiStatus } from '$lib/types/models';
import { socket, useFeatureFlags } from '$lib/stores';
import { api } from '$lib/api';
import { onMount, onDestroy } from 'svelte'
import { modals } from 'svelte-modals'
import { slide } from 'svelte/transition'
import { cubicOut } from 'svelte/easing'
import { notifications } from '$lib/components/toasts/notifications'
import DragDropList, { VerticalDropZone, reorder, type DropEvent } from 'svelte-dnd-list'
import SettingsCard from '$lib/components/SettingsCard.svelte'
import { PasswordInput } from '$lib/components/input'
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'
import ScanNetworks from './Scan.svelte'
import Spinner from '$lib/components/Spinner.svelte'
import InfoDialog from '$lib/components/InfoDialog.svelte'
import type { KnownNetworkItem, WifiSettings, WifiStatus } from '$lib/types/models'
import { socket } from '$lib/stores'
import { api } from '$lib/api'
import {
Cancel,
Delete,
@@ -32,9 +32,8 @@
Add,
Scan,
Edit
} from '$lib/components/icons';
const features = useFeatureFlags();
} from '$lib/components/icons'
import StatusItem from '$lib/components/StatusItem.svelte'
let networkEditable: KnownNetworkItem = $state({
ssid: '',
@@ -45,21 +44,21 @@
gateway_ip: undefined,
dns_ip_1: undefined,
dns_ip_2: undefined
});
})
let static_ip_config = $state(false);
let static_ip_config = $state(false)
let newNetwork: boolean = $state(true);
let showNetworkEditor: boolean = $state(false);
let newNetwork: boolean = $state(true)
let showNetworkEditor: boolean = $state(false)
let wifiStatus: WifiStatus = $state();
let wifiSettings: WifiSettings = $state();
let wifiStatus: WifiStatus = $state()
let wifiSettings: WifiSettings = $state()
let dndNetworkList: KnownNetworkItem[] = $state([]);
let dndNetworkList: KnownNetworkItem[] = $state([])
let showWifiDetails = $state(false);
let showWifiDetails = $state(false)
let formField: any = $state();
let formField: any = $state()
let formErrors = $state({
ssid: false,
@@ -68,155 +67,155 @@
subnet_mask: false,
dns_1: false,
dns_2: false
});
})
let formErrorhostname = $state(false);
let formErrorhostname = $state(false)
async function getWifiStatus() {
const result = await api.get<WifiStatus>('/api/wifi/sta/status');
const result = await api.get<WifiStatus>('/api/wifi/sta/status')
if (result.isErr()) {
console.error(`Error occurred while fetching: `, result.inner);
return;
console.error(`Error occurred while fetching: `, result.inner)
return
}
wifiStatus = result.inner;
return wifiStatus;
wifiStatus = result.inner
return wifiStatus
}
async function getWifiSettings() {
const result = await api.get<WifiSettings>('/api/wifi/sta/settings');
const result = await api.get<WifiSettings>('/api/wifi/sta/settings')
if (result.isErr()) {
console.error(`Error occurred while fetching: `, result.inner);
return;
console.error(`Error occurred while fetching: `, result.inner)
return
}
wifiSettings = result.inner;
dndNetworkList = wifiSettings.wifi_networks;
return wifiSettings;
wifiSettings = result.inner
dndNetworkList = wifiSettings.wifi_networks
return wifiSettings
}
onDestroy(() => socket.off('WiFiSettings'));
onDestroy(() => socket.off('WiFiSettings'))
onMount(() => {
socket.on<WifiSettings>('WiFiSettings', data => {
wifiSettings = data;
dndNetworkList = wifiSettings.wifi_networks;
});
});
wifiSettings = data
dndNetworkList = wifiSettings.wifi_networks
})
})
async function postWiFiSettings(data: WifiSettings) {
const result = await api.post<WifiSettings>('/api/wifi/sta/settings', data);
const result = await api.post<WifiSettings>('/api/wifi/sta/settings', data)
if (result.isErr()) {
console.error(`Error occurred while fetching: `, result.inner);
notifications.error('User not authorized.', 3000);
return;
console.error(`Error occurred while fetching: `, result.inner)
notifications.error('User not authorized.', 3000)
return
}
wifiSettings = result.inner;
notifications.success('Wi-Fi settings updated.', 3000);
wifiSettings = result.inner
notifications.success('Wi-Fi settings updated.', 3000)
}
function validateHostName() {
if (wifiSettings.hostname.length < 3 || wifiSettings.hostname.length > 32) {
formErrorhostname = true;
formErrorhostname = true
} else {
formErrorhostname = false;
formErrorhostname = false
// Update global wifiSettings object
wifiSettings.wifi_networks = dndNetworkList;
wifiSettings.wifi_networks = dndNetworkList
// Post to REST API
postWiFiSettings(wifiSettings);
console.log(wifiSettings);
postWiFiSettings(wifiSettings)
console.log(wifiSettings)
}
}
function validateWiFiForm(event: SubmitEvent) {
event.preventDefault();
let valid = true;
event.preventDefault()
let valid = true
// Validate SSID
if (networkEditable.ssid.length < 3 || networkEditable.ssid.length > 32) {
valid = false;
formErrors.ssid = true;
valid = false
formErrors.ssid = true
} else {
formErrors.ssid = false;
formErrors.ssid = false
}
networkEditable.static_ip_config = static_ip_config;
networkEditable.static_ip_config = static_ip_config
if (networkEditable.static_ip_config) {
// RegEx for IPv4
const regexExp =
/\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/;
/\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/
// Validate gateway IP
if (!regexExp.test(networkEditable.gateway_ip!)) {
valid = false;
formErrors.gateway_ip = true;
valid = false
formErrors.gateway_ip = true
} else {
formErrors.gateway_ip = false;
formErrors.gateway_ip = false
}
// Validate Subnet Mask
if (!regexExp.test(networkEditable.subnet_mask!)) {
valid = false;
formErrors.subnet_mask = true;
valid = false
formErrors.subnet_mask = true
} else {
formErrors.subnet_mask = false;
formErrors.subnet_mask = false
}
// Validate local IP
if (!regexExp.test(networkEditable.local_ip!)) {
valid = false;
formErrors.local_ip = true;
valid = false
formErrors.local_ip = true
} else {
formErrors.local_ip = false;
formErrors.local_ip = false
}
// Validate DNS 1
if (!regexExp.test(networkEditable.dns_ip_1!)) {
valid = false;
formErrors.dns_1 = true;
valid = false
formErrors.dns_1 = true
} else {
formErrors.dns_1 = false;
formErrors.dns_1 = false
}
// Validate DNS 2
if (!regexExp.test(networkEditable.dns_ip_2!)) {
valid = false;
formErrors.dns_2 = true;
valid = false
formErrors.dns_2 = true
} else {
formErrors.dns_2 = false;
formErrors.dns_2 = false
}
} else {
formErrors.local_ip = false;
formErrors.subnet_mask = false;
formErrors.gateway_ip = false;
formErrors.dns_1 = false;
formErrors.dns_2 = false;
formErrors.local_ip = false
formErrors.subnet_mask = false
formErrors.gateway_ip = false
formErrors.dns_1 = false
formErrors.dns_2 = false
}
// Submit JSON to REST API
if (valid) {
if (newNetwork) {
dndNetworkList.push(networkEditable);
dndNetworkList.push(networkEditable)
} else {
dndNetworkList.splice(dndNetworkList.indexOf(networkEditable), 1, networkEditable);
dndNetworkList.splice(dndNetworkList.indexOf(networkEditable), 1, networkEditable)
}
addNetwork();
dndNetworkList = [...dndNetworkList]; //Trigger reactivity
showNetworkEditor = false;
addNetwork()
dndNetworkList = [...dndNetworkList] //Trigger reactivity
showNetworkEditor = false
}
}
function scanForNetworks() {
modals.open(ScanNetworks, {
storeNetwork: (network: string) => {
addNetwork();
networkEditable.ssid = network;
showNetworkEditor = true;
modals.close();
addNetwork()
networkEditable.ssid = network
showNetworkEditor = true
modals.close()
}
});
})
}
function addNetwork() {
newNetwork = true;
newNetwork = true
networkEditable = {
ssid: '',
password: '',
@@ -226,13 +225,13 @@
gateway_ip: undefined,
dns_ip_1: undefined,
dns_ip_2: undefined
};
}
}
function handleEdit(index: number) {
newNetwork = false;
showNetworkEditor = true;
networkEditable = dndNetworkList[index];
newNetwork = false
showNetworkEditor = true
networkEditable = dndNetworkList[index]
}
function confirmDelete(index: number) {
@@ -246,15 +245,15 @@
onConfirm: () => {
// Check if network is currently been edited and delete as well
if (dndNetworkList[index].ssid === networkEditable.ssid) {
addNetwork();
addNetwork()
}
// Remove network from array
dndNetworkList.splice(index, 1);
dndNetworkList = [...dndNetworkList]; //Trigger reactivity
showNetworkEditor = false;
modals.close();
dndNetworkList.splice(index, 1)
dndNetworkList = [...dndNetworkList] //Trigger reactivity
showNetworkEditor = false
modals.close()
}
});
})
}
function checkNetworkList() {
@@ -265,20 +264,20 @@
'You have reached the maximum number of networks. Please delete one to add another.',
dismiss: { label: 'OK', icon: Check },
onDismiss: () => modals.close()
});
return false;
})
return false
} else {
return true;
return true
}
}
function onDrop({ detail: { from, to } }: CustomEvent<DropEvent>) {
if (!to || from === to) {
return;
return
}
dndNetworkList = reorder(dndNetworkList, from.index, to.index);
console.log(dndNetworkList);
dndNetworkList = reorder(dndNetworkList, from.index, to.index)
console.log(dndNetworkList)
}
</script>
@@ -295,78 +294,32 @@
{: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 {wifiStatus.status === 3 ?
'bg-success'
: 'bg-error'}"
>
<AP
class="h-auto w-full scale-75 {wifiStatus.status === 3 ?
'text-success-content'
: 'text-error-content'}"
/>
</div>
<div>
<div class="font-bold">Status</div>
<div class="text-sm opacity-75">
{wifiStatus.status === 3 ? 'Connected' : 'Inactive'}
</div>
</div>
</div>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<StatusItem
icon={AP}
title="Status"
variant={wifiStatus.status === 3 ? 'success' : 'error'}
description={wifiStatus.status === 3 ? 'Connected' : 'Inactive'} />
{#if wifiStatus.status === 3}
<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">
<SSID class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">SSID</div>
<div class="text-sm opacity-75">
{wifiStatus.ssid}
</div>
</div>
</div>
<StatusItem icon={SSID} title="SSID" description={wifiStatus.ssid} />
<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">
<Home class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">IP Address</div>
<div class="text-sm opacity-75">
{wifiStatus.local_ip}
</div>
</div>
</div>
<StatusItem icon={Home} title="IP Address" description={wifiStatus.local_ip} />
<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">
<WiFi class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">RSSI</div>
<div class="text-sm opacity-75">
{wifiStatus.rssi} dBm
</div>
</div>
<div class="grow"></div>
<StatusItem icon={WiFi} title="RSSI" description={`${wifiStatus.rssi} dBm`}>
<button
class="btn btn-circle btn-ghost btn-sm modal-button"
onclick={() => {
showWifiDetails = !showWifiDetails;
}}
>
showWifiDetails = !showWifiDetails
}}>
<Down
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {(
showWifiDetails
) ?
'rotate-180'
: ''}"
/>
: ''}" />
</button>
</div>
</StatusItem>
{/if}
</div>
@@ -374,67 +327,16 @@
{#if showWifiDetails}
<div
class="flex w-full flex-col space-y-1 pt-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 bg-primary h-auto w-10">
<MAC class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">MAC Address</div>
<div class="text-sm opacity-75">
{wifiStatus.mac_address}
</div>
</div>
</div>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<StatusItem icon={MAC} title="MAC Address" description={wifiStatus.mac_address} />
<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">
<Channel class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Channel</div>
<div class="text-sm opacity-75">
{wifiStatus.channel}
</div>
</div>
</div>
<StatusItem icon={Channel} title="Channel" description={wifiStatus.channel} />
<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">
<Gateway class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Gateway IP</div>
<div class="text-sm opacity-75">
{wifiStatus.gateway_ip}
</div>
</div>
</div>
<StatusItem icon={Gateway} title="Gateway IP" description={wifiStatus.gateway_ip} />
<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">
<Subnet class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">Subnet Mask</div>
<div class="text-sm opacity-75">
{wifiStatus.subnet_mask}
</div>
</div>
</div>
<StatusItem icon={Subnet} title="Subnet Mask" description={wifiStatus.subnet_mask} />
<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">
<DNS class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">DNS</div>
<div class="text-sm opacity-75">
{wifiStatus.dns_ip_1}
</div>
</div>
</div>
<StatusItem icon={DNS} title="DNS" description={wifiStatus.dns_ip_1} />
</div>
{/if}
{/await}
@@ -442,8 +344,7 @@
<div class="bg-base-200 relative grid w-full max-w-2xl self-center overflow-hidden">
<div
class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium"
>
class="min-h-16 flex w-full items-center justify-between space-x-3 p-0 text-xl font-medium">
Saved Networks
</div>
{#await getWifiSettings()}
@@ -454,67 +355,48 @@
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-16"
onclick={() => {
if (checkNetworkList()) {
addNetwork();
showNetworkEditor = true;
addNetwork()
showNetworkEditor = true
}
}}
>
<Add class="h-6 w-6" /></button
>
}}>
<Add class="h-6 w-6" /></button>
<button
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
onclick={() => {
if (checkNetworkList()) {
scanForNetworks();
showNetworkEditor = true;
scanForNetworks()
showNetworkEditor = true
}
}}
>
<Scan class="h-6 w-6" /></button
>
}}>
<Scan class="h-6 w-6" /></button>
<div
class="overflow-x-auto space-y-1"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<DragDropList
id="networks"
type={VerticalDropZone}
itemSize={60}
itemCount={dndNetworkList.length}
on:drop={onDrop}
>
on:drop={onDrop}>
{#snippet children({ index })}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<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 shrink-0">
<Router class="text-primary-content h-auto w-full scale-75" />
</div>
<div>
<div class="font-bold">{dndNetworkList[index].ssid}</div>
</div>
<div class="grow"></div>
<StatusItem icon={Router} title={dndNetworkList[index].ssid}>
<div class="space-x-0 px-0 mx-0">
<button
class="btn btn-ghost btn-sm"
onclick={() => {
handleEdit(index);
}}
>
<Edit class="h-6 w-6" /></button
>
handleEdit(index)
}}>
<Edit class="h-6 w-6" /></button>
<button
class="btn btn-ghost btn-sm"
onclick={() => {
confirmDelete(index);
}}
>
confirmDelete(index)
}}>
<Delete class="text-error h-6 w-6" />
</button>
</div>
</div>
</StatusItem>
{/snippet}
</DragDropList>
</div>
@@ -523,8 +405,7 @@
<div class="divider mb-0"></div>
<div
class="flex flex-col gap-2 p-0"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<form class="" onsubmit={validateWiFiForm} novalidate bind:this={formField}>
<div class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2">
<div>
@@ -542,24 +423,17 @@
: ''}"
bind:value={wifiSettings.hostname}
id="channel"
required
/>
required />
<label class="label" for="channel">
<span
class="label-text-alt text-error {formErrorhostname ? '' : (
'hidden'
)}">Host name must be between 2 and 32 characters long</span
>
<span class="label-text-alt text-error {formErrorhostname ? '' : 'hidden'}"
>Host name must be between 2 and 32 characters long</span>
</label>
</div>
<label
class="label inline-flex cursor-pointer content-end justify-start gap-4"
>
<label class="label inline-flex cursor-pointer content-end justify-start gap-4">
<input
type="checkbox"
bind:checked={wifiSettings.priority_RSSI}
class="checkbox checkbox-primary sm:-mb-5"
/>
class="checkbox checkbox-primary sm:-mb-5" />
<span class="sm:-mb-5">Connect to strongest WiFi</span>
</label>
</div>
@@ -568,8 +442,7 @@
<div class="divider my-0"></div>
<div
class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<div>
<label class="label" for="ssid">
<span class="label-text text-md">SSID</span>
@@ -585,14 +458,10 @@
id="ssid"
min="2"
max="32"
required
/>
required />
<label class="label" for="ssid">
<span
class="label-text-alt text-error {formErrors.ssid ? '' : (
'hidden'
)}">SSID must be between 3 and 32 characters long</span
>
<span class="label-text-alt text-error {formErrors.ssid ? '' : 'hidden'}"
>SSID must be between 3 and 32 characters long</span>
</label>
</div>
<div>
@@ -602,21 +471,18 @@
<PasswordInput bind:value={networkEditable.password} id="pwd" />
</div>
<label
class="label inline-flex cursor-pointer content-end justify-start gap-4 mt-2 sm:mb-4"
>
class="label inline-flex cursor-pointer content-end justify-start gap-4 mt-2 sm:mb-4">
<input
type="checkbox"
bind:checked={static_ip_config}
class="checkbox checkbox-primary sm:-mb-5"
/>
class="checkbox checkbox-primary sm:-mb-5" />
<span class="sm:-mb-5">Static IP Config?</span>
</label>
</div>
{#if static_ip_config}
<div
class="grid w-full grid-cols-1 content-center gap-x-4 px-4 sm:grid-cols-2"
transition:slide|local={{ duration: 300, easing: cubicOut }}
>
transition:slide|local={{ duration: 300, easing: cubicOut }}>
<div>
<label class="label" for="localIP">
<span class="label-text text-md">Local IP</span>
@@ -631,14 +497,10 @@
size="15"
bind:value={networkEditable.local_ip}
id="localIP"
required
/>
required />
<label class="label" for="localIP">
<span
class="label-text-alt text-error {formErrors.local_ip ?
''
: 'hidden'}">Must be a valid IPv4 address</span
>
<span class="label-text-alt text-error {formErrors.local_ip ? '' : 'hidden'}"
>Must be a valid IPv4 address</span>
</label>
</div>
@@ -655,16 +517,10 @@
maxlength="15"
size="15"
bind:value={networkEditable.gateway_ip}
required
/>
required />
<label class="label" for="gateway">
<span
class="label-text-alt text-error {(
formErrors.gateway_ip
) ?
''
: 'hidden'}">Must be a valid IPv4 address</span
>
<span class="label-text-alt text-error {formErrors.gateway_ip ? '' : 'hidden'}"
>Must be a valid IPv4 address</span>
</label>
</div>
<div>
@@ -680,16 +536,10 @@
maxlength="15"
size="15"
bind:value={networkEditable.subnet_mask}
required
/>
required />
<label class="label" for="subnet">
<span
class="label-text-alt text-error {(
formErrors.subnet_mask
) ?
''
: 'hidden'}"
>
class="label-text-alt text-error {formErrors.subnet_mask ? '' : 'hidden'}">
Must be a valid IPv4 address
</span>
</label>
@@ -700,20 +550,15 @@
</label>
<input
type="text"
class="input input-bordered w-full {formErrors.dns_1 ?
'border-error border-2'
class="input input-bordered w-full {formErrors.dns_1 ? 'border-error border-2'
: ''}"
minlength="7"
maxlength="15"
size="15"
bind:value={networkEditable.dns_ip_1}
required
/>
required />
<label class="label" for="gateway">
<span
class="label-text-alt text-error {formErrors.dns_1 ? ''
: 'hidden'}"
>
<span class="label-text-alt text-error {formErrors.dns_1 ? '' : 'hidden'}">
Must be a valid IPv4 address
</span>
</label>
@@ -724,20 +569,15 @@
</label>
<input
type="text"
class="input input-bordered w-full {formErrors.dns_2 ?
'border-error border-2'
class="input input-bordered w-full {formErrors.dns_2 ? 'border-error border-2'
: ''}"
minlength="7"
maxlength="15"
size="15"
bind:value={networkEditable.dns_ip_2}
required
/>
required />
<label class="label" for="subnet">
<span
class="label-text-alt text-error {formErrors.dns_2 ? ''
: 'hidden'}"
>
<span class="label-text-alt text-error {formErrors.dns_2 ? '' : 'hidden'}">
Must be a valid IPv4 address
</span>
</label>