Files
UpSnap/frontend/src/lib/components/DeviceCardNic.svelte
Maxi Quoß f53b93a3a5 feat: replace typesafe-i18n with inlang/paraglide-js
typesafe-i18n is no longer maintained
2025-03-05 04:07:25 +01:00

227 lines
6.3 KiB
Svelte

<script lang="ts">
import { m } from '$lib/paraglide/messages';
import { backendUrl, permission, pocketbase } from '$lib/stores/pocketbase';
import type { Device } from '$lib/types/device';
import { faPowerOff } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
import toast from 'svelte-french-toast';
export let device: Device;
let hoverText = '';
let disabled = false;
let timeout = 120;
var interval: number;
let modalWake: HTMLDialogElement;
let modalShutdown: HTMLDialogElement;
$: if (device.status === 'pending' && !interval) {
countdown(Date.parse(device.updated));
}
$: 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();
}
} 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() {
fetch(`${backendUrl}api/upsnap/wake/${device.id}`, {
headers: {
Authorization: $pocketbase.authStore.token
}
})
.then((resp) => resp.json())
.then(async (data) => {
device = data as Device;
await countdown(Date.parse(device.updated));
if (device.status === 'online' && device.link && device.link_open !== '') {
if (device.link_open === 'new_tab') {
window.open(device.link, '_blank');
} else {
window.open(device.link, '_self');
}
}
})
.catch((err) => {
toast.error(err.message);
});
}
function shutdown() {
fetch(`${backendUrl}api/upsnap/shutdown/${device.id}`, {
headers: {
Authorization: $pocketbase.authStore.token
}
})
.then((resp) => resp.json())
.then((data) => {
device = data as Device;
countdown(Date.parse(device.updated));
})
.catch((err) => {
toast.error(err.message);
});
}
function countdown(updated: number) {
return new Promise((resolve, reject) => {
try {
timeout = 120;
const end = updated + 2 * 60 * 1000;
if (interval) {
clearInterval(interval);
interval = 0;
}
interval = setInterval(() => {
timeout = Math.round((end - Date.now()) / 1000);
if (timeout <= 0 || device.status !== 'pending') {
clearInterval(interval);
interval = 0;
resolve(interval);
}
}, 1000);
} catch (error) {
reject(error);
}
});
}
function handleClick() {
if (device.status === 'offline') {
if (device.wake_confirm) {
askConfirmation('wake');
} else {
wake();
}
} else if (device.status === 'online') {
if (device.shutdown_confirm) {
askConfirmation('shutdown');
} else {
shutdown();
}
}
}
function askConfirmation(action: string) {
if (action === 'wake') {
modalWake.showModal();
} else {
modalShutdown.showModal();
}
}
</script>
<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}
role="none"
>
{#if device.status === 'offline'}
<button class="btn btn-error btn-circle size-12"><Fa icon={faPowerOff} /></button>
{:else if device.status === 'online'}
<button
class="btn btn-success btn-circle size-12"
class:cursor-not-allowed={device.shutdown_cmd === ''}><Fa icon={faPowerOff} /></button
>
{:else if device.status === 'pending'}
<button class="btn btn-warning">
<span class="countdown font-mono">
<span style="--value:{minutes};"></span>:
<span style="--value:{seconds};"></span>
</span>
</button>
{:else}
<div class="btn btn-warning btn-circle size-12">
<span class="loading loading-ring loading-sm"></span>
</div>
{/if}
<div class="grow">
<div class="text-lg leading-4 font-bold">{device.ip}</div>
<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}
<span class="flex items-center gap-1 break-all">
{#if port.status}
<div class="inline-grid *:[grid-area:1/1]">
<div class="status status-success h-3 w-3 animate-ping"></div>
<div class="status status-success h-3 w-3"></div>
</div>
{:else}
<div class="inline-grid *:[grid-area:1/1]">
<div class="status status-error h-3 w-3 animate-ping"></div>
<div class="status status-error h-3 w-3"></div>
</div>
{/if}
{#if port.link}
<a
href={port.link}
target="_blank"
class="underline"
on:click={(e) => e.stopPropagation()}>{port.name} ({port.number})</a
>
{:else}
{port.name} ({port.number})
{/if}
</span>
{/each}
{/if}
</div>
</div>
</div>
<dialog class="modal" bind:this={modalWake}>
<div class="modal-box">
<h3 class="text-lg font-bold">
{m.device_modal_confirm_wake_title({ device: device.name })}
</h3>
<p class="py-4">{m.device_modal_confirm_wake_desc({ device: device.name })}</p>
<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>
</form>
</div>
</div>
</dialog>
<dialog class="modal" bind:this={modalShutdown}>
<div class="modal-box">
<h3 class="text-lg font-bold">
{m.device_modal_confirm_shutdown_title({ device: device.name })}
</h3>
<p class="py-4">{m.device_modal_confirm_shutdown_desc({ device: device.name })}</p>
<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>
</form>
</div>
</div>
</dialog>