mirror of https://github.com/wg-easy/wg-easy
97 changed files with 9016 additions and 3165 deletions
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 167 KiB |
@ -0,0 +1 @@ |
|||||
|
setups.@nuxt/test-utils="4.0.0" |
||||
@ -0,0 +1,7 @@ |
|||||
|
:root { |
||||
|
color-scheme: light; |
||||
|
} |
||||
|
|
||||
|
.dark { |
||||
|
color-scheme: dark; |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
<template> |
||||
|
<textarea |
||||
|
v-model="data" |
||||
|
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400" |
||||
|
/> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
const data = defineModel<string>(); |
||||
|
</script> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<template> |
||||
|
<div class="flex items-center"> |
||||
|
<FormLabel :for="id"> |
||||
|
{{ label }} |
||||
|
</FormLabel> |
||||
|
<BaseTooltip v-if="description" :text="description"> |
||||
|
<IconsInfo class="size-4" /> |
||||
|
</BaseTooltip> |
||||
|
</div> |
||||
|
<BaseTextArea |
||||
|
:id="id" |
||||
|
v-model.trim="data" |
||||
|
:name="id" |
||||
|
:autocomplete="autocomplete" |
||||
|
:disabled="disabled" |
||||
|
/> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
defineProps<{ |
||||
|
id: string; |
||||
|
label: string; |
||||
|
description?: string; |
||||
|
autocomplete?: string; |
||||
|
disabled?: boolean; |
||||
|
}>(); |
||||
|
|
||||
|
const data = defineModel<string>(); |
||||
|
</script> |
||||
@ -0,0 +1,7 @@ |
|||||
|
<template> |
||||
|
<ClipboardDocumentIcon /> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import ClipboardDocumentIcon from '@heroicons/vue/24/outline/esm/ClipboardDocumentIcon'; |
||||
|
</script> |
||||
@ -0,0 +1,7 @@ |
|||||
|
<template> |
||||
|
<MagnifyingGlassIcon /> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup> |
||||
|
import MagnifyingGlassIcon from '@heroicons/vue/24/outline/esm/MagnifyingGlassIcon'; |
||||
|
</script> |
||||
@ -1,18 +1,25 @@ |
|||||
|
import type { H3Event } from 'h3'; |
||||
|
import type { SharedPublicUser } from '~~/shared/utils/permissions'; |
||||
|
|
||||
export const useAuthStore = defineStore('Auth', () => { |
export const useAuthStore = defineStore('Auth', () => { |
||||
const { data: userData, refresh: update } = useFetch('/api/session', { |
const userData = useState<SharedPublicUser | null>('user-data', () => null); |
||||
method: 'get', |
|
||||
}); |
|
||||
|
|
||||
async function getSession() { |
async function getSession(event?: H3Event) { |
||||
|
const fetch = event?.$fetch || $fetch; |
||||
try { |
try { |
||||
const { data } = await useFetch('/api/session', { |
const data = await fetch('/api/session', { |
||||
method: 'get', |
method: 'get', |
||||
}); |
}); |
||||
return data.value; |
return data; |
||||
} catch { |
} catch { |
||||
return null; |
return null; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
async function update() { |
||||
|
const data = await getSession(); |
||||
|
userData.value = data; |
||||
|
} |
||||
|
|
||||
return { userData, update, getSession }; |
return { userData, update, getSession }; |
||||
}); |
}); |
||||
|
|||||
@ -0,0 +1,71 @@ |
|||||
|
import { defineCommand } from 'citty'; |
||||
|
import { consola } from 'consola'; |
||||
|
import { eq } from 'drizzle-orm'; |
||||
|
|
||||
|
import { db, schema } from '../db'; |
||||
|
import { hashPassword } from '../../server/utils/password'; |
||||
|
|
||||
|
export default defineCommand({ |
||||
|
meta: { |
||||
|
name: 'db:admin:reset', |
||||
|
description: 'Reset the admin user password and TOTP settings', |
||||
|
}, |
||||
|
args: { |
||||
|
password: { |
||||
|
type: 'string', |
||||
|
description: 'New password for the admin user', |
||||
|
required: false, |
||||
|
}, |
||||
|
}, |
||||
|
async run(ctx) { |
||||
|
let password = ctx.args.password || undefined; |
||||
|
if (!password) { |
||||
|
password = await consola.prompt('Please enter a new password:', { |
||||
|
type: 'text', |
||||
|
}); |
||||
|
} |
||||
|
if (!password) { |
||||
|
consola.error('Password is required'); |
||||
|
return; |
||||
|
} |
||||
|
if (password.length < 12) { |
||||
|
consola.error('Password must be at least 12 characters long'); |
||||
|
return; |
||||
|
} |
||||
|
consola.info('Setting new password for admin user...'); |
||||
|
const hash = await hashPassword(password); |
||||
|
|
||||
|
const user = await db.transaction(async (tx) => { |
||||
|
const user = await tx |
||||
|
.select() |
||||
|
.from(schema.user) |
||||
|
.where(eq(schema.user.id, 1)) |
||||
|
.get(); |
||||
|
|
||||
|
if (!user) { |
||||
|
consola.error('Admin user not found'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
await tx |
||||
|
.update(schema.user) |
||||
|
.set({ |
||||
|
password: hash, |
||||
|
totpVerified: false, |
||||
|
totpKey: null, |
||||
|
}) |
||||
|
.where(eq(schema.user.id, 1)); |
||||
|
|
||||
|
return user; |
||||
|
}); |
||||
|
|
||||
|
if (!user) { |
||||
|
consola.error('Failed to update admin user'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
consola.success( |
||||
|
`Successfully updated admin user ${user.id} (${user.username})` |
||||
|
); |
||||
|
}, |
||||
|
}); |
||||
@ -0,0 +1,29 @@ |
|||||
|
import { defineCommand } from 'citty'; |
||||
|
import { consola } from 'consola'; |
||||
|
|
||||
|
import { db } from '../db'; |
||||
|
|
||||
|
export default defineCommand({ |
||||
|
meta: { |
||||
|
name: 'clients:list', |
||||
|
description: 'List all clients', |
||||
|
}, |
||||
|
async run() { |
||||
|
consola.info('Listing all clients...'); |
||||
|
const clients = await db.query.client.findMany({ |
||||
|
columns: { |
||||
|
id: true, |
||||
|
name: true, |
||||
|
publicKey: true, |
||||
|
enabled: true, |
||||
|
}, |
||||
|
}); |
||||
|
|
||||
|
if (clients.length === 0) { |
||||
|
consola.info('No clients found'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
console.table(clients); |
||||
|
}, |
||||
|
}); |
||||
@ -0,0 +1,71 @@ |
|||||
|
import { defineCommand } from 'citty'; |
||||
|
import { consola } from 'consola'; |
||||
|
import { eq } from 'drizzle-orm'; |
||||
|
|
||||
|
import { wg } from '../../server/utils/wgHelper'; |
||||
|
import { encodeQRCodeTerm } from '../../server/utils/qr'; |
||||
|
import { db, schema } from '../db'; |
||||
|
|
||||
|
export default defineCommand({ |
||||
|
meta: { |
||||
|
name: 'clients:qr', |
||||
|
description: 'Generate QR code for a client', |
||||
|
}, |
||||
|
args: { |
||||
|
id: { |
||||
|
required: true, |
||||
|
type: 'positional', |
||||
|
}, |
||||
|
ipv6: { |
||||
|
required: false, |
||||
|
type: 'boolean', |
||||
|
default: true, |
||||
|
}, |
||||
|
}, |
||||
|
async run(ctx) { |
||||
|
const clientId = Number(ctx.args.id); |
||||
|
const enableIpv6 = ctx.args.ipv6; |
||||
|
|
||||
|
if (Number.isNaN(clientId)) { |
||||
|
consola.error('Invalid client ID'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
consola.info('Generating QR code for client...'); |
||||
|
|
||||
|
const wgInterface = await db.query.wgInterface.findFirst({ |
||||
|
where: eq(schema.wgInterface.name, 'wg0'), |
||||
|
}); |
||||
|
if (!wgInterface) { |
||||
|
consola.error('WireGuard interface not found'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const userConfig = await db.query.userConfig.findFirst({ |
||||
|
where: eq(schema.userConfig.id, 'wg0'), |
||||
|
}); |
||||
|
if (!userConfig) { |
||||
|
consola.error('User config not found'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const client = await db.query.client.findFirst({ |
||||
|
where: eq(schema.client.id, clientId), |
||||
|
}); |
||||
|
if (!client) { |
||||
|
consola.error(`Client with ID ${clientId} not found`); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const clientConfig = wg.generateClientConfig( |
||||
|
wgInterface, |
||||
|
userConfig, |
||||
|
client, |
||||
|
{ |
||||
|
enableIpv6, |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
consola.log(encodeQRCodeTerm(clientConfig)); |
||||
|
}, |
||||
|
}); |
||||
@ -0,0 +1,10 @@ |
|||||
|
import { createClient } from '@libsql/client'; |
||||
|
import { drizzle } from 'drizzle-orm/libsql'; |
||||
|
|
||||
|
import * as schema from '../server/database/schema'; |
||||
|
|
||||
|
//const client = createClient({ url: 'file:../data/wg-easy.db' });
|
||||
|
const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' }); |
||||
|
export const db = drizzle({ client, schema }); |
||||
|
|
||||
|
export { schema }; |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"lib": ["ESNext"], |
||||
|
"module": "esnext", |
||||
|
"target": "es2024", |
||||
|
"esModuleInterop": true, |
||||
|
"strict": true, |
||||
|
"skipLibCheck": true, |
||||
|
"moduleResolution": "bundler" |
||||
|
}, |
||||
|
"include": ["./**/*.ts"] |
||||
|
} |
||||
@ -0,0 +1,286 @@ |
|||||
|
{ |
||||
|
"pages": { |
||||
|
"me": "Профил", |
||||
|
"clients": "Клиенти", |
||||
|
"admin": { |
||||
|
"panel": "Админ Панел", |
||||
|
"general": "Общи", |
||||
|
"config": "Конфигурация", |
||||
|
"interface": "Интерфейс", |
||||
|
"hooks": "Hooks" |
||||
|
} |
||||
|
}, |
||||
|
"user": { |
||||
|
"email": "Имейл" |
||||
|
}, |
||||
|
"me": { |
||||
|
"currentPassword": "Текуща парола", |
||||
|
"enable2fa": "Активирай двуфакторна автентикация", |
||||
|
"enable2faDesc": "Сканирай QR кода с твоето приложение за автентикатор или въведи ключа ръчно.", |
||||
|
"2faKey": "TOTP ключ", |
||||
|
"2faCodeDesc": "Въведи кода от твоето приложение за автентикатор.", |
||||
|
"disable2fa": "Деактивирай двуфакторна автентикация", |
||||
|
"disable2faDesc": "Въведи паролата си, за да деактивираш двуфакторната автентикация." |
||||
|
}, |
||||
|
"general": { |
||||
|
"name": "Име", |
||||
|
"username": "Потребителско име", |
||||
|
"password": "Парола", |
||||
|
"newPassword": "Нова парола", |
||||
|
"updatePassword": "Обнови парола", |
||||
|
"mtu": "MTU", |
||||
|
"allowedIps": "Разрешени IP-та", |
||||
|
"dns": "DNS", |
||||
|
"persistentKeepalive": "Постоянно поддържане на връзката", |
||||
|
"logout": "Изход", |
||||
|
"continue": "Продължи", |
||||
|
"host": "Хост", |
||||
|
"port": "Порт", |
||||
|
"yes": "Да", |
||||
|
"no": "Не", |
||||
|
"confirmPassword": "Потвърди парола", |
||||
|
"loading": "Зареждане...", |
||||
|
"2fa": "Двуфакторна автентикация", |
||||
|
"2faCode": "TOTP код" |
||||
|
}, |
||||
|
"setup": { |
||||
|
"welcome": "Добре дошъл в първоначалната настройка на wg-easy", |
||||
|
"welcomeDesc": "Откри най-лесния начин да инсталираш и управляваш WireGuard на всеки Linux сървър", |
||||
|
"existingSetup": "Имаш ли вече съществуваща инсталация?", |
||||
|
"createAdminDesc": "Моля, първо въведи администраторско потребителско име и силна сигурна парола. Тези данни ще се използват за вход в административния панел.", |
||||
|
"setupConfigDesc": "Моля, въведи хост и порт. Тази информация ще се използва при генериране на клиентски конфигурации за WireGuard.", |
||||
|
"setupMigrationDesc": "Ако желаеш да мигрираш данните от предишна версия на wg-easy, качи резервното копие.", |
||||
|
"upload": "Качи", |
||||
|
"migration": "Възстанови от резервно копие:", |
||||
|
"createAccount": "Създай акаунт", |
||||
|
"successful": "Настройката е успешна", |
||||
|
"hostDesc": "Публично име/адрес, към който клиентите ще се свързват", |
||||
|
"portDesc": "Публичен UDP порт, на който клиентите ще се свързват и на който WireGuard слуша" |
||||
|
}, |
||||
|
"update": { |
||||
|
"updateAvailable": "Налична е актуализация!", |
||||
|
"update": "Актуализирай" |
||||
|
}, |
||||
|
"theme": { |
||||
|
"dark": "Тъмна тема", |
||||
|
"light": "Светла тема", |
||||
|
"system": "Системна тема" |
||||
|
}, |
||||
|
"layout": { |
||||
|
"toggleCharts": "Покажи/скрий графики", |
||||
|
"donate": "Дарение" |
||||
|
}, |
||||
|
"login": { |
||||
|
"signIn": "Вход", |
||||
|
"rememberMe": "Запомни ме", |
||||
|
"rememberMeDesc": "Остани влязъл след затваряне на браузъра", |
||||
|
"insecure": "Не можеш да влезеш през несигурна връзка. Използвай HTTPS.", |
||||
|
"2faRequired": "Изисква се двуфакторна автентикация", |
||||
|
"2faWrong": "Грешен код за двуфакторна автентикация" |
||||
|
}, |
||||
|
"client": { |
||||
|
"empty": "Все още няма клиенти.", |
||||
|
"newShort": "Нов", |
||||
|
"sort": "Сортирай", |
||||
|
"create": "Създай клиент", |
||||
|
"created": "Клиентът е създаден", |
||||
|
"new": "Нов клиент", |
||||
|
"name": "Име", |
||||
|
"expireDate": "Дата на изтичане", |
||||
|
"expireDateDesc": "Дата, след която клиентът ще бъде деактивиран. Празно = постоянен", |
||||
|
"delete": "Изтрий", |
||||
|
"deleteClient": "Изтрий клиент", |
||||
|
"deleteDialog1": "Сигурен ли си, че искаш да изтриеш", |
||||
|
"deleteDialog2": "Това действие е необратимо.", |
||||
|
"enabled": "Активен", |
||||
|
"address": "Адрес", |
||||
|
"serverAllowedIps": "Разрешени IP-та от сървъра", |
||||
|
"otlDesc": "Генерирай кратък еднократен линк", |
||||
|
"permanent": "Постоянно", |
||||
|
"createdOn": "Създаден на ", |
||||
|
"lastSeen": "Последно видян на ", |
||||
|
"totalDownload": "Общо изтеглени: ", |
||||
|
"totalUpload": "Общо качени: ", |
||||
|
"newClient": "Нов клиент", |
||||
|
"disableClient": "Деактивирай клиент", |
||||
|
"enableClient": "Активирай клиент", |
||||
|
"noPrivKey": "Този клиент няма известен частен ключ. Не може да се създаде конфигурация.", |
||||
|
"showQR": "Покажи QR код", |
||||
|
"downloadConfig": "Изтегли конфигурация", |
||||
|
"allowedIpsDesc": "Кои IP-та ще се насочват през VPN (замества глобалната настройка)", |
||||
|
"serverAllowedIpsDesc": "Кои IP-та сървърът ще насочва към клиента", |
||||
|
"mtuDesc": "Задава максималния размер на пакета (MTU) за VPN тунела", |
||||
|
"persistentKeepaliveDesc": "Интервал (в секунди) за изпращане на keep-alive пакети. 0 = изключено", |
||||
|
"hooks": "Hooks", |
||||
|
"hooksDescription": "Hooks работят само с wg-quick", |
||||
|
"hooksLeaveEmpty": "Само за wg-quick. В противен случай остави празно", |
||||
|
"dnsDesc": "DNS сървър, който клиентите ще използват (замества глобалната настройка)", |
||||
|
"notConnected": "Клиентът не е свързан", |
||||
|
"endpoint": "Крайна точка", |
||||
|
"endpointDesc": "IP адресът на клиента, от който е установена WireGuard връзката", |
||||
|
"search": "Търси клиенти...", |
||||
|
"config": "Конфигурация", |
||||
|
"viewConfig": "Прегледай конфигурацията" |
||||
|
}, |
||||
|
"dialog": { |
||||
|
"change": "Промени", |
||||
|
"cancel": "Отказ", |
||||
|
"create": "Създай" |
||||
|
}, |
||||
|
"toast": { |
||||
|
"success": "Успех", |
||||
|
"saved": "Запазено", |
||||
|
"error": "Грешка" |
||||
|
}, |
||||
|
"form": { |
||||
|
"actions": "Действия", |
||||
|
"save": "Запази", |
||||
|
"revert": "Отмени промените", |
||||
|
"sectionGeneral": "Общи", |
||||
|
"sectionAdvanced": "Разширени", |
||||
|
"noItems": "Няма елементи", |
||||
|
"nullNoItems": "Няма елементи. Използва се глобалната конфигурация", |
||||
|
"add": "Добави" |
||||
|
}, |
||||
|
"admin": { |
||||
|
"general": { |
||||
|
"sessionTimeout": "Време на сесията", |
||||
|
"sessionTimeoutDesc": "Продължителност на сесията при „Запомни ме“ (в секунди)", |
||||
|
"metrics": "Метрики", |
||||
|
"metricsPassword": "Парола", |
||||
|
"metricsPasswordDesc": "Bearer парола за достъп до metrics ендпойнт (парола или argon2 хеш)", |
||||
|
"json": "JSON", |
||||
|
"jsonDesc": "Път за метрики в JSON формат", |
||||
|
"prometheus": "Prometheus", |
||||
|
"prometheusDesc": "Път за Prometheus метрики" |
||||
|
}, |
||||
|
"config": { |
||||
|
"connection": "Връзка", |
||||
|
"hostDesc": "Публично име/адрес за клиентите (инвалидира конфигурациите)", |
||||
|
"portDesc": "Публичен UDP порт за клиентите (инвалидира конфигурациите; вероятно искаш да смениш и порта на интерфейса)", |
||||
|
"allowedIpsDesc": "Разрешени IP-та за клиентите (глобална настройка)", |
||||
|
"dnsDesc": "DNS сървър за клиентите (глобална настройка)", |
||||
|
"mtuDesc": "MTU, който ще ползват клиентите (само за нови клиенти)", |
||||
|
"persistentKeepaliveDesc": "Интервал в секунди за keep-alive към сървъра. 0 = изключено (само за нови клиенти)", |
||||
|
"suggest": "Предложи", |
||||
|
"suggestDesc": "Избери IP адрес или хост за полето Host" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidrSuccess": "CIDR променен", |
||||
|
"device": "Устройство", |
||||
|
"deviceDesc": "Мрежово устройство, през което да се препраща WireGuard трафикът", |
||||
|
"mtuDesc": "MTU, който ще ползва WireGuard", |
||||
|
"portDesc": "UDP порт, на който слуша WireGuard (вероятно искаш да смениш и порта в Config)", |
||||
|
"changeCidr": "Смени CIDR", |
||||
|
"restart": "Рестартирай интерфейс", |
||||
|
"restartDesc": "Рестартиране на WireGuard интерфейса", |
||||
|
"restartWarn": "Сигурен ли си, че искаш да рестартираш интерфейса? Всички клиенти ще бъдат изключени.", |
||||
|
"restartSuccess": "Интерфейсът е рестартиран" |
||||
|
}, |
||||
|
"introText": "Добре дошъл в административния панел.\n\nТук можеш да управляваш общите настройки, конфигурацията, настройките на интерфейса и hooks.\n\nЗапочни, като избереш някоя от секциите в страничното меню." |
||||
|
}, |
||||
|
"zod": { |
||||
|
"generic": { |
||||
|
"required": "{0} е задължително", |
||||
|
"validNumber": "{0} трябва да е валидно число", |
||||
|
"validString": "{0} трябва да е валиден текст", |
||||
|
"validBoolean": "{0} трябва да е валидна булева стойност", |
||||
|
"validArray": "{0} трябва да е валиден масив", |
||||
|
"stringMin": "{0} трябва да съдържа поне {1} символа", |
||||
|
"numberMin": "{0} трябва да е поне {1}" |
||||
|
}, |
||||
|
"client": { |
||||
|
"id": "ID на клиента", |
||||
|
"name": "Име", |
||||
|
"expiresAt": "Изтича на", |
||||
|
"address4": "IPv4 адрес", |
||||
|
"address6": "IPv6 адрес", |
||||
|
"serverAllowedIps": "Разрешени IP-та от сървъра" |
||||
|
}, |
||||
|
"user": { |
||||
|
"username": "Потребителско име", |
||||
|
"password": "Парола", |
||||
|
"remember": "Запомни", |
||||
|
"name": "Име", |
||||
|
"email": "Имейл", |
||||
|
"emailInvalid": "Имейлът трябва да е валиден", |
||||
|
"passwordMatch": "Паролите трябва да съвпадат", |
||||
|
"totpEnable": "Активиране на TOTP", |
||||
|
"totpEnableTrue": "TOTP Enable трябва да е true", |
||||
|
"totpCode": "TOTP код" |
||||
|
}, |
||||
|
"userConfig": { |
||||
|
"host": "Хост" |
||||
|
}, |
||||
|
"general": { |
||||
|
"sessionTimeout": "Време на сесията", |
||||
|
"metricsEnabled": "Метрики", |
||||
|
"metricsPassword": "Парола за метрики" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidr": "CIDR", |
||||
|
"device": "Устройство", |
||||
|
"cidrValid": "CIDR трябва да е валиден" |
||||
|
}, |
||||
|
"otl": "Еднократен линк", |
||||
|
"stringMalformed": "Невалиден формат на низа", |
||||
|
"body": "Тялото трябва да е валиден обект", |
||||
|
"hook": "Hook", |
||||
|
"enabled": "Активиран", |
||||
|
"mtu": "MTU", |
||||
|
"port": "Порт", |
||||
|
"persistentKeepalive": "Постоянно поддържане на връзката", |
||||
|
"address": "IP адрес", |
||||
|
"dns": "DNS", |
||||
|
"allowedIps": "Разрешени IP-та", |
||||
|
"file": "Файл" |
||||
|
}, |
||||
|
"hooks": { |
||||
|
"preUp": "PreUp", |
||||
|
"postUp": "PostUp", |
||||
|
"preDown": "PreDown", |
||||
|
"postDown": "PostDown" |
||||
|
}, |
||||
|
"copy": { |
||||
|
"notSupported": "Копиране не се поддържа", |
||||
|
"copied": "Копирано!", |
||||
|
"failed": "Копирането неуспешно", |
||||
|
"copy": "Копирай" |
||||
|
}, |
||||
|
"awg": { |
||||
|
"jCLabel": "Брой junk пакети (Jc)", |
||||
|
"jCDescription": "Брой junk пакети за изпращане (1–128, препоръчително: 4–12)", |
||||
|
"jMinLabel": "Минимален размер на junk пакети (Jmin)", |
||||
|
"jMinDescription": "Минимален размер на junk пакетите (0–1279*, препоръчително: 8, трябва да е < Jmax)", |
||||
|
"jMaxLabel": "Максимален размер на junk пакети (Jmax)", |
||||
|
"jMaxDescription": "Максимален размер на junk пакетите (1–1280*, препоръчително: 80, трябва да е > Jmin)", |
||||
|
"s1Label": "Размер на junk в init пакета (S1)", |
||||
|
"s1Description": "Размер на junk в init пакета (0–1132 [1280*−148=1132], препоръчително: 15–150, S1+56 ≠ S2)", |
||||
|
"s2Label": "Размер на junk в отговорния пакет (S2)", |
||||
|
"s2Description": "Размер на junk в response пакета (0–1188 [1280*−92=1188], препоръчително: 15–150)", |
||||
|
"s3Label": "Размер на junk в cookie reply пакета (S3)", |
||||
|
"s3Description": "Размер на junk в cookie reply пакета", |
||||
|
"s4Label": "Размер на junk в транспортния пакет (S4)", |
||||
|
"s4Description": "Размер на junk в транспортния пакет", |
||||
|
"h1Label": "Init magic header (H1)", |
||||
|
"h1Description": "Стойност на хедера в init пакета (5–2147483647, уникална спрямо H2–H4)", |
||||
|
"h2Label": "Response magic header (H2)", |
||||
|
"h2Description": "Стойност на хедера в response пакета (5–2147483647, уникална спрямо H1, H3, H4)", |
||||
|
"h3Label": "Cookie reply magic header (H3)", |
||||
|
"h3Description": "Стойност на хедера в cookie reply пакета (5–2147483647, уникална спрямо H1, H2, H4)", |
||||
|
"h4Label": "Transport magic header (H4)", |
||||
|
"h4Description": "Стойност на хедера в транспортния пакет (5–2147483647, уникална спрямо H1–H3)", |
||||
|
"i1Label": "Специален junk пакет 1 (I1)", |
||||
|
"i1Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
||||
|
"i2Label": "Специален junk пакет 2 (I2)", |
||||
|
"i2Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
||||
|
"i3Label": "Специален junk пакет 3 (I3)", |
||||
|
"i3Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
||||
|
"i4Label": "Специален junk пакет 4 (I4)", |
||||
|
"i4Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
||||
|
"i5Label": "Специален junk пакет 5 (I5)", |
||||
|
"i5Description": "Пакет за имитация на протокол в hex формат: <b 0x...>", |
||||
|
"mtuNote": "Стойностите зависят от MTU", |
||||
|
"obfuscationParameters": "Параметри за обфускация на AmneziaWG" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,287 @@ |
|||||
|
{ |
||||
|
"pages": { |
||||
|
"me": "Účet", |
||||
|
"clients": "Klienti", |
||||
|
"admin": { |
||||
|
"panel": "Administrace", |
||||
|
"general": "Obecné", |
||||
|
"config": "Konfigurace", |
||||
|
"interface": "Rozhraní", |
||||
|
"hooks": "Nastavení reakcí" |
||||
|
} |
||||
|
}, |
||||
|
"user": { |
||||
|
"email": "E-mail" |
||||
|
}, |
||||
|
"me": { |
||||
|
"currentPassword": "Aktuální heslo", |
||||
|
"enable2fa": "Zapnout dvoufázové ověření (2FA)", |
||||
|
"enable2faDesc": "Naskenujte QR kód ve své autentizační aplikaci nebo zadejte klíč ručně.", |
||||
|
"2faKey": "TOTP klíč", |
||||
|
"2faCodeDesc": "Zadejte kód z vaší autentizační aplikace.", |
||||
|
"disable2fa": "Vypnout dvoufázové ověření", |
||||
|
"disable2faDesc": "Pro vypnutí dvoufázového ověření zadejte své heslo." |
||||
|
}, |
||||
|
"general": { |
||||
|
"name": "Jméno", |
||||
|
"username": "Uživatelské jméno", |
||||
|
"password": "Heslo", |
||||
|
"newPassword": "Nové heslo", |
||||
|
"updatePassword": "Aktualizovat heslo", |
||||
|
"mtu": "MTU", |
||||
|
"allowedIps": "Povolené IP adresy", |
||||
|
"dns": "DNS", |
||||
|
"persistentKeepalive": "Persistent Keepalive", |
||||
|
"logout": "Odhlásit se", |
||||
|
"continue": "Pokračovat", |
||||
|
"host": "Hostitel", |
||||
|
"port": "Port", |
||||
|
"yes": "Ano", |
||||
|
"no": "Ne", |
||||
|
"confirmPassword": "Potvrdit heslo", |
||||
|
"loading": "Načítání...", |
||||
|
"2fa": "Dvoufázové ověření", |
||||
|
"2faCode": "TOTP kód" |
||||
|
}, |
||||
|
"setup": { |
||||
|
"welcome": "Vítejte u první instalace wg-easy", |
||||
|
"welcomeDesc": "Našli jste nejjednodušší způsob, jak instalovat a spravovat WireGuard na jakémkoliv Linuxovém hostiteli", |
||||
|
"existingSetup": "Máte již existující nastavení?", |
||||
|
"createAdminDesc": "Nejprve zadejte uživatelské jméno administrátora a silné heslo. Tyto údaje budou použity pro přihlášení do administrace.", |
||||
|
"setupConfigDesc": "Zadejte údaje o hostiteli a portu. Tyto informace budou použity pro konfiguraci klientů při nastavování WireGuard na jejich zařízeních.", |
||||
|
"setupMigrationDesc": "Pokud chcete migrovat data z předchozí verze wg-easy do nové instalace, nahrajte soubor se zálohou.", |
||||
|
"upload": "Nahrát", |
||||
|
"migration": "Obnovit zálohu:", |
||||
|
"createAccount": "Vytvořit účet", |
||||
|
"successful": "Nastavení bylo úspěšné", |
||||
|
"hostDesc": "Veřejný název hostitele, ke kterému se budou klienti připojovat", |
||||
|
"portDesc": "Veřejný UDP port, ke kterému se budou klienti připojovat a na kterém bude WireGuard naslouchat" |
||||
|
}, |
||||
|
"update": { |
||||
|
"updateAvailable": "Je k dispozici aktualizace!", |
||||
|
"update": "Aktualizovat" |
||||
|
}, |
||||
|
"theme": { |
||||
|
"dark": "Tmavý režim", |
||||
|
"light": "Světlý režim", |
||||
|
"system": "Systémové nastavení" |
||||
|
}, |
||||
|
"layout": { |
||||
|
"toggleCharts": "Zobrazit/skrýt grafy", |
||||
|
"donate": "Přispět" |
||||
|
}, |
||||
|
"login": { |
||||
|
"signIn": "Přihlásit se", |
||||
|
"rememberMe": "Zapamatovat si mě", |
||||
|
"rememberMeDesc": "Zůstat přihlášen i po zavření prohlížeče", |
||||
|
"insecure": "Nemůžete se přihlásit přes nezabezpečené připojení. Použijte HTTPS.", |
||||
|
"2faRequired": "Je vyžadováno dvoufázové ověření", |
||||
|
"2faWrong": "Neplatný kód dvoufázového ověření" |
||||
|
}, |
||||
|
"client": { |
||||
|
"empty": "Zatím zde nejsou žádní klienti.", |
||||
|
"newShort": "Nový", |
||||
|
"sort": "Seřadit", |
||||
|
"create": "Vytvořit klienta", |
||||
|
"created": "Klient vytvořen", |
||||
|
"new": "Nový klient", |
||||
|
"name": "Jméno", |
||||
|
"expireDate": "Datum vypršení", |
||||
|
"expireDateDesc": "Datum, kdy bude klient deaktivován. Ponechte prázdné pro trvalý přístup.", |
||||
|
"delete": "Smazat", |
||||
|
"deleteClient": "Smazat klienta", |
||||
|
"deleteDialog1": "Opravdu chcete smazat uživatele", |
||||
|
"deleteDialog2": "Tuto akci nelze vzít zpět.", |
||||
|
"enabled": "Aktivní", |
||||
|
"address": "Adresa", |
||||
|
"serverAllowedIps": "Povolené IP adresy serveru", |
||||
|
"otlDesc": "Generovat krátký jednorázový odkaz", |
||||
|
"permanent": "Trvalý", |
||||
|
"createdOn": "Vytvořeno dne ", |
||||
|
"lastSeen": "Naposledy viděn ", |
||||
|
"totalDownload": "Celkem staženo: ", |
||||
|
"totalUpload": "Celkem nahráno: ", |
||||
|
"newClient": "Nový klient", |
||||
|
"disableClient": "Deaktivovat klienta", |
||||
|
"enableClient": "Aktivovat klienta", |
||||
|
"noPrivKey": "Tento klient nemá známý soukromý klíč. Nelze vytvořit konfiguraci.", |
||||
|
"showQR": "Zobrazit QR kód", |
||||
|
"downloadConfig": "Stáhnout konfiguraci", |
||||
|
"allowedIpsDesc": "Které IP adresy budou směrovány přes VPN (přebíjí globální nastavení)", |
||||
|
"serverAllowedIpsDesc": "Které IP adresy bude server směrovat ke klientovi", |
||||
|
"mtuDesc": "Nastavuje maximální velikost přenášeného paketu (MTU) pro VPN tunel", |
||||
|
"persistentKeepaliveDesc": "Nastavuje interval (v sekundách) pro udržovací pakety. 0 pro vypnutí", |
||||
|
"hooks": "Hooky", |
||||
|
"hooksDescription": "Hooky fungují pouze s wg-quick", |
||||
|
"hooksLeaveEmpty": "Pouze pro wg-quick. Jinak ponechte prázdné", |
||||
|
"dnsDesc": "DNS server, který budou klienti používat (přebíjí globální nastavení)", |
||||
|
"notConnected": "Klient není připojen", |
||||
|
"endpoint": "Koncový bod", |
||||
|
"endpointDesc": "IP adresa klienta, ze které je navázáno spojení WireGuard", |
||||
|
"search": "Hledat klienty...", |
||||
|
"config": "Konfigurace", |
||||
|
"viewConfig": "Zobrazit konfiguraci" |
||||
|
}, |
||||
|
"dialog": { |
||||
|
"change": "Změnit", |
||||
|
"cancel": "Zrušit", |
||||
|
"create": "Vytvořit" |
||||
|
}, |
||||
|
"toast": { |
||||
|
"success": "Úspěch", |
||||
|
"saved": "Uloženo", |
||||
|
"error": "Chyba" |
||||
|
}, |
||||
|
"form": { |
||||
|
"actions": "Akce", |
||||
|
"save": "Uložit", |
||||
|
"revert": "Vrátit změny", |
||||
|
"sectionGeneral": "Obecné", |
||||
|
"sectionAdvanced": "Pokročilé", |
||||
|
"noItems": "Žádné položky", |
||||
|
"nullNoItems": "Žádné položky. Používá se globální konfigurace", |
||||
|
"add": "Přidat" |
||||
|
}, |
||||
|
"admin": { |
||||
|
"general": { |
||||
|
"sessionTimeout": "Vypršení relace", |
||||
|
"sessionTimeoutDesc": "Doba trvání relace pro 'Zapamatovat si mě' (sekundy)", |
||||
|
"metrics": "Metriky", |
||||
|
"metricsPassword": "Heslo", |
||||
|
"metricsPasswordDesc": "Bearer heslo pro koncový bod metrik (heslo nebo argon2 hash)", |
||||
|
"json": "JSON", |
||||
|
"jsonDesc": "Cesta pro metriky ve formátu JSON", |
||||
|
"prometheus": "Prometheus", |
||||
|
"prometheusDesc": "Cesta pro metriky Prometheus" |
||||
|
}, |
||||
|
"config": { |
||||
|
"connection": "Připojení", |
||||
|
"hostDesc": "Veřejný název hostitele, ke kterému se klienti připojují (zneplatní stávající konfigurace)", |
||||
|
"portDesc": "Veřejný UDP port, ke kterému se klienti připojují (zneplatní stávající konfigurace, pravděpodobně budete chtít změnit i Port rozhraní)", |
||||
|
"allowedIpsDesc": "Povolené IP adresy, které budou klienti používat (globální nastavení)", |
||||
|
"dnsDesc": "DNS server, který budou klienti používat (globální nastavení)", |
||||
|
"mtuDesc": "MTU, které budou klienti používat (pouze pro nové klienty)", |
||||
|
"persistentKeepaliveDesc": "Interval v sekundách pro odesílání udržovacích paketů na server. 0 = vypnuto (pouze pro nové klienty)", |
||||
|
"suggest": "Navrhnout", |
||||
|
"suggestDesc": "Vyberte IP adresu nebo název hostitele pro pole Hostitel" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidrSuccess": "CIDR změněn", |
||||
|
"device": "Zařízení", |
||||
|
"deviceDesc": "Ethernetové zařízení, přes které má být provoz WireGuard přeposílán", |
||||
|
"mtuDesc": "MTU, které bude WireGuard používat", |
||||
|
"portDesc": "UDP port, na kterém bude WireGuard naslouchat (pravděpodobně budete chtít změnit i Konfigurační port)", |
||||
|
"changeCidr": "Změnit CIDR", |
||||
|
"restart": "Restartovat rozhraní", |
||||
|
"restartDesc": "Restartovat rozhraní WireGuard", |
||||
|
"restartWarn": "Opravdu chcete restartovat rozhraní? Dojde k odpojení všech klientů.", |
||||
|
"restartSuccess": "Rozhraní bylo restartováno" |
||||
|
}, |
||||
|
"introText": "Vítejte v administraci.\n\nZde můžete spravovat obecná nastavení, konfiguraci, nastavení rozhraní a hooky.\n\nZačněte výběrem jedné ze sekcí v bočním panelu." |
||||
|
}, |
||||
|
"zod": { |
||||
|
"generic": { |
||||
|
"required": "Pole {0} je povinné", |
||||
|
"validNumber": "{0} musí být platné číslo", |
||||
|
"validNumberRange": "{0} musí být platné číslo nebo rozsah čísel", |
||||
|
"validString": "{0} musí být platný řetězec", |
||||
|
"validBoolean": "{0} musí být platná logická hodnota", |
||||
|
"validArray": "{0} musí být platné pole", |
||||
|
"stringMin": "{0} musí mít alespoň {1} znak(ů)", |
||||
|
"numberMin": "{0} musí být alespoň {1}" |
||||
|
}, |
||||
|
"client": { |
||||
|
"id": "ID klienta", |
||||
|
"name": "Jméno", |
||||
|
"expiresAt": "Vyprší dne", |
||||
|
"address4": "IPv4 adresa", |
||||
|
"address6": "IPv6 adresa", |
||||
|
"serverAllowedIps": "Povolené IP adresy serveru" |
||||
|
}, |
||||
|
"user": { |
||||
|
"username": "Uživatelské jméno", |
||||
|
"password": "Heslo", |
||||
|
"remember": "Pamatovat si", |
||||
|
"name": "Jméno", |
||||
|
"email": "E-mail", |
||||
|
"emailInvalid": "E-mail musí být platná e-mailová adresa", |
||||
|
"passwordMatch": "Hesla se musí shodovat", |
||||
|
"totpEnable": "Zapnout TOTP", |
||||
|
"totpEnableTrue": "Zapnutí TOTP musí být potvrzeno", |
||||
|
"totpCode": "TOTP kód" |
||||
|
}, |
||||
|
"userConfig": { |
||||
|
"host": "Hostitel" |
||||
|
}, |
||||
|
"general": { |
||||
|
"sessionTimeout": "Vypršení relace", |
||||
|
"metricsEnabled": "Metriky", |
||||
|
"metricsPassword": "Heslo k metrikám" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidr": "CIDR", |
||||
|
"device": "Zařízení", |
||||
|
"cidrValid": "CIDR musí být platný" |
||||
|
}, |
||||
|
"otl": "Jednorázový odkaz", |
||||
|
"stringMalformed": "Řetězec má nesprávný formát", |
||||
|
"body": "Tělo požadavku musí být platný objekt", |
||||
|
"hook": "Hook", |
||||
|
"enabled": "Aktivní", |
||||
|
"mtu": "MTU", |
||||
|
"port": "Port", |
||||
|
"persistentKeepalive": "Persistent Keepalive", |
||||
|
"address": "IP adresa", |
||||
|
"dns": "DNS", |
||||
|
"allowedIps": "Povolené IP adresy", |
||||
|
"file": "Soubor" |
||||
|
}, |
||||
|
"hooks": { |
||||
|
"preUp": "PreUp", |
||||
|
"postUp": "PostUp", |
||||
|
"preDown": "PreDown", |
||||
|
"postDown": "PostDown" |
||||
|
}, |
||||
|
"copy": { |
||||
|
"notSupported": "Kopírování není podporováno", |
||||
|
"copied": "Zkopírováno!", |
||||
|
"failed": "Kopírování selhalo", |
||||
|
"copy": "Kopírovat" |
||||
|
}, |
||||
|
"awg": { |
||||
|
"jCLabel": "Počet junk paketů (Jc)", |
||||
|
"jCDescription": "Počet odesílaných junk paketů (1-128, doporučeno: 4-12)", |
||||
|
"jMinLabel": "Min. velikost junk paketu (Jmin)", |
||||
|
"jMinDescription": "Minimální velikost junk paketů (0-1279*, doporučeno: 8, musí být < Jmax)", |
||||
|
"jMaxLabel": "Max. velikost junk paketu (Jmax)", |
||||
|
"jMaxDescription": "Maximální velikost junk paketů (1-1280*, doporučeno: 80, musí být > Jmin)", |
||||
|
"s1Label": "Velikost junk dat u Init paketu (S1)", |
||||
|
"s1Description": "Velikost junk dat u Init paketu (0-1132, doporučeno: 15-150, S1+56 ≠ S2)", |
||||
|
"s2Label": "Velikost junk dat u Response paketu (S2)", |
||||
|
"s2Description": "Velikost junk dat u Response paketu (0-1188, doporučeno: 15-150)", |
||||
|
"s3Label": "Velikost junk dat u Cookie reply paketu (S3)", |
||||
|
"s3Description": "Velikost junk dat u paketu s odpovědí na cookie", |
||||
|
"s4Label": "Velikost junk dat u Transport paketu (S4)", |
||||
|
"s4Description": "Velikost junk dat u transportního paketu", |
||||
|
"h1Label": "Init magic header (H1)", |
||||
|
"h1Description": "Hodnota nebo rozsah hlavičky Init paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
||||
|
"h2Label": "Response magic header (H2)", |
||||
|
"h2Description": "Hodnota nebo rozsah hlavičky Response paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
||||
|
"h3Label": "Cookie reply magic header (H3)", |
||||
|
"h3Description": "Hodnota nebo rozsah hlavičky Cookie reply paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
||||
|
"h4Label": "Transport magic header (H4)", |
||||
|
"h4Description": "Hodnota nebo rozsah hlavičky Transport paketu (X nebo X-Y, kde X<Y. Min 5, max 2147483647. Nesmí se překrývat s ostatními hlavičkami)", |
||||
|
"i1Label": "Speciální junk paket 1 (I1)", |
||||
|
"i1Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
||||
|
"i2Label": "Speciální junk paket 2 (I2)", |
||||
|
"i2Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
||||
|
"i3Label": "Speciální junk paket 3 (I3)", |
||||
|
"i3Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
||||
|
"i4Label": "Speciální junk paket 4 (I4)", |
||||
|
"i4Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
||||
|
"i5Label": "Speciální junk paket 5 (I5)", |
||||
|
"i5Description": "Paket pro napodobení protokolu v hex formátu: <b 0x...>", |
||||
|
"mtuNote": "Hodnoty závisí na nastavení MTU", |
||||
|
"obfuscationParameters": "AmneziaWG parametry obfuskace" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,145 @@ |
|||||
|
{ |
||||
|
"pages": { |
||||
|
"me": "Conta", |
||||
|
"clients": "Clientes", |
||||
|
"admin": { |
||||
|
"panel": "Panel de administración", |
||||
|
"general": "Xeral", |
||||
|
"config": "Configuración", |
||||
|
"interface": "Interface", |
||||
|
"hooks": "Hooks" |
||||
|
} |
||||
|
}, |
||||
|
"user": { |
||||
|
"email": "Correo electrónico" |
||||
|
}, |
||||
|
"me": { |
||||
|
"currentPassword": "Contrasinal actual", |
||||
|
"enable2fa": "Activar a autenticación de dobre factor", |
||||
|
"enable2faDesc": "Escanea o código QR coa túa aplicación de autenticación ou introduce a chave manualmente.", |
||||
|
"2faKey": "Chave TOTP", |
||||
|
"2faCodeDesc": "Introduce o código da túa aplicación de autenticación.", |
||||
|
"disable2fa": "Desactivar a autenticación de dobre factor", |
||||
|
"disable2faDesc": "Introduce o teu contrasinal para desactivar a autenticación de dobre factor." |
||||
|
}, |
||||
|
"general": { |
||||
|
"name": "Nome", |
||||
|
"username": "Nome de usuario", |
||||
|
"password": "Contrasinal", |
||||
|
"newPassword": "Novo contrasinal", |
||||
|
"updatePassword": "Actualizar contrasinal", |
||||
|
"mtu": "MTU", |
||||
|
"allowedIps": "IP permitidas", |
||||
|
"dns": "DNS", |
||||
|
"persistentKeepalive": "Keepalive persistente", |
||||
|
"logout": "Pechar sesión", |
||||
|
"continue": "Continuar", |
||||
|
"host": "Host", |
||||
|
"port": "Porto", |
||||
|
"yes": "Si", |
||||
|
"no": "Non", |
||||
|
"confirmPassword": "Confirmar o contrasinal", |
||||
|
"loading": "Cargando...", |
||||
|
"2fa": "Autenticación de dobre factor", |
||||
|
"2faCode": "Código TOTP" |
||||
|
}, |
||||
|
"setup": { |
||||
|
"welcome": "Benvido á túa primeira configuración de wg-easy", |
||||
|
"welcomeDesc": "Atopaches a forma máis doada de instalar e xestionar WireGuard en calquera sistema Linux", |
||||
|
"existingSetup": "Tes unha configuración existente?", |
||||
|
"createAdminDesc": "Por favor, introduce primeiro un usuario administrador cun contrasinal seguro. Esta información empregarase para acceder ao panel de administración.", |
||||
|
"setupConfigDesc": "Por favor, introduce a información do host e do porto. Isto empregarase para a configuración dos clientes ao configurar WireGuard nos seus dispositivos.", |
||||
|
"setupMigrationDesc": "Por favor, fornece o ficheiro da copia de seguridade se queres migrar os datos da túa versión anterior de wg-easy á nova configuración.", |
||||
|
"upload": "Subir", |
||||
|
"migration": "Recuperar a copia de seguridade:", |
||||
|
"createAccount": "Crear conta", |
||||
|
"successful": "Configuración completada con éxito", |
||||
|
"hostDesc": "Nome de host público ao que se conectarán os clientes", |
||||
|
"portDesc": "Porto UDP público ao que se conectarán os clientes e no que escoitará WireGuard" |
||||
|
}, |
||||
|
"update": { |
||||
|
"updateAvailable": "Hai unha actualización dispoñible!", |
||||
|
"update": "Actualizar" |
||||
|
}, |
||||
|
"theme": { |
||||
|
"dark": "Tema escuro", |
||||
|
"light": "Tema claro", |
||||
|
"system": "Tema do sistema" |
||||
|
}, |
||||
|
"layout": { |
||||
|
"toggleCharts": "Amosar/Ocultar gráficas", |
||||
|
"donate": "Doar" |
||||
|
}, |
||||
|
"login": { |
||||
|
"signIn": "Iniciar sesión", |
||||
|
"rememberMe": "Lembrarme", |
||||
|
"rememberMeDesc": "Manter a sesión iniciada ao pechar o navegador", |
||||
|
"insecure": "Non podes iniciar sesión cunha conexión insegura. Usa HTTPS.", |
||||
|
"2faRequired": "É necesaria a autenticación de dobre factor", |
||||
|
"2faWrong": "A autenticación de dobre factor é incorrecta" |
||||
|
}, |
||||
|
"client": { |
||||
|
"empty": "Aínda non hai clientes.", |
||||
|
"newShort": "Novo", |
||||
|
"sort": "Ordenar", |
||||
|
"create": "Crear cliente", |
||||
|
"created": "Cliente creado", |
||||
|
"new": "Novo cliente", |
||||
|
"name": "Nome", |
||||
|
"expireDate": "Data de caducidade", |
||||
|
"expireDateDesc": "Data na que o cliente será desactivado. Déixao en branco para permanente", |
||||
|
"delete": "Eliminar", |
||||
|
"deleteClient": "Eliminar cliente", |
||||
|
"deleteDialog1": "Seguro que queres eliminar", |
||||
|
"deleteDialog2": "Esta acción non se pode desfacer.", |
||||
|
"enabled": "Activado", |
||||
|
"address": "Enderezo", |
||||
|
"serverAllowedIps": "IP permitidas polo servidor", |
||||
|
"otlDesc": "Xerar ligazón curta dun só uso", |
||||
|
"permanent": "Permanente", |
||||
|
"createdOn": "Creado o ", |
||||
|
"lastSeen": "Visto por última vez o ", |
||||
|
"totalDownload": "Descarga total: ", |
||||
|
"totalUpload": "Subida total: ", |
||||
|
"newClient": "Novo cliente", |
||||
|
"disableClient": "Desactivar cliente", |
||||
|
"enableClient": "Activar cliente", |
||||
|
"noPrivKey": "Este cliente non ten unha chave privada coñecida. Non se pode crear a configuración.", |
||||
|
"showQR": "Amosar código QR", |
||||
|
"downloadConfig": "Descargar configuración", |
||||
|
"allowedIpsDesc": "IP que se encamiñarán a través da VPN (sobrescribe a configuración global)", |
||||
|
"serverAllowedIpsDesc": "IP que o servidor encamiñará ao cliente", |
||||
|
"mtuDesc": "Define a unidade máxima de transmisión (tamaño do paquete) para o túnel VPN", |
||||
|
"persistentKeepaliveDesc": "Define o intervalo (en segundos) para os paquetes keep-alive. 0 desactívaos", |
||||
|
"hooks": "Hooks", |
||||
|
"hooksDescription": "Os hooks só funcionan con wg-quick", |
||||
|
"hooksLeaveEmpty": "Só para wg-quick. Noutro caso, déixao baleiro", |
||||
|
"dnsDesc": "Servidor DNS que empregarán os clientes (sobrescribe a configuración global)", |
||||
|
"notConnected": "Cliente non conectado", |
||||
|
"endpoint": "Punto final", |
||||
|
"endpointDesc": "IP do cliente desde a que se establece a conexión WireGuard", |
||||
|
"search": "Buscar clientes...", |
||||
|
"config": "Configuración", |
||||
|
"viewConfig": "Ver configuración" |
||||
|
}, |
||||
|
"dialog": { |
||||
|
"change": "Cambiar", |
||||
|
"cancel": "Cancelar", |
||||
|
"create": "Crear" |
||||
|
}, |
||||
|
"toast": { |
||||
|
"success": "Éxito", |
||||
|
"saved": "Gardado", |
||||
|
"error": "Erro" |
||||
|
}, |
||||
|
"form": { |
||||
|
"actions": "Accións", |
||||
|
"save": "Gardar", |
||||
|
"revert": "Reverter", |
||||
|
"sectionGeneral": "Xeral", |
||||
|
"sectionAdvanced": "Avanzado", |
||||
|
"noItems": "Sen elementos", |
||||
|
"nullNoItems": "Sen elementos. Usando a configuración global", |
||||
|
"add": "Engadir" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,286 @@ |
|||||
|
{ |
||||
|
"pages": { |
||||
|
"me": "Konto", |
||||
|
"clients": "Klienter", |
||||
|
"admin": { |
||||
|
"panel": "Adminpanel", |
||||
|
"general": "Generelt", |
||||
|
"config": "Oppsett", |
||||
|
"interface": "Grensesnitt", |
||||
|
"hooks": "Hooks" |
||||
|
} |
||||
|
}, |
||||
|
"user": { |
||||
|
"email": "E-post" |
||||
|
}, |
||||
|
"me": { |
||||
|
"currentPassword": "Nåværende passord", |
||||
|
"enable2fa": "Aktiver tofaktorautentisering", |
||||
|
"enable2faDesc": "Skann QR-koden med autentiseringsappen din eller skriv inn nøkkelen manuelt.", |
||||
|
"2faKey": "TOTP-nøkkel", |
||||
|
"2faCodeDesc": "Skriv inn koden fra autentiseringsappen din.", |
||||
|
"disable2fa": "Deaktiver tofaktorautentisering", |
||||
|
"disable2faDesc": "Skriv inn passordet ditt for å deaktivere tofaktorautentisering." |
||||
|
}, |
||||
|
"general": { |
||||
|
"name": "Navn", |
||||
|
"username": "Brukernavn", |
||||
|
"password": "Passord", |
||||
|
"newPassword": "Nytt passord", |
||||
|
"updatePassword": "Oppdater passord", |
||||
|
"mtu": "MTU", |
||||
|
"allowedIps": "Tillatte IP-er", |
||||
|
"dns": "DNS", |
||||
|
"persistentKeepalive": "Vedvarende keepalive", |
||||
|
"logout": "Logg ut", |
||||
|
"continue": "Fortsett", |
||||
|
"host": "Vert", |
||||
|
"port": "Port", |
||||
|
"yes": "Ja", |
||||
|
"no": "Nei", |
||||
|
"confirmPassword": "Bekreft passord", |
||||
|
"loading": "Laster...", |
||||
|
"2fa": "Tofaktorautentisering", |
||||
|
"2faCode": "TOTP-kode" |
||||
|
}, |
||||
|
"setup": { |
||||
|
"welcome": "Velkommen til ditt oppsett av wg-easy", |
||||
|
"welcomeDesc": "Du har funnet den enkleste måten å installere og administrere WireGuard på en hvilken som helst Linux-vert", |
||||
|
"existingSetup": "Har du et eksisterende oppsett?", |
||||
|
"createAdminDesc": "Skriv først inn et adminbrukernavn og et sterkt, sikkert passord. Denne informasjonen brukes til å logge inn i administrasjonspanelet.", |
||||
|
"setupConfigDesc": "Skriv inn vert- og portinformasjon. Dette brukes til klientkonfigurasjonen når du setter opp WireGuard på enhetene deres.", |
||||
|
"setupMigrationDesc": "Oppgi sikkerhetskopifilen hvis du vil migrere dataene dine fra forrige wg-easy-versjon til det nye oppsettet.", |
||||
|
"upload": "Last opp", |
||||
|
"migration": "Gjenopprett sikkerhetskopien:", |
||||
|
"createAccount": "Opprett konto", |
||||
|
"successful": "Oppsett vellykket", |
||||
|
"hostDesc": "Offentlig vertsnavn klienter vil koble seg til", |
||||
|
"portDesc": "Offentlig UDP-port klienter vil koble til og WireGuard vil lytte på" |
||||
|
}, |
||||
|
"update": { |
||||
|
"updateAvailable": "En oppdatering er tilgjengelig!", |
||||
|
"update": "Oppdater" |
||||
|
}, |
||||
|
"theme": { |
||||
|
"dark": "Mørkt tema", |
||||
|
"light": "Lyst tema", |
||||
|
"system": "Systemtema" |
||||
|
}, |
||||
|
"layout": { |
||||
|
"toggleCharts": "Vis/skjul diagrammer", |
||||
|
"donate": "Doner" |
||||
|
}, |
||||
|
"login": { |
||||
|
"signIn": "Logg inn", |
||||
|
"rememberMe": "Husk meg", |
||||
|
"rememberMeDesc": "Hold deg innlogget etter at nettleseren lukkes", |
||||
|
"insecure": "Du kan ikke logge inn med en usikker tilkobling. Bruk HTTPS.", |
||||
|
"2faRequired": "Tofaktorautentisering er påkrevd", |
||||
|
"2faWrong": "Tofaktorautentisering er feil" |
||||
|
}, |
||||
|
"client": { |
||||
|
"empty": "Det finnes ingen klienter ennå.", |
||||
|
"newShort": "Ny", |
||||
|
"sort": "Sorter", |
||||
|
"create": "Opprett klient", |
||||
|
"created": "Klient opprettet", |
||||
|
"new": "Ny klient", |
||||
|
"name": "Navn", |
||||
|
"expireDate": "Utløpsdato", |
||||
|
"expireDateDesc": "Datoen klienten blir deaktivert. Tomt for permanent", |
||||
|
"delete": "Slett", |
||||
|
"deleteClient": "Slett klient", |
||||
|
"deleteDialog1": "Er du sikker på at du vil slette", |
||||
|
"deleteDialog2": "Denne handlingen kan ikke angres.", |
||||
|
"enabled": "Aktivert", |
||||
|
"address": "Adresse", |
||||
|
"serverAllowedIps": "Server tillatte IP-er", |
||||
|
"otlDesc": "Generer kort engangslenke", |
||||
|
"permanent": "Permanent", |
||||
|
"createdOn": "Opprettet ", |
||||
|
"lastSeen": "Sist sett ", |
||||
|
"totalDownload": "Totalt nedlastet: ", |
||||
|
"totalUpload": "Totalt opplastet: ", |
||||
|
"newClient": "Ny klient", |
||||
|
"disableClient": "Deaktiver klient", |
||||
|
"enableClient": "Aktiver klient", |
||||
|
"noPrivKey": "Denne klienten har ingen kjent privat nøkkel. Kan ikke opprette konfigurasjon.", |
||||
|
"showQR": "Vis QR-kode", |
||||
|
"downloadConfig": "Last ned konfigurasjon", |
||||
|
"allowedIpsDesc": "Hvilke IP-er som rutes gjennom VPN (overstyrer global konfig)", |
||||
|
"serverAllowedIpsDesc": "Hvilke IP-er serveren ruter til klienten", |
||||
|
"mtuDesc": "Setter maksimal overføringsenhet (pakkestørrelse) for VPN-tunnelen", |
||||
|
"persistentKeepaliveDesc": "Setter intervallet (i sekunder) for keepalive-pakker. 0 deaktiverer det", |
||||
|
"hooks": "Hooks", |
||||
|
"hooksDescription": "Hooks fungerer bare med wg-quick", |
||||
|
"hooksLeaveEmpty": "Kun for wg-quick. Ellers la det være tomt", |
||||
|
"dnsDesc": "DNS-server klienter vil bruke (overstyrer global konfig)", |
||||
|
"notConnected": "Klient ikke tilkoblet", |
||||
|
"endpoint": "Endepunkt", |
||||
|
"endpointDesc": "IP-en til klienten som WireGuard-tilkoblingen etableres fra", |
||||
|
"search": "Søk etter klienter...", |
||||
|
"config": "Konfigurasjon", |
||||
|
"viewConfig": "Vis konfigurasjon" |
||||
|
}, |
||||
|
"dialog": { |
||||
|
"change": "Endre", |
||||
|
"cancel": "Avbryt", |
||||
|
"create": "Opprett" |
||||
|
}, |
||||
|
"toast": { |
||||
|
"success": "Vellykket", |
||||
|
"saved": "Lagret", |
||||
|
"error": "Feil" |
||||
|
}, |
||||
|
"form": { |
||||
|
"actions": "Handlinger", |
||||
|
"save": "Lagre", |
||||
|
"revert": "Tilbakestill", |
||||
|
"sectionGeneral": "Generelt", |
||||
|
"sectionAdvanced": "Avansert", |
||||
|
"noItems": "Ingen elementer", |
||||
|
"nullNoItems": "Ingen elementer. Bruker global konfig", |
||||
|
"add": "Legg til" |
||||
|
}, |
||||
|
"admin": { |
||||
|
"general": { |
||||
|
"sessionTimeout": "Øktutløp", |
||||
|
"sessionTimeoutDesc": "Øktvarighet for Husk meg (sekunder)", |
||||
|
"metrics": "Målinger", |
||||
|
"metricsPassword": "Passord", |
||||
|
"metricsPasswordDesc": "Bearer-passord for metrics-endepunktet (passord eller argon2-hash)", |
||||
|
"json": "JSON", |
||||
|
"jsonDesc": "Rute for metrics i JSON-format", |
||||
|
"prometheus": "Prometheus", |
||||
|
"prometheusDesc": "Rute for Prometheus-målinger" |
||||
|
}, |
||||
|
"config": { |
||||
|
"connection": "Tilkobling", |
||||
|
"hostDesc": "Offentlig vertsnavn klienter vil koble til (ugyldiggjør konfig)", |
||||
|
"portDesc": "Offentlig UDP-port klienter vil koble til (ugyldiggjør konfig, du vil sannsynligvis også endre Grensesnitt-port)", |
||||
|
"allowedIpsDesc": "Tillatte IP-er klienter vil bruke (global konfig)", |
||||
|
"dnsDesc": "DNS-server klienter vil bruke (global konfig)", |
||||
|
"mtuDesc": "MTU klienter vil bruke (kun for nye klienter)", |
||||
|
"persistentKeepaliveDesc": "Intervall i sekunder for å sende keepalives til serveren. 0 = deaktivert (kun for nye klienter)", |
||||
|
"suggest": "Foreslå", |
||||
|
"suggestDesc": "Velg en IP-adresse eller et vertsnavn for Vert-feltet" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidrSuccess": "CIDR endret", |
||||
|
"device": "Enhet", |
||||
|
"deviceDesc": "Ethernet-enhet som WireGuard-trafikken skal videresendes gjennom", |
||||
|
"mtuDesc": "MTU WireGuard vil bruke", |
||||
|
"portDesc": "UDP-port WireGuard vil lytte på (du vil sannsynligvis også endre Konfig-port)", |
||||
|
"changeCidr": "Endre CIDR", |
||||
|
"restart": "Start grensesnitt på nytt", |
||||
|
"restartDesc": "Start WireGuard-grensesnittet på nytt", |
||||
|
"restartWarn": "Er du sikker på at du vil starte grensesnittet på nytt? Dette vil koble fra alle klienter.", |
||||
|
"restartSuccess": "Grensesnitt startet på nytt" |
||||
|
}, |
||||
|
"introText": "Velkommen til adminpanelet.\n\nHer kan du administrere de generelle innstillingene, konfigurasjonen, grensesnittinnstillingene og hooks.\n\nStart med å velge en av seksjonene i sidepanelet." |
||||
|
}, |
||||
|
"zod": { |
||||
|
"generic": { |
||||
|
"required": "{0} er påkrevd", |
||||
|
"validNumber": "{0} må være et gyldig tall", |
||||
|
"validString": "{0} må være en gyldig streng", |
||||
|
"validBoolean": "{0} må være en gyldig boolsk verdi", |
||||
|
"validArray": "{0} må være en gyldig liste", |
||||
|
"stringMin": "{0} må være minst {1} tegn", |
||||
|
"numberMin": "{0} må være minst {1}" |
||||
|
}, |
||||
|
"client": { |
||||
|
"id": "Klient-ID", |
||||
|
"name": "Navn", |
||||
|
"expiresAt": "Utløper", |
||||
|
"address4": "IPv4-adresse", |
||||
|
"address6": "IPv6-adresse", |
||||
|
"serverAllowedIps": "Server tillatte IP-er" |
||||
|
}, |
||||
|
"user": { |
||||
|
"username": "Brukernavn", |
||||
|
"password": "Passord", |
||||
|
"remember": "Husk", |
||||
|
"name": "Navn", |
||||
|
"email": "E-post", |
||||
|
"emailInvalid": "E-post må være en gyldig e-postadresse", |
||||
|
"passwordMatch": "Passord må være like", |
||||
|
"totpEnable": "TOTP aktivert", |
||||
|
"totpEnableTrue": "TOTP aktivert må være sant", |
||||
|
"totpCode": "TOTP-kode" |
||||
|
}, |
||||
|
"userConfig": { |
||||
|
"host": "Vert" |
||||
|
}, |
||||
|
"general": { |
||||
|
"sessionTimeout": "Øktutløp", |
||||
|
"metricsEnabled": "Målinger", |
||||
|
"metricsPassword": "Målingspassord" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidr": "CIDR", |
||||
|
"device": "Enhet", |
||||
|
"cidrValid": "CIDR må være gyldig" |
||||
|
}, |
||||
|
"otl": "Engangslenke", |
||||
|
"stringMalformed": "Strengen er ugyldig", |
||||
|
"body": "Innholdet må være et gyldig objekt", |
||||
|
"hook": "Hook", |
||||
|
"enabled": "Aktivert", |
||||
|
"mtu": "MTU", |
||||
|
"port": "Port", |
||||
|
"persistentKeepalive": "Vedvarende keepalive", |
||||
|
"address": "IP-adresse", |
||||
|
"dns": "DNS", |
||||
|
"allowedIps": "Tillatte IP-er", |
||||
|
"file": "Fil" |
||||
|
}, |
||||
|
"hooks": { |
||||
|
"preUp": "PreUp", |
||||
|
"postUp": "PostUp", |
||||
|
"preDown": "PreDown", |
||||
|
"postDown": "PostDown" |
||||
|
}, |
||||
|
"copy": { |
||||
|
"notSupported": "Kopiering støttes ikke", |
||||
|
"copied": "Kopiert!", |
||||
|
"failed": "Kopiering mislyktes", |
||||
|
"copy": "Kopier" |
||||
|
}, |
||||
|
"awg": { |
||||
|
"jCLabel": "Antall junk-pakker (Jc)", |
||||
|
"jCDescription": "Antall junk-pakker som skal sendes (1-128, anbefalt: 4-12)", |
||||
|
"jMinLabel": "Min. størrelse på junk-pakker (Jmin)", |
||||
|
"jMinDescription": "Minimum størrelse på junk-pakker (0-1279*, anbefalt: 8, må være < Jmax)", |
||||
|
"jMaxLabel": "Maks. størrelse på junk-pakker (Jmax)", |
||||
|
"jMaxDescription": "Maksimal størrelse på junk-pakker (1-1280*, anbefalt: 80, må være > Jmin)", |
||||
|
"s1Label": "Init-pakke junk-størrelse (S1)", |
||||
|
"s1Description": "Init-pakke junk-størrelse (0-1132[1280* - 148 = 1132], anbefalt: 15-150, S1+56 ≠ S2)", |
||||
|
"s2Label": "Svarpakke junk-størrelse (S2)", |
||||
|
"s2Description": "Svarpakke junk-størrelse (0-1188[1280* - 92 = 1188], anbefalt: 15-150)", |
||||
|
"s3Label": "Cookie-svarpakke junk-størrelse (S3)", |
||||
|
"s3Description": "Cookie-svarpakke junk-størrelse", |
||||
|
"s4Label": "Transportpakke junk-størrelse (S4)", |
||||
|
"s4Description": "Transportpakke junk-størrelse", |
||||
|
"h1Label": "Init magisk header (H1)", |
||||
|
"h1Description": "Init-pakke header-verdi (5-2147483647, må være unik fra H2-H4)", |
||||
|
"h2Label": "Svar magisk header (H2)", |
||||
|
"h2Description": "Svarpakke header-verdi (5-2147483647, må være unik fra H1, H3, H4)", |
||||
|
"h3Label": "Cookie-svar magisk header (H3)", |
||||
|
"h3Description": "Cookie-svarpakke header-verdi (5-2147483647, må være unik fra H1, H2, H4)", |
||||
|
"h4Label": "Transport magisk header (H4)", |
||||
|
"h4Description": "Transportpakke header-verdi (5-2147483647, må være unik fra H1-H3)", |
||||
|
"i1Label": "Spesiell junk-pakke 1 (I1)", |
||||
|
"i1Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
||||
|
"i2Label": "Spesiell junk-pakke 2 (I2)", |
||||
|
"i2Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
||||
|
"i3Label": "Spesiell junk-pakke 3 (I3)", |
||||
|
"i3Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
||||
|
"i4Label": "Spesiell junk-pakke 4 (I4)", |
||||
|
"i4Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
||||
|
"i5Label": "Spesiell junk-pakke 5 (I5)", |
||||
|
"i5Description": "Protokolllignende pakke i heksformat: <b 0x...>", |
||||
|
"mtuNote": "Verdier avhenger av MTU", |
||||
|
"obfuscationParameters": "AmneziaWG obfuskasjonsparametere" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,297 @@ |
|||||
|
{ |
||||
|
"pages": { |
||||
|
"me": "Account", |
||||
|
"clients": "Cliënten", |
||||
|
"admin": { |
||||
|
"panel": "Admin-paneel", |
||||
|
"general": "Algemeen", |
||||
|
"config": "Configuratie", |
||||
|
"interface": "Interface", |
||||
|
"hooks": "Hooks" |
||||
|
} |
||||
|
}, |
||||
|
"user": { |
||||
|
"email": "E-mail" |
||||
|
}, |
||||
|
"me": { |
||||
|
"currentPassword": "Huidig wachtwoord", |
||||
|
"enable2fa": "Twee-factor-authenticatie inschakelen", |
||||
|
"enable2faDesc": "Scan de QR-code met uw authenticator-app of voer de sleutel handmatig in.", |
||||
|
"2faKey": "TOTP-sleutel", |
||||
|
"2faCodeDesc": "Voer de code in van uw authenticator-app.", |
||||
|
"disable2fa": "Twee-factor-authenticatie uitschakelen", |
||||
|
"disable2faDesc": "Voer uw wachtwoord in om de twee-factor-authenticatie uit te schakelen." |
||||
|
}, |
||||
|
"general": { |
||||
|
"name": "Naam", |
||||
|
"username": "Gebruikersnaam", |
||||
|
"password": "Wachtwoord", |
||||
|
"newPassword": "Nieuw wachtwoord", |
||||
|
"updatePassword": "Wachtwoord bijwerken", |
||||
|
"mtu": "MTU", |
||||
|
"allowedIps": "Toegestane IP's", |
||||
|
"dns": "DNS", |
||||
|
"persistentKeepalive": "Aanhoudende verbinding", |
||||
|
"logout": "Uitloggen", |
||||
|
"continue": "Doorgaan", |
||||
|
"host": "Host", |
||||
|
"port": "Poort", |
||||
|
"yes": "Ja", |
||||
|
"no": "Nee", |
||||
|
"confirmPassword": "Wachtwoord bevestigen", |
||||
|
"loading": "Laden...", |
||||
|
"2fa": "Twee-factor-authenticatie uitschakelen", |
||||
|
"2faCode": "TOTP-code" |
||||
|
}, |
||||
|
"setup": { |
||||
|
"welcome": "Welkom bij uw eerste installatie van wg-easy", |
||||
|
"welcomeDesc": "U hebt de gemakkelijkste manier gevonden om WireGuard op elke Linux-host te installeren en te beheren", |
||||
|
"existingSetup": "Heeft u een bestaande installatie?", |
||||
|
"createAdminDesc": "Voer eerst een beheerdersgebruikersnaam en een sterk veilig wachtwoord in. Deze gegevens worden gebruikt om in te loggen op uw beheerderspaneel.", |
||||
|
"setupConfigDesc": "Voer alstublieft de host- en poortinformatie in. Dit wordt gebruikt voor de clientconfiguratie bij het instellen van WireGuard op hun apparaten.", |
||||
|
"setupMigrationDesc": "Geef alstublieft het back-upbestand als u uw gegevens van uw vorige wg-easy-versie naar uw nieuwe installatie wilt overzetten.", |
||||
|
"upload": "Uploaden", |
||||
|
"migration": "Herstel de back-up:", |
||||
|
"createAccount": "Account aanmaken", |
||||
|
"successful": "Installatie succesvol", |
||||
|
"hostDesc": "Publieke hostnaam waar clients verbinding mee maken", |
||||
|
"portDesc": "Publieke UDP-poort waarop clients verbinding maken en waarop WireGuard luistert" |
||||
|
}, |
||||
|
"update": { |
||||
|
"updateAvailable": "Er is een update beschikbaar!", |
||||
|
"update": "Bijwerken" |
||||
|
}, |
||||
|
"theme": { |
||||
|
"dark": "Donker thema", |
||||
|
"light": "Licht thema", |
||||
|
"system": "Systeem-thema" |
||||
|
}, |
||||
|
"layout": { |
||||
|
"toggleCharts": "Grafieken tonen/verbergen", |
||||
|
"donate": "Donatie" |
||||
|
}, |
||||
|
"login": { |
||||
|
"signIn": "Inloggen", |
||||
|
"rememberMe": "Onthoud mij", |
||||
|
"rememberMeDesc": "Ingelogd blijven na het sluiten van de browser", |
||||
|
"insecure": "U kunt niet inloggen via een onveilige verbinding. Gebruik HTTPS.", |
||||
|
"2faRequired": "Twee-factor-authenticatie is vereist", |
||||
|
"2faWrong": "Twee-factor-authenticatiecode is fout" |
||||
|
}, |
||||
|
"client": { |
||||
|
"empty": "Er zijn nog geen cliënten.", |
||||
|
"newShort": "Nieuw", |
||||
|
"sort": "Sortering", |
||||
|
"create": "Cliënt aanmaken", |
||||
|
"created": "Cliënt aangemaakt", |
||||
|
"new": "Nieuwe cliënt", |
||||
|
"name": "Naam", |
||||
|
"expireDate": "Verloopdatum", |
||||
|
"expireDateDesc": "Datum waarop de cliënt wordt uitgeschakeld. Laat leeg voor permanent.", |
||||
|
"delete": "Verwijderen", |
||||
|
"deleteClient": "Cliënt verwijderen", |
||||
|
"deleteDialog1": "Weet u zeker dat u wilt verwijderen", |
||||
|
"deleteDialog2": "Deze actie kan niet ongedaan worden gemaakt.", |
||||
|
"enabled": "Ingeschakeld", |
||||
|
"address": "Adres", |
||||
|
"serverAllowedIps": "Toegestane IP's van de server", |
||||
|
"otlDesc": "Korte eenmalige link genereren", |
||||
|
"permanent": "Permanent", |
||||
|
"createdOn": "Aangemaakt op ", |
||||
|
"lastSeen": "Laatst gezien op ", |
||||
|
"totalDownload": "Totaal gedownload: ", |
||||
|
"totalUpload": "Totaal geüpload: ", |
||||
|
"newClient": "Nieuwe cliënt", |
||||
|
"disableClient": "Cliënt uitschakelen", |
||||
|
"enableClient": "Cliënt inschakelen", |
||||
|
"noPrivKey": "Deze cliënt heeft geen bekende privésleutel. Kan de configuratie niet aanmaken.", |
||||
|
"showQR": "QR-code weergeven", |
||||
|
"downloadConfig": "Configuratie downloaden", |
||||
|
"allowedIpsDesc": "Welke IP's via de VPN worden geleid (overschrijft algemene instellingen)", |
||||
|
"serverAllowedIpsDesc": "Naar welke IP's de server het cliëntverkeer zal routeren", |
||||
|
"mtuDesc": "Stelt de maximale transmissie-eenheid (pakketgrootte) voor de VPN-tunnel in", |
||||
|
"persistentKeepaliveDesc": "Stelt het interval (seconden) in voor keep-alive-pakketten. 0 schakelt dit uit", |
||||
|
"hooks": "Hooks", |
||||
|
"hooksDescription": "Hooks functioneren alleen met wg-quick", |
||||
|
"hooksLeaveEmpty": "Alleen voor wg-quick. Anders leeg laten", |
||||
|
"dnsDesc": "DNS-serverclients zullen gebruiken (overschrijft algemene instellingen)", |
||||
|
"notConnected": "Cliënt niet verbonden", |
||||
|
"endpoint": "Eindpunt", |
||||
|
"endpointDesc": "IP van de cliënt vanaf welke de WireGuard-verbinding tot stand wordt gebracht", |
||||
|
"search": "Cliënten zoeken...", |
||||
|
"config": "Configuratie", |
||||
|
"viewConfig": "Configuratie weergeven", |
||||
|
"firewallIps": "Firewall-toegestane IPs", |
||||
|
"firewallIpsDesc": "Bestemmings-IP's/CIDR's waartoe deze client toegang heeft (handhaving aan de serverzijde). Laat leeg om Toegestane IP's te gebruiken. Ondersteunt optionele poort - en protocolfiltering. Zie documentatie voor syntaxis.", |
||||
|
"downloadPng": "PNG downloaden", |
||||
|
"copyPng": "PNG kopiëren" |
||||
|
}, |
||||
|
"dialog": { |
||||
|
"change": "Wijzigen", |
||||
|
"cancel": "Annuleren", |
||||
|
"create": "Aanmaken" |
||||
|
}, |
||||
|
"toast": { |
||||
|
"success": "Succes", |
||||
|
"saved": "Opgeslagen", |
||||
|
"error": "Fout", |
||||
|
"unknown": "Onbekende fout. Zie console voor meer details" |
||||
|
}, |
||||
|
"form": { |
||||
|
"actions": "Acties", |
||||
|
"save": "Opslaan", |
||||
|
"revert": "Terugzetten", |
||||
|
"sectionGeneral": "Algemeen", |
||||
|
"sectionAdvanced": "Geavanceerd", |
||||
|
"noItems": "Geen items", |
||||
|
"nullNoItems": "Geen items. Globale configuratie gebruiken", |
||||
|
"add": "Toevoegen" |
||||
|
}, |
||||
|
"admin": { |
||||
|
"general": { |
||||
|
"sessionTimeout": "Sessie verlopen", |
||||
|
"sessionTimeoutDesc": "Sessieduur voor Onthoud mij (sec.)", |
||||
|
"metrics": "Metrics", |
||||
|
"metricsPassword": "Wachtwoord", |
||||
|
"metricsPasswordDesc": "Bearer-wachtwoord voor het metrics-eindpunt (wachtwoord of argon2-hash)", |
||||
|
"json": "JSON", |
||||
|
"jsonDesc": "Route voor metrics in JSON-formaat", |
||||
|
"prometheus": "Prometheus", |
||||
|
"prometheusDesc": "Route voor Prometheus-metrics" |
||||
|
}, |
||||
|
"config": { |
||||
|
"connection": "Verbinding", |
||||
|
"hostDesc": "Publieke hostnaam waarmee cliënten verbinding maken (maakt configuratie ongedaan)", |
||||
|
"portDesc": "Publieke UDP-poort waarmee clients verbinding maken (maakt configuratie ongedaan; u dient waarschijnlijk ook de interfacepoort te wijzigen)", |
||||
|
"allowedIpsDesc": "Toegestane IP's die cliënten zullen gebruiken (algemene configuratie)", |
||||
|
"dnsDesc": "DNS-server die cliënten zullen gebruiken (algemene configuratie)", |
||||
|
"mtuDesc": "MTU die cliënten zullen gebruiken (alleen voor nieuwe cliënten)", |
||||
|
"persistentKeepaliveDesc": "Interval in seconden om keepalives naar de server te sturen. 0 = uitgeschakeld (alleen voor nieuwe cliënten)", |
||||
|
"suggest": "Voorstellen", |
||||
|
"suggestDesc": "Kies een IP-adres of hostnaam voor het veld Host" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidrSuccess": "CIDR gewijzigd", |
||||
|
"device": "Apparaat", |
||||
|
"deviceDesc": "Ethernet-apparaat waar het WireGuard-verkeer doorheen moet worden doorgestuurd", |
||||
|
"mtuDesc": "MTU die WireGuard zal toepassen", |
||||
|
"portDesc": "UDP-poort waarop WireGuard zal luisteren (u dient waarschijnlijk ook de Config-poort te wijzigen)", |
||||
|
"changeCidr": "CIDR wijzigen", |
||||
|
"restart": "Interface opnieuw starten", |
||||
|
"restartDesc": "WireGuard-interface opnieuw starten", |
||||
|
"restartWarn": "Weet u zeker dat u de interface wilt herstarten? Dit zal alle cliënten loskoppelen.", |
||||
|
"restartSuccess": "Interface opnieuw gestart", |
||||
|
"firewall": "Traffic-filtering", |
||||
|
"firewallEnabled": "Per-Client Firewall inschakelen", |
||||
|
"firewallEnabledDesc": "Beperk het cliëntverkeer tot specifieke bestemmings-IP's met behulp van iptables. Indien ingeschakeld, kan elke cliënt worden geconfigureerd met toegestane bestemmingen." |
||||
|
}, |
||||
|
"introText": "Welkom bij het Admin-paneel.\n\nHier kunt u de algemene instellingen, de configuratie, de interface-instellingen en de hooks beheren.\n\nBegin met het kiezen van een van de secties in de zijbalk." |
||||
|
}, |
||||
|
"zod": { |
||||
|
"generic": { |
||||
|
"required": "{0} is vereist", |
||||
|
"validNumber": "{0} moet een geldig getal zijn", |
||||
|
"validNumberRange": "{0} moet een geldig getal of bereik zijn", |
||||
|
"validString": "{0} moet een geldige tekenreeks zijn", |
||||
|
"validBoolean": "{0} moet een geldige boolean zijn", |
||||
|
"validArray": "{0} moet een geldige array zijn", |
||||
|
"stringMin": "{0} moet minstens {1} teken bevatten", |
||||
|
"numberMin": "{0} moet minstens {1} zijn" |
||||
|
}, |
||||
|
"client": { |
||||
|
"id": "Cliënt-ID", |
||||
|
"name": "Naam", |
||||
|
"expiresAt": "Verloopt op", |
||||
|
"address4": "IPv4-adres", |
||||
|
"address6": "IPv6-adres", |
||||
|
"serverAllowedIps": "Toegestane IP's van de server", |
||||
|
"firewallIps": "Firewall-toegestane IPs", |
||||
|
"firewallIpsInvalid": "Ongeldige IP-invoer van firewall. Zie documentatie voor ondersteunde syntaxis." |
||||
|
}, |
||||
|
"user": { |
||||
|
"username": "Gebruikersnaam", |
||||
|
"password": "Wachtwoord", |
||||
|
"remember": "Onthouden", |
||||
|
"name": "Naam", |
||||
|
"email": "E-mail", |
||||
|
"emailInvalid": "E-mail moet een geldig e-mailadres zijn", |
||||
|
"passwordMatch": "Wachtwoorden moeten overeenkomen", |
||||
|
"totpEnable": "TOTP inschakelen", |
||||
|
"totpEnableTrue": "TOTP inschakelen moet waar zijn", |
||||
|
"totpCode": "TOTP-code" |
||||
|
}, |
||||
|
"userConfig": { |
||||
|
"host": "Host" |
||||
|
}, |
||||
|
"general": { |
||||
|
"sessionTimeout": "Sessie-verlooptijd", |
||||
|
"metricsEnabled": "Metrics", |
||||
|
"metricsPassword": "Metrics-wachtwoord" |
||||
|
}, |
||||
|
"interface": { |
||||
|
"cidr": "CIDR", |
||||
|
"device": "Apparaat", |
||||
|
"cidrValid": "CIDR moet geldig zijn" |
||||
|
}, |
||||
|
"otl": "Eenmalige link", |
||||
|
"stringMalformed": "Tekenreeks is beschadigd", |
||||
|
"body": "Body moet een geldig object bevatten", |
||||
|
"hook": "Hook", |
||||
|
"enabled": "Ingeschakeld", |
||||
|
"mtu": "MTU", |
||||
|
"port": "Poort", |
||||
|
"persistentKeepalive": "Aanhoudende verbinding", |
||||
|
"address": "IP-adres", |
||||
|
"dns": "DNS", |
||||
|
"allowedIps": "Toegestane IP's", |
||||
|
"file": "Bestand" |
||||
|
}, |
||||
|
"hooks": { |
||||
|
"preUp": "Pre-Up", |
||||
|
"postUp": "Post-Up", |
||||
|
"preDown": "Pre-Down", |
||||
|
"postDown": "Post-Down" |
||||
|
}, |
||||
|
"copy": { |
||||
|
"notSupported": "Kopiëren wordt niet ondersteund", |
||||
|
"copied": "Gekopieerd!", |
||||
|
"failed": "Kopiëren is mislukt", |
||||
|
"copy": "Kopiëren" |
||||
|
}, |
||||
|
"awg": { |
||||
|
"jCLabel": "Junk packet count (Jc)", |
||||
|
"jCDescription": "Aantal te verzenden junk packets (1-128, aanbevolen: 4-12)", |
||||
|
"jMinLabel": "Junk packet min size (Jmin)", |
||||
|
"jMinDescription": "Minimale grootte van junk packets (0-1279*, aanbevolen: 8, moet zijn < Jmax)", |
||||
|
"jMaxLabel": "Junk packet max size (Jmax)", |
||||
|
"jMaxDescription": "Maximale grootte van junk packets (1-1280*, aanbevolen: 80, moet zijn > Jmin)", |
||||
|
"s1Label": "Init packet junk size (S1)", |
||||
|
"s1Description": "Grootte Init packet junk (0-1132[1280* - 148 = 1132], aanbevolen: 15-150, S1+56 ≠ S2)", |
||||
|
"s2Label": "Response packet junk size (S2)", |
||||
|
"s2Description": "Grootte Response packet junk (0-1188[1280* - 92 = 1188], aanbevolen: 15-150)", |
||||
|
"s3Label": "Cookie reply packet junk size (S3)", |
||||
|
"s3Description": "Grootte Cookie reply packet junk", |
||||
|
"s4Label": "Transport packet junk size (S4)", |
||||
|
"s4Description": "Grootte Transport packet junk", |
||||
|
"h1Label": "Init magic header (H1)", |
||||
|
"h1Description": "Waarde of bereik Init packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
||||
|
"h2Label": "Response magic header (H2)", |
||||
|
"h2Description": "Waarde of bereik Response packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
||||
|
"h3Label": "Cookie reply magic header (H3)", |
||||
|
"h3Description": "Waarde of bereik Cookie reply packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
||||
|
"h4Label": "Transport magic header (H4)", |
||||
|
"h4Description": "Waarde of bereik Transport packet header (X of X-Y, waarbij X<Y. Min. 5, max. 2147483647. Waarde of bereik mag niet overlappen met andere headers)", |
||||
|
"i1Label": "Special junk packet 1 (I1)", |
||||
|
"i1Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
||||
|
"i2Label": "Special junk packet 2 (I2)", |
||||
|
"i2Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
||||
|
"i3Label": "Special junk packet 3 (I3)", |
||||
|
"i3Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
||||
|
"i4Label": "Special junk packet 4 (I4)", |
||||
|
"i4Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
||||
|
"i5Label": "Special junk packet 5 (I5)", |
||||
|
"i5Description": "Protocol mimic packet in hex formaat: <b 0x...>", |
||||
|
"mtuNote": "Waarden zijn afhankelijk van de MTU", |
||||
|
"obfuscationParameters": "AmneziaWG Obfuscation Parameters" |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,36 @@ |
|||||
|
PRAGMA foreign_keys=OFF;--> statement-breakpoint |
||||
|
CREATE TABLE `__new_interfaces_table` ( |
||||
|
`name` text PRIMARY KEY NOT NULL, |
||||
|
`device` text NOT NULL, |
||||
|
`port` integer NOT NULL, |
||||
|
`private_key` text NOT NULL, |
||||
|
`public_key` text NOT NULL, |
||||
|
`ipv4_cidr` text NOT NULL, |
||||
|
`ipv6_cidr` text NOT NULL, |
||||
|
`mtu` integer NOT NULL, |
||||
|
`j_c` integer DEFAULT 7, |
||||
|
`j_min` integer DEFAULT 10, |
||||
|
`j_max` integer DEFAULT 1000, |
||||
|
`s1` integer DEFAULT 128, |
||||
|
`s2` integer DEFAULT 56, |
||||
|
`s3` integer, |
||||
|
`s4` integer, |
||||
|
`h1` text, |
||||
|
`h2` text, |
||||
|
`h3` text, |
||||
|
`h4` text, |
||||
|
`i1` text, |
||||
|
`i2` text, |
||||
|
`i3` text, |
||||
|
`i4` text, |
||||
|
`i5` text, |
||||
|
`enabled` integer NOT NULL, |
||||
|
`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL, |
||||
|
`updated_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL |
||||
|
); |
||||
|
--> statement-breakpoint |
||||
|
INSERT INTO `__new_interfaces_table`("name", "device", "port", "private_key", "public_key", "ipv4_cidr", "ipv6_cidr", "mtu", "j_c", "j_min", "j_max", "s1", "s2", "s3", "s4", "h1", "h2", "h3", "h4", "i1", "i2", "i3", "i4", "i5", "enabled", "created_at", "updated_at") SELECT "name", "device", "port", "private_key", "public_key", "ipv4_cidr", "ipv6_cidr", "mtu", "j_c", "j_min", "j_max", "s1", "s2", "s3", "s4", "h1", "h2", "h3", "h4", "i1", "i2", "i3", "i4", "i5", "enabled", "created_at", "updated_at" FROM `interfaces_table`;--> statement-breakpoint |
||||
|
DROP TABLE `interfaces_table`;--> statement-breakpoint |
||||
|
ALTER TABLE `__new_interfaces_table` RENAME TO `interfaces_table`;--> statement-breakpoint |
||||
|
PRAGMA foreign_keys=ON;--> statement-breakpoint |
||||
|
CREATE UNIQUE INDEX `interfaces_table_port_unique` ON `interfaces_table` (`port`); |
||||
@ -0,0 +1,2 @@ |
|||||
|
ALTER TABLE `clients_table` ADD `firewall_ips` text;--> statement-breakpoint |
||||
|
ALTER TABLE `interfaces_table` ADD `firewall_enabled` integer DEFAULT false NOT NULL; |
||||
@ -0,0 +1,972 @@ |
|||||
|
{ |
||||
|
"version": "6", |
||||
|
"dialect": "sqlite", |
||||
|
"id": "68c43b7a-772d-4c34-8278-e9fce5b53df1", |
||||
|
"prevId": "e09bc17a-dab6-45a3-a09c-57af222b08fb", |
||||
|
"tables": { |
||||
|
"clients_table": { |
||||
|
"name": "clients_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": true |
||||
|
}, |
||||
|
"user_id": { |
||||
|
"name": "user_id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"interface_id": { |
||||
|
"name": "interface_id", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"name": { |
||||
|
"name": "name", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv4_address": { |
||||
|
"name": "ipv4_address", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv6_address": { |
||||
|
"name": "ipv6_address", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_up": { |
||||
|
"name": "pre_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"post_up": { |
||||
|
"name": "post_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"pre_down": { |
||||
|
"name": "pre_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"post_down": { |
||||
|
"name": "post_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"private_key": { |
||||
|
"name": "private_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"public_key": { |
||||
|
"name": "public_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_shared_key": { |
||||
|
"name": "pre_shared_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"expires_at": { |
||||
|
"name": "expires_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"allowed_ips": { |
||||
|
"name": "allowed_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"server_allowed_ips": { |
||||
|
"name": "server_allowed_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"persistent_keepalive": { |
||||
|
"name": "persistent_keepalive", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"mtu": { |
||||
|
"name": "mtu", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_c": { |
||||
|
"name": "j_c", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_min": { |
||||
|
"name": "j_min", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_max": { |
||||
|
"name": "j_max", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i1": { |
||||
|
"name": "i1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i2": { |
||||
|
"name": "i2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i3": { |
||||
|
"name": "i3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i4": { |
||||
|
"name": "i4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i5": { |
||||
|
"name": "i5", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"dns": { |
||||
|
"name": "dns", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"server_endpoint": { |
||||
|
"name": "server_endpoint", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"enabled": { |
||||
|
"name": "enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"clients_table_ipv4_address_unique": { |
||||
|
"name": "clients_table_ipv4_address_unique", |
||||
|
"columns": [ |
||||
|
"ipv4_address" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
}, |
||||
|
"clients_table_ipv6_address_unique": { |
||||
|
"name": "clients_table_ipv6_address_unique", |
||||
|
"columns": [ |
||||
|
"ipv6_address" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": { |
||||
|
"clients_table_user_id_users_table_id_fk": { |
||||
|
"name": "clients_table_user_id_users_table_id_fk", |
||||
|
"tableFrom": "clients_table", |
||||
|
"tableTo": "users_table", |
||||
|
"columnsFrom": [ |
||||
|
"user_id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"id" |
||||
|
], |
||||
|
"onDelete": "restrict", |
||||
|
"onUpdate": "cascade" |
||||
|
}, |
||||
|
"clients_table_interface_id_interfaces_table_name_fk": { |
||||
|
"name": "clients_table_interface_id_interfaces_table_name_fk", |
||||
|
"tableFrom": "clients_table", |
||||
|
"tableTo": "interfaces_table", |
||||
|
"columnsFrom": [ |
||||
|
"interface_id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"name" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"general_table": { |
||||
|
"name": "general_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": 1 |
||||
|
}, |
||||
|
"setup_step": { |
||||
|
"name": "setup_step", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"session_password": { |
||||
|
"name": "session_password", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"session_timeout": { |
||||
|
"name": "session_timeout", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"metrics_prometheus": { |
||||
|
"name": "metrics_prometheus", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"metrics_json": { |
||||
|
"name": "metrics_json", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"metrics_password": { |
||||
|
"name": "metrics_password", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": {}, |
||||
|
"foreignKeys": {}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"hooks_table": { |
||||
|
"name": "hooks_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "text", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_up": { |
||||
|
"name": "pre_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"post_up": { |
||||
|
"name": "post_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_down": { |
||||
|
"name": "pre_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"post_down": { |
||||
|
"name": "post_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": {}, |
||||
|
"foreignKeys": { |
||||
|
"hooks_table_id_interfaces_table_name_fk": { |
||||
|
"name": "hooks_table_id_interfaces_table_name_fk", |
||||
|
"tableFrom": "hooks_table", |
||||
|
"tableTo": "interfaces_table", |
||||
|
"columnsFrom": [ |
||||
|
"id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"name" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"interfaces_table": { |
||||
|
"name": "interfaces_table", |
||||
|
"columns": { |
||||
|
"name": { |
||||
|
"name": "name", |
||||
|
"type": "text", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"device": { |
||||
|
"name": "device", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"port": { |
||||
|
"name": "port", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"private_key": { |
||||
|
"name": "private_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"public_key": { |
||||
|
"name": "public_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv4_cidr": { |
||||
|
"name": "ipv4_cidr", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv6_cidr": { |
||||
|
"name": "ipv6_cidr", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"mtu": { |
||||
|
"name": "mtu", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_c": { |
||||
|
"name": "j_c", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 7 |
||||
|
}, |
||||
|
"j_min": { |
||||
|
"name": "j_min", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 10 |
||||
|
}, |
||||
|
"j_max": { |
||||
|
"name": "j_max", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 1000 |
||||
|
}, |
||||
|
"s1": { |
||||
|
"name": "s1", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 128 |
||||
|
}, |
||||
|
"s2": { |
||||
|
"name": "s2", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 56 |
||||
|
}, |
||||
|
"s3": { |
||||
|
"name": "s3", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"s4": { |
||||
|
"name": "s4", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h1": { |
||||
|
"name": "h1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h2": { |
||||
|
"name": "h2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h3": { |
||||
|
"name": "h3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h4": { |
||||
|
"name": "h4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i1": { |
||||
|
"name": "i1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i2": { |
||||
|
"name": "i2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i3": { |
||||
|
"name": "i3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i4": { |
||||
|
"name": "i4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i5": { |
||||
|
"name": "i5", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"enabled": { |
||||
|
"name": "enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"interfaces_table_port_unique": { |
||||
|
"name": "interfaces_table_port_unique", |
||||
|
"columns": [ |
||||
|
"port" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": {}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"one_time_links_table": { |
||||
|
"name": "one_time_links_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"one_time_link": { |
||||
|
"name": "one_time_link", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"expires_at": { |
||||
|
"name": "expires_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"one_time_links_table_one_time_link_unique": { |
||||
|
"name": "one_time_links_table_one_time_link_unique", |
||||
|
"columns": [ |
||||
|
"one_time_link" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": { |
||||
|
"one_time_links_table_id_clients_table_id_fk": { |
||||
|
"name": "one_time_links_table_id_clients_table_id_fk", |
||||
|
"tableFrom": "one_time_links_table", |
||||
|
"tableTo": "clients_table", |
||||
|
"columnsFrom": [ |
||||
|
"id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"id" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"users_table": { |
||||
|
"name": "users_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": true |
||||
|
}, |
||||
|
"username": { |
||||
|
"name": "username", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"password": { |
||||
|
"name": "password", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"email": { |
||||
|
"name": "email", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"name": { |
||||
|
"name": "name", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"role": { |
||||
|
"name": "role", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"totp_key": { |
||||
|
"name": "totp_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"totp_verified": { |
||||
|
"name": "totp_verified", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"enabled": { |
||||
|
"name": "enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"users_table_username_unique": { |
||||
|
"name": "users_table_username_unique", |
||||
|
"columns": [ |
||||
|
"username" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": {}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"user_configs_table": { |
||||
|
"name": "user_configs_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "text", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_mtu": { |
||||
|
"name": "default_mtu", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_persistent_keepalive": { |
||||
|
"name": "default_persistent_keepalive", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_dns": { |
||||
|
"name": "default_dns", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_allowed_ips": { |
||||
|
"name": "default_allowed_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_j_c": { |
||||
|
"name": "default_j_c", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 7 |
||||
|
}, |
||||
|
"default_j_min": { |
||||
|
"name": "default_j_min", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 10 |
||||
|
}, |
||||
|
"default_j_max": { |
||||
|
"name": "default_j_max", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 1000 |
||||
|
}, |
||||
|
"default_i1": { |
||||
|
"name": "default_i1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i2": { |
||||
|
"name": "default_i2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i3": { |
||||
|
"name": "default_i3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i4": { |
||||
|
"name": "default_i4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i5": { |
||||
|
"name": "default_i5", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"host": { |
||||
|
"name": "host", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"port": { |
||||
|
"name": "port", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": {}, |
||||
|
"foreignKeys": { |
||||
|
"user_configs_table_id_interfaces_table_name_fk": { |
||||
|
"name": "user_configs_table_id_interfaces_table_name_fk", |
||||
|
"tableFrom": "user_configs_table", |
||||
|
"tableTo": "interfaces_table", |
||||
|
"columnsFrom": [ |
||||
|
"id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"name" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
} |
||||
|
}, |
||||
|
"views": {}, |
||||
|
"enums": {}, |
||||
|
"_meta": { |
||||
|
"schemas": {}, |
||||
|
"tables": {}, |
||||
|
"columns": {} |
||||
|
}, |
||||
|
"internal": { |
||||
|
"indexes": {} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,987 @@ |
|||||
|
{ |
||||
|
"version": "6", |
||||
|
"dialect": "sqlite", |
||||
|
"id": "0f072f91-cd10-4702-ae7b-245255d69d1e", |
||||
|
"prevId": "68c43b7a-772d-4c34-8278-e9fce5b53df1", |
||||
|
"tables": { |
||||
|
"clients_table": { |
||||
|
"name": "clients_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": true |
||||
|
}, |
||||
|
"user_id": { |
||||
|
"name": "user_id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"interface_id": { |
||||
|
"name": "interface_id", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"name": { |
||||
|
"name": "name", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv4_address": { |
||||
|
"name": "ipv4_address", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv6_address": { |
||||
|
"name": "ipv6_address", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_up": { |
||||
|
"name": "pre_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"post_up": { |
||||
|
"name": "post_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"pre_down": { |
||||
|
"name": "pre_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"post_down": { |
||||
|
"name": "post_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "''" |
||||
|
}, |
||||
|
"private_key": { |
||||
|
"name": "private_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"public_key": { |
||||
|
"name": "public_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_shared_key": { |
||||
|
"name": "pre_shared_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"expires_at": { |
||||
|
"name": "expires_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"allowed_ips": { |
||||
|
"name": "allowed_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"server_allowed_ips": { |
||||
|
"name": "server_allowed_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"firewall_ips": { |
||||
|
"name": "firewall_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"persistent_keepalive": { |
||||
|
"name": "persistent_keepalive", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"mtu": { |
||||
|
"name": "mtu", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_c": { |
||||
|
"name": "j_c", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_min": { |
||||
|
"name": "j_min", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_max": { |
||||
|
"name": "j_max", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i1": { |
||||
|
"name": "i1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i2": { |
||||
|
"name": "i2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i3": { |
||||
|
"name": "i3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i4": { |
||||
|
"name": "i4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i5": { |
||||
|
"name": "i5", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"dns": { |
||||
|
"name": "dns", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"server_endpoint": { |
||||
|
"name": "server_endpoint", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"enabled": { |
||||
|
"name": "enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"clients_table_ipv4_address_unique": { |
||||
|
"name": "clients_table_ipv4_address_unique", |
||||
|
"columns": [ |
||||
|
"ipv4_address" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
}, |
||||
|
"clients_table_ipv6_address_unique": { |
||||
|
"name": "clients_table_ipv6_address_unique", |
||||
|
"columns": [ |
||||
|
"ipv6_address" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": { |
||||
|
"clients_table_user_id_users_table_id_fk": { |
||||
|
"name": "clients_table_user_id_users_table_id_fk", |
||||
|
"tableFrom": "clients_table", |
||||
|
"tableTo": "users_table", |
||||
|
"columnsFrom": [ |
||||
|
"user_id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"id" |
||||
|
], |
||||
|
"onDelete": "restrict", |
||||
|
"onUpdate": "cascade" |
||||
|
}, |
||||
|
"clients_table_interface_id_interfaces_table_name_fk": { |
||||
|
"name": "clients_table_interface_id_interfaces_table_name_fk", |
||||
|
"tableFrom": "clients_table", |
||||
|
"tableTo": "interfaces_table", |
||||
|
"columnsFrom": [ |
||||
|
"interface_id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"name" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"general_table": { |
||||
|
"name": "general_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": 1 |
||||
|
}, |
||||
|
"setup_step": { |
||||
|
"name": "setup_step", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"session_password": { |
||||
|
"name": "session_password", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"session_timeout": { |
||||
|
"name": "session_timeout", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"metrics_prometheus": { |
||||
|
"name": "metrics_prometheus", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"metrics_json": { |
||||
|
"name": "metrics_json", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"metrics_password": { |
||||
|
"name": "metrics_password", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": {}, |
||||
|
"foreignKeys": {}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"hooks_table": { |
||||
|
"name": "hooks_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "text", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_up": { |
||||
|
"name": "pre_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"post_up": { |
||||
|
"name": "post_up", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"pre_down": { |
||||
|
"name": "pre_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"post_down": { |
||||
|
"name": "post_down", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": {}, |
||||
|
"foreignKeys": { |
||||
|
"hooks_table_id_interfaces_table_name_fk": { |
||||
|
"name": "hooks_table_id_interfaces_table_name_fk", |
||||
|
"tableFrom": "hooks_table", |
||||
|
"tableTo": "interfaces_table", |
||||
|
"columnsFrom": [ |
||||
|
"id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"name" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"interfaces_table": { |
||||
|
"name": "interfaces_table", |
||||
|
"columns": { |
||||
|
"name": { |
||||
|
"name": "name", |
||||
|
"type": "text", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"device": { |
||||
|
"name": "device", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"port": { |
||||
|
"name": "port", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"private_key": { |
||||
|
"name": "private_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"public_key": { |
||||
|
"name": "public_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv4_cidr": { |
||||
|
"name": "ipv4_cidr", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"ipv6_cidr": { |
||||
|
"name": "ipv6_cidr", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"mtu": { |
||||
|
"name": "mtu", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"j_c": { |
||||
|
"name": "j_c", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 7 |
||||
|
}, |
||||
|
"j_min": { |
||||
|
"name": "j_min", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 10 |
||||
|
}, |
||||
|
"j_max": { |
||||
|
"name": "j_max", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 1000 |
||||
|
}, |
||||
|
"s1": { |
||||
|
"name": "s1", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 128 |
||||
|
}, |
||||
|
"s2": { |
||||
|
"name": "s2", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 56 |
||||
|
}, |
||||
|
"s3": { |
||||
|
"name": "s3", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"s4": { |
||||
|
"name": "s4", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h1": { |
||||
|
"name": "h1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h2": { |
||||
|
"name": "h2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h3": { |
||||
|
"name": "h3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"h4": { |
||||
|
"name": "h4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i1": { |
||||
|
"name": "i1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i2": { |
||||
|
"name": "i2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i3": { |
||||
|
"name": "i3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i4": { |
||||
|
"name": "i4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"i5": { |
||||
|
"name": "i5", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"enabled": { |
||||
|
"name": "enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"firewall_enabled": { |
||||
|
"name": "firewall_enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"interfaces_table_port_unique": { |
||||
|
"name": "interfaces_table_port_unique", |
||||
|
"columns": [ |
||||
|
"port" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": {}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"one_time_links_table": { |
||||
|
"name": "one_time_links_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"one_time_link": { |
||||
|
"name": "one_time_link", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"expires_at": { |
||||
|
"name": "expires_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"one_time_links_table_one_time_link_unique": { |
||||
|
"name": "one_time_links_table_one_time_link_unique", |
||||
|
"columns": [ |
||||
|
"one_time_link" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": { |
||||
|
"one_time_links_table_id_clients_table_id_fk": { |
||||
|
"name": "one_time_links_table_id_clients_table_id_fk", |
||||
|
"tableFrom": "one_time_links_table", |
||||
|
"tableTo": "clients_table", |
||||
|
"columnsFrom": [ |
||||
|
"id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"id" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"users_table": { |
||||
|
"name": "users_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "integer", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": true |
||||
|
}, |
||||
|
"username": { |
||||
|
"name": "username", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"password": { |
||||
|
"name": "password", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"email": { |
||||
|
"name": "email", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"name": { |
||||
|
"name": "name", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"role": { |
||||
|
"name": "role", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"totp_key": { |
||||
|
"name": "totp_key", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"totp_verified": { |
||||
|
"name": "totp_verified", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"enabled": { |
||||
|
"name": "enabled", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": { |
||||
|
"users_table_username_unique": { |
||||
|
"name": "users_table_username_unique", |
||||
|
"columns": [ |
||||
|
"username" |
||||
|
], |
||||
|
"isUnique": true |
||||
|
} |
||||
|
}, |
||||
|
"foreignKeys": {}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
}, |
||||
|
"user_configs_table": { |
||||
|
"name": "user_configs_table", |
||||
|
"columns": { |
||||
|
"id": { |
||||
|
"name": "id", |
||||
|
"type": "text", |
||||
|
"primaryKey": true, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_mtu": { |
||||
|
"name": "default_mtu", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_persistent_keepalive": { |
||||
|
"name": "default_persistent_keepalive", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_dns": { |
||||
|
"name": "default_dns", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_allowed_ips": { |
||||
|
"name": "default_allowed_ips", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_j_c": { |
||||
|
"name": "default_j_c", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 7 |
||||
|
}, |
||||
|
"default_j_min": { |
||||
|
"name": "default_j_min", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 10 |
||||
|
}, |
||||
|
"default_j_max": { |
||||
|
"name": "default_j_max", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false, |
||||
|
"default": 1000 |
||||
|
}, |
||||
|
"default_i1": { |
||||
|
"name": "default_i1", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i2": { |
||||
|
"name": "default_i2", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i3": { |
||||
|
"name": "default_i3", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i4": { |
||||
|
"name": "default_i4", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"default_i5": { |
||||
|
"name": "default_i5", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": false, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"host": { |
||||
|
"name": "host", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"port": { |
||||
|
"name": "port", |
||||
|
"type": "integer", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false |
||||
|
}, |
||||
|
"created_at": { |
||||
|
"name": "created_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
}, |
||||
|
"updated_at": { |
||||
|
"name": "updated_at", |
||||
|
"type": "text", |
||||
|
"primaryKey": false, |
||||
|
"notNull": true, |
||||
|
"autoincrement": false, |
||||
|
"default": "(CURRENT_TIMESTAMP)" |
||||
|
} |
||||
|
}, |
||||
|
"indexes": {}, |
||||
|
"foreignKeys": { |
||||
|
"user_configs_table_id_interfaces_table_name_fk": { |
||||
|
"name": "user_configs_table_id_interfaces_table_name_fk", |
||||
|
"tableFrom": "user_configs_table", |
||||
|
"tableTo": "interfaces_table", |
||||
|
"columnsFrom": [ |
||||
|
"id" |
||||
|
], |
||||
|
"columnsTo": [ |
||||
|
"name" |
||||
|
], |
||||
|
"onDelete": "cascade", |
||||
|
"onUpdate": "cascade" |
||||
|
} |
||||
|
}, |
||||
|
"compositePrimaryKeys": {}, |
||||
|
"uniqueConstraints": {}, |
||||
|
"checkConstraints": {} |
||||
|
} |
||||
|
}, |
||||
|
"views": {}, |
||||
|
"enums": {}, |
||||
|
"_meta": { |
||||
|
"schemas": {}, |
||||
|
"tables": {}, |
||||
|
"columns": {} |
||||
|
}, |
||||
|
"internal": { |
||||
|
"indexes": {} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,363 @@ |
|||||
|
import debug from 'debug'; |
||||
|
import { isIPv6 } from 'is-ip'; |
||||
|
|
||||
|
import type { ClientType } from '#db/repositories/client/types'; |
||||
|
import type { InterfaceType } from '#db/repositories/interface/types'; |
||||
|
import type { UserConfigType } from '#db/repositories/userConfig/types'; |
||||
|
|
||||
|
const FW_DEBUG = debug('Firewall'); |
||||
|
const CHAIN_NAME = 'WG_CLIENTS'; |
||||
|
|
||||
|
// Mutex to prevent concurrent rule rebuilds
|
||||
|
let rebuildInProgress = false; |
||||
|
let rebuildQueued = false; |
||||
|
|
||||
|
// Cache iptables availability check result
|
||||
|
let iptablesAvailable: boolean | null = null; |
||||
|
|
||||
|
type ParsedEntry = { |
||||
|
ip: string; |
||||
|
port?: number; |
||||
|
proto?: 'tcp' | 'udp' | 'both'; |
||||
|
}; |
||||
|
|
||||
|
type FirewallClient = Pick< |
||||
|
ClientType, |
||||
|
| 'id' |
||||
|
| 'name' |
||||
|
| 'ipv4Address' |
||||
|
| 'ipv6Address' |
||||
|
| 'allowedIps' |
||||
|
| 'firewallIps' |
||||
|
| 'enabled' |
||||
|
>; |
||||
|
|
||||
|
/** |
||||
|
* Sanitize a client identifier for use in an iptables comment. |
||||
|
* Strips all characters except ASCII alphanumeric, space, underscore, hyphen, and dot. |
||||
|
* Combines with client ID for a safe, identifiable comment. |
||||
|
* Truncates to 256 bytes (iptables comment module limit). |
||||
|
*/ |
||||
|
function sanitizeComment(clientId: number, clientName: string): string { |
||||
|
const safe = clientName.replace(/[^a-zA-Z0-9 _.-]/g, ''); |
||||
|
const comment = `client ${clientId}: ${safe}`; |
||||
|
return comment.slice(0, 256); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Parse a firewall entry string into its components. |
||||
|
* Supports formats: |
||||
|
* - IP: "10.0.0.1" or "2001:db8::1" |
||||
|
* - CIDR: "10.0.0.0/24" or "2001:db8::/32" |
||||
|
* - IP:port: "10.0.0.1:443" or "[2001:db8::1]:443" |
||||
|
* - IP:port/proto: "10.0.0.1:443/tcp" or "10.0.0.1:53/udp" |
||||
|
* - CIDR:port: "10.0.0.0/24:443" |
||||
|
* - CIDR:port/proto: "10.0.0.0/24:443/tcp" or "10.0.0.0/24:53/udp" |
||||
|
* |
||||
|
* Note: Protocol (/tcp or /udp) requires a port. "IP/tcp" or "CIDR/tcp" without |
||||
|
* a port is invalid and will throw an error. |
||||
|
* |
||||
|
* @throws {Error} If protocol is specified without a port |
||||
|
*/ |
||||
|
function parseFirewallEntry(entry: string): ParsedEntry { |
||||
|
// Extract protocol suffix first: /tcp or /udp
|
||||
|
let proto: 'tcp' | 'udp' | 'both' | undefined; |
||||
|
let remaining = entry; |
||||
|
|
||||
|
if (entry.endsWith('/tcp')) { |
||||
|
proto = 'tcp'; |
||||
|
remaining = entry.slice(0, -4); |
||||
|
} else if (entry.endsWith('/udp')) { |
||||
|
proto = 'udp'; |
||||
|
remaining = entry.slice(0, -4); |
||||
|
} |
||||
|
|
||||
|
// Handle IPv6 with port: [2001:db8::1]:443
|
||||
|
if (remaining.startsWith('[')) { |
||||
|
const match = remaining.match(/^\[(.+)\]:(\d+)$/); |
||||
|
if (match && match[1] && match[2]) { |
||||
|
return { |
||||
|
ip: match[1], |
||||
|
port: parseInt(match[2], 10), |
||||
|
proto: proto ?? 'both', |
||||
|
}; |
||||
|
} |
||||
|
// Just bracketed IPv6 without port
|
||||
|
const ipMatch = remaining.match(/^\[(.+)\]$/); |
||||
|
if (ipMatch && ipMatch[1]) { |
||||
|
if (proto) { |
||||
|
throw new Error( |
||||
|
`Invalid firewall entry "${entry}": Protocol (/${proto}) requires a port. Use format like "[${ipMatch[1]}]:443/${proto}"` |
||||
|
); |
||||
|
} |
||||
|
return { ip: ipMatch[1] }; |
||||
|
} |
||||
|
if (proto) { |
||||
|
throw new Error( |
||||
|
`Invalid firewall entry "${entry}": Protocol (/${proto}) requires a port` |
||||
|
); |
||||
|
} |
||||
|
return { ip: remaining }; |
||||
|
} |
||||
|
|
||||
|
// Handle IPv4 with port or CIDR with port
|
||||
|
// Count colons to distinguish IPv6 from IPv4:port
|
||||
|
const colonCount = (remaining.match(/:/g) || []).length; |
||||
|
|
||||
|
if (colonCount === 1) { |
||||
|
// Could be IPv4:port or CIDR:port
|
||||
|
const lastColon = remaining.lastIndexOf(':'); |
||||
|
const possiblePort = remaining.slice(lastColon + 1); |
||||
|
if (/^\d+$/.test(possiblePort)) { |
||||
|
return { |
||||
|
ip: remaining.slice(0, lastColon), |
||||
|
port: parseInt(possiblePort, 10), |
||||
|
proto: proto ?? 'both', |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Plain IP or CIDR (IPv4 or IPv6)
|
||||
|
if (proto) { |
||||
|
throw new Error( |
||||
|
`Invalid firewall entry "${entry}": Protocol (/${proto}) requires a port. Use format like "${remaining}:443/${proto}"` |
||||
|
); |
||||
|
} |
||||
|
return { ip: remaining }; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Generate iptables rule arguments for a single firewall entry |
||||
|
*/ |
||||
|
function generateRuleArgs( |
||||
|
clientIp: string, |
||||
|
entry: ParsedEntry, |
||||
|
comment?: string, |
||||
|
action: 'A' | 'D' = 'A' |
||||
|
): string[] { |
||||
|
const rules: string[] = []; |
||||
|
const commentArg = comment ? ` -m comment --comment "${comment}"` : ''; |
||||
|
const baseArgs = `-${action} ${CHAIN_NAME} -s ${clientIp} -d ${entry.ip}`; |
||||
|
|
||||
|
if (entry.port) { |
||||
|
// Port-specific rules
|
||||
|
if (entry.proto === 'tcp' || entry.proto === 'both') { |
||||
|
rules.push( |
||||
|
`${baseArgs} -p tcp --dport ${entry.port}${commentArg} -j ACCEPT` |
||||
|
); |
||||
|
} |
||||
|
if (entry.proto === 'udp' || entry.proto === 'both') { |
||||
|
rules.push( |
||||
|
`${baseArgs} -p udp --dport ${entry.port}${commentArg} -j ACCEPT` |
||||
|
); |
||||
|
} |
||||
|
} else { |
||||
|
// No port - allow all traffic to destination
|
||||
|
rules.push(`${baseArgs}${commentArg} -j ACCEPT`); |
||||
|
} |
||||
|
|
||||
|
return rules; |
||||
|
} |
||||
|
|
||||
|
export const firewall = { |
||||
|
/** |
||||
|
* Initialize the custom chain if it doesn't exist |
||||
|
*/ |
||||
|
async initChain(interfaceName: string): Promise<void> { |
||||
|
FW_DEBUG( |
||||
|
`Initializing firewall chain ${CHAIN_NAME} for interface ${interfaceName}` |
||||
|
); |
||||
|
|
||||
|
// Create chain if not exists (iptables returns error if exists, so we ignore)
|
||||
|
await exec(`iptables -N ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
await exec(`ip6tables -N ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
|
||||
|
// Ensure chain is referenced from FORWARD (if not already)
|
||||
|
// Insert at position 1 to process before generic ACCEPT rules
|
||||
|
await exec( |
||||
|
`iptables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || iptables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}` |
||||
|
); |
||||
|
await exec( |
||||
|
`ip6tables -C FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || ip6tables -I FORWARD 1 -i ${interfaceName} -j ${CHAIN_NAME}` |
||||
|
); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Flush all rules in the custom chain |
||||
|
*/ |
||||
|
async flushChain(): Promise<void> { |
||||
|
FW_DEBUG(`Flushing firewall chain ${CHAIN_NAME}`); |
||||
|
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Apply firewall rules for a single client |
||||
|
*/ |
||||
|
async applyClientRules( |
||||
|
client: FirewallClient, |
||||
|
defaultAllowedIps: string[], |
||||
|
enableIpv6: boolean |
||||
|
): Promise<void> { |
||||
|
// Determine which IPs to use for firewall rules
|
||||
|
// Priority: firewallIps > allowedIps > defaultAllowedIps
|
||||
|
const effectiveIps = |
||||
|
client.firewallIps && client.firewallIps.length > 0 |
||||
|
? client.firewallIps |
||||
|
: (client.allowedIps ?? defaultAllowedIps); |
||||
|
|
||||
|
FW_DEBUG( |
||||
|
`Applying firewall rules for client ${client.name} (${client.id}): ${effectiveIps.join(', ')}` |
||||
|
); |
||||
|
|
||||
|
const comment = sanitizeComment(client.id, client.name); |
||||
|
|
||||
|
for (const ipEntry of effectiveIps) { |
||||
|
const parsed = parseFirewallEntry(ipEntry); |
||||
|
const baseIp = parsed.ip.split('/')[0] ?? parsed.ip; // Handle CIDR by checking base IP
|
||||
|
const destIsIpv6 = isIPv6(baseIp); |
||||
|
|
||||
|
if (destIsIpv6) { |
||||
|
if (enableIpv6) { |
||||
|
const rules = generateRuleArgs(client.ipv6Address, parsed, comment); |
||||
|
for (const rule of rules) { |
||||
|
await exec(`ip6tables ${rule}`); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
const rules = generateRuleArgs(client.ipv4Address, parsed, comment); |
||||
|
for (const rule of rules) { |
||||
|
await exec(`iptables ${rule}`); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Full rebuild of firewall rules from database state |
||||
|
*/ |
||||
|
async rebuildRules( |
||||
|
wgInterface: InterfaceType, |
||||
|
clients: FirewallClient[], |
||||
|
userConfig: UserConfigType, |
||||
|
enableIpv6: boolean |
||||
|
): Promise<void> { |
||||
|
if (!wgInterface.firewallEnabled) { |
||||
|
FW_DEBUG('Firewall filtering disabled, removing any existing rules'); |
||||
|
await this.removeFiltering(wgInterface.name); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// Handle concurrent rebuilds with queue
|
||||
|
if (rebuildInProgress) { |
||||
|
FW_DEBUG('Rebuild already in progress, queuing'); |
||||
|
rebuildQueued = true; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
rebuildInProgress = true; |
||||
|
|
||||
|
try { |
||||
|
FW_DEBUG('Rebuilding firewall rules...'); |
||||
|
|
||||
|
// Initialize chain structure
|
||||
|
await this.initChain(wgInterface.name); |
||||
|
|
||||
|
// Flush existing rules
|
||||
|
await this.flushChain(); |
||||
|
|
||||
|
// Apply rules for each enabled client
|
||||
|
for (const client of clients) { |
||||
|
if (!client.enabled) continue; |
||||
|
await this.applyClientRules( |
||||
|
client, |
||||
|
userConfig.defaultAllowedIps, |
||||
|
enableIpv6 |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
// Add final DROP for any traffic not explicitly allowed
|
||||
|
await exec(`iptables -A ${CHAIN_NAME} -j DROP`); |
||||
|
if (enableIpv6) { |
||||
|
await exec(`ip6tables -A ${CHAIN_NAME} -j DROP`); |
||||
|
} |
||||
|
|
||||
|
FW_DEBUG('Firewall rules rebuilt successfully'); |
||||
|
} finally { |
||||
|
rebuildInProgress = false; |
||||
|
|
||||
|
// If another rebuild was queued, run it now
|
||||
|
if (rebuildQueued) { |
||||
|
rebuildQueued = false; |
||||
|
FW_DEBUG('Processing queued rebuild'); |
||||
|
await this.rebuildRules(wgInterface, clients, userConfig, enableIpv6); |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Remove all firewall filtering (when feature is disabled) |
||||
|
*/ |
||||
|
async removeFiltering(interfaceName: string): Promise<void> { |
||||
|
FW_DEBUG(`Removing firewall filtering for interface ${interfaceName}`); |
||||
|
|
||||
|
// Remove jump rules from FORWARD chain
|
||||
|
await exec( |
||||
|
`iptables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true` |
||||
|
); |
||||
|
await exec( |
||||
|
`ip6tables -D FORWARD -i ${interfaceName} -j ${CHAIN_NAME} 2>/dev/null || true` |
||||
|
); |
||||
|
|
||||
|
// Flush and delete the chain
|
||||
|
await exec(`iptables -F ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
await exec(`ip6tables -F ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
await exec(`iptables -X ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
await exec(`ip6tables -X ${CHAIN_NAME} 2>/dev/null || true`); |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Check if iptables (and optionally ip6tables) are available on the system. |
||||
|
* @param enableIpv6 - If true, also check for ip6tables. Defaults to true. |
||||
|
*/ |
||||
|
async isAvailable(enableIpv6: boolean = true): Promise<boolean> { |
||||
|
// Return cached result if we've already checked
|
||||
|
if (iptablesAvailable !== null) { |
||||
|
return iptablesAvailable; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
// Check for iptables (always required)
|
||||
|
await exec('iptables --version'); |
||||
|
FW_DEBUG('iptables is available'); |
||||
|
|
||||
|
// Check for ip6tables (only if IPv6 is enabled)
|
||||
|
if (enableIpv6) { |
||||
|
await exec('ip6tables --version'); |
||||
|
FW_DEBUG('ip6tables is available'); |
||||
|
} else { |
||||
|
FW_DEBUG('IPv6 disabled, skipping ip6tables check'); |
||||
|
} |
||||
|
|
||||
|
iptablesAvailable = true; |
||||
|
return true; |
||||
|
} catch (error) { |
||||
|
iptablesAvailable = false; |
||||
|
FW_DEBUG('iptables/ip6tables is not available:', error); |
||||
|
return false; |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
/** |
||||
|
* Clear the availability cache to force a re-check |
||||
|
*/ |
||||
|
clearAvailabilityCache(): void { |
||||
|
iptablesAvailable = null; |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
export const firewallTestExports = { |
||||
|
parseFirewallEntry, |
||||
|
generateRuleArgs, |
||||
|
sanitizeComment, |
||||
|
}; |
||||
@ -0,0 +1,41 @@ |
|||||
|
// ! Auto Imports are not supported in this file
|
||||
|
|
||||
|
import type { ErrorCorrection } from 'qr'; |
||||
|
import { encodeQR } from 'qr'; |
||||
|
|
||||
|
export function encodeQRCode(config: string): string { |
||||
|
return tryECCModes((ecc) => { |
||||
|
return encodeQR(config, 'svg', { |
||||
|
ecc, |
||||
|
scale: 2, |
||||
|
encoding: 'byte', |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function encodeQRCodeTerm(config: string): string { |
||||
|
return tryECCModes((ecc) => { |
||||
|
return encodeQR(config, 'term', { |
||||
|
ecc, |
||||
|
encoding: 'byte', |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function tryECCModes<T>(callback: (ecc: ErrorCorrection) => T): T { |
||||
|
// defined manually, as qr's ECMode is in wrong order
|
||||
|
const ECMode = ['high', 'quartile', 'medium', 'low'] as const; |
||||
|
for (const ecc of ECMode) { |
||||
|
try { |
||||
|
return callback(ecc); |
||||
|
} catch (err) { |
||||
|
if (!(err instanceof Error && err.message === 'Capacity overflow')) { |
||||
|
throw err; |
||||
|
} |
||||
|
// retry with lower ecc
|
||||
|
} |
||||
|
} |
||||
|
throw new Error( |
||||
|
'Failed to generate QR code: Capacity overflow at all ECC levels' |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,294 @@ |
|||||
|
import { describe, expect, test } from 'vitest'; |
||||
|
import { firewallTestExports } from '../../server/utils/firewall'; |
||||
|
import { typesTestExports } from '../../server/utils/types'; |
||||
|
|
||||
|
describe('firewall', () => { |
||||
|
describe('isValidFirewallEntry', () => { |
||||
|
test('invalid ips', () => { |
||||
|
expect(() => typesTestExports.FirewallIpEntrySchema.parse('')).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('255.255.255.256') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.256') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1.1') |
||||
|
).toThrow(); |
||||
|
|
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('[]:443/udp') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('[]:443') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('[::1]/32') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('[1.1.1.1]/32') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('[::g]/32') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('2001:dbx::1') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('invalid port, protocol or cidr', () => { |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:80/tcpp') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:65536') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1:0') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/33') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/32:0') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('protocol without port', () => { |
||||
|
expect(() => |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1/tcp') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('valid entries', () => { |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('1.1.1.1')).toBe( |
||||
|
'1.1.1.1' |
||||
|
); |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('::/0')).toBe('::/0'); |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('::0/0')).toBe( |
||||
|
'::0/0' |
||||
|
); |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('2001:db8::1')).toBe( |
||||
|
'2001:db8::1' |
||||
|
); |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('::1')).toBe('::1'); |
||||
|
expect( |
||||
|
typesTestExports.FirewallIpEntrySchema.parse('2001:db8::1/32') |
||||
|
).toBe('2001:db8::1/32'); |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1]')).toBe( |
||||
|
'[::1]' |
||||
|
); |
||||
|
expect(typesTestExports.FirewallIpEntrySchema.parse('[::1/32]')).toBe( |
||||
|
'[::1/32]' |
||||
|
); |
||||
|
}); |
||||
|
}); |
||||
|
describe('parseFirewallEntry', () => { |
||||
|
test('IPv4', () => { |
||||
|
expect(firewallTestExports.parseFirewallEntry('1.1.1.1')).toEqual({ |
||||
|
ip: '1.1.1.1', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv4 with Protocol', () => { |
||||
|
expect(() => |
||||
|
firewallTestExports.parseFirewallEntry('1.1.1.1/tcp') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
firewallTestExports.parseFirewallEntry('1.1.1.1/udp') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('IPv4 with CIDR', () => { |
||||
|
expect(firewallTestExports.parseFirewallEntry('1.1.1.1/32')).toEqual({ |
||||
|
ip: '1.1.1.1/32', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv4 with CIDR and Protocol', () => { |
||||
|
expect(() => |
||||
|
firewallTestExports.parseFirewallEntry('1.1.1.1/32/tcp') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('IPv4 with Port', () => { |
||||
|
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80')).toEqual({ |
||||
|
ip: '1.1.1.1', |
||||
|
port: 80, |
||||
|
proto: 'both', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv4 with Port and Protocol', () => { |
||||
|
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/tcp')).toEqual({ |
||||
|
ip: '1.1.1.1', |
||||
|
port: 80, |
||||
|
proto: 'tcp', |
||||
|
}); |
||||
|
expect(firewallTestExports.parseFirewallEntry('1.1.1.1:80/udp')).toEqual({ |
||||
|
ip: '1.1.1.1', |
||||
|
port: 80, |
||||
|
proto: 'udp', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv4 with CIDR and Port', () => { |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443') |
||||
|
).toEqual({ |
||||
|
ip: '10.10.0.0/24', |
||||
|
port: 443, |
||||
|
proto: 'both', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv4 with CIDR, Port and Protocol', () => { |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443/tcp') |
||||
|
).toEqual({ |
||||
|
ip: '10.10.0.0/24', |
||||
|
port: 443, |
||||
|
proto: 'tcp', |
||||
|
}); |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('10.10.0.0/24:443/udp') |
||||
|
).toEqual({ |
||||
|
ip: '10.10.0.0/24', |
||||
|
port: 443, |
||||
|
proto: 'udp', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv6', () => { |
||||
|
expect(firewallTestExports.parseFirewallEntry('[2001:db8::1]')).toEqual({ |
||||
|
ip: '2001:db8::1', |
||||
|
}); |
||||
|
expect(firewallTestExports.parseFirewallEntry('2001:db8::1')).toEqual({ |
||||
|
ip: '2001:db8::1', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv6 with Protocol', () => { |
||||
|
expect(() => |
||||
|
firewallTestExports.parseFirewallEntry('[2001:db8::1]/tcp') |
||||
|
).toThrow(); |
||||
|
expect(() => |
||||
|
firewallTestExports.parseFirewallEntry('2001:db8::1/udp') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('IPv6 with CIDR', () => { |
||||
|
expect(firewallTestExports.parseFirewallEntry('::0/0')).toEqual({ |
||||
|
ip: '::0/0', |
||||
|
}); |
||||
|
expect(firewallTestExports.parseFirewallEntry('[::0/0]')).toEqual({ |
||||
|
ip: '::0/0', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv6 with CIDR and Protocol', () => { |
||||
|
expect(() => |
||||
|
firewallTestExports.parseFirewallEntry('::0/0/tcp') |
||||
|
).toThrow(); |
||||
|
}); |
||||
|
test('IPv6 with Port', () => { |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443') |
||||
|
).toEqual({ |
||||
|
ip: '2001:db8::1', |
||||
|
port: 443, |
||||
|
proto: 'both', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv6 with Port and Protocol', () => { |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/tcp') |
||||
|
).toEqual({ |
||||
|
ip: '2001:db8::1', |
||||
|
port: 443, |
||||
|
proto: 'tcp', |
||||
|
}); |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('[2001:db8::1]:443/udp') |
||||
|
).toEqual({ |
||||
|
ip: '2001:db8::1', |
||||
|
port: 443, |
||||
|
proto: 'udp', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv6 with CIDR and Port', () => { |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('[2001:db8::/32]:443') |
||||
|
).toEqual({ |
||||
|
ip: '2001:db8::/32', |
||||
|
port: 443, |
||||
|
proto: 'both', |
||||
|
}); |
||||
|
}); |
||||
|
test('IPv6 with CIDR, Port and Protocol', () => { |
||||
|
expect( |
||||
|
firewallTestExports.parseFirewallEntry('[2001:db8::/32]:443/tcp') |
||||
|
).toEqual({ |
||||
|
ip: '2001:db8::/32', |
||||
|
port: 443, |
||||
|
proto: 'tcp', |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
describe('sanitizeComment', () => { |
||||
|
test('basic ASCII name', () => { |
||||
|
expect(firewallTestExports.sanitizeComment(1, 'My Laptop')).toBe( |
||||
|
'client 1: My Laptop' |
||||
|
); |
||||
|
}); |
||||
|
test('strips non-ASCII and shell metacharacters', () => { |
||||
|
expect(firewallTestExports.sanitizeComment(42, 'café')).toBe( |
||||
|
'client 42: caf' |
||||
|
); |
||||
|
expect(firewallTestExports.sanitizeComment(5, 'a"; rm -rf /')).toBe( |
||||
|
'client 5: a rm -rf ' |
||||
|
); |
||||
|
expect(firewallTestExports.sanitizeComment(7, 'test$(cmd)`id`')).toBe( |
||||
|
'client 7: testcmdid' |
||||
|
); |
||||
|
}); |
||||
|
test('preserves allowed punctuation', () => { |
||||
|
expect(firewallTestExports.sanitizeComment(3, 'phone-2.lan_home')).toBe( |
||||
|
'client 3: phone-2.lan_home' |
||||
|
); |
||||
|
}); |
||||
|
test('truncates to 256 bytes', () => { |
||||
|
const longName = 'a'.repeat(300); |
||||
|
const result = firewallTestExports.sanitizeComment(1, longName); |
||||
|
expect(result.length).toBeLessThanOrEqual(256); |
||||
|
expect(result).toBe('client 1: ' + 'a'.repeat(246)); |
||||
|
}); |
||||
|
}); |
||||
|
describe('generateRuleArgs', () => { |
||||
|
test('includes comment when provided', () => { |
||||
|
const rules = firewallTestExports.generateRuleArgs( |
||||
|
'10.8.0.2', |
||||
|
{ ip: '10.0.0.1' }, |
||||
|
'client 1: test' |
||||
|
); |
||||
|
expect(rules).toEqual([ |
||||
|
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -m comment --comment "client 1: test" -j ACCEPT', |
||||
|
]); |
||||
|
}); |
||||
|
test('omits comment when not provided', () => { |
||||
|
const rulesTcp = firewallTestExports.generateRuleArgs('10.8.0.2', { |
||||
|
ip: '10.0.0.1', |
||||
|
port: 80, |
||||
|
proto: 'tcp', |
||||
|
}); |
||||
|
expect(rulesTcp).toEqual([ |
||||
|
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p tcp --dport 80 -j ACCEPT', |
||||
|
]); |
||||
|
const rulesUdp = firewallTestExports.generateRuleArgs('10.8.0.2', { |
||||
|
ip: '10.0.0.1', |
||||
|
port: 80, |
||||
|
proto: 'udp', |
||||
|
}); |
||||
|
expect(rulesUdp).toEqual([ |
||||
|
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p udp --dport 80 -j ACCEPT', |
||||
|
]); |
||||
|
}); |
||||
|
test('comment with port generates two rules for both proto', () => { |
||||
|
const rules = firewallTestExports.generateRuleArgs( |
||||
|
'10.8.0.2', |
||||
|
{ ip: '10.0.0.1', port: 443, proto: 'both' }, |
||||
|
'client 2: phone' |
||||
|
); |
||||
|
expect(rules).toEqual([ |
||||
|
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p tcp --dport 443 -m comment --comment "client 2: phone" -j ACCEPT', |
||||
|
'-A WG_CLIENTS -s 10.8.0.2 -d 10.0.0.1 -p udp --dport 443 -m comment --comment "client 2: phone" -j ACCEPT', |
||||
|
]); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,20 @@ |
|||||
|
import { expect, test, describe } from 'vitest'; |
||||
|
import { |
||||
|
hashPassword, |
||||
|
isPasswordValid, |
||||
|
isValidPasswordHash, |
||||
|
} from '../../server/utils/password'; |
||||
|
|
||||
|
describe('password', () => { |
||||
|
test('password', async () => { |
||||
|
const hash = await hashPassword('password'); |
||||
|
|
||||
|
await expect(isPasswordValid('password', hash)).resolves.toBe(true); |
||||
|
await expect(isPasswordValid('wrong', hash)).resolves.toBe(false); |
||||
|
|
||||
|
expect(isValidPasswordHash('not a hash')).toBe(false); |
||||
|
expect(isValidPasswordHash(hash)).toBe(true); |
||||
|
|
||||
|
expect(isValidPasswordHash(hash.replace('argon2', 'argon3'))).toBe(false); |
||||
|
}); |
||||
|
}); |
||||
@ -0,0 +1,18 @@ |
|||||
|
import { defineConfig } from 'vitest/config'; |
||||
|
|
||||
|
export default defineConfig({ |
||||
|
test: { |
||||
|
projects: [ |
||||
|
{ |
||||
|
test: { |
||||
|
name: 'unit', |
||||
|
include: ['test/unit/*.{test,spec}.ts'], |
||||
|
environment: 'node', |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
coverage: { |
||||
|
enabled: true, |
||||
|
}, |
||||
|
}, |
||||
|
}); |
||||
Loading…
Reference in new issue