Init React App
Signed-off-by: Matheus Sampaio Queiroga <srherobrine20@gmail.com>
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -9,4 +9,7 @@ src/**/*.d.ts
|
||||
src/**/*.js
|
||||
|
||||
# Project
|
||||
*.db
|
||||
*.db
|
||||
|
||||
# Nextjs
|
||||
.next/
|
@ -154,8 +154,11 @@ export const wg_peer = db.define<PeerWgSequelize>("peers", {
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
Promise.all([
|
||||
user.sync(),
|
||||
wg_interfaces.sync(),
|
||||
wg_peer.sync(),
|
||||
]);
|
||||
]).then(() => console.info("Databases created"), err => {
|
||||
console.error(err);
|
||||
process.exit(-1);
|
||||
});
|
181
lib/key.ts
Normal file
181
lib/key.ts
Normal file
@ -0,0 +1,181 @@
|
||||
function gf(init?: number[]) {
|
||||
var r = new Float64Array(16);
|
||||
if (init) {
|
||||
for (var i = 0; i < init.length; ++i)
|
||||
r[i] = init[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function pack(o, n) {
|
||||
var b, m = gf(), t = gf();
|
||||
for (var i = 0; i < 16; ++i)
|
||||
t[i] = n[i];
|
||||
carry(t);
|
||||
carry(t);
|
||||
carry(t);
|
||||
for (var j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed;
|
||||
for (var i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
||||
m[i - 1] &= 0xffff;
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
||||
b = (m[15] >> 16) & 1;
|
||||
m[14] &= 0xffff;
|
||||
cswap(t, m, 1 - b);
|
||||
}
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff;
|
||||
o[2 * i + 1] = t[i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
function carry(o) {
|
||||
var c;
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
|
||||
o[i] &= 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
function cswap(p, q, b) {
|
||||
var t, c = ~(b - 1);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i]);
|
||||
p[i] ^= t;
|
||||
q[i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
function add(o, a, b) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] + b[i]) | 0;
|
||||
}
|
||||
|
||||
function subtract(o, a, b) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] - b[i]) | 0;
|
||||
}
|
||||
|
||||
function multmod(o, a, b) {
|
||||
var t = new Float64Array(31);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
for (var j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j];
|
||||
}
|
||||
for (var i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16];
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = t[i];
|
||||
carry(o);
|
||||
carry(o);
|
||||
}
|
||||
|
||||
function invert(o, i) {
|
||||
var c = gf();
|
||||
for (var a = 0; a < 16; ++a)
|
||||
c[a] = i[a];
|
||||
for (var a = 253; a >= 0; --a) {
|
||||
multmod(c, c, c);
|
||||
if (a !== 2 && a !== 4)
|
||||
multmod(c, c, i);
|
||||
}
|
||||
for (var a = 0; a < 16; ++a)
|
||||
o[a] = c[a];
|
||||
}
|
||||
|
||||
function clamp(z) {
|
||||
z[31] = (z[31] & 127) | 64;
|
||||
z[0] &= 248;
|
||||
}
|
||||
|
||||
export function generatePublicKey(privateKey: Uint8Array) {
|
||||
var r, z = new Uint8Array(32);
|
||||
var a = gf([1]),
|
||||
b = gf([9]),
|
||||
c = gf(),
|
||||
d = gf([1]),
|
||||
e = gf(),
|
||||
f = gf(),
|
||||
_121665 = gf([0xdb41, 1]),
|
||||
_9 = gf([9]);
|
||||
for (var i = 0; i < 32; ++i)
|
||||
z[i] = privateKey[i];
|
||||
clamp(z);
|
||||
for (var i = 254; i >= 0; --i) {
|
||||
r = (z[i >>> 3] >>> (i & 7)) & 1;
|
||||
cswap(a, b, r);
|
||||
cswap(c, d, r);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
add(c, b, d);
|
||||
subtract(b, b, d);
|
||||
multmod(d, e, e);
|
||||
multmod(f, a, a);
|
||||
multmod(a, c, a);
|
||||
multmod(c, b, e);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
multmod(b, a, a);
|
||||
subtract(c, d, f);
|
||||
multmod(a, c, _121665);
|
||||
add(a, a, d);
|
||||
multmod(c, c, a);
|
||||
multmod(a, d, f);
|
||||
multmod(d, b, _9);
|
||||
multmod(b, e, e);
|
||||
cswap(a, b, r);
|
||||
cswap(c, d, r);
|
||||
}
|
||||
invert(c, c);
|
||||
multmod(a, a, c);
|
||||
pack(z, a);
|
||||
return z;
|
||||
}
|
||||
|
||||
export function generatePresharedKey() {
|
||||
var privateKey = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
export function generatePrivateKey() {
|
||||
var privateKey = generatePresharedKey();
|
||||
clamp(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
function encodeBase64(dest, src) {
|
||||
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
|
||||
for (var i = 0; i < 4; ++i)
|
||||
dest[i] = input[i] + 65 +
|
||||
(((25 - input[i]) >> 8) & 6) -
|
||||
(((51 - input[i]) >> 8) & 75) -
|
||||
(((61 - input[i]) >> 8) & 15) +
|
||||
(((62 - input[i]) >> 8) & 3);
|
||||
}
|
||||
|
||||
export function keyToBase64(key: Uint8Array): string {
|
||||
var i, base64 = new Uint8Array(44);
|
||||
for (i = 0; i < 32 / 3; ++i) encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
|
||||
encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
|
||||
base64[43] = 61;
|
||||
return String.fromCharCode.apply(null, base64);
|
||||
}
|
||||
|
||||
export function Base64ToKey(key: string): Uint8Array {
|
||||
var binaryString = atob(key);
|
||||
var bytes = new Uint8Array(binaryString.length);
|
||||
for (var i = 0; i < binaryString.length; i++) bytes[i] = binaryString.charCodeAt(i);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
export function generateKeypair() {
|
||||
var privateKey = generatePrivateKey();
|
||||
var publicKey = generatePublicKey(privateKey);
|
||||
return {
|
||||
publicKey: keyToBase64(publicKey),
|
||||
privateKey: keyToBase64(privateKey)
|
||||
};
|
||||
}
|
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
11
next.config.mjs
Normal file
11
next.config.mjs
Normal file
@ -0,0 +1,11 @@
|
||||
/** @type {import("next").NextConfig} */
|
||||
export default {
|
||||
webpack(webpackConfig, { webpack }) {
|
||||
webpackConfig.resolve.extensionAlias = {
|
||||
".js": [".ts", ".tsx", ".js", ".jsx"],
|
||||
".mjs": [".mts", ".mjs"],
|
||||
".cjs": [".cts", ".cjs"],
|
||||
};
|
||||
return webpackConfig;
|
||||
},
|
||||
};
|
11
package.json
11
package.json
@ -1,23 +1,22 @@
|
||||
{
|
||||
"name": "wg-dashboard",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"dev": "next dev"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.10.5",
|
||||
"@types/react": "18.2.46",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sirherobrine23/extends": "^3.7.1",
|
||||
"neste": "^3.1.2",
|
||||
"next": "^14.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sequelize": "^6.35.2",
|
||||
"sqlite3": "^5.1.6",
|
||||
"wireguard-tools.js": "^1.8.1"
|
||||
|
102
pages/_app.css
Normal file
102
pages/_app.css
Normal file
@ -0,0 +1,102 @@
|
||||
a,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
div,
|
||||
span,
|
||||
select,
|
||||
option,
|
||||
button
|
||||
{
|
||||
font-family: "IdreesIncMonocraft";
|
||||
color: #000000db;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="buttom"]
|
||||
{
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "IdreesIncMonocraft";
|
||||
background-color: #000000db;
|
||||
color: #f0ece2;
|
||||
padding: 0.2rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
min-width: 15ch;
|
||||
max-width: 25vmax;
|
||||
border: 1px solid #777;
|
||||
border-radius: 0.25em;
|
||||
padding: 0.25em 0.5em;
|
||||
cursor: pointer;
|
||||
line-height: 1.1;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(to top, #f9f9f9, #fff 33%);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #ababab;
|
||||
color: #213e3b68;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #14274e;
|
||||
}
|
||||
h3 {
|
||||
color: #7579e7;
|
||||
}
|
||||
a {
|
||||
color: #3862d4;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
a,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
div,
|
||||
span,
|
||||
select,
|
||||
option,
|
||||
button {
|
||||
color: #f0ece2;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(to top, #f9f9f9, #fff 33%);
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #000000e8;
|
||||
color: #f0ece2;
|
||||
}
|
||||
h1 {
|
||||
color: #7579e7;
|
||||
}
|
||||
h3 {
|
||||
color: #69779b;
|
||||
}
|
||||
img {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
code {
|
||||
background-color: #ffffffde;
|
||||
color: #000000db;
|
||||
}
|
||||
}
|
23
pages/_app.tsx
Normal file
23
pages/_app.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useEffect, useState } from "react";
|
||||
import "./_app.css";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
// Render on front side not back
|
||||
const [ front, setFront ] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!front) setFront(true);
|
||||
}, []);
|
||||
if (!front) return <> Loading </>;
|
||||
|
||||
// Render default page style
|
||||
return <>
|
||||
<Head>
|
||||
<title>Wireguard Dashboard</title>
|
||||
</Head>
|
||||
<div className="bodyCamp">
|
||||
<Component {...pageProps}/>
|
||||
</div>
|
||||
</>
|
||||
}
|
@ -1,12 +1,17 @@
|
||||
#!/usr/bin/env node
|
||||
import { extendNet } from "@sirherobrine23/extends";
|
||||
import * as neste from "neste";
|
||||
import { tmpdir } from "node:os";
|
||||
import wg from "wireguard-tools.js";
|
||||
import * as db from "./database.js";
|
||||
import * as wg from "wireguard-tools.js";
|
||||
import * as db from "../../lib/database.js";
|
||||
|
||||
const app = neste.default();
|
||||
app.listen(process.env.PORT||3000, () => console.log("Listen on %s", app.address()));
|
||||
const app = new neste.Router({ "app path": "/api" });
|
||||
export default app;
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
responseLimit: false,
|
||||
}
|
||||
};
|
||||
|
||||
app.get("/favicon(.*)", (_, res) => res.sendStatus(404));
|
||||
|
||||
@ -93,7 +98,7 @@ app.delete("/:id", async ({params: {id}}, res) => {
|
||||
const peers = await db.wg_peer.findAll({ where: { interfaceID: inter.interfaceID } });
|
||||
await Promise.all(peers.map(async s => s.destroy()));
|
||||
await inter.destroy();
|
||||
return res.status(200).json({
|
||||
return res.status(202).json({
|
||||
interface: inter.toJSON(),
|
||||
peers: peers.map(s => s.toJSON()),
|
||||
});
|
||||
@ -137,6 +142,21 @@ app.get("/:id/config", async ({query, params: {id}}, res) => {
|
||||
return res.json(interfaceConfig);
|
||||
});
|
||||
|
||||
// Get random IPs
|
||||
app.get("/:id/peer/ips", async ({params: {id}}, res) => {
|
||||
const inter = await db.wg_interfaces.findOne({ where: { interfaceID: parseInt(id) } });
|
||||
if (!inter) return res.status(404).json({ errors: [ { type: "interface", message: "Interface not configured" } ] });
|
||||
let ipv4: string = null;
|
||||
do {
|
||||
ipv4 = await extendNet.randomIp(inter.ipv4.concat("/24"), (await db.wg_peer.findAll({ where: { interfaceID: inter.interfaceID } })).map(s => s.ipv4))
|
||||
if (await db.wg_peer.findOne({ where: { ipv4 } })) ipv4 = null;
|
||||
} while (!ipv4);
|
||||
return res.json({
|
||||
ipv4,
|
||||
ipv6: extendNet.toString(extendNet.toInt(ipv4), true)
|
||||
});
|
||||
});
|
||||
|
||||
// Create peers
|
||||
app.post("/:id/peer", async ({params: {id}, query, body}, res) => {
|
||||
const inter = await db.wg_interfaces.findOne({ where: { interfaceID: parseInt(id) } });
|
||||
@ -158,11 +178,40 @@ app.post("/:id/peer", async ({params: {id}, query, body}, res) => {
|
||||
privateKey: body.privateKey,
|
||||
presharedKey: body.presharedKey,
|
||||
ipv4: body.ipv4,
|
||||
ipv6: extendNet.toString(extendNet.toInt(body.ipv4), true)
|
||||
ipv6: body.ipv6 || extendNet.toString(extendNet.toInt(body.ipv4), true)
|
||||
});
|
||||
return res.status(201).json(userPeer.toJSON());
|
||||
});
|
||||
|
||||
// Delete peer v2
|
||||
app.delete("/:id/peer", async ({body, params: {id}}, res) => {
|
||||
const inter = await db.wg_interfaces.findOne({ where: { interfaceID: parseInt(id) } });
|
||||
if (!inter) return res.status(404).json({ errors: [ { type: "interface", message: "Interface not configured" } ] });
|
||||
if (!(Array.isArray(body) || typeof body?.peerID === "string")) return res.status(400).json({ erros: [ { type: "body", message: "require Array or `peerID` in body" } ] });
|
||||
|
||||
// Multi delete
|
||||
if (Array.isArray(body)) {
|
||||
const destroys = await db.wg_peer.findAll({ where: { interfaceID: inter.interfaceID, peerID: body.map(Number) } });
|
||||
await Promise.all(destroys.map(s => s.destroy()));
|
||||
return res.status(202).json(destroys.map(s => s.toJSON()));
|
||||
}
|
||||
|
||||
// Delete peer
|
||||
const peer = await db.wg_peer.findOne({ where: { peerID: Number(body.peerID) } });
|
||||
await peer.destroy();
|
||||
return res.status(202).json(peer.toJSON());
|
||||
});
|
||||
|
||||
// Delete peer
|
||||
app.delete("/:id/:peerID", async ({params: { id, peerID }, body}, res) => {
|
||||
const inter = await db.wg_interfaces.findOne({ where: { interfaceID: parseInt(id) } });
|
||||
if (!inter) return res.status(404).json({ errors: [ { type: "interface", message: "Interface not configured" } ] });
|
||||
const peer = await db.wg_peer.findOne({ where: { interfaceID: inter.interfaceID, peerID: parseInt(peerID) } });
|
||||
if (!peer) return res.status(404).json({ errors: [ { type: "peer", message: "peer not exists" } ] });
|
||||
await peer.destroy();
|
||||
return res.status(202).json(peer.toJSON());
|
||||
});
|
||||
|
||||
// Edit peer
|
||||
app.patch("/:id/:peerID", async ({params: { id, peerID }, body}, res) => {
|
||||
const inter = await db.wg_interfaces.findOne({ where: { interfaceID: parseInt(id) } });
|
28
pages/index.module.css
Normal file
28
pages/index.module.css
Normal file
@ -0,0 +1,28 @@
|
||||
.interface {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.interface > .inName {
|
||||
font-size: 3.5ch;
|
||||
}
|
||||
|
||||
.PeerContainer {
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
justify-items: stretch;
|
||||
}
|
||||
|
||||
.Peer {
|
||||
padding: 12px;
|
||||
margin: 6px;
|
||||
margin-top: 12px;
|
||||
width: 410px;
|
||||
backdrop-filter: invert(6%);
|
||||
flex: 1 1 490px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.PeerContainer {
|
||||
display: flex;
|
||||
}
|
||||
}
|
238
pages/index.tsx
Normal file
238
pages/index.tsx
Normal file
@ -0,0 +1,238 @@
|
||||
import { CSSProperties, DetailedHTMLProps, FormEvent, InputHTMLAttributes, useState } from "react";
|
||||
import type { PeerWg, Wg } from "../lib/database.js";
|
||||
import * as key from "../lib/key.js";
|
||||
import HomeStyle from "./index.module.css";
|
||||
import { GetServerSidePropsContext, InferGetServerSidePropsType, PreviewData } from "next";
|
||||
import { ParsedUrlQuery } from "querystring";
|
||||
|
||||
function DynamicInput(props: DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>) {
|
||||
const
|
||||
tbase = {
|
||||
margin: 0,
|
||||
},
|
||||
resizeContainer: CSSProperties = {
|
||||
...tbase,
|
||||
padding: "2px 0px",
|
||||
display: "inline-block",
|
||||
position: "relative"
|
||||
},
|
||||
resizeText: CSSProperties = {
|
||||
...tbase,
|
||||
display: "inline-block",
|
||||
visibility: "hidden",
|
||||
whiteSpace: "pre",
|
||||
padding: "2px 0px 4px 0px",
|
||||
minWidth: "40px",
|
||||
},
|
||||
resizeInput: CSSProperties = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
margin: 0
|
||||
};
|
||||
return <div className="resizeContainer" style={resizeContainer}>
|
||||
<span style={resizeText}>{props.defaultValue||props.value}</span>
|
||||
<input {...props} style={{...props.style, ...resizeInput}}
|
||||
onInput={e => e.currentTarget.previousElementSibling.textContent = e.currentTarget.value}
|
||||
onChange={e => e.currentTarget.previousElementSibling.textContent = e.currentTarget.value}
|
||||
/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
interface CreatePeerProps {
|
||||
interfaceID: number;
|
||||
done(): void;
|
||||
}
|
||||
|
||||
function CreatePeer(prop: CreatePeerProps) {
|
||||
async function submit(e: FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
const body = new FormData(e.currentTarget);
|
||||
const res = await fetch(String.prototype.concat("/api/", String(prop.interfaceID), "/peer"), {
|
||||
method: "POST",
|
||||
body
|
||||
});
|
||||
if (res.ok) prop.done();
|
||||
}
|
||||
|
||||
const [genPresharedKey, updaterPresharedKey] = useState(false);
|
||||
const [ips, setIps] = useState<{ipv4: string, ipv6: string}>(null);
|
||||
const [{ privateKey, publicKey }, genKey] = useState(key.generateKeypair());
|
||||
const [presharedKey, UpdatePresharedKey] = useState(key.keyToBase64(key.generatePresharedKey()));
|
||||
|
||||
return <div className={HomeStyle["Peer"]}>
|
||||
<div style={{ paddingBottom: "5px", display: "flex" }}>
|
||||
<div>
|
||||
<input type="button" style={{ backgroundColor: "red" }} onClick={prop.done} value="Cancel" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="button" onClick={async () => {
|
||||
try {
|
||||
const res = await fetch(String.prototype.concat("/api/", String(prop.interfaceID), "/peer/ips"));
|
||||
if (res.ok) return setIps(await res.json());
|
||||
} catch {
|
||||
}
|
||||
}} value="Get Random IP" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="button" onClick={() => genKey(key.generateKeypair())} value="Recrate key" />
|
||||
</div>
|
||||
{
|
||||
genPresharedKey && <div>
|
||||
<input type="button" onClick={() => UpdatePresharedKey(key.keyToBase64(key.generatePresharedKey()))} value="Recrate preshared key" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<form onSubmit={submit}>
|
||||
<div>
|
||||
Private Key:
|
||||
<DynamicInput type="text" name="privateKey" defaultValue={privateKey} value={privateKey} />
|
||||
</div>
|
||||
<div>
|
||||
Public Key:
|
||||
<DynamicInput readOnly type="text" name="" defaultValue={publicKey} value={publicKey} />
|
||||
</div>
|
||||
<div>
|
||||
Include Preshared key:
|
||||
<input type="checkbox" onClick={e => updaterPresharedKey(e.currentTarget.checked)} defaultChecked={genPresharedKey} />
|
||||
</div>
|
||||
{genPresharedKey && <div>
|
||||
Preshared Key:
|
||||
<DynamicInput type="text" name="presharedKey" defaultValue={presharedKey} value={presharedKey} />
|
||||
</div>}
|
||||
<div>
|
||||
IPv4:
|
||||
<DynamicInput readOnly type="text" name="ipv4" value={ips?.ipv4} />
|
||||
</div>
|
||||
<div>
|
||||
IPv6:
|
||||
<DynamicInput readOnly type="text" name="ipv6" value={ips?.ipv6} />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Create peer" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (req: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) => {
|
||||
// console.log(process.env);
|
||||
const res = await fetch(`http://${req.req.headers.host}/api/interfaces`);
|
||||
if (!res.ok) return { noFound: true };
|
||||
|
||||
return {
|
||||
props: {
|
||||
interface: await res.json() as any,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default function Home(props: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const [ interfaces, setInterface ] = useState<(Wg & { peers: PeerWg[] })[]>(props.interface);
|
||||
const [ peersCreate, UpdatePeerCreate ] = useState<{interfaceID: number, id: string}[]>([]);
|
||||
const updateFunc = async () => {
|
||||
try {
|
||||
const res = await fetch("/api/interfaces");
|
||||
setInterface(await res.json() as any);
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return <div>
|
||||
<div>
|
||||
<button onClick={updateFunc}>Refresh</button>
|
||||
<button>Create interface</button>
|
||||
<div>
|
||||
{
|
||||
interfaces && interfaces.map((wg) => {
|
||||
return <div key={String.prototype.concat("interfaceHome", String(wg.interfaceID))} className={HomeStyle["interface"]}>
|
||||
<div className={HomeStyle["inName"]} style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div>{wg.name}</div>
|
||||
<div>
|
||||
<button>Delete interface</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div style={{marginLeft: "1%"}}>
|
||||
{wg.privateKey && <div>Private key: {wg.privateKey}</div>}
|
||||
{wg.ipv4 && <div>Interface IPv4: {wg.ipv4}</div>}
|
||||
{wg.ipv6 && <div>Interface IPv6: {wg.ipv6}</div>}
|
||||
{wg.portListen && <div>Port Listen: <input type="number" min={0} max={65535} defaultValue={wg.portListen} onChange={e => {
|
||||
if (e.currentTarget.valueAsNumber > 65535) e.currentTarget.value = String((e.currentTarget.valueAsNumber = 65535));
|
||||
else if (e.currentTarget.valueAsNumber < 0) e.currentTarget.value = String((e.currentTarget.valueAsNumber = 0));
|
||||
}}/></div>}
|
||||
<div style={{paddingTop: "5px"}}>
|
||||
<button onClick={() => UpdatePeerCreate(e => e.concat({ interfaceID: wg.interfaceID, id: crypto.randomUUID() }))}>Create peer</button>
|
||||
<button onClick={async () => {
|
||||
const { ok } = await fetch(String.prototype.concat("/api/", String(wg.interfaceID), "/peer"), { method: "POST" });
|
||||
if (ok) await updateFunc();
|
||||
}}>Create random peer</button>
|
||||
{wg.peers.length > 1 && <button onClick={async () => {
|
||||
const { ok } = await fetch(String.prototype.concat("/api/", String(wg.interfaceID), "/peer"), {
|
||||
method: "DELETE",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(wg.peers.map(s => s.peerID)),
|
||||
});
|
||||
if (ok) await updateFunc();
|
||||
}}>Delete all peers</button>}
|
||||
{peersCreate.filter(s => s.interfaceID === wg.interfaceID).length > 0 && <div>
|
||||
Peers to create:
|
||||
<div style={{marginLeft: "2%", marginRight: "2%"}} className={HomeStyle["PeerContainer"]}>
|
||||
{peersCreate.filter(s => s.interfaceID === wg.interfaceID).map(({id: peerID}) =>
|
||||
<CreatePeer interfaceID={wg.interfaceID} done={() => {
|
||||
UpdatePeerCreate(e => e.filter(({id}) => id !== peerID))
|
||||
updateFunc();
|
||||
}
|
||||
} />
|
||||
)}
|
||||
</div>
|
||||
</div>}
|
||||
{
|
||||
wg.peers.length > 0 && <div>
|
||||
<div style={{ paddingTop: "2px" }}>Peers:</div>
|
||||
<div style={{marginLeft: "2%", marginRight: "2%"}} className={HomeStyle["PeerContainer"]}>
|
||||
{wg.peers.reverse().map(peer => {
|
||||
async function deletePeer() {
|
||||
try {
|
||||
const res = await fetch(("/api/").concat(String(wg.interfaceID), "/", String(peer.peerID)), {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (res.ok) setInterface(e => e.map(s => { s.peers = s.peers.filter(s => peer.peerID !== s.peerID); return s; }));
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={HomeStyle["Peer"]} key={String.prototype.concat("peerHome", String(wg.interfaceID), "peer", String(peer.peerID))}>
|
||||
<div style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<div>
|
||||
{key.keyToBase64(key.generatePublicKey(key.Base64ToKey(peer.privateKey)))}
|
||||
</div>
|
||||
<div>
|
||||
<button onClick={deletePeer}>Delete peer</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<div style={{marginLeft: "2%", paddingTop: "8px"}}>
|
||||
{peer.presharedKey && <div>Preshared Key: <span>{peer.presharedKey}</span></div>}
|
||||
{peer.ipv4 && <div>Peer IPv4: <span>{peer.ipv4}</span></div>}
|
||||
{peer.ipv6 && <div>Peer IPv6: <span>{peer.ipv6}</span></div>}
|
||||
</div>
|
||||
</div>;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,30 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": false,
|
||||
"jsx": "preserve",
|
||||
"target": "ESNext",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"declaration": true,
|
||||
"strict": false,
|
||||
"noUnusedLocals": true,
|
||||
"isolatedModules": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"composite": true,
|
||||
"lib": [
|
||||
"ESNext"
|
||||
]
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"exclude": [
|
||||
"**/node_modules/**",
|
||||
"**/*.test.*"
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"ts-node": {
|
||||
"files": true,
|
||||
"esm": true,
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user