Compare commits

...

32 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
theis.gaedigk 9581e6eacb 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-05-22 23:32:00 +02:00
Bernd Storath 90e2bbe0a6 patch wg-quick. fix sysctl (#2630)
no unneeded sysctl
2026-05-21 11:59:46 +02:00
theis.gaedigk 7b5ba95938 edited pngs
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-05-20 13:14:10 +02:00
theis.gaedigk da90d67cc0 changed icons
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
Edge / Merge & Deploy Docker (push) Has been cancelled
Edge / Build & Deploy Docs (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Lint / Check 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-05-20 10:37:40 +02:00
HackingAll a52da67b38 Adding new translated lines (#2624) 2026-05-19 14:24:37 +02:00
Bernd Storath e513090074 update badges 2026-05-18 12:36:21 +02:00
Bernd Storath 2dc8ba7792 Bump version to 15.3.0 2026-05-18 12:19:51 +02:00
Bernd Storath 4e8cccb4c7 replace debug with obug (#2619)
* patch unenv

* replaces debug with obug

reverts unenv patch
2026-05-18 09:40:36 +02:00
Bernd Storath e57b0977d3 update packages 2026-05-18 08:14:09 +02:00
Bernd Storath b8be53c3f7 fix build 2026-05-13 09:16:24 +02:00
Bernd Storath 0794413191 update packages 2026-05-13 09:02:56 +02:00
Bernd Storath 261b0d6b8f Bump version to 15.3.0-beta.3 2026-05-07 13:59:19 +02:00
Ming Mak f656d57d20 Translation: update Traditional Chinese (zh-HK) localization (#2603)
* Update Chinese (HK) translations for clarity and completion

* Fix spacing

* Update zh-HK.json

Taking reference from zh_TW, making awg clearer

* Remove duplicate 'search' entry in zh-HK.json

* Fix translation typos in zh-HK locale

Replaced "户" with "戶".
2026-05-07 13:51:37 +02:00
Bernd Storath 46074fea1c update packages 2026-05-05 10:57:30 +02:00
Felipe Cordova Huenupil 05c655ede9 fix(ui):Error pops up when enabling disabled client (#2594)
* fix(ui):Error pops up when enabling disabled client

Action is prevented and a clear message is displayed in Web UI

* fix formatting

* fix type issue

* fix formatting

---------

Co-authored-by: Bernd Storath <999999bst@gmail.com>
2026-05-04 09:21:18 +02:00
Bernd Storath ebcc42cc49 fix build 2026-04-27 09:07:21 +02:00
Evgeniy be8d24e492 Fix: Add trailing newline to Prometheus metrics output (#2573)
Fix prometheus metrics
2026-04-27 08:30:03 +02:00
Bernd Storath 9682dedea7 update packages 2026-04-27 08:28:17 +02:00
Bernd Storath 5eb80fe3c1 update packages 2026-04-13 14:16:05 +02:00
dependabot[bot] dd9da2a067 build(deps): bump pnpm/action-setup from 5 to 6 (#2574)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 5 to 6.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v5...v6)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-13 13:35:40 +02:00
Bernd Storath 15111ecd62 update packages 2026-04-13 13:02:53 +02:00
dependabot[bot] e9f4b4650b build(deps): bump pnpm/action-setup from 4 to 5 (#2570)
Bumps [pnpm/action-setup](https://github.com/pnpm/action-setup) from 4 to 5.
- [Release notes](https://github.com/pnpm/action-setup/releases)
- [Commits](https://github.com/pnpm/action-setup/compare/v4...v5)

---
updated-dependencies:
- dependency-name: pnpm/action-setup
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-08 16:06:15 +02:00
Bernd Storath e3e4049f8e update packages 2026-04-08 15:51:37 +02:00
51 changed files with 4775 additions and 3332 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"
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v6
name: Install pnpm name: Install pnpm
with: with:
run_install: false run_install: false
@@ -49,7 +49,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
- uses: pnpm/action-setup@v4 - uses: pnpm/action-setup@v6
name: Install pnpm name: Install pnpm
with: with:
run_install: false run_install: false
+6
View File
@@ -14,11 +14,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- CLI: Show QR code (https://github.com/wg-easy/wg-easy/pull/2518) - CLI: Show QR code (https://github.com/wg-easy/wg-easy/pull/2518)
- Copy QR code to clipboard / save as png (https://github.com/wg-easy/wg-easy/pull/2521) - Copy QR code to clipboard / save as png (https://github.com/wg-easy/wg-easy/pull/2521)
### Fixed
- Add trailing newline to Prometheus metrics output (https://github.com/wg-easy/wg-easy/pull/2573)
- Correctly use DEBUG env var (https://github.com/wg-easy/wg-easy/pull/2619)
### Changed ### Changed
- Hooks are now Textareas (https://github.com/wg-easy/wg-easy/pull/2522) - Hooks are now Textareas (https://github.com/wg-easy/wg-easy/pull/2522)
- Update to Node Krypton (24) (https://github.com/wg-easy/wg-easy/pull/2536) - Update to Node Krypton (24) (https://github.com/wg-easy/wg-easy/pull/2536)
- Mobile UI (https://github.com/wg-easy/wg-easy/pull/2569) - Mobile UI (https://github.com/wg-easy/wg-easy/pull/2569)
- Prevent enabling client when expired (https://github.com/wg-easy/wg-easy/pull/2594)
## [15.2.2] - 2026-02-06 ## [15.2.2] - 2026-02-06
+12 -7
View File
@@ -7,7 +7,7 @@ RUN npm install --global corepack@latest
RUN corepack enable pnpm RUN corepack enable pnpm
# Copy Web UI # Copy Web UI
COPY src/package.json src/pnpm-lock.yaml ./ COPY src/package.json src/pnpm-lock.yaml src/pnpm-workspace.yaml ./
RUN pnpm install RUN pnpm install
# Build UI # Build UI
@@ -21,7 +21,12 @@ RUN apk add linux-headers build-base go git && \
cd amneziawg-go && \ cd amneziawg-go && \
make && \ make && \
cd ../amneziawg-tools/src && \ cd ../amneziawg-tools/src && \
make make && \
sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' ./wg-quick/linux.bash
FROM docker.io/library/node:krypton-alpine AS build-libsql
WORKDIR /app
RUN npm install --no-save --omit=dev libsql
# Copy build result to a new image. # Copy build result to a new image.
# This saves a lot of disk space. # This saves a lot of disk space.
@@ -35,9 +40,8 @@ COPY --from=build /app/.output /app
# Copy migrations # Copy migrations
COPY --from=build /app/server/database/migrations /app/server/database/migrations COPY --from=build /app/server/database/migrations /app/server/database/migrations
# libsql (https://github.com/nitrojs/nitro/issues/3328) # libsql (https://github.com/nitrojs/nitro/issues/3328)
RUN cd /app/server && \ COPY --from=build-libsql /app/node_modules /app/server/node_modules
npm install --no-save --omit=dev libsql && \
npm cache clean --force
# cli # cli
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
RUN chmod +x /usr/local/bin/cli RUN chmod +x /usr/local/bin/cli
@@ -59,7 +63,8 @@ RUN apk add --no-cache \
kmod \ kmod \
iptables-legacy \ iptables-legacy \
wireguard-go \ wireguard-go \
wireguard-tools wireguard-tools && \
sed -i 's|\[\[ $proto == -4 \]\] && cmd sysctl -q net\.ipv4\.conf\.all\.src_valid_mark=1|[[ $proto == -4 ]] \&\& [[ $(sysctl -n net.ipv4.conf.all.src_valid_mark) != 1 ]] \&\& cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1|' /usr/bin/wg-quick
RUN mkdir -p /etc/amnezia RUN mkdir -p /etc/amnezia
RUN ln -s /etc/wireguard /etc/amnezia/amneziawg RUN ln -s /etc/wireguard /etc/amnezia/amneziawg
@@ -69,7 +74,7 @@ RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save
# Set Environment # Set Environment
ENV DEBUG=Server,WireGuard,Database,CMD ENV DEBUG=Server,WireGuard,Database,CMD,Firewall
ENV PORT=51821 ENV PORT=51821
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV INSECURE=false ENV INSECURE=false
+2 -2
View File
@@ -24,7 +24,7 @@ RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save
# Set Environment # Set Environment
ENV DEBUG=Server,WireGuard,Database,CMD ENV DEBUG=Server,WireGuard,Database,CMD,Firewall
ENV PORT=51821 ENV PORT=51821
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV INSECURE=true ENV INSECURE=true
@@ -32,7 +32,7 @@ ENV INIT_ENABLED=false
ENV DISABLE_IPV6=false ENV DISABLE_IPV6=false
# Install Dependencies # Install Dependencies
COPY src/package.json src/pnpm-lock.yaml ./ COPY src/package.json src/pnpm-lock.yaml src/pnpm-workspace.yaml ./
RUN pnpm install RUN pnpm install
# Copy Project # Copy Project
+2 -4
View File
@@ -1,16 +1,14 @@
# WireGuard Easy # WireGuard Easy
[![Build & Publish latest Image](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg?branch=production)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml) [![Build & Publish latest Image](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
[![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml) [![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml)
[![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/stargazers) [![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/stargazers)
[![License](https://img.shields.io/github/license/wg-easy/wg-easy)](LICENSE) [![License](https://img.shields.io/github/license/wg-easy/wg-easy)](LICENSE)
[![GitHub Release](https://img.shields.io/github/v/release/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/releases/latest) [![GitHub Release](https://img.shields.io/github/v/release/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/releases/latest)
[![Image Pulls](https://img.shields.io/badge/image_pulls-12M+-blue)](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy) [![Image Pulls](https://img.shields.io/badge/image_pulls-28M+-blue)](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
You have found the easiest way to install & manage WireGuard on any Linux host! You have found the easiest way to install & manage WireGuard on any Linux host!
<!-- TOOD: update screenshot -->
<p align="center"> <p align="center">
<img src="./assets/screenshot.png" width="802" alt="wg-easy Screenshot" /> <img src="./assets/screenshot.png" width="802" alt="wg-easy Screenshot" />
</p> </p>
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

@@ -0,0 +1,63 @@
{
"fill" : {
"automatic-gradient" : "display-p3:0.48853,0.13220,0.12335,1.00000"
},
"groups" : [
{
"layers" : [
{
"fill" : {
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
},
"image-name" : "wireguard-logo.png",
"name" : "wireguard-logo",
"position" : {
"scale" : 0.5,
"translation-in-points" : [
255.828125,
-225.5
]
}
},
{
"fill-specializations" : [
{
"value" : {
"automatic-gradient" : "srgb:1.00000,1.00000,1.00000,1.00000"
}
},
{
"appearance" : "dark",
"value" : {
"automatic-gradient" : "display-p3:0.48853,0.13220,0.12335,1.00000"
}
}
],
"image-name" : "ticket.png",
"name" : "ticket",
"position" : {
"scale" : 1.2,
"translation-in-points" : [
-119.91562499999998,
165.65625
]
}
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}
@@ -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
+2 -2
View File
@@ -11,7 +11,7 @@
"format:check:docs": "prettier --check docs" "format:check:docs": "prettier --check docs"
}, },
"devDependencies": { "devDependencies": {
"prettier": "^3.8.1" "prettier": "^3.8.3"
}, },
"packageManager": "pnpm@10.32.1" "packageManager": "pnpm@11.5.0"
} }
+5 -5
View File
@@ -9,16 +9,16 @@ importers:
.: .:
devDependencies: devDependencies:
prettier: prettier:
specifier: ^3.8.1 specifier: ^3.8.3
version: 3.8.1 version: 3.8.3
packages: packages:
prettier@3.8.1: prettier@3.8.3:
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
snapshots: snapshots:
prettier@3.8.1: {} prettier@3.8.3: {}
+1 -1
View File
@@ -1 +1 @@
setups.@nuxt/test-utils="4.0.0" setups.@nuxt/test-utils="4.0.3"
+1 -1
View File
@@ -16,7 +16,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { ApexOptions } from 'apexcharts'; import type { ApexChart, ApexOptions } from 'apexcharts';
defineProps<{ defineProps<{
client: LocalClient; client: LocalClient;
@@ -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,
+152 -1
View File
@@ -120,7 +120,11 @@
"endpointDesc": "IP do cliente desde a que se establece a conexión WireGuard", "endpointDesc": "IP do cliente desde a que se establece a conexión WireGuard",
"search": "Buscar clientes...", "search": "Buscar clientes...",
"config": "Configuración", "config": "Configuración",
"viewConfig": "Ver configuración" "viewConfig": "Ver configuración",
"firewallIps": "IPs permitidas do cortalumes",
"firewallIpsDesc": "IPs/CIDRs de destino aos que este cliente pode acceder (aplicado no lado do servidor). Déixao baleiro para usar as IPs permitidas. Admite filtrado opcional por porto e protocolo. Consulta a documentación para a sintaxe.",
"downloadPng": "Descargar PNG",
"copyPng": "Copiar PNG"
}, },
"dialog": { "dialog": {
"change": "Cambiar", "change": "Cambiar",
@@ -141,5 +145,152 @@
"noItems": "Sen elementos", "noItems": "Sen elementos",
"nullNoItems": "Sen elementos. Usando a configuración global", "nullNoItems": "Sen elementos. Usando a configuración global",
"add": "Engadir" "add": "Engadir"
},
"admin": {
"general": {
"sessionTimeout": "Tempo límite da sesión",
"sessionTimeoutDesc": "Duración da sesión para Lembrarme (segundos)",
"metrics": "Métricas",
"metricsPassword": "Contrasinal",
"metricsPasswordDesc": "Contrasinal Bearer para o endpoint de métricas (contrasinal ou hash argon2)",
"json": "JSON",
"jsonDesc": "Ruta para as métricas en formato JSON",
"prometheus": "Prometheus",
"prometheusDesc": "Ruta para as métricas de Prometheus"
},
"config": {
"connection": "Conexión",
"hostDesc": "Nome de host público ao que se conectarán os clientes (invalida a configuración)",
"portDesc": "Porto UDP público ao que se conectarán os clientes (invalida a configuración; probablemente tamén queiras cambiar o Porto da interface)",
"allowedIpsDesc": "IPs permitidas que usarán os clientes (configuración global)",
"dnsDesc": "Servidor DNS que usarán os clientes (configuración global)",
"mtuDesc": "MTU que usarán os clientes (só para clientes novos)",
"persistentKeepaliveDesc": "Intervalo en segundos para enviar keepalives ao servidor. 0 = desactivado (só para clientes novos)",
"suggest": "Suxerir",
"suggestDesc": "Escolle un enderezo IP ou nome de host para o campo Host"
},
"interface": {
"cidrSuccess": "CIDR cambiado",
"device": "Dispositivo",
"deviceDesc": "Dispositivo Ethernet polo que se redirixirá o tráfico de WireGuard",
"mtuDesc": "MTU que usará WireGuard",
"portDesc": "Porto UDP no que WireGuard escoitará (probablemente tamén queiras cambiar o Porto da configuración)",
"changeCidr": "Cambiar CIDR",
"restart": "Reiniciar interface",
"restartDesc": "Reiniciar a interface de WireGuard",
"restartWarn": "Seguro que queres reiniciar a interface? Isto desconectará todos os clientes.",
"restartSuccess": "Interface reiniciada",
"firewall": "Filtrado de tráfico",
"firewallEnabled": "Activar devasa por cliente",
"firewallEnabledDesc": "Restrinxir o tráfico dos clientes a IPs de destino específicas usando iptables. Cando está activado, cada cliente pode configurarse con destinos permitidos."
},
"introText": "Benvido ao panel de administración.\n\nAquí podes xestionar a configuración xeral, a configuración, os axustes da interface e os hooks.\n\nComeza escollendo unha das seccións na barra lateral."
},
"zod": {
"generic": {
"required": "{0} é obrigatorio",
"validNumber": "{0} debe ser un número válido",
"validNumberRange": "{0} debe ser un número válido ou un rango de números",
"validString": "{0} debe ser unha cadea válida",
"validBoolean": "{0} debe ser un booleano válido",
"validArray": "{0} debe ser un array válido",
"stringMin": "{0} debe ter polo menos {1} carácter",
"numberMin": "{0} debe ser polo menos {1}"
},
"client": {
"id": "ID do cliente",
"name": "Nome",
"expiresAt": "Caduca o",
"address4": "Enderezo IPv4",
"address6": "Enderezo IPv6",
"serverAllowedIps": "IPs permitidas do servidor",
"firewallIps": "IPs permitidas da devasa",
"firewallIpsInvalid": "Entrada de IP da devasa non válida. Consulta a documentación para a sintaxe admitida."
},
"user": {
"username": "Nome de usuario",
"password": "Contrasinal",
"remember": "Lembrar",
"name": "Nome",
"email": "Correo electrónico",
"emailInvalid": "O correo electrónico debe ser válido",
"passwordMatch": "Os contrasinais deben coincidir",
"totpEnable": "Activar TOTP",
"totpEnableTrue": "Activar TOTP debe ser verdadeiro",
"totpCode": "Código TOTP"
},
"userConfig": {
"host": "Host"
},
"general": {
"sessionTimeout": "Tempo límite da sesión",
"metricsEnabled": "Métricas",
"metricsPassword": "Contrasinal das métricas"
},
"interface": {
"cidr": "CIDR",
"device": "Dispositivo",
"cidrValid": "O CIDR debe ser válido"
},
"otl": "Ligazón dun só uso",
"stringMalformed": "A cadea está mal formada",
"body": "O corpo debe ser un obxecto válido",
"hook": "Hook",
"enabled": "Activado",
"mtu": "MTU",
"port": "Porto",
"persistentKeepalive": "Keepalive persistente",
"address": "Enderezo IP",
"dns": "DNS",
"allowedIps": "IPs permitidas",
"file": "Ficheiro"
},
"hooks": {
"preUp": "PreUp",
"postUp": "PostUp",
"preDown": "PreDown",
"postDown": "PostDown"
},
"copy": {
"notSupported": "Copiar non é compatible",
"copied": "Copiado!",
"failed": "Erro ao copiar",
"copy": "Copiar"
},
"awg": {
"jCLabel": "Número de paquetes lixo (Jc)",
"jCDescription": "Número de paquetes lixo para enviar (1-128, recomendado: 4-12)",
"jMinLabel": "Tamaño mínimo dos paquetes lixo (Jmin)",
"jMinDescription": "Tamaño mínimo dos paquetes lixo (0-1279*, recomendado: 8, debe ser < Jmax)",
"jMaxLabel": "Tamaño máximo dos paquetes lixo (Jmax)",
"jMaxDescription": "Tamaño máximo dos paquetes lixo (1-1280*, recomendado: 80, debe ser > Jmin)",
"s1Label": "Tamaño lixo do paquete de inicio (S1)",
"s1Description": "Tamaño lixo do paquete de inicio (0-1132[1280* - 148 = 1132], recomendado: 15-150, S1+56 ≠ S2)",
"s2Label": "Tamaño lixo do paquete de resposta (S2)",
"s2Description": "Tamaño lixo do paquete de resposta (0-1188[1280* - 92 = 1188], recomendado: 15-150)",
"s3Label": "Tamaño lixo do paquete de resposta cookie (S3)",
"s3Description": "Tamaño lixo do paquete de resposta cookie",
"s4Label": "Tamaño lixo do paquete de transporte (S4)",
"s4Description": "Tamaño lixo do paquete de transporte",
"h1Label": "Cabeceira máxica de inicio (H1)",
"h1Description": "Valor ou rango da cabeceira do paquete de inicio (X ou X-Y, onde X<Y. Mín 5, máx 2147483647. O valor ou rango non debe solaparse con outras cabeceiras)",
"h2Label": "Cabeceira máxica de resposta (H2)",
"h2Description": "Valor ou rango da cabeceira do paquete de resposta (X ou X-Y, onde X<Y. Mín 5, máx 2147483647. O valor ou rango non debe solaparse con outras cabeceiras)",
"h3Label": "Cabeceira máxica da resposta cookie (H3)",
"h3Description": "Valor ou rango da cabeceira do paquete de resposta cookie (X ou X-Y, onde X<Y. Mín 5, máx 2147483647. O valor ou rango non debe solaparse con outras cabeceiras)",
"h4Label": "Cabeceira máxica de transporte (H4)",
"h4Description": "Valor ou rango da cabeceira do paquete de transporte (X ou X-Y, onde X<Y. Mín 5, máx 2147483647. O valor ou rango non debe solaparse con outras cabeceiras)",
"i1Label": "Paquete lixo especial 1 (I1)",
"i1Description": "Paquete de simulación de protocolo en formato hexadecimal: <b 0x...>",
"i2Label": "Paquete lixo especial 2 (I2)",
"i2Description": "Paquete de simulación de protocolo en formato hexadecimal: <b 0x...>",
"i3Label": "Paquete lixo especial 3 (I3)",
"i3Description": "Paquete de simulación de protocolo en formato hexadecimal: <b 0x...>",
"i4Label": "Paquete lixo especial 4 (I4)",
"i4Description": "Paquete de simulación de protocolo en formato hexadecimal: <b 0x...>",
"i5Label": "Paquete lixo especial 5 (I5)",
"i5Description": "Paquete de simulación de protocolo en formato hexadecimal: <b 0x...>",
"mtuNote": "Os valores dependen da MTU",
"obfuscationParameters": "Parámetros de ofuscación de 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": "अनुमत 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 難読化パラメーター"
}
}
+68 -9
View File
@@ -45,9 +45,9 @@
}, },
"setup": { "setup": {
"welcome": "歡迎首次設定wg-easy", "welcome": "歡迎首次設定wg-easy",
"welcomeDesc": "這是最簡單的方法讓你在任何Linux主機上安裝管理WireGuard", "welcomeDesc": "這是在任何Linux主機上安裝管理WireGuard最簡單的方法。",
"existingSetup": "你有現存設定嗎?", "existingSetup": "你有現存設定嗎?",
"createAdminDesc": "請輸入管理員用戶名及一個高強度密碼。這些資料將用於登入管理員版面。", "createAdminDesc": "請輸入管理員用戶名及高強度密碼。這些資料將用於登入管理員版面。",
"setupConfigDesc": "請輸入主機及連接埠資料。這將用於客戶端設定,以便在他們的裝置上設定WireGuard。", "setupConfigDesc": "請輸入主機及連接埠資料。這將用於客戶端設定,以便在他們的裝置上設定WireGuard。",
"setupMigrationDesc": "如果你想將舊版wg-easy的資料轉移到新設定,請提供備份檔案。", "setupMigrationDesc": "如果你想將舊版wg-easy的資料轉移到新設定,請提供備份檔案。",
"upload": "上傳", "upload": "上傳",
@@ -88,6 +88,7 @@
"name": "名稱", "name": "名稱",
"expireDate": "有效期", "expireDate": "有效期",
"expireDateDesc": "客戶端將於此日期停用。留空則為永久有效", "expireDateDesc": "客戶端將於此日期停用。留空則為永久有效",
"delete": "刪除",
"deleteClient": "刪除客戶端", "deleteClient": "刪除客戶端",
"deleteDialog1": "你確定要刪除", "deleteDialog1": "你確定要刪除",
"deleteDialog2": "此操作無法還原。", "deleteDialog2": "此操作無法還原。",
@@ -114,21 +115,31 @@
"hooksDescription": "掛鉤僅適用於wg-quick", "hooksDescription": "掛鉤僅適用於wg-quick",
"hooksLeaveEmpty": "僅適用於wg-quick,否則請留空", "hooksLeaveEmpty": "僅適用於wg-quick,否則請留空",
"dnsDesc": "客戶端使用的域名系統伺服器(取代全局配置)", "dnsDesc": "客戶端使用的域名系統伺服器(取代全局配置)",
"search": "搜尋客戶端..." "notConnected": "客戶端尚未連接",
"endpoint": "端點",
"endpointDesc": "建立WireGuard連線的客戶端IP地址",
"search": "搜尋客戶端...",
"config": "配置",
"viewConfig": "檢視配置",
"firewallIps": "防火牆IP白名單",
"firewallIpsDesc": "此客戶端可存取的目的地IP/CIDR(伺服器端強制執行)。留空則使用「IP白名單」。支援選填的連接埠及協定過濾,語法請參閱說明文件。",
"downloadPng": "正在下載PNG",
"copyPng": "複製PNG"
}, },
"dialog": { "dialog": {
"change": "更改", "change": "更改",
"cancel": "取消", "cancel": "取消",
"create": "建" "create": "建"
}, },
"toast": { "toast": {
"success": "成功", "success": "成功",
"saved": "已存", "saved": "已存",
"error": "錯誤" "error": "錯誤",
"unknown": "未知錯誤。詳情請參閱主控台。"
}, },
"form": { "form": {
"actions": "操作", "actions": "操作",
"save": "存", "save": "存",
"revert": "重置", "revert": "重置",
"sectionGeneral": "一般設定", "sectionGeneral": "一般設定",
"sectionAdvanced": "進階", "sectionAdvanced": "進階",
@@ -169,7 +180,10 @@
"restart": "重啟介面", "restart": "重啟介面",
"restartDesc": "重啟WireGuard介面", "restartDesc": "重啟WireGuard介面",
"restartWarn": "你確定要重新啟動介面嗎?這將中斷所有客戶端連線。", "restartWarn": "你確定要重新啟動介面嗎?這將中斷所有客戶端連線。",
"restartSuccess": "介面已重啟" "restartSuccess": "介面已重啟",
"firewall": "流量過濾",
"firewallEnabled": "啟用獨立客戶端防火牆",
"firewallEnabledDesc": "使用iptables以限制客戶端流量至特定的目的地IP。啟用後,即可為每個客戶端單獨設定允許目的地。"
}, },
"introText": "歡迎來到管理員版面。\n\n你可以在側邊欄中管理一般、配置、介面和掛鉤設定。" "introText": "歡迎來到管理員版面。\n\n你可以在側邊欄中管理一般、配置、介面和掛鉤設定。"
}, },
@@ -177,6 +191,7 @@
"generic": { "generic": {
"required": "{0}為必填項", "required": "{0}為必填項",
"validNumber": "{0}必須是有效的數字", "validNumber": "{0}必須是有效的數字",
"validNumberRange": "{0}必須是有效的數字或數字範圍",
"validString": "{0}必須是有效的字串", "validString": "{0}必須是有效的字串",
"validBoolean": "{0}必須是有效的布林值", "validBoolean": "{0}必須是有效的布林值",
"validArray": "{0}必須是有效的陣列", "validArray": "{0}必須是有效的陣列",
@@ -189,7 +204,9 @@
"expiresAt": "到期於", "expiresAt": "到期於",
"address4": "IPv4地址", "address4": "IPv4地址",
"address6": "IPv6地址", "address6": "IPv6地址",
"serverAllowedIps": "伺服器IP白名單" "serverAllowedIps": "伺服器IP白名單",
"firewallIps": "防火牆IP白名單",
"firewallIpsInvalid": "防火牆IP輸入格式無效。請參閱說明文件以查看支援語法。"
}, },
"user": { "user": {
"username": "用戶名", "username": "用戶名",
@@ -234,5 +251,47 @@
"postUp": "PostUp", "postUp": "PostUp",
"preDown": "PreDown", "preDown": "PreDown",
"postDown": "PostDown" "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-150S1+56 ≠ S2)",
"s2Label": "回應封包填充大小 (S2)",
"s2Description": "回應封包填充大小 (0-1188 [1280* - 92 = 1188],建議: 15-150)",
"s3Label": "Cookie 回覆封包填充大小 (S3)",
"s3Description": "Cookie 回覆封包填充大小",
"s4Label": "傳輸封包填充大小 (S4)",
"s4Description": "傳輸封包填充大小",
"h1Label": "初始特徵標頭 (H1)",
"h1Description": "初始封包標頭值 (5-2147483647,必須與 H2-H4 不同)",
"h2Label": "回應特徵標頭 (H2)",
"h2Description": "回應封包標頭值 (5-2147483647,必須與 H1、H3、H4 不同)",
"h3Label": "Cookie 回覆特徵標頭 (H3)",
"h3Description": "Cookie 回覆封包標頭值 (5-2147483647,必須與 H1、H2、H4 不同)",
"h4Label": "傳輸特徵標頭 (H4)",
"h4Description": "傳輸封包標頭值 (5-2147483647,必須與 H1-H3 不同)",
"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',
+29 -30
View File
@@ -1,6 +1,6 @@
{ {
"name": "wg-easy", "name": "wg-easy",
"version": "15.3.0-beta.2", "version": "15.3.0",
"description": "The easiest way to run WireGuard VPN + Web-based Admin UI.", "description": "The easiest way to run WireGuard VPN + Web-based Admin UI.",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -23,56 +23,55 @@
"dependencies": { "dependencies": {
"@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.0", "@libsql/client": "^0.17.3",
"@nuxtjs/i18n": "^10.2.3", "@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.2.1", "@vueuse/core": "^14.3.0",
"@vueuse/nuxt": "^14.2.1", "@vueuse/nuxt": "^14.3.0",
"apexcharts": "^5.10.4", "apexcharts": "^5.13.0",
"argon2": "^0.44.0", "argon2": "^0.44.0",
"cidr-tools": "^11.2.1", "cidr-tools": "^12.0.2",
"citty": "^0.2.1", "citty": "^0.2.2",
"consola": "^3.4.2", "consola": "^3.4.2",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"debug": "^4.4.3", "drizzle-orm": "^0.45.2",
"drizzle-orm": "^0.45.1", "ip-bigint": "^9.0.5",
"ip-bigint": "^8.2.12", "is-cidr": "^7.0.0",
"is-cidr": "^6.0.3",
"is-ip": "^5.0.1", "is-ip": "^5.0.1",
"js-sha256": "^0.11.1", "js-sha256": "^0.11.1",
"nuxt": "^3.21.2", "nuxt": "^3.21.6",
"otpauth": "^9.5.0", "obug": "^2.1.1",
"otpauth": "^9.5.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"qr": "^0.5.5", "qr": "^0.6.0",
"radix-vue": "^1.9.17", "radix-vue": "^1.9.17",
"semver": "^7.7.4", "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",
"vue3-apexcharts": "^1.11.1", "vue3-apexcharts": "^1.11.1",
"zod": "^4.3.6" "zod": "^4.4.3"
}, },
"devDependencies": { "devDependencies": {
"@nuxt/eslint": "^1.15.2", "@nuxt/eslint": "^1.15.2",
"@nuxt/test-utils": "^4.0.0", "@nuxt/test-utils": "^4.0.3",
"@types/debug": "^4.1.12",
"@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.0", "@vitest/coverage-v8": "^4.1.7",
"@vitest/ui": "^4.1.0", "@vitest/ui": "^4.1.7",
"drizzle-kit": "^0.31.9", "drizzle-kit": "^0.31.10",
"esbuild": "^0.27.4", "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.1", "prettier": "^3.8.3",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.8.0",
"tsx": "^4.21.0", "tsx": "^4.22.3",
"typescript": "^5.9.3", "typescript": "^6.0.3",
"vitest": "^4.1.0", "vitest": "^4.1.7",
"vue-tsc": "^3.2.5" "vue-tsc": "^3.3.3"
}, },
"packageManager": "pnpm@10.32.1" "packageManager": "pnpm@11.5.0"
} }
+3630 -3085
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
allowBuilds:
'@parcel/watcher': false
argon2: false
esbuild: false
unrs-resolver: false
vue-demi: false
minimumReleaseAgeStrict: true
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 663 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

@@ -12,6 +12,19 @@ export default definePermissionEventHandler(
const client = await Database.clients.get(clientId); const client = await Database.clients.get(clientId);
checkPermissions(client); checkPermissions(client);
if (
client &&
client.expiresAt !== null &&
new Date() > new Date(client.expiresAt)
) {
throw createError({
statusCode: 422,
statusMessage:
'Client is expired. Please update the expiration date first.',
message: 'Client is expired. Please update the expiration date first.',
});
}
await Database.clients.toggle(clientId, true); await Database.clients.toggle(clientId, true);
await WireGuard.saveConfig(); await WireGuard.saveConfig();
return { success: true }; return { success: 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');
} }
+2 -2
View File
@@ -1,7 +1,7 @@
import { drizzle } from 'drizzle-orm/libsql'; import { drizzle } from 'drizzle-orm/libsql';
import { migrate as drizzleMigrate } from 'drizzle-orm/libsql/migrator'; import { migrate as drizzleMigrate } from 'drizzle-orm/libsql/migrator';
import { createClient } from '@libsql/client'; import { createClient } from '@libsql/client';
import debug from 'debug'; import { createDebug } from 'obug';
import { eq } from 'drizzle-orm'; import { eq } from 'drizzle-orm';
import * as schema from './schema'; import * as schema from './schema';
@@ -13,7 +13,7 @@ import { InterfaceService } from './repositories/interface/service';
import { HooksService } from './repositories/hooks/service'; import { HooksService } from './repositories/hooks/service';
import { OneTimeLinkService } from './repositories/oneTimeLink/service'; import { OneTimeLinkService } from './repositories/oneTimeLink/service';
const DB_DEBUG = debug('Database'); const DB_DEBUG = createDebug('Database');
const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' }); const client = createClient({ url: 'file:/etc/wireguard/wg-easy.db' });
const db = drizzle({ client, schema }); const db = drizzle({ client, schema });
+2 -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',
@@ -62,6 +60,7 @@ async function getPrometheusResponse() {
'# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake', '# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake',
'# TYPE wireguard_latest_handshake_seconds gauge', '# TYPE wireguard_latest_handshake_seconds gauge',
`${wireguardLatestHandshakeSeconds.join('\n')}`, `${wireguardLatestHandshakeSeconds.join('\n')}`,
'',
]; ];
return returnText.join('\n'); return returnText.join('\n');
+2 -2
View File
@@ -1,8 +1,8 @@
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import debug from 'debug'; import { createDebug } from 'obug';
import type { InterfaceType } from '#db/repositories/interface/types'; import type { InterfaceType } from '#db/repositories/interface/types';
const WG_DEBUG = debug('WireGuard'); const WG_DEBUG = createDebug('WireGuard');
const generateRandomHeaderValue = () => const generateRandomHeaderValue = () =>
Math.floor(Math.random() * 2147483642) + 5; Math.floor(Math.random() * 2147483642) + 5;
+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;
+2 -2
View File
@@ -1,7 +1,7 @@
import childProcess from 'child_process'; import childProcess from 'child_process';
import debug from 'debug'; import { createDebug } from 'obug';
const CMD_DEBUG = debug('CMD'); const CMD_DEBUG = createDebug('CMD');
export function exec( export function exec(
cmd: string, cmd: string,
+3 -2
View File
@@ -1,9 +1,9 @@
import debug from 'debug'; import { createDebug } from 'obug';
import packageJson from '@@/package.json'; import packageJson from '@@/package.json';
export const RELEASE = 'v' + packageJson.version; export const RELEASE = 'v' + packageJson.version;
export const SERVER_DEBUG = debug('Server'); export const SERVER_DEBUG = createDebug('Server');
export const OLD_ENV = { export const OLD_ENV = {
/** @deprecated Only for migration purposes */ /** @deprecated Only for migration purposes */
@@ -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 = {
+2 -2
View File
@@ -1,11 +1,11 @@
import debug from 'debug'; import { createDebug } from 'obug';
import { isIPv6 } from 'is-ip'; import { isIPv6 } from 'is-ip';
import type { ClientType } from '#db/repositories/client/types'; import type { ClientType } from '#db/repositories/client/types';
import type { InterfaceType } from '#db/repositories/interface/types'; import type { InterfaceType } from '#db/repositories/interface/types';
import type { UserConfigType } from '#db/repositories/userConfig/types'; import type { UserConfigType } from '#db/repositories/userConfig/types';
const FW_DEBUG = debug('Firewall'); const FW_DEBUG = createDebug('Firewall');
const CHAIN_NAME = 'WG_CLIENTS'; const CHAIN_NAME = 'WG_CLIENTS';
// Mutex to prevent concurrent rule rebuilds // Mutex to prevent concurrent rule rebuilds
+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',