mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-07-23 14:03:47 -04:00
feat: use new svelte 5 syntax
This commit is contained in:
@@ -19,11 +19,9 @@
|
||||
import { scale } from 'svelte/transition';
|
||||
import DeviceCardNic from './DeviceCardNic.svelte';
|
||||
|
||||
export let device: Device;
|
||||
|
||||
let { device = $bindable() }: { device: Device } = $props();
|
||||
let modalReboot: HTMLDialogElement;
|
||||
|
||||
$: moreButtons = [
|
||||
let moreButtons = $derived([
|
||||
{
|
||||
text: m.device_card_btn_more_sleep(),
|
||||
icon: faBed,
|
||||
@@ -48,18 +46,17 @@
|
||||
onClick: () => goto(`/device/${device.id}`),
|
||||
requires: $pocketbase.authStore.isSuperuser || $permission.update?.includes(device.id)
|
||||
}
|
||||
];
|
||||
]);
|
||||
|
||||
// update device status change
|
||||
let now = Date.now();
|
||||
let now = $state(Date.now());
|
||||
let interval: number;
|
||||
$: {
|
||||
$effect(() => {
|
||||
clearInterval(interval);
|
||||
interval = setInterval(() => {
|
||||
// eslint-disable-next-line svelte/infinite-reactive-loop
|
||||
now = Date.now();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
function sleep() {
|
||||
fetch(`${backendUrl}api/upsnap/sleep/${device.id}`, {
|
||||
@@ -146,7 +143,7 @@
|
||||
{#each moreButtons as btn, i (i)}
|
||||
{#if btn.requires}
|
||||
<div class="tooltip" data-tip={btn.text}>
|
||||
<button class="btn btn-sm btn-circle" on:click={btn.onClick}>
|
||||
<button class="btn btn-sm btn-circle" onclick={btn.onClick}>
|
||||
<Fa icon={btn.icon} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -167,7 +164,7 @@
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">{m.buttons_cancel()}</button>
|
||||
<button class="btn btn-success" on:click={reboot}>{m.buttons_confirm()}</button>
|
||||
<button class="btn btn-success" onclick={reboot}>{m.buttons_confirm()}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,44 +6,46 @@
|
||||
import Fa from 'svelte-fa';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
export let device: Device;
|
||||
|
||||
let hoverText = '';
|
||||
let disabled = false;
|
||||
let { device = $bindable() }: { device: Device } = $props();
|
||||
let hoverText = $state('');
|
||||
let disabled = $state(false);
|
||||
let timeout = 120;
|
||||
let interval: number;
|
||||
let modalWake: HTMLDialogElement;
|
||||
let modalShutdown: HTMLDialogElement;
|
||||
let seconds = $derived(timeout % 60);
|
||||
let minutes = $derived(Math.floor(timeout / 60));
|
||||
|
||||
$: if (device.status === 'pending' && !interval) {
|
||||
// eslint-disable-next-line svelte/infinite-reactive-loop
|
||||
countdown(Date.parse(device.updated), 'wake');
|
||||
}
|
||||
$: minutes = Math.floor(timeout / 60);
|
||||
$: seconds = timeout % 60;
|
||||
$: if (device.status === 'pending' || device.status === '') {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_pending();
|
||||
} else if (device.status === 'online') {
|
||||
if (device.shutdown_cmd === '') {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_shutdown_no_cmd();
|
||||
} else if (!$pocketbase.authStore.isSuperuser && !$permission.power?.includes(device.id)) {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_shutdown_no_permission();
|
||||
} else {
|
||||
disabled = false;
|
||||
hoverText = m.device_card_nic_tooltip_shutdown();
|
||||
$effect(() => {
|
||||
if (device.status === 'pending' && !interval) {
|
||||
countdown(Date.parse(device.updated), 'wake');
|
||||
}
|
||||
} else if (device.status === 'offline') {
|
||||
if (!$pocketbase.authStore.isSuperuser && !$permission.power?.includes(device.id)) {
|
||||
});
|
||||
$effect(() => {
|
||||
if (device.status === 'pending' || device.status === '') {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_power_no_permission();
|
||||
} else {
|
||||
disabled = false;
|
||||
hoverText = m.device_card_nic_tooltip_power();
|
||||
hoverText = m.device_card_nic_tooltip_pending();
|
||||
} else if (device.status === 'online') {
|
||||
if (device.shutdown_cmd === '') {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_shutdown_no_cmd();
|
||||
} else if (!$pocketbase.authStore.isSuperuser && !$permission.power?.includes(device.id)) {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_shutdown_no_permission();
|
||||
} else {
|
||||
disabled = false;
|
||||
hoverText = m.device_card_nic_tooltip_shutdown();
|
||||
}
|
||||
} else if (device.status === 'offline') {
|
||||
if (!$pocketbase.authStore.isSuperuser && !$permission.power?.includes(device.id)) {
|
||||
disabled = true;
|
||||
hoverText = m.device_card_nic_tooltip_power_no_permission();
|
||||
} else {
|
||||
disabled = false;
|
||||
hoverText = m.device_card_nic_tooltip_power();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function wake() {
|
||||
countdown(Date.now(), 'wake');
|
||||
@@ -114,7 +116,7 @@
|
||||
|
||||
if (timeout <= 0 || device.status !== 'pending') {
|
||||
clearInterval(interval);
|
||||
// eslint-disable-next-line svelte/infinite-reactive-loop
|
||||
|
||||
interval = 0;
|
||||
}
|
||||
}, 1000);
|
||||
@@ -148,8 +150,8 @@
|
||||
<div
|
||||
class={`tooltip ${disabled ? 'cursor-not-allowed' : 'hover:bg-base-300 cursor-pointer'} bg-base-100 rounded-box flex items-start gap-4 p-2`}
|
||||
data-tip={hoverText}
|
||||
on:click={disabled ? null : handleClick}
|
||||
on:keydown={disabled ? null : handleClick}
|
||||
onclick={disabled ? null : handleClick}
|
||||
onkeydown={disabled ? null : handleClick}
|
||||
role="none"
|
||||
>
|
||||
{#if device.status === 'offline'}
|
||||
@@ -176,7 +178,9 @@
|
||||
<div>{device.mac}</div>
|
||||
<div class="flex flex-wrap gap-x-4">
|
||||
{#if device?.expand?.ports}
|
||||
{#each device?.expand?.ports.sort((a, b) => a.number - b.number) as port (port.id)}
|
||||
{#each $state
|
||||
.snapshot(device)
|
||||
.expand?.ports.sort((a, b) => a.number - b.number) as port (port.id)}
|
||||
<span class="flex items-center gap-1 break-all">
|
||||
{#if port.status}
|
||||
<div class="inline-grid *:[grid-area:1/1]">
|
||||
@@ -194,7 +198,7 @@
|
||||
href={port.link}
|
||||
target="_blank"
|
||||
class="underline"
|
||||
on:click={(e) => e.stopPropagation()}>{port.name} ({port.number})</a
|
||||
onclick={(e) => e.stopPropagation()}>{port.name} ({port.number})</a
|
||||
>
|
||||
{:else}
|
||||
{port.name} ({port.number})
|
||||
@@ -215,7 +219,7 @@
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">{m.buttons_cancel()}</button>
|
||||
<button class="btn btn-success" on:click={wake}>{m.buttons_confirm()}</button>
|
||||
<button class="btn btn-success" onclick={wake}>{m.buttons_confirm()}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -230,7 +234,7 @@
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">{m.buttons_cancel()}</button>
|
||||
<button class="btn btn-success" on:click={shutdown}>{m.buttons_confirm()}</button>
|
||||
<button class="btn btn-success" onclick={shutdown}>{m.buttons_confirm()}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +194,13 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={save}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
save();
|
||||
}}
|
||||
class="flex w-full flex-col"
|
||||
>
|
||||
<div class="card bg-base-200 w-full shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{m.device_general()}</h2>
|
||||
@@ -275,7 +281,7 @@
|
||||
</div>
|
||||
<button
|
||||
class="btn max-w-xs {device.expand.ports.length > 0 ? 'mt-4' : ''}"
|
||||
on:click={() => createEmptyPort()}
|
||||
onclick={() => createEmptyPort()}
|
||||
type="button">{m.device_ports_add_new()}</button
|
||||
>
|
||||
</div>
|
||||
@@ -666,12 +672,12 @@ second (0–59, optional)
|
||||
<button
|
||||
class="btn btn-error join-item"
|
||||
type="button"
|
||||
on:click={() => deleteGroup(group)}><Fa icon={faX} /></button
|
||||
onclick={() => deleteGroup(group)}><Fa icon={faX} /></button
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="btn join-item bg-base-100 hover:bg-base-200"
|
||||
on:click={() => toggleGroup(group.id)}
|
||||
onclick={() => toggleGroup(group.id)}
|
||||
role="none"
|
||||
>
|
||||
<input
|
||||
@@ -689,7 +695,7 @@ second (0–59, optional)
|
||||
class="input join-item"
|
||||
placeholder={m.device_groups_placeholder()}
|
||||
type="text"
|
||||
on:keydown={(e) => {
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addGroup();
|
||||
@@ -697,7 +703,7 @@ second (0–59, optional)
|
||||
}}
|
||||
bind:value={newGroup}
|
||||
/>
|
||||
<button class="btn btn-neutral join-item" type="button" on:click={() => addGroup()}
|
||||
<button class="btn btn-neutral join-item" type="button" onclick={() => addGroup()}
|
||||
>{m.buttons_add()}</button
|
||||
>
|
||||
</div>
|
||||
@@ -705,7 +711,7 @@ second (0–59, optional)
|
||||
</div>
|
||||
<div class="card-actions mt-6 justify-end gap-4">
|
||||
{#if $pocketbase.authStore.isSuperuser || $permission.delete?.includes(device.id)}
|
||||
<button class="btn btn-error" type="button" on:click={() => deleteModal.showModal()}
|
||||
<button class="btn btn-error" type="button" onclick={() => deleteModal.showModal()}
|
||||
><Fa icon={faTrash} />{m.buttons_delete()}</button
|
||||
>
|
||||
{/if}
|
||||
@@ -719,7 +725,7 @@ second (0–59, optional)
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">{m.buttons_cancel()}</button>
|
||||
<button class="btn btn-error" on:click={() => deleteDevice()}>{m.buttons_delete()}</button>
|
||||
<button class="btn btn-error" onclick={() => deleteDevice()}>{m.buttons_delete()}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</fieldset>
|
||||
<button
|
||||
class="btn btn-error btn-xs btn-soft ms-auto"
|
||||
on:click={() => deletePort(device.expand.ports[index])}
|
||||
onclick={() => deletePort(device.expand.ports[index])}
|
||||
type="button"><Fa icon={faTrash} />{m.buttons_delete()}</button
|
||||
>
|
||||
</fieldset>
|
||||
|
||||
@@ -145,8 +145,8 @@
|
||||
<button
|
||||
class="gap-3 px-2"
|
||||
data-set-theme={theme}
|
||||
on:click={() => (activeTheme = theme)}
|
||||
on:keydown={() => (activeTheme = theme)}
|
||||
onclick={() => (activeTheme = theme)}
|
||||
onkeydown={() => (activeTheme = theme)}
|
||||
>
|
||||
<div
|
||||
data-theme={theme}
|
||||
@@ -217,8 +217,8 @@
|
||||
<button
|
||||
class="gap-3 px-2"
|
||||
data-set-theme={theme}
|
||||
on:click={() => (activeTheme = theme)}
|
||||
on:keydown={() => (activeTheme = theme)}
|
||||
onclick={() => (activeTheme = theme)}
|
||||
onkeydown={() => (activeTheme = theme)}
|
||||
>
|
||||
<div
|
||||
data-theme={theme}
|
||||
@@ -266,7 +266,7 @@
|
||||
<a href="/account"><Fa icon={faUserGear} />{m.navbar_edit_account()}</a>
|
||||
</li>
|
||||
<li>
|
||||
<div on:click={async () => logout()} on:keydown={async () => logout()} role="none">
|
||||
<div onclick={async () => logout()} onkeydown={async () => logout()} role="none">
|
||||
<Fa icon={faDoorOpen} />{m.navbar_logout()}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -125,7 +125,12 @@
|
||||
{m.device_network_scan_desc()}
|
||||
</p>
|
||||
<div class="flex flex-row flex-wrap items-end gap-4">
|
||||
<form on:submit|preventDefault={saveSettings}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
saveSettings();
|
||||
}}
|
||||
>
|
||||
<fieldset class="fieldset p-0">
|
||||
<label class="floating-label mt-2">
|
||||
<span>{m.device_network_scan_ip_range()}</span>
|
||||
@@ -160,7 +165,7 @@
|
||||
{m.device_network_scan_running()}
|
||||
</button>
|
||||
{:else}
|
||||
<button class="btn btn-success" on:click={() => scan()}>
|
||||
<button class="btn btn-success" onclick={() => scan()}>
|
||||
<Fa icon={faMagnifyingGlass} />
|
||||
{m.device_network_scan()}
|
||||
</button>
|
||||
@@ -197,7 +202,7 @@
|
||||
<div class="ms-auto">
|
||||
<button
|
||||
class="btn btn-success btn-sm"
|
||||
on:click={(e) => {
|
||||
onclick={(e) => {
|
||||
addSingle(device);
|
||||
e.currentTarget.disabled = true;
|
||||
}}><Fa icon={faPlus} />{m.buttons_add()}</button
|
||||
@@ -240,7 +245,7 @@
|
||||
{#if addAllCheckbox}
|
||||
<button
|
||||
class="btn btn-success"
|
||||
on:click={() => addAll()}
|
||||
onclick={() => addAll()}
|
||||
disabled={scanResponse.devices.length === 0}
|
||||
>
|
||||
<Fa icon={faPlus} />
|
||||
@@ -249,7 +254,7 @@
|
||||
{:else}
|
||||
<button
|
||||
class="btn btn-success"
|
||||
on:click={() => addAll()}
|
||||
onclick={() => addAll()}
|
||||
disabled={scanResponse.devices.filter((dev) => dev.name !== 'Unknown').length === 0}
|
||||
>
|
||||
<Fa icon={faPlus} />
|
||||
@@ -263,7 +268,7 @@
|
||||
<div class="max-w-fit">
|
||||
<button
|
||||
class="btn btn-success"
|
||||
on:click={() => addAll()}
|
||||
onclick={() => addAll()}
|
||||
disabled={scanResponse.devices.length === 0}
|
||||
>
|
||||
<Fa icon={faPlus} />
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
import Fa from 'svelte-fa';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
let loading = true;
|
||||
let devices: Device[] = [];
|
||||
let orderBy: 'name' | 'ip' = 'name';
|
||||
let orderExpanded = false;
|
||||
let orderByGroups = true;
|
||||
let searchQuery = '';
|
||||
let searchInput: HTMLInputElement;
|
||||
let loading = $state(true);
|
||||
let devices: Device[] = $state([]);
|
||||
let orderBy: 'name' | 'ip' = $state('name');
|
||||
let orderExpanded = $state(false);
|
||||
let orderByGroups = $state(true);
|
||||
let searchQuery = $state('');
|
||||
let searchInput: HTMLInputElement | undefined = $state();
|
||||
let filteredDevices: Device[] = $derived([]);
|
||||
let devicesWithoutGroups: Device[] = $derived([]);
|
||||
let devicesWithGroup: Record<string, Device[]> = $derived({});
|
||||
|
||||
const isMac = /Mac|iPhone|iPad/.test(navigator.userAgent);
|
||||
const gridClass = 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4';
|
||||
|
||||
@@ -35,32 +39,41 @@
|
||||
orderByGroups = localStorage.getItem('orderByGroups') !== 'false';
|
||||
}
|
||||
|
||||
$: if (browser) {
|
||||
localStorage.setItem('orderBy', orderBy);
|
||||
}
|
||||
$: if (browser) {
|
||||
localStorage.setItem('orderByGroups', String(orderByGroups));
|
||||
}
|
||||
|
||||
$: filteredDevices = devices.filter(
|
||||
(dev) =>
|
||||
dev.ip.includes(searchQuery.toLowerCase()) ||
|
||||
dev.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
dev.mac.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
dev.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
$: devicesWithoutGroups = filteredDevices.filter((dev) => dev.groups.length === 0);
|
||||
|
||||
$: devicesWithGroup = filteredDevices.reduce(
|
||||
(groups, dev) => {
|
||||
dev.expand?.groups?.forEach((group: Group) => {
|
||||
groups[group.id] = [...(groups[group.id] || []), dev];
|
||||
});
|
||||
return groups;
|
||||
},
|
||||
{} as Record<string, Device[]>
|
||||
);
|
||||
$effect(() => {
|
||||
if (browser) {
|
||||
localStorage.setItem('orderBy', orderBy);
|
||||
}
|
||||
});
|
||||
$effect(() => {
|
||||
if (browser) {
|
||||
localStorage.setItem('orderByGroups', String(orderByGroups));
|
||||
}
|
||||
});
|
||||
$effect(() => {
|
||||
filteredDevices = devices.filter(
|
||||
(dev) =>
|
||||
dev.ip.includes(searchQuery.toLowerCase()) ||
|
||||
dev.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
dev.mac.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
dev.description.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
});
|
||||
$effect(() => {
|
||||
devicesWithoutGroups = filteredDevices
|
||||
.filter((dev) => dev.groups.length === 0)
|
||||
.sort((a, b) => a[orderBy].localeCompare(b[orderBy], $localeStore, { numeric: true }));
|
||||
});
|
||||
$effect(() => {
|
||||
devicesWithGroup = filteredDevices.reduce(
|
||||
(groups, dev) => {
|
||||
dev.expand?.groups?.forEach((group: Group) => {
|
||||
groups[group.id] = [...(groups[group.id] || []), dev];
|
||||
});
|
||||
return groups;
|
||||
},
|
||||
{} as Record<string, Device[]>
|
||||
);
|
||||
});
|
||||
|
||||
function getAllDevices() {
|
||||
$pocketbase
|
||||
@@ -74,7 +87,7 @@
|
||||
function handleShortcut(event: KeyboardEvent) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
||||
event.preventDefault();
|
||||
searchInput.focus();
|
||||
searchInput?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +111,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:document on:keydown={handleShortcut} />
|
||||
<svelte:document onkeydown={handleShortcut} />
|
||||
|
||||
{#if loading}
|
||||
<PageLoading />
|
||||
@@ -127,7 +140,7 @@
|
||||
</label>
|
||||
<div class="join ms-auto">
|
||||
{#if orderExpanded}
|
||||
<button class="btn join-item !outline-0" on:click={() => (orderByGroups = !orderByGroups)}>
|
||||
<button class="btn join-item !outline-0" onclick={() => (orderByGroups = !orderByGroups)}>
|
||||
{m.home_order_groups()}
|
||||
<input type="checkbox" class="checkbox" checked={orderByGroups} />
|
||||
</button>
|
||||
@@ -151,7 +164,7 @@
|
||||
<button
|
||||
class="btn join-item tooltip {orderExpanded ? '' : 'rounded-field'}"
|
||||
data-tip={m.home_order_tooltip()}
|
||||
on:click={() => (orderExpanded = !orderExpanded)}
|
||||
onclick={() => (orderExpanded = !orderExpanded)}
|
||||
>
|
||||
<Fa icon={orderExpanded ? faChevronCircleRight : faChevronCircleLeft} size="lg" />
|
||||
</button>
|
||||
@@ -162,7 +175,7 @@
|
||||
<div class="space-y-6">
|
||||
{#if devicesWithoutGroups.length > 0}
|
||||
<div class={gridClass}>
|
||||
{#each structuredClone(devicesWithoutGroups).sort( (a, b) => a[orderBy].localeCompare( b[orderBy], $localeStore, { numeric: true } ) ) as device (device.id)}
|
||||
{#each devicesWithoutGroups as device (device.id)}
|
||||
<DeviceCard {device} />
|
||||
{/each}
|
||||
</div>
|
||||
@@ -172,7 +185,7 @@
|
||||
<h1 class="mb-3 text-2xl font-bold">
|
||||
{groupDevices[0].expand.groups.find((grp) => grp.id === group)?.name ||
|
||||
'Unknown group name'}
|
||||
<button class="btn btn-sm btn-success btn-soft" on:click={() => wakeGroup(group)}
|
||||
<button class="btn btn-sm btn-success btn-soft" onclick={() => wakeGroup(group)}
|
||||
><Fa icon={faPowerOff} /> {m.home_wake_group()}</button
|
||||
>
|
||||
</h1>
|
||||
@@ -186,7 +199,9 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class={gridClass}>
|
||||
{#each structuredClone(filteredDevices).sort( (a, b) => a[orderBy].localeCompare( b[orderBy], $localeStore, { numeric: true } ) ) as device (device.id)}
|
||||
{#each $state
|
||||
.snapshot(filteredDevices)
|
||||
.sort( (a, b) => a[orderBy].localeCompare( b[orderBy], $localeStore, { numeric: true } ) ) as device (device.id)}
|
||||
<DeviceCard {device} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
import Fa from 'svelte-fa';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
let newAvatar: number;
|
||||
|
||||
let selectedLang: Locale;
|
||||
let newAvatar: number | undefined = $state();
|
||||
let selectedLang: Locale = $state('en-US' as Locale);
|
||||
|
||||
// locales
|
||||
const languageEmojis = {
|
||||
@@ -36,21 +35,27 @@
|
||||
};
|
||||
|
||||
// password change
|
||||
let newPassword = {
|
||||
let newPassword = $state({
|
||||
old: '',
|
||||
password: '',
|
||||
confirm: ''
|
||||
};
|
||||
$: adminBody = JSON.stringify({
|
||||
password: newPassword.password,
|
||||
passwordConfirm: newPassword.confirm
|
||||
});
|
||||
$: userBody = JSON.stringify({
|
||||
oldPassword: newPassword.old,
|
||||
password: newPassword.password,
|
||||
passwordConfirm: newPassword.confirm
|
||||
});
|
||||
let adminBody: string;
|
||||
let userBody: string;
|
||||
|
||||
$effect(() => {
|
||||
adminBody = JSON.stringify({
|
||||
password: newPassword.password,
|
||||
passwordConfirm: newPassword.confirm
|
||||
});
|
||||
});
|
||||
$effect(() => {
|
||||
userBody = JSON.stringify({
|
||||
oldPassword: newPassword.old,
|
||||
password: newPassword.password,
|
||||
passwordConfirm: newPassword.confirm
|
||||
});
|
||||
});
|
||||
onMount(() => {
|
||||
selectedLang = getLocale();
|
||||
});
|
||||
@@ -157,7 +162,12 @@
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<form on:submit|preventDefault={saveUser}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
saveUser();
|
||||
}}
|
||||
>
|
||||
<h2 class="card-title mt-4 mb-2">{m.account_avatar_title()}</h2>
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
{#each [...Array(10).keys()] as i (i)}
|
||||
@@ -176,7 +186,7 @@
|
||||
class:ring-offset-2={newAvatar !== undefined
|
||||
? i === newAvatar
|
||||
: i === $pocketbase.authStore.record?.avatar}
|
||||
on:click={() => (newAvatar = i)}
|
||||
onclick={() => (newAvatar = i)}
|
||||
role="none"
|
||||
>
|
||||
{#if $pocketbase.authStore.record?.id}
|
||||
@@ -207,7 +217,12 @@
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{m.account_change_password_title()}</h2>
|
||||
<p>{m.account_change_password_body()}</p>
|
||||
<form on:submit|preventDefault={changePassword}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
changePassword();
|
||||
}}
|
||||
>
|
||||
<div class="w-full max-w-xs">
|
||||
{#if !$pocketbase.authStore.isSuperuser}
|
||||
<fieldset class="fieldset">
|
||||
|
||||
@@ -8,27 +8,31 @@
|
||||
import type { Device, Group, Port } from '$lib/types/device';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
$: if (Object.hasOwn($permission, 'update')) {
|
||||
if (!$pocketbase.authStore.isSuperuser && !$permission.update.includes(page.params.id)) {
|
||||
toast(m.toasts_no_permission({ url: page.url.pathname }), {
|
||||
icon: '⛔'
|
||||
});
|
||||
goto('/');
|
||||
$effect(() => {
|
||||
if (Object.hasOwn($permission, 'update')) {
|
||||
const id = page.params.id;
|
||||
if (!$pocketbase.authStore.isSuperuser && !$permission.update.includes(id || '')) {
|
||||
toast(m.toasts_no_permission({ url: page.url.pathname }), {
|
||||
icon: '⛔'
|
||||
});
|
||||
goto('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function getDevice(): Promise<Device> {
|
||||
const resp = await $pocketbase
|
||||
.collection('devices')
|
||||
.getOne(page.params.id, { expand: 'ports,groups' });
|
||||
const id = page.params.id;
|
||||
if (!id) throw new Error('No device ID provided');
|
||||
|
||||
const resp = await $pocketbase.collection('devices').getOne(id, { expand: 'ports,groups' });
|
||||
let device = resp as Device;
|
||||
|
||||
if (!device.expand)
|
||||
if (!device.expand) {
|
||||
device.expand = {} as {
|
||||
ports: Port[];
|
||||
groups: Group[];
|
||||
};
|
||||
}
|
||||
|
||||
if (!device.expand.ports) {
|
||||
device.expand.ports = [] as Port[];
|
||||
|
||||
@@ -43,15 +43,18 @@
|
||||
sol_password: '',
|
||||
sol_port: 0
|
||||
} as Device;
|
||||
let activeTab = $state('manual');
|
||||
|
||||
$: if (Object.hasOwn($permission, 'create')) {
|
||||
if (!$pocketbase.authStore.isSuperuser && !$permission.create) {
|
||||
toast(m.toasts_no_permission({ url: $page.url.pathname }), {
|
||||
icon: '⛔'
|
||||
});
|
||||
goto('/');
|
||||
$effect(() => {
|
||||
if (Object.hasOwn($permission, 'create')) {
|
||||
if (!$pocketbase.authStore.isSuperuser && !$permission.create) {
|
||||
toast(m.toasts_no_permission({ url: $page.url.pathname }), {
|
||||
icon: '⛔'
|
||||
});
|
||||
goto('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let tabs = [
|
||||
{
|
||||
@@ -67,7 +70,6 @@
|
||||
show: $pocketbase.authStore.isSuperuser
|
||||
}
|
||||
];
|
||||
let activeTab = 'manual';
|
||||
</script>
|
||||
|
||||
<h1 class="mb-8 text-3xl font-bold">{m.device_page_title()}</h1>
|
||||
@@ -76,7 +78,7 @@
|
||||
{#each tabs as tab (tab)}
|
||||
{#if tab.show}
|
||||
<li>
|
||||
<button class:menu-active={activeTab === tab.name} on:click={() => (activeTab = tab.name)}
|
||||
<button class:menu-active={activeTab === tab.name} onclick={() => (activeTab = tab.name)}
|
||||
>{tab.ll_name} <Fa icon={tab.icon} class="ms-2" /></button
|
||||
>
|
||||
</li>
|
||||
|
||||
@@ -73,7 +73,13 @@
|
||||
</figure>
|
||||
<h2 class="card-title">{m.login_welcome()}</h2>
|
||||
</div>
|
||||
<form class="w-full" on:submit|preventDefault={tryAdminThenUser}>
|
||||
<form
|
||||
class="w-full"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
tryAdminThenUser();
|
||||
}}
|
||||
>
|
||||
<label class="label" for="email">
|
||||
<span>{m.login_email_label()}</span>
|
||||
</label>
|
||||
@@ -85,8 +91,8 @@
|
||||
<div
|
||||
class="absolute top-1/2 right-4 -translate-y-1/2 cursor-pointer"
|
||||
role="none"
|
||||
on:click={() => toggleVisibility(inputPassword)}
|
||||
on:keydown={() => toggleVisibility(inputPassword)}
|
||||
onclick={() => toggleVisibility(inputPassword)}
|
||||
onkeydown={() => toggleVisibility(inputPassword)}
|
||||
>
|
||||
<Fa icon={faEye} />
|
||||
</div>
|
||||
@@ -118,7 +124,7 @@
|
||||
{#if authMethods.oauth2.enabled && authMethods.oauth2.providers.length > 0}
|
||||
{#each authMethods.oauth2.providers as provider (provider)}
|
||||
<li>
|
||||
<button type="button" on:click={() => loginWithProvider(provider)}
|
||||
<button type="button" onclick={() => loginWithProvider(provider)}
|
||||
>{provider.displayName}</button
|
||||
>
|
||||
</li>
|
||||
|
||||
@@ -107,7 +107,13 @@
|
||||
<PageLoading />
|
||||
{:else}
|
||||
<h1 class="mb-8 text-3xl font-bold">{m.settings_page_title()}</h1>
|
||||
<form on:submit|preventDefault={save}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
save();
|
||||
}}
|
||||
class="flex w-full flex-col"
|
||||
>
|
||||
<div class="card bg-base-200 mt-6 w-full shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{m.settings_ping_interval_title()}</h2>
|
||||
@@ -201,8 +207,8 @@ second (0–59, optional)
|
||||
/>
|
||||
<button
|
||||
class="btn btn-error"
|
||||
on:click={() => resetFavicon()}
|
||||
on:keydown={() => resetFavicon()}>{m.buttons_reset()}</button
|
||||
onclick={() => resetFavicon()}
|
||||
onkeydown={() => resetFavicon()}>{m.buttons_reset()}</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,24 +12,26 @@
|
||||
import Fa from 'svelte-fa';
|
||||
import toast from 'svelte-french-toast';
|
||||
|
||||
let getUsersPromise = getUsers();
|
||||
let getPermissionsPromise = getPermissions();
|
||||
let getDevicesPromise = getDevices();
|
||||
let deleteModal = [] as HTMLDialogElement[];
|
||||
let getUsersPromise = $state(getUsers());
|
||||
let getPermissionsPromise = $state(getPermissions());
|
||||
let getDevicesPromise = $state(getDevices());
|
||||
let deleteModal = $state([] as HTMLDialogElement[]);
|
||||
|
||||
let users = [] as User[];
|
||||
let permissions = [] as Permission[];
|
||||
let devices = [] as Device[];
|
||||
let newUser = {
|
||||
let users = $state([] as User[]);
|
||||
let permissions = $state([] as Permission[]);
|
||||
let devices = $state([] as Device[]);
|
||||
let newUser = $state({
|
||||
username: '',
|
||||
password: '',
|
||||
passwordConfirm: ''
|
||||
};
|
||||
});
|
||||
|
||||
$: users.map((user) => {
|
||||
if (!permissions.find((perm) => perm.user === user.id)) {
|
||||
permissions = [...permissions, { user: user.id } as Permission];
|
||||
}
|
||||
$effect(() => {
|
||||
users.map((user) => {
|
||||
if (!permissions.find((perm) => perm.user === user.id)) {
|
||||
permissions = [...permissions, { user: user.id } as Permission];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
@@ -183,7 +185,12 @@
|
||||
{:then}
|
||||
<h1 class="mb-8 text-3xl font-bold">{m.users_page_title()}</h1>
|
||||
{#each users as user, index (user.id)}
|
||||
<form on:submit|preventDefault={() => save(user)}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
save(user);
|
||||
}}
|
||||
>
|
||||
<div class="card bg-base-200 mt-6 w-full shadow-sm">
|
||||
<div class="card-body gap-4">
|
||||
<h2 class="card-title">
|
||||
@@ -255,7 +262,7 @@
|
||||
<button
|
||||
class="btn btn-sm md:col-start-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
toggleAllPermissions(user, 'read');
|
||||
}}
|
||||
><span class="hidden md:block">{m.users_toggle()}</span><Fa
|
||||
@@ -266,7 +273,7 @@
|
||||
<button
|
||||
class=" btn btn-sm"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
toggleAllPermissions(user, 'update');
|
||||
}}
|
||||
><span class="hidden md:block">{m.users_toggle()}</span><Fa
|
||||
@@ -277,7 +284,7 @@
|
||||
<button
|
||||
class=" btn btn-sm"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
toggleAllPermissions(user, 'delete');
|
||||
}}
|
||||
><span class="hidden md:block">{m.users_toggle()}</span><Fa
|
||||
@@ -288,7 +295,7 @@
|
||||
<button
|
||||
class=" btn btn-sm"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
toggleAllPermissions(user, 'power');
|
||||
}}
|
||||
><span class="hidden md:block">{m.users_toggle()}</span><Fa
|
||||
@@ -305,7 +312,7 @@
|
||||
<button
|
||||
class="btn btn-error join-item"
|
||||
type="button"
|
||||
on:click={() => deleteModal[index].showModal()}
|
||||
onclick={() => deleteModal[index].showModal()}
|
||||
><Fa icon={faTrash} />{m.buttons_delete()}</button
|
||||
>
|
||||
<button class="btn btn-success join-item" type="submit"
|
||||
@@ -322,7 +329,7 @@
|
||||
<div class="modal-action">
|
||||
<form method="dialog">
|
||||
<button class="btn">{m.buttons_cancel()}</button>
|
||||
<button class="btn btn-error" on:click={() => deleteUser(user)}
|
||||
<button class="btn btn-error" onclick={() => deleteUser(user)}
|
||||
>{m.buttons_delete()}</button
|
||||
>
|
||||
</form>
|
||||
@@ -333,7 +340,12 @@
|
||||
<div class="card bg-base-200 mt-6 w-full shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">{m.users_create_new_user()}</h2>
|
||||
<form on:submit|preventDefault={createUser}>
|
||||
<form
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
createUser();
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row flex-wrap gap-4">
|
||||
<div class="w-full max-w-xs">
|
||||
<label class="label" for="username">
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<h2 class="card-title">{m.welcome_not_expected_title()}</h2>
|
||||
<p>{m.welcome_not_expected_desc()}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-success" on:click={() => goto('/')}
|
||||
<button class="btn btn-success" onclick={() => goto('/')}
|
||||
>{m.welcome_not_expected_back()}</button
|
||||
>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
<h2 class="card-title">{m.welcome_step1_page_title()}</h2>
|
||||
<p>{m.welcome_step1_setup_desc()}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-success" on:click={() => (stepsCompleted = 1)}
|
||||
<button class="btn btn-success" onclick={() => (stepsCompleted = 1)}
|
||||
>{m.welcome_step1_setup_btn_next()} <Fa icon={faArrowRight} /></button
|
||||
>
|
||||
</div>
|
||||
@@ -98,7 +98,13 @@
|
||||
<figure class="w-16"><img src="/gopher.svg" alt="Gopher" /></figure>
|
||||
<h2 class="card-title">{m.welcome_step2_page_title()}</h2>
|
||||
</div>
|
||||
<form class="w-full" on:submit|preventDefault={register}>
|
||||
<form
|
||||
class="w-full"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
register();
|
||||
}}
|
||||
>
|
||||
<label class="label" for="email">
|
||||
<span>{m.welcome_step2_label_email()}</span>
|
||||
</label>
|
||||
@@ -111,8 +117,8 @@
|
||||
<div
|
||||
class="absolute top-1/2 right-4 -translate-y-1/2 cursor-pointer"
|
||||
role="none"
|
||||
on:click={() => toggleVisibility(inputPassword)}
|
||||
on:keydown={() => toggleVisibility(inputPassword)}
|
||||
onclick={() => toggleVisibility(inputPassword)}
|
||||
onkeydown={() => toggleVisibility(inputPassword)}
|
||||
>
|
||||
<Fa icon={faEye} />
|
||||
</div>
|
||||
@@ -134,8 +140,8 @@
|
||||
<div
|
||||
class="absolute top-1/2 right-4 -translate-y-1/2 cursor-pointer"
|
||||
role="none"
|
||||
on:click={() => toggleVisibility(inputConfirm)}
|
||||
on:keydown={() => toggleVisibility(inputConfirm)}
|
||||
onclick={() => toggleVisibility(inputConfirm)}
|
||||
onkeydown={() => toggleVisibility(inputConfirm)}
|
||||
>
|
||||
<Fa icon={faEye} />
|
||||
</div>
|
||||
@@ -163,7 +169,7 @@
|
||||
<h2 class="card-title">{m.welcome_step3_page_title()}</h2>
|
||||
<p>{m.welcome_step3_page_desc()}</p>
|
||||
<div class="card-actions justify-end">
|
||||
<button class="btn btn-success" on:click={() => goto('/')}
|
||||
<button class="btn btn-success" onclick={() => goto('/')}
|
||||
>{m.welcome_step3_btn_done()}</button
|
||||
>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user