connections page: update API, combine opnsense data with db data
This commit is contained in:
parent
76559d2931
commit
29fbccc953
@ -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
10
src/lib/connections.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export type ConnectionDetails = {
|
||||
deviceId: number;
|
||||
deviceName: string;
|
||||
devicePublicKey: string;
|
||||
deviceIps: string[];
|
||||
endpoint: string;
|
||||
transferRx: number;
|
||||
transferTx: number;
|
||||
latestHandshake: number;
|
||||
};
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user