refactor: Component-ify interface and peer

Signed-off-by: Christoph Heiss <contact@christoph-heiss.at>
This commit is contained in:
Christoph Heiss 2022-07-08 15:22:28 +02:00
parent 00e43ad112
commit f5b04e9c9f
Signed by: c8h4
GPG key ID: 9C82009BEEDEA0FF
4 changed files with 228 additions and 186 deletions

View file

@ -0,0 +1,65 @@
import Peer from 'components/peer';
import type { WireguardInterface } from 'lib/wireguard';
import { formatTraffic } from 'lib/utils';
export default function Interface(props: WireguardInterface) {
const {
name,
up,
rxBytes,
txBytes,
listenPort,
publicKey,
peers,
} = props;
return (
<div className="md:flex">
<aside className="w-2/12 content">
<h2 style={{ marginBottom: -5 }}>
{name}
</h2>
{up
? <div className="chip ~positive">Up</div>
: <div className="chip ~critical">Down</div>}
</aside>
<div className="md:w-10/12 content">
<div className="flex">
<div className="grow">
<h4>
Listen port
</h4>
{listenPort}
</div>
<div className="grow">
<h4>
RX traffic
</h4>
{formatTraffic(rxBytes)}
</div>
<div className="grow">
<h4>
TX traffic
</h4>
{formatTraffic(txBytes)}
</div>
</div>
<div>
<h4>
Public key
</h4>
<code>
{publicKey}
</code>
</div>
{peers.map((peer) => (
<Peer key={peer.publicKey} {...peer} />
))}
</div>
</div>
);
}

101
src/components/peer.tsx Normal file
View file

@ -0,0 +1,101 @@
import type { WireguardPeer } from 'lib/wireguard';
import { formatKeepAlive, formatTimeDelta, formatTraffic } from 'lib/utils';
export default function Peer(props: WireguardPeer) {
const {
publicKey,
hasPresharedKey,
endpoint,
keepAlive,
lastHandshake,
rxBytes,
txBytes,
allowedIps,
} = props;
return (
<div className="card">
<span className="chip ~neutral">
Peer
</span>
{!endpoint && (
<span className="chip ~critical ml-2">
Down
</span>
)}
{hasPresharedKey
? (
<span className="chip ~positive ml-2">
Has preshared key
</span>
) : (
<span className="chip ~critical ml-2">
No preshared key
</span>
)}
<hr className="sep h-2" />
<div className="flex mb-4">
<div className="basis-1/4">
<h4>
Endpoint
</h4>
{endpoint}
</div>
<div className="basis-1/2">
<h4>
RX traffic
</h4>
{formatTraffic(rxBytes)}
</div>
<div className="basis-1/4">
<h4>
TX traffic
</h4>
{formatTraffic(txBytes)}
</div>
</div>
<div className="flex mb-4">
<div className="basis-1/4">
<h4>
Keep alive
</h4>
{formatKeepAlive(keepAlive)}
</div>
<div className="basis-1/2">
<h4>
Last handshake
</h4>
{formatTimeDelta(lastHandshake)}
{' '}
ago
</div>
<div className="basis-1/4">
<h4>
Allowed IPs
</h4>
{allowedIps.map((ip) => (
<>
<span>{ip}</span>
<br />
</>
))}
</div>
</div>
<div>
<h4>
Public key
</h4>
<code>
{publicKey}
</code>
</div>
</div>
);
}

View file

