783 lines
34 KiB
Svelte
783 lines
34 KiB
Svelte
<svelte:options immutable={true} />
|
|
|
|
<script lang="ts">
|
|
import { onMount, onDestroy } from 'svelte';
|
|
import { openModal, closeModal } from 'svelte-modals';
|
|
import { slide } from 'svelte/transition';
|
|
import { cubicOut } from 'svelte/easing';
|
|
import { user } from '$lib/stores/user';
|
|
import { page } from '$app/stores';
|
|
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 {
|
|
Cancel,
|
|
Delete,
|
|
Check,
|
|
Router,
|
|
AP,
|
|
SSID,
|
|
Home,
|
|
WiFi,
|
|
Down,
|
|
MAC,
|
|
Channel,
|
|
Gateway,
|
|
Subnet,
|
|
DNS,
|
|
Add,
|
|
Scan,
|
|
Edit
|
|
} from '$lib/components/icons';
|
|
|
|
const features = useFeatureFlags();
|
|
|
|
let networkEditable: KnownNetworkItem = {
|
|
ssid: '',
|
|
password: '',
|
|
static_ip_config: false,
|
|
local_ip: undefined,
|
|
subnet_mask: undefined,
|
|
gateway_ip: undefined,
|
|
dns_ip_1: undefined,
|
|
dns_ip_2: undefined
|
|
};
|
|
|
|
let static_ip_config = false;
|
|
|
|
let newNetwork: boolean = true;
|
|
let showNetworkEditor: boolean = false;
|
|
|
|
let wifiStatus: WifiStatus;
|
|
let wifiSettings: WifiSettings;
|
|
|
|
let dndNetworkList: KnownNetworkItem[] = [];
|
|
|
|
let showWifiDetails = false;
|
|
|
|
let formField: any;
|
|
|
|
let formErrors = {
|
|
ssid: false,
|
|
local_ip: false,
|
|
gateway_ip: false,
|
|
subnet_mask: false,
|
|
dns_1: false,
|
|
dns_2: false
|
|
};
|
|
|
|
let formErrorhostname = false;
|
|
|
|
async function getWifiStatus() {
|
|
const result = await api.get<WifiStatus>('/api/wifi/status');
|
|
if (result.isErr()) {
|
|
console.error(`Error occurred while fetching: `, result.inner);
|
|
return;
|
|
}
|
|
wifiStatus = result.inner;
|
|
return wifiStatus;
|
|
}
|
|
|
|
async function getWifiSettings() {
|
|
const result = await api.get<WifiSettings>('/api/wifi/settings');
|
|
if (result.isErr()) {
|
|
console.error(`Error occurred while fetching: `, result.inner);
|
|
return;
|
|
}
|
|
wifiSettings = result.inner;
|
|
dndNetworkList = wifiSettings.wifi_networks;
|
|
return wifiSettings;
|
|
}
|
|
|
|
onDestroy(() => socket.off('WiFiSettings'));
|
|
|
|
onMount(() => {
|
|
socket.on<WifiSettings>('WiFiSettings', data => {
|
|
wifiSettings = data;
|
|
dndNetworkList = wifiSettings.wifi_networks;
|
|
});
|
|
});
|
|
|
|
async function postWiFiSettings(data: WifiSettings) {
|
|
const result = await api.post<WifiSettings>('/api/wifi/settings', data);
|
|
if (result.isErr()) {
|
|
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);
|
|
}
|
|
|
|
function validateHostName() {
|
|
if (wifiSettings.hostname.length < 3 || wifiSettings.hostname.length > 32) {
|
|
formErrorhostname = true;
|
|
} else {
|
|
formErrorhostname = false;
|
|
// Update global wifiSettings object
|
|
wifiSettings.wifi_networks = dndNetworkList;
|
|
// Post to REST API
|
|
postWiFiSettings(wifiSettings);
|
|
console.log(wifiSettings);
|
|
}
|
|
}
|
|
|
|
function validateWiFiForm() {
|
|
let valid = true;
|
|
|
|
// Validate SSID
|
|
if (networkEditable.ssid.length < 3 || networkEditable.ssid.length > 32) {
|
|
valid = false;
|
|
formErrors.ssid = true;
|
|
} else {
|
|
formErrors.ssid = false;
|
|
}
|
|
|
|
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/;
|
|
|
|
// Validate gateway IP
|
|
if (!regexExp.test(networkEditable.gateway_ip!)) {
|
|
valid = false;
|
|
formErrors.gateway_ip = true;
|
|
} else {
|
|
formErrors.gateway_ip = false;
|
|
}
|
|
|
|
// Validate Subnet Mask
|
|
if (!regexExp.test(networkEditable.subnet_mask!)) {
|
|
valid = false;
|
|
formErrors.subnet_mask = true;
|
|
} else {
|
|
formErrors.subnet_mask = false;
|
|
}
|
|
|
|
// Validate local IP
|
|
if (!regexExp.test(networkEditable.local_ip!)) {
|
|
valid = false;
|
|
formErrors.local_ip = true;
|
|
} else {
|
|
formErrors.local_ip = false;
|
|
}
|
|
|
|
// Validate DNS 1
|
|
if (!regexExp.test(networkEditable.dns_ip_1!)) {
|
|
valid = false;
|
|
formErrors.dns_1 = true;
|
|
} else {
|
|
formErrors.dns_1 = false;
|
|
}
|
|
|
|
// Validate DNS 2
|
|
if (!regexExp.test(networkEditable.dns_ip_2!)) {
|
|
valid = false;
|
|
formErrors.dns_2 = true;
|
|
} else {
|
|
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;
|
|
}
|
|
// Submit JSON to REST API
|
|
if (valid) {
|
|
if (newNetwork) {
|
|
dndNetworkList.push(networkEditable);
|
|
} else {
|
|
dndNetworkList.splice(dndNetworkList.indexOf(networkEditable), 1, networkEditable);
|
|
}
|
|
addNetwork();
|
|
dndNetworkList = [...dndNetworkList]; //Trigger reactivity
|
|
showNetworkEditor = false;
|
|
}
|
|
}
|
|
|
|
function scanForNetworks() {
|
|
openModal(ScanNetworks, {
|
|
storeNetwork: (network: string) => {
|
|
addNetwork();
|
|
networkEditable.ssid = network;
|
|
showNetworkEditor = true;
|
|
closeModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
function addNetwork() {
|
|
newNetwork = true;
|
|
networkEditable = {
|
|
ssid: '',
|
|
password: '',
|
|
static_ip_config: false,
|
|
local_ip: undefined,
|
|
subnet_mask: undefined,
|
|
gateway_ip: undefined,
|
|
dns_ip_1: undefined,
|
|
dns_ip_2: undefined
|
|
};
|
|
}
|
|
|
|
function handleEdit(index: number) {
|
|
newNetwork = false;
|
|
showNetworkEditor = true;
|
|
networkEditable = dndNetworkList[index];
|
|
}
|
|
|
|
function confirmDelete(index: number) {
|
|
openModal(ConfirmDialog, {
|
|
title: 'Delete Network',
|
|
message: 'Are you sure you want to delete this network?',
|
|
labels: {
|
|
cancel: { label: 'Cancel', icon: Cancel },
|
|
confirm: { label: 'Delete', icon: Delete }
|
|
},
|
|
onConfirm: () => {
|
|
// Check if network is currently been edited and delete as well
|
|
if (dndNetworkList[index].ssid === networkEditable.ssid) {
|
|
addNetwork();
|
|
}
|
|
// Remove network from array
|
|
dndNetworkList.splice(index, 1);
|
|
dndNetworkList = [...dndNetworkList]; //Trigger reactivity
|
|
showNetworkEditor = false;
|
|
closeModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
function checkNetworkList() {
|
|
if (dndNetworkList.length >= 5) {
|
|
openModal(InfoDialog, {
|
|
title: 'Reached Maximum Networks',
|
|
message:
|
|
'You have reached the maximum number of networks. Please delete one to add another.',
|
|
dismiss: { label: 'OK', icon: Check },
|
|
onDismiss: () => {
|
|
closeModal();
|
|
}
|
|
});
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function onDrop({ detail: { from, to } }: CustomEvent<DropEvent>) {
|
|
if (!to || from === to) {
|
|
return;
|
|
}
|
|
|
|
dndNetworkList = reorder(dndNetworkList, from.index, to.index);
|
|
console.log(dndNetworkList);
|
|
}
|
|
</script>
|
|
|
|
<SettingsCard collapsible={false}>
|
|
<Router slot="icon" class="lex-shrink-0 mr-2 h-6 w-6 self-end" />
|
|
<span slot="title">WiFi Connection</span>
|
|
<div class="w-full overflow-x-auto">
|
|
{#await getWifiStatus()}
|
|
<Spinner />
|
|
{:then nothing}
|
|
<div
|
|
class="flex w-full flex-col space-y-1"
|
|
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
|
>
|
|
<div class="rounded-box bg-base-100 flex items-center space-x-3 px-4 py-2">
|
|
<div
|
|
class="mask mask-hexagon h-auto w-10 {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>
|
|
{#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>
|
|
|
|
<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>
|
|
|
|
<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" />
|
|
<button
|
|
class="btn btn-circle btn-ghost btn-sm modal-button"
|
|
on:click={() => {
|
|
showWifiDetails = !showWifiDetails;
|
|
}}
|
|
>
|
|
<Down
|
|
class="text-base-content h-auto w-6 transition-transform duration-300 ease-in-out {(
|
|
showWifiDetails
|
|
) ?
|
|
'rotate-180'
|
|
: ''}"
|
|
/>
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Folds open -->
|
|
{#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>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
</div>
|
|
{/if}
|
|
{/await}
|
|
</div>
|
|
|
|
{#if !$features.security || $user.admin}
|
|
<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"
|
|
>
|
|
Saved Networks
|
|
</div>
|
|
{#await getWifiSettings()}
|
|
<Spinner />
|
|
{:then nothing}
|
|
<div class="relative w-full overflow-visible">
|
|
<button
|
|
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-16"
|
|
on:click={() => {
|
|
if (checkNetworkList()) {
|
|
addNetwork();
|
|
showNetworkEditor = true;
|
|
}
|
|
}}
|
|
>
|
|
<Add class="h-6 w-6" /></button
|
|
>
|
|
<button
|
|
class="btn btn-primary text-primary-content btn-md absolute -top-14 right-0"
|
|
on:click={() => {
|
|
if (checkNetworkList()) {
|
|
scanForNetworks();
|
|
showNetworkEditor = true;
|
|
}
|
|
}}
|
|
>
|
|
<Scan class="h-6 w-6" /></button
|
|
>
|
|
|
|
<div
|
|
class="overflow-x-auto space-y-1"
|
|
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
|
>
|
|
<DragDropList
|
|
id="networks"
|
|
type={VerticalDropZone}
|
|
itemSize={60}
|
|
itemCount={dndNetworkList.length}
|
|
on:drop={onDrop}
|
|
let: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>
|
|
{#if !$features.security || $user.admin}
|
|
<div class="flex-grow" />
|
|
<div class="space-x-0 px-0 mx-0">
|
|
<button
|
|
class="btn btn-ghost btn-sm"
|
|
on:click={() => {
|
|
handleEdit(index);
|
|
}}
|
|
>
|
|
<Edit class="h-6 w-6" /></button
|
|
>
|
|
<button
|
|
class="btn btn-ghost btn-sm"
|
|
on:click={() => {
|
|
confirmDelete(index);
|
|
}}
|
|
>
|
|
<Delete class="text-error h-6 w-6" />
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</DragDropList>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider mb-0" />
|
|
<div
|
|
class="flex flex-col gap-2 p-0"
|
|
transition:slide|local={{ duration: 300, easing: cubicOut }}
|
|
>
|
|
<form
|
|
class=""
|
|
on:submit|preventDefault={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>
|
|
<label class="label" for="channel">
|
|
<span class="label-text text-md">Host Name</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
min="1"
|
|
max="32"
|
|
class="input input-bordered invalid:border-error w-full invalid:border-2 {(
|
|
formErrorhostname
|
|
) ?
|
|
'border-error border-2'
|
|
: ''}"
|
|
bind:value={wifiSettings.hostname}
|
|
id="channel"
|
|
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
|
|
>
|
|
</label>
|
|
</div>
|
|
<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"
|
|
/>
|
|
<span class="sm:-mb-5">Connect to strongest WiFi</span>
|
|
</label>
|
|
</div>
|
|
|
|
{#if showNetworkEditor}
|
|
<div class="divider my-0" />
|
|
<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 }}
|
|
>
|
|
<div>
|
|
<label class="label" for="ssid">
|
|
<span class="label-text text-md">SSID</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="input input-bordered invalid:border-error w-full invalid:border-2 {(
|
|
formErrors.ssid
|
|
) ?
|
|
'border-error border-2'
|
|
: ''}"
|
|
bind:value={networkEditable.ssid}
|
|
id="ssid"
|
|
min="2"
|
|
max="32"
|
|
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
|
|
>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="label" for="pwd">
|
|
<span class="label-text text-md">Password</span>
|
|
</label>
|
|
<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"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
bind:checked={static_ip_config}
|
|
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 }}
|
|
>
|
|
<div>
|
|
<label class="label" for="localIP">
|
|
<span class="label-text text-md">Local IP</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="input input-bordered w-full {(
|
|
formErrors.local_ip
|
|
) ?
|
|
'border-error border-2'
|
|
: ''}"
|
|
minlength="7"
|
|
maxlength="15"
|
|
size="15"
|
|
bind:value={networkEditable.local_ip}
|
|
id="localIP"
|
|
required
|
|
/>
|
|
<label class="label" for="localIP">
|
|
<span
|
|
class="label-text-alt text-error {(
|
|
formErrors.local_ip
|
|
) ?
|
|
''
|
|
: 'hidden'}">Must be a valid IPv4 address</span
|
|
>
|
|
</label>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="label" for="gateway">
|
|
<span class="label-text text-md">Gateway IP</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="input input-bordered w-full {(
|
|
formErrors.gateway_ip
|
|
) ?
|
|
'border-error border-2'
|
|
: ''}"
|
|
minlength="7"
|
|
maxlength="15"
|
|
size="15"
|
|
bind:value={networkEditable.gateway_ip}
|
|
required
|
|
/>
|
|
<label class="label" for="gateway">
|
|
<span
|
|
class="label-text-alt text-error {(
|
|
formErrors.gateway_ip
|
|
) ?
|
|
''
|
|
: 'hidden'}">Must be a valid IPv4 address</span
|
|
>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="label" for="subnet">
|
|
<span class="label-text text-md">Subnet Mask</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
class="input input-bordered w-full {(
|
|
formErrors.subnet_mask
|
|
) ?
|
|
'border-error border-2'
|
|
: ''}"
|
|
minlength="7"
|
|
maxlength="15"
|
|
size="15"
|
|
bind:value={networkEditable.subnet_mask}
|
|
required
|
|
/>
|
|
<label class="label" for="subnet">
|
|
<span
|
|
class="label-text-alt text-error {(
|
|
formErrors.subnet_mask
|
|
) ?
|
|
''
|
|
: 'hidden'}">Must be a valid IPv4 address</span
|
|
>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="label" for="gateway">
|
|
<span class="label-text text-md">DNS 1</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
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
|
|
/>
|
|
<label class="label" for="gateway">
|
|
<span
|
|
class="label-text-alt text-error {formErrors.dns_1 ?
|
|
''
|
|
: 'hidden'}">Must be a valid IPv4 address</span
|
|
>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="label" for="subnet">
|
|
<span class="label-text text-md">DNS 2</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
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
|
|
/>
|
|
<label class="label" for="subnet">
|
|
<span
|
|
class="label-text-alt text-error {formErrors.dns_2 ?
|
|
''
|
|
: 'hidden'}">Must be a valid IPv4 address</span
|
|
>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
|
|
<div class="divider mb-2 mt-0" />
|
|
<div class="mx-4 flex flex-wrap justify-end gap-2">
|
|
<button
|
|
class="btn btn-primary"
|
|
type="submit"
|
|
disabled={!showNetworkEditor}
|
|
>{newNetwork ? 'Add Network' : 'Update Network'}</button
|
|
>
|
|
<button
|
|
class="btn btn-primary"
|
|
type="button"
|
|
on:click={validateHostName}>Apply Settings</button
|
|
>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{/await}
|
|
</div>
|
|
{/if}
|
|
</SettingsCard>
|