add settings page

This commit is contained in:
seriousm4x
2023-07-30 17:41:51 +02:00
parent 1c06774617
commit da63e134d6
4 changed files with 191 additions and 14 deletions

View File

@@ -17,9 +17,7 @@
.collection('ports')
.delete(port.id)
.then(() => {
deviceClone.ports = deviceClone.ports.filter(
(id: number) => id !== deviceClone.ports[index]
);
deviceClone.ports = deviceClone.ports.filter((id) => id !== deviceClone.ports[index]);
})
.catch((err) => {
clearTimeout(portErrTimeout);
@@ -68,7 +66,7 @@
</div>
<div class="card-actions justify-end">
<button
class="btn btn-xs btn-error"
class="btn btn-xs btn-outline btn-error"
on:click={() => deletePort(deviceClone.expand.ports[index])}
type="button"><Fa icon={faTrash} />Delete</button
>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { pocketbase, backendUrl } from '$lib/stores/pocketbase';
import { settingsPub } from '$lib/stores/settings';
import Fa from 'svelte-fa';
@@ -38,17 +39,25 @@
tabindex="-1"
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-300 rounded-box w-52"
>
<li><a href="/"><Fa icon={faHome} />Home</a></li>
<li><a href="/" class:active={$page.url.pathname === '/'}><Fa icon={faHome} />Home</a></li>
{#if $pocketbase.authStore.model?.collectionName !== 'users'}
<li><a href="/"><Fa icon={faKey} />Permissions</a></li>
<li><a href="/"><Fa icon={faCog} />Settings</a></li>
<li>
<a href="/" class:active={$page.url.pathname.startsWith('/permissions')}
><Fa icon={faKey} />Permissions</a
>
</li>
<li>
<a href="/settings/" class:active={$page.url.pathname.startsWith('/settings')}
><Fa icon={faCog} />Settings</a
>
</li>
{/if}
</ul>
</div>
<a class="btn btn-ghost normal-case text-xl" href="/">
<img
src={$settingsPub?.collectionId && $settingsPub?.favicon
? `/api/files/settings_public/${$settingsPub?.collectionId}/${$settingsPub?.favicon}`
src={$settingsPub?.id && $settingsPub?.favicon
? `${backendUrl}api/files/settings_public/${$settingsPub?.id}/${$settingsPub?.favicon}`
: '/gopher.svg'}
alt={$settingsPub?.website_title ? $settingsPub?.website_title : 'UpSnap'}
width="45"
@@ -58,10 +67,18 @@
</div>
<div class="navbar-center hidden md:flex">
<ul class="menu menu-horizontal px-1">
<li><a href="/"><Fa icon={faHome} />Home</a></li>
<li><a href="/" class:active={$page.url.pathname === '/'}><Fa icon={faHome} />Home</a></li>
{#if $pocketbase.authStore.model?.collectionName !== 'users'}
<li><a href="/"><Fa icon={faKey} />Permissions</a></li>
<li><a href="/"><Fa icon={faCog} />Settings</a></li>
<li>
<a href="/" class:active={$page.url.pathname.startsWith('/permissions')}
><Fa icon={faKey} />Permissions</a
>
</li>
<li>
<a href="/settings/" class:active={$page.url.pathname.startsWith('/settings')}
><Fa icon={faCog} />Settings</a
>
</li>
{/if}
</ul>
</div>

View File

@@ -55,10 +55,15 @@
<svelte:head>
<link
rel="shortcut icon"
href={$settingsPub?.collectionId && $settingsPub?.favicon
? `${backendUrl}/api/files/settings_public/${$settingsPub?.collectionId}/${$settingsPub?.favicon}`
href={$settingsPub?.id && $settingsPub?.favicon
? `${backendUrl}api/files/settings_public/${$settingsPub?.id}/${$settingsPub?.favicon}`
: '/gopher.svg'}
/>
{#if $settingsPub === undefined}
<title>UpSnap</title>
{:else}
<title>{$settingsPub.website_title === '' ? 'UpSnap' : $settingsPub.website_title}</title>
{/if}
</svelte:head>
{#if $pocketbase.authStore.isValid}

View File

@@ -0,0 +1,157 @@
<script lang="ts">
import { settingsPub, settingsPriv } from '$lib/stores/settings';
import { pocketbase, backendUrl } from '$lib/stores/pocketbase';
import type { SettingsPublic, SettingsPrivate } from '$lib/types/settings';
let settingsPubClone: SettingsPublic | undefined;
let settingsPrivClone: SettingsPrivate | undefined;
let faviconPreview: HTMLImageElement;
let faviconInputElement: HTMLInputElement;
let saveBtn: HTMLButtonElement;
let version = import.meta.env.UPSNAP_VERSION;
settingsPub.subscribe((settings) => {
settingsPubClone = settings;
});
settingsPriv.subscribe((settings) => {
settingsPrivClone = settings;
});
if (!$settingsPriv && $pocketbase.authStore.isValid) {
$pocketbase
.collection('settings_private')
.getList(1, 1)
.then((res) => {
settingsPriv.set(res.items[0] as SettingsPrivate);
});
}
function resetFavicon() {
if (settingsPubClone === undefined) return;
settingsPubClone.favicon = '';
faviconInputElement.value = '';
faviconPreview.src = '/gopher.svg';
}
async function save() {
if (
settingsPubClone === undefined ||
settingsPrivClone === undefined ||
faviconInputElement === undefined ||
saveBtn === undefined
)
return;
saveBtn.classList.remove('btn-primary');
saveBtn.classList.add('btn-warning');
if (faviconInputElement.files !== null && faviconInputElement.files?.length > 0) {
let form = new FormData();
form.append('favicon', faviconInputElement.files[0]);
await $pocketbase.collection('settings_public').update(settingsPubClone.id, form);
}
const res = await $pocketbase
.collection('settings_public')
.update(settingsPubClone.id, settingsPubClone);
settingsPub.set(res as SettingsPublic);
saveBtn.classList.remove('btn-warning');
saveBtn.classList.add('btn-primary');
}
</script>
{#if settingsPubClone === undefined || settingsPrivClone === undefined}
<div class="container mx-auto text-center"><span class="loading loading-ring loading-md" /></div>
{:else}
<h1 class="text-3xl font-bold mb-8">Settings</h1>
<form on:submit|preventDefault={save}>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Ping interval</h2>
<p class="mt-2">
Sets the interval in which the devices are pinged. Leave blank to use default value of <span
class="badge">@every 3s</span
>.
</p>
<p class="mb-2">
Learn more about the correct syntax for cron on
<a target="_blank" class="link" href="https://en.wikipedia.org/wiki/Cron">Wikipedia</a>
or refer to the
<a target="_blank" class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3"
>package documentation</a
>.
</p>
<div class="form-control w-full">
<input
type="text"
placeholder="e.g. '@every 5s' or '@every 1m'"
class="input w-full max-w-xs"
bind:value={settingsPrivClone.interval}
/>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Website title</h2>
<p class="my-2">Sets the title of the website and in the browser tab.</p>
<div class="form-control w-full">
<input
type="text"
placeholder="e.g. 'UpSnap'"
class="input w-full max-w-xs"
bind:value={settingsPubClone.website_title}
/>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Icon</h2>
<p class="my-2">
Set a custom favicon. Supported file types are
<span class="badge">.ico</span>, <span class="badge">.png</span>,
<span class="badge">.svg</span>, <span class="badge">.gif</span> and
<span class="badge">.jpg/.jpeg</span>.
</p>
<div>
<img
class="h-36"
src={$settingsPub.favicon !== ''
? `${backendUrl}api/files/settings_public/${$settingsPub.id}/${$settingsPub.favicon}`
: '/gopher.svg'}
alt="Favicon preview"
bind:this={faviconPreview}
/>
</div>
<div class="form-control w-full max-w-md flex flex-row gap-4">
<input
type="file"
class="file-input w-full max-w-xs"
accept=".ico,.png,.svg,.gif,.jpg,.jpeg"
bind:this={faviconInputElement}
/>
<button
class="btn btn-outline btn-error"
on:click={() => resetFavicon()}
on:keydown={() => resetFavicon()}>Reset</button
>
</div>
</div>
</div>
<div class="card-actions justify-end mt-4">
<button class="btn btn-primary" type="submit" bind:this={saveBtn}>Save</button>
</div>
</form>
<div class="container mx-auto text-center">
{#if version === undefined}
UpSnap version: (untracked)
{:else}
UpSnap version: <a
href="https://github.com/seriousm4x/UpSnap/releases/tag/{version}"
class="text-reset">{version}</a
>
{/if}
</div>
{/if}