Compare commits

...

9 Commits

Author SHA1 Message Date
theis.gaedigk 8c70c24205 Merge remote-tracking branch 'upstream/master'
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-24.04-arm platform:linux/arm64]) (push) Has been cancelled
Edge / Build Docker (map[os:ubuntu-latest platform:linux/amd64]) (push) Has been cancelled
Lint / Check Docs (push) Has been cancelled
Edge / Merge & Deploy Docker (push) Has been cancelled
Edge / Build & Deploy Docs (push) Has been cancelled
Lint / Lint (format:check) (push) Has been cancelled
Lint / Lint (lint) (push) Has been cancelled
Lint / Lint (typecheck) (push) Has been cancelled
2026-06-03 16:30:28 +02:00
Néstor c70ad1d08b Small code quality improvements (#2553)
* Small code quality improvements

- Fix misleading JSDoc comment in cache.ts
- Mitigate timing-based username enumeration in Basic auth
- Extract duplicated TOTP configuration into private method
- Replace manual peer counter with clients.length in Prometheus metrics
- Simplify isValidPasswordHash return expression

* reset session.ts

this is currently worked on in the dev-oauth branch

* reset password.ts

no need to change

* specify unit for cache function

* remove type assertion

---------

Co-authored-by: Anghios <Anghios@users.noreply.github.com>
Co-authored-by: Bernd Storath <bernd@berndstorath.de>
2026-06-03 14:43:32 +02:00
minhducsun2002 d0566a1df9 Support disabling version check (#2648)
* Support disabling version check

* Update docs

* Move the bypass logic back to update checking function

* fix linting

* fix linting (again)
2026-06-03 14:42:15 +02:00
Bernd Storath bc95a2851f chore: rework useSubmit (#2649)
rework useSubmit
2026-06-03 10:08:07 +02:00
Bernd Storath e03d743307 update packages 2026-06-01 08:54:27 +02:00
Bernd Storath 99357848e5 remove dependabot for npm
npm updates are handled manually to correctly dedupe
2026-05-26 08:31:07 +02:00
Ankit Agarwal c41ae0d4c5 Add Hindi translation (#2632) 2026-05-26 08:29:18 +02:00
ふぁ 66f8bde206 Add Japanese translation (#2642) 2026-05-26 08:28:48 +02:00
Bernd Storath b3afb9ac1b update packages 2026-05-26 08:21:10 +02:00
31 changed files with 2542 additions and 1930 deletions
-10
View File
@@ -5,13 +5,3 @@ updates:
schedule: schedule:
interval: "weekly" interval: "weekly"
rebase-strategy: "auto" rebase-strategy: "auto"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
rebase-strategy: "auto"
- package-ecosystem: "npm"
directory: "/src/"
schedule:
interval: "weekly"
rebase-strategy: "auto"
@@ -4,12 +4,13 @@ title: Optional Configuration
You can set these environment variables to configure the container. They are not required, but can be useful in some cases. You can set these environment variables to configure the container. They are not required, but can be useful in some cases.
| Env | Default | Example | Description | | Env | Default | Example | Description |
| -------------- | --------- | ----------- | ---------------------------------- | | ----------------------- | --------- | ----------- | --------------------------------------- |
| `PORT` | `51821` | `6789` | TCP port for Web UI. | | `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. | | `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `INSECURE` | `false` | `true` | If access over http is allowed | | `INSECURE` | `false` | `true` | If access over http is allowed |
| `DISABLE_IPV6` | `false` | `true` | If IPv6 support should be disabled | | `DISABLE_IPV6` | `false` | `true` | If IPv6 support should be disabled |
| `DISABLE_VERSION_CHECK` | `false` | `true` | If wg-easy should check for new updates |
/// note | IPv6 Caveats /// note | IPv6 Caveats
+1 -1
View File
@@ -13,5 +13,5 @@
"devDependencies": { "devDependencies": {
"prettier": "^3.8.3" "prettier": "^3.8.3"
}, },
"packageManager": "pnpm@11.1.2" "packageManager": "pnpm@11.5.0"
} }
@@ -14,10 +14,11 @@ const props = defineProps<{ client: LocalClient }>();
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
const _showOneTimeLink = useSubmit( const _showOneTimeLink = useSubmit(
`/api/client/${props.client.id}/generateOneTimeLink`, (data) =>
{ $fetch(`/api/client/${props.client.id}/generateOneTimeLink`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await clientsStore.refresh(); await clientsStore.refresh();
+10 -8
View File
@@ -18,10 +18,11 @@ const enabled = ref(props.client.enabled);
const clientsStore = useClientsStore(); const clientsStore = useClientsStore();
const _disableClient = useSubmit( const _disableClient = useSubmit(
`/api/client/${props.client.id}/disable`, (data) =>
{ $fetch(`/api/client/${props.client.id}/disable`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await clientsStore.refresh(); await clientsStore.refresh();
@@ -31,10 +32,11 @@ const _disableClient = useSubmit(
); );
const _enableClient = useSubmit( const _enableClient = useSubmit(
`/api/client/${props.client.id}/enable`, (data) =>
{ $fetch(`/api/client/${props.client.id}/enable`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await clientsStore.refresh(); await clientsStore.refresh();
+5 -4
View File
@@ -43,10 +43,11 @@ function createClient() {
} }
const _createClient = useSubmit( const _createClient = useSubmit(
'/api/client', (data) =>
{ $fetch('/api/client', {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: () => clientsStore.refresh(), revert: () => clientsStore.refresh(),
successMsg: t('client.created'), successMsg: t('client.created'),
+5 -4
View File
@@ -70,10 +70,11 @@ const authStore = useAuthStore();
const toggleState = ref(false); const toggleState = ref(false);
const _submit = useSubmit( const _submit = useSubmit(
'/api/session', (data) =>
{ $fetch('/api/session', {
method: 'delete', method: 'delete',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await navigateTo('/login'); await navigateTo('/login');
+11 -36
View File
@@ -1,49 +1,24 @@
import type {
NitroFetchRequest,
NitroFetchOptions,
TypedInternalResponse,
ExtractedRouteMethod,
} from 'nitropack/types';
import { FetchError } from 'ofetch'; import { FetchError } from 'ofetch';
type RevertFn< type RevertFn<T> = (success: boolean, data: T | undefined) => Promise<void>;
R extends NitroFetchRequest,
T = unknown,
O extends NitroFetchOptions<R> = NitroFetchOptions<R>,
> = (
success: boolean,
data:
| TypedInternalResponse<
R,
T,
NitroFetchOptions<R> extends O ? 'get' : ExtractedRouteMethod<R, O>
>
| undefined
) => Promise<void>;
type SubmitOpts< type SubmitOpts<T> = {
R extends NitroFetchRequest, revert: RevertFn<T>;
T = unknown,
O extends NitroFetchOptions<R> = NitroFetchOptions<R>,
> = {
revert: RevertFn<R, T, O>;
successMsg?: string; successMsg?: string;
noSuccessToast?: boolean; noSuccessToast?: boolean;
}; };
export function useSubmit< type Body = Record<string, unknown> | null | undefined;
R extends NitroFetchRequest,
O extends NitroFetchOptions<R> & { body?: never }, export function useSubmit<T>(
T = unknown, fetcher: (data: Body) => Promise<T>,
>(url: R, options: O, opts: SubmitOpts<R, T, O>) { opts: SubmitOpts<T>
) {
const toast = useToast(); const toast = useToast();
return async (data: unknown) => { return async (data: Body) => {
try { try {
const res = await $fetch(url, { const res = await fetcher(data);
...options,
body: data,
});
if (!opts.noSuccessToast) { if (!opts.noSuccessToast) {
toast.showToast({ toast.showToast({
+5 -4
View File
@@ -121,10 +121,11 @@ const { data: _data, refresh } = await useFetch(`/api/admin/userconfig`, {
const data = toRef(_data.value); const data = toRef(_data.value);
const _submit = useSubmit( const _submit = useSubmit(
`/api/admin/userconfig`, (data) =>
{ $fetch(`/api/admin/userconfig`, {
method: 'post', method: 'post',
}, body: data,
}),
{ revert } { revert }
); );
+5 -4
View File
@@ -46,10 +46,11 @@ const { data: _data, refresh } = await useFetch(`/api/admin/general`, {
const data = toRef(_data.value); const data = toRef(_data.value);
const _submit = useSubmit( const _submit = useSubmit(
`/api/admin/general`, (data) =>
{ $fetch(`/api/admin/general`, {
method: 'post', method: 'post',
}, body: data,
}),
{ revert } { revert }
); );
+5 -4
View File
@@ -40,10 +40,11 @@ const { data: _data, refresh } = await useFetch(`/api/admin/hooks`, {
const data = toRef(_data.value); const data = toRef(_data.value);
const _submit = useSubmit( const _submit = useSubmit(
`/api/admin/hooks`, (data) =>
{ $fetch(`/api/admin/hooks`, {
method: 'post', method: 'post',
}, body: data,
}),
{ revert } { revert }
); );
+15 -12
View File
@@ -176,10 +176,11 @@ const { data: _data, refresh } = await useFetch(`/api/admin/interface`, {
const data = toRef(_data.value); const data = toRef(_data.value);
const _submit = useSubmit( const _submit = useSubmit(
`/api/admin/interface`, (data) =>
{ $fetch(`/api/admin/interface`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success) => { revert: async (success) => {
await revert(); await revert();
@@ -201,10 +202,11 @@ async function revert() {
} }
const _changeCidr = useSubmit( const _changeCidr = useSubmit(
`/api/admin/interface/cidr`, (data) =>
{ $fetch(`/api/admin/interface/cidr`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert, revert,
successMsg: t('admin.interface.cidrSuccess'), successMsg: t('admin.interface.cidrSuccess'),
@@ -216,10 +218,11 @@ async function changeCidr(ipv4Cidr: string, ipv6Cidr: string) {
} }
const _restartInterface = useSubmit( const _restartInterface = useSubmit(
`/api/admin/interface/restart`, (data) =>
{ $fetch(`/api/admin/interface/restart`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert, revert,
successMsg: t('admin.interface.restartSuccess'), successMsg: t('admin.interface.restartSuccess'),
+10 -8
View File
@@ -225,10 +225,11 @@ const { data: _data, refresh } = await useFetch(`/api/client/${id}`, {
const data = toRef(_data.value); const data = toRef(_data.value);
const _submit = useSubmit( const _submit = useSubmit(
`/api/client/${id}`, (data) =>
{ $fetch(`/api/client/${id}`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success) => { revert: async (success) => {
if (success) { if (success) {
@@ -250,10 +251,11 @@ async function revert() {
} }
const _deleteClient = useSubmit( const _deleteClient = useSubmit(
`/api/client/${id}`, (data) =>
{ $fetch(`/api/client/${id}`, {
method: 'delete', method: 'delete',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
await navigateTo('/'); await navigateTo('/');
+5 -4
View File
@@ -78,10 +78,11 @@ const totpRequired = ref(false);
const totp = ref<string>(''); const totp = ref<string>('');
const _submit = useSubmit( const _submit = useSubmit(
'/api/session', (data) =>
{ $fetch('/api/session', {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success, data) => { revert: async (success, data) => {
if (success) { if (success) {
+25 -20
View File
@@ -127,10 +127,11 @@ const name = ref(authStore.userData?.name);
const email = ref(authStore.userData?.email); const email = ref(authStore.userData?.email);
const _submit = useSubmit( const _submit = useSubmit(
`/api/me`, (data) =>
{ $fetch(`/api/me`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: () => { revert: () => {
return authStore.update(); return authStore.update();
@@ -147,10 +148,11 @@ const newPassword = ref('');
const confirmPassword = ref(''); const confirmPassword = ref('');
const _updatePassword = useSubmit( const _updatePassword = useSubmit(
`/api/me/password`, (data) =>
{ $fetch(`/api/me/password`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async () => { revert: async () => {
currentPassword.value = ''; currentPassword.value = '';
@@ -171,10 +173,11 @@ function updatePassword() {
const twofa = ref<{ key: string; qrcode: string } | null>(null); const twofa = ref<{ key: string; qrcode: string } | null>(null);
const _setup2fa = useSubmit( const _setup2fa = useSubmit(
`/api/me/totp`, (data) =>
{ $fetch(`/api/me/totp`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success, data) => { revert: async (success, data) => {
if (success && data?.type === 'setup') { if (success && data?.type === 'setup') {
@@ -199,10 +202,11 @@ async function setup2fa() {
const code = ref<string>(''); const code = ref<string>('');
const _enable2fa = useSubmit( const _enable2fa = useSubmit(
`/api/me/totp`, (data) =>
{ $fetch(`/api/me/totp`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success, data) => { revert: async (success, data) => {
if (success && data?.type === 'created') { if (success && data?.type === 'created') {
@@ -224,10 +228,11 @@ async function enable2fa() {
const disable2faPassword = ref(''); const disable2faPassword = ref('');
const _disable2fa = useSubmit( const _disable2fa = useSubmit(
`/api/me/totp`, (data) =>
{ $fetch(`/api/me/totp`, {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success, data) => { revert: async (success, data) => {
if (success && data?.type === 'deleted') { if (success && data?.type === 'deleted') {
+5 -4
View File
@@ -50,10 +50,11 @@ const password = ref<string>('');
const confirmPassword = ref<string>(''); const confirmPassword = ref<string>('');
const _submit = useSubmit( const _submit = useSubmit(
'/api/setup/2', (data) =>
{ $fetch('/api/setup/2', {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success) => { revert: async (success) => {
if (success) { if (success) {
+5 -4
View File
@@ -43,10 +43,11 @@ const host = ref<null | string>(null);
const port = ref<number>(51820); const port = ref<number>(51820);
const _submit = useSubmit( const _submit = useSubmit(
'/api/setup/4', (data) =>
{ $fetch('/api/setup/4', {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success) => { revert: async (success) => {
if (success) { if (success) {
+5 -4
View File
@@ -36,10 +36,11 @@ function onChangeFile(evt: Event) {
} }
const _submit = useSubmit( const _submit = useSubmit(
'/api/setup/migrate', (data) =>
{ $fetch('/api/setup/migrate', {
method: 'post', method: 'post',
}, body: data,
}),
{ {
revert: async (success) => { revert: async (success) => {
if (success) { if (success) {
+4
View File
@@ -4,6 +4,7 @@ import uk from './locales/uk.json';
import fr from './locales/fr.json'; import fr from './locales/fr.json';
import de from './locales/de.json'; import de from './locales/de.json';
import it from './locales/it.json'; import it from './locales/it.json';
import ja from './locales/ja.json';
import ru from './locales/ru.json'; import ru from './locales/ru.json';
import zhhk from './locales/zh-HK.json'; import zhhk from './locales/zh-HK.json';
import zhcn from './locales/zh-CN.json'; import zhcn from './locales/zh-CN.json';
@@ -17,6 +18,7 @@ import id from './locales/id.json';
import nl from './locales/nl.json'; import nl from './locales/nl.json';
import nb from './locales/nb.json'; import nb from './locales/nb.json';
import bg from './locales/bg.json'; import bg from './locales/bg.json';
import hi from './locales/hi.json';
import gl from './locales/gl.json'; import gl from './locales/gl.json';
import cs from './locales/cs.json'; import cs from './locales/cs.json';
import vi from './locales/vi.json'; import vi from './locales/vi.json';
@@ -31,6 +33,7 @@ export default defineI18nConfig(() => ({
fr, fr,
de, de,
it, it,
ja,
ru, ru,
'zh-HK': zhhk, 'zh-HK': zhhk,
'zh-CN': zhcn, 'zh-CN': zhcn,
@@ -44,6 +47,7 @@ export default defineI18nConfig(() => ({
nl, nl,
nb, nb,
bg, bg,
hi,
gl, gl,
cs, cs,
vi, vi,
+297
View File
@@ -0,0 +1,297 @@
{
"pages": {
"me": "खाता",
"clients": "क्लाइंट",
"admin": {
"panel": "एडमिन पैनल",
"general": "सामान्य",
"config": "कॉन्फ़िगरेशन",
"interface": "इंटरफ़ेस",
"hooks": "हुक्स"
}
},
"user": {
"email": "ई-मेल"
},
"me": {
"currentPassword": "वर्तमान पासवर्ड",
"enable2fa": "दो-कारक प्रमाणीकरण सक्षम करें",
"enable2faDesc": "अपने प्रमाणक ऐप से QR कोड स्कैन करें या कुंजी मैन्युअल रूप से दर्ज करें।",
"2faKey": "TOTP कुंजी",
"2faCodeDesc": "अपने प्रमाणक ऐप से कोड दर्ज करें।",
"disable2fa": "दो-कारक प्रमाणीकरण अक्षम करें",
"disable2faDesc": "दो-कारक प्रमाणीकरण अक्षम करने के लिए अपना पासवर्ड दर्ज करें।"
},
"general": {
"name": "नाम",
"username": "उपयोगकर्ता नाम",
"password": "पासवर्ड",
"newPassword": "नया पासवर्ड",
"updatePassword": "पासवर्ड अपडेट करें",
"mtu": "MTU",
"allowedIps": "अनुमत IPs",
"dns": "DNS",
"persistentKeepalive": "स्थायी कीपअलाइव",
"logout": "लॉगआउट",
"continue": "जारी रखें",
"host": "होस्ट",
"port": "पोर्ट",
"yes": "हाँ",
"no": "नहीं",
"confirmPassword": "पासवर्ड की पुष्टि करें",
"loading": "लोड हो रहा है...",
"2fa": "दो-कारक प्रमाणीकरण",
"2faCode": "TOTP कोड"
},
"setup": {
"welcome": "wg-easy की प्रारंभिक सेटअप में आपका स्वागत है",
"welcomeDesc": "आपने किसी भी Linux होस्ट पर WireGuard इंस्टॉल और प्रबंधित करने का सबसे आसान तरीका खोज लिया है",
"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": "सर्वर द्वारा अनुमत IPs",
"otlDesc": "छोटा एकबारगी लिंक उत्पन्न करें",
"permanent": "स्थायी",
"createdOn": "बनाया गया ",
"lastSeen": "अंतिम बार देखा गया ",
"totalDownload": "कुल डाउनलोड: ",
"totalUpload": "कुल अपलोड: ",
"newClient": "नया क्लाइंट",
"disableClient": "क्लाइंट अक्षम करें",
"enableClient": "क्लाइंट सक्षम करें",
"noPrivKey": "इस क्लाइंट की कोई ज्ञात निजी कुंजी नहीं है। कॉन्फ़िगरेशन नहीं बना सकते।",
"showQR": "QR कोड दिखाएं",
"downloadConfig": "कॉन्फ़िगरेशन डाउनलोड करें",
"allowedIpsDesc": "कौन से IPs VPN के माध्यम से रूट होंगे (वैश्विक कॉन्फ़िगरेशन को ओवरराइड करता है)",
"serverAllowedIpsDesc": "कौन से IPs सर्वर क्लाइंट को रूट करेगा",
"mtuDesc": "VPN टनल के लिए अधिकतम ट्रांसमिशन इकाई (पैकेट आकार) सेट करता है",
"persistentKeepaliveDesc": "कीप-अलाइव पैकेट के लिए अंतराल (सेकंड में) सेट करता है। 0 इसे अक्षम करता है",
"hooks": "हुक्स",
"hooksDescription": "हुक्स केवल wg-quick के साथ काम करते हैं",
"hooksLeaveEmpty": "केवल wg-quick के लिए। अन्यथा, खाली छोड़ें",
"dnsDesc": "DNS सर्वर जिसे क्लाइंट उपयोग करेंगे (वैश्विक कॉन्फ़िगरेशन को ओवरराइड करता है)",
"notConnected": "क्लाइंट कनेक्ट नहीं है",
"endpoint": "एंडपॉइंट",
"endpointDesc": "क्लाइंट का IP पता जहाँ से WireGuard कनेक्शन स्थापित होता है",
"search": "क्लाइंट खोजें...",
"config": "कॉन्फ़िगरेशन",
"viewConfig": "कॉन्फ़िगरेशन देखें",
"firewallIps": "फ़ायरवॉल द्वारा अनुमत IPs",
"firewallIpsDesc": "गंतव्य IPs/CIDRs जिन तक यह क्लाइंट पहुँच सकता है (सर्वर-साइड नियंत्रण)। अनुमत IPs उपयोग करने के लिए खाली छोड़ें। वैकल्पिक पोर्ट और प्रोटोकॉल फ़िल्टरिंग का समर्थन करता है। सिंटैक्स के लिए दस्तावेज़ देखें।",
"downloadPng": "PNG डाउनलोड करें",
"copyPng": "PNG कॉपी करें"
},
"dialog": {
"change": "बदलें",
"cancel": "रद्द करें",
"create": "बनाएं"
},
"toast": {
"success": "सफल",
"saved": "सहेजा गया",
"error": "त्रुटि",
"unknown": "अज्ञात त्रुटि। अधिक जानकारी के लिए कंसोल देखें"
},
"form": {
"actions": "क्रियाएं",
"save": "सहेजें",
"revert": "पूर्ववत करें",
"sectionGeneral": "सामान्य",
"sectionAdvanced": "उन्नत",
"noItems": "कोई आइटम नहीं",
"nullNoItems": "कोई आइटम नहीं। वैश्विक कॉन्फ़िगरेशन उपयोग में है",
"add": "जोड़ें"
},
"admin": {
"general": {
"sessionTimeout": "सत्र समय समाप्ति",
"sessionTimeoutDesc": "रिमेम्बर मी के लिए सत्र अवधि (सेकंड में)",
"metrics": "मेट्रिक्स",
"metricsPassword": "पासवर्ड",
"metricsPasswordDesc": "मेट्रिक्स एंडपॉइंट के लिए बियरर पासवर्ड (पासवर्ड या argon2 हैश)",
"json": "JSON",
"jsonDesc": "JSON फ़ॉर्मेट में मेट्रिक्स का मार्ग",
"prometheus": "Prometheus",
"prometheusDesc": "Prometheus मेट्रिक्स का मार्ग"
},
"config": {
"connection": "कनेक्शन",
"hostDesc": "सार्वजनिक होस्टनाम जिससे क्लाइंट कनेक्ट होंगे (कॉन्फ़िगरेशन को अमान्य करता है)",
"portDesc": "सार्वजनिक UDP पोर्ट जिससे क्लाइंट कनेक्ट होंगे (कॉन्फ़िगरेशन को अमान्य करता है, शायद आप इंटरफ़ेस पोर्ट भी बदलना चाहें)",
"allowedIpsDesc": "क्लाइंट द्वारा उपयोग किए जाने वाले अनुमत IPs (वैश्विक कॉन्फ़िगरेशन)",
"dnsDesc": "क्लाइंट द्वारा उपयोग किया जाने वाला DNS सर्वर (वैश्विक कॉन्फ़िगरेशन)",
"mtuDesc": "क्लाइंट द्वारा उपयोग किया जाने वाला MTU (केवल नए क्लाइंट के लिए)",
"persistentKeepaliveDesc": "सर्वर को कीपअलाइव भेजने का अंतराल सेकंड में। 0 = अक्षम (केवल नए क्लाइंट के लिए)",
"suggest": "सुझाएं",
"suggestDesc": "होस्ट फ़ील्ड के लिए IP पता या होस्टनाम चुनें"
},
"interface": {
"cidrSuccess": "CIDR बदला गया",
"device": "डिवाइस",
"deviceDesc": "ईथरनेट डिवाइस जिसके माध्यम से WireGuard ट्रैफ़िक फ़ॉरवर्ड किया जाना चाहिए",
"mtuDesc": "MTU जिसे WireGuard उपयोग करेगा",
"portDesc": "UDP पोर्ट जिस पर WireGuard सुनेगा (शायद आप कॉन्फ़िग पोर्ट भी बदलना चाहें)",
"changeCidr": "CIDR बदलें",
"restart": "इंटरफ़ेस रीस्टार्ट करें",
"restartDesc": "WireGuard इंटरफ़ेस को रीस्टार्ट करें",
"restartWarn": "क्या आप वाकई इंटरफ़ेस रीस्टार्ट करना चाहते हैं? इससे सभी क्लाइंट डिस्कनेक्ट हो जाएंगे।",
"restartSuccess": "इंटरफ़ेस रीस्टार्ट हो गया",
"firewall": "ट्रैफ़िक फ़िल्टरिंग",
"firewallEnabled": "प्रति-क्लाइंट फ़ायरवॉल सक्षम करें",
"firewallEnabledDesc": "iptables का उपयोग करके क्लाइंट ट्रैफ़िक को विशिष्ट गंतव्य IPs तक सीमित करें। सक्षम होने पर, प्रत्येक क्लाइंट को अनुमत गंतव्यों के साथ कॉन्फ़िगर किया जा सकता है।"
},
"introText": "एडमिन पैनल में आपका स्वागत है।\n\nयहाँ आप सामान्य सेटिंग्स, कॉन्फ़िगरेशन, इंटरफ़ेस सेटिंग्स और हुक्स प्रबंधित कर सकते हैं।\n\nसाइडबार में किसी एक अनुभाग को चुनकर शुरू करें।"
},
"zod": {
"generic": {
"required": "{0} आवश्यक है",
"validNumber": "{0} एक वैध संख्या होनी चाहिए",
"validNumberRange": "{0} एक वैध संख्या या संख्या श्रेणी होनी चाहिए",
"validString": "{0} एक वैध स्ट्रिंग होनी चाहिए",
"validBoolean": "{0} एक वैध बूलियन होना चाहिए",
"validArray": "{0} एक वैध ऐरे होना चाहिए",
"stringMin": "{0} कम से कम {1} अक्षर का होना चाहिए",
"numberMin": "{0} कम से कम {1} होना चाहिए"
},
"client": {
"id": "क्लाइंट ID",
"name": "नाम",
"expiresAt": "समाप्ति तिथि",
"address4": "IPv4 पता",
"address6": "IPv6 पता",
"serverAllowedIps": "सर्वर द्वारा अनुमत IPs",
"firewallIps": "फ़ायरवॉल द्वारा अनुमत IPs",
"firewallIpsInvalid": "अमान्य फ़ायरवॉल IP प्रविष्टि। समर्थित सिंटैक्स के लिए दस्तावेज़ देखें।"
},
"user": {
"username": "उपयोगकर्ता नाम",
"password": "पासवर्ड",
"remember": "याद रखें",
"name": "नाम",
"email": "ई-मेल",
"emailInvalid": "ई-मेल एक वैध ईमेल होनी चाहिए",
"passwordMatch": "पासवर्ड मेल खाने चाहिए",
"totpEnable": "TOTP सक्षम करें",
"totpEnableTrue": "TOTP सक्षम सत्य होना चाहिए",
"totpCode": "TOTP कोड"
},
"userConfig": {
"host": "होस्ट"
},
"general": {
"sessionTimeout": "सत्र समय समाप्ति",
"metricsEnabled": "मेट्रिक्स",
"metricsPassword": "मेट्रिक्स पासवर्ड"
},
"interface": {
"cidr": "CIDR",
"device": "डिवाइस",
"cidrValid": "CIDR वैध होना चाहिए"
},
"otl": "एकबारगी लिंक",
"stringMalformed": "स्ट्रिंग विकृत है",
"body": "बॉडी एक वैध ऑब्जेक्ट होनी चाहिए",
"hook": "हुक",
"enabled": "सक्षम",
"mtu": "MTU",
"port": "पोर्ट",
"persistentKeepalive": "स्थायी कीपअलाइव",
"address": "IP पता",
"dns": "DNS",
"allowedIps": "अनुमत IPs",
"file": "फ़ाइल"
},
"hooks": {
"preUp": "PreUp",
"postUp": "PostUp",
"preDown": "PreDown",
"postDown": "PostDown"
},
"copy": {
"notSupported": "कॉपी समर्थित नहीं है",
"copied": "कॉपी हो गया!",
"failed": "कॉपी विफल रहा",
"copy": "कॉपी करें"
},
"awg": {
"jCLabel": "जंक पैकेट गणना (Jc)",
"jCDescription": "भेजे जाने वाले जंक पैकेट की संख्या (1-128, अनुशंसित: 4-12)",
"jMinLabel": "जंक पैकेट न्यूनतम आकार (Jmin)",
"jMinDescription": "जंक पैकेट का न्यूनतम आकार (0-1279*, अनुशंसित: 8, Jmax से कम होना चाहिए)",
"jMaxLabel": "जंक पैकेट अधिकतम आकार (Jmax)",
"jMaxDescription": "जंक पैकेट का अधिकतम आकार (1-1280*, अनुशंसित: 80, Jmin से अधिक होना चाहिए)",
"s1Label": "इनिट पैकेट जंक आकार (S1)",
"s1Description": "इनिट पैकेट जंक आकार (0-1132[1280* - 148 = 1132], अनुशंसित: 15-150, S1+56 ≠ S2)",
"s2Label": "रिस्पॉन्स पैकेट जंक आकार (S2)",
"s2Description": "रिस्पॉन्स पैकेट जंक आकार (0-1188[1280* - 92 = 1188], अनुशंसित: 15-150)",
"s3Label": "कुकी रिप्लाई पैकेट जंक आकार (S3)",
"s3Description": "कुकी रिप्लाई पैकेट जंक आकार",
"s4Label": "ट्रांसपोर्ट पैकेट जंक आकार (S4)",
"s4Description": "ट्रांसपोर्ट पैकेट जंक आकार",
"h1Label": "इनिट मैजिक हेडर (H1)",
"h1Description": "इनिट पैकेट हेडर मान या श्रेणी (X या X-Y, जहाँ X<Y। न्यूनतम 5, अधिकतम 2147483647। मान या श्रेणी अन्य हेडर से ओवरलैप नहीं होनी चाहिए)",
"h2Label": "रिस्पॉन्स मैजिक हेडर (H2)",
"h2Description": "रिस्पॉन्स पैकेट हेडर मान या श्रेणी (X या X-Y, जहाँ X<Y। न्यूनतम 5, अधिकतम 2147483647। मान या श्रेणी अन्य हेडर से ओवरलैप नहीं होनी चाहिए)",
"h3Label": "कुकी रिप्लाई मैजिक हेडर (H3)",
"h3Description": "कुकी रिप्लाई पैकेट हेडर मान या श्रेणी (X या X-Y, जहाँ X<Y। न्यूनतम 5, अधिकतम 2147483647। मान या श्रेणी अन्य हेडर से ओवरलैप नहीं होनी चाहिए)",
"h4Label": "ट्रांसपोर्ट मैजिक हेडर (H4)",
"h4Description": "ट्रांसपोर्ट पैकेट हेडर मान या श्रेणी (X या X-Y, जहाँ X<Y। न्यूनतम 5, अधिकतम 2147483647। मान या श्रेणी अन्य हेडर से ओवरलैप नहीं होनी चाहिए)",
"i1Label": "विशेष जंक पैकेट 1 (I1)",
"i1Description": "hex फ़ॉर्मेट में प्रोटोकॉल मिमिक पैकेट: <b 0x...>",
"i2Label": "विशेष जंक पैकेट 2 (I2)",
"i2Description": "hex फ़ॉर्मेट में प्रोटोकॉल मिमिक पैकेट: <b 0x...>",
"i3Label": "विशेष जंक पैकेट 3 (I3)",
"i3Description": "hex फ़ॉर्मेट में प्रोटोकॉल मिमिक पैकेट: <b 0x...>",
"i4Label": "विशेष जंक पैकेट 4 (I4)",
"i4Description": "hex फ़ॉर्मेट में प्रोटोकॉल मिमिक पैकेट: <b 0x...>",
"i5Label": "विशेष जंक पैकेट 5 (I5)",
"i5Description": "hex फ़ॉर्मेट में प्रोटोकॉल मिमिक पैकेट: <b 0x...>",
"mtuNote": "मान MTU पर निर्भर करते हैं",
"obfuscationParameters": "AmneziaWG ऑबफस्केशन पैरामीटर"
}
}
+297
View File
@@ -0,0 +1,297 @@
{
"pages": {
"me": "アカウント",
"clients": "クライアント",
"admin": {
"panel": "管理パネル",
"general": "一般",
"config": "構成",
"interface": "インターフェイス",
"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": "永続的 Keepalive",
"logout": "ログアウト",
"continue": "続行",
"host": "ホスト",
"port": "ポート",
"yes": "はい",
"no": "いいえ",
"confirmPassword": "パスワードの確認",
"loading": "読み込み中...",
"2fa": "二要素認証",
"2faCode": "TOTPコード"
},
"setup": {
"welcome": "wg-easy の初期セットアップへようこそ",
"welcomeDesc": "Linux ホストで WireGuard をインストールして管理する最も簡単な方法です",
"existingSetup": "既存のセットアップがありますか?",
"createAdminDesc": "まず管理者ユーザー名と強力で安全なパスワードを入力してください。この情報は管理パネルへのログインに使用されます。",
"setupConfigDesc": "ホストとポート情報を入力してください。これは各デバイスで WireGuard を設定するときのクライアント構成に使用されます。",
"setupMigrationDesc": "新しいセットアップへ以前の wg-easy バージョンからデータを移行する場合は、バックアップファイルを指定してください。",
"upload": "アップロード",
"migration": "バックアップを復元:",
"createAccount": "アカウントを作成",
"successful": "セットアップが完了しました",
"hostDesc": "クライアントが接続する公開ホスト名",
"portDesc": "クライアントが接続し、WireGuard が待ち受ける公開 UDP ポート"
},
"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": "VPN 経由でルーティングされる IP (グローバル設定を上書き)",
"serverAllowedIpsDesc": "サーバーがこのクライアントへルーティングする IP",
"mtuDesc": "VPN トンネルの最大転送単位 (パケットサイズ) を設定します",
"persistentKeepaliveDesc": "Keepalive パケットを送信する間隔 (秒) を設定します。0 で無効化します",
"hooks": "フック",
"hooksDescription": "フックは wg-quick でのみ動作します",
"hooksLeaveEmpty": "wg-quick 専用です。それ以外の場合は空のままにしてください",
"dnsDesc": "クライアントが使用する DNS サーバー (グローバル設定を上書き)",
"notConnected": "クライアントは接続されていません",
"endpoint": "エンドポイント",
"endpointDesc": "WireGuard 接続が確立されているクライアントの IP",
"search": "クライアントを検索...",
"config": "構成",
"viewConfig": "構成を表示",
"firewallIps": "ファイアウォール許可 IP",
"firewallIpsDesc": "このクライアントがアクセスできる宛先 IP/CIDR (サーバー側で強制)。空欄の場合は Allowed IPs を使用します。任意のポートとプロトコルによるフィルタリングに対応しています。構文はドキュメントを参照してください。",
"downloadPng": "PNG をダウンロード",
"copyPng": "PNG をコピー"
},
"dialog": {
"change": "変更",
"cancel": "キャンセル",
"create": "作成"
},
"toast": {
"success": "成功",
"saved": "保存しました",
"error": "エラー",
"unknown": "不明なエラーです。詳細はコンソールを確認してください"
},
"form": {
"actions": "アクション",
"save": "保存",
"revert": "元に戻す",
"sectionGeneral": "一般",
"sectionAdvanced": "詳細",
"noItems": "項目なし",
"nullNoItems": "項目なし。グローバル設定を使用しています",
"add": "追加"
},
"admin": {
"general": {
"sessionTimeout": "セッションタイムアウト",
"sessionTimeoutDesc": "ログイン状態を保持する場合のセッション期間 (秒)",
"metrics": "メトリクス",
"metricsPassword": "パスワード",
"metricsPasswordDesc": "メトリクスエンドポイント用の Bearer パスワード (パスワードまたは argon2 ハッシュ)",
"json": "JSON",
"jsonDesc": "JSON 形式のメトリクスルート",
"prometheus": "Prometheus",
"prometheusDesc": "Prometheus メトリクスのルート"
},
"config": {
"connection": "接続",
"hostDesc": "クライアントが接続する公開ホスト名 (構成が無効化されます)",
"portDesc": "クライアントが接続する公開 UDP ポート (構成が無効化されます。通常はインターフェイスのポートも変更します)",
"allowedIpsDesc": "クライアントが使用する許可 IP (グローバル設定)",
"dnsDesc": "クライアントが使用する DNS サーバー (グローバル設定)",
"mtuDesc": "クライアントが使用する MTU (新規クライアントのみ)",
"persistentKeepaliveDesc": "サーバーへ Keepalive を送信する間隔 (秒)。0 = 無効 (新規クライアントのみ)",
"suggest": "候補",
"suggestDesc": "ホスト欄に使用する IP アドレスまたはホスト名を選択してください"
},
"interface": {
"cidrSuccess": "CIDR を変更しました",
"device": "デバイス",
"deviceDesc": "WireGuard トラフィックを転送する Ethernet デバイス",
"mtuDesc": "WireGuard が使用する MTU",
"portDesc": "WireGuard が待ち受ける UDP ポート (通常は構成ポートも変更します)",
"changeCidr": "CIDR を変更",
"restart": "インターフェイスを再起動",
"restartDesc": "WireGuard インターフェイスを再起動します",
"restartWarn": "インターフェイスを再起動してもよろしいですか?すべてのクライアントが切断されます。",
"restartSuccess": "インターフェイスを再起動しました",
"firewall": "トラフィックフィルタリング",
"firewallEnabled": "クライアントごとのファイアウォールを有効化",
"firewallEnabledDesc": "iptables を使用して、クライアントのトラフィックを特定の宛先 IP に制限します。有効にすると、クライアントごとに許可する宛先を設定できます。"
},
"introText": "管理パネルへようこそ。\n\nここでは一般設定、構成、インターフェイス設定、フックを管理できます。\n\nまずサイドバーからセクションを選択してください。"
},
"zod": {
"generic": {
"required": "{0} は必須です",
"validNumber": "{0} は有効な数値である必要があります",
"validNumberRange": "{0} は有効な数値または数値範囲である必要があります",
"validString": "{0} は有効な文字列である必要があります",
"validBoolean": "{0} は有効な真偽値である必要があります",
"validArray": "{0} は有効な配列である必要があります",
"stringMin": "{0} は {1} 文字以上である必要があります",
"numberMin": "{0} は {1} 以上である必要があります"
},
"client": {
"id": "クライアント ID",
"name": "名前",
"expiresAt": "有効期限",
"address4": "IPv4 アドレス",
"address6": "IPv6 アドレス",
"serverAllowedIps": "サーバー許可 IP",
"firewallIps": "ファイアウォール許可 IP",
"firewallIpsInvalid": "ファイアウォール IP の指定が無効です。対応している構文はドキュメントを参照してください。"
},
"user": {
"username": "ユーザー名",
"password": "パスワード",
"remember": "ログイン状態を保持",
"name": "名前",
"email": "メール",
"emailInvalid": "メールは有効なメールアドレスである必要があります",
"passwordMatch": "パスワードが一致しません",
"totpEnable": "TOTP 有効化",
"totpEnableTrue": "TOTP 有効化は true である必要があります",
"totpCode": "TOTPコード"
},
"userConfig": {
"host": "ホスト"
},
"general": {
"sessionTimeout": "セッションタイムアウト",
"metricsEnabled": "メトリクス",
"metricsPassword": "メトリクスパスワード"
},
"interface": {
"cidr": "CIDR",
"device": "デバイス",
"cidrValid": "CIDR は有効である必要があります"
},
"otl": "ワンタイムリンク",
"stringMalformed": "文字列の形式が正しくありません",
"body": "本文は有効なオブジェクトである必要があります",
"hook": "フック",
"enabled": "有効",
"mtu": "MTU",
"port": "ポート",
"persistentKeepalive": "永続的 Keepalive",
"address": "IP アドレス",
"dns": "DNS",
"allowedIps": "許可 IP",
"file": "ファイル"
},
"hooks": {
"preUp": "PreUp",
"postUp": "PostUp",
"preDown": "PreDown",
"postDown": "PostDown"
},
"copy": {
"notSupported": "コピーはサポートされていません",
"copied": "コピーしました!",
"failed": "コピーに失敗しました",
"copy": "コピー"
},
"awg": {
"jCLabel": "ジャンクパケット数 (Jc)",
"jCDescription": "送信するジャンクパケット数 (1-128、推奨: 4-12)",
"jMinLabel": "ジャンクパケット最小サイズ (Jmin)",
"jMinDescription": "ジャンクパケットの最小サイズ (0-1279*、推奨: 8、Jmax 未満)",
"jMaxLabel": "ジャンクパケット最大サイズ (Jmax)",
"jMaxDescription": "ジャンクパケットの最大サイズ (1-1280*、推奨: 80、Jmin より大きい)",
"s1Label": "初期化パケットのジャンクサイズ (S1)",
"s1Description": "初期化パケットのジャンクサイズ (0-1132[1280* - 148 = 1132]、推奨: 15-150、S1+56 ≠ S2)",
"s2Label": "応答パケットのジャンクサイズ (S2)",
"s2Description": "応答パケットのジャンクサイズ (0-1188[1280* - 92 = 1188]、推奨: 15-150)",
"s3Label": "Cookie 応答パケットのジャンクサイズ (S3)",
"s3Description": "Cookie 応答パケットのジャンクサイズ",
"s4Label": "トランスポートパケットのジャンクサイズ (S4)",
"s4Description": "トランスポートパケットのジャンクサイズ",
"h1Label": "初期化マジックヘッダー (H1)",
"h1Description": "初期化パケットのヘッダー値または範囲 (X または X-Y、X<Y。最小 5、最大 2147483647。値または範囲は他のヘッダーと重複してはいけません)",
"h2Label": "応答マジックヘッダー (H2)",
"h2Description": "応答パケットのヘッダー値または範囲 (X または X-Y、X<Y。最小 5、最大 2147483647。値または範囲は他のヘッダーと重複してはいけません)",
"h3Label": "Cookie 応答マジックヘッダー (H3)",
"h3Description": "Cookie 応答パケットのヘッダー値または範囲 (X または X-Y、X<Y。最小 5、最大 2147483647。値または範囲は他のヘッダーと重複してはいけません)",
"h4Label": "トランスポートマジックヘッダー (H4)",
"h4Description": "トランスポートパケットのヘッダー値または範囲 (X または X-Y、X<Y。最小 5、最大 2147483647。値または範囲は他のヘッダーと重複してはいけません)",
"i1Label": "特殊ジャンクパケット 1 (I1)",
"i1Description": "16進数形式のプロトコル模倣パケット: <b 0x...>",
"i2Label": "特殊ジャンクパケット 2 (I2)",
"i2Description": "16進数形式のプロトコル模倣パケット: <b 0x...>",
"i3Label": "特殊ジャンクパケット 3 (I3)",
"i3Description": "16進数形式のプロトコル模倣パケット: <b 0x...>",
"i4Label": "特殊ジャンクパケット 4 (I4)",
"i4Description": "16進数形式のプロトコル模倣パケット: <b 0x...>",
"i5Label": "特殊ジャンクパケット 5 (I5)",
"i5Description": "16進数形式のプロトコル模倣パケット: <b 0x...>",
"mtuNote": "値は MTU に依存します",
"obfuscationParameters": "AmneziaWG 難読化パラメーター"
}
}
+10
View File
@@ -51,6 +51,11 @@ export default defineNuxtConfig({
language: 'it-IT', language: 'it-IT',
name: 'Italiano', name: 'Italiano',
}, },
{
code: 'ja',
language: 'ja-JP',
name: '日本語',
},
{ {
code: 'fr', code: 'fr',
language: 'fr-FR', language: 'fr-FR',
@@ -131,6 +136,11 @@ export default defineNuxtConfig({
language: 'bg-BG', language: 'bg-BG',
name: 'Български', name: 'Български',
}, },
{
code: 'hi',
language: 'hi-IN',
name: 'हिन्दी',
},
{ {
code: 'gl', code: 'gl',
language: 'gl-ES', language: 'gl-ES',
+12 -12
View File
@@ -24,31 +24,31 @@
"@eschricht/nuxt-color-mode": "^1.2.0", "@eschricht/nuxt-color-mode": "^1.2.0",
"@heroicons/vue": "^2.2.0", "@heroicons/vue": "^2.2.0",
"@libsql/client": "^0.17.3", "@libsql/client": "^0.17.3",
"@nuxtjs/i18n": "^10.3.0", "@nuxtjs/i18n": "^10.4.0",
"@nuxtjs/tailwindcss": "^6.14.0", "@nuxtjs/tailwindcss": "^6.14.0",
"@phc/format": "^1.0.0", "@phc/format": "^1.0.0",
"@pinia/nuxt": "^0.11.3", "@pinia/nuxt": "^0.11.3",
"@tailwindcss/forms": "^0.5.11", "@tailwindcss/forms": "^0.5.11",
"@vueuse/core": "^14.3.0", "@vueuse/core": "^14.3.0",
"@vueuse/nuxt": "^14.3.0", "@vueuse/nuxt": "^14.3.0",
"apexcharts": "^5.12.0", "apexcharts": "^5.13.0",
"argon2": "^0.44.0", "argon2": "^0.44.0",
"cidr-tools": "^12.0.1", "cidr-tools": "^12.0.2",
"citty": "^0.2.2", "citty": "^0.2.2",
"consola": "^3.4.2", "consola": "^3.4.2",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"drizzle-orm": "^0.45.2", "drizzle-orm": "^0.45.2",
"ip-bigint": "^9.0.4", "ip-bigint": "^9.0.5",
"is-cidr": "^7.0.0", "is-cidr": "^7.0.0",
"is-ip": "^5.0.1", "is-ip": "^5.0.1",
"js-sha256": "^0.11.1", "js-sha256": "^0.11.1",
"nuxt": "^3.21.5", "nuxt": "^3.21.6",
"obug": "^2.1.1", "obug": "^2.1.1",
"otpauth": "^9.5.1", "otpauth": "^9.5.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"qr": "^0.6.0", "qr": "^0.6.0",
"radix-vue": "^1.9.17", "radix-vue": "^1.9.17",
"semver": "^7.8.0", "semver": "^7.8.1",
"tailwindcss": "^3.4.19", "tailwindcss": "^3.4.19",
"timeago.js": "^4.0.2", "timeago.js": "^4.0.2",
"vue": "latest", "vue": "latest",
@@ -60,18 +60,18 @@
"@nuxt/test-utils": "^4.0.3", "@nuxt/test-utils": "^4.0.3",
"@types/phc__format": "^1.0.1", "@types/phc__format": "^1.0.1",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^4.1.6", "@vitest/coverage-v8": "^4.1.7",
"@vitest/ui": "^4.1.6", "@vitest/ui": "^4.1.7",
"drizzle-kit": "^0.31.10", "drizzle-kit": "^0.31.10",
"esbuild": "^0.28.0", "esbuild": "^0.28.0",
"eslint": "^9.39.4", "eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.3", "prettier": "^3.8.3",
"prettier-plugin-tailwindcss": "^0.8.0", "prettier-plugin-tailwindcss": "^0.8.0",
"tsx": "^4.22.1", "tsx": "^4.22.3",
"typescript": "^6.0.3", "typescript": "^6.0.3",
"vitest": "^4.1.6", "vitest": "^4.1.7",
"vue-tsc": "^3.2.9" "vue-tsc": "^3.3.3"
}, },
"packageManager": "pnpm@11.1.2" "packageManager": "pnpm@11.5.0"
} }
+1759 -1744
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -4,3 +4,5 @@ allowBuilds:
esbuild: false esbuild: false
unrs-resolver: false unrs-resolver: false
vue-demi: false vue-demi: false
minimumReleaseAgeStrict: true
+3 -3
View File
@@ -18,9 +18,9 @@ export default defineEventHandler(async (event) => {
statusMessage: 'Invalid username or password', statusMessage: 'Invalid username or password',
}); });
case 'TOTP_REQUIRED': case 'TOTP_REQUIRED':
return { status: 'TOTP_REQUIRED' }; return { status: 'TOTP_REQUIRED' as const };
case 'INVALID_TOTP_CODE': case 'INVALID_TOTP_CODE':
return { status: 'INVALID_TOTP_CODE' }; return { status: 'INVALID_TOTP_CODE' as const };
case 'USER_DISABLED': case 'USER_DISABLED':
throw createError({ throw createError({
statusCode: 401, statusCode: 401,
@@ -47,5 +47,5 @@ export default defineEventHandler(async (event) => {
SERVER_DEBUG(`New Session: ${data.id} for ${user.id} (${user.username})`); SERVER_DEBUG(`New Session: ${data.id} for ${user.id} (${user.username})`);
return { status: 'success' }; return { status: 'success' as const };
}); });
@@ -58,6 +58,17 @@ export class UserService {
this.#statements = createPreparedStatement(db); this.#statements = createPreparedStatement(db);
} }
#createTotp(user: { username: string; totpKey: string }) {
return new TOTP({
issuer: 'wg-easy',
label: user.username,
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: user.totpKey,
});
}
async getAll() { async getAll() {
return this.#statements.findAll.execute(); return this.#statements.findAll.execute();
} }
@@ -156,22 +167,13 @@ export class UserService {
if (!code) { if (!code) {
return { success: false, error: 'TOTP_REQUIRED' }; return { success: false, error: 'TOTP_REQUIRED' };
} else { } else {
if (!txUser.totpKey) { const totpKey = txUser.totpKey;
if (!totpKey) {
return { success: false, error: 'UNEXPECTED_ERROR' }; return { success: false, error: 'UNEXPECTED_ERROR' };
} }
const totp = new TOTP({ const totp = this.#createTotp({ username: txUser.username, totpKey });
issuer: 'wg-easy', if (totp.validate({ token: code, window: 1 }) === null) {
label: txUser.username,
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: txUser.totpKey,
});
const valid = totp.validate({ token: code, window: 1 });
if (valid === null) {
return { success: false, error: 'INVALID_TOTP_CODE' }; return { success: false, error: 'INVALID_TOTP_CODE' };
} }
} }
@@ -195,22 +197,13 @@ export class UserService {
throw new Error('User not found'); throw new Error('User not found');
} }
if (!txUser.totpKey) { const totpKey = txUser.totpKey;
if (!totpKey) {
throw new Error('TOTP key is not set'); throw new Error('TOTP key is not set');
} }
const totp = new TOTP({ const totp = this.#createTotp({ username: txUser.username, totpKey });
issuer: 'wg-easy', if (totp.validate({ token: code, window: 1 }) === null) {
label: txUser.username,
algorithm: 'SHA1',
digits: 6,
period: 30,
secret: txUser.totpKey,
});
const valid = totp.validate({ token: code, window: 1 });
if (valid === null) {
throw new Error('Invalid TOTP code'); throw new Error('Invalid TOTP code');
} }
+1 -3
View File
@@ -6,14 +6,12 @@ export default defineMetricsHandler('prometheus', async ({ event }) => {
async function getPrometheusResponse() { async function getPrometheusResponse() {
const wgInterface = await Database.interfaces.get(); const wgInterface = await Database.interfaces.get();
const clients = await WireGuard.getAllClients(); const clients = await WireGuard.getAllClients();
let wireguardPeerCount = 0;
let wireguardEnabledPeersCount = 0; let wireguardEnabledPeersCount = 0;
let wireguardConnectedPeersCount = 0; let wireguardConnectedPeersCount = 0;
const wireguardSentBytes = []; const wireguardSentBytes = [];
const wireguardReceivedBytes = []; const wireguardReceivedBytes = [];
const wireguardLatestHandshakeSeconds = []; const wireguardLatestHandshakeSeconds = [];
for (const client of clients) { for (const client of clients) {
wireguardPeerCount++;
if (client.enabled === true) { if (client.enabled === true) {
wireguardEnabledPeersCount++; wireguardEnabledPeersCount++;
} }
@@ -41,7 +39,7 @@ async function getPrometheusResponse() {
const returnText = [ const returnText = [
'# HELP wireguard_configured_peers', '# HELP wireguard_configured_peers',
'# TYPE wireguard_configured_peers gauge', '# TYPE wireguard_configured_peers gauge',
`wireguard_configured_peers{${id}} ${wireguardPeerCount}`, `wireguard_configured_peers{${id}} ${clients.length}`,
'', '',
'# HELP wireguard_enabled_peers', '# HELP wireguard_enabled_peers',
'# TYPE wireguard_enabled_peers gauge', '# TYPE wireguard_enabled_peers gauge',
+1 -1
View File
@@ -6,7 +6,7 @@ type Opts = {
}; };
/** /**
* Cache function for 1 hour * Cache the result of a function for the given expiry time in milliseconds
*/ */
export function cacheFunction<T>(fn: () => T, { expiry }: Opts): () => T { export function cacheFunction<T>(fn: () => T, { expiry }: Opts): () => T {
let cache: { value: T; expiry: number } | null = null; let cache: { value: T; expiry: number } | null = null;
+1
View File
@@ -38,6 +38,7 @@ export const WG_ENV = {
/** If IPv6 should be disabled */ /** If IPv6 should be disabled */
DISABLE_IPV6: process.env.DISABLE_IPV6 === 'true', DISABLE_IPV6: process.env.DISABLE_IPV6 === 'true',
WG_EXECUTABLE: await detectAwg(), WG_EXECUTABLE: await detectAwg(),
DISABLE_VERSION_CHECK: process.env.DISABLE_VERSION_CHECK === 'true',
}; };
export const WG_INITIAL_ENV = { export const WG_INITIAL_ENV = {
+7
View File
@@ -4,6 +4,13 @@ type GithubRelease = {
}; };
async function fetchLatestRelease() { async function fetchLatestRelease() {
if (WG_ENV.DISABLE_VERSION_CHECK) {
return {
version: RELEASE,
changelog: '',
};
}
try { try {
const response = await $fetch<GithubRelease>( const response = await $fetch<GithubRelease>(
'https://api.github.com/repos/wg-easy/wg-easy/releases/latest', 'https://api.github.com/repos/wg-easy/wg-easy/releases/latest',