mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-07-23 14:03:47 -04:00
273 lines
7.9 KiB
Svelte
273 lines
7.9 KiB
Svelte
<script lang="ts">
|
|
import { goto } from '$app/navigation';
|
|
import PageLoading from '$lib/components/PageLoading.svelte';
|
|
import LL from '$lib/i18n/i18n-svelte';
|
|
import { backendUrl, pocketbase } from '$lib/stores/pocketbase';
|
|
import { settingsPriv } from '$lib/stores/settings';
|
|
import type { Device } from '$lib/types/device';
|
|
import type { ScanResponse, ScannedDevice } from '$lib/types/scan';
|
|
import type { SettingsPrivate } from '$lib/types/settings';
|
|
import { faMagnifyingGlass, faPlus, faX } from '@fortawesome/free-solid-svg-icons';
|
|
import { onMount } from 'svelte';
|
|
import Fa from 'svelte-fa';
|
|
import toast from 'svelte-french-toast';
|
|
|
|
let scanRange = '';
|
|
let scanRunning = false;
|
|
let scanResponse: ScanResponse = {
|
|
netmask: '',
|
|
devices: []
|
|
};
|
|
let addAllCheckbox = true;
|
|
let replaceNetmaskCheckbox = false;
|
|
let replaceNetmask = '';
|
|
|
|
onMount(() => {
|
|
if (!$settingsPriv) {
|
|
$pocketbase
|
|
.collection('settings_private')
|
|
.getFirstListItem('')
|
|
.then((res) => {
|
|
settingsPriv.set(res as SettingsPrivate);
|
|
scanRange = $settingsPriv.scan_range;
|
|
})
|
|
.catch((err) => {
|
|
toast.error(err.message);
|
|
});
|
|
} else {
|
|
scanRange = $settingsPriv.scan_range;
|
|
}
|
|
});
|
|
|
|
function saveSettings() {
|
|
$pocketbase
|
|
.collection('settings_private')
|
|
.update($settingsPriv.id, {
|
|
scan_range: scanRange
|
|
})
|
|
.then((res) => {
|
|
settingsPriv.set(res as SettingsPrivate);
|
|
toast.success($LL.device.network_scan_range_saved());
|
|
})
|
|
.catch((err) => {
|
|
toast.error(err.message);
|
|
});
|
|
}
|
|
|
|
function scan() {
|
|
scanRunning = true;
|
|
fetch(`${backendUrl}api/upsnap/scan`, {
|
|
headers: {
|
|
Authorization: $pocketbase.authStore.token
|
|
}
|
|
})
|
|
.then(async (resp) => {
|
|
if (resp.ok) {
|
|
return resp.json();
|
|
} else {
|
|
return Promise.reject(await resp.json());
|
|
}
|
|
})
|
|
.then((data) => {
|
|
scanResponse = data as ScanResponse;
|
|
})
|
|
.catch((err) => {
|
|
toast.error(err);
|
|
})
|
|
.finally(() => (scanRunning = false));
|
|
}
|
|
|
|
async function createDevice(device: ScannedDevice): Promise<Device> {
|
|
if (replaceNetmaskCheckbox) {
|
|
device.netmask = replaceNetmask;
|
|
} else {
|
|
device.netmask = scanResponse.netmask;
|
|
}
|
|
return $pocketbase.collection('devices').create(device);
|
|
}
|
|
|
|
async function addSingle(device: ScannedDevice) {
|
|
await createDevice(device)
|
|
.then(() => {
|
|
toast.success($LL.toasts.device_created({ device: device.name }));
|
|
})
|
|
.catch((err) => {
|
|
toast.error(err.message);
|
|
});
|
|
}
|
|
|
|
async function addAll() {
|
|
let count = 0;
|
|
await Promise.all(
|
|
scanResponse.devices.map(async (dev) => {
|
|
if (!addAllCheckbox && dev.name === 'Unknown') return;
|
|
await createDevice(dev)
|
|
.catch((err) => {
|
|
toast.error(err.message);
|
|
})
|
|
.then(() => {
|
|
count += 1;
|
|
});
|
|
})
|
|
);
|
|
toast.success($LL.toasts.devices_created_multiple({ count: count }));
|
|
goto('/');
|
|
}
|
|
</script>
|
|
|
|
{#if $settingsPriv}
|
|
<div class="card w-full bg-base-300 shadow-xl mt-6">
|
|
<div class="card-body">
|
|
<h2 class="card-title">{$LL.device.tabs[1]()}</h2>
|
|
<p class="my-2">
|
|
{$LL.device.network_scan_desc()}
|
|
</p>
|
|
<div class="flex flex-row flex-wrap gap-4 items-end">
|
|
<form on:submit|preventDefault={saveSettings}>
|
|
<label class="label" for="scan-range">
|
|
<span class="label-text">{$LL.device.network_scan_ip_range()}</span>
|
|
</label>
|
|
<div class="join max-w-xs">
|
|
<input
|
|
id="scan-range"
|
|
class="input input-bordered join-item w-full"
|
|
type="text"
|
|
placeholder="192.168.1.0/24"
|
|
bind:value={scanRange}
|
|
/>
|
|
<button class="btn btn-primary join-item" type="submit">{$LL.buttons.save()}</button>
|
|
</div>
|
|
</form>
|
|
<div>
|
|
<div>
|
|
{#if !$settingsPriv.scan_range}
|
|
<button class="btn btn-error" disabled>
|
|
<Fa icon={faX} />
|
|
{$LL.device.network_scan_no_range()}
|
|
</button>
|
|
{:else if scanRange !== $settingsPriv.scan_range}
|
|
<button class="btn btn-error" disabled>
|
|
<Fa icon={faX} />
|
|
{$LL.device.network_scan_unsaved_changes()}
|
|
</button>
|
|
{:else if scanRunning}
|
|
<button class="btn no-animation">
|
|
<span class="loading loading-spinner" />
|
|
{$LL.device.network_scan_running()}
|
|
</button>
|
|
{:else}
|
|
<button class="btn btn-success" on:click={() => scan()}>
|
|
<Fa icon={faMagnifyingGlass} />
|
|
{$LL.device.network_scan()}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{#if scanResponse.devices?.length > 0}
|
|
{#each scanResponse.devices.sort( (a, b) => a.ip.localeCompare( b.ip, undefined, { numeric: true } ) ) as device, index}
|
|
<div class="collapse collapse-arrow bg-base-200">
|
|
<input type="radio" name="scanned-devices" checked={index === 0} />
|
|
<div class="collapse-title font-bold">
|
|
{device.name} <span class="badge">{device.ip}</span>
|
|
</div>
|
|
<div class="collapse-content">
|
|
<div class="flex flex-row flex-wrap gap-4">
|
|
<div>
|
|
<strong>{$LL.device.network_scan_ip()}</strong><br />
|
|
{device.ip}
|
|
</div>
|
|
<div>
|
|
<strong>{$LL.device.network_scan_mac()}</strong><br />
|
|
{device.mac}
|
|
</div>
|
|
<div>
|
|
<strong>{$LL.device.network_scan_mac_vendor()}</strong><br />
|
|
{device.mac_vendor}
|
|
</div>
|
|
|
|
<div>
|
|
<strong>{$LL.device.network_scan_netmask()}</strong><br />
|
|
{scanResponse.netmask}
|
|
</div>
|
|
<div class="ms-auto">
|
|
<button
|
|
class="btn btn-sm btn-success"
|
|
on:click={(e) => {
|
|
addSingle(device);
|
|
e.currentTarget.disabled = true;
|
|
}}><Fa icon={faPlus} />{$LL.buttons.add()}</button
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
<h2 class="card-title mt-4">{$LL.device.network_scan_add_all()}</h2>
|
|
<div class="form-control max-w-fit">
|
|
<label class="label cursor-pointer">
|
|
<input type="checkbox" class="checkbox" bind:checked={replaceNetmaskCheckbox} />
|
|
<span class="label-text ms-2">{$LL.device.network_scan_replace_netmask()}</span>
|
|
</label>
|
|
</div>
|
|
{#if replaceNetmaskCheckbox}
|
|
<div class="form-control max-w-fit">
|
|
<label class="label cursor-pointer" for="replaceNetmaskInput">
|
|
<span class="label-text ms-2">{$LL.device.network_scan_new_netmask()}</span>
|
|
</label>
|
|
<input
|
|
id="replaceNetmaskInput"
|
|
class="input input-bordered"
|
|
type="text"
|
|
placeholder="255.255.255.0"
|
|
bind:value={replaceNetmask}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
{#if scanResponse.devices.find((dev) => dev.name === 'Unknown')}
|
|
<div class="form-control max-w-fit">
|
|
<label class="label cursor-pointer">
|
|
<input type="checkbox" class="checkbox" bind:checked={addAllCheckbox} />
|
|
<span class="label-text ms-2">{$LL.device.network_scan_include_unknown()}</span>
|
|
</label>
|
|
{#if addAllCheckbox}
|
|
<button
|
|
class="btn btn-success"
|
|
on:click={() => addAll()}
|
|
disabled={scanResponse.devices.length === 0}
|
|
>
|
|
<Fa icon={faPlus} />
|
|
{$LL.device.network_scan_add_all()} ({scanResponse.devices.length})
|
|
</button>
|
|
{:else}
|
|
<button
|
|
class="btn btn-success"
|
|
on:click={() => addAll()}
|
|
disabled={scanResponse.devices.filter((dev) => dev.name !== 'Unknown').length === 0}
|
|
>
|
|
<Fa icon={faPlus} />
|
|
{$LL.device.network_scan_add_all()} ({scanResponse.devices.filter(
|
|
(dev) => dev.name !== 'Unknown'
|
|
).length})
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
{:else}
|
|
<div class="form-control max-w-fit">
|
|
<button
|
|
class="btn btn-success"
|
|
on:click={() => addAll()}
|
|
disabled={scanResponse.devices.length === 0}
|
|
>
|
|
<Fa icon={faPlus} />
|
|
{$LL.device.network_scan_add_all()} ({scanResponse.devices.length})
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<PageLoading />
|
|
{/if}
|