From 34050ed173139738cf2fc0b85898732ac7f6e566 Mon Sep 17 00:00:00 2001 From: Shahrad Elahi Date: Mon, 25 Sep 2023 15:29:10 +0330 Subject: [PATCH] updates ui and fixes a few minor issues --- src/pages/[serverId]/index.tsx | 179 ++++++++++++++++++++++++++--- src/pages/index.tsx | 29 ++--- src/ui/MiddleEllipsis.tsx | 4 +- src/ui/Modal/CreateClientModal.tsx | 29 ++--- src/ui/Modal/CreateServerModal.tsx | 18 ++- src/ui/Modal/QRCodeModal.tsx | 39 +++++++ 6 files changed, 248 insertions(+), 50 deletions(-) create mode 100644 src/ui/Modal/QRCodeModal.tsx diff --git a/src/pages/[serverId]/index.tsx b/src/pages/[serverId]/index.tsx index b43961c..9cbd5c1 100644 --- a/src/pages/[serverId]/index.tsx +++ b/src/pages/[serverId]/index.tsx @@ -4,13 +4,16 @@ import PageRouter from "@ui/pages/PageRouter"; import React from "react"; import { PlusOutlined } from "@ant-design/icons"; import useSWR from "swr"; -import { APIResponse } from "@lib/typings"; +import { APIResponse, WgServer } from "@lib/typings"; import useSWRMutation from "swr/mutation"; import { useRouter } from "next/router"; import { MiddleEllipsis } from "@ui/MiddleEllipsis"; import StatusBadge from "@ui/StatusBadge"; import { SmartModalRef } from "@ui/Modal/SmartModal"; import CreateClientModal from "@ui/Modal/CreateClientModal"; +import { twMerge } from "tailwind-merge"; +import QRCodeModal from "@ui/Modal/QRCodeModal"; +import { getPeerConf } from "@lib/wireguard-utils"; export async function getServerSideProps(context: any) { @@ -31,7 +34,7 @@ export default function ServerPage(props: PageProps) { const createClientRef = React.useRef(null) - const { data, error, isLoading } = useSWR( + const { data, error, isLoading, mutate: refresh } = useSWR( `/api/wireguard/${props.serverId}`, async (url: string) => { const resp = await fetch(url, { @@ -52,7 +55,9 @@ export default function ServerPage(props: PageProps) { const { isMutating: isChangingStatus, trigger: changeStatus } = useSWRMutation( `/api/wireguard/${props.serverId}`, - async (url: string, { arg }: { arg: string }) => { + async (url: string, { arg }: { + arg: string + }) => { const resp = await fetch(url, { method: arg === 'remove' ? 'DELETE' : 'PUT', headers: { @@ -71,10 +76,8 @@ export default function ServerPage(props: PageProps) { return true }, { - onSuccess: () => { - }, - onError: () => { - } + onSuccess: async () => await refresh(), + onError: async () => await refresh(), } ) @@ -82,7 +85,11 @@ export default function ServerPage(props: PageProps) { return ( - + refresh()} + /> .ant-card-body]:p-0'} - title={ Clients } + title={( +
+ Clients + + {data && data.peers.length > 0 && ( +
+ +
+ )} +
+ )} > -
-

- There are no clients yet! -

- -
+ {data && data.peers.length > 0 ? ( + + {data.peers.map((s) => ( + refresh()} + /> + ))} + + ) : ( +
+

+ There are no clients yet! +

+ +
+ )} )} @@ -175,6 +212,114 @@ export default function ServerPage(props: PageProps) { ); } +type Peer = WgServer['peers'][0] + +interface ClientProps extends Peer { + serverId: string + listenPort: number + refreshTrigger: () => void +} + +function Client(props: ClientProps) { + + const qrcodeRef = React.useRef(null) + + const [ conf, setConf ] = React.useState(null) + + React.useEffect(() => { + setConf(getPeerConf({ + ...props, + port: props.listenPort + })) + console.log('conf', conf) + }, [ props ]) + + const { isMutating: removingClient, trigger: removeClient } = useSWRMutation( + `/api/wireguard/${props.serverId}/${props.id}`, + async (url: string,) => { + const resp = await fetch(url, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' } + }) + const data = await resp.json() as APIResponse + if (!data.ok) throw new Error('Server responded with error status') + return true + }, + { + onSuccess: () => props.refreshTrigger(), + onError: () => props.refreshTrigger() + } + ) + + return ( + + +
+
+ {props.name} +
+
+ {/* QRCode */} + { + qrcodeRef.current?.open() + }}> + + + + {/* Download */} + { + if (!conf) { + console.error('conf is null') + return + } + console.log('conf', conf) + // create a blob + const blob = new Blob([ conf ], { type: 'text/plain' }) + // create a link + const link = document.createElement('a') + link.href = window.URL.createObjectURL(blob) + link.download = `${props.name}.conf` + // click the link + link.click() + // remove the link + link.remove() + }}> + + + + {/* Remove */} + removeClient()}> + + +
+ + ) +} + +function ClientBaseButton(props: { + onClick: () => void + loading?: boolean + disabled?: boolean + children: React.ReactNode +}) { + return ( +
+ {props.children} +
+ ) +} + function Row(props: { label: string children: React.ReactNode diff --git a/src/pages/index.tsx b/src/pages/index.tsx index d2e69ef..3c6a19e 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -13,17 +13,20 @@ import CreateServerModal from "@ui/Modal/CreateServerModal"; import StatusBadge from "@ui/StatusBadge"; export default function Home() { - const { data, error, isLoading } = useSWR('/api/wireguard/listServers', async (url: string) => { - const resp = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }) - const data = await resp.json() as APIResponse - if (!data.ok) throw new Error('Server responded with error status') - return data.result - }) + const { data, error, isLoading, mutate } = useSWR( + '/api/wireguard/listServers', + async (url: string) => { + const resp = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + const data = await resp.json() as APIResponse + if (!data.ok) throw new Error('Server responded with error status') + return data.result + } + ) const createServerRef = React.useRef(null) return ( @@ -35,7 +38,7 @@ export default function Home() { )} - + mutate()} />
{error ? ( @@ -51,7 +54,7 @@ export default function Home() { title={ Servers } > - {data.map((s) => )} + {data.map((s) => )} ) : ( diff --git a/src/ui/MiddleEllipsis.tsx b/src/ui/MiddleEllipsis.tsx index 9cb22fb..a32ca06 100644 --- a/src/ui/MiddleEllipsis.tsx +++ b/src/ui/MiddleEllipsis.tsx @@ -19,8 +19,8 @@ export function MiddleEllipsis(props: MiddleEllipsisProps) { }, [ maxLength ]) const [ left, right ] = React.useMemo(() => { - if (content.length <= maxLength) return [ content, '' ] - return [ content.slice(0, leftL), content.slice(content.length - rightL) ] + if (content?.length <= maxLength) return [ content, '' ] + return [ content.slice(0, leftL), content.slice(content?.length - rightL) ] }, [ content, leftL, rightL ]) return ( diff --git a/src/ui/Modal/CreateClientModal.tsx b/src/ui/Modal/CreateClientModal.tsx index fa5eade..18e1e80 100644 --- a/src/ui/Modal/CreateClientModal.tsx +++ b/src/ui/Modal/CreateClientModal.tsx @@ -4,13 +4,19 @@ import { Button, Form, Input, notification } from "antd"; import { z } from "zod"; import { APIResponse } from "@lib/typings"; import useSWRMutation from "swr/mutation"; -import { AddressSchema, DnsSchema, MtuSchema, NameSchema, PortSchema, TypeSchema } from "@lib/schemas/WireGuard"; +import { NameSchema } from "@lib/schemas/WireGuard"; import { zodErrorMessage } from "@lib/zod"; + +type CreateClientModalProps = { + serverId: string + refreshTrigger: () => void +} + const CreateClientModal = React.forwardRef< SmartModalRef, - {} ->((_, ref) => { + CreateClientModalProps +>((props, ref) => { const [ notificationApi, contextHolder ] = notification.useNotification() @@ -25,7 +31,7 @@ const CreateClientModal = React.forwardRef< }, []) const { isMutating, trigger } = useSWRMutation( - '/api/wireguard/createClient', + `/api/wireguard/${props.serverId}/createClient`, async (url: string, { arg }: { arg: FormValues }) => { const resp = await fetch(url, { method: 'POST', @@ -35,11 +41,12 @@ const CreateClientModal = React.forwardRef< body: JSON.stringify(arg) }) const data = await resp.json() as APIResponse - if (!data.ok) throw new Error('Client responded with error status') - return data.result + if (!data.ok) throw new Error('Server responded with error status') + return true }, { onSuccess: () => { + props.refreshTrigger() notificationApi.success({ message: 'Success', description: ( @@ -52,6 +59,7 @@ const CreateClientModal = React.forwardRef< form?.resetFields() }, onError: () => { + props.refreshTrigger() notificationApi.error({ message: 'Error', description: 'Failed to create Client' @@ -98,7 +106,7 @@ const CreateClientModal = React.forwardRef< - @@ -110,12 +118,7 @@ const CreateClientModal = React.forwardRef< export default CreateClientModal const FormSchema = z.object({ - name: NameSchema, - address: AddressSchema, - port: PortSchema, - type: TypeSchema, - dns: DnsSchema, - mtu: MtuSchema + name: NameSchema }) type FormValues = z.infer diff --git a/src/ui/Modal/CreateServerModal.tsx b/src/ui/Modal/CreateServerModal.tsx index 61f0bd6..246962f 100644 --- a/src/ui/Modal/CreateServerModal.tsx +++ b/src/ui/Modal/CreateServerModal.tsx @@ -9,7 +9,9 @@ import { isPrivateIP } from "@lib/utils"; import { AddressSchema, DnsSchema, MtuSchema, NameSchema, PortSchema, TypeSchema } from "@lib/schemas/WireGuard"; import { zodErrorMessage } from "@lib/zod"; -export type CreateServerModalProps = {} +type CreateServerModalProps = { + refreshTrigger: () => void +} const CreateServerModal = React.forwardRef< SmartModalRef, @@ -42,6 +44,7 @@ const CreateServerModal = React.forwardRef< }) const data = await resp.json() as APIResponse if (!data.ok) throw new Error('Server responded with error status') + props.refreshTrigger() return data.result }, { @@ -146,7 +149,7 @@ const CreateServerModal = React.forwardRef< defaultValue={type} onChange={(v) => setType(v as any)} options={[ - { label: 'Direct', value: 'default', icon: }, + { label: 'Direct', value: 'direct', icon: }, { label: 'Tor', value: 'tor', icon: , disabled: true } ]} /> @@ -156,7 +159,7 @@ const CreateServerModal = React.forwardRef< { validator: (_, value) => { if (!value) return Promise.resolve() - const res = NameSchema.safeParse(value) + const res = DnsSchema.safeParse(value) if (res.success) return Promise.resolve() return Promise.reject(zodErrorMessage(res.error)[0]) } @@ -169,7 +172,7 @@ const CreateServerModal = React.forwardRef< { validator: (_, value) => { if (!value) return Promise.resolve() - const res = NameSchema.safeParse(value) + const res = MtuSchema.safeParse(value) if (res.success) return Promise.resolve() return Promise.reject(zodErrorMessage(res.error)[0]) } @@ -178,7 +181,12 @@ const CreateServerModal = React.forwardRef< - diff --git a/src/ui/Modal/QRCodeModal.tsx b/src/ui/Modal/QRCodeModal.tsx new file mode 100644 index 0000000..e37699d --- /dev/null +++ b/src/ui/Modal/QRCodeModal.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import SmartModal, { SmartModalRef } from "@ui/Modal/SmartModal"; +import { QRCodeCanvas } from "qrcode.react"; +import { SHA1 } from "crypto-js"; + +type QRCodeModalProps = { + content: string +} + +const QRCodeModal = React.forwardRef< + SmartModalRef, + QRCodeModalProps +>((props, ref) => { + + const innerRef = React.useRef(null) + + React.useImperativeHandle(ref, () => innerRef.current as SmartModalRef) + + return ( + +
+ +
+
+ ) +}) + +export default QRCodeModal +