This commit is contained in:
Shahrad Elahi
2023-11-08 04:00:10 +03:30
parent 771a0f301c
commit f649ce4294
22 changed files with 402 additions and 63 deletions

View File

@@ -1,18 +0,0 @@
<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
<stop stop-color="#420C5D" offset="0%"></stop>
<stop stop-color="#951AD1" offset="100%"></stop>
</linearGradient>
</defs>
<g transform="translate(-58.12 -303.3)" fill="url(#linearGradient-1)" fill-rule="evenodd">
<path d="m77.15 303.3c-1.608 1.868-0.09027 2.972-0.9891 4.84 1.514-2.129 5.034-2.862 7.328-3.643-3.051 2.72-5.457 6.326-8.489 9.009l-1.975-0.8374c-0.4647-4.514-1.736-4.705 4.125-9.369z"
fill-rule="evenodd"/>
<path d="m74.04 313.1 2.932 0.9454c-0.615 2.034 0.3559 2.791 0.9472 3.123 1.324 0.7332 2.602 1.49 3.619 2.412 1.916 1.75 3.004 4.21 3.004 6.812 0 2.578-1.183 5.061-3.169 6.717-1.868 1.561-4.446 2.223-6.953 2.223-1.561 0-2.956-0.0708-4.47-0.5677-3.453-1.159-6.031-4.115-6.244-7.663-0.1893-2.767 0.4257-4.872 2.578-7.072 1.111-1.159 2.563-2.749 4.1-3.813 0.757-0.5204 1.119-1.191-0.4183-3.958l1.28-1.076 2.795 1.918-2.352-0.3007c0.1656 0.2366 1.189 0.7706 1.284 1.078 0.2128 0.8751-0.1911 1.771-0.3804 2.149-0.9696 1.75-1.86 2.275-3.066 3.268-2.129 1.75-4.27 2.836-4.01 7.637 0.1183 2.365 1.433 5.295 4.2 6.643 1.561 0.757 2.859 1.189 4.68 1.284 1.632 0.071 4.754-0.8988 6.457-2.318 1.821-1.514 2.838-3.808 2.838-6.149 0-2.365-0.9461-4.612-2.72-6.197-1.017-0.9223-2.696-2.034-3.737-2.625-1.041-0.5912-2.782-2.06-2.356-3.645z"/>
<path d="m73.41 316.6c-0.186 1.088-0.4177 3.117-0.8909 3.917-0.3293 0.5488-0.4126 0.8101-0.7846 1.094-1.09 1.535-1.45 1.761-2.132 4.552-0.1447 0.5914-0.3832 1.516-0.2591 2.107 0.372 1.703 0.6612 2.874 1.316 4.103 0 0 0.1271 0.1217 0.1271 0.169 0.6821 0.9225 0.6264 1.05 2.665 2.246l-0.06204 0.3313c-1.55-0.4729-2.604-0.9591-3.41-2.024 0-0.0236-0.1513-0.1558-0.1513-0.1558-0.868-1.135-1.753-2.788-2.021-4.546-0.1447-0.7097-0.0769-1.341 0.08833-2.075 0.7026-2.885 1.415-4.093 2.744-5.543 0.3514-0.2601 0.6704-0.6741 1.001-1.092 0.4859-0.6764 1.462-2.841 1.814-4.189z"/>
<path d="m74.09 318.6c0.0237 1.04 0.0078 3.036 0.3389 3.796 0.0945 0.2599 0.3274 1.414 0.9422 2.794 0.4258 0.96 0.5418 1.933 0.6128 2.193 0.2838 1.14-0.4002 3.086-0.8734 4.906-0.2364 0.98-0.6051 1.773-1.371 2.412l0.2796 0.3593c0.5204-0.02 1.954-1.096 2.403-2.416 0.757-2.24 1.328-3.317 0.9729-5.797-0.0473-0.2402-0.2094-1.134-0.6588-2.014-0.6622-1.34-1.474-2.614-1.592-2.874-0.213-0.4198-1.007-2.119-1.054-3.359z"/>
<path d="m74.88 313.9 0.9727 0.4962c-0.09145 0.6403 0.04572 2.059 0.686 2.424 2.836 1.761 5.512 3.683 6.565 5.604 3.751 6.771-2.63 13.04-8.143 12.44 2.996-2.219 4.428-6.583 3.307-11.55-0.4574-1.944-1.729-3.893-2.987-5.883-0.545-0.9768-0.3547-2.188-0.4006-3.538z"
fill-rule="evenodd"/>
<rect x="73.07" y="312.8" width="1" height="22"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,74 @@
<script lang="ts">
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '$lib/components/ui/dialog';
import { Skeleton } from '$lib/components/ui/skeleton';
import { Dialog as DialogPrimitive } from 'bits-ui';
import { Button } from '$lib/components/ui/button';
import type { SafeReturn } from '$lib/typings';
const DialogClose = DialogPrimitive.Close;
let loading: boolean = true;
let error: boolean = false;
let imageData: string | undefined = undefined;
export let content: string | undefined;
const getImageData = async (content: string): Promise<SafeReturn<string>> => {
const resp = await fetch('/api/qrcode', {
method: 'POST',
body: content,
headers: {
'Content-Type': 'text/plain',
},
});
if (resp.statusText !== 'OK') {
return { error: new Error('request failed') };
}
const data = await resp.text();
return { data };
};
$: if (typeof content === 'string') {
(async () => {
const { data, error: e } = await getImageData(content);
loading = false;
if (!data || e) {
error = true;
return;
}
imageData = data;
})();
}
</script>
<Dialog>
<DialogTrigger asChild let:builder>
<slot {builder} />
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>QRCode</DialogTitle>
</DialogHeader>
<div class="flex items-center justify-center">
{#if loading}
<Skeleton class="w-[280px] h-[280px] rounded-lg" />
{:else if error || !imageData}
<div>!!ERROR!!</div>
{:else}
<img src={imageData} alt="QRCode" width="300" height="300" />
{/if}
</div>
<DialogFooter>
<DialogClose>
<Button size="sm">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -0,0 +1,7 @@
import Root from './QRCodeDialog.svelte';
export {
Root,
//
Root as QRCodeDialog,
};

View File

@@ -2,6 +2,7 @@ import { Dialog as DialogPrimitive } from 'bits-ui';
const Root = DialogPrimitive.Root;
const Trigger = DialogPrimitive.Trigger;
const Close = DialogPrimitive.Close;
import Title from './dialog-title.svelte';
import Portal from './dialog-portal.svelte';
@@ -18,6 +19,7 @@ export {
Footer,
Header,
Trigger,
Close,
Overlay,
Content,
Description,
@@ -28,6 +30,7 @@ export {
Footer as DialogFooter,
Header as DialogHeader,
Trigger as DialogTrigger,
Close as DialogClose,
Overlay as DialogOverlay,
Content as DialogContent,
Description as DialogDescription,

View File

@@ -0,0 +1,7 @@
import Root from "./skeleton.svelte";
export {
Root,
//
Root as Skeleton
};

View File

@@ -0,0 +1,14 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { HTMLAttributes } from "svelte/elements";
type $$Props = HTMLAttributes<HTMLDivElement>;
let className: $$Props["class"] = undefined;
export { className as class };
</script>
<div
class={cn("animate-pulse rounded-md bg-muted", className)}
{...$$restProps}
/>

View File

@@ -76,4 +76,10 @@ export type APISuccessResponse<D> = {
result: D;
};
export type SafeReturn<T, K = any> = LeastOne<{
data: T,
error: K
}>
export type LeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U];

View File

@@ -525,13 +525,13 @@ export async function isConfigIdReserved(id: number): Promise<boolean> {
}
export async function getNextFreeConfId(): Promise<number> {
let id = maxConfId();
for (let i = 0; i < 1_000; i++) {
if (!(await isConfigIdReserved(id))) {
return id;
let id = maxConfId() + 1;
for (let i = id; i < 1_000; i++) {
if (!(await isConfigIdReserved(i))) {
return i;
}
}
throw new Error('Could not find a free config id');
throw new Error('WireGuard: Error: Could not find a free config id');
}
export function getConfigHash(confId: number): string | undefined {

View File

@@ -8,7 +8,6 @@
import fetchAction from '$lib/fetch-action';
export let data: PageData;
export let isOpen = false;
const handleRename = async (id: string, name: string) => {
const resp = await fetchAction({
@@ -32,10 +31,13 @@
<BasePage showLogout={true}>
<div class={'flex items-center justify-between py-3 px-2'}>
<h2 class={'font-bold text-xl'}>Hello there 👋</h2>
<Button on:click={() => (isOpen = true)}>
<i class="fas fa-plus mr-2"></i>
Create Server
</Button>
<CreateServerDialog let:builder>
<Button builders={[builder]}>
<i class="fas fa-plus mr-2"></i>
Create Server
</Button>
</CreateServerDialog>
</div>
<div class="space-y-3.5">
@@ -58,5 +60,3 @@
{/if}
</div>
</BasePage>
<CreateServerDialog {isOpen} />

View File

@@ -1,7 +1,14 @@
<script lang="ts">
import { CreateServerSchema, type CreateServerSchemaType } from './schema';
import type { SuperValidated } from 'sveltekit-superforms';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '$lib/components/ui/dialog';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '$lib/components/ui/dialog';
import {
Form,
FormButton,
@@ -18,13 +25,15 @@
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '$lib/components/ui/collapsible';
import { Button } from '$lib/components/ui/button';
export let isOpen = false;
let loading: boolean = false;
let form: SuperValidated<CreateServerSchemaType>;
</script>
<Dialog open={isOpen}>
<Dialog>
<DialogTrigger asChild let:builder>
<slot {builder} />
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Server</DialogTitle>

View File

@@ -18,7 +18,9 @@
<div class="w-full md:w-2/3 flex items-center gap-x-2">
<div class="flex grow">
<div class={'w-12 aspect-square flex items-center justify-center mr-4 rounded-full bg-gray-200 max-md:hidden'}>
<i class={'fa-solid fa-server text-gray-400 text-xl'} />
<i
class={cn(server.tor ? 'fa-solid fa-onion text-purple-700' : 'fa-solid fa-server text-gray-400', 'text-xl')}
/>
</div>
<div class="h-full flex flex-col justify-between col-span-4 gap-y-1.5">

View File

@@ -13,7 +13,6 @@
import { goto, invalidateAll } from '$app/navigation';
export let data: PageData;
export let dialogOpen: boolean = false;
const handleRename = async (peerId: string, name: string) => {
const resp = await fetchAction({
@@ -76,8 +75,6 @@
};
</script>
<CreatePeerDialog open={dialogOpen} on:close={() => (dialogOpen = false)} />
<Card>
<CardHeader>
<CardTitle>Server</CardTitle>
@@ -166,12 +163,16 @@
{/each}
</CardContent>
<CardFooter>
<Button on:click={() => (dialogOpen = true)}>Add Client</Button>
<CreatePeerDialog let:builder>
<Button size="sm" builders={[builder]}>Add Client</Button>
</CreatePeerDialog>
</CardFooter>
{:else}
<CardContent>
<div>No Clients!</div>
<Button on:click={() => (dialogOpen = true)}>Add Client</Button>
<CreatePeerDialog let:builder>
<Button size="sm" builders={[builder]}>Add Client</Button>
</CreatePeerDialog>
</CardContent>
{/if}
</Card>

View File

@@ -1,7 +1,14 @@
<script lang="ts">
import { CreatePeerSchema, type CreatePeerSchemaType } from './schema';
import type { SuperValidated } from 'sveltekit-superforms';
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '$lib/components/ui/dialog';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '$lib/components/ui/dialog';
import { Form, FormButton, FormField, FormInput, FormLabel, FormValidation } from '$lib/components/ui/form';
import { FormItem } from '$lib/components/ui/form/index.js';
import { cn } from '$lib/utils';
@@ -10,7 +17,6 @@
const dispatch = createEventDispatcher();
export let open = false;
let loading: boolean = false;
let form: SuperValidated<CreatePeerSchemaType>;
@@ -21,7 +27,10 @@
};
</script>
<Dialog {open}>
<Dialog>
<DialogTrigger asChild let:builder>
<slot {builder} />
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Create Peer</DialogTitle>

View File

@@ -6,6 +6,7 @@
import PeerActionButton from './PeerActionButton.svelte';
import { createEventDispatcher, onMount } from 'svelte';
import { getPeerConf } from '$lib/wireguard/utils';
import { QRCodeDialog } from '$lib/components/qrcode-dialog';
export let peer: Peer;
@@ -15,7 +16,7 @@
export let conf: string | undefined = undefined;
export let isLoading: boolean = false;
let isLoading: boolean = false;
onMount(async () => {
conf = await getPeerConf({
@@ -35,7 +36,7 @@
}
console.log('conf', conf);
// create a blob
const blob = new Blob([conf], { type: 'text/plain' });
const blob = new Blob([ conf ], { type: 'text/plain' });
// create a link
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
@@ -46,8 +47,6 @@
link.remove();
};
const handleQRCode = async () => {};
const handleRename = (value: string) => {
dispatch('rename', value);
};
@@ -58,7 +57,7 @@
</script>
<div class="flex items-center justify-between p-4 border border-neutral-200/60 rounded-md hover:border-neutral-200">
<div class="flex items-center gap-2.5">
<div class="flex items-center gap-x-2">
<div class={'w-12 aspect-square flex items-center justify-center mr-4 rounded-full bg-gray-200 max-md:hidden'}>
<i class={'fas fa-user text-gray-400 text-lg'} />
</div>
@@ -80,16 +79,18 @@
</div>
<div class="flex items-center justify-center gap-x-3">
<!-- QRCode -->
<PeerActionButton disabled={isLoading} on:click={handleQRCode}>
<i class={'fal text-neutral-700 group-hover:text-primary fa-qrcode'} />
</PeerActionButton>
<QRCodeDialog let:builder content={conf}>
<PeerActionButton builders={[builder]} disabled={isLoading}>
<i class={'fal text-neutral-700 group-hover:text-primary fa-qrcode'} />
</PeerActionButton>
</QRCodeDialog>
<!-- Download -->
<PeerActionButton disabled={isLoading} on:click={handleDownload}>
<i class={'fal text-neutral-700 group-hover:text-primary fa-download'} />
</PeerActionButton>
<!-- Download -->
<!-- Remove -->
<PeerActionButton loading={isLoading} on:click={handleRemove}>
<i class={'fal text-neutral-700 group-hover:text-primary text-lg fa-trash-can'} />
</PeerActionButton>

View File

@@ -1,9 +1,16 @@
<script lang="ts">
import { cn } from '$lib/utils';
import { createEventDispatcher } from 'svelte';
import { Button } from 'bits-ui';
type $$Props = Button.Props & {
disabled?: boolean;
loading?: boolean;
};
export let disabled: boolean = false;
export let loading: boolean = false;
export let builders: $$Props['builders'] = [];
const dispatch = createEventDispatcher();
@@ -13,10 +20,9 @@
}
</script>
<div
aria-roledescription="Action"
role="button"
tabindex="0"
<Button.Root
{builders}
{...$$restProps}
class={cn(
'group flex items-center justify-center w-10 aspect-square rounded-md',
'bg-gray-200/80 hover:bg-gray-100/50',
@@ -31,5 +37,9 @@
if (e.key === 'Enter') handleClick();
}}
>
<slot />
</div>
{#if loading}
<i class="far fa-spinner-third fa-spin" />
{:else}
<slot />
{/if}
</Button.Root>

View File

@@ -0,0 +1,16 @@
import type { RequestHandler } from '@sveltejs/kit';
import QRCode from 'qrcode';
export const POST: RequestHandler = async ({ request }) => {
if (!request.headers.has('Content-Type') || request.headers.get('Content-Type') !== 'text/plain') {
return new Response(null, { status: 400, headers: { 'Content-Type': 'text/plain' } });
}
const body = await request.text();
const data = await QRCode.toDataURL(body, {
errorCorrectionLevel: 'H',
width: 500,
});
return new Response(data, { status: 200, headers: { 'Content-Type': 'text/plain' } });
};

View File

@@ -28,7 +28,6 @@ export const actions: Actions = {
const receivedHashed = Buffer.from(password.toString()).toString('hex').toLowerCase();
if (hashed !== receivedHashed) {
console.log('[+] TEST ONLY', password, hashed, receivedHashed);
return setError(form, 'password', 'Incorrect password.');
}
}
@@ -41,20 +40,16 @@ export const actions: Actions = {
const { ORIGIN } = process.env;
if (ORIGIN) {
console.log('[+] TEST ONLY', 'ORIGIN', ORIGIN);
const secure = ORIGIN.startsWith('https://');
event.cookies.set('authorization', token, {
secure,
httpOnly: true,
path: '/'
path: '/',
});
} else {
event.cookies.set('authorization', token);
}
return { ok: true };
},
};