connections page: update API, combine opnsense data with db data

This commit is contained in:
Yuri Tatishchev 2025-01-07 18:51:24 -08:00
parent 76559d2931
commit 29fbccc953
Signed by: CaZzzer
GPG Key ID: E0EBF441EA424369
7 changed files with 75 additions and 37 deletions

View File

@ -88,7 +88,7 @@
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--surface: 217.2 40.6% 10.5%;
--surface: 217.2 40.6% 11.5%;
}
}
}

10
src/lib/connections.ts Normal file
View File

@ -0,0 +1,10 @@
export type ConnectionDetails = {
deviceId: number;
deviceName: string;
devicePublicKey: string;
deviceIps: string[];
endpoint: string;
transferRx: number;
transferTx: number;
latestHandshake: number;
};

View File

@ -2,11 +2,54 @@ import { error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { opnsenseAuth, opnsenseUrl } from '$lib/server/opnsense';
import type { OpnsenseWgPeers } from '$lib/opnsense/wg';
import { findDevices } from '$lib/server/devices';
import type { ConnectionDetails } from '$lib/connections';
export const GET: RequestHandler = async (event) => {
if (!event.locals.user) {
return error(401, 'Unauthorized');
}
console.debug('/api/connections');
const peers = await fetchOpnsensePeers(event.locals.user.username);
console.debug('/api/connections: fetched opnsense peers', peers.rowCount);
const devices = await findDevices(event.locals.user.id);
console.debug('/api/connections: fetched db devices');
if (!peers) {
return error(500, 'Error getting info from OPNsense API');
}
// TODO: this is all garbage performance
// filter devices with no recent handshakes
peers.rows = peers.rows.filter((peer) => peer['latest-handshake']);
// start from devices, to treat db as the source of truth
const connections: ConnectionDetails[] = [];
for (const device of devices) {
const peerData = peers.rows.find((peer) => peer['public-key'] === device.publicKey);
if (!peerData) continue;
connections.push({
deviceId: device.id,
deviceName: device.name,
devicePublicKey: device.publicKey,
deviceIps: peerData['allowed-ips'].split(','),
endpoint: peerData['endpoint'],
// swap rx and tx, since the opnsense values are from the server perspective
transferRx: peerData['transfer-tx'],
transferTx: peerData['transfer-rx'],
latestHandshake: peerData['latest-handshake'] * 1000,
});
}
return new Response(JSON.stringify(connections), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'max-age=5',
},
});
};
async function fetchOpnsensePeers(username: string) {
const apiUrl = `${opnsenseUrl}/api/wireguard/service/show`;
const options: RequestInit = {
method: 'POST',
@ -16,28 +59,17 @@ export const GET: RequestHandler = async (event) => {
'Content-Type': 'application/json',
},
body: JSON.stringify({
'current': 1,
current: 1,
// "rowCount": 7,
'sort': {},
sort: {},
// TODO: use a more unique search phrase
// unfortunately 64 character limit,
// but it should be fine if users can't change their own username
'searchPhrase': `vpgen-${event.locals.user.username}`,
'type': ['peer'],
searchPhrase: `vpgen-${username}`,
type: ['peer'],
}),
};
const res = await fetch(apiUrl, options);
const peers = await res.json() as OpnsenseWgPeers;
peers.rows = peers.rows.filter(peer => peer['latest-handshake'])
if (!peers) {
return error(500, 'Error getting info from OPNsense API');
}
return new Response(JSON.stringify(peers), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'max-age=5',
}
});
};
return (await res.json()) as OpnsenseWgPeers;
}

View File

@ -16,7 +16,7 @@
return () => clearInterval(interval);
});
function getSize(size: number) {
function toSizeString(size: number) {
let sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
for (let i = 1; i < sizes.length; i++) {
@ -34,35 +34,31 @@
<Table.Root class="divide-y-2 divide-background overflow-hidden rounded-lg bg-accent">
<Table.Header>
<Table.Row>
<Table.Head scope="col">Name</Table.Head>
<Table.Head scope="col">Device</Table.Head>
<Table.Head scope="col">Public Key</Table.Head>
<Table.Head scope="col">Endpoint</Table.Head>
<Table.Head scope="col">Allowed IPs</Table.Head>
<Table.Head scope="col">Device IPs</Table.Head>
<Table.Head scope="col">Latest Handshake</Table.Head>
<Table.Head scope="col">RX</Table.Head>
<Table.Head scope="col">TX</Table.Head>
<Table.Head scope="col" class="hidden">Persistent Keepalive</Table.Head>
<Table.Head scope="col" class="hidden">Interface Name</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body class="divide-y-2 divide-background">
{#each data.peers.rows as peer}
{#each data.connections as conn}
<Table.Row class="hover:bg-surface">
<Table.Head scope="row">{peer.name}</Table.Head>
<Table.Cell class="max-w-[10ch] truncate">{peer['public-key']}</Table.Cell>
<Table.Cell>{peer.endpoint}</Table.Cell>
<Table.Head scope="row">{conn.deviceName}</Table.Head>
<Table.Cell class="max-w-[10ch] truncate">{conn.devicePublicKey}</Table.Cell>
<Table.Cell>{conn.endpoint}</Table.Cell>
<Table.Cell>
<div class="flex flex-wrap gap-1">
{#each peer['allowed-ips'].split(',') as addr}
{#each conn.deviceIps as addr}
<Badge class="select-auto bg-background" variant="secondary">{addr}</Badge>
{/each}
</div>
</Table.Cell>
<Table.Cell>{new Date(peer['latest-handshake'] * 1000).toLocaleString()}</Table.Cell>
<Table.Cell>{getSize(peer['transfer-rx'])}</Table.Cell>
<Table.Cell>{getSize(peer['transfer-tx'])}</Table.Cell>
<Table.Cell class="hidden">{peer['persistent-keepalive']}</Table.Cell>
<Table.Cell class="hidden">{peer.ifname}</Table.Cell>
<Table.Cell>{new Date(conn.latestHandshake).toLocaleString()}</Table.Cell>
<Table.Cell>{toSizeString(conn.transferRx)}</Table.Cell>
<Table.Cell>{toSizeString(conn.transferTx)}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>

View File

@ -1,9 +1,9 @@
import type { PageLoad } from './$types';
import type { OpnsenseWgPeers } from '$lib/opnsense/wg';
import type { ConnectionDetails } from '$lib/connections';
export const load: PageLoad = async ({ fetch }) => {
const res = await fetch('/api/connections');
const peers = await res.json() as OpnsenseWgPeers;
const connections = await res.json() as ConnectionDetails[];
return { peers };
return { connections };
};

View File

@ -42,7 +42,7 @@
<Table.Row class="hover:bg-surface group">
<Table.Head scope="row">
<a
href={`/devices/${device.id}`}
href="/devices/{device.id}"
class="flex size-full items-center group-hover:underline"
>
{device.name}