@ -1,3 +1,5 @@
import { Temporal } from '@js-temporal/polyfill';
export function classNames(...args: any[]): string {
const classes = [];
@ -57,3 +59,58 @@ export async function apiGet(url: string): Promise<any> {
return Promise.resolve({});
});
}
export function formatTraffic(b: number): string {
if (b < 1024 * 1024) {
return `${(b / 1024).toFixed(2)} KiB`;
}
if (b < 1024 * 1024 * 1024) {
return `${(b / 1024 / 1024).toFixed(2)} MiB`;
}
return `${(b / 1024 / 1024 / 1024).toFixed(2)} GiB`;
}
export function formatTimeDelta(date: string): string {
let result = '';
try {
const from = Temporal.Instant.from(date).toZonedDateTimeISO(Temporal.Now.timeZone());
const delta = Temporal.Now.zonedDateTimeISO().since(from, {
largestUnit: 'day',
smallestUnit: 'second',
});
if (delta.days > 0) {
result += `${delta.days}d `;
}
if (delta.hours > 0) {
result += `${delta.hours}h `;
}
if (delta.minutes > 0) {
result += `${delta.minutes}min `;
}
if (delta.seconds > 0) {
result += `${delta.seconds}s `;
}
// return delta.toLocaleString('en-US', {
// hours: 'narrow',
// minutes: 'narrow',
// seconds: 'narrow',
// });
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
return result;
}
export function formatKeepAlive(keepAlive: number): string {
return keepAlive !== 0 ? `${keepAlive}s` : 'disabled';
}

View file

@ -1,64 +1,9 @@
import { useEffect, useState } from 'react';
import BaseLayout from 'components/base-layout';
import Interface from 'components/interface';
import { withSessionSsr } from 'lib/withSession';
import { apiGet } from 'lib/utils';
import type { WireguardInterface } from 'lib/wireguard';
import { Temporal } from '@js-temporal/polyfill';
function formatTraffic(b: number): string {
if (b < 1024 * 1024) {
return `${(b / 1024).toFixed(2)} KiB`;
}
if (b < 1024 * 1024 * 1024) {
return `${(b / 1024 / 1024).toFixed(2)} MiB`;
}
return `${(b / 1024 / 1024 / 1024).toFixed(2)} GiB`;
}
function formatTimeDelta(date: string): string {
let result = '';
try {
const from = Temporal.Instant.from(date).toZonedDateTimeISO(Temporal.Now.timeZone());
const delta = Temporal.Now.zonedDateTimeISO().since(from, {
largestUnit: 'day',
smallestUnit: 'second',
});
if (delta.days > 0) {
result += `${delta.days}d `;
}
if (delta.hours > 0) {
result += `${delta.hours}h `;
}
if (delta.minutes > 0) {
result += `${delta.minutes}min `;
}
if (delta.seconds > 0) {
result += `${delta.seconds}s `;
}
// return delta.toLocaleString('en-US', {
// hours: 'narrow',
// minutes: 'narrow',
// seconds: 'narrow',
// });
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
return result;
}
function formatKeepAlive(keepAlive: number): string {
return keepAlive !== 0 ? `${keepAlive}s` : 'disabled';
}
interface IndexProps {
user: string;
@ -71,7 +16,9 @@ export default function Index({ user }: IndexProps) {
const fetchData = async () => {
const res = await apiGet('/api/interfaces');
if (typeof res.error === 'string') {
if (!res || typeof res.error === 'string') {
// eslint-disable-next-line no-console
console.log(res);
setFetchFailed(true);
} else {
setFetchFailed(false);
@ -101,135 +48,7 @@ export default function Index({ user }: IndexProps) {
</>
)}
{interfaces.map((wgif) => (
<div className="md:flex" key={wgif.index}>
<aside className="w-2/12 content">
<h2 style={{ marginBottom: -5 }}>
{wgif.name}
</h2>
{wgif.up
? <div className="chip ~positive">Up</div>
: <div className="chip ~critical">Down</div>}
</aside>
<div className="md:w-10/12 content">
<div className="flex">
<div className="grow">
<h4>
Listen port
</h4>
{wgif.listenPort}
</div>
<div className="grow">
<h4>
RX traffic
</h4>
{formatTraffic(wgif.rxBytes)}
</div>
<div className="grow">
<h4>
TX traffic
</h4>
{formatTraffic(wgif.txBytes)}
</div>
</div>
<div>
<h4>
Public key
</h4>
<code>
{wgif.publicKey}
</code>
</div>
{wgif.peers.map((peer) => (
<div key={peer.publicKey} className="card">
<span className="chip ~neutral">
Peer
</span>
{!peer.endpoint && (
<span className="chip ~critical ml-2">
Down
</span>
)}
{peer.hasPresharedKey
? (
<span className="chip ~positive ml-2">
Has preshared key
</span>
) : (
<span className="chip ~critical ml-2">
No preshared key
</span>
)}
<hr className="sep h-2" />
<div className="flex mb-4">
<div className="basis-1/4">
<h4>
Endpoint
</h4>
{peer.endpoint}
</div>
<div className="basis-1/2">
<h4>
RX traffic
</h4>
{formatTraffic(peer.rxBytes)}
</div>
<div className="basis-1/4">
<h4>
TX traffic
</h4>
{formatTraffic(peer.txBytes)}
</div>
</div>
<div className="flex mb-4">
<div className="basis-1/4">
<h4>
Keep alive
</h4>
{formatKeepAlive(peer.keepAlive)}
</div>
<div className="basis-1/2">
<h4>
Last handshake
</h4>
{formatTimeDelta(peer.lastHandshake)}
{' '}
ago
</div>
<div className="basis-1/4">
<h4>
Allowed IPs
</h4>
{peer.allowedIps.map((ip) => (
<>
<span>{ip}</span>
<br />
</>
))}
</div>
</div>
<div>
<h4>
Public key
</h4>
<code>
{peer.publicKey}
</code>
</div>
</div>
))}
</div>
</div>
<Interface key={wgif.index} {...wgif} />
))}
</BaseLayout>
);