mirror of
https://github.com/wireadmin/wireadmin
synced 2025-06-26 18:28:06 +00:00
fix
This commit is contained in:
parent
626380c1f7
commit
d87ba2fc6a
17
bin/logs
17
bin/logs
@ -1,13 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
# This script is used for getting last 100 lines of a screen session
|
||||
|
||||
LOG_DIR="/var/vlogs"
|
||||
LOG_FILE="${LOG_DIR}/${1}"
|
||||
SCREEN_NAME="$1"
|
||||
LIMIT="${2:-100}"
|
||||
SCRIPT=$(basename "${0}")
|
||||
|
||||
if [ ! -f "${LOG_FILE}" ]; then
|
||||
echo "Usage: ${0} <filename>"
|
||||
if [ -z "$SCREEN_NAME" ]; then
|
||||
echo "Usage: ${SCRIPT} <screen_name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat "${LOG_FILE}"
|
||||
screen -S "$SCREEN_NAME" -X hardcopy /tmp/screen-hardcopy
|
||||
tail -n "${LIMIT}" /tmp/screen-hardcopy
|
||||
rm /tmp/screen-hardcopy
|
||||
|
@ -9,6 +9,5 @@ services:
|
||||
environment:
|
||||
- WG_HOST=192.168.1.102
|
||||
- UI_PASSWORD=password
|
||||
- TOR_SOCKS5_PROXY=host.docker.internal:8080
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
@ -101,7 +101,7 @@ awk -F= '!a[$1]++' "${ENV_FILE}" >"/tmp/$(basename "${ENV_FILE}")" &&
|
||||
generate_tor_config
|
||||
|
||||
# Start Tor on the background
|
||||
screen -L -Logfile /var/vlogs/tor -dmS "tor" tor -f "${TOR_CONFIG}"
|
||||
screen -dmS "tor" tor -f "${TOR_CONFIG}"
|
||||
|
||||
sleep 1
|
||||
echo -e "\n======================== Versions ========================"
|
||||
|
@ -15,47 +15,47 @@
|
||||
},
|
||||
"packageManager": "pnpm@8.15.0",
|
||||
"engines": {
|
||||
"node": ">=20.9.0"
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-node": "^4.0.1",
|
||||
"@sveltejs/kit": "^2.5.0",
|
||||
"@sveltejs/adapter-node": "^5.0.1",
|
||||
"@sveltejs/kit": "^2.5.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.2",
|
||||
"@types/chai": "^4.3.11",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/chai": "^4.3.14",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^20.11.17",
|
||||
"@types/node": "^20.12.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"chai": "^5.1.0",
|
||||
"mocha": "^10.3.0",
|
||||
"postcss": "^8.4.35",
|
||||
"mocha": "^10.4.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-load-config": "^5.0.3",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-svelte": "^3.2.1",
|
||||
"svelte": "^4.2.10",
|
||||
"svelte-check": "^3.6.4",
|
||||
"prettier-plugin-svelte": "^3.2.2",
|
||||
"svelte": "^4.2.12",
|
||||
"svelte-check": "^3.6.8",
|
||||
"svelte-preprocess": "^5.1.3",
|
||||
"sveltekit-superforms": "^1.13.4",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"sveltekit-superforms": "^2.12.2",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.7.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.2",
|
||||
"typescript": "^5.4.3",
|
||||
"vite": "^5.2.7",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@litehex/storage-box": "^0.2.1",
|
||||
"@litehex/storage-box": "^0.2.2-canary.0",
|
||||
"@t3-oss/env-core": "0.7.3",
|
||||
"bits-ui": "^0.18.0",
|
||||
"clsx": "^2.1.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dotenv": "^16.4.4",
|
||||
"execa": "^8.0.1",
|
||||
"formsnap": "^0.4.4",
|
||||
"formsnap": "^1.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lucide-svelte": "^0.330.0",
|
||||
"node-netkit": "0.1.0-canary.1",
|
||||
"node-netkit": "0.1.0-canary.2",
|
||||
"pino": "^8.18.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,21 +1,21 @@
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { type Handle, redirect } from '@sveltejs/kit';
|
||||
import { verifyToken } from '$lib/auth';
|
||||
import { AUTH_COOKIE } from '$lib/constants';
|
||||
import 'dotenv/config';
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
if (!AUTH_EXCEPTION.includes(event.url.pathname)) {
|
||||
const token = event.cookies.get('authorization');
|
||||
const token = event.cookies.get(AUTH_COOKIE);
|
||||
const token_valid = await verifyToken(token ?? '');
|
||||
|
||||
const redirect = new Response(null, { status: 302, headers: { location: '/login' } });
|
||||
const is_login_page = event.url.pathname === '/login';
|
||||
|
||||
if (!token_valid && !is_login_page) {
|
||||
return redirect;
|
||||
// return redirect;
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
|
||||
if (token_valid && is_login_page) {
|
||||
return new Response(null, { status: 302, headers: { location: '/' } });
|
||||
throw redirect(303, '/');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,6 @@
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { sha256 } from '$lib/hash';
|
||||
import { client } from '$lib/storage';
|
||||
import 'dotenv/config';
|
||||
|
||||
export const AUTH_SECRET = process.env.AUTH_SECRET || sha256(randomUUID());
|
||||
import { env } from '$lib/env';
|
||||
|
||||
export async function generateToken(): Promise<string> {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
@ -15,7 +11,7 @@ export async function generateToken(): Promise<string> {
|
||||
iat: now,
|
||||
exp: now + oneHour,
|
||||
},
|
||||
AUTH_SECRET,
|
||||
env.AUTH_SECRET,
|
||||
);
|
||||
client.setex(token, '1', oneHour);
|
||||
return token;
|
||||
@ -23,7 +19,7 @@ export async function generateToken(): Promise<string> {
|
||||
|
||||
export async function verifyToken(token: string): Promise<boolean> {
|
||||
try {
|
||||
const decode = jwt.verify(token, AUTH_SECRET);
|
||||
const decode = jwt.verify(token, env.AUTH_SECRET);
|
||||
if (!decode) return false;
|
||||
|
||||
const exists = client.exists(token);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils.js';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
export let showInHover: boolean = false;
|
||||
export let rootClass: string | undefined = undefined;
|
||||
|
@ -12,7 +12,9 @@
|
||||
<div class="w-full min-h-screen flex justify-center px-2 md:px-6 py-2">
|
||||
<div class={cn('w-full mx-auto max-w-3xl space-y-3.5', rootClass)}>
|
||||
<PageHeader {showLogout} />
|
||||
<main class={cn('py-2', className)}>
|
||||
<main
|
||||
class={cn('flex flex-col items-center justify-between py-3 px-2 [&>*]:w-full', className)}
|
||||
>
|
||||
<slot />
|
||||
</main>
|
||||
<PageFooter />
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Button as ButtonPrimitive } from 'bits-ui';
|
||||
import { cn } from '$lib/utils';
|
||||
import { buttonVariants, type Props, type Events } from '.';
|
||||
import { type Events, type Props, buttonVariants } from '.';
|
||||
|
||||
type $$Props = Props;
|
||||
type $$Events = Events;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Root from './button.svelte';
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
import { type VariantProps, tv } from 'tailwind-variants';
|
||||
import type { Button as ButtonPrimitive } from 'bits-ui';
|
||||
import Root from './button.svelte';
|
||||
|
||||
const buttonVariants = tv({
|
||||
base: 'inline-flex items-center justify-center rounded-md text-sm font-medium whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import type { HeadingLevel } from './index.js';
|
||||
import { cn } from '$lib/utils';
|
||||
import type { HeadingLevel } from '.';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLHeadingElement> & {
|
||||
tag?: HeadingLevel;
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Checkbox as CheckboxPrimitive } from 'bits-ui';
|
||||
import { Check, Minus } from 'lucide-svelte';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import Minus from 'lucide-svelte/icons/minus';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = CheckboxPrimitive.Props;
|
||||
@ -13,7 +14,7 @@
|
||||
|
||||
<CheckboxPrimitive.Root
|
||||
class={cn(
|
||||
'box-content peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50',
|
||||
'peer box-content h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[disabled=true]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
bind:checked
|
||||
@ -21,7 +22,7 @@
|
||||
on:click
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
class={cn('flex items-center justify-center text-current h-4 w-4')}
|
||||
class={cn('flex h-4 w-4 items-center justify-center text-current')}
|
||||
let:isChecked
|
||||
let:isIndeterminate
|
||||
>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import * as Button from '$lib/components/ui/button';
|
||||
import * as Button from '$lib/components/ui/button/index.js';
|
||||
|
||||
type $$Props = Button.Props;
|
||||
type $$Events = Button.Events;
|
||||
</script>
|
||||
|
||||
<Button.Root type="submit" {...$$restProps} on:click on:keydown>
|
||||
<Button.Root type="submit" on:click on:keydown {...$$restProps}>
|
||||
<slot />
|
||||
</Button.Root>
|
||||
|
@ -1,13 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { Form as FormPrimitive } from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = HTMLAttributes<HTMLSpanElement>;
|
||||
let className: string | undefined | null = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Description class={cn('text-sm text-muted-foreground', className)} {...$$restProps}>
|
||||
<slot />
|
||||
<FormPrimitive.Description
|
||||
class={cn('text-sm text-muted-foreground', className)}
|
||||
{...$$restProps}
|
||||
let:descriptionAttrs
|
||||
>
|
||||
<slot {descriptionAttrs} />
|
||||
</FormPrimitive.Description>
|
||||
|
26
web/src/lib/components/ui/form/form-element-field.svelte
Normal file
26
web/src/lib/components/ui/form/form-element-field.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPathLeaves, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPathLeaves<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPathLeaves<T>">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = FormPrimitive.ElementFieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.ElementField {form} {name} let:constraints let:errors let:tainted let:value>
|
||||
<div class={cn('space-y-2', className)}>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</div>
|
||||
</FormPrimitive.ElementField>
|
26
web/src/lib/components/ui/form/form-field-errors.svelte
Normal file
26
web/src/lib/components/ui/form/form-field-errors.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = FormPrimitive.FieldErrorsProps & {
|
||||
errorClasses?: string | undefined | null;
|
||||
};
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
export let errorClasses: $$Props['class'] = undefined;
|
||||
</script>
|
||||
|
||||
<FormPrimitive.FieldErrors
|
||||
class={cn('text-sm font-medium text-destructive', className)}
|
||||
{...$$restProps}
|
||||
let:errors
|
||||
let:fieldErrorsAttrs
|
||||
let:errorAttrs
|
||||
>
|
||||
<slot {errors} {fieldErrorsAttrs} {errorAttrs}>
|
||||
{#each errors as error}
|
||||
<div {...errorAttrs} class={cn(errorClasses)}>{error}</div>
|
||||
{/each}
|
||||
</slot>
|
||||
</FormPrimitive.FieldErrors>
|
26
web/src/lib/components/ui/form/form-field.svelte
Normal file
26
web/src/lib/components/ui/form/form-field.svelte
Normal file
@ -0,0 +1,26 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = FormPrimitive.FieldProps<T, U> & HTMLAttributes<HTMLElement>;
|
||||
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Field {form} {name} let:constraints let:errors let:tainted let:value>
|
||||
<div class={cn('space-y-2', className)}>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</div>
|
||||
</FormPrimitive.Field>
|
31
web/src/lib/components/ui/form/form-fieldset.svelte
Normal file
31
web/src/lib/components/ui/form/form-fieldset.svelte
Normal file
@ -0,0 +1,31 @@
|
||||
<script lang="ts" context="module">
|
||||
import type { FormPath, SuperForm } from 'sveltekit-superforms';
|
||||
|
||||
type T = Record<string, unknown>;
|
||||
type U = FormPath<T>;
|
||||
</script>
|
||||
|
||||
<script lang="ts" generics="T extends Record<string, unknown>, U extends FormPath<T>">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = FormPrimitive.FieldsetProps<T, U>;
|
||||
|
||||
export let form: SuperForm<T>;
|
||||
export let name: U;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Fieldset
|
||||
{form}
|
||||
{name}
|
||||
let:constraints
|
||||
let:errors
|
||||
let:tainted
|
||||
let:value
|
||||
class={cn('space-y-2', className)}
|
||||
>
|
||||
<slot {constraints} {errors} {tainted} {value} />
|
||||
</FormPrimitive.Fieldset>
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Label as LabelPrimitive } from 'bits-ui';
|
||||
import { getFormField } from 'formsnap';
|
||||
import { getFormControl } from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
|
||||
@ -9,9 +9,9 @@
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
|
||||
const { errors, ids } = getFormField();
|
||||
const { labelAttrs } = getFormControl();
|
||||
</script>
|
||||
|
||||
<Label for={$ids.input} class={cn($errors && 'text-destructive', className)} {...$$restProps}>
|
||||
<slot />
|
||||
<Label {...$labelAttrs} class={cn('data-[fs-error]:text-destructive', className)} {...$$restProps}>
|
||||
<slot {labelAttrs} />
|
||||
</Label>
|
||||
|
17
web/src/lib/components/ui/form/form-legend.svelte
Normal file
17
web/src/lib/components/ui/form/form-legend.svelte
Normal file
@ -0,0 +1,17 @@
|
||||
<script lang="ts">
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = FormPrimitive.LegendProps;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<FormPrimitive.Legend
|
||||
{...$$restProps}
|
||||
class={cn('text-sm font-medium leading-none data-[fs-error]:text-destructive', className)}
|
||||
let:legendAttrs
|
||||
>
|
||||
<slot {legendAttrs} />
|
||||
</FormPrimitive.Legend>
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getFormField } from 'formsnap';
|
||||
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
||||
import type { TextareaGetFormField } from '.';
|
||||
import type { TextareaGetFormField } from './index';
|
||||
import { Textarea, type TextareaEvents } from '$lib/components/ui/textarea';
|
||||
|
||||
type $$Props = HTMLTextareaAttributes;
|
||||
|
@ -1,82 +1,33 @@
|
||||
import { Form as FormPrimitive, getFormField } from 'formsnap';
|
||||
import * as RadioGroupComp from '$lib/components/ui/radio-group';
|
||||
import * as SelectComp from '$lib/components/ui/select';
|
||||
import type { Writable } from 'svelte/store';
|
||||
import Item from './form-item.svelte';
|
||||
import Input from './form-input.svelte';
|
||||
import Textarea from './form-textarea.svelte';
|
||||
import * as FormPrimitive from 'formsnap';
|
||||
import Description from './form-description.svelte';
|
||||
import Label from './form-label.svelte';
|
||||
import Validation from './form-validation.svelte';
|
||||
import Checkbox from './form-checkbox.svelte';
|
||||
import Switch from './form-switch.svelte';
|
||||
import NativeSelect from './form-native-select.svelte';
|
||||
import RadioGroup from './form-radio-group.svelte';
|
||||
import Select from './form-select.svelte';
|
||||
import SelectTrigger from './form-select-trigger.svelte';
|
||||
import FieldErrors from './form-field-errors.svelte';
|
||||
import Field from './form-field.svelte';
|
||||
import Fieldset from './form-fieldset.svelte';
|
||||
import Legend from './form-legend.svelte';
|
||||
import ElementField from './form-element-field.svelte';
|
||||
import Button from './form-button.svelte';
|
||||
|
||||
const Root = FormPrimitive.Root;
|
||||
const Field = FormPrimitive.Field;
|
||||
const Control = FormPrimitive.Control;
|
||||
const RadioItem = RadioGroupComp.Item;
|
||||
const NativeRadio = FormPrimitive.Radio;
|
||||
const SelectContent = SelectComp.Content;
|
||||
const SelectLabel = SelectComp.Label;
|
||||
const SelectGroup = SelectComp.Group;
|
||||
const SelectItem = SelectComp.Item;
|
||||
const SelectSeparator = SelectComp.Separator;
|
||||
|
||||
export type TextareaGetFormField = Omit<ReturnType<typeof getFormField>, 'value'> & {
|
||||
value: Writable<string>;
|
||||
};
|
||||
|
||||
export {
|
||||
Root,
|
||||
Field,
|
||||
Control,
|
||||
Item,
|
||||
Input,
|
||||
Label,
|
||||
Button,
|
||||
Switch,
|
||||
Select,
|
||||
Checkbox,
|
||||
Textarea,
|
||||
Validation,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
FieldErrors,
|
||||
Description,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
NativeSelect,
|
||||
NativeRadio,
|
||||
Fieldset,
|
||||
Legend,
|
||||
ElementField,
|
||||
//
|
||||
Root as Form,
|
||||
Field as FormField,
|
||||
Control as FormControl,
|
||||
Item as FormItem,
|
||||
Input as FormInput,
|
||||
Textarea as FormTextarea,
|
||||
Description as FormDescription,
|
||||
Label as FormLabel,
|
||||
Validation as FormValidation,
|
||||
NativeSelect as FormNativeSelect,
|
||||
NativeRadio as FormNativeRadio,
|
||||
Checkbox as FormCheckbox,
|
||||
Switch as FormSwitch,
|
||||
RadioGroup as FormRadioGroup,
|
||||
RadioItem as FormRadioItem,
|
||||
Select as FormSelect,
|
||||
SelectContent as FormSelectContent,
|
||||
SelectLabel as FormSelectLabel,
|
||||
SelectGroup as FormSelectGroup,
|
||||
SelectItem as FormSelectItem,
|
||||
SelectSeparator as FormSelectSeparator,
|
||||
SelectTrigger as FormSelectTrigger,
|
||||
FieldErrors as FormFieldErrors,
|
||||
Fieldset as FormFieldset,
|
||||
Legend as FormLegend,
|
||||
ElementField as FormElementField,
|
||||
Button as FormButton,
|
||||
};
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
|
||||
import Root from './select.svelte';
|
||||
import Label from './select-label.svelte';
|
||||
import Item from './select-item.svelte';
|
||||
import Content from './select-content.svelte';
|
||||
import Trigger from './select-trigger.svelte';
|
||||
import Separator from './select-separator.svelte';
|
||||
|
||||
const Root = SelectPrimitive.Root;
|
||||
const Group = SelectPrimitive.Group;
|
||||
const Input = SelectPrimitive.Input;
|
||||
const Value = SelectPrimitive.Value;
|
||||
|
||||
export {
|
||||
Root,
|
||||
Group,
|
||||
|
@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { cn, flyAndScale } from '$lib/utils';
|
||||
import { scale } from 'svelte/transition';
|
||||
import { cn, flyAndScale } from '$lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.ContentProps;
|
||||
type $$Events = SelectPrimitive.ContentEvents;
|
||||
|
||||
export let sideOffset: $$Props['sideOffset'] = 4;
|
||||
export let inTransition: $$Props['inTransition'] = flyAndScale;
|
||||
export let inTransitionConfig: $$Props['inTransitionConfig'] = undefined;
|
||||
export let outTransition: $$Props['outTransition'] = scale;
|
||||
@ -23,6 +25,7 @@
|
||||
{inTransitionConfig}
|
||||
{outTransition}
|
||||
{outTransitionConfig}
|
||||
{sideOffset}
|
||||
class={cn(
|
||||
'relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md outline-none',
|
||||
className,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils';
|
||||
import Check from 'lucide-svelte/icons/check';
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { Check } from 'lucide-svelte';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.ItemProps;
|
||||
type $$Events = SelectPrimitive.ItemEvents;
|
||||
@ -18,7 +18,7 @@
|
||||
{disabled}
|
||||
{label}
|
||||
class={cn(
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...$$restProps}
|
||||
@ -34,5 +34,7 @@
|
||||
<Check class="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<slot />
|
||||
<slot>
|
||||
{label || value}
|
||||
</slot>
|
||||
</SelectPrimitive.Item>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
import { ChevronDown } from 'lucide-svelte';
|
||||
import ChevronDown from 'lucide-svelte/icons/chevron-down';
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = SelectPrimitive.TriggerProps;
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
<SelectPrimitive.Trigger
|
||||
class={cn(
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||
className,
|
||||
)}
|
||||
{...$$restProps}
|
||||
|
@ -1,12 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Select as SelectPrimitive } from 'bits-ui';
|
||||
|
||||
type $$Props = SelectPrimitive.Props;
|
||||
|
||||
export let selected: $$Props['selected'] = undefined;
|
||||
export let open: $$Props['open'] = undefined;
|
||||
</script>
|
||||
|
||||
<SelectPrimitive.Root bind:selected bind:open {...$$restProps}>
|
||||
<slot />
|
||||
</SelectPrimitive.Root>
|
@ -3,6 +3,7 @@
|
||||
import { cn } from '$lib/utils';
|
||||
|
||||
type $$Props = SwitchPrimitive.Props;
|
||||
type $$Events = SwitchPrimitive.Events;
|
||||
|
||||
let className: $$Props['class'] = undefined;
|
||||
export let checked: $$Props['checked'] = undefined;
|
||||
@ -16,6 +17,8 @@
|
||||
className,
|
||||
)}
|
||||
{...$$restProps}
|
||||
on:click
|
||||
on:keydown
|
||||
>
|
||||
<SwitchPrimitive.Thumb
|
||||
class={cn(
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
<textarea
|
||||
class={cn(
|
||||
'flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
bind:value
|
||||
|
@ -1,5 +1,5 @@
|
||||
export const WG_PATH = '/etc/wireguard';
|
||||
|
||||
export const IPV4_REGEX = new RegExp(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
|
||||
|
||||
export const WG_SEVER_PATH = `WG::SERVERS`;
|
||||
|
||||
export const AUTH_COOKIE = 'authorization';
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { createEnv } from '@t3-oss/env-core';
|
||||
import { z } from 'zod';
|
||||
import { sha256 } from '$lib/hash';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import 'dotenv/config';
|
||||
|
||||
export const env = createEnv({
|
||||
runtimeEnv: process.env,
|
||||
server: {
|
||||
STORAGE_PATH: z.string().default('/data/storage.pack'),
|
||||
AUTH_SECRET: z.string().default(sha256(randomUUID())),
|
||||
HASHED_PASSWORD: z.string(),
|
||||
ORIGIN: z.string().optional(),
|
||||
},
|
||||
});
|
||||
|
@ -1,32 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export default class FileManager {
|
||||
static readDirectoryFiles(dir: string): string[] {
|
||||
const files_: string[] = [];
|
||||
const files = fs.readdirSync(dir);
|
||||
for (const i in files) {
|
||||
const name = dir + '/' + files[i];
|
||||
if (!fs.statSync(name).isDirectory()) {
|
||||
files_.push(path.resolve(process.cwd(), name));
|
||||
}
|
||||
}
|
||||
return files_;
|
||||
}
|
||||
|
||||
static readFile(filePath: string): string {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error('file not found');
|
||||
}
|
||||
return fs.readFileSync(filePath, { encoding: 'utf8' });
|
||||
}
|
||||
|
||||
static writeFile(filePath: string, content: string, forced: boolean = false): void {
|
||||
const dir_ = filePath.split('/');
|
||||
const dir = dir_.slice(0, dir_.length - 1).join('/');
|
||||
if (!fs.existsSync(dir) && forced) {
|
||||
fs.mkdirSync(dir, { mode: 0o744 });
|
||||
}
|
||||
fs.writeFileSync(filePath, content, { encoding: 'utf8' });
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { accessSync, promises } from 'fs';
|
||||
import { accessSync, promises } from 'node:fs';
|
||||
|
||||
export function fsAccess(path: string): boolean {
|
||||
try {
|
||||
|
@ -5,7 +5,7 @@ export const SERVICES = <const>{
|
||||
tor: {
|
||||
name: 'Tor',
|
||||
command: {
|
||||
start: 'screen -L -Logfile /var/vlogs/tor -dmS "tor" tor -f /etc/tor/torrc',
|
||||
start: 'screen -dmS "tor" tor -f /etc/tor/torrc',
|
||||
stop: 'pkill tor',
|
||||
logs: 'logs tor',
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
import { IPV4_REGEX } from '$lib/constants';
|
||||
import { NameSchema, TorSchema } from '$lib/wireguard/schema';
|
||||
import { IPV4_REGEX } from 'node-netkit/ip';
|
||||
|
||||
export const WgKeySchema = z.object({
|
||||
privateKey: z.string(),
|
||||
|
@ -2,19 +2,6 @@ import { type ClassValue, clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import type { TransitionConfig } from 'svelte/transition';
|
||||
import { IPV4_REGEX } from '$lib/constants';
|
||||
|
||||
export function isValidIPv4(str: string): boolean {
|
||||
return IPV4_REGEX.test(str);
|
||||
}
|
||||
|
||||
export function isBetween(v: any, n1: number, n2: number): boolean {
|
||||
if (Number.isNaN(v)) {
|
||||
return false;
|
||||
}
|
||||
const n = Number(v);
|
||||
return n1 <= n && n >= n2;
|
||||
}
|
||||
|
||||
export function isJson(data: any): boolean {
|
||||
if (typeof data !== 'string') {
|
||||
@ -28,27 +15,10 @@ export function isJson(data: any): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function isObject(obj: object) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
}
|
||||
|
||||
export async function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Private IP Address Identifier in Regular Expression
|
||||
*
|
||||
* 127. 0.0.0 – 127.255.255.255 127.0.0.0 /8
|
||||
* 10. 0.0.0 – 10.255.255.255 10.0.0.0 /8
|
||||
* 172. 16.0.0 – 172. 31.255.255 172.16.0.0 /12
|
||||
* 192.168.0.0 – 192.168.255.255 192.168.0.0 /16
|
||||
*/
|
||||
export function isPrivateIP(ip: string) {
|
||||
const ipRegex = /^(127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)/;
|
||||
return ipRegex.test(ip);
|
||||
}
|
||||
|
||||
export function dynaJoin(lines: (string | 0 | null | undefined)[]): string[] {
|
||||
return lines.filter((d) => typeof d === 'string') as string[];
|
||||
}
|
||||
|
14
web/src/lib/utils/ip.ts
Normal file
14
web/src/lib/utils/ip.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export const IPV4_REGEX = new RegExp(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/);
|
||||
|
||||
/**
|
||||
* Private IP Address Identifier in Regular Expression
|
||||
*
|
||||
* 127. 0.0.0 – 127.255.255.255 127.0.0.0 /8
|
||||
* 10. 0.0.0 – 10.255.255.255 10.0.0.0 /8
|
||||
* 172. 16.0.0 – 172. 31.255.255 172.16.0.0 /12
|
||||
* 192.168.0.0 – 192.168.255.255 192.168.0.0 /16
|
||||
*/
|
||||
export function isPrivateIP(ip: string) {
|
||||
const ipRegex = /^(127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.)/;
|
||||
return ipRegex.test(ip);
|
||||
}
|
7
web/src/lib/utils/number.ts
Normal file
7
web/src/lib/utils/number.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export function isBetween(v: any, n1: number, n2: number): boolean {
|
||||
if (Number.isNaN(v)) {
|
||||
return false;
|
||||
}
|
||||
const n = Number(v);
|
||||
return n1 <= n && n >= n2;
|
||||
}
|
3
web/src/lib/utils/object.ts
Normal file
3
web/src/lib/utils/object.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export function isObject(obj: object) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import fs from 'fs';
|
||||
import fs from 'node:fs';
|
||||
import path from 'path';
|
||||
import deepmerge from 'deepmerge';
|
||||
import type { Peer, WgKey, WgServer } from '$lib/typings';
|
||||
@ -192,7 +192,7 @@ export class WGServer {
|
||||
static async getFreePeerIp(serverId: string): Promise<string | undefined> {
|
||||
const server = await findServer(serverId);
|
||||
if (!server) {
|
||||
logger.error('WGSerevr: GetFreePeerIP: no server found');
|
||||
logger.error('WGServer: GetFreePeerIP: no server found');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
import { isBetween, isPrivateIP } from '$lib/utils';
|
||||
import { IPV4_REGEX } from '$lib/constants';
|
||||
import { isBetween } from '$lib/utils/number';
|
||||
import { IPV4_REGEX, isPrivateIP } from '$lib/utils/ip';
|
||||
|
||||
export const NameSchema = z
|
||||
.string()
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { type Actions, error } from '@sveltejs/kit';
|
||||
import { type Actions, error, fail } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import {
|
||||
findServer,
|
||||
@ -8,15 +8,16 @@ import {
|
||||
isPortReserved,
|
||||
WGServer,
|
||||
} from '$lib/wireguard';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import { CreateServerSchema } from './schema';
|
||||
import { setError, superValidate } from 'sveltekit-superforms';
|
||||
import { createServerSchema } from './schema';
|
||||
import { NameSchema } from '$lib/wireguard/schema';
|
||||
import logger from '$lib/logger';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
servers: getServers(),
|
||||
form: superValidate(CreateServerSchema),
|
||||
form: await superValidate(zod(createServerSchema)),
|
||||
};
|
||||
};
|
||||
|
||||
@ -43,9 +44,11 @@ export const actions: Actions = {
|
||||
return { ok: true };
|
||||
},
|
||||
create: async (event) => {
|
||||
const form = await superValidate(event, CreateServerSchema);
|
||||
const form = await superValidate(event, zod(createServerSchema));
|
||||
if (!form.valid) {
|
||||
return setError(form, 'Bad Request');
|
||||
return fail(400, {
|
||||
form,
|
||||
});
|
||||
}
|
||||
|
||||
const { name, address, tor = false, port, dns, mtu = '1350' } = form.data;
|
||||
|
@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import BasePage from '$lib/components/page/BasePage.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import CreateServerDialog from './CreateServerDialog.svelte';
|
||||
import Server from './Server.svelte';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
|
||||
import fetchAction from '$lib/fetch-action';
|
||||
import { Empty } from '$lib/components/empty';
|
||||
import Service from './Service.svelte';
|
||||
import { Empty } from '$lib/components/empty';
|
||||
import Server from './Server.svelte';
|
||||
import fetchAction from '$lib/fetch-action';
|
||||
import CreateServerDialog from './CreateServerDialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
<div class={'flex items-center justify-between py-3 px-2'}>
|
||||
<h2 class={'font-bold text-xl'}>Hello there 👋</h2>
|
||||
|
||||
<CreateServerDialog let:builder>
|
||||
<CreateServerDialog data={data.form} let:builder>
|
||||
<Button builders={[builder]}>
|
||||
<i class="fas fa-plus mr-2"></i>
|
||||
Create Server
|
||||
@ -42,7 +42,7 @@
|
||||
</CreateServerDialog>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3.5">
|
||||
<div class="w-full space-y-3.5">
|
||||
<Card>
|
||||
{#if data.servers?.length < 1}
|
||||
<Empty description={'No server!'} />
|
||||
@ -72,5 +72,5 @@
|
||||
</Service>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</BasePage>
|
||||
</div></BasePage
|
||||
>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { CreateServerSchema, type CreateServerSchemaType } from './schema';
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
import { createServerSchema, type CreateServerSchemaType } from './schema';
|
||||
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -10,16 +10,13 @@
|
||||
DialogTrigger,
|
||||
} from '$lib/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormButton,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormInput,
|
||||
FormFieldErrors,
|
||||
FormLabel,
|
||||
FormSwitch,
|
||||
FormValidation,
|
||||
} from '$lib/components/ui/form';
|
||||
import { FormItem } from '$lib/components/ui/form/index.js';
|
||||
import { cn } from '$lib/utils';
|
||||
import {
|
||||
Collapsible,
|
||||
@ -28,11 +25,38 @@
|
||||
} from '$lib/components/ui/collapsible';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
|
||||
let loading: boolean = false;
|
||||
let dialogOpen = false;
|
||||
|
||||
let form: SuperValidated<CreateServerSchemaType>;
|
||||
export let data: SuperValidated<Infer<CreateServerSchemaType>>;
|
||||
|
||||
const form = superForm(data, {
|
||||
dataType: 'json',
|
||||
validators: zodClient(createServerSchema),
|
||||
onSubmit: () => {
|
||||
loading = true;
|
||||
},
|
||||
onError: (e) => {
|
||||
loading = false;
|
||||
console.error('Client-side: FormError:', e);
|
||||
},
|
||||
onResult: ({ result }) => {
|
||||
loading = false;
|
||||
if (result.type === 'success') {
|
||||
dialogOpen = false;
|
||||
toast.success('Server created successfully!');
|
||||
} else {
|
||||
console.error('Server-failure: Result:', result);
|
||||
toast.error('Server failed to create.');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<Dialog bind:open={dialogOpen}>
|
||||
@ -43,98 +67,82 @@
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Server</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form
|
||||
{form}
|
||||
schema={CreateServerSchema}
|
||||
class="space-y-3.5"
|
||||
action="?/create"
|
||||
method={'POST'}
|
||||
let:config
|
||||
options={{
|
||||
onSubmit: () => {
|
||||
loading = true;
|
||||
},
|
||||
onError: (e) => {
|
||||
loading = false;
|
||||
console.error('Client-side: FormError:', e);
|
||||
},
|
||||
onResult: ({ result }) => {
|
||||
loading = false;
|
||||
if (result.type === 'success') {
|
||||
dialogOpen = false;
|
||||
toast.success('Server created successfully!');
|
||||
} else {
|
||||
console.error('Server-failure: Result:', result);
|
||||
toast.error('Server failed to create.');
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormField {config} name={'name'}>
|
||||
<FormItem>
|
||||
|
||||
<form method="POST" action="?/create" use:enhance>
|
||||
<FormField {form} name={'name'}>
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormInput placeholder={'e.g. CuteHub'} type={'text'} />
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
<Input
|
||||
{...attrs}
|
||||
bind:value={$formData.name}
|
||||
placeholder={'e.g. CuteHub'}
|
||||
type={'text'}
|
||||
/>
|
||||
<FormFieldErrors />
|
||||
</FormControl>
|
||||
</FormField>
|
||||
|
||||
<FormField {config} name={'address'}>
|
||||
<FormItem>
|
||||
<FormField {form} name={'address'}>
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>Address</FormLabel>
|
||||
<FormInput placeholder={'e.g. 10.8.0.1'} type={'text'} />
|
||||
<FormDescription>This is the Private IP Address of the server.</FormDescription>
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
<Input
|
||||
{...attrs}
|
||||
bind:value={$formData.address}
|
||||
placeholder={'e.g. 10.8.0.1'}
|
||||
type={'text'}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>This is the Private IP Address of the server.</FormDescription>
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
|
||||
<FormField {config} name={'port'}>
|
||||
<FormItem>
|
||||
<FormField {form} name={'port'}>
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>Port</FormLabel>
|
||||
<FormInput placeholder={'e.g. 51820'} type={'text'} />
|
||||
<FormDescription
|
||||
>This is the port that the WireGuard server will listen on.</FormDescription
|
||||
>
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
<Input {...attrs} bind:value={$formData.port} placeholder={'e.g. 51820'} type={'text'} />
|
||||
</FormControl>
|
||||
<FormDescription>This is the port that the WireGuard server will listen on.</FormDescription
|
||||
>
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger asChild let:builder>
|
||||
<Button builders={[builder]} variant="ghost" size="sm" class="mb-4 -mr-2">
|
||||
<i class="far fa-cog mr-2"></i>
|
||||
<i
|
||||
class={cn('far fa-cog mr-2', builder['data-state'] === 'open' ? 'animate-spin' : '')}
|
||||
/>
|
||||
<span>Advanced Options</span>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent class="space-y-6">
|
||||
<FormField {config} name={'tor'}>
|
||||
<FormItem class="flex items-center justify-between">
|
||||
<div class="space-y-0.5">
|
||||
<FormLabel>Use Tor</FormLabel>
|
||||
<FormDescription>This will route all outgoing traffic through Tor.</FormDescription>
|
||||
</div>
|
||||
<FormSwitch />
|
||||
</FormItem>
|
||||
<FormField {form} name={'tor'}>
|
||||
<FormControl let:attrs>
|
||||
<Switch {...attrs} bind:checked={$formData.tor} />
|
||||
<FormLabel>Use Tor</FormLabel>
|
||||
</FormControl>
|
||||
<FormDescription>This will route all outgoing traffic through Tor.</FormDescription>
|
||||
</FormField>
|
||||
|
||||
<FormField {config} name={'dns'}>
|
||||
<FormItem>
|
||||
<FormField {form} name={'dns'}>
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>DNS</FormLabel>
|
||||
<FormInput placeholder={'e.g. 1.1.1.1'} type={'text'} />
|
||||
<FormDescription
|
||||
>Optional. This is the DNS server that will be pushed to clients.</FormDescription
|
||||
>
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
<Input placeholder={'e.g. 1.1.1.1'} type={'text'} />
|
||||
</FormControl>
|
||||
<FormDescription
|
||||
>Optional. This is the DNS server that will be pushed to clients.</FormDescription
|
||||
>
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
|
||||
<FormField {config} name={'mtu'}>
|
||||
<FormItem>
|
||||
<FormField {form} name={'mtu'}>
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>MTU</FormLabel>
|
||||
<FormInput placeholder={'1350'} type={'text'} />
|
||||
<FormDescription>Optional. Recommended to leave this blank.</FormDescription>
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
<Input {...attrs} bind:value={$formData.mtu} placeholder={'1350'} type={'text'} />
|
||||
</FormControl>
|
||||
<FormDescription>Optional. Recommended to leave this blank.</FormDescription>
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
@ -146,6 +154,6 @@
|
||||
Create
|
||||
</FormButton>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -2,13 +2,14 @@ import { type Actions, error, redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { findServer, generateWgKey, WGServer } from '$lib/wireguard';
|
||||
import { NameSchema } from '$lib/wireguard/schema';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import { CreatePeerSchema } from './schema';
|
||||
import { setError, superValidate } from 'sveltekit-superforms';
|
||||
import { createPeerSchema } from './schema';
|
||||
import logger from '$lib/logger';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const { serverId } = params;
|
||||
const exists = await WGServer.exists(serverId ?? '');
|
||||
const exists = WGServer.exists(serverId ?? '');
|
||||
|
||||
if (!exists) {
|
||||
logger.warn(`Server not found. Redirecting to home page. ServerId: ${serverId}`);
|
||||
@ -31,6 +32,7 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||
return {
|
||||
server,
|
||||
usage,
|
||||
form: await superValidate(zod(createPeerSchema)),
|
||||
};
|
||||
};
|
||||
|
||||
@ -153,7 +155,7 @@ export const actions: Actions = {
|
||||
return { ok: true };
|
||||
},
|
||||
create: async (event) => {
|
||||
const form = await superValidate(event, CreatePeerSchema);
|
||||
const form = await superValidate(event, zod(createPeerSchema));
|
||||
if (!form.valid) {
|
||||
logger.warn('CreatePeer: Bad Request: failed to validate form');
|
||||
return setError(form, 'Bad Request');
|
||||
|
@ -15,6 +15,7 @@
|
||||
import { onDestroy } from 'svelte';
|
||||
|
||||
export let data: PageData;
|
||||
let dialogOpen = false;
|
||||
|
||||
const handleRename = async (peerId: string, name: string) => {
|
||||
const resp = await fetchAction({
|
||||
@ -75,7 +76,9 @@
|
||||
|
||||
// revalidate every 2 seconds
|
||||
const interval = setInterval(() => {
|
||||
invalidateAll();
|
||||
if (!dialogOpen) {
|
||||
invalidateAll();
|
||||
}
|
||||
}, 2000);
|
||||
|
||||
onDestroy(() => {
|
||||
@ -206,8 +209,9 @@
|
||||
<Empty description={'No Clients!'} />
|
||||
</CardContent>
|
||||
{/if}
|
||||
|
||||
<CardFooter>
|
||||
<CreatePeerDialog let:builder>
|
||||
<CreatePeerDialog data={data.form} let:builder bind:open={dialogOpen}>
|
||||
<Button size="sm" builders={[builder]}>Add Client</Button>
|
||||
</CreatePeerDialog>
|
||||
</CardFooter>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { CreatePeerSchema, type CreatePeerSchemaType } from './schema';
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
import { createPeerSchema, type CreatePeerSchemaType } from './schema';
|
||||
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -10,31 +10,54 @@
|
||||
DialogTrigger,
|
||||
} from '$lib/components/ui/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormButton,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormInput,
|
||||
FormItem,
|
||||
FormFieldErrors,
|
||||
FormLabel,
|
||||
FormValidation,
|
||||
} from '$lib/components/ui/form';
|
||||
import { cn } from '$lib/utils';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import toast from 'svelte-french-toast';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
|
||||
let loading: boolean = false;
|
||||
let dialogOpen = false;
|
||||
export let open = false;
|
||||
|
||||
let form: SuperValidated<CreatePeerSchemaType>;
|
||||
export let data: SuperValidated<Infer<CreatePeerSchemaType>>;
|
||||
|
||||
const form = superForm(data, {
|
||||
dataType: 'json',
|
||||
validators: zodClient(createPeerSchema),
|
||||
onSubmit: () => {
|
||||
loading = true;
|
||||
},
|
||||
onError: (e) => {
|
||||
loading = false;
|
||||
console.error('Client-side: FormError:', e);
|
||||
},
|
||||
onResult: ({ result }) => {
|
||||
if (result.type === 'success') {
|
||||
handleSuccess();
|
||||
} else {
|
||||
toast.error('Failed to create peer');
|
||||
console.error('Server-failure: Result:', result);
|
||||
}
|
||||
loading = false;
|
||||
},
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
|
||||
const handleSuccess = async () => {
|
||||
await invalidateAll();
|
||||
toast.success('Peer created!');
|
||||
dialogOpen = false;
|
||||
open = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<Dialog bind:open={dialogOpen}>
|
||||
<Dialog bind:open>
|
||||
<DialogTrigger asChild let:builder>
|
||||
<slot {builder} />
|
||||
</DialogTrigger>
|
||||
@ -42,38 +65,18 @@
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create Peer</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form
|
||||
{form}
|
||||
schema={CreatePeerSchema}
|
||||
class="space-y-3.5"
|
||||
action="?/create"
|
||||
method={'POST'}
|
||||
let:config
|
||||
options={{
|
||||
onSubmit: () => {
|
||||
loading = true;
|
||||
},
|
||||
onError: (e) => {
|
||||
loading = false;
|
||||
console.error('Client-side: FormError:', e);
|
||||
},
|
||||
onResult: ({ result }) => {
|
||||
if (result.type === 'success') {
|
||||
handleSuccess();
|
||||
} else {
|
||||
toast.error('Failed to create peer');
|
||||
console.error('Server-failure: Result:', result);
|
||||
}
|
||||
loading = false;
|
||||
},
|
||||
}}
|
||||
>
|
||||
<FormField {config} name={'name'}>
|
||||
<FormItem>
|
||||
<form class="space-y-3.5" method="POST" action="?/create" use:enhance>
|
||||
<FormField {form} name={'name'}>
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormInput placeholder={'e.g. Unicorn'} type={'text'} />
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
<Input
|
||||
{...attrs}
|
||||
bind:value={$formData.name}
|
||||
placeholder={'e.g. Unicorn'}
|
||||
type={'text'}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
|
||||
<DialogFooter>
|
||||
@ -83,6 +86,6 @@
|
||||
Create
|
||||
</FormButton>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { NameSchema } from '$lib/wireguard/schema';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CreatePeerSchema = z.object({
|
||||
export const createPeerSchema = z.object({
|
||||
name: NameSchema,
|
||||
});
|
||||
|
||||
export type CreatePeerSchemaType = typeof CreatePeerSchema;
|
||||
export type CreatePeerSchemaType = typeof createPeerSchema;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import logger from '$lib/logger';
|
||||
import { execaCommand } from 'execa';
|
||||
import { execa } from 'execa';
|
||||
import 'dotenv/config';
|
||||
|
||||
export const GET: RequestHandler = async () => {
|
||||
|
@ -1,30 +1,30 @@
|
||||
import type { Actions } from '@sveltejs/kit';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { setError, superValidate } from 'sveltekit-superforms/server';
|
||||
import { setError, superValidate } from 'sveltekit-superforms';
|
||||
import { formSchema } from './schema';
|
||||
import { generateToken } from '$lib/auth';
|
||||
import logger from '$lib/logger';
|
||||
import dotenv from 'dotenv';
|
||||
import { zod } from 'sveltekit-superforms/adapters';
|
||||
import { env } from '$lib/env';
|
||||
import { AUTH_COOKIE } from '$lib/constants';
|
||||
|
||||
export const load: PageServerLoad = () => {
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
form: superValidate(formSchema),
|
||||
form: await superValidate(zod(formSchema)),
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
default: async (event) => {
|
||||
const { cookies } = event;
|
||||
const form = await superValidate(event, formSchema);
|
||||
const form = await superValidate(event, zod(formSchema));
|
||||
|
||||
if (!form.valid) {
|
||||
return fail(400, { ok: false, message: 'Bad Request', form });
|
||||
}
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const { HASHED_PASSWORD } = process.env;
|
||||
const { HASHED_PASSWORD } = env;
|
||||
if (HASHED_PASSWORD && HASHED_PASSWORD !== '') {
|
||||
const { password } = form.data;
|
||||
|
||||
@ -34,18 +34,14 @@ export const actions: Actions = {
|
||||
if (hashed !== receivedHashed) {
|
||||
return setError(form, 'password', 'Incorrect password.');
|
||||
}
|
||||
}
|
||||
|
||||
if (!HASHED_PASSWORD || HASHED_PASSWORD === '') {
|
||||
} else {
|
||||
logger.warn('No password is set!');
|
||||
}
|
||||
|
||||
const token = await generateToken();
|
||||
|
||||
const { ORIGIN } = process.env;
|
||||
|
||||
const secure = ORIGIN?.startsWith('https://') ?? false;
|
||||
cookies.set('authorization', token, {
|
||||
const secure = env.ORIGIN?.startsWith('https://') ?? false;
|
||||
cookies.set(AUTH_COOKIE, token, {
|
||||
secure,
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
|
@ -1,26 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { formSchema, type FormSchema } from './schema';
|
||||
import type { SuperValidated } from 'sveltekit-superforms';
|
||||
import { type Infer, superForm, type SuperValidated } from 'sveltekit-superforms';
|
||||
import { Card, CardContent } from '$lib/components/ui/card';
|
||||
import {
|
||||
Form,
|
||||
FormButton,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormInput,
|
||||
FormItem,
|
||||
FormFieldErrors,
|
||||
FormLabel,
|
||||
FormValidation,
|
||||
} from '$lib/components/ui/form';
|
||||
import { zodClient } from 'sveltekit-superforms/adapters';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { FormOptions } from 'formsnap';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
|
||||
export let form: SuperValidated<FormSchema>;
|
||||
export let data: SuperValidated<Infer<FormSchema>>;
|
||||
|
||||
const options: FormOptions<typeof formSchema> = {
|
||||
validators: formSchema,
|
||||
warnings: {
|
||||
noValidationAndConstraints: false,
|
||||
},
|
||||
const form = superForm(data, {
|
||||
dataType: 'json',
|
||||
validators: zodClient(formSchema),
|
||||
onResult: ({ result }) => {
|
||||
if (result.type === 'success') {
|
||||
goto('/');
|
||||
@ -28,29 +25,29 @@
|
||||
console.error('Server-failure: Validation failed');
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { form: formData, enhance } = form;
|
||||
</script>
|
||||
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Form {form} {options} schema={formSchema} let:config let:enhance asChild>
|
||||
<form method="POST" class="pt-4 space-y-8" use:enhance>
|
||||
<div class="w-full flex items-center justify-center">
|
||||
<div class="w-16 aspect-square flex items-center justify-center rounded-full bg-gray-200">
|
||||
<i class="fas fa-user text-primary text-2xl" />
|
||||
</div>
|
||||
<form method="POST" class="pt-4 space-y-8" use:enhance>
|
||||
<div class="w-full flex items-center justify-center">
|
||||
<div class="w-16 aspect-square flex items-center justify-center rounded-full bg-gray-200">
|
||||
<i class="fas fa-user text-primary text-2xl" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormField {config} name="password">
|
||||
<FormItem>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<FormInput type="password" autocomplete="off" />
|
||||
<FormValidation />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
<FormField {form} name="password">
|
||||
<FormControl let:attrs>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<Input {...attrs} bind:value={$formData.password} type="password" autocomplete="off" />
|
||||
</FormControl>
|
||||
<FormFieldErrors />
|
||||
</FormField>
|
||||
|
||||
<FormButton class="w-full">Sign In</FormButton>
|
||||
</form>
|
||||
</Form>
|
||||
<FormButton class="w-full">Sign In</FormButton>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import { revokeToken } from '$lib/auth';
|
||||
import { AUTH_COOKIE } from '$lib/constants';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies }) => {
|
||||
if (!!cookies.get('authorization')) {
|
||||
const token = cookies.get('authorization')!;
|
||||
await revokeToken(token).catch(() => {});
|
||||
const authToken = cookies.get(AUTH_COOKIE);
|
||||
if (!!authToken) {
|
||||
await revokeToken(authToken).catch(() => {});
|
||||
}
|
||||
cookies.delete('authorization', {
|
||||
cookies.delete(AUTH_COOKIE, {
|
||||
path: '/',
|
||||
});
|
||||
throw redirect(302, '/login');
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
TorSchema,
|
||||
} from '$lib/wireguard/schema';
|
||||
|
||||
export const CreateServerSchema = z.object({
|
||||
export const createServerSchema = z.object({
|
||||
name: NameSchema,
|
||||
address: AddressSchema,
|
||||
port: PortSchema,
|
||||
@ -17,4 +17,4 @@ export const CreateServerSchema = z.object({
|
||||
mtu: MtuSchema,
|
||||
});
|
||||
|
||||
export type CreateServerSchemaType = typeof CreateServerSchema;
|
||||
export type CreateServerSchemaType = typeof createServerSchema;
|
||||
|
Loading…
Reference in New Issue
Block a user