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