add new device form

This commit is contained in:
Maxi Quoß
2023-01-26 15:30:45 +01:00
parent 4d21b0fc92
commit a3b1413fbb
6 changed files with 288 additions and 59 deletions

View File

@@ -31,7 +31,7 @@ Open up [http://localhost:5173/](http://localhost:5173/)
- frontend
- [x] add rest of settings page
- [ ] form for adding new devices
- [x] form for adding new devices
- [ ] add per device settings
- [x] ~~theme toggle button~~
- [x] ~~add device ports to cards~~

View File

@@ -13,8 +13,8 @@ func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `{
"id": "z5lghx2r3tm45n1",
"created": "2023-01-23 19:32:19.965Z",
"updated": "2023-01-23 19:32:19.965Z",
"created": "2023-01-26 14:28:42.378Z",
"updated": "2023-01-26 14:28:42.378Z",
"name": "devices",
"type": "base",
"system": false,
@@ -111,8 +111,8 @@ func init() {
},
{
"system": false,
"id": "1a7yrwo9",
"name": "shutdown",
"id": "kzvdbbws",
"name": "wake_cron",
"type": "text",
"required": false,
"unique": false,
@@ -124,8 +124,34 @@ func init() {
},
{
"system": false,
"id": "kzvdbbws",
"name": "wake",
"id": "91bs6clw",
"name": "shutdown_cron",
"type": "text",
"required": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "1a7yrwo9",
"name": "shutdown_cmd",
"type": "text",
"required": false,
"unique": false,
"options": {
"min": null,
"max": null,
"pattern": ""
}
},
{
"system": false,
"id": "iarksm3l",
"name": "password",
"type": "text",
"required": false,
"unique": false,

View File

@@ -13,8 +13,8 @@ func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `{
"id": "cti4l8f4mz8df3r",
"created": "2023-01-23 19:32:19.965Z",
"updated": "2023-01-23 19:32:19.965Z",
"created": "2023-01-26 14:28:42.378Z",
"updated": "2023-01-26 14:28:42.378Z",
"name": "ports",
"type": "base",
"system": false,

View File

@@ -13,8 +13,8 @@ func init() {
m.Register(func(db dbx.Builder) error {
jsonData := `{
"id": "nmj3ko20gzkg8n3",
"created": "2023-01-23 19:32:19.965Z",
"updated": "2023-01-23 19:32:19.965Z",
"created": "2023-01-26 14:28:42.378Z",
"updated": "2023-01-26 14:28:42.378Z",
"name": "settings",
"type": "base",
"system": false,

View File

@@ -22,8 +22,8 @@ func init() {
}, func(db dbx.Builder) error {
jsonData := `{
"id": "_pb_users_auth_",
"created": "2023-01-23 19:31:58.918Z",
"updated": "2023-01-23 19:31:58.919Z",
"created": "2023-01-26 14:28:20.206Z",
"updated": "2023-01-26 14:28:20.206Z",
"name": "users",
"type": "auth",
"system": false,

View File

@@ -1,14 +1,40 @@
<script>
import { onMount } from 'svelte';
import { pocketbase } from '@stores/pocketbase';
import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
let pb;
let files;
let settings = {};
let buttons = {
saved: 'none',
restore: 'none'
}
add_device: {
state: 'none',
error: ''
},
saved: {
state: 'none',
error: ''
},
restore: {
state: 'none',
error: ''
}
};
let newDevice = {
name: '',
ip: '',
mac: '',
netmask: '255.255.255.0',
link: '',
wake_cron: '',
shutdown_cron: '',
shutdown_cmd: '',
password: ''
};
let passwordShow = false;
$: passwordType = passwordShow ? 'text' : 'password';
onMount(async () => {
pocketbase.subscribe((conn) => {
@@ -19,10 +45,28 @@
settings = result.items[0];
});
async function saveSettings() {
buttons.saved = 'waiting';
async function addDevice() {
buttons.add_device.state = 'waiting';
try {
const result = await pb.collection('settings').update(
await pb.collection('devices').create(newDevice, { $autoCancel: false });
setTimeout(() => {
buttons.add_device.state = 'none';
}, 3000);
buttons.add_device.state = 'success';
} catch (error) {
setTimeout(() => {
buttons.add_device.error = '';
buttons.add_device.state = 'none';
}, 3000);
buttons.add_device.error = error;
buttons.add_device.state = 'failed';
}
}
async function saveSettings() {
buttons.saved.state = 'waiting';
try {
await pb.collection('settings').update(
settings.id,
{
interval: settings.interval,
@@ -31,19 +75,21 @@
{ $autoCancel: false }
);
setTimeout(() => {
buttons.saved = 'none';
}, 2000);
buttons.saved = 'success';
buttons.saved.state = 'none';
}, 3000);
buttons.saved.state = 'success';
} catch (error) {
setTimeout(() => {
buttons.saved = 'none';
}, 2000);
buttons.saved = 'failed';
buttons.saved.error = '';
buttons.saved.state = 'none';
}, 3000);
buttons.saved.error = error;
buttons.saved.state = 'failed';
}
}
async function restoreV2Backup() {
buttons.restore = 'waiting'
buttons.restore.state = 'waiting';
try {
// delete all devices in pocketbase
@@ -101,19 +147,181 @@
}
};
setTimeout(() => {
buttons.restore = 'none';
}, 2000);
buttons.restore = 'success';
buttons.restore.state = 'none';
}, 3000);
buttons.restore.state = 'success';
} catch (error) {
setTimeout(() => {
buttons.restore = 'none';
}, 2000);
buttons.restore = 'failed';
buttons.restore.error = '';
buttons.restore.state = 'none';
}, 3000);
buttons.restore.error = error;
buttons.restore.state = 'failed';
}
}
function onPasswordInput(event) {
newDevice.password = event.target.value;
}
</script>
<div class="container">
<section class="m-0 mt-4 p-4 shadow-sm">
<h3 class="mb-3">Add new device</h3>
<div class="row">
<div class="col-md-6">
<form on:submit|preventDefault={addDevice}>
<h5>Required:</h5>
<div class="input-group mb-3">
<span class="input-group-text">Name</span>
<input
class="form-control"
placeholder="Office Pc"
aria-label="Name"
aria-describedby="addon-wrapping"
type="text"
required
bind:value={newDevice.name}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">IP</span>
<input
class="form-control"
placeholder="192.168.1.34"
aria-label="IP"
aria-describedby="addon-wrapping"
type="text"
required
bind:value={newDevice.ip}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">MAC</span>
<input
class="form-control"
placeholder="aa:bb:cc:dd:ee:ff"
aria-label="MAC"
aria-describedby="addon-wrapping"
type="text"
required
bind:value={newDevice.mac}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Netmask</span>
<input
class="form-control"
placeholder="Most likely 255.255.255.0 or 255.255.0.0"
aria-label="Netmask"
aria-describedby="addon-wrapping"
type="text"
required
bind:value={newDevice.netmask}
/>
</div>
<h5 class="mt-4">Optional:</h5>
<div class="input-group mb-3">
<span class="input-group-text">Link</span>
<input
class="form-control"
placeholder="Clickable link on device card"
aria-label="Link"
aria-describedby="addon-wrapping"
type="url"
bind:value={newDevice.link}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Wake Cron<sup>(1)</sup></span>
<input
class="form-control"
placeholder="Automatically wake device with cron"
aria-label="Wake Cron"
aria-describedby="addon-wrapping"
type="text"
bind:value={newDevice.wake_cron}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Shutdown Cron<sup>(1)</sup></span>
<input
class="form-control"
placeholder="Automatically shutdown device with cron"
aria-label="Shutdown Cron"
aria-describedby="addon-wrapping"
type="text"
bind:value={newDevice.shutdown_cron}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Shutdown Cmd<sup>(2)</sup></span>
<input
class="form-control"
placeholder="Command to shutdown device"
aria-label="Shutdown Cmd"
aria-describedby="addon-wrapping"
type="text"
bind:value={newDevice.shutdown_cmd}
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text">Password<sup>(3)</sup></span>
<input
class="form-control"
placeholder="BIOS password for wol"
aria-label="IP"
aria-describedby="addon-wrapping"
type={passwordType}
value={newDevice.password}
on:input={onPasswordInput}
/>
<button
class="btn btn-outline-secondary"
type="button"
on:click={() => (passwordShow = !passwordShow)}
>
<Fa icon={passwordShow ? faEyeSlash : faEye} />
</button>
</div>
<button
type="submit"
class="btn btn-secondary"
class:btn-success={buttons.add_device.state === 'success' ? true : false}
class:btn-warning={buttons.add_device.state === 'waiting' ? true : false}
class:btn-danger={buttons.add_device.state === 'failed' ? true : false}
>
{#if buttons.add_device.state === 'none'}
Add device
{:else if buttons.add_device.state === 'success'}
Added successfully
{:else if buttons.add_device.state === 'waiting'}
Waiting
{:else if buttons.add_device.state === 'failed'}
Failed: {buttons.add_device.error}
{/if}
</button>
</form>
</div>
<div class="col-md-6">
<div class="callout callout-info mb-0">
<h5>Optional:</h5>
<p class="m-0">(1) Same cron syntax as for ping interval.</p>
<p class="m-0">(2) Shell command to be executed. e.g.:</p>
<ul>
<li>Windows: "net rpc shutdown -I 192.168.1.13 -U test%test"</li>
<li>
Linux: "sshpass -p your_password ssh -o 'StrictHostKeyChecking=no' user@hostname 'sudo
shutdown'"
</li>
</ul>
<p class="m-0">
(3) Some network cards have the option to set a password for magic packets.
</p>
</div>
</div>
</div>
</section>
<section class="m-0 mt-4 p-4 shadow-sm">
<h3 class="mb-3">Ping interval</h3>
<div class="row">
@@ -122,7 +330,6 @@
<div class="input-group mb-3">
<span class="input-group-text">Cron</span>
<input
id="settings-interval"
class="form-control"
placeholder="e.g. '@every 5s' or '@every 1m'"
aria-label="Interval"
@@ -147,9 +354,7 @@
</div>
</div>
</div>
</section>
<section class="m-0 mt-4 p-4 shadow-sm">
<h3 class="mb-3">Notifications</h3>
<h3 class="my-3">Notifications</h3>
<p>Show toast notifications in the bottom right corner.</p>
<div class="form-check form-switch">
<label class="form-check-label" for="settings-notifications">Enable notifications</label>
@@ -161,33 +366,31 @@
bind:checked={settings.notifications}
/>
</div>
</section>
<div class="m-0 mt-4 shadow-sm">
<button
type="button"
class="btn btn-secondary w-100"
class:btn-success={buttons.saved === 'success' ? true : false}
class:btn-warning={buttons.saved === 'waiting' ? true : false}
class:btn-danger={buttons.saved === 'failed' ? true : false}
class="btn btn-secondary mt-3"
class:btn-success={buttons.saved.state === 'success' ? true : false}
class:btn-warning={buttons.saved.state === 'waiting' ? true : false}
class:btn-danger={buttons.saved.state === 'failed' ? true : false}
on:click={() => saveSettings()}
>
{#if buttons.saved === 'none'}
{#if buttons.saved.state === 'none'}
Save
{:else if buttons.saved === 'success'}
{:else if buttons.saved.state === 'success'}
Saved
{:else if buttons.saved === 'waiting'}
{:else if buttons.saved.state === 'waiting'}
Waiting
{:else if buttons.saved === 'failed'}
Failed
{:else if buttons.saved.state === 'failed'}
Failed: {buttons.add_device.error}
{/if}
</button>
</div>
<section class="m-0 mt-5 p-4 shadow-sm">
</section>
<section class="m-0 my-4 p-4 shadow-sm">
<h3 class="mb-3">Restore</h3>
<p>If you had UpSnap v2.3.2 (or higher) running before, you can restore your devices here.</p>
<div class="callout callout-danger fw-bold">This will wipe the existing database!</div>
<input
class="form-control mb-3"
class="form-control"
placeholder="Username"
aria-label="Username"
aria-describedby="addon-wrapping"
@@ -197,21 +400,21 @@
/>
<button
type="button"
class="btn btn-secondary"
class:btn-success={buttons.restore === 'success' ? true : false}
class:btn-warning={buttons.restore === 'waiting' ? true : false}
class:btn-danger={buttons.restore === 'failed' ? true : false}
class="btn btn-secondary mt-3"
class:btn-success={buttons.restore.state === 'success' ? true : false}
class:btn-warning={buttons.restore.state === 'waiting' ? true : false}
class:btn-danger={buttons.restore.state === 'failed' ? true : false}
disabled={files ? false : true}
on:click={() => restoreV2Backup()}
>
{#if buttons.restore === 'none'}
{#if buttons.restore.state === 'none'}
Restore
{:else if buttons.restore === 'success'}
{:else if buttons.restore.state === 'success'}
Restored
{:else if buttons.restore === 'waiting'}
{:else if buttons.restore.state === 'waiting'}
Waiting
{:else if buttons.restore === 'failed'}
Failed
{:else if buttons.restore.state === 'failed'}
Failed: {buttons.restore.error}
{/if}
</button>
</section>