reuse device form, simplify realtime updates

This commit is contained in:
seriousm4x
2023-08-05 12:34:29 +02:00
parent 5b112d81ad
commit 18046b6db6
8 changed files with 1846 additions and 856 deletions

1849
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,8 @@
<div class="card bg-base-300 shadow-md rounded-3xl" transition:scale={{ delay: 0, duration: 200 }}>
<div class="card-body p-6">
{#if device.link !== ''}
<a href={device.link} target="_blank">
{#if device.link.toString() !== ''}
<a href={device.link.toString()} target="_blank">
<h1 class="card-title link">{device.name}</h1>
</a>
{:else}
@@ -21,7 +21,7 @@
<DeviceCardNic {device} />
</ul>
<div class="card-actions">
<span class="tooltip" data-tip="Last status change">
<span class="tooltip" data-tip="Last status change: {device.updated}">
{formatDistanceToNow(parseISO(device.updated), {
includeSeconds: true,
addSuffix: true

View File

@@ -0,0 +1,402 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { pocketbase } from '$lib/stores/pocketbase';
import DeviceFormPort from '$lib/components/DeviceFormPort.svelte';
import Alert from '$lib/components/Alert.svelte';
import { faSave, faTrash, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import type { Device, Port } from '$lib/types/device';
import Fa from 'svelte-fa';
export let device: Device;
let portErrMsg = '';
let portErrTimeout: number;
let saveErrMsg = '';
let saveErrTimeout: number;
let deleteModal: HTMLDialogElement;
async function save() {
// create/update all ports
let portIds: string[] = [];
await Promise.all(
device.expand.ports.map(async (port) => {
if (port.id === undefined) {
const data = await createPort(port as Port);
if (data === undefined) return;
portIds = [...portIds, data.id];
} else {
const data = await updatePort(port as Port);
if (data === undefined) return;
portIds = [...portIds, data.id];
}
})
);
device.ports = portIds;
device.id ? updateDevice(device) : createDevice(device);
}
async function updateDevice(device: Device) {
$pocketbase
.collection('devices')
.update(device.id, device)
.then(() => {
goto('/');
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
});
}
async function createDevice(device: Device) {
$pocketbase
.collection('devices')
.create(device)
.then(() => {
goto('/');
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
});
}
async function createPort(port: Port): Promise<Port | undefined> {
let newport: Port | undefined;
await $pocketbase
.collection('ports')
.create(port)
.then((data) => {
newport = data as Port;
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
newport = undefined;
});
return newport;
}
async function updatePort(port: Port): Promise<Port | undefined> {
let newport: Port | undefined;
await $pocketbase
.collection('ports')
.update(port.id, port)
.then((data) => {
newport = data as Port;
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
newport = undefined;
});
return newport;
}
function deleteDevice() {
$pocketbase
.collection('devices')
.delete(device.id)
.then(() => {
goto('/');
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
});
}
function createEmptyPort() {
if (device === undefined) return;
device.expand.ports = [...device.expand.ports, { name: '', number: 1 } as Port];
}
</script>
<form on:submit|preventDefault={save}>
<div class="card w-full bg-base-300 shadow-xl">
<div class="card-body">
<h2 class="card-title">General</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
<div class="form-control w-full max-w-xs">
<label class="label" for="device-name">
<div class="label-text">
<span>Name</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-name"
type="text"
placeholder="Device name"
class="input w-full max-w-xs"
bind:value={device.name}
required
/>
</div>
<div class="form-control w-full max-w-xs">
<label class="label" for="device-ip">
<div class="label-text">
<span>IP</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-ip"
type="text"
placeholder="192.168.0.5"
class="input w-full max-w-xs"
bind:value={device.ip}
required
/>
</div>
<div class="form-control w-full max-w-xs">
<label class="label" for="device-mac">
<div class="label-text">
<span>Mac</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-mac"
type="text"
placeholder="aa:bb:cc:dd:ee:ff"
class="input w-full max-w-xs"
bind:value={device.mac}
required
/>
</div>
<div class="form-control w-full max-w-xs">
<label class="label" for="device-netmask">
<div class="label-text">
<span>Netmask</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-netmask"
type="text"
placeholder="255.255.255.0"
class="input w-full max-w-xs"
bind:value={device.netmask}
required
/>
</div>
<span class="text-error me-4 self-center">* required field</span>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Ports</h2>
<p class="my-2">UpSnap can also check if given ports are open. You can define them below.</p>
<div class="form-control w-full">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{#each device.expand.ports as _, index}
<DeviceFormPort bind:deviceClone={device} {index} bind:portErrMsg bind:portErrTimeout />
{/each}
</div>
{#if portErrMsg !== ''}
<Alert
color="error"
icon={faTriangleExclamation}
message={portErrMsg}
customClasses="mt-4 max-w-fit"
/>
{/if}
<button
class="btn btn-wide btn-neutral mt-4"
on:click={() => createEmptyPort()}
type="button">Add new port</button
>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Link</h2>
<p class="my-2">
Makes your device name a clickable link, perfect for linking a dashboard for example.
</p>
<div class="form-control w-full">
<input
type="url"
placeholder="https:// ..."
class="input w-full max-w-xs"
bind:value={device.link}
/>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Wake</h2>
<p class="my-2">
You can power this device using a scheduled cron job. If you are not familiar with cron, you
can read about it <a class="link" href="https://en.wikipedia.org/wiki/Cron" target="_blank"
>here</a
>
or refer to the
<a class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3" target="_blank"
>package documentation</a
>
for more information.
</p>
<div class="form-control flex flex-row flex-wrap gap-4">
<div>
<label class="label" for="wake-cron">
<span class="label-text">Wake cron</span>
</label>
<input
id="wake-cron"
type="text"
placeholder="M H DoM M DoW"
class="input w-80"
bind:value={device.wake_cron}
disabled={!device.wake_cron_enabled}
/>
</div>
<div class="flex flex-col">
<label class="label cursor-pointer" for="wake-cron-enable">
<span class="label-text">Enable wake cron</span>
</label>
<input
id="wake-cron-enable"
type="checkbox"
class="toggle toggle-success"
bind:checked={device.wake_cron_enabled}
/>
</div>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Shutdown</h2>
<p class="my-2">
This <strong>shell command</strong> will run inside your container (if you use docker) or on
your host (if you use the binary). To verify that works you can run the command inside the
container or on your host shell first. Common commands are
<span class="badge">net rpc</span> for windows, <span class="badge">sshpass</span> for linux
or
<span class="badge">curl</span> in general to make web requests.
</p>
<p class="my-2 font-bold">Examples:</p>
<div class="alert text-sm bg-base-100 text-start w-fit">
<div>
<span class="font-bold">Shutdown remote windows machine:</span>
<pre
class="break-words whitespace-pre-wrap">net rpc shutdown -I 192.168.1.13 -U "user%password"</pre>
</div>
</div>
<div class="alert text-sm my-2 bg-base-100 text-start w-fit">
<div>
<span class="font-bold">Shutdown remote linux machine:</span>
<pre
class="break-words whitespace-pre-wrap">sshpass -p your_password ssh -o "StrictHostKeyChecking=no" user@hostname "sudo poweroff"</pre>
</div>
</div>
<div class="form-control w-full">
<label class="label cursor-pointer" for="shutdown-cmd">
<span class="label-text">Shutdown command</span>
</label>
<input
id="shutdown-cmd"
type="text"
placeholder="$:"
class="input w-full max-w-xs mb-2"
bind:value={device.shutdown_cmd}
/>
</div>
<p class="my-2">
Just like setting a cron to wake the device, you can also schedule a cron job to shut down
this device.
</p>
<div class="form-control flex flex-row flex-wrap gap-4">
<div>
<label class="label" for="shutdown-cron">
<span class="label-text">Shutdown cron</span>
</label>
<input
id="shutdown-cron"
type="text"
placeholder="M H DoM M DoW"
class="input w-80"
bind:value={device.shutdown_cron}
disabled={!device.shutdown_cron_enabled}
/>
</div>
<div class="flex flex-col">
<label class="label cursor-pointer" for="shutdown-cron-enable">
<span class="label-text">Enable shutdown cron</span>
</label>
<input
id="shutdown-cron-enable"
type="checkbox"
class="toggle toggle-success"
bind:checked={device.shutdown_cron_enabled}
/>
</div>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Password</h2>
<p class="my-2">
Some network cards have the option to set a password for magic packets, also called <span
class="badge">SecureON</span
>. Password can only be 0, 4 or 6 characters in length.
</p>
<div class="form-control w-full">
<input
type="password"
placeholder="123456"
class="input w-full max-w-xs"
maxlength="6"
bind:value={device.password}
/>
</div>
{#if saveErrMsg !== ''}
<Alert
color="error"
message={saveErrMsg}
icon={faTriangleExclamation}
customClasses="mt-4 max-w-fit"
/>
{/if}
</div>
</div>
<div class="card-actions mt-4 justify-end gap-4">
{#if device.id}
<button class="btn btn-error" type="button" on:click={() => deleteModal.showModal()}
><Fa icon={faTrash} />Delete</button
>
<dialog id="my_modal_1" class="modal" bind:this={deleteModal}>
<form method="dialog" class="modal-box">
<h3 class="font-bold text-lg">Confirm delete</h3>
<p class="py-4">Are you sure you want to delete <strong>{device.name}</strong>?</p>
<div class="modal-action">
<button class="btn">Cancle</button>
<button class="btn btn-error" on:click={() => deleteDevice()}>Delete</button>
</div>
</form>
</dialog>
{/if}
<button class="btn btn-success" type="submit"><Fa icon={faSave} />Save</button>
</div>
</form>

View File

@@ -4,7 +4,7 @@
import { pocketbase, backendUrl } from '$lib/stores/pocketbase';
import { settingsPub } from '$lib/stores/settings';
import Fa from 'svelte-fa';
import { faCog, faHome, faCircleQuestion, faKey } from '@fortawesome/free-solid-svg-icons';
import { faCog, faHome, faPlus, faKey } from '@fortawesome/free-solid-svg-icons';
$: avatar = $pocketbase.authStore?.model?.avatar
? $pocketbase.authStore?.model.avatar
@@ -65,7 +65,7 @@
/>{$settingsPub?.website_title ? $settingsPub?.website_title : ''}
</a>
</div>
<div class="navbar-center hidden md:flex">
<div class="hidden md:flex">
<ul class="menu menu-horizontal px-1">
<li><a href="/" class:active={$page.url.pathname === '/'}><Fa icon={faHome} />Home</a></li>
{#if $pocketbase.authStore.model?.collectionName !== 'users'}
@@ -84,6 +84,10 @@
</div>
<div class="justify-end ms-auto">
{#if $pocketbase.authStore?.model !== null}
<a class="btn btn-sm btn-success me-4" href="/device/new">
<Fa icon={faPlus} />
New
</a>
<div class="dropdown dropdown-end">
<label tabindex="-1" class="btn btn-ghost btn-circle avatar" for="avatar">
<div class="w-10 rounded-full" id="avatar">

View File

@@ -7,7 +7,6 @@
import { settingsPub } from '$lib/stores/settings';
import Navbar from '$lib/components/Navbar.svelte';
import Transition from '$lib/components/Transition.svelte';
import type { Device } from '$lib/types/device';
import type { SettingsPublic } from '$lib/types/settings';
onMount(async () => {
@@ -41,14 +40,6 @@
} else {
$pocketbase.admins.authRefresh();
}
// fill devices store
$pocketbase
.collection('devices')
.getFullList(1000, { sort: 'name', expand: 'ports,groups' })
.then((data) => {
devices.set(data as Device[]);
});
});
</script>

View File

@@ -2,45 +2,28 @@
import DeviceCard from '$lib/components/DeviceCard.svelte';
import { pocketbase, devices } from '$lib/stores/pocketbase';
import { onMount } from 'svelte';
import type { Device, Port } from '$lib/types/device';
import Fa from 'svelte-fa';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import type { Device } from '$lib/types/device';
onMount(() => {
$pocketbase.collection('devices').subscribe('*', (e) => {
const index = $devices.findIndex((device) => device.id === e.record.id);
if (e.action === 'create') {
$devices = [...$devices, e.record as Device];
} else if (e.action === 'update') {
// get expanded values before updating the store
$pocketbase
.collection('devices')
.getOne(e.record.id, {
expand: 'ports,groups'
})
.then((data) => {
$devices[index] = data as Device;
});
} else if (e.action === 'delete') {
$devices = $devices.filter((d) => d !== $devices[index]);
}
onMount(async () => {
await getAllDevices();
$pocketbase.collection('devices').subscribe('*', async () => {
await getAllDevices();
});
$pocketbase.collection('ports').subscribe('*', (e) => {
if (e.action === 'update') {
// replace port in device store by finding both indexes
let portIndex = -1;
const deviceIndex = $devices.findIndex((device) =>
device.expand.ports.map((port) => {
if (port.id === e.record.id) {
portIndex = device.expand.ports.indexOf(port);
}
})
);
if (deviceIndex !== -1 && portIndex !== -1) {
$devices[deviceIndex].expand.ports[portIndex] = e.record as Port;
}
}
$pocketbase.collection('ports').subscribe('*', async () => {
await getAllDevices();
});
});
async function getAllDevices() {
const resp = await $pocketbase
.collection('devices')
.getFullList(1000, { sort: 'name', expand: 'ports,groups' });
devices.set(resp as Device[]);
}
</script>
{#if $devices.length > 0}
@@ -52,5 +35,12 @@
{/each}
</div>
{:else}
<div class="container mx-auto text-center">No devices</div>
<div class="container text-center">
<p>You have not created any devices.</p>
<p>
<a href="/device/new" class="btn btn-ghost"
><Fa icon={faPlus} class="ms-2" />Add your first device
</a>
</p>
</div>
{/if}

View File

@@ -1,22 +1,15 @@
<script lang="ts">
import { page } from '$app/stores';
import { pocketbase, devices } from '$lib/stores/pocketbase';
import { devices } from '$lib/stores/pocketbase';
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import Alert from '$lib/components/Alert.svelte';
import DeviceFormPort from '$lib/components/DeviceFormPort.svelte';
import type { Device, Port } from '$lib/types/device';
import { goto } from '$app/navigation';
import DeviceForm from '$lib/components/DeviceForm.svelte';
import type { Device } from '$lib/types/device';
let waitingForDevices = true;
let device: Device | undefined;
let deviceClone: Device | undefined;
let portErrMsg = '';
let portErrTimeout: number;
let saveErrMsg = '';
let saveErrTimeout: number;
devices.subscribe((data) => {
if (waitingForDevices === false) return;
if (data.length === 0) return;
@@ -30,84 +23,6 @@
}
waitingForDevices = false;
});
async function saveDevice() {
if (deviceClone === undefined) return;
// create/update all ports
let portIds: string[] = [];
await Promise.all(
deviceClone.expand.ports.map(async (port) => {
if (port.id === undefined) {
const data = await createPort(port as Port);
if (data === undefined) return;
portIds = [...portIds, data.id];
} else {
const data = await updatePort(port as Port);
if (data === undefined) return;
portIds = [...portIds, data.id];
}
})
);
deviceClone.ports = portIds;
$pocketbase
.collection('devices')
.update(deviceClone.id, deviceClone)
.then(() => {
goto('/');
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
});
}
async function createPort(port: Port): Promise<Port | undefined> {
let newport: Port | undefined;
await $pocketbase
.collection('ports')
.create(port)
.then((data) => {
newport = data as Port;
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
newport = undefined;
});
return newport;
}
async function updatePort(port: Port): Promise<Port | undefined> {
let newport: Port | undefined;
await $pocketbase
.collection('ports')
.update(port.id, port)
.then((data) => {
newport = data as Port;
})
.catch((err) => {
clearTimeout(saveErrTimeout);
saveErrTimeout = setTimeout(() => {
saveErrMsg = '';
}, 10000);
saveErrMsg = err;
newport = undefined;
});
return newport;
}
function createEmptyPort() {
if (deviceClone === undefined) return;
deviceClone.expand.ports = [...deviceClone.expand.ports, { name: '', number: 1 } as Port];
}
</script>
{#if waitingForDevices}
@@ -124,267 +39,5 @@
</div>
{:else}
<h1 class="text-3xl font-bold mb-8">{device.name}</h1>
<form on:submit|preventDefault={saveDevice}>
<div class="card w-full bg-base-300 shadow-xl">
<div class="card-body">
<h2 class="card-title">General</h2>
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-4">
<div class="form-control w-full max-w-xs">
<label class="label" for="device-name">
<div class="label-text">
<span>Name</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-name"
type="text"
placeholder="Device name"
class="input w-full max-w-xs"
bind:value={deviceClone.name}
required
/>
</div>
<div class="form-control w-full max-w-xs">
<label class="label" for="device-ip">
<div class="label-text">
<span>IP</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-ip"
type="text"
placeholder="192.168.0.5"
class="input w-full max-w-xs"
bind:value={deviceClone.ip}
required
/>
</div>
<div class="form-control w-full max-w-xs">
<label class="label" for="device-mac">
<div class="label-text">
<span>Mac</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-mac"
type="text"
placeholder="aa:bb:cc:dd:ee:ff"
class="input w-full max-w-xs"
bind:value={deviceClone.mac}
required
/>
</div>
<div class="form-control w-full max-w-xs">
<label class="label" for="device-netmask">
<div class="label-text">
<span>Netmask</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="device-netmask"
type="text"
placeholder="255.255.255.0"
class="input w-full max-w-xs"
bind:value={deviceClone.netmask}
required
/>
</div>
<span class="text-error me-4 self-center">* required field</span>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Ports</h2>
<p class="my-2">
UpSnap can also check if given ports are open. You can define them below.
</p>
<div class="form-control w-full">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{#each deviceClone.expand.ports as _, index}
<DeviceFormPort bind:deviceClone {index} bind:portErrMsg bind:portErrTimeout />
{/each}
</div>
{#if portErrMsg !== ''}
<Alert
color="error"
icon={faTriangleExclamation}
message={portErrMsg}
customClasses="mt-4 max-w-fit"
/>
{/if}
<button
class="btn btn-wide btn-neutral mt-4"
on:click={() => createEmptyPort()}
type="button">Add new port</button
>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Link</h2>
<p class="my-2">
Makes your device name a clickable link, perfect for linking a dashboard for example.
</p>
<div class="form-control w-full">
<input
type="url"
placeholder="https:// ..."
class="input w-full max-w-xs"
bind:value={deviceClone.link}
/>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Wake</h2>
<p class="my-2">
You can power this device using a scheduled cron job. If you are not familiar with cron,
you can read about it <a
class="link"
href="https://en.wikipedia.org/wiki/Cron"
target="_blank">here</a
>
or refer to the
<a class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3" target="_blank"
>package documentation</a
>
for more information. <strong>Tip:</strong> Once you've entered your syntax into the input
field and click out of it, your syntax will be automatically validated to check for correctness.
</p>
<div class="form-control flex flex-row flex-wrap gap-4">
<div>
<label class="label" for="wake-cron">
<span class="label-text">Wake cron</span>
</label>
<input
id="wake-cron"
type="text"
placeholder="M H DoM M DoW"
class="input w-80"
bind:value={deviceClone.wake_cron}
disabled={!deviceClone.wake_cron_enabled}
/>
</div>
<div class="flex flex-col">
<label class="label cursor-pointer" for="wake-cron-enable">
<span class="label-text">Enable wake cron</span>
</label>
<input
id="wake-cron-enable"
type="checkbox"
class="toggle toggle-success"
bind:checked={deviceClone.wake_cron_enabled}
/>
</div>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Shutdown</h2>
<p class="my-2">
This <strong>shell command</strong> will run inside your container (if you use docker) or
on your host (if you use the binary). To verify that works you can run the command inside
the container or on your host shell first. Common commands are
<span class="badge">net rpc</span> for windows, <span class="badge">sshpass</span> for
linux or
<span class="badge">curl</span> in general to make web requests.
</p>
<p class="my-2 font-bold">Examples:</p>
<div class="alert text-sm bg-base-100 text-start w-fit">
<div>
<span class="font-bold">Shutdown remote windows machine:</span>
<pre
class="break-words whitespace-pre-wrap">net rpc shutdown -I 192.168.1.13 -U "user%password"</pre>
</div>
</div>
<div class="alert text-sm my-2 bg-base-100 text-start w-fit">
<div>
<span class="font-bold">Shutdown remote linux machine:</span>
<pre
class="break-words whitespace-pre-wrap">sshpass -p your_password ssh -o "StrictHostKeyChecking=no" user@hostname "sudo poweroff"</pre>
</div>
</div>
<div class="form-control w-full">
<label class="label cursor-pointer" for="shutdown-cmd">
<span class="label-text">Shutdown command</span>
</label>
<input
id="shutdown-cmd"
type="text"
placeholder="$:"
class="input w-full max-w-xs mb-2"
bind:value={deviceClone.shutdown_cmd}
/>
</div>
<p class="my-2">
Just like setting a cron to wake the device, you can also schedule a cron job to shut down
this device.
</p>
<div class="form-control flex flex-row flex-wrap gap-4">
<div>
<label class="label" for="shutdown-cron">
<span class="label-text">Shutdown cron</span>
</label>
<input
id="shutdown-cron"
type="text"
placeholder="M H DoM M DoW"
class="input w-80"
bind:value={deviceClone.shutdown_cron}
disabled={!deviceClone.shutdown_cron_enabled}
/>
</div>
<div class="flex flex-col">
<label class="label cursor-pointer" for="shutdown-cron-enable">
<span class="label-text">Enable shutdown cron</span>
</label>
<input
id="shutdown-cron-enable"
type="checkbox"
class="toggle toggle-success"
bind:checked={deviceClone.shutdown_cron_enabled}
/>
</div>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Password</h2>
<p class="my-2">
Some network cards have the option to set a password for magic packets, also called <span
class="badge">SecureON</span
>. Password can only be 0, 4 or 6 characters in length.
</p>
<div class="form-control w-full">
<input
type="password"
placeholder="123456"
class="input w-full max-w-xs"
maxlength="6"
bind:value={deviceClone.password}
/>
</div>
{#if saveErrMsg !== ''}
<Alert
color="error"
message={saveErrMsg}
icon={faTriangleExclamation}
customClasses="mt-4 max-w-fit"
/>
{/if}
</div>
</div>
<div class="card-actions justify-end mt-4">
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>
<DeviceForm device={deviceClone} />
{/if}

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import DeviceForm from '$lib/components/DeviceForm.svelte';
import type { Device, Port } from '$lib/types/device';
let device: Device = {
expand: {
ports: [] as Port[]
}
} as Device;
</script>
<h1 class="text-3xl font-bold mb-8">New device</h1>
<DeviceForm {device} />