mirror of
https://github.com/wireadmin/wireadmin
synced 2025-02-26 05:48:44 +00:00
feat: show total net usage and connection mode in server page
This commit is contained in:
parent
212363c7fa
commit
9dd427f90c
@ -3,12 +3,12 @@
|
|||||||
|
|
||||||
export let showInHover: boolean = false;
|
export let showInHover: boolean = false;
|
||||||
export let rootClass: string | undefined = undefined;
|
export let rootClass: string | undefined = undefined;
|
||||||
export let value: string;
|
export let value: string | number;
|
||||||
let className: string | undefined = undefined;
|
let className: string | undefined = undefined;
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value?.toString() || '');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ export const badgeVariants = tv({
|
|||||||
destructive:
|
destructive:
|
||||||
'bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground',
|
'bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground',
|
||||||
outline: 'text-foreground',
|
outline: 'text-foreground',
|
||||||
|
tor: 'bg-purple-700 hover:bg-purple-700/80 border-transparent text-white',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -11,6 +11,7 @@ import logger from '$lib/logger';
|
|||||||
import { sha256 } from '$lib/hash';
|
import { sha256 } from '$lib/hash';
|
||||||
import { fsAccess } from '$lib/fs-extra';
|
import { fsAccess } from '$lib/fs-extra';
|
||||||
import { getClient } from '$lib/redis';
|
import { getClient } from '$lib/redis';
|
||||||
|
import { execaCommand } from 'execa';
|
||||||
|
|
||||||
export class WGServer {
|
export class WGServer {
|
||||||
readonly id: string;
|
readonly id: string;
|
||||||
@ -133,25 +134,41 @@ export class WGServer {
|
|||||||
|
|
||||||
async hasInterface(): Promise<boolean> {
|
async hasInterface(): Promise<boolean> {
|
||||||
const server = await this.get();
|
const server = await this.get();
|
||||||
return await Network.checkInterfaceExists(`wg${server.confId}`);
|
try {
|
||||||
|
const res = await execaCommand(`wg show wg${server.confId}`);
|
||||||
|
return res.stdout.includes('wg');
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUsage(): Promise<WgUsage> {
|
async getUsage(): Promise<WgUsage> {
|
||||||
const server = await this.get();
|
const server = await this.get();
|
||||||
const hasInterface = await this.hasInterface();
|
const hasInterface = await this.hasInterface();
|
||||||
|
|
||||||
|
const usages: WgUsage = {
|
||||||
|
total: { rx: 0, tx: 0 },
|
||||||
|
peers: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
if (!hasInterface) {
|
if (!hasInterface) {
|
||||||
logger.error('GetUsage: interface does not exists');
|
logger.debug('GetUsage: interface does not exists');
|
||||||
return new Map();
|
return usages;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await Shell.exec(`wg show wg${server.confId} transfer`);
|
const { stdout, stderr } = await execaCommand(`wg show wg${server.confId} transfer`);
|
||||||
const lines = res.split('\n');
|
if (stderr) {
|
||||||
|
logger.warn(`WgServer: GetUsage: ${stderr}`);
|
||||||
|
return usages;
|
||||||
|
}
|
||||||
|
|
||||||
const usages: WgUsage = new Map();
|
const lines = stdout.split('\n');
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const [peer, rx, tx] = line.split('\t');
|
const [peer, tx, rx] = line.split('\t');
|
||||||
if (!peer) continue;
|
if (!peer) continue;
|
||||||
usages.set(peer, { rx: Number(rx), tx: Number(tx) });
|
usages.peers.set(peer, { rx: Number(rx), tx: Number(tx) });
|
||||||
|
usages.total.rx += Number(rx);
|
||||||
|
usages.total.tx += Number(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return usages;
|
return usages;
|
||||||
@ -180,7 +197,10 @@ export class WGServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WgUsage = Map<string, PeerUsage>;
|
export type WgUsage = {
|
||||||
|
total: PeerUsage;
|
||||||
|
peers: Map<string, PeerUsage>;
|
||||||
|
};
|
||||||
|
|
||||||
export type PeerUsage = {
|
export type PeerUsage = {
|
||||||
rx: number;
|
rx: number;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { type Actions, error } from '@sveltejs/kit';
|
import { type Actions, error, redirect } from '@sveltejs/kit';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
import { findServer, generateWgKey, WGServer } from '$lib/wireguard';
|
import { findServer, generateWgKey, WGServer } from '$lib/wireguard';
|
||||||
import { NameSchema } from '$lib/wireguard/schema';
|
import { NameSchema } from '$lib/wireguard/schema';
|
||||||
@ -8,10 +8,25 @@ import logger from '$lib/logger';
|
|||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
const { serverId } = params;
|
const { serverId } = params;
|
||||||
const server = await findServer(serverId);
|
const exists = await WGServer.exists(serverId ?? '');
|
||||||
|
|
||||||
if (server) {
|
if (exists) {
|
||||||
return { server };
|
const wg = new WGServer(serverId);
|
||||||
|
const server = await wg.get();
|
||||||
|
|
||||||
|
if (server.status === 'up') {
|
||||||
|
const hasInterface = await wg.hasInterface();
|
||||||
|
if (!hasInterface) {
|
||||||
|
await wg.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usage = await wg.getUsage();
|
||||||
|
|
||||||
|
return {
|
||||||
|
server,
|
||||||
|
usage,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error(404, 'Not found');
|
throw error(404, 'Not found');
|
||||||
@ -95,8 +110,8 @@ export const actions: Actions = {
|
|||||||
|
|
||||||
const server = await findServer(serverId ?? '');
|
const server = await findServer(serverId ?? '');
|
||||||
if (!server) {
|
if (!server) {
|
||||||
console.error('Server not found');
|
logger.error('Action: ChangeState: Server not found');
|
||||||
throw error(404, 'Not found');
|
throw redirect(303, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
@ -128,7 +143,7 @@ export const actions: Actions = {
|
|||||||
|
|
||||||
return { ok: true };
|
return { ok: true };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Exception:', e);
|
logger.error('Exception: ChangeState:', e);
|
||||||
throw error(500, 'Unhandled Exception');
|
throw error(500, 'Unhandled Exception');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { Card } from '$lib/components/ui/card';
|
|
||||||
import CreatePeerDialog from './CreatePeerDialog.svelte';
|
import CreatePeerDialog from './CreatePeerDialog.svelte';
|
||||||
import { CardContent, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
|
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import Peer from './Peer.svelte';
|
import Peer from './Peer.svelte';
|
||||||
import fetchAction from '$lib/fetch-action';
|
import fetchAction from '$lib/fetch-action';
|
||||||
@ -12,6 +11,8 @@
|
|||||||
import { MiddleEllipsis } from '$lib/components/middle-ellipsis';
|
import { MiddleEllipsis } from '$lib/components/middle-ellipsis';
|
||||||
import { goto, invalidateAll } from '$app/navigation';
|
import { goto, invalidateAll } from '$app/navigation';
|
||||||
import { Empty } from '$lib/components/empty';
|
import { Empty } from '$lib/components/empty';
|
||||||
|
import prettyBytes from 'pretty-bytes';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
@ -57,16 +58,13 @@
|
|||||||
const resp = await fetchAction({
|
const resp = await fetchAction({
|
||||||
action: '?/change-server-state',
|
action: '?/change-server-state',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
form: {
|
form: { state },
|
||||||
state,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
if (resp.statusText !== 'OK') {
|
if (resp.statusText !== 'OK') {
|
||||||
console.error('err: failed to change server state.');
|
console.error('error: failed to change server state.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('server state changed!');
|
|
||||||
if (state === 'remove') {
|
if (state === 'remove') {
|
||||||
await goto('/');
|
await goto('/');
|
||||||
return;
|
return;
|
||||||
@ -74,6 +72,16 @@
|
|||||||
|
|
||||||
await invalidateAll();
|
await invalidateAll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// revalidate every 2 seconds
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
invalidateAll();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
@ -87,15 +95,38 @@
|
|||||||
{data.server.name}
|
{data.server.name}
|
||||||
</CopiableText>
|
</CopiableText>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
|
|
||||||
|
{#if data.server.tor}
|
||||||
|
<DetailRow label={'Mode'}>
|
||||||
|
<Badge variant="tor">Tor</Badge>
|
||||||
|
</DetailRow>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<DetailRow label={'IP address'}>
|
<DetailRow label={'IP address'}>
|
||||||
<pre> {data.server.address}/24 </pre>
|
<pre> {data.server.address}/24 </pre>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
|
|
||||||
<DetailRow label={'Listen Port'}>
|
<DetailRow label={'Listen Port'}>
|
||||||
<pre> {data.server.listen} </pre>
|
<pre> {data.server.listen} </pre>
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
|
|
||||||
|
<DetailRow label={'Total Usage'}>
|
||||||
|
<div class="flex items-center gap-3 text-sm">
|
||||||
|
<div class="flex items-center gap-x-1.5">
|
||||||
|
<i class="fas fa-arrow-up text-gray-500"></i>
|
||||||
|
<span>{prettyBytes(data.usage.total.tx)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-x-1.5">
|
||||||
|
<i class="fas fa-arrow-down text-gray-500"></i>
|
||||||
|
<span>{prettyBytes(data.usage.total.rx)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DetailRow>
|
||||||
|
|
||||||
<DetailRow label={'Status'}>
|
<DetailRow label={'Status'}>
|
||||||
<Badge variant={data.server.status === 'up' ? 'success' : 'destructive'} />
|
<Badge variant={data.server.status === 'up' ? 'success' : 'destructive'} />
|
||||||
</DetailRow>
|
</DetailRow>
|
||||||
|
|
||||||
<DetailRow label={'Public Key'}>
|
<DetailRow label={'Public Key'}>
|
||||||
<CopiableText value={data.server.publicKey}>
|
<CopiableText value={data.server.publicKey}>
|
||||||
<MiddleEllipsis content={data.server.publicKey} maxLength={12} />
|
<MiddleEllipsis content={data.server.publicKey} maxLength={12} />
|
||||||
|
Loading…
Reference in New Issue
Block a user