mirror of
https://github.com/h44z/wg-portal.git
synced 2025-10-05 16:06:17 +00:00
Compare commits
173 Commits
v2.0.0-alp
...
v2.0.2
Author | SHA1 | Date | |
---|---|---|---|
|
930ef7b573 | ||
|
8816165260 | ||
|
ab9995350f | ||
|
18296673d7 | ||
|
7df4e4b813 | ||
|
657c4307b3 | ||
|
b918fb6522 | ||
|
78deede360 | ||
|
a8fb4365cf | ||
|
0102588d23 | ||
|
6a96925be7 | ||
|
f018babca7 | ||
|
c6253e7c15 | ||
|
2a1d82251e | ||
|
99d6ce73ad | ||
|
3eb84f0ee9 | ||
|
d8a57edef9 | ||
|
4ccc59c109 | ||
|
8271dd7c1f | ||
|
4ca37089bc | ||
|
8e5d5138c0 | ||
|
c73286e11a | ||
|
b4aa6f8ef3 | ||
|
432c627f9b | ||
|
cd60761ea7 | ||
|
2c8304417b | ||
|
020ebb64e7 | ||
|
923d4a6188 | ||
|
2b46dca770 | ||
|
b9c4ca04f5 | ||
|
dddf0c475b | ||
|
fe60a5ab9b | ||
|
e176e07f7d | ||
|
b06c03ef8e | ||
|
6b0b78d749 | ||
|
62f3c8d4a1 | ||
|
fbcb22198c | ||
|
2c443a4a9b | ||
|
059234d416 | ||
|
e2966d32ea | ||
|
9354a1d9d3 | ||
|
e75a32e4d0 | ||
|
1d94f6baaf | ||
|
6681dfa96f | ||
|
a60feb7fc9 | ||
|
37904f96fb | ||
|
1e9ee25e49 | ||
|
30eac7c44a | ||
|
801ce76616 | ||
|
5f9c3bab3e | ||
|
e19f42b1eb | ||
|
34fb373659 | ||
|
b938bc8c4c | ||
|
87bf5da5bd | ||
|
3723e4cc75 | ||
|
6cbccf6d43 | ||
|
a49cfa6343 | ||
|
fe681c015c | ||
|
7d0da4e7ad | ||
|
3218bdd6fb | ||
|
12ccd6e32d | ||
|
02ed7b19df | ||
|
678b6c6456 | ||
|
0206952182 | ||
|
53bae9d194 | ||
|
f616a9f5f4 | ||
|
bf5453c264 | ||
|
fd631d3b9f | ||
|
9680e8350c | ||
|
7473132932 | ||
|
5c51573874 | ||
|
fdb436b135 | ||
|
e24acfa57d | ||
|
10332c7f9a | ||
|
f7d7038829 | ||
|
66ccdc29e9 | ||
|
40b4538e78 | ||
|
986f6fdead | ||
|
dabdf111f9 | ||
|
b074af6dc5 | ||
|
eeb0c87c68 | ||
|
67f076effe | ||
|
f6d7a851d1 | ||
|
fc712ebf42 | ||
|
43163273fa | ||
|
5697c2b7f2 | ||
|
e983a7b8f3 | ||
|
c33eaba1c0 | ||
|
3774257abb | ||
|
588f09bdaa | ||
|
7557a6ef5a | ||
|
3478645317 | ||
|
a950dd76ba | ||
|
8c0ecec485 | ||
|
d01d865b4d | ||
|
1b8cdc3417 | ||
|
d35889de73 | ||
|
0b18b5efd6 | ||
|
2cf2341e4c | ||
|
043d25a08f | ||
|
f6c8cd5ea8 | ||
|
a04eaa4bfb | ||
|
7a0a2117f5 | ||
|
2cea2e477a | ||
|
c2658534b0 | ||
|
2030c59362 | ||
|
e31c170f48 | ||
|
49a987cbce | ||
|
3526240faf | ||
|
075fd0171e | ||
|
c73ce0288e | ||
|
31c0daeba8 | ||
|
662e9c0549 | ||
|
6523a87dfb | ||
|
7ccec5db8d | ||
|
c211c56f75 | ||
|
17844ed929 | ||
|
2d78fe33b8 | ||
|
63d85d8123 | ||
|
26d3257516 | ||
|
d596f578f6 | ||
|
ad267ed0a8 | ||
|
624988aef1 | ||
|
3020fbca4e | ||
|
6d86f15ff8 | ||
|
62dbdfe0f9 | ||
|
378252ba2f | ||
|
0664bd0ad0 | ||
|
877cdae587 | ||
|
edb5c82a66 | ||
|
0ea24e313d | ||
|
983568b36a | ||
|
81ff0cde60 | ||
|
0f27443ffc | ||
|
ca6070689e | ||
|
ba9b6c39e0 | ||
|
afcba8d43e | ||
|
90a570bd66 | ||
|
f7c3bdf456 | ||
|
486a6ac038 | ||
|
bf9183256a | ||
|
6bb683047e | ||
|
5a289276f4 | ||
|
d8eac37302 | ||
|
386597e057 | ||
|
f22a7e4a2e | ||
|
ae1be0e367 | ||
|
7a08c14de4 | ||
|
2c01f42369 | ||
|
3196010a58 | ||
|
6ffe1a90ae | ||
|
e3d05a4678 | ||
|
deff2334ac | ||
|
4f1044a963 | ||
|
2428dedc42 | ||
|
605841f2a0 | ||
|
a46dabc1d3 | ||
|
3f72de6af4 | ||
|
f1f5280cbc | ||
|
48f4b6cb0e | ||
|
58294a3c2a | ||
|
6f52cb2ada | ||
|
85381121ee | ||
|
a6d985c2fe | ||
|
aebf80bf68 | ||
|
e72ba87619 | ||
|
e6b01a9903 | ||
|
2f79dd04c0 | ||
|
e5ed9736b3 | ||
|
c8353b85ae | ||
|
6142031387 | ||
|
dd86d0ff49 | ||
|
bdd426a679 |
@@ -1,67 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
jobs:
|
||||
build-latest:
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-latest-v4-{{ checksum "go.sum" }}
|
||||
- run:
|
||||
name: Build Frontend
|
||||
command: |
|
||||
make frontend
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: |
|
||||
make build-dependencies
|
||||
- save_cache:
|
||||
key: go-mod-latest-v4-{{ checksum "go.sum" }}
|
||||
paths:
|
||||
- "~/go/pkg/mod"
|
||||
- run:
|
||||
name: Build AMD64
|
||||
command: |
|
||||
VERSION=$CIRCLE_BRANCH
|
||||
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build-amd64
|
||||
- run:
|
||||
name: Install Cross-Platform Dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||
sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross
|
||||
sudo ln -s /usr/include/asm-generic /usr/include/asm
|
||||
- run:
|
||||
name: Build ARM64
|
||||
command: |
|
||||
VERSION=$CIRCLE_BRANCH
|
||||
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build-arm64
|
||||
- run:
|
||||
name: Build ARM
|
||||
command: |
|
||||
VERSION=$CIRCLE_BRANCH
|
||||
if [ ! -z "${CIRCLE_TAG}" ]; then VERSION=$CIRCLE_TAG; fi
|
||||
make ENV_BUILD_IDENTIFIER=$VERSION ENV_BUILD_VERSION=$(echo $CIRCLE_SHA1 | cut -c1-7) build-arm
|
||||
- store_artifacts:
|
||||
path: ~/repo/dist
|
||||
- run:
|
||||
name: "Publish Release on GitHub"
|
||||
command: |
|
||||
if [ ! -z "${CIRCLE_TAG}" ]; then
|
||||
go install github.com/tcnksm/ghr@latest
|
||||
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -replace $CIRCLE_TAG ~/repo/dist
|
||||
fi
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
- image: cimg/go:1.21-node
|
||||
|
||||
workflows:
|
||||
build-and-release:
|
||||
jobs:
|
||||
#--------------- BUILD ---------------#
|
||||
- build-latest:
|
||||
filters:
|
||||
tags:
|
||||
only: /^v.*/
|
@@ -1,5 +1,14 @@
|
||||
.github/
|
||||
**/.vscode/
|
||||
docs/
|
||||
frontend/node_modules/
|
||||
internal/app/api/core/frontend-dist
|
||||
# Ignore everything
|
||||
*
|
||||
|
||||
# Allow backend files
|
||||
!cmd/
|
||||
!internal/
|
||||
!go.mod
|
||||
!go.sum
|
||||
|
||||
# Allow frontend files
|
||||
!frontend/
|
||||
|
||||
# Ignore node_modules
|
||||
**/node_modules/
|
||||
|
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve WG-Portal
|
||||
labels: bug
|
||||
|
||||
---
|
||||
<!-- Tip: you can use code blocks
|
||||
for better formatting of yaml config or logs
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
```
|
||||
|
||||
```console
|
||||
logs here
|
||||
``` -->
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Steps to reproduce**
|
||||
<!--Steps to reproduce the bug should be clear and easily reproducible to help people
|
||||
gain an understanding of the problem.-->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
- Application version: v
|
||||
- Install method: binary/docker/helm/sources
|
||||
<!-- - OS: -->
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
labels: 'enhancement'
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is. -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -18,3 +18,13 @@ updates:
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
groups:
|
||||
golang:
|
||||
patterns:
|
||||
- golang.org*
|
||||
gorm:
|
||||
patterns:
|
||||
- gorm.io*
|
||||
patch:
|
||||
update-types:
|
||||
- patch
|
||||
|
18
.github/pull_request_template.md
vendored
Normal file
18
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
## Problem Statement
|
||||
|
||||
What is the problem you're trying to solve?
|
||||
|
||||
## Related Issue
|
||||
|
||||
Fixes #...
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
How do you like to solve the issue and why?
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Commits are signed with `git commit --signoff`
|
||||
- [ ] Changes have reasonable test coverage
|
||||
- [ ] Tests pass with `make test`
|
||||
- [ ] Helm docs are up-to-date with `make helm-docs`
|
75
.github/workflows/chart.yml
vendored
Normal file
75
.github/workflows/chart.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
# Publish chart to the GitHub Container Registry (GHCR) on push to master
|
||||
# Run the following tests on PRs:
|
||||
# - Check if chart's documentation is up to date
|
||||
# - Check chart linting
|
||||
# - Check chart installation in a Kind cluster
|
||||
# - Check chart packaging
|
||||
|
||||
name: Chart
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
paths: ['deploy/helm/**']
|
||||
push:
|
||||
branches: [master]
|
||||
paths: ['deploy/helm/**']
|
||||
|
||||
jobs:
|
||||
lint-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check docs
|
||||
run: |
|
||||
make helm-docs
|
||||
if ! git diff --exit-code; then
|
||||
echo "error::Documentation is not up to date. Please run helm-docs and commit changes."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ct lint requires Python 3.x to run following packages:
|
||||
# - yamale (https://github.com/23andMe/Yamale)
|
||||
# - yamllint (https://github.com/adrienverge/yamllint)
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.x'
|
||||
|
||||
- uses: helm/chart-testing-action@v2
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml
|
||||
|
||||
- uses: nolar/setup-k3d-k3s@v1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: ct install --config ct.yaml
|
||||
|
||||
- name: Check chart packaging
|
||||
run: helm package deploy/helm
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
permissions:
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Package helm chart
|
||||
run: helm package deploy/helm
|
||||
|
||||
- name: Push chart to GHCR
|
||||
run: helm push wg-portal-*.tgz oci://ghcr.io/${{ github.repository_owner }}/charts
|
70
.github/workflows/codeql-analysis.yml
vendored
70
.github/workflows/codeql-analysis.yml
vendored
@@ -1,70 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '35 15 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go', 'javascript-typescript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
56
.github/workflows/docker-publish.yml
vendored
56
.github/workflows/docker-publish.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
push:
|
||||
branches: [master, stable]
|
||||
branches: [master]
|
||||
# Publish vX.X.X tags as releases.
|
||||
tags: ["v*.*.*"]
|
||||
|
||||
@@ -59,15 +59,20 @@ jobs:
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
# semver tags, without v prefix
|
||||
type=semver,pattern={{version}}
|
||||
# major and major.minor tags are not available for alpha or beta releases
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
type=semver,pattern=v{{major}}
|
||||
# set latest tag for default branch
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
# add v{{major}} tag, even for beta or release-canidate releases
|
||||
type=match,pattern=(v\d),group=1,enable=${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
||||
# add {{major}} tag, even for beta releases or release-canidate releases
|
||||
type=match,pattern=v(\d),group=1,enable=${{ contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -77,3 +82,44 @@ jobs:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
build-args: |
|
||||
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
||||
|
||||
- name: Export binaries from images
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
target: binaries
|
||||
outputs: type=local,dest=./binaries
|
||||
build-args: |
|
||||
BUILD_VERSION=${{ env.BUILD_VERSION }}
|
||||
|
||||
- name: Rename binaries
|
||||
run: |
|
||||
for file in binaries/linux*/wg-portal; do
|
||||
mv $file binaries/wg-portal_$(basename $(dirname $file))
|
||||
done
|
||||
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
path: binaries/wg-portal_linux*
|
||||
retention-days: 10
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-n-push
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: binaries
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: 'wg-portal_linux*'
|
||||
generate_release_notes: true
|
||||
|
36
.github/workflows/pages.yml
vendored
36
.github/workflows/pages.yml
vendored
@@ -1,22 +1,40 @@
|
||||
name: github-pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
branches: [master]
|
||||
tags:
|
||||
- 'v*'
|
||||
- '!v*-alpha*'
|
||||
- '!v*-beta*'
|
||||
- '!v*-rc*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
key: ${{ github.ref }}
|
||||
path: .cache
|
||||
- run: pip install mkdocs-material
|
||||
- run: pip install pillow cairosvg
|
||||
- run: mkdocs gh-deploy --force
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install mike mkdocs-material[imaging] mkdocs-minify-plugin mkdocs-swagger-ui-tag
|
||||
|
||||
- name: Publish documentation
|
||||
if: ${{ ! startsWith(github.ref, 'refs/tags/') }}
|
||||
run: mike deploy --push ${{ github.ref_name }}
|
||||
env:
|
||||
GIT_COMMITTER_NAME: "github-actions[bot]"
|
||||
GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
- name: Publish latest documentation
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
run: mike deploy --push --update-aliases ${{ github.ref_name }} latest
|
||||
env:
|
||||
GIT_COMMITTER_NAME: "github-actions[bot]"
|
||||
GIT_COMMITTER_EMAIL: "41898282+github-actions[bot]@users.noreply.github.com"
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -32,11 +32,12 @@ ssh.key
|
||||
.testCoverage.txt
|
||||
wg_portal.db
|
||||
sqlite.db
|
||||
swagger.json
|
||||
swagger.yaml
|
||||
/config.yml
|
||||
/config.yaml
|
||||
/config/
|
||||
venv/
|
||||
.cache/
|
||||
# ignore local frontend dist directory
|
||||
internal/app/api/core/frontend-dist
|
||||
# mkdocs output directory
|
||||
site/
|
||||
|
21
Dockerfile
21
Dockerfile
@@ -4,7 +4,7 @@
|
||||
######
|
||||
# Build frontend
|
||||
######
|
||||
FROM --platform=${BUILDPLATFORM} node:lts-alpine as frontend
|
||||
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS frontend
|
||||
# Set the working directory
|
||||
WORKDIR /build
|
||||
# Download dependencies
|
||||
@@ -20,14 +20,15 @@ RUN npm run build
|
||||
######
|
||||
# Build backend
|
||||
######
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.21-alpine as builder
|
||||
FROM --platform=${BUILDPLATFORM} golang:1.24-alpine AS builder
|
||||
# Set the working directory
|
||||
WORKDIR /build
|
||||
# Download dependencies
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
# Copy the sources to the working directory
|
||||
COPY . .
|
||||
COPY ./cmd ./cmd
|
||||
COPY ./internal ./internal
|
||||
# Copy the frontend build result
|
||||
COPY --from=frontend /build/dist/ ./internal/app/api/core/frontend-dist/
|
||||
# Set the build version from arguments
|
||||
@@ -40,20 +41,28 @@ RUN CGO_ENABLED=0 GOARCH=${TARGETARCH} go build -o /build/dist/wg-portal \
|
||||
-tags netgo \
|
||||
cmd/wg-portal/main.go
|
||||
|
||||
######
|
||||
# Export binaries
|
||||
######
|
||||
FROM scratch AS binaries
|
||||
COPY --from=builder /build/dist/wg-portal /
|
||||
|
||||
######
|
||||
# Final image
|
||||
######
|
||||
FROM alpine:3.19
|
||||
# Install OS-level dependencies
|
||||
RUN apk add --no-cache bash curl iptables nftables openresolv
|
||||
RUN apk add --no-cache bash curl iptables nftables openresolv wireguard-tools
|
||||
# Setup timezone
|
||||
ENV TZ=Europe/Vienna
|
||||
ENV TZ=UTC
|
||||
# Copy binaries
|
||||
COPY --from=builder /build/dist/wg-portal /app/wg-portal
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /app
|
||||
# by default, the web-portal is reachable on port 8888
|
||||
# Expose default ports for metrics, web and wireguard
|
||||
EXPOSE 8787/tcp
|
||||
EXPOSE 8888/tcp
|
||||
EXPOSE 51820/udp
|
||||
# the database and config file can be mounted from the host
|
||||
VOLUME [ "/app/data", "/app/config" ]
|
||||
# Command to run the executable
|
||||
|
13
Makefile
13
Makefile
@@ -127,4 +127,15 @@ build-docker:
|
||||
docker build --progress=plain \
|
||||
--build-arg BUILD_IDENTIFIER=${ENV_BUILD_IDENTIFIER} --build-arg BUILD_VERSION=${ENV_BUILD_VERSION} \
|
||||
--build-arg TARGETPLATFORM=unknown . \
|
||||
-t h44z/wg-portal:local
|
||||
-t h44z/wg-portal:local
|
||||
|
||||
#< helm-docs: Generate the helm chart documentation
|
||||
.PHONY: helm-docs
|
||||
helm-docs:
|
||||
docker run --rm --volume "${PWD}/deploy:/helm-docs" -u "$$(id -u)" jnorwood/helm-docs -s file
|
||||
|
||||
#< run-mkdocs: Run a local instance of MkDocs
|
||||
.PHONY: run-mkdocs
|
||||
run-mkdocs:
|
||||
python -m venv venv; source venv/bin/activate; pip install mike cairosvg mkdocs-material mkdocs-minify-plugin mkdocs-swagger-ui-tag
|
||||
venv/bin/mkdocs serve
|
||||
|
226
README.md
226
README.md
@@ -1,209 +1,67 @@
|
||||
# WireGuard Portal (v2 - testing)
|
||||
# WireGuard Portal v2
|
||||
|
||||
[](https://travis-ci.com/h44z/wg-portal)
|
||||
[](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||

|
||||

|
||||
[](https://goreportcard.com/report/github.com/h44z/wg-portal)
|
||||

|
||||

|
||||
[](https://hub.docker.com/r/wgportal/wg-portal/)
|
||||
|
||||
> :warning: **IMPORTANT** Version 2 is currently under development and may contain bugs. It is currently not advised to use this version
|
||||
in production. Use version [v1](https://github.com/h44z/wg-portal/tree/stable) instead.
|
||||
|
||||
Since the project was accepted by the Docker-Sponsored Open Source Program, the Docker image location has moved to: https://hub.docker.com/r/wgportal/wg-portal.
|
||||
Please update the Docker image from **h44z/wg-portal** to **wgportal/wg-portal**.
|
||||
|
||||
A simple, web based configuration portal for [WireGuard](https://wireguard.com).
|
||||
## Introduction
|
||||
<!-- Text from this line # is included in docs/documentation/overview.md -->
|
||||
**WireGuard Portal** is a simple, web-based configuration portal for [WireGuard](https://wireguard.com) server management.
|
||||
The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
|
||||
interfaces. This allows for seamless activation or deactivation of new users, without disturbing existing VPN
|
||||
interfaces. This allows for the seamless activation or deactivation of new users without disturbing existing VPN
|
||||
connections.
|
||||
|
||||
The configuration portal supports using a database (SQLite, MySQL, MsSQL or Postgres), OAuth or LDAP (Active Directory or OpenLDAP) as a user source for authentication and profile data.
|
||||
|
||||
The configuration portal supports using a database (SQLite, MySQL, MsSQL, or Postgres), OAuth or LDAP
|
||||
(Active Directory or OpenLDAP) as a user source for authentication and profile data.
|
||||
|
||||
## Features
|
||||
* Self-hosted - the whole application is a single binary
|
||||
* Responsive web UI written in Vue.JS
|
||||
* Automatically select IP from the network pool assigned to client
|
||||
* QR-Code for convenient mobile client configuration
|
||||
* Sent email to client with QR-code and client config
|
||||
* Enable / Disable clients seamlessly
|
||||
* Generation of wg-quick configuration file (`wgX.conf`) if required
|
||||
* User authentication (database, OAuth or LDAP)
|
||||
* IPv6 ready
|
||||
* Docker ready
|
||||
* Can be used with existing WireGuard setups
|
||||
* Support for multiple WireGuard interfaces
|
||||
* Peer Expiry Feature
|
||||
* Handle route and DNS settings like wg-quick does
|
||||
* ~~REST API for management and client deployment~~ (coming soon)
|
||||
|
||||

|
||||
* Self-hosted - the whole application is a single binary
|
||||
* Responsive multi-language web UI written in Vue.js
|
||||
* Automatically selects IP from the network pool assigned to the client
|
||||
* QR-Code for convenient mobile client configuration
|
||||
* Sends email to the client with QR-code and client config
|
||||
* Enable / Disable clients seamlessly
|
||||
* Generation of wg-quick configuration file (`wgX.conf`) if required
|
||||
* User authentication (database, OAuth, or LDAP)
|
||||
* IPv6 ready
|
||||
* Docker ready
|
||||
* Can be used with existing WireGuard setups
|
||||
* Support for multiple WireGuard interfaces
|
||||
* Peer Expiry Feature
|
||||
* Handles route and DNS settings like wg-quick does
|
||||
* Exposes Prometheus metrics for monitoring and alerting
|
||||
* REST API for management and client deployment
|
||||
* Webhook for custom actions on peer, interface, or user updates
|
||||
|
||||
<!-- Text to this line # is included in docs/documentation/overview.md -->
|
||||

|
||||
|
||||
## Configuration
|
||||
You can configure WireGuard Portal using a yaml configuration file.
|
||||
The filepath of the yaml configuration file defaults to **config/config.yml** in the working directory of the executable.
|
||||
It is possible to override the configuration filepath using the environment variable **WG_PORTAL_CONFIG**.
|
||||
For example: `WG_PORTAL_CONFIG=/home/test/config.yml ./wg-portal-amd64`.
|
||||
## Documentation
|
||||
|
||||
By default, WireGuard Portal uses a SQLite database. The database is stored in **data/sqlite.db** in the working directory of the executable.
|
||||
|
||||
### Configuration Options
|
||||
The following configuration options are available:
|
||||
|
||||
| configuration key | parent key | default_value | description |
|
||||
|---------------------------------|------------|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| admin_user | core | admin@wgportal.local | The administrator user. This user will be created as default admin if it does not yet exist. |
|
||||
| admin_password | core | wgportal | The administrator password. If unchanged, a random password will be set on first startup. |
|
||||
| editable_keys | core | true | Allow to edit key-pairs in the UI. |
|
||||
| create_default_peer | core | false | If an LDAP user logs in for the first time and has no peers associated, a new WireGuard peer will be created for all server interfaces. |
|
||||
| create_default_peer_on_creation | core | false | If an LDAP user is created (e.g. through LDAP sync), a new WireGuard peer will be created for all server interfaces. |
|
||||
| self_provisioning_allowed | core | false | Allow registered users to automatically create peers via their profile page. |
|
||||
| import_existing | core | true | Import existing WireGuard interfaces and peers into WireGuard Portal. |
|
||||
| restore_state | core | true | Restore the WireGuard interface state after WireGuard Portal has started. |
|
||||
| log_level | advanced | warn | The loglevel, can be one of: trace, debug, info, warn, error. |
|
||||
| log_pretty | advanced | false | Uses pretty, colorized log messages. |
|
||||
| log_json | advanced | false | Logs in JSON format. |
|
||||
| ldap_sync_interval | advanced | 15m | The time interval after which users will be synchronized from LDAP. |
|
||||
| start_listen_port | advanced | 51820 | The first port number that will be used as listening port for new interfaces. |
|
||||
| start_cidr_v4 | advanced | 10.11.12.0/24 | The first IPv4 subnet that will be used for new interfaces. |
|
||||
| start_cidr_v6 | advanced | fdfd:d3ad:c0de:1234::0/64 | The first IPv6 subnet that will be used for new interfaces. |
|
||||
| use_ip_v6 | advanced | true | Enable IPv6 support. |
|
||||
| config_storage_path | advanced | | If a wg-quick style configuration should be stored to the filesystem, specify a storage directory. |
|
||||
| expiry_check_interval | advanced | 15m | The interval after which existing peers will be checked if they expired. |
|
||||
| rule_prio_offset | advanced | 20000 | The default offset for ip route rule priorities. |
|
||||
| route_table_offset | advanced | 20000 | The default offset for ip route table id's. |
|
||||
| use_ping_checks | statistics | true | If enabled, peers will be pinged periodically to check if they are still connected. |
|
||||
| ping_check_workers | statistics | 10 | Number of parallel ping checks that will be executed. |
|
||||
| ping_unprivileged | statistics | false | If set to false, the ping checks will run without root permissions (BETA). |
|
||||
| ping_check_interval | statistics | 1m | The interval time between two ping check runs. |
|
||||
| data_collection_interval | statistics | 10m | The interval between the data collection cycles. |
|
||||
| collect_interface_data | statistics | true | A flag to enable interface data collection like bytes sent and received. |
|
||||
| collect_peer_data | statistics | true | A flag to enable peer data collection like bytes sent and received, last handshake and remote endpoint address. |
|
||||
| collect_audit_data | statistics | true | If enabled, some events, like portal logins, will be logged to the database. |
|
||||
| host | mail | 127.0.0.1 | The mail-server address. |
|
||||
| port | mail | 25 | The mail-server SMTP port. |
|
||||
| encryption | mail | none | SMTP encryption type, allowed values: none, tls, starttls. |
|
||||
| cert_validation | mail | false | Validate the mail server certificate (if encryption tls is used). |
|
||||
| username | mail | | The SMTP user name. |
|
||||
| password | mail | | The SMTP password. |
|
||||
| auth_type | mail | plain | SMTP authentication type, allowed values: plain, login, crammd5. |
|
||||
| from | mail | Wireguard Portal <noreply@wireguard.local> | The address that is used to send mails. |
|
||||
| link_only | mail | false | Only send links to WireGuard Portal instead of the full configuration. |
|
||||
| callback_url_prefix | auth | /api/v0 | OAuth callback URL prefix. The full callback URL will look like: https://wg.portal.local/callback_url_prefix/provider_name/callback |
|
||||
| oidc | auth | Empty Array - no providers configured | A list of OpenID Connect providers. See auth/oidc properties to setup a new provider. |
|
||||
| oauth | auth | Empty Array - no providers configured | A list of plain OAuth providers. See auth/oauth properties to setup a new provider. |
|
||||
| ldap | auth | Empty Array - no providers configured | A list of LDAP providers. See auth/ldap properties to setup a new provider. |
|
||||
| provider_name | auth/oidc | | A unique provider name. This name must be unique throughout all authentication providers (even other types). |
|
||||
| display_name | auth/oidc | | The display name is shown at the login page (the login button). |
|
||||
| base_url | auth/oidc | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". |
|
||||
| client_id | auth/oidc | | The OAuth client id. |
|
||||
| client_secret | auth/oidc | | The OAuth client secret. |
|
||||
| extra_scopes | auth/oidc | | Extra scopes that should be used in the OpenID Connect authentication flow. |
|
||||
| field_map | auth/oidc | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. |
|
||||
| registration_enabled | auth/oidc | | If registration is enabled, new user accounts will created in WireGuard Portal. |
|
||||
| provider_name | auth/oauth | | A unique provider name. This name must be unique throughout all authentication providers (even other types). |
|
||||
| display_name | auth/oauth | | The display name is shown at the login page (the login button). |
|
||||
| base_url | auth/oauth | | The base_url is the URL identifier for the service. For example: "https://accounts.google.com". |
|
||||
| client_id | auth/oauth | | The OAuth client id. |
|
||||
| client_secret | auth/oauth | | The OAuth client secret. |
|
||||
| auth_url | auth/oauth | | The URL for the authentication endpoint. |
|
||||
| token_url | auth/oauth | | The URL for the token endpoint. |
|
||||
| redirect_url | auth/oauth | | The redirect URL. |
|
||||
| user_info_url | auth/oauth | | The URL for the user information endpoint. |
|
||||
| scopes | auth/oauth | | OAuth scopes. |
|
||||
| field_map | auth/oauth | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and is_admin. |
|
||||
| registration_enabled | auth/oauth | | If registration is enabled, new user accounts will created in WireGuard Portal. |
|
||||
| url | auth/ldap | | The LDAP server url. For example: ldap://srv-ad01.company.local:389 |
|
||||
| start_tls | auth/ldap | | Use STARTTLS to encrypt LDAP requests. |
|
||||
| cert_validation | auth/ldap | | Validate the LDAP server certificate. |
|
||||
| tls_certificate_path | auth/ldap | | A path to the TLS certificate. |
|
||||
| tls_key_path | auth/ldap | | A path to the TLS key. |
|
||||
| base_dn | auth/ldap | | The base DN for searching users. For example: DC=COMPANY,DC=LOCAL |
|
||||
| bind_user | auth/ldap | | The bind user. For example: company\\ldap_wireguard |
|
||||
| bind_pass | auth/ldap | | The bind password. |
|
||||
| field_map | auth/ldap | | Mapping of user fields. Internal fields: user_identifier, email, firstname, lastname, phone, department and memberof. |
|
||||
| login_filter | auth/ldap | | LDAP filters for users that should be allowed to log in. {{login_identifier}} will be replaced with the login username. |
|
||||
| admin_group | auth/ldap | | Users in this group are marked as administrators. |
|
||||
| synchronize | auth/ldap | | Periodically synchronize users (name, department, phone, status, ...) to the WireGuard Portal database. |
|
||||
| disable_missing | auth/ldap | | If synchronization is enabled, missing LDAP users will be disabled in WireGuard Portal. |
|
||||
| sync_filter | auth/ldap | | LDAP filters for users that should be synchronized to WireGuard Portal. |
|
||||
| registration_enabled | auth/ldap | | If registration is enabled, new user accounts will created in WireGuard Portal. |
|
||||
| debug | database | false | Debug database statements (log each statement). |
|
||||
| slow_query_threshold | database | | A threshold for slow database queries. If the threshold is exceeded, a warning message will be logged. |
|
||||
| type | database | sqlite | The database type. Allowed values: sqlite, mssql, mysql or postgres. |
|
||||
| dsn | database | data/sqlite.db | The database DSN. For example: user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local |
|
||||
| request_logging | web | false | Log all HTTP requests. |
|
||||
| external_url | web | http://localhost:8888 | The URL where a client can access WireGuard Portal. |
|
||||
| listening_address | web | :8888 | The listening port of the web server. |
|
||||
| session_identifier | web | wgPortalSession | The session identifier for the web frontend. |
|
||||
| session_secret | web | very_secret | The session secret for the web frontend. |
|
||||
| csrf_secret | web | extremely_secret | The CSRF secret. |
|
||||
| site_title | web | WireGuard Portal | The title that is shown in the web frontend. |
|
||||
| site_company_name | web | WireGuard Portal | The company name that is shown at the bottom of the web frontend. |
|
||||
|
||||
|
||||
## Upgrading from V1
|
||||
|
||||
> :warning: Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!
|
||||
|
||||
To start the upgrade process, start the wg-portal binary with the **-migrateFrom** parameter.
|
||||
The configuration (config.yml) for WireGuard Portal must be updated and valid before starting the upgrade.
|
||||
|
||||
To upgrade from a previous SQLite database, start wg-portal like:
|
||||
|
||||
```shell
|
||||
./wg-portal-amd64 -migrateFrom=old_wg_portal.db
|
||||
```
|
||||
|
||||
You can also specify the database type using the parameter **-migrateFromType**, supported types: mysql, mssql, postgres or sqlite.
|
||||
For example:
|
||||
|
||||
```shell
|
||||
./wg-portal-amd64 -migrateFromType=mysql -migrateFrom=user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
|
||||
```
|
||||
|
||||
The upgrade will transform the old, existing database and store the values in the new database specified in config.yml.
|
||||
Ensure that the new database does not contain any data!
|
||||
|
||||
|
||||
## V2 TODOs
|
||||
* Public REST API
|
||||
* Translations
|
||||
* Documentation
|
||||
* Audit UI
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
To build a standalone application, use the Makefile provided in the repository.
|
||||
Go version 1.20 or higher has to be installed to build WireGuard Portal.
|
||||
If you want to re-compile the frontend, NodeJS 18 and NPM >= 9 is required.
|
||||
|
||||
```shell
|
||||
# build the frontend
|
||||
make frontend
|
||||
|
||||
# build the binary
|
||||
make build
|
||||
```
|
||||
For the complete documentation visit [wgportal.org](https://wgportal.org).
|
||||
|
||||
## What is out of scope
|
||||
* Automatic generation or application of any `iptables` or `nftables` rules.
|
||||
* Support for operating systems other than linux.
|
||||
* Automatic import of private keys of an existing WireGuard setup.
|
||||
|
||||
* Automatic generation or application of any `iptables` or `nftables` rules.
|
||||
* Support for operating systems other than linux.
|
||||
* Automatic import of private keys of an existing WireGuard setup.
|
||||
|
||||
## Application stack
|
||||
|
||||
* [wgctrl-go](https://github.com/WireGuard/wgctrl-go) and [netlink](https://github.com/vishvananda/netlink) for interface handling
|
||||
* [Gin](https://github.com/gin-gonic/gin), HTTP web framework written in Go
|
||||
* [Bootstrap](https://getbootstrap.com/), for the HTML templates
|
||||
* [Vue.JS](https://vuejs.org/), for the frontend
|
||||
|
||||
* [wgctrl-go](https://github.com/WireGuard/wgctrl-go) and [netlink](https://github.com/vishvananda/netlink) for interface handling
|
||||
* [Bootstrap](https://getbootstrap.com/), for the HTML templates
|
||||
* [Vue.js](https://vuejs.org/), for the frontend
|
||||
|
||||
## License
|
||||
|
||||
* MIT License. [MIT](LICENSE.txt) or https://opensource.org/licenses/MIT
|
||||
* MIT License. [MIT](LICENSE.txt) or <https://opensource.org/licenses/MIT>
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Since the project was accepted by the Docker-Sponsored Open Source Program, the Docker image location has moved to [wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal).
|
||||
> Please update the Docker image from **h44z/wg-portal** to **wgportal/wg-portal**.
|
||||
|
33
SECURITY.md
Normal file
33
SECURITY.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Security Policy
|
||||
|
||||
If you believe you've found a security issue in one of the supported versions of *WireGuard Portal*, please report it to us as described below.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|--------------------|
|
||||
| v2.x | :white_check_mark: |
|
||||
| v1.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please do not report security vulnerabilities through public GitHub issues.
|
||||
|
||||
Instead, we encourage you to submit a report through GitHub [private vulnerability reporting](https://github.com/h44z/wg-portal/security).
|
||||
If you prefer to submit a report without logging in to GitHub, please email *info (at) wgportal.org*.
|
||||
We will respond as soon as possible, but as only two people currently maintain this project, we cannot guarantee specific response times.
|
||||
|
||||
We prefer all communications to be in English.
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
- Type of issue (e.g. SQL injection, cross-site scripting, ...)
|
||||
- Full paths of source file(s) related to the manifestation of the issue
|
||||
- The location of the affected source code (tag/branch/commit or direct URL)
|
||||
- Any special configuration required to reproduce the issue
|
||||
- Step-by-step instructions to reproduce the issue
|
||||
- Proof-of-concept or exploit code (if possible)
|
||||
- Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
Thank you for helping keep *WireGuard Portal* and its users safe!
|
@@ -7,11 +7,15 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/swaggo/swag"
|
||||
"github.com/swaggo/swag/gen"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var apiRootPath = "/internal/app/api"
|
||||
var apiDocPath = "core/assets/doc"
|
||||
var apiMkDocPath = "/docs/documentation/rest-api"
|
||||
|
||||
// this replaces the call to: swag init --propertyStrategy pascalcase --parseDependency --parseInternal --generalInfo base.go
|
||||
func main() {
|
||||
wd, err := os.Getwd() // should be the project root
|
||||
@@ -19,10 +23,9 @@ func main() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
apiBasePath := filepath.Join(wd, "/internal/app/api")
|
||||
apis := []string{"v0"}
|
||||
apiBasePath := filepath.Join(wd, apiRootPath)
|
||||
apis := []string{"v0", "v1"}
|
||||
|
||||
hasError := false
|
||||
for _, apiVersion := range apis {
|
||||
apiPath := filepath.Join(apiBasePath, apiVersion, "handlers")
|
||||
|
||||
@@ -33,16 +36,20 @@ func main() {
|
||||
|
||||
err := generateApi(apiBasePath, apiPath, apiVersion)
|
||||
if err != nil {
|
||||
hasError = true
|
||||
logrus.Errorf("failed to generate API docs for %s: %v", apiVersion, err)
|
||||
log.Fatalf("failed to generate API docs for %s: %v", apiVersion, err)
|
||||
}
|
||||
|
||||
// copy the latest version of the API docs for mkdocs
|
||||
if apiVersion == apis[len(apis)-1] {
|
||||
if err = copyDocForMkdocs(wd, apiBasePath, apiVersion); err != nil {
|
||||
log.Printf("failed to copy API docs for mkdocs: %v", err)
|
||||
} else {
|
||||
log.Println("Copied API docs " + apiVersion + " for mkdocs")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Generated swagger docs for API", apiVersion)
|
||||
}
|
||||
|
||||
if hasError {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func generateApi(basePath, apiPath, version string) error {
|
||||
@@ -51,7 +58,7 @@ func generateApi(basePath, apiPath, version string) error {
|
||||
Excludes: "",
|
||||
MainAPIFile: "base.go",
|
||||
PropNamingStrategy: swag.PascalCase,
|
||||
OutputDir: filepath.Join(basePath, "core/assets/doc"),
|
||||
OutputDir: filepath.Join(basePath, apiDocPath),
|
||||
OutputTypes: []string{"json", "yaml"},
|
||||
ParseVendor: false,
|
||||
ParseDependency: 3,
|
||||
@@ -68,3 +75,43 @@ func generateApi(basePath, apiPath, version string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyDocForMkdocs(workingDir, basePath, version string) error {
|
||||
srcPath := filepath.Join(basePath, apiDocPath, fmt.Sprintf("%s_swagger.yaml", version))
|
||||
dstPath := filepath.Join(workingDir, apiMkDocPath, "swagger.yaml")
|
||||
|
||||
// copy the file
|
||||
input, err := os.ReadFile(srcPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while reading swagger doc: %w", err)
|
||||
}
|
||||
|
||||
output, err := removeAuthorizeButton(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while removing authorize button: %w", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(dstPath, output, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while writing swagger doc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAuthorizeButton(input []byte) ([]byte, error) {
|
||||
var swagger map[string]any
|
||||
err := yaml.Unmarshal(input, &swagger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while unmarshalling swagger file: %w", err)
|
||||
}
|
||||
|
||||
delete(swagger, "securityDefinitions")
|
||||
|
||||
output, err := yaml.Marshal(&swagger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while marshalling swagger file: %w", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
@@ -2,41 +2,48 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
evbus "github.com/vardius/message-bus"
|
||||
"gorm.io/gorm/schema"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/adapters"
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
"github.com/h44z/wg-portal/internal/app/api/core"
|
||||
backendV0 "github.com/h44z/wg-portal/internal/app/api/v0/backend"
|
||||
handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers"
|
||||
backendV1 "github.com/h44z/wg-portal/internal/app/api/v1/backend"
|
||||
handlersV1 "github.com/h44z/wg-portal/internal/app/api/v1/handlers"
|
||||
"github.com/h44z/wg-portal/internal/app/audit"
|
||||
"github.com/h44z/wg-portal/internal/app/auth"
|
||||
"github.com/h44z/wg-portal/internal/app/configfile"
|
||||
"github.com/h44z/wg-portal/internal/app/mail"
|
||||
"github.com/h44z/wg-portal/internal/app/route"
|
||||
"github.com/h44z/wg-portal/internal/app/users"
|
||||
"github.com/h44z/wg-portal/internal/app/webhooks"
|
||||
"github.com/h44z/wg-portal/internal/app/wireguard"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/adapters"
|
||||
"github.com/h44z/wg-portal/internal/app"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
evbus "github.com/vardius/message-bus"
|
||||
)
|
||||
|
||||
// main entry point for WireGuard Portal
|
||||
func main() {
|
||||
ctx := internal.SignalAwareContext(context.Background(), syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
logrus.Infof("Starting WireGuard Portal V2...")
|
||||
logrus.Infof("WireGuard Portal version: %s", internal.Version)
|
||||
slog.Info("Starting WireGuard Portal V2...", "version", internal.Version)
|
||||
|
||||
cfg, err := config.GetConfig()
|
||||
internal.AssertNoError(err)
|
||||
setupLogging(cfg)
|
||||
internal.SetupLogging(cfg.Advanced.LogLevel, cfg.Advanced.LogPretty, cfg.Advanced.LogJson)
|
||||
|
||||
cfg.LogStartupValues()
|
||||
|
||||
dbEncryptedSerializer := app.NewGormEncryptedStringSerializer(cfg.Database.EncryptionPassphrase)
|
||||
schema.RegisterSerializer("encstr", dbEncryptedSerializer)
|
||||
rawDb, err := adapters.NewDatabase(cfg.Database)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
@@ -49,34 +56,45 @@ func main() {
|
||||
|
||||
mailer := adapters.NewSmtpMailRepo(cfg.Mail)
|
||||
|
||||
metricsServer := adapters.NewMetricsServer(cfg)
|
||||
|
||||
cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
shouldExit, err := app.HandleProgramArgs(cfg, rawDb)
|
||||
shouldExit, err := app.HandleProgramArgs(rawDb)
|
||||
switch {
|
||||
case shouldExit && err == nil:
|
||||
return
|
||||
case shouldExit && err != nil:
|
||||
logrus.Errorf("Failed to process program args: %v", err)
|
||||
case shouldExit:
|
||||
slog.Error("Failed to process program args", "error", err)
|
||||
os.Exit(1)
|
||||
case !shouldExit:
|
||||
default:
|
||||
internal.AssertNoError(err)
|
||||
}
|
||||
|
||||
queueSize := 100
|
||||
eventBus := evbus.New(queueSize)
|
||||
|
||||
auditManager := audit.NewManager(database)
|
||||
|
||||
auditRecorder, err := audit.NewAuditRecorder(cfg, eventBus, database)
|
||||
internal.AssertNoError(err)
|
||||
auditRecorder.StartBackgroundJobs(ctx)
|
||||
|
||||
userManager, err := users.NewUserManager(cfg, eventBus, database, database)
|
||||
internal.AssertNoError(err)
|
||||
userManager.StartBackgroundJobs(ctx)
|
||||
|
||||
authenticator, err := auth.NewAuthenticator(&cfg.Auth, eventBus, userManager)
|
||||
authenticator, err := auth.NewAuthenticator(&cfg.Auth, cfg.Web.ExternalUrl, eventBus, userManager)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
|
||||
internal.AssertNoError(err)
|
||||
wireGuardManager.StartBackgroundJobs(ctx)
|
||||
|
||||
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard)
|
||||
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, eventBus, database, wireGuard, metricsServer)
|
||||
internal.AssertNoError(err)
|
||||
statisticsCollector.StartBackgroundJobs(ctx)
|
||||
|
||||
cfgFileManager, err := configfile.NewConfigFileManager(cfg, eventBus, database, database, cfgFileSystem)
|
||||
internal.AssertNoError(err)
|
||||
@@ -84,62 +102,88 @@ func main() {
|
||||
mailManager, err := mail.NewMailManager(cfg, mailer, cfgFileManager, database, database)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
auditRecorder, err := audit.NewAuditRecorder(cfg, eventBus, database)
|
||||
internal.AssertNoError(err)
|
||||
auditRecorder.StartBackgroundJobs(ctx)
|
||||
|
||||
routeManager, err := route.NewRouteManager(cfg, eventBus, database)
|
||||
internal.AssertNoError(err)
|
||||
routeManager.StartBackgroundJobs(ctx)
|
||||
|
||||
backend, err := app.New(cfg, eventBus, authenticator, userManager, wireGuardManager,
|
||||
statisticsCollector, cfgFileManager, mailManager)
|
||||
webhookManager, err := webhooks.NewManager(cfg, eventBus)
|
||||
internal.AssertNoError(err)
|
||||
err = backend.Startup(ctx)
|
||||
webhookManager.StartBackgroundJobs(ctx)
|
||||
|
||||
err = app.Initialize(cfg, wireGuardManager, userManager)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
apiFrontend := handlersV0.NewRestApi(cfg, backend)
|
||||
validatorManager := validator.New()
|
||||
|
||||
webSrv, err := core.NewServer(cfg, apiFrontend)
|
||||
// region API v0 (SPA frontend)
|
||||
|
||||
apiV0Session := handlersV0.NewSessionWrapper(cfg)
|
||||
apiV0Auth := handlersV0.NewAuthenticationHandler(authenticator, apiV0Session)
|
||||
|
||||
apiV0BackendUsers := backendV0.NewUserService(cfg, userManager, wireGuardManager)
|
||||
apiV0BackendInterfaces := backendV0.NewInterfaceService(cfg, wireGuardManager, cfgFileManager)
|
||||
apiV0BackendPeers := backendV0.NewPeerService(cfg, wireGuardManager, cfgFileManager, mailManager)
|
||||
|
||||
apiV0EndpointAuth := handlersV0.NewAuthEndpoint(cfg, apiV0Auth, apiV0Session, validatorManager, authenticator)
|
||||
apiV0EndpointAudit := handlersV0.NewAuditEndpoint(cfg, apiV0Auth, auditManager)
|
||||
apiV0EndpointUsers := handlersV0.NewUserEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendUsers)
|
||||
apiV0EndpointInterfaces := handlersV0.NewInterfaceEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendInterfaces)
|
||||
apiV0EndpointPeers := handlersV0.NewPeerEndpoint(cfg, apiV0Auth, validatorManager, apiV0BackendPeers)
|
||||
apiV0EndpointConfig := handlersV0.NewConfigEndpoint(cfg, apiV0Auth)
|
||||
apiV0EndpointTest := handlersV0.NewTestEndpoint(apiV0Auth)
|
||||
|
||||
apiFrontend := handlersV0.NewRestApi(apiV0Session,
|
||||
apiV0EndpointAuth,
|
||||
apiV0EndpointAudit,
|
||||
apiV0EndpointUsers,
|
||||
apiV0EndpointInterfaces,
|
||||
apiV0EndpointPeers,
|
||||
apiV0EndpointConfig,
|
||||
apiV0EndpointTest,
|
||||
)
|
||||
|
||||
// endregion API v0 (SPA frontend)
|
||||
|
||||
// region API v1 (User REST API)
|
||||
|
||||
apiV1Auth := handlersV1.NewAuthenticationHandler(userManager)
|
||||
apiV1BackendUsers := backendV1.NewUserService(cfg, userManager)
|
||||
apiV1BackendPeers := backendV1.NewPeerService(cfg, wireGuardManager, userManager)
|
||||
apiV1BackendInterfaces := backendV1.NewInterfaceService(cfg, wireGuardManager)
|
||||
apiV1BackendProvisioning := backendV1.NewProvisioningService(cfg, userManager, wireGuardManager, cfgFileManager)
|
||||
apiV1BackendMetrics := backendV1.NewMetricsService(cfg, database, userManager, wireGuardManager)
|
||||
|
||||
apiV1EndpointUsers := handlersV1.NewUserEndpoint(apiV1Auth, validatorManager, apiV1BackendUsers)
|
||||
apiV1EndpointPeers := handlersV1.NewPeerEndpoint(apiV1Auth, validatorManager, apiV1BackendPeers)
|
||||
apiV1EndpointInterfaces := handlersV1.NewInterfaceEndpoint(apiV1Auth, validatorManager, apiV1BackendInterfaces)
|
||||
apiV1EndpointProvisioning := handlersV1.NewProvisioningEndpoint(apiV1Auth, validatorManager,
|
||||
apiV1BackendProvisioning)
|
||||
apiV1EndpointMetrics := handlersV1.NewMetricsEndpoint(apiV1Auth, validatorManager, apiV1BackendMetrics)
|
||||
|
||||
apiV1 := handlersV1.NewRestApi(
|
||||
apiV1EndpointUsers,
|
||||
apiV1EndpointPeers,
|
||||
apiV1EndpointInterfaces,
|
||||
apiV1EndpointProvisioning,
|
||||
apiV1EndpointMetrics,
|
||||
)
|
||||
|
||||
// endregion API v1 (User REST API)
|
||||
|
||||
webSrv, err := core.NewServer(cfg, apiFrontend, apiV1)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
go metricsServer.Run(ctx)
|
||||
go webSrv.Run(ctx, cfg.Web.ListeningAddress)
|
||||
|
||||
slog.Info("Application startup complete")
|
||||
|
||||
// wait until context gets cancelled
|
||||
<-ctx.Done()
|
||||
|
||||
logrus.Infof("Stopping WireGuard Portal")
|
||||
slog.Info("Stopping WireGuard Portal")
|
||||
|
||||
time.Sleep(5 * time.Second) // wait for (most) goroutines to finish gracefully
|
||||
|
||||
logrus.Infof("Stopped WireGuard Portal")
|
||||
}
|
||||
|
||||
func setupLogging(cfg *config.Config) {
|
||||
switch strings.ToLower(cfg.Advanced.LogLevel) {
|
||||
case "trace":
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
case "debug":
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
case "info", "information":
|
||||
logrus.SetLevel(logrus.InfoLevel)
|
||||
case "warn", "warning":
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
case "error":
|
||||
logrus.SetLevel(logrus.ErrorLevel)
|
||||
default:
|
||||
logrus.SetLevel(logrus.WarnLevel)
|
||||
}
|
||||
|
||||
switch {
|
||||
case cfg.Advanced.LogJson:
|
||||
logrus.SetFormatter(&logrus.JSONFormatter{
|
||||
PrettyPrint: cfg.Advanced.LogPretty,
|
||||
})
|
||||
case cfg.Advanced.LogPretty:
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: true,
|
||||
DisableColors: false,
|
||||
})
|
||||
}
|
||||
slog.Info("Stopped WireGuard Portal")
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
# More information about the configuration can be found in the documentation: https://wgportal.org/master/documentation/overview/
|
||||
|
||||
advanced:
|
||||
log_level: trace
|
||||
|
||||
@@ -11,19 +13,22 @@ web:
|
||||
external_url: http://localhost:8888
|
||||
request_logging: true
|
||||
|
||||
webhook:
|
||||
url: ""
|
||||
authentication: ""
|
||||
timeout: 10s
|
||||
|
||||
auth:
|
||||
callback_url_prefix: http://localhost:8888/api/v0
|
||||
ldap:
|
||||
- id: ldap1
|
||||
provider_name: company ldap
|
||||
display_name: Login with</br>LDAP
|
||||
url: ldap://ldap.yourcompany.local:389
|
||||
bind_user: ldap_wireguard@yourcompany.local
|
||||
bind_pass: super_Secret_PASSWORD
|
||||
base_dn: DC=YOURCOMPANY,DC=LOCAL
|
||||
login_filter: (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))
|
||||
admin_group: CN=WireGuardAdmins,OU=it,DC=YOURCOMPANY,DC=LOCAL
|
||||
synchronize: false
|
||||
sync_interval: 0 # sync disabled
|
||||
sync_filter: (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))
|
||||
registration_enabled: true
|
||||
oidc:
|
||||
@@ -46,4 +51,46 @@ auth:
|
||||
extra_scopes:
|
||||
- https://www.googleapis.com/auth/userinfo.email
|
||||
- https://www.googleapis.com/auth/userinfo.profile
|
||||
registration_enabled: true
|
||||
registration_enabled: true
|
||||
oauth:
|
||||
- id: google_plain_oauth
|
||||
provider_name: google3
|
||||
display_name: Login with</br>Google3
|
||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
||||
client_secret: A_CLIENT_SECRET
|
||||
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
||||
token_url: https://oauth2.googleapis.com/token
|
||||
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
field_map:
|
||||
email: email
|
||||
firstname: name
|
||||
user_identifier: sub
|
||||
is_admin: this-attribute-must-be-true
|
||||
registration_enabled: true
|
||||
- id: google_plain_oauth_with_groups
|
||||
provider_name: google4
|
||||
display_name: Login with</br>Google4
|
||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
||||
client_secret: A_CLIENT_SECRET
|
||||
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
||||
token_url: https://oauth2.googleapis.com/token
|
||||
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
- i-want-some-groups
|
||||
field_map:
|
||||
email: email
|
||||
firstname: name
|
||||
user_identifier: sub
|
||||
user_groups: groups
|
||||
admin_mapping:
|
||||
admin_value_regex: ^true$
|
||||
admin_group_regex: ^admin-group-name$
|
||||
registration_enabled: true
|
||||
log_user_info: true
|
5
ct.yaml
Normal file
5
ct.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
# See https://github.com/helm/chart-testing#configuration
|
||||
remote: origin
|
||||
chart-dirs: deploy
|
||||
target-branch: master
|
||||
validate-maintainers: false
|
23
deploy/helm/.helmignore
Normal file
23
deploy/helm/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
25
deploy/helm/Chart.yaml
Normal file
25
deploy/helm/Chart.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
apiVersion: v2
|
||||
name: wg-portal
|
||||
description: WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
|
||||
# Version is set to ensure compatibility with the chart's Ingress resource.
|
||||
kubeVersion: ">=1.19.0"
|
||||
type: application
|
||||
home: https://wgportal.org
|
||||
icon: https://wgportal.org/latest/assets/images/logo.svg
|
||||
sources:
|
||||
- https://github.com/h44z/wg-portal
|
||||
|
||||
annotations:
|
||||
artifacthub.io/category: networking
|
||||
artifacthub.io/changes: ""
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.7.1
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v2"
|
124
deploy/helm/README.md
Normal file
124
deploy/helm/README.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# wg-portal
|
||||
|
||||
  
|
||||
|
||||
WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
|
||||
|
||||
**Homepage:** <https://wgportal.org>
|
||||
|
||||
## Source Code
|
||||
|
||||
* <https://github.com/h44z/wg-portal>
|
||||
|
||||
## Requirements
|
||||
|
||||
Kubernetes: `>=1.19.0`
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart with the release name `wg-portal`:
|
||||
|
||||
```console
|
||||
helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal
|
||||
```
|
||||
|
||||
This command deploy wg-portal on the Kubernetes cluster in the default configuration.
|
||||
The [Values](#values) section lists the parameters that can be configured during installation.
|
||||
|
||||
## Values
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| nameOverride | string | `""` | Partially override resource names (adds suffix) |
|
||||
| fullnameOverride | string | `""` | Fully override resource names |
|
||||
| extraDeploy | list | `[]` | Array of extra objects to deploy with the release |
|
||||
| config.advanced | tpl/object | `{}` | [Advanced configuration](https://wgportal.org/latest/documentation/configuration/overview/#advanced) options. |
|
||||
| config.auth | tpl/object | `{}` | [Auth configuration](https://wgportal.org/latest/documentation/configuration/overview/#auth) options. |
|
||||
| config.core | tpl/object | `{}` | [Core configuration](https://wgportal.org/latest/documentation/configuration/overview/#core) options.<br> If external admins in `auth` are defined and there are no `admin_user` and `admin_password` defined here, the default admin account will be disabled. |
|
||||
| config.database | tpl/object | `{}` | [Database configuration](https://wgportal.org/latest/documentation/configuration/overview/#database) options |
|
||||
| config.mail | tpl/object | `{}` | [Mail configuration](https://wgportal.org/latest/documentation/configuration/overview/#mail) options |
|
||||
| config.statistics | tpl/object | `{}` | [Statistics configuration](https://wgportal.org/latest/documentation/configuration/overview/#statistics) options |
|
||||
| config.web | tpl/object | `{}` | [Web configuration](https://wgportal.org/latest/documentation/configuration/overview/#web) options.<br> `listening_address` will be set automatically from `service.web.port`. `external_url` is required to enable ingress and certificate resources. |
|
||||
| revisionHistoryLimit | string | `10` | The number of old ReplicaSets to retain to allow rollback. |
|
||||
| workloadType | string | `"Deployment"` | Workload type - `Deployment` or `StatefulSet` |
|
||||
| strategy | object | `{"type":"RollingUpdate"}` | Update strategy for the workload Valid values are: `RollingUpdate` or `Recreate` for Deployment, `RollingUpdate` or `OnDelete` for StatefulSet |
|
||||
| image.repository | string | `"ghcr.io/h44z/wg-portal"` | Image repository |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
||||
| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion |
|
||||
| imagePullSecrets | list | `[]` | Image pull secrets |
|
||||
| podAnnotations | tpl/object | `{}` | Extra annotations to add to the pod |
|
||||
| podLabels | object | `{}` | Extra labels to add to the pod |
|
||||
| podSecurityContext | object | `{}` | Pod Security Context |
|
||||
| securityContext.capabilities.add | list | `["NET_ADMIN"]` | Add capabilities to the container |
|
||||
| initContainers | tpl/list | `[]` | Pod init containers |
|
||||
| sidecarContainers | tpl/list | `[]` | Pod sidecar containers |
|
||||
| dnsPolicy | string | `"ClusterFirst"` | Set DNS policy for the pod. Valid values are `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`. |
|
||||
| restartPolicy | string | `"Always"` | Restart policy for all containers within the pod. Valid values are `Always`, `OnFailure` or `Never`. |
|
||||
| hostNetwork | string | `false`. | Use the host's network namespace. |
|
||||
| resources | object | `{}` | Resources requests and limits |
|
||||
| command | list | `[]` | Overwrite pod command |
|
||||
| args | list | `[]` | Additional pod arguments |
|
||||
| env | tpl/list | `[]` | Additional environment variables |
|
||||
| envFrom | tpl/list | `[]` | Additional environment variables from a secret or configMap |
|
||||
| livenessProbe | object | `{}` | Liveness probe configuration |
|
||||
| readinessProbe | object | `{}` | Readiness probe configuration |
|
||||
| startupProbe | object | `{}` | Startup probe configuration |
|
||||
| volumes | tpl/list | `[]` | Additional volumes |
|
||||
| volumeMounts | tpl/list | `[]` | Additional volumeMounts |
|
||||
| nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node Selector configuration |
|
||||
| tolerations | list | `[]` | Tolerations configuration |
|
||||
| affinity | object | `{}` | Affinity configuration |
|
||||
| service.mixed.enabled | bool | `false` | Whether to create a single service for the web and wireguard interfaces |
|
||||
| service.mixed.type | string | `"LoadBalancer"` | Service type |
|
||||
| service.web.annotations | object | `{}` | Annotations for the web service |
|
||||
| service.web.type | string | `"ClusterIP"` | Web service type |
|
||||
| service.web.port | int | `8888` | Web service port Used for the web interface listener |
|
||||
| service.web.appProtocol | string | `"http"` | Web service appProtocol. Will be auto set to `https` if certificate is enabled. |
|
||||
| service.wireguard.annotations | object | `{}` | Annotations for the WireGuard service |
|
||||
| service.wireguard.type | string | `"LoadBalancer"` | Wireguard service type |
|
||||
| service.wireguard.ports | list | `[51820]` | Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. |
|
||||
| service.metrics.port | int | `8787` | |
|
||||
| ingress.enabled | bool | `false` | Specifies whether an ingress resource should be created |
|
||||
| ingress.className | string | `""` | Ingress class name |
|
||||
| ingress.annotations | object | `{}` | Ingress annotations |
|
||||
| ingress.tls | bool | `false` | Ingress TLS configuration. Enable certificate resource or add ingress annotation to create required secret |
|
||||
| certificate.enabled | bool | `false` | Specifies whether a certificate resource should be created. If enabled, certificate will be used for the web. |
|
||||
| certificate.issuer.name | string | `""` | Certificate issuer name |
|
||||
| certificate.issuer.kind | string | `""` | Certificate issuer kind (ClusterIssuer or Issuer) |
|
||||
| certificate.issuer.group | string | `"cert-manager.io"` | Certificate issuer group |
|
||||
| certificate.duration | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.renewBefore | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.commonName | string | `""` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.emailAddresses | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.ipAddresses | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.keystores | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.privateKey | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.secretTemplate | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.subject | object | `{}` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.uris | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| certificate.usages | list | `[]` | Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources) |
|
||||
| persistence.enabled | bool | `false` | Specifies whether an persistent volume should be created |
|
||||
| persistence.annotations | object | `{}` | Persistent Volume Claim annotations |
|
||||
| persistence.storageClass | string | `""` | Persistent Volume storage class. If undefined (the default) cluster's default provisioner will be used. |
|
||||
| persistence.accessMode | string | `"ReadWriteOnce"` | Persistent Volume Access Mode |
|
||||
| persistence.size | string | `"1Gi"` | Persistent Volume size |
|
||||
| persistence.volumeName | string | `""` | Persistent Volume Name (optional) |
|
||||
| serviceAccount.create | bool | `true` | Specifies whether a service account should be created |
|
||||
| serviceAccount.annotations | object | `{}` | Service account annotations |
|
||||
| serviceAccount.automount | bool | `false` | Automatically mount a ServiceAccount's API credentials |
|
||||
| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template |
|
||||
| monitoring.enabled | bool | `false` | Enable Prometheus monitoring. |
|
||||
| monitoring.apiVersion | string | `"monitoring.coreos.com/v1"` | API version of the Prometheus resource. Use `azmonitoring.coreos.com/v1` for Azure Managed Prometheus. |
|
||||
| monitoring.kind | string | `"PodMonitor"` | Kind of the Prometheus resource. Could be `PodMonitor` or `ServiceMonitor`. |
|
||||
| monitoring.labels | object | `{}` | Resource labels. |
|
||||
| monitoring.annotations | object | `{}` | Resource annotations. |
|
||||
| monitoring.interval | string | `1m` | Interval at which metrics should be scraped. If not specified `config.statistics.data_collection_interval` interval is used. |
|
||||
| monitoring.metricRelabelings | list | `[]` | Relabelings to samples before ingestion. |
|
||||
| monitoring.relabelings | list | `[]` | Relabelings to samples before scraping. |
|
||||
| monitoring.scrapeTimeout | string | `""` | Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used. |
|
||||
| monitoring.jobLabel | string | `""` | The label to use to retrieve the job name from. |
|
||||
| monitoring.podTargetLabels | object | `{}` | Transfers labels on the Kubernetes Pod onto the target. |
|
||||
| monitoring.dashboard.enabled | bool | `false` | Enable Grafana dashboard. |
|
||||
| monitoring.dashboard.annotations | object | `{}` | Annotations for the dashboard ConfigMap. |
|
||||
| monitoring.dashboard.labels | object | `{}` | Additional labels for the dashboard ConfigMap. |
|
||||
| monitoring.dashboard.namespace | string | `""` | Dashboard ConfigMap namespace Overrides the namespace for the dashboard ConfigMap. |
|
27
deploy/helm/README.md.gotmpl
Normal file
27
deploy/helm/README.md.gotmpl
Normal file
@@ -0,0 +1,27 @@
|
||||
{{ template "chart.header" . }}
|
||||
{{ template "chart.deprecationWarning" . }}
|
||||
|
||||
{{ template "chart.badgesSection" . }}
|
||||
|
||||
{{ template "chart.description" . }}
|
||||
|
||||
{{ template "chart.homepageLine" . }}
|
||||
|
||||
{{ template "chart.maintainersSection" . }}
|
||||
|
||||
{{ template "chart.sourcesSection" . }}
|
||||
|
||||
{{ template "chart.requirementsSection" . }}
|
||||
|
||||
## Installing the Chart
|
||||
|
||||
To install the chart with the release name `wg-portal`:
|
||||
|
||||
```console
|
||||
helm install wg-portal oci://ghcr.io/h44z/charts/wg-portal
|
||||
```
|
||||
|
||||
This command deploy wg-portal on the Kubernetes cluster in the default configuration.
|
||||
The [Values](#values) section lists the parameters that can be configured during installation.
|
||||
|
||||
{{ template "chart.valuesSection" . }}
|
917
deploy/helm/files/dashboard.json
Normal file
917
deploy/helm/files/dashboard.json
Normal file
@@ -0,0 +1,917 @@
|
||||
{
|
||||
"annotations": {},
|
||||
"description": "WireGuard Portal Dashboard",
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"default": false,
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "opacity",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": 3600000,
|
||||
"lineInterpolation": "smooth",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"disableTextWrap": false,
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum by (instance, interface) (wireguard_interface_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"})",
|
||||
"fullMetaSearch": false,
|
||||
"hide": false,
|
||||
"includeNullMetadata": true,
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Received {{interface}}",
|
||||
"range": true,
|
||||
"refId": "A",
|
||||
"useBackend": false
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by (instance, interface) (wireguard_interface_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"})",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"legendFormat": "Sent {{interface}}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Interface Bytes Total",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"default": false,
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "opacity",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": 3600000,
|
||||
"lineInterpolation": "smooth",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by (instance, interface) (rate(wireguard_interface_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Received {{interface}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by (instance, interface) (rate(wireguard_interface_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Sent {{interface}}",
|
||||
"range": true,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Interface Bandwidth",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"default": false,
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "opacity",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": 3600000,
|
||||
"lineInterpolation": "smooth",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 16,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by (name, instance, interface) (rate(wireguard_peer_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"interval": "$interval",
|
||||
"legendFormat": "{{name}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Peer Receive Bandwidth",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"default": false,
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "opacity",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": 3600000,
|
||||
"lineInterpolation": "smooth",
|
||||
"lineStyle": {
|
||||
"fill": "solid"
|
||||
},
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 9
|
||||
},
|
||||
"id": 17,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "right",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "sum by (instance, interface, name) (rate(wireguard_peer_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__rate_interval]))",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
"interval": "$interval",
|
||||
"legendFormat": "{{name}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Peer Transmit Bandwidth",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"default": false,
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"fillOpacity": 60,
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineWidth": 1
|
||||
},
|
||||
"fieldMinMax": false,
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "bool_yes_no"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 11,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 18
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"colWidth": 0.85,
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": false
|
||||
},
|
||||
"rowHeight": 0.85,
|
||||
"showValue": "never",
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum by(name) (wireguard_peer_up{instance=\"$instance\", interface=~\"$interface\"})",
|
||||
"instant": false,
|
||||
"interval": "$interval",
|
||||
"legendFormat": "{{name}}",
|
||||
"range": true,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Peer Connection History",
|
||||
"type": "status-history"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"default": false,
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic-by-name"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto",
|
||||
"wrapText": false
|
||||
},
|
||||
"filterable": false,
|
||||
"inspect": false
|
||||
},
|
||||
"fieldMinMax": false,
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "dark-red",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byRegexp",
|
||||
"options": "/(Time|instance|interface|name)\\s\\d*/"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hidden",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byRegexp",
|
||||
"options": "/Received|Transmitted/"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "bytes"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Last Handshake"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "s"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Connected"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "mappings",
|
||||
"value": [
|
||||
{
|
||||
"options": {
|
||||
"0": {
|
||||
"color": "red",
|
||||
"index": 0,
|
||||
"text": "No"
|
||||
},
|
||||
"1": {
|
||||
"color": "green",
|
||||
"index": 1,
|
||||
"text": "Yes"
|
||||
}
|
||||
},
|
||||
"type": "value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "custom.cellOptions",
|
||||
"value": {
|
||||
"type": "color-text"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 14,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 29
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"enablePagination": false,
|
||||
"fields": [],
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true,
|
||||
"sortBy": [
|
||||
{
|
||||
"desc": true,
|
||||
"displayName": "Sent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"disableTextWrap": false,
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum by(id, instance, interface, name, addresses) (increase(wireguard_peer_received_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__range]))",
|
||||
"format": "table",
|
||||
"fullMetaSearch": false,
|
||||
"hide": false,
|
||||
"includeNullMetadata": true,
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "A",
|
||||
"useBackend": false
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"disableTextWrap": false,
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum by(id, instance, interface, name) (increase(wireguard_peer_sent_bytes_total{instance=\"$instance\", interface=~\"$interface\"}[$__range]))",
|
||||
"format": "table",
|
||||
"fullMetaSearch": false,
|
||||
"includeNullMetadata": true,
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "__auto",
|
||||
"range": true,
|
||||
"refId": "B",
|
||||
"useBackend": false
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "time()-sum(wireguard_peer_last_handshake_seconds{instance=\"$instance\", interface=~\"$interface\"}) by(id, instance, interface, name) ",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "__auto",
|
||||
"range": false,
|
||||
"refId": "C"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "sum(wireguard_peer_up{instance=\"$instance\", interface=~\"$interface\"}) by(id, instance, interface, name) ",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"legendFormat": "__auto",
|
||||
"range": false,
|
||||
"refId": "D"
|
||||
}
|
||||
],
|
||||
"title": "Peer Info",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "joinByField",
|
||||
"options": {
|
||||
"byField": "id",
|
||||
"mode": "outer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organize",
|
||||
"options": {
|
||||
"excludeByName": {
|
||||
"Time 1": false,
|
||||
"Time 2": false,
|
||||
"Time 3": false,
|
||||
"Time 4": false
|
||||
},
|
||||
"includeByName": {},
|
||||
"indexByName": {
|
||||
"Time 1": 8,
|
||||
"Time 2": 9,
|
||||
"Time 3": 10,
|
||||
"Time 4": 11,
|
||||
"Value #A": 4,
|
||||
"Value #B": 5,
|
||||
"Value #C": 6,
|
||||
"Value #D": 7,
|
||||
"addresses": 2,
|
||||
"id": 3,
|
||||
"instance 1": 12,
|
||||
"instance 2": 13,
|
||||
"instance 3": 16,
|
||||
"instance 4": 19,
|
||||
"interface 1": 0,
|
||||
"interface 2": 14,
|
||||
"interface 3": 17,
|
||||
"interface 4": 20,
|
||||
"name 1": 1,
|
||||
"name 2": 15,
|
||||
"name 3": 18,
|
||||
"name 4": 21
|
||||
},
|
||||
"renameByName": {
|
||||
"Value #A": "Received",
|
||||
"Value #B": "Transmitted",
|
||||
"Value #C": "Last Handshake",
|
||||
"Value #D": "Connected",
|
||||
"addresses": "IP Addresses",
|
||||
"id": "Public Key",
|
||||
"interface": "Interface",
|
||||
"interface 1": "Interface",
|
||||
"name": "Name",
|
||||
"name 1": "Name"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "table"
|
||||
}
|
||||
],
|
||||
"refresh": "1m",
|
||||
"tags": [
|
||||
"wireguard",
|
||||
"vpn"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "Prometheus",
|
||||
"multi": false,
|
||||
"name": "datasource",
|
||||
"options": [],
|
||||
"query": "prometheus",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "datasource"
|
||||
},
|
||||
{
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"definition": "label_values(wireguard_interface_sent_bytes_total,instance)",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "Instance",
|
||||
"multi": false,
|
||||
"name": "instance",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(wireguard_interface_sent_bytes_total,instance)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": {},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${datasource}"
|
||||
},
|
||||
"definition": "label_values(wireguard_interface_sent_bytes_total{instance=\"$instance\"},interface)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Interface",
|
||||
"multi": true,
|
||||
"name": "interface",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(wireguard_interface_sent_bytes_total{instance=\"$instance\"},interface)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
"text": "2m",
|
||||
"value": "2m"
|
||||
},
|
||||
"description": "",
|
||||
"label": "Step Interval",
|
||||
"name": "interval",
|
||||
"options": [
|
||||
{
|
||||
"selected": false,
|
||||
"text": "30s",
|
||||
"value": "30s"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "1m",
|
||||
"value": "1m"
|
||||
},
|
||||
{
|
||||
"selected": true,
|
||||
"text": "2m",
|
||||
"value": "2m"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "5m",
|
||||
"value": "5m"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "10m",
|
||||
"value": "10m"
|
||||
}
|
||||
],
|
||||
"query": "30s,1m,2m,5m,10m",
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-12h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "WireGuard Portal",
|
||||
"uid": "wireguard-portal",
|
||||
"weekStart": ""
|
||||
}
|
24
deploy/helm/templates/NOTES.txt
Normal file
24
deploy/helm/templates/NOTES.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- $serviceName := printf "%s-web" (include "wg-portal.fullname" .) -}}
|
||||
{{- $servicePort := .Values.service.web.port }}
|
||||
|
||||
{{- if not .Values.ingress.enabled }}
|
||||
Get the application URL by running these commands:
|
||||
{{- if eq "ClusterIP" .Values.service.web.type }}
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward svc/{{ $serviceName }} {{ $servicePort }}:{{ $servicePort }}
|
||||
|
||||
Visit http://127.0.0.1:{{ $servicePort }} to use your application
|
||||
|
||||
{{- else if eq "LoadBalancer" .Values.service.web.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ $serviceName }}'
|
||||
export SERVICE_IP=$(kubectl get --namespace {{ .Release.Namespace }} svc {{ $serviceName }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ $servicePort }}
|
||||
|
||||
{{- else if eq "NodePort" .Values.service.web.type }}
|
||||
export NODE_IP=$(kubectl get --namespace {{ .Release.Namespace }} nodes -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} svc {{ $serviceName }} -o jsonpath="{.spec.ports[0].nodePort}" )
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
Visit http{{ if .Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}{{ .Values.ingress.path }} to use your application
|
||||
{{- end }}
|
132
deploy/helm/templates/_helpers.tpl
Normal file
132
deploy/helm/templates/_helpers.tpl
Normal file
@@ -0,0 +1,132 @@
|
||||
{{/*
|
||||
Expand the name of the chart
|
||||
*/}}
|
||||
{{- define "wg-portal.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "wg-portal.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label
|
||||
*/}}
|
||||
{{- define "wg-portal.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "wg-portal.labels" -}}
|
||||
helm.sh/chart: {{ include "wg-portal.chart" . }}
|
||||
{{ include "wg-portal.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "wg-portal.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "wg-portal.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "wg-portal.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "wg-portal.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Disables default admin credentials
|
||||
If external auth is enabled and has admin group mappings,
|
||||
the admin_user will be set to blank (disabled).
|
||||
*/}}
|
||||
{{- define "wg-portal.admin" -}}
|
||||
{{- $externalAdmin := false -}}
|
||||
{{- with .Values.config.auth -}}
|
||||
{{- range (default list .ldap) -}}
|
||||
{{- if hasKey . "admin_group" -}}
|
||||
{{- $externalAdmin = true -}}
|
||||
{{- end -}}
|
||||
{{- end }}
|
||||
{{- range (concat (default list .oidc) (default list .oauth)) -}}
|
||||
{{- if hasKey .field_map "is_admin" -}}
|
||||
{{- $externalAdmin = true -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- if $externalAdmin -}}
|
||||
admin_user: ""
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Define PersistentVolumeClaim spec
|
||||
*/}}
|
||||
{{- define "wg-portal.pvc" -}}
|
||||
accessModes:
|
||||
- {{ .Values.persistence.accessMode }}
|
||||
resources:
|
||||
requests:
|
||||
storage: {{ .Values.persistence.size | quote }}
|
||||
{{- with .Values.persistence.storageClass }}
|
||||
storageClassName: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.persistence.volumeName }}
|
||||
volumeName: {{ . }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Define hostname
|
||||
*/}}
|
||||
{{- define "wg-portal.hostname" -}}
|
||||
{{- if .Values.config.web.external_url -}}
|
||||
{{- (urlParse (tpl .Values.config.web.external_url .)).hostname -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{/*
|
||||
wg-portal.util.merge will merge two YAML templates or dict with template and output the result.
|
||||
This takes an array of three values:
|
||||
- the top context
|
||||
- the template name or dict of the overrides (destination)
|
||||
- the template name of the base (source)
|
||||
{{- include "wg-portal.util.merge" (list $ .Values.podLabels "wg-portal.selectorLabels") }}
|
||||
{{- include "wg-portal.util.merge" (list $ "wg-portal.destTemplate" "wg-portal.sourceTemplate") }}
|
||||
*/}}
|
||||
{{- define "wg-portal.util.merge" -}}
|
||||
{{- $top := first . -}}
|
||||
{{- $overrides := index . 1 -}}
|
||||
{{- $base := fromYaml (include (index . 2) $top) | default (dict) -}}
|
||||
{{- if kindIs "string" $overrides -}}
|
||||
{{- $overrides = fromYaml (include $overrides $top) | default (dict) -}}
|
||||
{{- end -}}
|
||||
{{- toYaml (merge $overrides $base) -}}
|
||||
{{- end -}}
|
119
deploy/helm/templates/_pod.tpl
Normal file
119
deploy/helm/templates/_pod.tpl
Normal file
@@ -0,0 +1,119 @@
|
||||
{{- define "wg-portal.podTemplate" -}}
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
kubectl.kubernetes.io/default-container: {{ .Chart.Name }}
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
||||
{{- end }}
|
||||
labels: {{- include "wg-portal.util.merge" (list $ .Values.podLabels "wg-portal.selectorLabels") | nindent 4 }}
|
||||
spec:
|
||||
{{- with .Values.affinity }}
|
||||
affinity: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
|
||||
containers:
|
||||
{{- with .Values.sidecarContainers }}
|
||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
||||
{{- end }}
|
||||
- name: {{ .Chart.Name }}
|
||||
image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag}}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- with .Values.command }}
|
||||
command: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.args }}
|
||||
args: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.env }}
|
||||
env: {{- tpl (toYaml .) $ | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.envFrom }}
|
||||
envFrom: {{- tpl (toYaml .) $ | nindent 8 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: {{ .Values.service.metrics.port}}
|
||||
protocol: TCP
|
||||
- name: web
|
||||
containerPort: {{ .Values.service.web.port }}
|
||||
protocol: TCP
|
||||
{{- range $index, $port := .Values.service.wireguard.ports }}
|
||||
- name: wg{{ $index }}
|
||||
containerPort: {{ $port }}
|
||||
protocol: UDP
|
||||
{{- end }}
|
||||
{{- with .Values.livenessProbe }}
|
||||
livenessProbe: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.readinessProbe }}
|
||||
readinessProbe: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.startupProbe }}
|
||||
startupProbe: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.securityContext }}
|
||||
securityContext: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.resources}}
|
||||
resources: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /app/config
|
||||
readOnly: true
|
||||
- name: data
|
||||
mountPath: /app/data
|
||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }}
|
||||
- name: certs
|
||||
mountPath: /app/certs
|
||||
{{- end }}
|
||||
{{- with .Values.volumeMounts }}
|
||||
{{- tpl (toYaml .) $ | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.dnsPolicy }}
|
||||
dnsPolicy: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.hostNetwork }}
|
||||
hostNetwork: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.initContainers }}
|
||||
initContainers: {{- tpl (toYaml .) $ | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.restartPolicy }}
|
||||
restartPolicy: {{ . }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "wg-portal.serviceAccountName" . }}
|
||||
{{- with .Values.podSecurityContext }}
|
||||
securityContext: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config
|
||||
secret:
|
||||
secretName: {{ include "wg-portal.fullname" . }}
|
||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }}
|
||||
- name: certs
|
||||
secret:
|
||||
secretName: {{ include "wg-portal.fullname" . }}-tls
|
||||
{{- end }}
|
||||
{{- if not .Values.persistence.enabled }}
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
{{- else if eq .Values.workloadType "Deployment" }}
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ include "wg-portal.fullname" . }}
|
||||
{{- end }}
|
||||
{{- with .Values.volumes }}
|
||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
66
deploy/helm/templates/_service.tpl
Normal file
66
deploy/helm/templates/_service.tpl
Normal file
@@ -0,0 +1,66 @@
|
||||
{{/*
|
||||
Define the service template
|
||||
{{- include "wg-portal.service" (dict "context" $ "scope" .Values.service.<name> "ports" list "name" "<name>") -}}
|
||||
*/}}
|
||||
{{- define "wg-portal.service.tpl" -}}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
{{- with .scope.annotations }}
|
||||
annotations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels: {{- include "wg-portal.labels" .context | nindent 4 }}
|
||||
name: {{ include "wg-portal.fullname" .context }}{{ ternary "" (printf "-%s" .name) (empty .name) }}
|
||||
spec:
|
||||
{{- with .scope.clusterIP }}
|
||||
clusterIP: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.externalIPs }}
|
||||
externalIPs: {{ toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .scope.externalName }}
|
||||
externalName: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.externalTrafficPolicy }}
|
||||
externalTrafficPolicy: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.healthCheckNodePort }}
|
||||
healthCheckNodePort: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.loadBalancerIP }}
|
||||
loadBalancerIP: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.loadBalancerSourceRanges }}
|
||||
loadBalancerSourceRanges: {{ toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
ports: {{- toYaml .ports | nindent 4 }}
|
||||
{{- with .scope.publishNotReadyAddresses }}
|
||||
publishNotReadyAddresses: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.sessionAffinity }}
|
||||
sessionAffinity: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .scope.sessionAffinityConfig }}
|
||||
sessionAffinityConfig: {{ toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .scope.topologyKeys }}
|
||||
topologyKeys: {{ toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .scope.type }}
|
||||
type: {{ . }}
|
||||
{{- end }}
|
||||
selector: {{- include "wg-portal.selectorLabels" .context | nindent 4 }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Define the service port template for the web port
|
||||
*/}}
|
||||
{{- define "wg-portal.service.webPort" -}}
|
||||
name: web
|
||||
port: {{ .Values.service.web.port }}
|
||||
protocol: TCP
|
||||
targetPort: web
|
||||
{{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.Version }}
|
||||
appProtocol: {{ ternary "https" .Values.service.web.appProtocol .Values.certificate.enabled }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
54
deploy/helm/templates/certificate.yaml
Normal file
54
deploy/helm/templates/certificate.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
{{/* https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources */}}
|
||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) -}}
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
spec:
|
||||
secretName: {{ include "wg-portal.fullname" . }}-tls
|
||||
{{- with .Values.certificate.secretTemplate }}
|
||||
secretTemplate: {{ toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.privateKey }}
|
||||
privateKey: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.keystores }}
|
||||
keystores: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.duration }}
|
||||
duration: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.renewBefore }}
|
||||
renewBefore: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.usages }}
|
||||
usages: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.subject }}
|
||||
subject: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.commonName }}
|
||||
commonName: {{ . }}
|
||||
{{- end }}
|
||||
dnsNames:
|
||||
- {{ include "wg-portal.hostname" . }}
|
||||
{{- with .Values.certificate.uris }}
|
||||
uris: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.emailAddresses }}
|
||||
emailAddresses: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.ipAddresses }}
|
||||
ipAddresses: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- with .Values.certificate.otherNames }}
|
||||
otherNames: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
issuerRef:
|
||||
{{- with .Values.certificate.issuer.group }}
|
||||
group: {{ . }}
|
||||
{{- end }}
|
||||
kind: {{ .Values.certificate.issuer.kind }}
|
||||
name: {{ .Values.certificate.issuer.name }}
|
||||
{{- end -}}
|
14
deploy/helm/templates/cm-dashboards.yaml
Normal file
14
deploy/helm/templates/cm-dashboards.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
{{- with .Values.monitoring.dashboard -}}
|
||||
{{- if .enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
{{- with .annotations }}
|
||||
annotations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels: {{- include "wg-portal.util.merge" (list $ .labels "wg-portal.labels") | nindent 4 }}
|
||||
name: {{ printf "grafana-dashboards-%s" (include "wg-portal.fullname" $) }}
|
||||
namespace: {{ default $.Release.Namespace .namespace }}
|
||||
data: {{ ($.Files.Glob "files/dashboard.json").AsConfig | nindent 2 }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
17
deploy/helm/templates/deployment.yaml
Normal file
17
deploy/helm/templates/deployment.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
{{- if eq .Values.workloadType "Deployment" -}}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- with .Values.revisionHistoryLimit }}
|
||||
revisionHistoryLimit: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.strategy }}
|
||||
strategy: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels: {{- include "wg-portal.selectorLabels" . | nindent 6 }}
|
||||
template: {{- include "wg-portal.podTemplate" . | nindent 4 }}
|
||||
{{- end -}}
|
4
deploy/helm/templates/extras.yaml
Normal file
4
deploy/helm/templates/extras.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
{{- range .Values.extraDeploy -}}
|
||||
{{- tpl (toYaml .) $ }}
|
||||
---
|
||||
{{- end -}}
|
30
deploy/helm/templates/ingress.yaml
Normal file
30
deploy/helm/templates/ingress.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
{{- $hostname := include "wg-portal.hostname" . -}}
|
||||
{{- if and .Values.ingress.enabled $hostname -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
spec:
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
rules:
|
||||
- host: {{ $hostname }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ default "/" (urlParse (tpl .Values.config.web.external_url .)).path }}
|
||||
pathType: {{ default "ImplementationSpecific" .pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
port:
|
||||
name: web
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
- hosts:
|
||||
- {{ $hostname | quote }}
|
||||
secretName: {{ include "wg-portal.fullname" . }}-tls
|
||||
{{- end }}
|
||||
{{- end }}
|
44
deploy/helm/templates/monitoring.yaml
Normal file
44
deploy/helm/templates/monitoring.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
{{- with .Values.monitoring -}}
|
||||
{{- if and .enabled ($.Capabilities.APIVersions.Has .apiVersion) -}}
|
||||
{{- $endpointsKey := (eq .kind "PodMonitor") | ternary "podMetricsEndpoints" "endpoints" -}}
|
||||
apiVersion: {{ .apiVersion }}
|
||||
kind: {{ .kind }}
|
||||
metadata:
|
||||
{{- with .annotations }}
|
||||
annotations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels: {{- include "wg-portal.util.merge" (list $ .labels "wg-portal.labels") | nindent 4 }}
|
||||
name: {{ include "wg-portal.fullname" $ }}
|
||||
spec:
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ $.Release.Namespace }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "wg-portal.selectorLabels" $ | nindent 6 }}
|
||||
{{ $endpointsKey }}:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
interval: {{ coalesce .interval ($.Values.config.statistics).data_collection_interval "1m" }}
|
||||
{{- with .metricRelabelings }}
|
||||
metricRelabelings: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
relabelings:
|
||||
- action: replace
|
||||
sourceLabels:
|
||||
- __meta_kubernetes_pod_label_app_kubernetes_io_name
|
||||
targetLabel: instance
|
||||
{{- with .relabelings }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .scrapeTimeout }}
|
||||
scrapeTimeout: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .jobLabel }}
|
||||
jobLabel: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .podTargetLabels }}
|
||||
podTargetLabels: {{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
11
deploy/helm/templates/pvc.yaml
Normal file
11
deploy/helm/templates/pvc.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
{{- if and .Values.persistence.enabled (eq .Values.workloadType "Deployment") -}}
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
{{- with .Values.persistence.annotations }}
|
||||
annotations: {{- toYaml . | nindent 4}}
|
||||
{{- end }}
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
spec: {{- include "wg-portal.pvc" . | nindent 2 }}
|
||||
{{- end -}}
|
42
deploy/helm/templates/secret.yaml
Normal file
42
deploy/helm/templates/secret.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
{{- $advanced := dict "start_listen_port" (.Values.service.wireguard.ports | sortAlpha | first | int) -}}
|
||||
{{- $statistics := dict "listening_address" (printf ":%v" .Values.service.metrics.port) -}}
|
||||
{{- $web:= dict "listening_address" (printf ":%v" .Values.service.web.port) -}}
|
||||
{{- if and .Values.certificate.enabled (include "wg-portal.hostname" .) }}
|
||||
{{- $_ := set $web "cert_file" "/app/certs/tls.crt" }}
|
||||
{{- $_ := set $web "key_file" "/app/certs/tls.key" }}
|
||||
{{- end }}
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
stringData:
|
||||
config.yml: |
|
||||
{{- with mustMerge $advanced .Values.config.advanced }}
|
||||
advanced: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.config.auth }}
|
||||
auth: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with mustMerge .Values.config.core (include "wg-portal.admin" . | fromYaml) }}
|
||||
core: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.config.database }}
|
||||
database: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.config.mail }}
|
||||
mail: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with mustMerge $statistics .Values.config.statistics }}
|
||||
statistics: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with mustMerge $web .Values.config.web }}
|
||||
web: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
20
deploy/helm/templates/service.yaml
Normal file
20
deploy/helm/templates/service.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
{{- $portsWeb := list (include "wg-portal.service.webPort" . | fromYaml) -}}
|
||||
{{- $ports := list -}}
|
||||
{{- range $idx, $port := .Values.service.wireguard.ports -}}
|
||||
{{- $name := printf "wg%d" $idx -}}
|
||||
{{- $ports = append $ports (dict "name" $name "port" $port "protocol" "UDP" "targetPort" $name) -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- if .Values.service.mixed.enabled -}}
|
||||
{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.mixed "ports" (concat $portsWeb $ports)) }}
|
||||
{{- else }}
|
||||
{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.web "ports" $portsWeb) }}
|
||||
---
|
||||
{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.wireguard "ports" $ports "name" "wireguard") }}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and .Values.monitoring.enabled (eq .Values.monitoring.kind "ServiceMonitor") }}
|
||||
---
|
||||
{{- $portsMetrics := list (dict "name" "metrics" "port" .Values.service.metrics.port "protocol" "TCP" "targetPort" "metrics") -}}
|
||||
{{- include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.metrics "ports" $portsWeb "name" "metrics") }}
|
||||
{{- end -}}
|
10
deploy/helm/templates/serviceaccount.yaml
Normal file
10
deploy/helm/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "wg-portal.serviceAccountName" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
24
deploy/helm/templates/statefulset.yaml
Normal file
24
deploy/helm/templates/statefulset.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
{{- if eq .Values.workloadType "StatefulSet" -}}
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: {{ include "wg-portal.fullname" . }}
|
||||
labels: {{- include "wg-portal.labels" . | nindent 4 }}
|
||||
spec:
|
||||
{{- with .Values.revisionHistoryLimit }}
|
||||
revisionHistoryLimit: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .Values.strategy }}
|
||||
updateStrategy: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
serviceName: {{ template "wg-portal.fullname" . }}-web
|
||||
selector:
|
||||
matchLabels: {{- include "wg-portal.selectorLabels" . | nindent 6 }}
|
||||
template: {{- include "wg-portal.podTemplate" . | nindent 4 }}
|
||||
{{- if .Values.persistence.enabled }}
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec: {{- include "wg-portal.pvc" . | nindent 8 }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
248
deploy/helm/values.yaml
Normal file
248
deploy/helm/values.yaml
Normal file
@@ -0,0 +1,248 @@
|
||||
# Default values for wg-portal.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
# -- Partially override resource names (adds suffix)
|
||||
nameOverride: ""
|
||||
# -- Fully override resource names
|
||||
fullnameOverride: ""
|
||||
# -- Array of extra objects to deploy with the release
|
||||
extraDeploy: []
|
||||
|
||||
config:
|
||||
# -- (tpl/object) [Advanced configuration](https://wgportal.org/latest/documentation/configuration/overview/#advanced) options.
|
||||
advanced: {}
|
||||
# -- (tpl/object) [Auth configuration](https://wgportal.org/latest/documentation/configuration/overview/#auth) options.
|
||||
auth: {}
|
||||
# -- (tpl/object) [Core configuration](https://wgportal.org/latest/documentation/configuration/overview/#core) options.<br>
|
||||
# If external admins in `auth` are defined and
|
||||
# there are no `admin_user` and `admin_password` defined here,
|
||||
# the default admin account will be disabled.
|
||||
core: {}
|
||||
# -- (tpl/object) [Database configuration](https://wgportal.org/latest/documentation/configuration/overview/#database) options
|
||||
database: {}
|
||||
# -- (tpl/object) [Mail configuration](https://wgportal.org/latest/documentation/configuration/overview/#mail) options
|
||||
mail: {}
|
||||
# -- (tpl/object) [Statistics configuration](https://wgportal.org/latest/documentation/configuration/overview/#statistics) options
|
||||
statistics: {}
|
||||
# -- (tpl/object) [Web configuration](https://wgportal.org/latest/documentation/configuration/overview/#web) options.<br>
|
||||
# `listening_address` will be set automatically from `service.web.port`.
|
||||
# `external_url` is required to enable ingress and certificate resources.
|
||||
web: {}
|
||||
|
||||
# -- The number of old ReplicaSets to retain to allow rollback.
|
||||
# @default -- `10`
|
||||
revisionHistoryLimit: ""
|
||||
# -- Workload type - `Deployment` or `StatefulSet`
|
||||
workloadType: Deployment
|
||||
# -- Update strategy for the workload
|
||||
# Valid values are:
|
||||
# `RollingUpdate` or `Recreate` for Deployment,
|
||||
# `RollingUpdate` or `OnDelete` for StatefulSet
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
|
||||
image:
|
||||
# -- Image repository
|
||||
repository: ghcr.io/h44z/wg-portal
|
||||
# -- Image pull policy
|
||||
pullPolicy: IfNotPresent
|
||||
# -- Overrides the image tag whose default is the chart appVersion
|
||||
tag: ""
|
||||
|
||||
# -- Image pull secrets
|
||||
imagePullSecrets: []
|
||||
# -- (tpl/object) Extra annotations to add to the pod
|
||||
podAnnotations: {}
|
||||
# -- Extra labels to add to the pod
|
||||
podLabels: {}
|
||||
# -- Pod Security Context
|
||||
podSecurityContext: {}
|
||||
# Container Security Context
|
||||
securityContext:
|
||||
capabilities:
|
||||
# -- Add capabilities to the container
|
||||
add:
|
||||
- NET_ADMIN
|
||||
|
||||
# -- (tpl/list) Pod init containers
|
||||
initContainers: []
|
||||
# -- (tpl/list) Pod sidecar containers
|
||||
sidecarContainers: []
|
||||
# -- Set DNS policy for the pod.
|
||||
# Valid values are `ClusterFirstWithHostNet`, `ClusterFirst`, `Default` or `None`.
|
||||
# @default -- `"ClusterFirst"`
|
||||
dnsPolicy: ""
|
||||
# -- Restart policy for all containers within the pod.
|
||||
# Valid values are `Always`, `OnFailure` or `Never`.
|
||||
# @default -- `"Always"`
|
||||
restartPolicy: ""
|
||||
# -- Use the host's network namespace.
|
||||
# @default -- `false`.
|
||||
hostNetwork: ""
|
||||
# -- Resources requests and limits
|
||||
resources: {}
|
||||
# -- Overwrite pod command
|
||||
command: []
|
||||
# -- Additional pod arguments
|
||||
args: []
|
||||
# -- (tpl/list) Additional environment variables
|
||||
env: []
|
||||
# -- (tpl/list) Additional environment variables from a secret or configMap
|
||||
envFrom: []
|
||||
# -- Liveness probe configuration
|
||||
livenessProbe: {}
|
||||
# -- Readiness probe configuration
|
||||
readinessProbe: {}
|
||||
# -- Startup probe configuration
|
||||
startupProbe: {}
|
||||
# -- (tpl/list) Additional volumes
|
||||
volumes: []
|
||||
# -- (tpl/list) Additional volumeMounts
|
||||
volumeMounts: []
|
||||
# -- Node Selector configuration
|
||||
nodeSelector:
|
||||
kubernetes.io/os: linux
|
||||
# -- Tolerations configuration
|
||||
tolerations: []
|
||||
# -- Affinity configuration
|
||||
affinity: {}
|
||||
|
||||
service:
|
||||
mixed:
|
||||
# -- Whether to create a single service for the web and wireguard interfaces
|
||||
enabled: false
|
||||
# -- Service type
|
||||
type: LoadBalancer
|
||||
web:
|
||||
# -- Annotations for the web service
|
||||
annotations: {}
|
||||
# -- Web service type
|
||||
type: ClusterIP
|
||||
# -- Web service port
|
||||
# Used for the web interface listener
|
||||
port: 8888
|
||||
# -- Web service appProtocol. Will be auto set to `https` if certificate is enabled.
|
||||
appProtocol: http
|
||||
wireguard:
|
||||
# -- Annotations for the WireGuard service
|
||||
annotations: {}
|
||||
# -- Wireguard service type
|
||||
type: LoadBalancer
|
||||
# -- Wireguard service ports.
|
||||
# Exposes the WireGuard ports for created interfaces.
|
||||
# Lowerest port is selected as start port for the first interface.
|
||||
# Increment next port by 1 for each additional interface.
|
||||
ports:
|
||||
- 51820
|
||||
metrics:
|
||||
port: 8787
|
||||
|
||||
ingress:
|
||||
# -- Specifies whether an ingress resource should be created
|
||||
enabled: false
|
||||
# -- Ingress class name
|
||||
className: ""
|
||||
# -- Ingress annotations
|
||||
annotations: {}
|
||||
# -- Ingress TLS configuration.
|
||||
# Enable certificate resource or add ingress annotation to create required secret
|
||||
tls: false
|
||||
|
||||
certificate:
|
||||
# -- Specifies whether a certificate resource should be created.
|
||||
# If enabled, certificate will be used for the web.
|
||||
enabled: false
|
||||
issuer:
|
||||
# -- Certificate issuer name
|
||||
name: ""
|
||||
# -- Certificate issuer kind (ClusterIssuer or Issuer)
|
||||
kind: ""
|
||||
# -- Certificate issuer group
|
||||
group: cert-manager.io
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
duration: ""
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
renewBefore: ""
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
commonName: ""
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
emailAddresses: []
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
ipAddresses: []
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
keystores: {}
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
privateKey: {}
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
secretTemplate: {}
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
subject: {}
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
uris: []
|
||||
# -- Optional. [Documentation](https://cert-manager.io/docs/usage/certificate/#creating-certificate-resources)
|
||||
usages: []
|
||||
|
||||
persistence:
|
||||
# -- Specifies whether an persistent volume should be created
|
||||
enabled: false
|
||||
# -- Persistent Volume Claim annotations
|
||||
annotations: {}
|
||||
# -- Persistent Volume storage class.
|
||||
# If undefined (the default) cluster's default provisioner will be used.
|
||||
storageClass: ""
|
||||
# -- Persistent Volume Access Mode
|
||||
accessMode: ReadWriteOnce
|
||||
# -- Persistent Volume size
|
||||
size: 1Gi
|
||||
# -- Persistent Volume Name (optional)
|
||||
volumeName: ""
|
||||
|
||||
serviceAccount:
|
||||
# -- Specifies whether a service account should be created
|
||||
create: true
|
||||
# -- Service account annotations
|
||||
annotations: {}
|
||||
# -- Automatically mount a ServiceAccount's API credentials
|
||||
automount: false
|
||||
# -- The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ""
|
||||
|
||||
monitoring:
|
||||
# -- Enable Prometheus monitoring.
|
||||
enabled: false
|
||||
# -- API version of the Prometheus resource.
|
||||
# Use `azmonitoring.coreos.com/v1` for Azure Managed Prometheus.
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
# -- Kind of the Prometheus resource.
|
||||
# Could be `PodMonitor` or `ServiceMonitor`.
|
||||
kind: PodMonitor
|
||||
# -- Resource labels.
|
||||
labels: {}
|
||||
# -- Resource annotations.
|
||||
annotations: {}
|
||||
# -- Interval at which metrics should be scraped. If not specified `config.statistics.data_collection_interval` interval is used.
|
||||
# @default -- `1m`
|
||||
interval: ""
|
||||
# -- Relabelings to samples before ingestion.
|
||||
metricRelabelings: []
|
||||
# -- Relabelings to samples before scraping.
|
||||
relabelings: []
|
||||
# -- Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used.
|
||||
scrapeTimeout: ""
|
||||
# -- The label to use to retrieve the job name from.
|
||||
jobLabel: ""
|
||||
# -- Transfers labels on the Kubernetes Pod onto the target.
|
||||
podTargetLabels: {}
|
||||
|
||||
dashboard:
|
||||
# -- Enable Grafana dashboard.
|
||||
enabled: false
|
||||
# -- Annotations for the dashboard ConfigMap.
|
||||
annotations: {}
|
||||
# -- Additional labels for the dashboard ConfigMap.
|
||||
labels: {}
|
||||
# -- Dashboard ConfigMap namespace
|
||||
# Overrides the namespace for the dashboard ConfigMap.
|
||||
namespace: ""
|
@@ -1,5 +1,4 @@
|
||||
---
|
||||
version: '3.6'
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:v2
|
||||
@@ -11,8 +10,10 @@ services:
|
||||
max-file: "3"
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
# Use host network mode for WireGuard and the UI. Ensure that access to the UI is properly secured.
|
||||
network_mode: "host"
|
||||
volumes:
|
||||
# left side is the host path, right side is the container path
|
||||
- /etc/wireguard:/etc/wireguard
|
||||
- ./data:/app/data
|
||||
- ./config:/app/config
|
||||
|
BIN
docs/assets/images/dashboard.png
Executable file
BIN
docs/assets/images/dashboard.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 269 KiB |
193
docs/documentation/configuration/examples.md
Normal file
193
docs/documentation/configuration/examples.md
Normal file
@@ -0,0 +1,193 @@
|
||||
Below are some sample YAML configurations demonstrating how to override some default values.
|
||||
|
||||
## Basic
|
||||
|
||||
```yaml
|
||||
core:
|
||||
admin_user: test@example.com
|
||||
admin_password: password
|
||||
admin_api_token: super-s3cr3t-api-token-or-a-UUID
|
||||
import_existing: false
|
||||
create_default_peer: true
|
||||
self_provisioning_allowed: true
|
||||
|
||||
web:
|
||||
site_title: My WireGuard Server
|
||||
site_company_name: My Company
|
||||
listening_address: :8080
|
||||
external_url: https://my.external-domain.com
|
||||
csrf_secret: super-s3cr3t-csrf
|
||||
session_secret: super-s3cr3t-session
|
||||
request_logging: true
|
||||
|
||||
advanced:
|
||||
log_level: trace
|
||||
log_pretty: true
|
||||
log_json: false
|
||||
config_storage_path: /etc/wireguard
|
||||
expiry_check_interval: 5m
|
||||
|
||||
database:
|
||||
debug: true
|
||||
type: sqlite
|
||||
dsn: data/sqlite.db
|
||||
encryption_passphrase: change-this-s3cr3t-encryption-passphrase
|
||||
```
|
||||
|
||||
## LDAP Authentication and Synchronization
|
||||
|
||||
```yaml
|
||||
# ... (basic configuration)
|
||||
|
||||
auth:
|
||||
ldap:
|
||||
# a sample LDAP provider with user sync enabled
|
||||
- id: ldap
|
||||
provider_name: Active Directory
|
||||
url: ldap://srv-ad1.company.local:389
|
||||
bind_user: ldap_wireguard@company.local
|
||||
bind_pass: super-s3cr3t-ldap
|
||||
base_dn: DC=COMPANY,DC=LOCAL
|
||||
login_filter: (&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))
|
||||
sync_interval: 15m
|
||||
sync_filter: (&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))
|
||||
disable_missing: true
|
||||
field_map:
|
||||
user_identifier: sAMAccountName
|
||||
email: mail
|
||||
firstname: givenName
|
||||
lastname: sn
|
||||
phone: telephoneNumber
|
||||
department: department
|
||||
memberof: memberOf
|
||||
admin_group: CN=WireGuardAdmins,OU=Some-OU,DC=COMPANY,DC=LOCAL
|
||||
registration_enabled: true
|
||||
log_user_info: true
|
||||
```
|
||||
|
||||
## OpenID Connect (OIDC) Authentication
|
||||
|
||||
```yaml
|
||||
# ... (basic configuration)
|
||||
|
||||
auth:
|
||||
oidc:
|
||||
# A sample Entra ID provider with environment variable substitution.
|
||||
# Only users with an @outlook.com email address are allowed to register or login.
|
||||
- id: azure
|
||||
provider_name: azure
|
||||
display_name: Login with</br>Entra ID
|
||||
registration_enabled: true
|
||||
base_url: "https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0"
|
||||
client_id: "${AZURE_CLIENT_ID}"
|
||||
client_secret: "${AZURE_CLIENT_SECRET}"
|
||||
allowed_domains:
|
||||
- "outlook.com"
|
||||
extra_scopes:
|
||||
- profile
|
||||
- email
|
||||
|
||||
# a sample provider where users with the attribute `wg_admin` set to `true` are considered as admins
|
||||
- id: oidc-with-admin-attribute
|
||||
provider_name: google
|
||||
display_name: Login with</br>Google
|
||||
base_url: https://accounts.google.com
|
||||
client_id: the-client-id-1234.apps.googleusercontent.com
|
||||
client_secret: A_CLIENT_SECRET
|
||||
extra_scopes:
|
||||
- https://www.googleapis.com/auth/userinfo.email
|
||||
- https://www.googleapis.com/auth/userinfo.profile
|
||||
field_map:
|
||||
user_identifier: sub
|
||||
email: email
|
||||
firstname: given_name
|
||||
lastname: family_name
|
||||
phone: phone_number
|
||||
department: department
|
||||
is_admin: wg_admin
|
||||
admin_mapping:
|
||||
admin_value_regex: ^true$
|
||||
registration_enabled: true
|
||||
log_user_info: true
|
||||
|
||||
# a sample provider where users in the group `the-admin-group` are considered as admins
|
||||
- id: oidc-with-admin-group
|
||||
provider_name: google2
|
||||
display_name: Login with</br>Google2
|
||||
base_url: https://accounts.google.com
|
||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
||||
client_secret: A_CLIENT_SECRET
|
||||
extra_scopes:
|
||||
- https://www.googleapis.com/auth/userinfo.email
|
||||
- https://www.googleapis.com/auth/userinfo.profile
|
||||
field_map:
|
||||
user_identifier: sub
|
||||
email: email
|
||||
firstname: given_name
|
||||
lastname: family_name
|
||||
phone: phone_number
|
||||
department: department
|
||||
user_groups: groups
|
||||
admin_mapping:
|
||||
admin_group_regex: ^the-admin-group$
|
||||
registration_enabled: true
|
||||
log_user_info: true
|
||||
```
|
||||
|
||||
## Plain OAuth2 Authentication
|
||||
|
||||
```yaml
|
||||
# ... (basic configuration)
|
||||
|
||||
auth:
|
||||
oauth:
|
||||
# a sample provider where users with the attribute `this-attribute-must-be-true` set to `true` or `True`
|
||||
# are considered as admins
|
||||
- id: google_plain_oauth-with-admin-attribute
|
||||
provider_name: google3
|
||||
display_name: Login with</br>Google3
|
||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
||||
client_secret: A_CLIENT_SECRET
|
||||
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
||||
token_url: https://oauth2.googleapis.com/token
|
||||
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
field_map:
|
||||
user_identifier: sub
|
||||
email: email
|
||||
firstname: name
|
||||
is_admin: this-attribute-must-be-true
|
||||
admin_mapping:
|
||||
admin_value_regex: ^(True|true)$
|
||||
registration_enabled: true
|
||||
|
||||
# a sample provider where either users with the attribute `this-attribute-must-be-true` set to `true` or
|
||||
# users in the group `admin-group-name` are considered as admins
|
||||
- id: google_plain_oauth_with_groups
|
||||
provider_name: google4
|
||||
display_name: Login with</br>Google4
|
||||
client_id: another-client-id-1234.apps.googleusercontent.com
|
||||
client_secret: A_CLIENT_SECRET
|
||||
auth_url: https://accounts.google.com/o/oauth2/v2/auth
|
||||
token_url: https://oauth2.googleapis.com/token
|
||||
user_info_url: https://openidconnect.googleapis.com/v1/userinfo
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
- i-want-some-groups
|
||||
field_map:
|
||||
email: email
|
||||
firstname: name
|
||||
user_identifier: sub
|
||||
is_admin: this-attribute-must-be-true
|
||||
user_groups: groups
|
||||
admin_mapping:
|
||||
admin_value_regex: ^true$
|
||||
admin_group_regex: ^admin-group-name$
|
||||
registration_enabled: true
|
||||
log_user_info: true
|
||||
```
|
663
docs/documentation/configuration/overview.md
Normal file
663
docs/documentation/configuration/overview.md
Normal file
@@ -0,0 +1,663 @@
|
||||
This page provides an overview of **all available configuration options** for WireGuard Portal.
|
||||
|
||||
You can supply these configurations in a **YAML** file when starting the Portal.
|
||||
The path of the configuration file defaults to `config/config.yaml` (or `config/config.yml`) in the working directory of the executable.
|
||||
It is possible to override the configuration filepath using the environment variable `WG_PORTAL_CONFIG`.
|
||||
For example: `WG_PORTAL_CONFIG=/etc/wg-portal/config.yaml ./wg-portal`.
|
||||
Also, environment variable substitution in the config file is supported. Refer to the [syntax](https://github.com/a8m/envsubst?tab=readme-ov-file#docs).
|
||||
|
||||
Configuration examples are available on the [Examples](./examples.md) page.
|
||||
|
||||
<details>
|
||||
<summary>Default configuration</summary>
|
||||
|
||||
```yaml
|
||||
core:
|
||||
admin_user: admin@wgportal.local
|
||||
admin_password: wgportal
|
||||
admin_api_token: ""
|
||||
editable_keys: true
|
||||
create_default_peer: false
|
||||
create_default_peer_on_creation: false
|
||||
re_enable_peer_after_user_enable: true
|
||||
delete_peer_after_user_deleted: false
|
||||
self_provisioning_allowed: false
|
||||
import_existing: true
|
||||
restore_state: true
|
||||
|
||||
advanced:
|
||||
log_level: info
|
||||
log_pretty: false
|
||||
log_json: false
|
||||
start_listen_port: 51820
|
||||
start_cidr_v4: 10.11.12.0/24
|
||||
start_cidr_v6: fdfd:d3ad:c0de:1234::0/64
|
||||
use_ip_v6: true
|
||||
config_storage_path: ""
|
||||
expiry_check_interval: 15m
|
||||
rule_prio_offset: 20000
|
||||
route_table_offset: 20000
|
||||
api_admin_only: true
|
||||
|
||||
database:
|
||||
debug: false
|
||||
slow_query_threshold: "0"
|
||||
type: sqlite
|
||||
dsn: data/sqlite.db
|
||||
encryption_passphrase: ""
|
||||
|
||||
statistics:
|
||||
use_ping_checks: true
|
||||
ping_check_workers: 10
|
||||
ping_unprivileged: false
|
||||
ping_check_interval: 1m
|
||||
data_collection_interval: 1m
|
||||
collect_interface_data: true
|
||||
collect_peer_data: true
|
||||
collect_audit_data: true
|
||||
listening_address: :8787
|
||||
|
||||
mail:
|
||||
host: 127.0.0.1
|
||||
port: 25
|
||||
encryption: none
|
||||
cert_validation: true
|
||||
username: ""
|
||||
password: ""
|
||||
auth_type: plain
|
||||
from: Wireguard Portal <noreply@wireguard.local>
|
||||
link_only: false
|
||||
|
||||
auth:
|
||||
oidc: []
|
||||
oauth: []
|
||||
ldap: []
|
||||
|
||||
web:
|
||||
listening_address: :8888
|
||||
external_url: http://localhost:8888
|
||||
site_company_name: WireGuard Portal
|
||||
site_title: WireGuard Portal
|
||||
session_identifier: wgPortalSession
|
||||
session_secret: very_secret
|
||||
csrf_secret: extremely_secret
|
||||
request_logging: false
|
||||
expose_host_info: false
|
||||
cert_file: ""
|
||||
key_File: ""
|
||||
|
||||
webhook:
|
||||
url: ""
|
||||
authentication: ""
|
||||
timeout: 10s
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Below you will find sections like
|
||||
[`core`](#core),
|
||||
[`advanced`](#advanced),
|
||||
[`database`](#database),
|
||||
[`statistics`](#statistics),
|
||||
[`mail`](#mail),
|
||||
[`auth`](#auth),
|
||||
[`web`](#web) and
|
||||
[`webhook`](#webhook).
|
||||
Each section describes the individual configuration keys, their default values, and a brief explanation of their purpose.
|
||||
|
||||
---
|
||||
|
||||
## Core
|
||||
|
||||
These are the primary configuration options that control fundamental WireGuard Portal behavior.
|
||||
More advanced options are found in the subsequent `Advanced` section.
|
||||
|
||||
### `admin_user`
|
||||
- **Default:** `admin@wgportal.local`
|
||||
- **Description:** The administrator user. This user will be created as a default admin if it does not yet exist.
|
||||
|
||||
### `admin_password`
|
||||
- **Default:** `wgportal`
|
||||
- **Description:** The administrator password. The default password of `wgportal` should be changed immediately.
|
||||
|
||||
### `admin_api_token`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** An API token for the admin user. If a token is provided, the REST API can be accessed using this token. If empty, the API is initially disabled for the admin user.
|
||||
|
||||
### `editable_keys`
|
||||
- **Default:** `true`
|
||||
- **Description:** Allow editing of WireGuard key-pairs directly in the UI.
|
||||
|
||||
### `create_default_peer`
|
||||
- **Default:** `false`
|
||||
- **Description:** If a user logs in for the first time with no existing peers, automatically create a new WireGuard peer for **all** server interfaces.
|
||||
|
||||
### `create_default_peer_on_creation`
|
||||
- **Default:** `false`
|
||||
- **Description:** If an LDAP user is created (e.g., through LDAP sync) and has no peers, automatically create a new WireGuard peer for **all** server interfaces.
|
||||
|
||||
### `re_enable_peer_after_user_enable`
|
||||
- **Default:** `true`
|
||||
- **Description:** Re-enable all peers that were previously disabled if the associated user is re-enabled.
|
||||
|
||||
### `delete_peer_after_user_deleted`
|
||||
- **Default:** `false`
|
||||
- **Description:** If a user is deleted, remove all linked peers. Otherwise, peers remain but are disabled.
|
||||
|
||||
### `self_provisioning_allowed`
|
||||
- **Default:** `false`
|
||||
- **Description:** Allow registered (non-admin) users to self-provision peers from their profile page.
|
||||
|
||||
### `import_existing`
|
||||
- **Default:** `true`
|
||||
- **Description:** On startup, import existing WireGuard interfaces and peers into WireGuard Portal.
|
||||
|
||||
### `restore_state`
|
||||
- **Default:** `true`
|
||||
- **Description:** Restore the WireGuard interface states (up/down) that existed before WireGuard Portal started.
|
||||
|
||||
---
|
||||
|
||||
## Advanced
|
||||
|
||||
Additional or more specialized configuration options for logging and interface creation details.
|
||||
|
||||
### `log_level`
|
||||
- **Default:** `info`
|
||||
- **Description:** The log level used by the application. Valid options are: `trace`, `debug`, `info`, `warn`, `error`.
|
||||
|
||||
### `log_pretty`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, log messages are colorized and formatted for readability (pretty-print).
|
||||
|
||||
### `log_json`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, log messages are structured in JSON format.
|
||||
|
||||
### `start_listen_port`
|
||||
- **Default:** `51820`
|
||||
- **Description:** The first port to use when automatically creating new WireGuard interfaces.
|
||||
|
||||
### `start_cidr_v4`
|
||||
- **Default:** `10.11.12.0/24`
|
||||
- **Description:** The initial IPv4 subnet to use when automatically creating new WireGuard interfaces.
|
||||
|
||||
### `start_cidr_v6`
|
||||
- **Default:** `fdfd:d3ad:c0de:1234::0/64`
|
||||
- **Description:** The initial IPv6 subnet to use when automatically creating new WireGuard interfaces.
|
||||
|
||||
### `use_ip_v6`
|
||||
- **Default:** `true`
|
||||
- **Description:** Enable or disable IPv6 support.
|
||||
|
||||
### `config_storage_path`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Path to a directory where `wg-quick` style configuration files will be stored (if you need local filesystem configs).
|
||||
|
||||
### `expiry_check_interval`
|
||||
- **Default:** `15m`
|
||||
- **Description:** Interval after which existing peers are checked if they are expired. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
|
||||
|
||||
### `rule_prio_offset`
|
||||
- **Default:** `20000`
|
||||
- **Description:** Offset for IP route rule priorities when configuring routing.
|
||||
|
||||
### `route_table_offset`
|
||||
- **Default:** `20000`
|
||||
- **Description:** Offset for IP route table IDs when configuring routing.
|
||||
|
||||
### `api_admin_only`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, the public REST API is accessible only to admin users. The API docs live at [`/api/v1/doc.html`](../rest-api/api-doc.md).
|
||||
|
||||
---
|
||||
|
||||
## Database
|
||||
|
||||
Configuration for the underlying database used by WireGuard Portal.
|
||||
Supported databases include SQLite, MySQL, Microsoft SQL Server, and Postgres.
|
||||
|
||||
If sensitive values (like private keys) should be stored in an encrypted format, set the `encryption_passphrase` option.
|
||||
|
||||
### `debug`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, logs all database statements (verbose).
|
||||
|
||||
### `slow_query_threshold`
|
||||
- **Default:** "0"
|
||||
- **Description:** A time threshold (e.g., `100ms`) above which queries are considered slow and logged as warnings. If zero, slow query logging is disabled. Format uses `s`, `ms` for seconds, milliseconds, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). The value must be a string.
|
||||
|
||||
### `type`
|
||||
- **Default:** `sqlite`
|
||||
- **Description:** The database type. Valid options: `sqlite`, `mssql`, `mysql`, `postgres`.
|
||||
|
||||
### `dsn`
|
||||
- **Default:** `data/sqlite.db`
|
||||
- **Description:** The Data Source Name (DSN) for connecting to the database.
|
||||
For example:
|
||||
```text
|
||||
user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
|
||||
```
|
||||
|
||||
### `encryption_passphrase`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Passphrase for encrypting sensitive values such as private keys in the database. Encryption is only applied if this passphrase is set.
|
||||
**Important:** Once you enable encryption by setting this passphrase, you cannot disable it or change it afterward.
|
||||
New or updated records will be encrypted; existing data remains in plaintext until it’s next modified.
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
Controls how WireGuard Portal collects and reports usage statistics, including ping checks and Prometheus metrics.
|
||||
|
||||
### `use_ping_checks`
|
||||
- **Default:** `true`
|
||||
- **Description:** Enable periodic ping checks to verify that peers remain responsive.
|
||||
|
||||
### `ping_check_workers`
|
||||
- **Default:** `10`
|
||||
- **Description:** Number of parallel worker processes for ping checks.
|
||||
|
||||
### `ping_unprivileged`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `false`, ping checks run without root privileges. This is currently considered BETA.
|
||||
|
||||
### `ping_check_interval`
|
||||
- **Default:** `1m`
|
||||
- **Description:** Interval between consecutive ping checks for all peers. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
|
||||
|
||||
### `data_collection_interval`
|
||||
- **Default:** `1m`
|
||||
- **Description:** Interval between data collection cycles (bytes sent/received, handshake times, etc.). Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
|
||||
|
||||
### `collect_interface_data`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, collects interface-level data (bytes in/out) for monitoring and statistics.
|
||||
|
||||
### `collect_peer_data`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, collects peer-level data (bytes, last handshake, endpoint, etc.).
|
||||
|
||||
### `collect_audit_data`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, logs certain portal events (such as user logins) to the database.
|
||||
|
||||
### `listening_address`
|
||||
- **Default:** `:8787`
|
||||
- **Description:** Address and port for the integrated Prometheus metric server (e.g., `:8787` or `127.0.0.1:8787`).
|
||||
|
||||
---
|
||||
|
||||
## Mail
|
||||
|
||||
Options for configuring email notifications or sending peer configurations via email.
|
||||
|
||||
### `host`
|
||||
- **Default:** `127.0.0.1`
|
||||
- **Description:** Hostname or IP of the SMTP server.
|
||||
|
||||
### `port`
|
||||
- **Default:** `25`
|
||||
- **Description:** Port number for the SMTP server.
|
||||
|
||||
### `encryption`
|
||||
- **Default:** `none`
|
||||
- **Description:** SMTP encryption type. Valid values: `none`, `tls`, `starttls`.
|
||||
|
||||
### `cert_validation`
|
||||
- **Default:** `true`
|
||||
- **Description:** If `true`, validate the SMTP server certificate (relevant if `encryption` = `tls`).
|
||||
|
||||
### `username`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Optional SMTP username for authentication.
|
||||
|
||||
### `password`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Optional SMTP password for authentication.
|
||||
|
||||
### `auth_type`
|
||||
- **Default:** `plain`
|
||||
- **Description:** SMTP authentication type. Valid values: `plain`, `login`, `crammd5`.
|
||||
|
||||
### `from`
|
||||
- **Default:** `Wireguard Portal <noreply@wireguard.local>`
|
||||
- **Description:** The default "From" address when sending emails.
|
||||
|
||||
### `link_only`
|
||||
- **Default:** `false`
|
||||
- **Description:** If `true`, emails only contain a link to WireGuard Portal, rather than attaching the full configuration.
|
||||
|
||||
---
|
||||
|
||||
## Auth
|
||||
|
||||
WireGuard Portal supports multiple authentication strategies, including **OpenID Connect** (`oidc`), **OAuth** (`oauth`), and **LDAP** (`ldap`).
|
||||
Each can have multiple providers configured. Below are the relevant keys.
|
||||
|
||||
---
|
||||
|
||||
### OIDC
|
||||
|
||||
The `oidc` array contains a list of OpenID Connect providers.
|
||||
Below are the properties for each OIDC provider entry inside `auth.oidc`:
|
||||
|
||||
#### `provider_name`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A **unique** name for this provider. Must not conflict with other providers.
|
||||
|
||||
#### `display_name`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A user-friendly name shown on the login page (e.g., "Login with Google").
|
||||
|
||||
#### `base_url`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The OIDC provider’s base URL (e.g., `https://accounts.google.com`).
|
||||
|
||||
#### `client_id`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The OAuth client ID from the OIDC provider.
|
||||
|
||||
#### `client_secret`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The OAuth client secret from the OIDC provider.
|
||||
|
||||
#### `extra_scopes`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A list of additional OIDC scopes (e.g., `profile`, `email`).
|
||||
|
||||
#### `allowed_domains`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.
|
||||
|
||||
#### `field_map`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Maps OIDC claims to WireGuard Portal user fields.
|
||||
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `is_admin`, `user_groups`.
|
||||
|
||||
| **Field** | **Typical OIDC Claim** | **Explanation** |
|
||||
|-------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `user_identifier` | `sub` or `preferred_username` | A unique identifier for the user. Often the OIDC `sub` claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support `preferred_username` if it’s unique. |
|
||||
| `email` | `email` | The user’s email address as provided by the IdP. Not always verified, depending on IdP settings. |
|
||||
| `firstname` | `given_name` | The user’s first name, typically provided by the IdP in the `given_name` claim. |
|
||||
| `lastname` | `family_name` | The user’s last (family) name, typically provided by the IdP in the `family_name` claim. |
|
||||
| `phone` | `phone_number` | The user’s phone number. This may require additional scopes/permissions from the IdP to access. |
|
||||
| `department` | Custom claim (e.g., `department`) | If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., `department`, `org`, or another attribute). |
|
||||
| `is_admin` | Custom claim or derived role | If the IdP returns a role or admin flag, you can map that to `is_admin`. Often this is managed through custom claims or group membership. |
|
||||
| `user_groups` | `groups` or another custom claim | A list of group memberships for the user. Some IdPs provide `groups` out of the box; others require custom claims or directory lookups. |
|
||||
|
||||
#### `admin_mapping`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** WgPortal can grant a user admin rights by matching the value of the `is_admin` claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the `user_group` claim. The regular expressions are defined in `admin_value_regex` and `admin_group_regex`.
|
||||
- `admin_value_regex`: A regular expression to match the `is_admin` claim. By default, this expression matches the string "true" (`^true$`).
|
||||
- `admin_group_regex`: A regular expression to match the `user_groups` claim. Each entry in the `user_groups` claim is checked against this regex.
|
||||
|
||||
#### `registration_enabled`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, a new user will be created in WireGuard Portal if not already present.
|
||||
|
||||
#### `log_user_info`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, OIDC user data is logged at the trace level upon login (for debugging).
|
||||
|
||||
---
|
||||
|
||||
### OAuth
|
||||
|
||||
The `oauth` array contains a list of plain OAuth2 providers.
|
||||
Below are the properties for each OAuth provider entry inside `auth.oauth`:
|
||||
|
||||
#### `provider_name`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A **unique** name for this provider. Must not conflict with other providers.
|
||||
|
||||
#### `display_name`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A user-friendly name shown on the login page.
|
||||
|
||||
#### `client_id`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The OAuth client ID for the provider.
|
||||
|
||||
#### `client_secret`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The OAuth client secret for the provider.
|
||||
|
||||
#### `auth_url`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** URL of the authentication endpoint.
|
||||
|
||||
#### `token_url`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** URL of the token endpoint.
|
||||
|
||||
#### `user_info_url`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** URL of the user information endpoint.
|
||||
|
||||
#### `scopes`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A list of OAuth scopes.
|
||||
|
||||
#### `allowed_domains`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A list of allowlisted domains. Only users with email addresses in these domains can log in or register. This is useful for restricting access to specific organizations or groups.
|
||||
|
||||
#### `field_map`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Maps OAuth attributes to WireGuard Portal fields.
|
||||
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `is_admin`, `user_groups`.
|
||||
|
||||
| **Field** | **Typical Claim** | **Explanation** |
|
||||
|-------------------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `user_identifier` | `sub` or `preferred_username` | A unique identifier for the user. Often the OIDC `sub` claim is used because it’s guaranteed to be unique for the user within the IdP. Some providers also support `preferred_username` if it’s unique. |
|
||||
| `email` | `email` | The user’s email address as provided by the IdP. Not always verified, depending on IdP settings. |
|
||||
| `firstname` | `given_name` | The user’s first name, typically provided by the IdP in the `given_name` claim. |
|
||||
| `lastname` | `family_name` | The user’s last (family) name, typically provided by the IdP in the `family_name` claim. |
|
||||
| `phone` | `phone_number` | The user’s phone number. This may require additional scopes/permissions from the IdP to access. |
|
||||
| `department` | Custom claim (e.g., `department`) | If the IdP can provide organizational data, it may store it in a custom claim. Adjust accordingly (e.g., `department`, `org`, or another attribute). |
|
||||
| `is_admin` | Custom claim or derived role | If the IdP returns a role or admin flag, you can map that to `is_admin`. Often this is managed through custom claims or group membership. |
|
||||
| `user_groups` | `groups` or another custom claim | A list of group memberships for the user. Some IdPs provide `groups` out of the box; others require custom claims or directory lookups. |
|
||||
|
||||
#### `admin_mapping`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** WgPortal can grant a user admin rights by matching the value of the `is_admin` claim against a regular expression. Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the `user_group` claim. The regular expressions are defined in `admin_value_regex` and `admin_group_regex`.
|
||||
- `admin_value_regex`: A regular expression to match the `is_admin` claim. By default, this expression matches the string "true" (`^true$`).
|
||||
- `admin_group_regex`: A regular expression to match the `user_groups` claim. Each entry in the `user_groups` claim is checked against this regex.
|
||||
|
||||
#### `registration_enabled`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, new users are created automatically on successful login.
|
||||
|
||||
#### `log_user_info`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, logs user info at the trace level upon login.
|
||||
|
||||
---
|
||||
|
||||
### LDAP
|
||||
|
||||
The `ldap` array contains a list of LDAP authentication providers.
|
||||
Below are the properties for each LDAP provider entry inside `auth.ldap`:
|
||||
|
||||
#### `provider_name`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A **unique** name for this provider. Must not conflict with other providers.
|
||||
|
||||
#### `url`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The LDAP server URL (e.g., `ldap://srv-ad01.company.local:389`).
|
||||
|
||||
#### `start_tls`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, use STARTTLS to secure the LDAP connection.
|
||||
|
||||
#### `cert_validation`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, validate the LDAP server’s TLS certificate.
|
||||
|
||||
#### `tls_certificate_path`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Path to a TLS certificate if needed for LDAP connections.
|
||||
|
||||
#### `tls_key_path`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Path to the corresponding TLS certificate key.
|
||||
|
||||
#### `base_dn`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The base DN for user searches (e.g., `DC=COMPANY,DC=LOCAL`).
|
||||
|
||||
#### `bind_user`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The bind user for LDAP (e.g., `company\\ldap_wireguard` or `ldap_wireguard@company.local`).
|
||||
|
||||
#### `bind_pass`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The bind password for LDAP authentication.
|
||||
|
||||
#### `field_map`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** Maps LDAP attributes to WireGuard Portal fields.
|
||||
- Available fields: `user_identifier`, `email`, `firstname`, `lastname`, `phone`, `department`, `memberof`.
|
||||
|
||||
| **WireGuard Portal Field** | **Typical LDAP Attribute** | **Short Description** |
|
||||
|----------------------------|----------------------------|--------------------------------------------------------------|
|
||||
| user_identifier | sAMAccountName / uid | Uniquely identifies the user within the LDAP directory. |
|
||||
| email | mail / userPrincipalName | Stores the user's primary email address. |
|
||||
| firstname | givenName | Contains the user's first (given) name. |
|
||||
| lastname | sn | Contains the user's last (surname) name. |
|
||||
| phone | telephoneNumber / mobile | Holds the user's phone or mobile number. |
|
||||
| department | departmentNumber / ou | Specifies the department or organizational unit of the user. |
|
||||
| memberof | memberOf | Lists the groups and roles to which the user belongs. |
|
||||
|
||||
#### `login_filter`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** An LDAP filter to restrict which users can log in. Use `{{login_identifier}}` to insert the username.
|
||||
For example:
|
||||
```text
|
||||
(&(objectClass=organizationalPerson)(mail={{login_identifier}})(!userAccountControl:1.2.840.113556.1.4.803:=2))
|
||||
```
|
||||
|
||||
#### `admin_group`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** A specific LDAP group whose members are considered administrators in WireGuard Portal.
|
||||
For example:
|
||||
```text
|
||||
CN=WireGuardAdmins,OU=Some-OU,DC=YOURDOMAIN,DC=LOCAL
|
||||
```
|
||||
|
||||
#### `sync_interval`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** How frequently (in duration, e.g. `30m`) to synchronize users from LDAP. Empty or `0` disables sync. Format uses `s`, `m`, `h`, `d` for seconds, minutes, hours, days, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration).
|
||||
Only users that match the `sync_filter` are synchronized, if `disable_missing` is `true`, users not found in LDAP are disabled.
|
||||
|
||||
#### `sync_filter`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** An LDAP filter to select which users get synchronized into WireGuard Portal.
|
||||
For example:
|
||||
```text
|
||||
(&(objectClass=organizationalPerson)(!userAccountControl:1.2.840.113556.1.4.803:=2)(mail=*))
|
||||
```
|
||||
|
||||
#### `disable_missing`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, any user **not** found in LDAP (during sync) is disabled in WireGuard Portal.
|
||||
|
||||
#### `auto_re_enable`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, users that where disabled because they were missing (see `disable_missing`) will be re-enabled once they are found again.
|
||||
|
||||
#### `registration_enabled`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, new user accounts are created in WireGuard Portal upon first login.
|
||||
|
||||
#### `log_user_info`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** If `true`, logs LDAP user data at the trace level upon login.
|
||||
|
||||
---
|
||||
|
||||
## Web
|
||||
|
||||
The web section contains configuration options for the web server, including the listening address, session management, and CSRF protection.
|
||||
It is important to specify a valid `external_url` for the web server, especially if you are using a reverse proxy.
|
||||
Without a valid `external_url`, the login process may fail due to CSRF protection.
|
||||
|
||||
### `listening_address`
|
||||
- **Default:** `:8888`
|
||||
- **Description:** The listening address and port for the web server (e.g., `:8888` to bind on all interfaces or `127.0.0.1:8888` to bind only on the loopback interface).
|
||||
Ensure that access to WireGuard Portal is protected against unauthorized access, especially if binding to all interfaces.
|
||||
|
||||
### `external_url`
|
||||
- **Default:** `http://localhost:8888`
|
||||
- **Description:** The URL where a client can access WireGuard Portal. This URL is used for generating links in emails and for performing OAUTH redirects.
|
||||
**Important:** If you are using a reverse proxy, set this to the external URL of the reverse proxy, otherwise login will fail. If you access the portal via IP address, set this to the IP address of the server.
|
||||
|
||||
### `site_company_name`
|
||||
- **Default:** `WireGuard Portal`
|
||||
- **Description:** The company name that is shown at the bottom of the web frontend.
|
||||
|
||||
### `site_title`
|
||||
- **Default:** `WireGuard Portal`
|
||||
- **Description:** The title that is shown in the web frontend.
|
||||
|
||||
### `session_identifier`
|
||||
- **Default:** `wgPortalSession`
|
||||
- **Description:** The session identifier for the web frontend.
|
||||
|
||||
### `session_secret`
|
||||
- **Default:** `very_secret`
|
||||
- **Description:** The session secret for the web frontend.
|
||||
|
||||
### `csrf_secret`
|
||||
- **Default:** `extremely_secret`
|
||||
- **Description:** The CSRF secret.
|
||||
|
||||
### `request_logging`
|
||||
- **Default:** `false`
|
||||
- **Description:** Log all HTTP requests.
|
||||
|
||||
### `expose_host_info`
|
||||
- **Default:** `false`
|
||||
- **Description:** Expose the hostname and version of the WireGuard Portal server in an HTTP header. This is useful for debugging but may expose sensitive information.
|
||||
|
||||
### `cert_file`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** (Optional) Path to the TLS certificate file.
|
||||
|
||||
### `key_file`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** (Optional) Path to the TLS certificate key file.
|
||||
|
||||
---
|
||||
|
||||
## Webhook
|
||||
|
||||
The webhook section allows you to configure a webhook that is called on certain events in WireGuard Portal.
|
||||
A JSON object is sent in a POST request to the webhook URL with the following structure:
|
||||
```json
|
||||
{
|
||||
"event": "peer_created",
|
||||
"entity": "peer",
|
||||
"identifier": "the-peer-identifier",
|
||||
"payload": {
|
||||
// The payload of the event, e.g. peer data.
|
||||
// Check the API documentation for the exact structure.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `url`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The POST endpoint to which the webhook is sent. The URL must be reachable from the WireGuard Portal server. If the URL is empty, the webhook is disabled.
|
||||
|
||||
### `authentication`
|
||||
- **Default:** *(empty)*
|
||||
- **Description:** The Authorization header for the webhook endpoint. The value is send as-is in the header. For example: `Bearer <token>`.
|
||||
|
||||
### `timeout`
|
||||
- **Default:** `10s`
|
||||
- **Description:** The timeout for the webhook request. If the request takes longer than this, it is aborted.
|
42
docs/documentation/getting-started/binaries.md
Normal file
42
docs/documentation/getting-started/binaries.md
Normal file
@@ -0,0 +1,42 @@
|
||||
Starting from v2, each [release](https://github.com/h44z/wg-portal/releases) includes compiled binaries for supported platforms.
|
||||
These binary versions can be manually downloaded and installed.
|
||||
|
||||
## Download
|
||||
|
||||
Make sure that you download the correct binary for your architecture. The available binaries are:
|
||||
|
||||
- `wg-portal_linux_amd64` - Linux x86_64
|
||||
- `wg-portal_linux_arm64` - Linux ARM 64-bit
|
||||
- `wg-portal_linux_arm_v7` - Linux ARM 32-bit
|
||||
|
||||
With `curl`:
|
||||
|
||||
```shell
|
||||
curl -L -o wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64
|
||||
```
|
||||
|
||||
With `wget`:
|
||||
|
||||
```shell
|
||||
wget -O wg-portal https://github.com/h44z/wg-portal/releases/download/${WG_PORTAL_VERSION}/wg-portal_linux_amd64
|
||||
```
|
||||
|
||||
with `gh cli`:
|
||||
|
||||
```shell
|
||||
gh release download ${WG_PORTAL_VERSION} --repo h44z/wg-portal --output wg-portal --pattern '*amd64'
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /opt/wg-portal
|
||||
sudo install wg-portal /opt/wg-portal/
|
||||
```
|
||||
|
||||
## Unreleased versions (master branch builds)
|
||||
|
||||
Unreleased versions can be fetched directly from the artifacts section of the [GitHub Workflow](https://github.com/h44z/wg-portal/actions/workflows/docker-publish.yml?query=branch%3Amaster).
|
||||
|
@@ -1,11 +0,0 @@
|
||||
To build a standalone application, use the Makefile provided in the repository.
|
||||
Go version **1.21** or higher has to be installed to build WireGuard Portal.
|
||||
If you want to re-compile the frontend, NodeJS **18** and NPM >= **9** is required.
|
||||
|
||||
```shell
|
||||
# build the frontend (optional)
|
||||
make frontend
|
||||
|
||||
# build the binary
|
||||
make build
|
||||
```
|
@@ -1,81 +1,161 @@
|
||||
## Image Usage
|
||||
|
||||
The preferred way to start WireGuard Portal as Docker container is to use Docker Compose.
|
||||
The WireGuard Portal Docker image is available on both [Docker Hub](https://hub.docker.com/r/wgportal/wg-portal) and [GitHub Container Registry](https://github.com/h44z/wg-portal/pkgs/container/wg-portal).
|
||||
It is built on the official Alpine Linux base image and comes pre-packaged with all necessary WireGuard dependencies.
|
||||
|
||||
A sample docker-compose.yml:
|
||||
This container allows you to establish WireGuard VPN connections without relying on a host system that supports WireGuard or using the `linuxserver/wireguard` Docker image.
|
||||
|
||||
The recommended method for deploying WireGuard Portal is via Docker Compose for ease of configuration and management.
|
||||
|
||||
A sample docker-compose.yml (managing WireGuard interfaces directly on the host) is provided below:
|
||||
|
||||
```yaml
|
||||
version: '3.6'
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:v2
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
network_mode: "host"
|
||||
ports:
|
||||
- "8888:8888"
|
||||
volumes:
|
||||
- /etc/wireguard:/etc/wireguard
|
||||
- ./data:/app/data
|
||||
- ./config:/app/config
|
||||
--8<-- "docker-compose.yml::19"
|
||||
```
|
||||
|
||||
By default, the webserver is listening on port **8888**.
|
||||
By default, the webserver for the UI is listening on port **8888** on all available interfaces.
|
||||
|
||||
Volumes for `/app/data` and `/app/config` should be used ensure data persistence across container restarts.
|
||||
|
||||
## WireGuard Interface Handling
|
||||
|
||||
WireGuard Portal supports managing WireGuard interfaces through three distinct deployment methods, providing flexibility based on your system architecture and operational preferences:
|
||||
|
||||
- **Directly on the host system**:
|
||||
WireGuard Portal can control WireGuard interfaces natively on the host, without using containers.
|
||||
This setup is ideal for environments where direct access to system networking is preferred.
|
||||
To use this method, you need to set the network mode to `host` in your docker-compose.yml file.
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
...
|
||||
network_mode: "host"
|
||||
...
|
||||
```
|
||||
> :warning: If host networking is used, the WireGuard Portal UI will be accessible on all the host's IP addresses if the listening address is set to `:8888` in the configuration file.
|
||||
To avoid this, you can bind the listening address to a specific IP address, for example, the loopback address (`127.0.0.1:8888`). It is also possible to deploy firewall rules to restrict access to the WireGuard Portal UI.
|
||||
|
||||
- **Within the WireGuard Portal Docker container**:
|
||||
WireGuard interfaces can be managed directly from within the WireGuard Portal container itself.
|
||||
This is the recommended approach when running WireGuard Portal via Docker, as it encapsulates all functionality in a single, portable container without requiring a separate WireGuard host or image.
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:v2
|
||||
container_name: wg-portal
|
||||
...
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
# host port : container port
|
||||
# WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)
|
||||
- "51820:51820/udp"
|
||||
# Web UI port
|
||||
- "8888:8888/tcp"
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
# host path : container path
|
||||
- ./wg/data:/app/data
|
||||
- ./wg/config:/app/config
|
||||
```
|
||||
|
||||
- **Via a separate Docker container**:
|
||||
WireGuard Portal can interface with and control WireGuard running in another Docker container, such as the [linuxserver/wireguard](https://docs.linuxserver.io/images/docker-wireguard/) image.
|
||||
This method is useful in setups that already use `linuxserver/wireguard` or where you want to isolate the VPN backend from the portal frontend.
|
||||
For this, you need to set the network mode to `service:wireguard` in your docker-compose.yml file, `wireguard` is the service name of your WireGuard container.
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:v2
|
||||
container_name: wg-portal
|
||||
...
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
network_mode: "service:wireguard" # So we ensure to stay on the same network as the wireguard container.
|
||||
volumes:
|
||||
# host path : container path
|
||||
- ./wg/etc:/etc/wireguard
|
||||
- ./wg/data:/app/data
|
||||
- ./wg/config:/app/config
|
||||
|
||||
wireguard:
|
||||
image: lscr.io/linuxserver/wireguard:latest
|
||||
container_name: wireguard
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
# host port : container port
|
||||
- "51820:51820/udp" # WireGuard port, needs to match the port in wg-portal interface config
|
||||
- "8888:8888/tcp" # Noticed that the port of the web UI is exposed in the wireguard container.
|
||||
volumes:
|
||||
- ./wg/etc:/config/wg_confs # We share the configuration (wgx.conf) between wg-portal and wireguard
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
```
|
||||
As the `linuxserver/wireguard` image uses _wg-quick_ to manage the interfaces, you need to have at least the following configuration set for WireGuard Portal:
|
||||
```yaml
|
||||
core:
|
||||
# The WireGuard container uses wg-quick to manage the WireGuard interfaces - this conflicts with WireGuard Portal during startup.
|
||||
# To avoid this, we need to set the restore_state option to false so that wg-quick can create the interfaces.
|
||||
restore_state: false
|
||||
# Usually, there are no existing interfaces in the WireGuard container, so we can set this to false.
|
||||
import_existing: false
|
||||
advanced:
|
||||
# WireGuard Portal needs to export the WireGuard configuration as wg-quick config files so that the WireGuard container can use them.
|
||||
config_storage_path: /etc/wireguard/
|
||||
```
|
||||
|
||||
## Image Versioning
|
||||
|
||||
All images are hosted on Docker Hub at [https://hub.docker.com/r/wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal).
|
||||
All images are hosted on Docker Hub at [https://hub.docker.com/r/wgportal/wg-portal](https://hub.docker.com/r/wgportal/wg-portal) or in the [GitHub Container Registry](https://github.com/h44z/wg-portal/pkgs/container/wg-portal).
|
||||
|
||||
Version **2** is the current stable release. Version **1** has moved to legacy status and is no longer recommended.
|
||||
|
||||
There are three types of tags in the repository:
|
||||
|
||||
#### Semantic versioned tags
|
||||
For example, `1.0.19`.
|
||||
|
||||
These are official releases of WireGuard Portal. They correspond to the GitHub tags that we make, and you can see the release notes for them here: [https://github.com/h44z/wg-portal/releases](https://github.com/h44z/wg-portal/releases).
|
||||
For example, `2.0.0-rc.1` or `v2.0.0-rc.1`.
|
||||
|
||||
Once these tags show up in this repository, they will never change.
|
||||
These are official releases of WireGuard Portal. For production deployments of WireGuard Portal, we strongly recommend using one of these versioned tags instead of the latest or canary tags.
|
||||
|
||||
For production deployments of WireGuard Portal, we strongly recommend using one of these tags, e.g. **wgportal/wg-portal:1.0.19**, instead of the latest or canary tags.
|
||||
There are different types of these tags:
|
||||
|
||||
If you only want to stay at the same major or major+minor version, use either `v[MAJOR]` or `[MAJOR].[MINOR]` tags. For example `v1` or `1.0`.
|
||||
- Major version tags: `v2` or `2`. These tags always refer to the latest image for WireGuard Portal version **2**.
|
||||
- Minor version tags: `v2.x` or `2.0`. These tags always refer to the latest image for WireGuard Portal version **2.x**.
|
||||
- Specific version tags (patch version): `v2.0.0` or `2.0.0`. These tags denote a very specific release. They correspond to the GitHub tags that we make, and you can see the release notes for them here: [https://github.com/h44z/wg-portal/releases](https://github.com/h44z/wg-portal/releases). Once these tags for a specific version show up in the Docker repository, they will never change.
|
||||
|
||||
Version **1** is currently **stable**, version **2** is in **development**.
|
||||
#### The `latest` tag
|
||||
|
||||
#### latest
|
||||
This is the most recent build to master! It changes a lot and is very unstable.
|
||||
The lastest tag is the latest stable release of WireGuard Portal. For version **2**, this is the same as the `v2` tag.
|
||||
|
||||
We recommend that you don't use it except for development purposes.
|
||||
#### The `master` tag
|
||||
|
||||
#### Branch tags
|
||||
For each commit in the master and the stable branch, a corresponding Docker image is build. These images use the `master` or `stable` tags.
|
||||
This is the most recent build to the main branch! It changes a lot and is very unstable.
|
||||
|
||||
We recommend that you don't use it except for development purposes or to test the latest features.
|
||||
|
||||
|
||||
## Configuration
|
||||
You can configure WireGuard Portal using a yaml configuration file.
|
||||
The filepath of the yaml configuration file defaults to `/app/config/config.yml`.
|
||||
|
||||
You can configure WireGuard Portal using a YAML configuration file.
|
||||
The filepath of the YAML configuration file defaults to `/app/config/config.yaml`.
|
||||
It is possible to override the configuration filepath using the environment variable **WG_PORTAL_CONFIG**.
|
||||
|
||||
By default, WireGuard Portal uses a SQLite database. The database is stored in `/app/data/sqlite.db`.
|
||||
By default, WireGuard Portal uses an SQLite database. The database is stored in `/app/data/sqlite.db`.
|
||||
|
||||
You should mount those directories as a volume:
|
||||
- /app/data
|
||||
- /app/config
|
||||
|
||||
### Configuration Options
|
||||
All available YAML configuration options are available [here](https://github.com/h44z/wg-portal#configuration).
|
||||
- `/app/data`
|
||||
- `/app/config`
|
||||
|
||||
A very basic example:
|
||||
A detailed description of the configuration options can be found [here](../configuration/overview.md).
|
||||
|
||||
If you want to access configuration files in wg-quick format, you can mount the `/etc/wireguard` directory inside the container to a location of your choice.
|
||||
Also enable the `config_storage_path` option in the configuration file:
|
||||
```yaml
|
||||
core:
|
||||
admin_user: test@wg-portal.local
|
||||
admin_password: secret
|
||||
|
||||
web:
|
||||
external_url: http://localhost:8888
|
||||
request_logging: true
|
||||
advanced:
|
||||
config_storage_path: /etc/wireguard
|
||||
```
|
||||
|
||||
|
1
docs/documentation/getting-started/helm.md
Normal file
1
docs/documentation/getting-started/helm.md
Normal file
@@ -0,0 +1 @@
|
||||
--8<-- "./deploy/helm/README.md:16"
|
98
docs/documentation/getting-started/reverse-proxy.md
Normal file
98
docs/documentation/getting-started/reverse-proxy.md
Normal file
@@ -0,0 +1,98 @@
|
||||
## Reverse Proxy for HTTPS
|
||||
|
||||
For production deployments, always serve the WireGuard Portal over HTTPS. You have two options to secure your connection:
|
||||
|
||||
|
||||
### Reverse Proxy
|
||||
|
||||
Let a front‐end proxy handle HTTPS for you. This also frees you from managing certificates manually and is therefore the preferred option.
|
||||
You can use Nginx, Traefik, Caddy or any other proxy.
|
||||
|
||||
Below is an example using a Docker Compose stack with [Traefik](https://traefik.io/traefik/).
|
||||
It exposes the WireGuard Portal on `https://wg.domain.com` and redirects initial HTTP traffic to HTTPS.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik:v3.3
|
||||
restart: unless-stopped
|
||||
command:
|
||||
#- '--log.level=DEBUG'
|
||||
- '--providers.docker.endpoint=unix:///var/run/docker.sock'
|
||||
- '--providers.docker.exposedbydefault=false'
|
||||
- '--entrypoints.web.address=:80'
|
||||
- '--entrypoints.websecure.address=:443'
|
||||
- '--entrypoints.websecure.http3'
|
||||
- '--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true'
|
||||
- '--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web'
|
||||
- '--certificatesresolvers.letsencryptresolver.acme.email=your.email@domain.com'
|
||||
- '--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json'
|
||||
#- '--certificatesresolvers.letsencryptresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory' # just for testing
|
||||
ports:
|
||||
- 80:80 # for HTTP
|
||||
- 443:443/tcp # for HTTPS
|
||||
- 443:443/udp # for HTTP/3
|
||||
volumes:
|
||||
- acme-certs:/letsencrypt
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
# HTTP Catchall for redirecting HTTP -> HTTPS
|
||||
- 'traefik.http.routers.dashboard-catchall.rule=Host(`wg.domain.com`) && PathPrefix(`/`)'
|
||||
- 'traefik.http.routers.dashboard-catchall.entrypoints=web'
|
||||
- 'traefik.http.routers.dashboard-catchall.middlewares=redirect-to-https'
|
||||
- 'traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https'
|
||||
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:v2
|
||||
container_name: wg-portal
|
||||
restart: unless-stopped
|
||||
logging:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
# host port : container port
|
||||
# WireGuard port, needs to match the port in wg-portal interface config (add one port mapping for each interface)
|
||||
- "51820:51820/udp"
|
||||
# Web UI port (only available on localhost, Traefik will handle the HTTPS)
|
||||
- "127.0.0.1:8888:8888/tcp"
|
||||
sysctls:
|
||||
- net.ipv4.conf.all.src_valid_mark=1
|
||||
volumes:
|
||||
# host path : container path
|
||||
- ./wg/data:/app/data
|
||||
- ./wg/config:/app/config
|
||||
labels:
|
||||
- 'traefik.enable=true'
|
||||
- 'traefik.http.routers.wgportal.rule=Host(`wg.domain.com`)'
|
||||
- 'traefik.http.routers.wgportal.entrypoints=websecure'
|
||||
- 'traefik.http.routers.wgportal.tls.certresolver=letsencryptresolver'
|
||||
- 'traefik.http.routers.wgportal.service=wgportal'
|
||||
- 'traefik.http.services.wgportal.loadbalancer.server.port=8888'
|
||||
|
||||
volumes:
|
||||
acme-certs:
|
||||
```
|
||||
|
||||
The WireGuard Portal configuration must be updated accordingly so that the correct external URL is set for the web interface:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
external_url: https://wg.domain.com
|
||||
```
|
||||
|
||||
### Built-in TLS
|
||||
|
||||
If you prefer to let WireGuard Portal handle TLS itself, you can use the built-in TLS support.
|
||||
In your `config.yaml`, under the `web` section, point to your certificate and key files:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
cert_file: /path/to/your/fullchain.pem
|
||||
key_file: /path/to/your/privkey.pem
|
||||
```
|
||||
|
||||
The web server will then use these files to serve HTTPS traffic directly instead of HTTP.
|
26
docs/documentation/getting-started/sources.md
Normal file
26
docs/documentation/getting-started/sources.md
Normal file
@@ -0,0 +1,26 @@
|
||||
To build the application from source files, use the Makefile provided in the repository.
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [Make](https://www.gnu.org/software/make/)
|
||||
- [Go](https://go.dev/dl/): `>=1.24.0`
|
||||
- [Node.js with npm](https://nodejs.org/en/download): `node>=18, npm>=9`
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
# Get source code
|
||||
git clone https://github.com/h44z/wg-portal -b ${WG_PORTAL_VERSION:-master} --depth 1
|
||||
cd wg-portal
|
||||
# Build the frontend
|
||||
make frontend
|
||||
# Build the backend
|
||||
make build
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
Compiled binary will be available in `./dist` directory.
|
||||
|
||||
For installation instructions, check the [Binaries](./binaries.md) section.
|
@@ -1,25 +0,0 @@
|
||||
For production deployments of WireGuard Portal, we strongly recommend using version 1.
|
||||
If you want to use version 2, please be aware that it is still in beta and not feature complete.
|
||||
|
||||
## Upgrade from v1 to v2
|
||||
|
||||
> :warning: Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!
|
||||
|
||||
To start the upgrade process, start the wg-portal binary with the **-migrateFrom** parameter.
|
||||
The configuration (config.yml) for WireGuard Portal must be updated and valid before starting the upgrade.
|
||||
|
||||
To upgrade from a previous SQLite database, start wg-portal like:
|
||||
|
||||
```shell
|
||||
./wg-portal-amd64 -migrateFrom=old_wg_portal.db
|
||||
```
|
||||
|
||||
You can also specify the database type using the parameter **-migrateFromType**, supported types: mysql, mssql, postgres or sqlite.
|
||||
For example:
|
||||
|
||||
```shell
|
||||
./wg-portal-amd64 -migrateFromType=mysql -migrateFrom=user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local
|
||||
```
|
||||
|
||||
The upgrade will transform the old, existing database and store the values in the new database specified in the **config.yml** configuration file.
|
||||
Ensure that the new database does not contain any data!
|
32
docs/documentation/monitoring/prometheus.md
Normal file
32
docs/documentation/monitoring/prometheus.md
Normal file
@@ -0,0 +1,32 @@
|
||||
By default, WG-Portal exposes Prometheus metrics on port `8787` if interface/peer statistic data collection is enabled.
|
||||
|
||||
## Exposed Metrics
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------------------------------------------|-------|------------------------------------------------|
|
||||
| `wireguard_interface_received_bytes_total` | gauge | Bytes received through the interface. |
|
||||
| `wireguard_interface_sent_bytes_total` | gauge | Bytes sent through the interface. |
|
||||
| `wireguard_peer_last_handshake_seconds` | gauge | Seconds from the last handshake with the peer. |
|
||||
| `wireguard_peer_received_bytes_total` | gauge | Bytes received from the peer. |
|
||||
| `wireguard_peer_sent_bytes_total` | gauge | Bytes sent to the peer. |
|
||||
| `wireguard_peer_up` | gauge | Peer connection state (boolean: 1/0). |
|
||||
|
||||
## Prometheus Config
|
||||
|
||||
Add the following scrape job to your Prometheus config file:
|
||||
|
||||
```yaml
|
||||
# prometheus.yaml
|
||||
scrape_configs:
|
||||
- job_name: wg-portal
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:8787 # Change localhost to IP Address or hostname with WG-Portal
|
||||
```
|
||||
|
||||
# Grafana Dashboard
|
||||
|
||||
You may import [`dashboard.json`](https://github.com/h44z/wg-portal/blob/master/deploy/helm/files/dashboard.json) into your Grafana instance.
|
||||
|
||||

|
@@ -1,29 +1 @@
|
||||
**WireGuard Portal** is a simple, web based configuration portal for [WireGuard](https://wireguard.com).
|
||||
The portal uses the WireGuard [wgctrl](https://github.com/WireGuard/wgctrl-go) library to manage existing VPN
|
||||
interfaces. This allows for seamless activation or deactivation of new users, without disturbing existing VPN
|
||||
connections.
|
||||
|
||||
The configuration portal supports using a database (SQLite, MySQL, MsSQL or Postgres), OAuth or LDAP
|
||||
(Active Directory or OpenLDAP) as a user source for authentication and profile data.
|
||||
|
||||
## Features
|
||||
* Self-hosted - the whole application is a single binary
|
||||
* Responsive web UI written in Vue.JS
|
||||
* Automatically select IP from the network pool assigned to client
|
||||
* QR-Code for convenient mobile client configuration
|
||||
* Sent email to client with QR-code and client config
|
||||
* Enable / Disable clients seamlessly
|
||||
* Generation of wg-quick configuration file (`wgX.conf`) if required
|
||||
* User authentication (database, OAuth or LDAP)
|
||||
* IPv6 ready
|
||||
* Docker ready
|
||||
* Can be used with existing WireGuard setups
|
||||
* Support for multiple WireGuard interfaces
|
||||
* Peer Expiry Feature
|
||||
* Handle route and DNS settings like wg-quick does
|
||||
* ~~REST API for management and client deployment~~ (coming soon)
|
||||
|
||||
## Quick-Start
|
||||
|
||||
The easiest way to get started is to use the provided [Docker image](./getting-started/docker.md).
|
||||
|
||||
--8<-- "README.md:12:41"
|
||||
|
1
docs/documentation/rest-api/api-doc.md
Normal file
1
docs/documentation/rest-api/api-doc.md
Normal file
@@ -0,0 +1 @@
|
||||
<swagger-ui src="./swagger.yaml"/>
|
1559
docs/documentation/rest-api/swagger.yaml
Normal file
1559
docs/documentation/rest-api/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
37
docs/documentation/upgrade/v1.md
Normal file
37
docs/documentation/upgrade/v1.md
Normal file
@@ -0,0 +1,37 @@
|
||||
Major upgrades between different versions may require special procedures, which are described in the following sections.
|
||||
|
||||
## Upgrade from v1 to v2
|
||||
|
||||
> :warning: Before upgrading from V1, make sure that you have a backup of your currently working configuration files and database!
|
||||
|
||||
To start the upgrade process, start the wg-portal binary with the **-migrateFrom** parameter.
|
||||
The configuration (config.yaml) for WireGuard Portal must be updated and valid before starting the upgrade.
|
||||
|
||||
To upgrade from a previous SQLite database, start wg-portal like:
|
||||
|
||||
```shell
|
||||
./wg-portal-amd64 -migrateFrom=old_wg_portal.db
|
||||
```
|
||||
|
||||
You can also specify the database type using the parameter **-migrateFromType**.
|
||||
Supported database types: `mysql`, `mssql`, `postgres` or `sqlite`.
|
||||
|
||||
For example:
|
||||
|
||||
```shell
|
||||
./wg-portal-amd64 -migrateFromType=mysql -migrateFrom='user:pass@tcp(1.2.3.4:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local'
|
||||
```
|
||||
|
||||
The upgrade will transform the old, existing database and store the values in the new database specified in the **config.yaml** configuration file.
|
||||
Ensure that the new database does not contain any data!
|
||||
|
||||
If you are using Docker, you can adapt the docker-compose.yml file to start the upgrade process:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
wg-portal:
|
||||
image: wgportal/wg-portal:v2
|
||||
# ... other settings
|
||||
restart: no
|
||||
command: ["-migrateFrom=/app/data/old_wg_portal.db"]
|
||||
```
|
@@ -327,7 +327,7 @@
|
||||
<div class="md-container">
|
||||
<div class="tx-hero__image">
|
||||
<img
|
||||
src="{{config.site_url}}assets/images/screenshot.png"
|
||||
src="{{config.site_url}}/assets/images/screenshot.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
>
|
||||
@@ -356,7 +356,7 @@
|
||||
<div class="second-column">
|
||||
<div class="image-wrapper">
|
||||
<img
|
||||
src="{{config.site_url}}assets/images/wg-tool.png"
|
||||
src="{{config.site_url}}/assets/images/wg-tool.png"
|
||||
alt=""
|
||||
draggable="false"
|
||||
>
|
||||
|
@@ -24,7 +24,7 @@
|
||||
<div id="toasts"></div>
|
||||
|
||||
<!-- main application -->
|
||||
<div id="app"></div>
|
||||
<div id="app" class="d-flex flex-column flex-grow-1"></div>
|
||||
|
||||
<!-- vue teleport will add modals and dialogs here -->
|
||||
<div id="modals"></div>
|
||||
|
1752
frontend/package-lock.json
generated
1752
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,24 +8,27 @@
|
||||
"preview": "vite preview --port 5050"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.5.1",
|
||||
"@kyvg/vue3-notification": "^3.1.3",
|
||||
"@fontsource/nunito-sans": "^5.2.5",
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"@kyvg/vue3-notification": "^3.4.1",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootswatch": "^5.3.2",
|
||||
"flag-icons": "^7.1.0",
|
||||
"is-cidr": "^5.0.3",
|
||||
"@vojtechlanka/vue-tags-input": "^3.1.1",
|
||||
"bootstrap": "^5.3.5",
|
||||
"bootswatch": "^5.3.5",
|
||||
"flag-icons": "^7.3.2",
|
||||
"ip-address": "^10.0.1",
|
||||
"is-cidr": "^5.1.1",
|
||||
"is-ip": "^5.0.1",
|
||||
"pinia": "^2.1.7",
|
||||
"prismjs": "^1.29.0",
|
||||
"vue": "^3.3.13",
|
||||
"vue-i18n": "^9.8.0",
|
||||
"pinia": "^3.0.2",
|
||||
"prismjs": "^1.30.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^11.1.3",
|
||||
"vue-prism-component": "github:h44z/vue-prism-component",
|
||||
"vue-router": "^4.2.5",
|
||||
"vue3-tags-input": "^1.0.12"
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"vite": "^5.0.10"
|
||||
"@vitejs/plugin-vue": "^5.2.3",
|
||||
"sass-embedded": "^1.86.3",
|
||||
"vite": "6.3.4"
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { computed, getCurrentInstance, onMounted, ref } from "vue";
|
||||
import { authStore } from "./stores/auth";
|
||||
import { securityStore } from "./stores/security";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
import { Notifications } from "@kyvg/vue3-notification";
|
||||
|
||||
const appGlobal = getCurrentInstance().appContext.config.globalProperties
|
||||
const auth = authStore()
|
||||
@@ -42,10 +43,18 @@ const switchLanguage = function (lang) {
|
||||
const languageFlag = computed(() => {
|
||||
// `this` points to the component instance
|
||||
let lang = appGlobal.$i18n.locale.toLowerCase();
|
||||
if (lang === "en") {
|
||||
lang = "us";
|
||||
if (!appGlobal.$i18n.availableLocales.includes(lang)) {
|
||||
lang = appGlobal.$i18n.fallbackLocale;
|
||||
}
|
||||
return "fi-" + lang;
|
||||
const langMap = {
|
||||
en: "us",
|
||||
pt: "pt",
|
||||
uk: "ua",
|
||||
zh: "cn",
|
||||
ko: "kr",
|
||||
|
||||
};
|
||||
return "fi-" + (langMap[lang] || lang);
|
||||
})
|
||||
|
||||
const companyName = ref(WGPORTAL_SITE_COMPANY_NAME);
|
||||
@@ -59,7 +68,7 @@ const currentYear = ref(new Date().getFullYear())
|
||||
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/"><img alt="WireGuard Portal" src="/img/header-logo.png" /></a>
|
||||
<a class="navbar-brand" href="/"><img :alt="companyName" src="/img/header-logo.png" /></a>
|
||||
<button aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
|
||||
data-bs-target="#navbarTop" data-bs-toggle="collapse" type="button">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@@ -76,6 +85,9 @@ const currentYear = ref(new Date().getFullYear())
|
||||
<li v-if="auth.IsAuthenticated && auth.IsAdmin" class="nav-item">
|
||||
<RouterLink :to="{ name: 'users' }" class="nav-link">{{ $t('menu.users') }}</RouterLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<RouterLink :to="{ name: 'key-generator' }" class="nav-link">{{ $t('menu.keygen') }}</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="navbar-nav d-flex justify-content-end">
|
||||
@@ -84,6 +96,8 @@ const currentYear = ref(new Date().getFullYear())
|
||||
href="#" role="button">{{ auth.User.Firstname }} {{ auth.User.Lastname }}</a>
|
||||
<div class="dropdown-menu">
|
||||
<RouterLink :to="{ name: 'profile' }" class="dropdown-item"><i class="fas fa-user"></i> {{ $t('menu.profile') }}</RouterLink>
|
||||
<RouterLink :to="{ name: 'settings' }" class="dropdown-item" v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')"><i class="fas fa-gears"></i> {{ $t('menu.settings') }}</RouterLink>
|
||||
<RouterLink :to="{ name: 'audit' }" class="dropdown-item" v-if="auth.IsAdmin"><i class="fas fa-file-shield"></i> {{ $t('menu.audit') }}</RouterLink>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" @click.prevent="auth.Logout"><i class="fas fa-sign-out-alt"></i> {{ $t('menu.logout') }}</a>
|
||||
</div>
|
||||
@@ -110,15 +124,23 @@ const currentYear = ref(new Date().getFullYear())
|
||||
<button aria-expanded="false" aria-haspopup="true" class="btn btn btn-secondary pe-0"
|
||||
data-bs-toggle="dropdown" type="button"><span :class="languageFlag" class="fi"></span></button>
|
||||
<div aria-labelledby="btnGroupDrop3" class="dropdown-menu" style="">
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('en')"><span class="fi fi-us"></span>English</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('de')"><span class="fi fi-de"></span>Deutsch</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ru')"><span class="fi fi-ru"></span>Русский</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('de')"><span class="fi fi-de"></span> Deutsch</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('en')"><span class="fi fi-us"></span> English</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('fr')"><span class="fi fi-fr"></span> Français</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ko')"><span class="fi fi-kr"></span> 한국어</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('pt')"><span class="fi fi-pt"></span> Português</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('ru')"><span class="fi fi-ru"></span> Русский</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('uk')"><span class="fi fi-ua"></span> Українська</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('vi')"><span class="fi fi-vi"></span> Tiếng Việt</a>
|
||||
<a class="dropdown-item" href="#" @click.prevent="switchLanguage('zh')"><span class="fi fi-cn"></span> 中文</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer></template>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style></style>
|
||||
|
@@ -1,5 +1,99 @@
|
||||
a.disabled {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
color: #888888;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.text-wrap {
|
||||
overflow-break: anywhere;
|
||||
}
|
||||
|
||||
.asc::after {
|
||||
content: " ↑";
|
||||
}
|
||||
|
||||
.desc::after {
|
||||
content: " ↓";
|
||||
}
|
||||
|
||||
/* style the background and the text color of the input ... */
|
||||
.vue-tags-input {
|
||||
max-width: 100% !important;
|
||||
background-color: #f7f7f9 !important;
|
||||
padding: 0 0;
|
||||
}
|
||||
|
||||
.vue-tags-input .ti-input {
|
||||
padding: 0 0;
|
||||
border: none !important;
|
||||
transition: border-bottom 200ms ease;
|
||||
}
|
||||
|
||||
.vue-tags-input .ti-new-tag-input {
|
||||
background: transparent;
|
||||
color: var(--bs-body-color);
|
||||
padding: 0.75rem 1.5rem !important;
|
||||
}
|
||||
|
||||
|
||||
/* style the placeholders color across all browser */
|
||||
.vue-tags-input ::-webkit-input-placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
.vue-tags-input .ti-input::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.vue-tags-input ::-moz-placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.vue-tags-input :-ms-input-placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
.vue-tags-input :-moz-placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
}
|
||||
|
||||
/* default styles for all the tags */
|
||||
.vue-tags-input .ti-tag {
|
||||
position: relative;
|
||||
background: #ffffff;
|
||||
border: 2px solid var(--bs-body-color);
|
||||
margin: 6px;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
/* the styles if a tag is invalid */
|
||||
.vue-tags-input .ti-tag.ti-invalid {
|
||||
background-color: #e88a74;
|
||||
}
|
||||
|
||||
/* if the user input is invalid, the input color should be red */
|
||||
.vue-tags-input .ti-new-tag-input.ti-invalid {
|
||||
color: #e88a74;
|
||||
}
|
||||
|
||||
/* if a tag or the user input is a duplicate, it should be crossed out */
|
||||
.vue-tags-input .ti-duplicate span,
|
||||
.vue-tags-input .ti-new-tag-input.ti-duplicate {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* if the user presses backspace, the complete tag should be crossed out, to mark it for deletion */
|
||||
.vue-tags-input .ti-tag:after {
|
||||
transition: transform .2s;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 2px;
|
||||
width: 108%;
|
||||
left: -4%;
|
||||
top: calc(50% - 1px);
|
||||
background-color: #000;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
.vue-tags-input .ti-deletion-mark:after {
|
||||
transform: scaleX(1);
|
||||
}
|
20
frontend/src/assets/custom.scss
Normal file
20
frontend/src/assets/custom.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
// disable external web fonts
|
||||
$web-font-path: false;
|
||||
|
||||
@import "bootswatch/dist/lux/variables";
|
||||
@import "bootstrap/scss/bootstrap";
|
||||
@import "bootswatch/dist/lux/bootswatch";
|
||||
|
||||
// fix strange border width bug in bootswatch 5.3
|
||||
:root {
|
||||
--bs-border-width: 1px;
|
||||
}
|
||||
|
||||
// for future use, once bootswatch supports @use
|
||||
/*
|
||||
@use "bootswatch/dist/lux/_variables.scss" as lux-vars with (
|
||||
$web-font-path: false
|
||||
);
|
||||
@use "bootstrap/scss/bootstrap" as bs;
|
||||
@use "bootswatch/dist/lux/_bootswatch.scss" as lux-theme;
|
||||
*/
|
@@ -4,7 +4,7 @@ import {interfaceStore} from "@/stores/interfaces";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
import Vue3TagsInput from 'vue3-tags-input';
|
||||
import { VueTagsInput } from '@vojtechlanka/vue-tags-input';
|
||||
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||
import isCidr from "is-cidr";
|
||||
import {isIP} from 'is-ip';
|
||||
@@ -38,6 +38,15 @@ const title = computed(() => {
|
||||
return t("modals.interface-edit.headline-new")
|
||||
})
|
||||
|
||||
const currentTags = ref({
|
||||
Addresses: "",
|
||||
Dns: "",
|
||||
DnsSearch: "",
|
||||
PeerDefNetwork: "",
|
||||
PeerDefAllowedIPs: "",
|
||||
PeerDefDns: "",
|
||||
PeerDefDnsSearch: ""
|
||||
})
|
||||
const formData = ref(freshInterface())
|
||||
|
||||
// functions
|
||||
@@ -137,94 +146,94 @@ function close() {
|
||||
function handleChangeAddresses(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(isCidr(tag) === 0) {
|
||||
if(isCidr(tag.text) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
formData.value.Addresses = tags
|
||||
formData.value.Addresses = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeDns(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(!isIP(tag)) {
|
||||
if(!isIP(tag.text)) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid IP",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
formData.value.Dns = tags
|
||||
formData.value.Dns = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeDnsSearch(tags) {
|
||||
formData.value.DnsSearch = tags
|
||||
formData.value.DnsSearch = tags.map(tag => tag.text)
|
||||
}
|
||||
|
||||
function handleChangePeerDefNetwork(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(isCidr(tag) === 0) {
|
||||
if(isCidr(tag.text) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
formData.value.PeerDefNetwork = tags
|
||||
formData.value.PeerDefNetwork = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangePeerDefAllowedIPs(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(isCidr(tag) === 0) {
|
||||
if(isCidr(tag.text) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
formData.value.PeerDefAllowedIPs = tags
|
||||
formData.value.PeerDefAllowedIPs = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangePeerDefDns(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if(!isIP(tag)) {
|
||||
if(!isIP(tag.text)) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid IP",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if(validInput) {
|
||||
formData.value.PeerDefDns = tags
|
||||
formData.value.PeerDefDns = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangePeerDefDnsSearch(tags) {
|
||||
formData.value.PeerDefDnsSearch = tags
|
||||
formData.value.PeerDefDnsSearch = tags.map(tag => tag.text)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
@@ -322,22 +331,26 @@ async function del() {
|
||||
<legend class="mt-4">{{ $t('modals.interface-edit.header-crypto') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.private-key.label') }}</label>
|
||||
<input v-model="formData.PrivateKey" class="form-control" :placeholder="$t('modals.interface-edit.private-key.placeholder')" required type="email">
|
||||
<input v-model="formData.PrivateKey" class="form-control" :placeholder="$t('modals.interface-edit.private-key.placeholder')" required type="text">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.public-key.label') }}</label>
|
||||
<input v-model="formData.PublicKey" class="form-control" :placeholder="$t('modals.interface-edit.public-key.placeholder')" required type="email">
|
||||
<input v-model="formData.PublicKey" class="form-control" :placeholder="$t('modals.interface-edit.public-key.placeholder')" required type="text">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend class="mt-4">{{ $t('modals.interface-edit.header-network') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
||||
:placeholder="$t('modals.interface-edit.ip.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR"
|
||||
@on-tags-changed="handleChangeAddresses"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.Addresses"
|
||||
:tags="formData.Addresses.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.ip.placeholder')"
|
||||
:validation="validateCIDR()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeAddresses"/>
|
||||
</div>
|
||||
<div v-if="formData.Mode==='server'" class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.listen-port.label') }}</label>
|
||||
@@ -345,19 +358,27 @@ async function del() {
|
||||
</div>
|
||||
<div v-if="formData.Mode!=='server'" class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Dns"
|
||||
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateIP"
|
||||
@on-tags-changed="handleChangeDns"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.Dns"
|
||||
:tags="formData.Dns.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
||||
:validation="validateIP()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeDns"/>
|
||||
</div>
|
||||
<div v-if="formData.Mode!=='server'" class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.DnsSearch"
|
||||
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateDomain"
|
||||
@on-tags-changed="handleChangeDnsSearch"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.DnsSearch"
|
||||
:tags="formData.DnsSearch.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
||||
:validation="validateDomain()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeDnsSearch"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
@@ -420,36 +441,52 @@ async function del() {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.networks.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefNetwork"
|
||||
:placeholder="$t('modals.interface-edit.defaults.networks.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR"
|
||||
@on-tags-changed="handleChangePeerDefNetwork"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.PeerDefNetwork"
|
||||
:tags="formData.PeerDefNetwork.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.defaults.networks.placeholder')"
|
||||
:validation="validateCIDR()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangePeerDefNetwork"/>
|
||||
<small class="form-text text-muted">{{ $t('modals.interface-edit.defaults.networks.description') }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.defaults.allowed-ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefAllowedIPs"
|
||||
:placeholder="$t('modals.interface-edit.defaults.allowed-ip.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR"
|
||||
@on-tags-changed="handleChangePeerDefAllowedIPs"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.PeerDefAllowedIPs"
|
||||
:tags="formData.PeerDefAllowedIPs.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.defaults.allowed-ip.placeholder')"
|
||||
:validation="validateCIDR()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangePeerDefAllowedIPs"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefDns"
|
||||
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateIP"
|
||||
@on-tags-changed="handleChangePeerDefDns"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.PeerDefDns"
|
||||
:tags="formData.PeerDefDns.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.dns.placeholder')"
|
||||
:validation="validateIP()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangePeerDefDns"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.interface-edit.dns-search.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.PeerDefDnsSearch"
|
||||
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateDomain"
|
||||
@on-tags-changed="handleChangePeerDefDnsSearch"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTags.PeerDefDnsSearch"
|
||||
:tags="formData.PeerDefDnsSearch.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.interface-edit.dns-search.placeholder')"
|
||||
:validation="validateDomain()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangePeerDefDnsSearch"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
|
@@ -5,7 +5,7 @@ import { interfaceStore } from "@/stores/interfaces";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
import Vue3TagsInput from "vue3-tags-input";
|
||||
import { VueTagsInput } from '@vojtechlanka/vue-tags-input';
|
||||
import { validateCIDR, validateIP, validateDomain } from '@/helpers/validators';
|
||||
import isCidr from "is-cidr";
|
||||
import { isIP } from 'is-ip';
|
||||
@@ -65,6 +65,13 @@ const title = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const currentTags = ref({
|
||||
Addresses: "",
|
||||
AllowedIPs: "",
|
||||
ExtraAllowedIPs: "",
|
||||
Dns: "",
|
||||
DnsSearch: ""
|
||||
})
|
||||
const formData = ref(freshPeer())
|
||||
|
||||
// functions
|
||||
@@ -193,73 +200,73 @@ function close() {
|
||||
function handleChangeAddresses(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if (isCidr(tag) === 0) {
|
||||
if (isCidr(tag.text) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if (validInput) {
|
||||
formData.value.Addresses = tags
|
||||
formData.value.Addresses = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeAllowedIPs(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if (isCidr(tag) === 0) {
|
||||
if (isCidr(tag.text) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if (validInput) {
|
||||
formData.value.AllowedIPs.Value = tags
|
||||
formData.value.AllowedIPs.Value = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeExtraAllowedIPs(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if (isCidr(tag) === 0) {
|
||||
if (isCidr(tag.text) === 0) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid CIDR",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if (validInput) {
|
||||
formData.value.ExtraAllowedIPs = tags
|
||||
formData.value.ExtraAllowedIPs = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeDns(tags) {
|
||||
let validInput = true
|
||||
tags.forEach(tag => {
|
||||
if (!isIP(tag)) {
|
||||
if (!isIP(tag.text)) {
|
||||
validInput = false
|
||||
notify({
|
||||
title: "Invalid IP",
|
||||
text: tag + " is not a valid IP address",
|
||||
text: tag.text + " is not a valid IP address",
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
})
|
||||
if (validInput) {
|
||||
formData.value.Dns.Value = tags
|
||||
formData.value.Dns.Value = tags.map(tag => tag.text)
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeDnsSearch(tags) {
|
||||
formData.value.DnsSearch.Value = tags
|
||||
formData.value.DnsSearch.Value = tags.map(tag => tag.text)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
@@ -316,17 +323,18 @@ async function del() {
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-crypto') }}</legend>
|
||||
<div class="form-group" v-if="selectedInterface.Mode === 'server'">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.private-key.label') }}</label>
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.private-key.placeholder')" required
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.private-key.placeholder')" required
|
||||
v-model="formData.PrivateKey">
|
||||
<small id="privateKeyHelp" class="form-text text-muted">{{ $t('modals.peer-edit.private-key.help') }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.public-key.label') }}</label>
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required
|
||||
v-model="formData.PublicKey">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.preshared-key.label') }}</label>
|
||||
<input type="email" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')"
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')"
|
||||
v-model="formData.PresharedKey">
|
||||
</div>
|
||||
<div class="form-group" v-if="formData.Mode === 'client'">
|
||||
@@ -344,34 +352,64 @@ async function del() {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Addresses"
|
||||
:placeholder="$t('modals.peer-edit.ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR" @on-tags-changed="handleChangeAddresses" />
|
||||
<vue-tags-input class="form-control" v-model="currentTags.Addresses"
|
||||
:tags="formData.Addresses.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.peer-edit.ip.placeholder')"
|
||||
:validation="validateCIDR()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeAddresses" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.allowed-ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.AllowedIPs.Value"
|
||||
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR" @on-tags-changed="handleChangeAllowedIPs" />
|
||||
<vue-tags-input class="form-control" v-model="currentTags.AllowedIPs"
|
||||
:tags="formData.AllowedIPs.Value.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.peer-edit.allowed-ip.placeholder')"
|
||||
:validation="validateCIDR()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeAllowedIPs" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.extra-allowed-ip.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.ExtraAllowedIPs"
|
||||
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateCIDR" @on-tags-changed="handleChangeExtraAllowedIPs" />
|
||||
<vue-tags-input class="form-control" v-model="currentTags.ExtraAllowedIPs"
|
||||
:tags="formData.ExtraAllowedIPs.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.peer-edit.extra-allowed-ip.placeholder')"
|
||||
:validation="validateCIDR()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeExtraAllowedIPs" />
|
||||
<small class="form-text text-muted">{{ $t('modals.peer-edit.extra-allowed-ip.description') }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Dns.Value"
|
||||
:placeholder="$t('modals.peer-edit.dns.placeholder')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateIP" @on-tags-changed="handleChangeDns" />
|
||||
<vue-tags-input class="form-control" v-model="currentTags.Dns"
|
||||
:tags="formData.Dns.Value.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.peer-edit.dns.placeholder')"
|
||||
:validation="validateIP()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeDns" />
|
||||
</div>
|
||||
<div hidden class="form-group">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.dns-search.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.DnsSearch.Value"
|
||||
:placeholder="$t('modals.peer-edit.dns-search.label')" :add-tag-on-keys="[13, 188, 32, 9]"
|
||||
:validate="validateDomain" @on-tags-changed="handleChangeDnsSearch" />
|
||||
<vue-tags-input class="form-control" v-model="currentTags.DnsSearch"
|
||||
:tags="formData.DnsSearch.Value.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.peer-edit.dns-search.label')"
|
||||
:validation="validateDomain()"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeDnsSearch" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
|
@@ -5,7 +5,7 @@ import {interfaceStore} from "@/stores/interfaces";
|
||||
import {computed, ref} from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
import Vue3TagsInput from "vue3-tags-input";
|
||||
import { VueTagsInput } from '@vojtechlanka/vue-tags-input';
|
||||
import { freshInterface } from '@/helpers/models';
|
||||
|
||||
const { t } = useI18n()
|
||||
@@ -36,6 +36,7 @@ function freshForm() {
|
||||
}
|
||||
}
|
||||
|
||||
const currentTag = ref("")
|
||||
const formData = ref(freshForm())
|
||||
|
||||
const title = computed(() => {
|
||||
@@ -55,7 +56,7 @@ function close() {
|
||||
}
|
||||
|
||||
function handleChangeUserIdentifiers(tags) {
|
||||
formData.value.Identifiers = tags
|
||||
formData.value.Identifiers = tags.map(tag => tag.text)
|
||||
}
|
||||
|
||||
async function save() {
|
||||
@@ -89,10 +90,14 @@ async function save() {
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-multi-create.identifiers.label') }}</label>
|
||||
<vue3-tags-input class="form-control" :tags="formData.Identifiers"
|
||||
:placeholder="$t('modals.peer-multi-create.identifiers.placeholder')"
|
||||
:add-tag-on-keys="[13, 188, 32, 9]"
|
||||
@on-tags-changed="handleChangeUserIdentifiers"/>
|
||||
<vue-tags-input class="form-control" v-model="currentTag"
|
||||
:tags="formData.Identifiers.map(str => ({ text: str }))"
|
||||
:placeholder="$t('modals.peer-multi-create.identifiers.placeholder')"
|
||||
:add-on-key="[13, 188, 32, 9]"
|
||||
:save-on-key="[13, 188, 32, 9]"
|
||||
:allow-edit-tags="true"
|
||||
:separators="[',', ';', ' ']"
|
||||
@tags-changed="handleChangeUserIdentifiers"/>
|
||||
<small class="form-text text-muted">{{ $t('modals.peer-multi-create.identifiers.description') }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
@@ -89,19 +89,11 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
||||
|
||||
function download() {
|
||||
// credit: https://www.bitdegree.org/learn/javascript-download
|
||||
let filename = 'WireGuard-Tunnel.conf'
|
||||
if (selectedPeer.value.DisplayName) {
|
||||
filename = selectedPeer.value.DisplayName
|
||||
.replace(/ /g, "_")
|
||||
.replace(/[^a-zA-Z0-9-_]/g, "")
|
||||
.substring(0, 16)
|
||||
+ ".conf"
|
||||
}
|
||||
let text = configString.value
|
||||
|
||||
let element = document.createElement('a')
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))
|
||||
element.setAttribute('download', filename)
|
||||
element.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(text))
|
||||
element.setAttribute('download', selectedPeer.value.Filename)
|
||||
|
||||
element.style.display = 'none'
|
||||
document.body.appendChild(element)
|
||||
@@ -212,7 +204,7 @@ function ConfigQrUrl() {
|
||||
<div class="flex-fill text-start">
|
||||
<button @click.prevent="download" type="button" class="btn btn-primary me-1">{{
|
||||
$t('modals.peer-view.button-download') }}</button>
|
||||
<button @click.prevent="email" hidden type="button" class="btn btn-primary me-1">{{
|
||||
<button @click.prevent="email" type="button" class="btn btn-primary me-1">{{
|
||||
$t('modals.peer-view.button-email') }}</button>
|
||||
</div>
|
||||
<button @click.prevent="close" type="button" class="btn btn-secondary">{{ $t('general.close') }}</button>
|
||||
|
@@ -51,6 +51,7 @@ watch(() => props.visible, async (newValue, oldValue) => {
|
||||
formData.value.Notes = selectedUser.value.Notes
|
||||
formData.value.Password = ""
|
||||
formData.value.Disabled = selectedUser.value.Disabled
|
||||
formData.value.Locked = selectedUser.value.Locked
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +166,7 @@ async function del() {
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex-fill text-start">
|
||||
<button v-if="props.userId!=='#NEW#'&&formData.Source==='db'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ $t('general.delete') }}</button>
|
||||
<button v-if="props.userId!=='#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{ $t('general.delete') }}</button>
|
||||
</div>
|
||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||
|
295
frontend/src/components/UserPeerEditModal.vue
Normal file
295
frontend/src/components/UserPeerEditModal.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<script setup>
|
||||
import Modal from "./Modal.vue";
|
||||
import { peerStore } from "@/stores/peers";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
import { freshPeer, freshInterface } from '@/helpers/models';
|
||||
import { profileStore } from "@/stores/profile";
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const peers = peerStore()
|
||||
const profile = profileStore()
|
||||
|
||||
const props = defineProps({
|
||||
peerId: String,
|
||||
visible: Boolean,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const selectedPeer = computed(() => {
|
||||
let p = peers.Find(props.peerId)
|
||||
|
||||
if (!p) {
|
||||
if (!!props.peerId || props.peerId.length) {
|
||||
p = profile.peers.find((p) => p.Identifier === props.peerId)
|
||||
} else {
|
||||
p = freshPeer() // dummy peer to avoid 'undefined' exceptions
|
||||
}
|
||||
}
|
||||
return p
|
||||
})
|
||||
|
||||
const selectedInterface = computed(() => {
|
||||
let iId = profile.selectedInterfaceId;
|
||||
|
||||
let i = freshInterface() // dummy interface to avoid 'undefined' exceptions
|
||||
if (iId) {
|
||||
i = profile.interfaces.find((i) => i.Identifier === iId)
|
||||
}
|
||||
|
||||
return i
|
||||
})
|
||||
|
||||
const title = computed(() => {
|
||||
if (!props.visible) {
|
||||
return ""
|
||||
}
|
||||
|
||||
if (selectedPeer.value) {
|
||||
return t("modals.peer-edit.headline-edit-peer") + " " + selectedPeer.value.Identifier
|
||||
}
|
||||
return t("modals.peer-edit.headline-new-peer")
|
||||
})
|
||||
|
||||
const formData = ref(freshPeer())
|
||||
|
||||
// functions
|
||||
|
||||
watch(() => props.visible, async (newValue, oldValue) => {
|
||||
if (oldValue === false && newValue === true) { // if modal is shown
|
||||
if (!selectedPeer.value) {
|
||||
await peers.PreparePeer(selectedInterface.value.Identifier)
|
||||
|
||||
formData.value.Identifier = peers.Prepared.Identifier
|
||||
formData.value.DisplayName = peers.Prepared.DisplayName
|
||||
formData.value.UserIdentifier = peers.Prepared.UserIdentifier
|
||||
formData.value.InterfaceIdentifier = peers.Prepared.InterfaceIdentifier
|
||||
formData.value.Disabled = peers.Prepared.Disabled
|
||||
formData.value.ExpiresAt = peers.Prepared.ExpiresAt
|
||||
formData.value.Notes = peers.Prepared.Notes
|
||||
|
||||
formData.value.Endpoint = peers.Prepared.Endpoint
|
||||
formData.value.EndpointPublicKey = peers.Prepared.EndpointPublicKey
|
||||
formData.value.AllowedIPs = peers.Prepared.AllowedIPs
|
||||
formData.value.ExtraAllowedIPs = peers.Prepared.ExtraAllowedIPs
|
||||
formData.value.PresharedKey = peers.Prepared.PresharedKey
|
||||
formData.value.PersistentKeepalive = peers.Prepared.PersistentKeepalive
|
||||
|
||||
formData.value.PrivateKey = peers.Prepared.PrivateKey
|
||||
formData.value.PublicKey = peers.Prepared.PublicKey
|
||||
|
||||
formData.value.Mode = peers.Prepared.Mode
|
||||
|
||||
formData.value.Addresses = peers.Prepared.Addresses
|
||||
formData.value.CheckAliveAddress = peers.Prepared.CheckAliveAddress
|
||||
formData.value.Dns = peers.Prepared.Dns
|
||||
formData.value.DnsSearch = peers.Prepared.DnsSearch
|
||||
formData.value.Mtu = peers.Prepared.Mtu
|
||||
formData.value.FirewallMark = peers.Prepared.FirewallMark
|
||||
formData.value.RoutingTable = peers.Prepared.RoutingTable
|
||||
|
||||
formData.value.PreUp = peers.Prepared.PreUp
|
||||
formData.value.PostUp = peers.Prepared.PostUp
|
||||
formData.value.PreDown = peers.Prepared.PreDown
|
||||
formData.value.PostDown = peers.Prepared.PostDown
|
||||
|
||||
} else { // fill existing data
|
||||
formData.value.Identifier = selectedPeer.value.Identifier
|
||||
formData.value.DisplayName = selectedPeer.value.DisplayName
|
||||
formData.value.UserIdentifier = selectedPeer.value.UserIdentifier
|
||||
formData.value.InterfaceIdentifier = selectedPeer.value.InterfaceIdentifier
|
||||
formData.value.Disabled = selectedPeer.value.Disabled
|
||||
formData.value.ExpiresAt = selectedPeer.value.ExpiresAt
|
||||
formData.value.Notes = selectedPeer.value.Notes
|
||||
|
||||
formData.value.Endpoint = selectedPeer.value.Endpoint
|
||||
formData.value.EndpointPublicKey = selectedPeer.value.EndpointPublicKey
|
||||
formData.value.AllowedIPs = selectedPeer.value.AllowedIPs
|
||||
formData.value.ExtraAllowedIPs = selectedPeer.value.ExtraAllowedIPs
|
||||
formData.value.PresharedKey = selectedPeer.value.PresharedKey
|
||||
formData.value.PersistentKeepalive = selectedPeer.value.PersistentKeepalive
|
||||
|
||||
formData.value.PrivateKey = selectedPeer.value.PrivateKey
|
||||
formData.value.PublicKey = selectedPeer.value.PublicKey
|
||||
|
||||
formData.value.Mode = selectedPeer.value.Mode
|
||||
|
||||
formData.value.Addresses = selectedPeer.value.Addresses
|
||||
formData.value.CheckAliveAddress = selectedPeer.value.CheckAliveAddress
|
||||
formData.value.Dns = selectedPeer.value.Dns
|
||||
formData.value.DnsSearch = selectedPeer.value.DnsSearch
|
||||
formData.value.Mtu = selectedPeer.value.Mtu
|
||||
formData.value.FirewallMark = selectedPeer.value.FirewallMark
|
||||
formData.value.RoutingTable = selectedPeer.value.RoutingTable
|
||||
|
||||
formData.value.PreUp = selectedPeer.value.PreUp
|
||||
formData.value.PostUp = selectedPeer.value.PostUp
|
||||
formData.value.PreDown = selectedPeer.value.PreDown
|
||||
formData.value.PostDown = selectedPeer.value.PostDown
|
||||
|
||||
if (!formData.value.Endpoint.Overridable ||
|
||||
!formData.value.EndpointPublicKey.Overridable ||
|
||||
!formData.value.AllowedIPs.Overridable ||
|
||||
!formData.value.PersistentKeepalive.Overridable ||
|
||||
!formData.value.Dns.Overridable ||
|
||||
!formData.value.DnsSearch.Overridable ||
|
||||
!formData.value.Mtu.Overridable ||
|
||||
!formData.value.FirewallMark.Overridable ||
|
||||
!formData.value.RoutingTable.Overridable ||
|
||||
!formData.value.PreUp.Overridable ||
|
||||
!formData.value.PostUp.Overridable ||
|
||||
!formData.value.PreDown.Overridable ||
|
||||
!formData.value.PostDown.Overridable) {
|
||||
formData.value.IgnoreGlobalSettings = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(() => formData.value.Disabled, async (newValue, oldValue) => {
|
||||
if (oldValue && !newValue && formData.value.ExpiresAt) {
|
||||
formData.value.ExpiresAt = "" // reset expiry date
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
function close() {
|
||||
formData.value = freshPeer()
|
||||
emit('close')
|
||||
}
|
||||
|
||||
async function save() {
|
||||
try {
|
||||
if (props.peerId !== '#NEW#') {
|
||||
await peers.UpdatePeer(selectedPeer.value.Identifier, formData.value)
|
||||
} else {
|
||||
await peers.CreatePeer(selectedInterface.value.Identifier, formData.value)
|
||||
}
|
||||
close()
|
||||
} catch (e) {
|
||||
// console.log(e)
|
||||
notify({
|
||||
title: "Failed to save peer!",
|
||||
text: e.toString(),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function del() {
|
||||
try {
|
||||
await peers.DeletePeer(selectedPeer.value.Identifier)
|
||||
close()
|
||||
} catch (e) {
|
||||
// console.log(e)
|
||||
notify({
|
||||
title: "Failed to delete peer!",
|
||||
text: e.toString(),
|
||||
type: 'error',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal :title="title" :visible="visible" @close="close">
|
||||
<template #default>
|
||||
<fieldset>
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-general') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.display-name.label') }}</label>
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.display-name.placeholder')"
|
||||
v-model="formData.DisplayName">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-crypto') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.private-key.label') }}</label>
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.private-key.placeholder')" required
|
||||
v-model="formData.PrivateKey">
|
||||
<small id="privateKeyHelp" class="form-text text-muted">{{ $t('modals.peer-edit.private-key.help') }}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.public-key.label') }}</label>
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.public-key.placeholder')" required
|
||||
v-model="formData.PublicKey">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.preshared-key.label') }}</label>
|
||||
<input type="text" class="form-control" :placeholder="$t('modals.peer-edit.preshared-key.placeholder')"
|
||||
v-model="formData.PresharedKey">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-network') }}</legend>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.keep-alive.label') }}</label>
|
||||
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.keep-alive.label')"
|
||||
v-model="formData.PersistentKeepalive.Value">
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.mtu.label') }}</label>
|
||||
<input type="number" class="form-control" :placeholder="$t('modals.peer-edit.mtu.label')"
|
||||
v-model="formData.Mtu.Value">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-hooks') }}</legend>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.pre-up.label') }}</label>
|
||||
<textarea v-model="formData.PreUp.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.pre-up.placeholder')"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.post-up.label') }}</label>
|
||||
<textarea v-model="formData.PostUp.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.post-up.placeholder')"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.pre-down.label') }}</label>
|
||||
<textarea v-model="formData.PreDown.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.pre-down.placeholder')"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('modals.peer-edit.post-down.label') }}</label>
|
||||
<textarea v-model="formData.PostDown.Value" class="form-control" rows="2"
|
||||
:placeholder="$t('modals.peer-edit.post-down.placeholder')"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend class="mt-4">{{ $t('modals.peer-edit.header-state') }}</legend>
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" v-model="formData.Disabled">
|
||||
<label class="form-check-label">{{ $t('modals.peer-edit.disabled.label') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label class="form-label">{{ $t('modals.peer-edit.expires-at.label') }}</label>
|
||||
<input type="date" pattern="\d{4}-\d{2}-\d{2}" class="form-control" min="2023-01-01"
|
||||
v-model="formData.ExpiresAt">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex-fill text-start">
|
||||
<button v-if="props.peerId !== '#NEW#'" class="btn btn-danger me-1" type="button" @click.prevent="del">{{
|
||||
$t('general.delete') }}</button>
|
||||
</div>
|
||||
<button class="btn btn-primary me-1" type="button" @click.prevent="save">{{ $t('general.save') }}</button>
|
||||
<button class="btn btn-secondary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style></style>
|
@@ -88,6 +88,10 @@ function close() {
|
||||
<td>{{ $t('modals.user-view.department') }}:</td>
|
||||
<td>{{selectedUser.Department}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('modals.user-view.api-enabled') }}:</td>
|
||||
<td>{{selectedUser.ApiEnabled}}</td>
|
||||
</tr>
|
||||
<tr v-if="selectedUser.Disabled">
|
||||
<td>{{ $t('modals.user-view.disabled') }}:</td>
|
||||
<td>{{selectedUser.DisabledReason}}</td>
|
||||
|
@@ -42,7 +42,8 @@ export function freshInterface() {
|
||||
PeerDefPostDown: "",
|
||||
|
||||
TotalPeers: 0,
|
||||
EnabledPeers: 0
|
||||
EnabledPeers: 0,
|
||||
Filename: ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,8 +121,11 @@ export function freshPeer() {
|
||||
Overridable: true,
|
||||
},
|
||||
|
||||
// Internal value
|
||||
IgnoreGlobalSettings: false
|
||||
Filename: "",
|
||||
|
||||
// Internal values
|
||||
IgnoreGlobalSettings: false,
|
||||
IsSelected: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +150,12 @@ export function freshUser() {
|
||||
Locked: false,
|
||||
LockedReason: "",
|
||||
|
||||
PeerCount: 0
|
||||
ApiEnabled: false,
|
||||
|
||||
PeerCount: 0,
|
||||
|
||||
// Internal values
|
||||
IsSelected: false
|
||||
}
|
||||
}
|
||||
|
||||
|
20
frontend/src/helpers/utils.js
Normal file
20
frontend/src/helpers/utils.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Address4, Address6 } from "ip-address"
|
||||
|
||||
export function ipToBigInt(ip) {
|
||||
// Check if it's an IPv4 address
|
||||
if (ip.includes(".")) {
|
||||
const addr = new Address4(ip)
|
||||
return addr.bigInteger()
|
||||
}
|
||||
|
||||
// Otherwise, assume it's an IPv6 address
|
||||
const addr = new Address6(ip)
|
||||
return addr.bigInteger()
|
||||
}
|
||||
|
||||
export function humanFileSize(size) {
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
||||
if (size === 0) return "0B"
|
||||
const i = parseInt(Math.floor(Math.log(size) / Math.log(1024)))
|
||||
return Math.round(size / Math.pow(1024, i), 2) + sizes[i]
|
||||
}
|
@@ -1,14 +1,26 @@
|
||||
import isCidr from "is-cidr";
|
||||
import {isIP} from 'is-ip';
|
||||
|
||||
export function validateCIDR(value) {
|
||||
return isCidr(value) !== 0
|
||||
export function validateCIDR() {
|
||||
return [{
|
||||
classes: 'invalid-cidr',
|
||||
rule: ({ text }) => isCidr(text) === 0,
|
||||
disableAdd: true,
|
||||
}]
|
||||
}
|
||||
|
||||
export function validateIP(value) {
|
||||
return isIP(value)
|
||||
export function validateIP() {
|
||||
return [{
|
||||
classes: 'invalid-ip',
|
||||
rule: ({ text }) => !isIP(text),
|
||||
disableAdd: true,
|
||||
}]
|
||||
}
|
||||
|
||||
export function validateDomain(value) {
|
||||
return true
|
||||
export function validateDomain() {
|
||||
return [{
|
||||
classes: 'invalid-domain',
|
||||
rule: tag => tag.text.length < 3,
|
||||
disableAdd: true,
|
||||
}]
|
||||
}
|
@@ -1,29 +1,38 @@
|
||||
// src/lang/index.js
|
||||
import de from './translations/de.json';
|
||||
import ru from './translations/ru.json';
|
||||
import en from './translations/en.json';
|
||||
import {createI18n} from "vue-i18n";
|
||||
import fr from './translations/fr.json';
|
||||
import ko from './translations/ko.json';
|
||||
import pt from './translations/pt.json';
|
||||
import ru from './translations/ru.json';
|
||||
import uk from './translations/uk.json';
|
||||
import vi from './translations/vi.json';
|
||||
import zh from './translations/zh.json';
|
||||
|
||||
function getStoredLanguage() {
|
||||
let initialLang = localStorage.getItem('wgLang');
|
||||
if (!initialLang) {
|
||||
initialLang = "en"
|
||||
}
|
||||
return initialLang
|
||||
}
|
||||
import {createI18n} from "vue-i18n";
|
||||
|
||||
// Create i18n instance with options
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
globalInjection: true,
|
||||
allowComposition: true,
|
||||
locale: getStoredLanguage(), // set locale
|
||||
locale: (
|
||||
localStorage.getItem('wgLang')
|
||||
|| (window && window.navigator && (window.navigator.userLanguage || window.navigator.language).split('-')[0])
|
||||
|| 'en'
|
||||
), // set locale
|
||||
fallbackLocale: "en", // set fallback locale
|
||||
messages: {
|
||||
"de": de,
|
||||
"en": en,
|
||||
"fr": fr,
|
||||
"ko": ko,
|
||||
"pt": pt,
|
||||
"ru": ru,
|
||||
"en": en
|
||||
"uk": uk,
|
||||
"vi": vi,
|
||||
"zh": zh,
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n
|
||||
export default i18n
|
||||
|
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"languages": {
|
||||
"de": "Deutsch"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Anzahl an Elementen",
|
||||
@@ -23,7 +26,7 @@
|
||||
"placeholder": "Bitte geben Sie Ihren Benutzernamen ein"
|
||||
},
|
||||
"password": {
|
||||
"label": "Kennwort",
|
||||
"label": "Passwort",
|
||||
"placeholder": "Bitte geben Sie Ihr Passwort ein"
|
||||
},
|
||||
"button": "Anmelden"
|
||||
@@ -34,8 +37,11 @@
|
||||
"users": "Benutzer",
|
||||
"lang": "Sprache ändern",
|
||||
"profile": "Mein Profil",
|
||||
"settings": "Einstellungen",
|
||||
"audit": "Event Protokoll",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden"
|
||||
"logout": "Abmelden",
|
||||
"keygen": "Schlüsselgenerator"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® VPN Portal",
|
||||
@@ -75,77 +81,77 @@
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "Schnittstellenverwaltung",
|
||||
"headline-peers": "Current VPN Peers",
|
||||
"headline-endpoints": "Current Endpoints",
|
||||
"headline-peers": "Aktuelle VPN-Peers",
|
||||
"headline-endpoints": "Aktuelle Endpunkte",
|
||||
"no-interface": {
|
||||
"default-selection": "No Interface available",
|
||||
"headline": "No interfaces found...",
|
||||
"abstract": "Click the plus button above to create a new WireGuard interface."
|
||||
"default-selection": "Keine Schnittstelle verfügbar",
|
||||
"headline": "Keine Schnittstellen gefunden...",
|
||||
"abstract": "Klicken Sie auf die Plus-Schaltfläche oben, um eine neue WireGuard-Schnittstelle zu erstellen."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "No peers available",
|
||||
"abstract": "Currently, there are no peers available for the selected WireGuard interface."
|
||||
"headline": "Keine Peers verfügbar",
|
||||
"abstract": "Derzeit sind keine Peers für die ausgewählte WireGuard-Schnittstelle verfügbar."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "Name",
|
||||
"user": "User",
|
||||
"user": "Benutzer",
|
||||
"ip": "IP's",
|
||||
"endpoint": "Endpoint",
|
||||
"endpoint": "Endpunkt",
|
||||
"status": "Status"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "Interface status for",
|
||||
"mode": "mode",
|
||||
"key": "Public Key",
|
||||
"endpoint": "Public Endpoint",
|
||||
"port": "Listening Port",
|
||||
"peers": "Enabled Peers",
|
||||
"total-peers": "Total Peers",
|
||||
"endpoints": "Enabled Endpoints",
|
||||
"total-endpoints": "Total Endpoints",
|
||||
"ip": "IP Address",
|
||||
"default-allowed-ip": "Default allowed IPs",
|
||||
"dns": "DNS Servers",
|
||||
"headline": "Schnittstellenstatus für",
|
||||
"mode": "Modus",
|
||||
"key": "Öffentlicher Schlüssel",
|
||||
"endpoint": "Öffentlicher Endpunkt",
|
||||
"port": "Port",
|
||||
"peers": "Aktive Peers",
|
||||
"total-peers": "Gesamtanzahl Peers",
|
||||
"endpoints": "Aktive Endpunkte",
|
||||
"total-endpoints": "Gesamtanzahl Endpunkte",
|
||||
"ip": "IP-Adresse",
|
||||
"default-allowed-ip": "Standard Erlaubte-IPs",
|
||||
"dns": "DNS-Server",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "Default Keepalive Interval",
|
||||
"button-show-config": "Show configuration",
|
||||
"button-download-config": "Download configuration",
|
||||
"button-store-config": "Store configuration for wg-quick",
|
||||
"button-edit": "Edit interface"
|
||||
"default-keep-alive": "Standard Keepalive-Intervall",
|
||||
"button-show-config": "Konfiguration anzeigen",
|
||||
"button-download-config": "Konfiguration herunterladen",
|
||||
"button-store-config": "Konfiguration für wg-quick speichern",
|
||||
"button-edit": "Schnittstelle bearbeiten"
|
||||
},
|
||||
"button-add-interface": "Add Interface",
|
||||
"button-add-peer": "Add Peer",
|
||||
"button-add-peers": "Add Multiple Peers",
|
||||
"button-show-peer": "Show Peer",
|
||||
"button-edit-peer": "Edit Peer",
|
||||
"peer-disabled": "Peer is disabled, reason:",
|
||||
"peer-expiring": "Peer is expiring at",
|
||||
"peer-connected": "Connected",
|
||||
"peer-not-connected": "Not Connected",
|
||||
"peer-handshake": "Last handshake:"
|
||||
"button-add-interface": "Schnittstelle hinzufügen",
|
||||
"button-add-peer": "Peer hinzufügen",
|
||||
"button-add-peers": "Mehrere Peers hinzufügen",
|
||||
"button-show-peer": "Peer anzeigen",
|
||||
"button-edit-peer": "Peer bearbeiten",
|
||||
"peer-disabled": "Peer ist deaktiviert, Grund:",
|
||||
"peer-expiring": "Peer läuft ab am",
|
||||
"peer-connected": "Verbunden",
|
||||
"peer-not-connected": "Nicht verbunden",
|
||||
"peer-handshake": "Letzter Handshake:"
|
||||
},
|
||||
"users": {
|
||||
"headline": "Benutzerverwaltung",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Firstname",
|
||||
"lastname": "Lastname",
|
||||
"source": "Source",
|
||||
"firstname": "Vorname",
|
||||
"lastname": "Nachname",
|
||||
"source": "Quelle",
|
||||
"peers": "Peers",
|
||||
"admin": "Admin"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "No users available",
|
||||
"abstract": "Currently, there are no users registered with WireGuard Portal."
|
||||
"headline": "Keine Benutzer verfügbar",
|
||||
"abstract": "Derzeit sind keine Benutzer im WireGuard-Portal registriert."
|
||||
},
|
||||
"button-add-user": "Add User",
|
||||
"button-show-user": "Show User",
|
||||
"button-edit-user": "Edit User",
|
||||
"user-disabled": "User is disabled, reason:",
|
||||
"user-locked": "Account is locked, reason:",
|
||||
"admin": "User has administrator privileges",
|
||||
"no-admin": "User has no administrator privileges"
|
||||
"button-add-user": "Benutzer hinzufügen",
|
||||
"button-show-user": "Benutzer anzeigen",
|
||||
"button-edit-user": "Benutzer bearbeiten",
|
||||
"user-disabled": "Benutzer ist deaktiviert, Grund:",
|
||||
"user-locked": "Konto ist gesperrt, Grund:",
|
||||
"admin": "Benutzer hat Administratorrechte",
|
||||
"no-admin": "Benutzer hat keine Administratorrechte"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "Meine VPN-Konfigurationen",
|
||||
@@ -153,336 +159,393 @@
|
||||
"name": "Name",
|
||||
"ip": "IP's",
|
||||
"stats": "Status",
|
||||
"interface": "Server Interface"
|
||||
"interface": "Server-Schnittstelle"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "No peers available",
|
||||
"abstract": "Currently, there are no peers associated with your user profile."
|
||||
"headline": "Keine Peers verfügbar",
|
||||
"abstract": "Derzeit sind keine Peers mit Ihrem Benutzerprofil verknüpft."
|
||||
},
|
||||
"peer-connected": "Connected",
|
||||
"button-add-peer": "Add Peer",
|
||||
"button-show-peer": "Show Peer",
|
||||
"button-edit-peer": "Edit Peer"
|
||||
"peer-connected": "Verbunden",
|
||||
"button-add-peer": "Peer hinzufügen",
|
||||
"button-show-peer": "Peer anzeigen",
|
||||
"button-edit-peer": "Peer bearbeiten"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Einstellungen",
|
||||
"abstract": "Hier finden Sie persönliche Einstellungen für WireGuard Portal.",
|
||||
"api": {
|
||||
"headline": "API Einstellungen",
|
||||
"abstract": "Hier können Sie die RESTful API verwalten.",
|
||||
"active-description": "Die API ist derzeit für Ihr Benutzerkonto aktiv. Alle API-Anfragen werden mit Basic Auth authentifiziert. Verwenden Sie zur Authentifizierung die folgenden Anmeldeinformationen.",
|
||||
"inactive-description": "Die API ist derzeit inaktiv. Klicken Sie auf die Schaltfläche unten, um sie zu aktivieren.",
|
||||
"user-label": "API Benutzername:",
|
||||
"user-placeholder": "API Benutzer",
|
||||
"token-label": "API Passwort:",
|
||||
"token-placeholder": "API Token",
|
||||
"token-created-label": "API-Zugriff gewährt seit: ",
|
||||
"button-disable-title": "Deaktivieren Sie die API. Dadurch wird der aktuelle Token ungültig.",
|
||||
"button-disable-text": "API deaktivieren",
|
||||
"button-enable-title": "Aktivieren Sie die API, dadurch wird ein neuer Token generiert.",
|
||||
"button-enable-text": "API aktivieren",
|
||||
"api-link": "API Dokumentation"
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"headline": "Eventprotokoll",
|
||||
"abstract": "Hier finden Sie das Eventprotokoll aller im WireGuard-Portal vorgenommenen Aktionen.",
|
||||
"no-entries": {
|
||||
"headline": "Keine Protokolleinträge verfügbar",
|
||||
"abstract": "Derzeit sind keine Eventprotokolle aufgezeichnet."
|
||||
},
|
||||
"entries-headline": "Protokolleinträge",
|
||||
"table-heading": {
|
||||
"id": "#",
|
||||
"time": "Zeit",
|
||||
"user": "Benutzer",
|
||||
"severity": "Schweregrad",
|
||||
"origin": "Ursprung",
|
||||
"message": "Nachricht"
|
||||
}
|
||||
},
|
||||
"keygen": {
|
||||
"headline": "WireGuard Key Generator",
|
||||
"abstract": "Hier können Sie WireGuard Schlüsselpaare generieren. Die Schlüssel werden lokal auf Ihrem Computer generiert und niemals an den Server gesendet.",
|
||||
"headline-keypair": "Neues Schlüsselpaar",
|
||||
"headline-preshared-key": "Neuer Pre-Shared Key",
|
||||
"button-generate": "Erzeugen",
|
||||
"private-key": {
|
||||
"label": "Privater Schlüssel",
|
||||
"placeholder": "Der private Schlüssel"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Öffentlicher Schlüssel",
|
||||
"placeholder": "Der öffentliche Schlüssel"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Pre-Shared Key",
|
||||
"placeholder": "Der geteilte Schlüssel"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "User Account:",
|
||||
"tab-user": "Information",
|
||||
"headline": "Benutzerkonto:",
|
||||
"tab-user": "Informationen",
|
||||
"tab-peers": "Peers",
|
||||
"headline-info": "User Information:",
|
||||
"headline-notes": "Notes:",
|
||||
"headline-info": "Benutzerinformationen:",
|
||||
"headline-notes": "Notizen:",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Firstname",
|
||||
"lastname": "Lastname",
|
||||
"phone": "Phone number",
|
||||
"department": "Department",
|
||||
"disabled": "Account Disabled",
|
||||
"locked": "Account Locked",
|
||||
"no-peers": "User has no associated peers.",
|
||||
"firstname": "Vorname",
|
||||
"lastname": "Nachname",
|
||||
"phone": "Telefonnummer",
|
||||
"department": "Abteilung",
|
||||
"api-enabled": "API-Zugriff",
|
||||
"disabled": "Konto deaktiviert",
|
||||
"locked": "Konto gesperrt",
|
||||
"no-peers": "Benutzer hat keine zugeordneten Peers.",
|
||||
"peers": {
|
||||
"name": "Name",
|
||||
"interface": "Interface",
|
||||
"interface": "Schnittstelle",
|
||||
"ip": "IP's"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "Edit user:",
|
||||
"headline-new": "New user",
|
||||
"header-general": "General",
|
||||
"header-personal": "User Information",
|
||||
"header-notes": "Notes",
|
||||
"header-state": "State",
|
||||
"headline-edit": "Benutzer bearbeiten:",
|
||||
"headline-new": "Neuer Benutzer",
|
||||
"header-general": "Allgemein",
|
||||
"header-personal": "Benutzerinformationen",
|
||||
"header-notes": "Notizen",
|
||||
"header-state": "Status",
|
||||
"identifier": {
|
||||
"label": "Identifier",
|
||||
"placeholder": "The unique user identifier"
|
||||
"label": "Kennung",
|
||||
"placeholder": "Die eindeutige Benutzerkennung"
|
||||
},
|
||||
"source": {
|
||||
"label": "Source",
|
||||
"placeholder": "The user source"
|
||||
"label": "Quelle",
|
||||
"placeholder": "Die Benutzerquelle"
|
||||
},
|
||||
"password": {
|
||||
"label": "Password",
|
||||
"placeholder": "A super secret password",
|
||||
"description": "Leave this field blank to keep current password."
|
||||
"label": "Passwort",
|
||||
"placeholder": "Ein super geheimes Passwort",
|
||||
"description": "Lassen Sie dieses Feld leer, um das aktuelle Passwort beizubehalten."
|
||||
},
|
||||
"email": {
|
||||
"label": "Email",
|
||||
"placeholder": "The email address"
|
||||
"label": "E-Mail",
|
||||
"placeholder": "Die E-Mail-Adresse"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Phone",
|
||||
"placeholder": "The phone number"
|
||||
"label": "Telefon",
|
||||
"placeholder": "Die Telefonnummer"
|
||||
},
|
||||
"department": {
|
||||
"label": "Department",
|
||||
"placeholder": "The department"
|
||||
"label": "Abteilung",
|
||||
"placeholder": "Die Abteilung"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "Firstname",
|
||||
"placeholder": "Firstname"
|
||||
"label": "Vorname",
|
||||
"placeholder": "Vorname"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "Lastname",
|
||||
"placeholder": "Lastname"
|
||||
"label": "Nachname",
|
||||
"placeholder": "Nachname"
|
||||
},
|
||||
"notes": {
|
||||
"label": "Notes",
|
||||
"label": "Notizen",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Disabled (no WireGuard connection and no login possible)"
|
||||
"label": "Deaktiviert (keine WireGuard-Verbindung und kein Login möglich)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "Locked (no login possible, WireGuard connections still work)"
|
||||
"label": "Gesperrt (kein Login möglich, WireGuard-Verbindungen funktionieren weiterhin)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "Is Admin"
|
||||
"label": "Ist Administrator"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "Config for Interface:"
|
||||
"headline": "Konfiguration für Schnittstelle:"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "Edit Interface:",
|
||||
"headline-new": "New Interface",
|
||||
"tab-interface": "Interface",
|
||||
"tab-peerdef": "Peer Defaults",
|
||||
"header-general": "General",
|
||||
"header-network": "Network",
|
||||
"header-crypto": "Cryptography",
|
||||
"header-hooks": "Interface Hooks",
|
||||
"headline-edit": "Schnittstelle bearbeiten:",
|
||||
"headline-new": "Neue Schnittstelle",
|
||||
"tab-interface": "Schnittstelle",
|
||||
"tab-peerdef": "Peer-Standardeinstellungen",
|
||||
"header-general": "Allgemein",
|
||||
"header-network": "Netzwerk",
|
||||
"header-crypto": "Kryptografie",
|
||||
"header-hooks": "Schnittstellen-Hooks",
|
||||
"header-peer-hooks": "Hooks",
|
||||
"header-state": "State",
|
||||
"header-state": "Status",
|
||||
"identifier": {
|
||||
"label": "Identifier",
|
||||
"placeholder": "The unique interface identifier"
|
||||
"label": "Kennung",
|
||||
"placeholder": "Die eindeutige Schnittstellenkennung"
|
||||
},
|
||||
"mode": {
|
||||
"label": "Interface Mode",
|
||||
"server": "Server Mode",
|
||||
"client": "Client Mode",
|
||||
"any": "Unknown Mode"
|
||||
"label": "Schnittstellenmodus",
|
||||
"server": "Server-Modus",
|
||||
"client": "Client-Modus",
|
||||
"any": "Unbekannter Modus"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "Display Name",
|
||||
"placeholder": "The descriptive name for the interface"
|
||||
"label": "Anzeigename",
|
||||
"placeholder": "Der beschreibende Name für die Schnittstelle"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Private Key",
|
||||
"placeholder": "The private key"
|
||||
"label": "Privater Schlüssel",
|
||||
"placeholder": "Der private Schlüssel"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Public Key",
|
||||
"placeholder": "The public key"
|
||||
"label": "Öffentlicher Schlüssel",
|
||||
"placeholder": "Der öffentliche Schlüssel"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP Addresses",
|
||||
"placeholder": "IP Addresses (CIDR format)"
|
||||
"label": "IP-Adressen",
|
||||
"placeholder": "IP-Adressen (CIDR-Format)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "Listen Port",
|
||||
"placeholder": "The listening port"
|
||||
"label": "Port",
|
||||
"placeholder": "Der Port der WireGuard Schnittstelle"
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS Server",
|
||||
"placeholder": "The DNS servers that should be used"
|
||||
"label": "DNS-Server",
|
||||
"placeholder": "Die zu verwendenden DNS-Server"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS Search Domains",
|
||||
"placeholder": "DNS search prefixes"
|
||||
"label": "DNS-Suchdomänen",
|
||||
"placeholder": "DNS-Suchpräfixe"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "The interface MTU (0 = keep default)"
|
||||
"placeholder": "Die Schnittstellen-MTU (0 = Standard beibehalten)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "Firewall Mark",
|
||||
"placeholder": "Firewall mark that is applied to outgoing traffic. (0 = automatic)"
|
||||
"label": "Firewall-Markierung",
|
||||
"placeholder": "Firewall-Markierung, die auf ausgehenden Datenverkehr angewendet wird. (0 = automatisch)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "Routing Table",
|
||||
"placeholder": "The routing table ID",
|
||||
"description": "Special cases: off = do not manage routes, 0 = automatic"
|
||||
"label": "Routing-Tabelle",
|
||||
"placeholder": "Die Routing-Tabellen-ID",
|
||||
"description": "Spezialfälle: off = Routen nicht verwalten, 0 = automatisch"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Interface Disabled"
|
||||
"label": "Schnittstelle deaktiviert"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "Automatically save wg-quick config"
|
||||
"label": "wg-quick Konfiguration automatisch speichern"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "Endpoint Address",
|
||||
"placeholder": "Endpoint Address",
|
||||
"description": "The endpoint address that peers will connect to."
|
||||
"label": "Endpunktadresse",
|
||||
"placeholder": "Endpunktadresse",
|
||||
"description": "Die Endpunktadresse, mit der sich Peers verbinden. (z.B. wg.example.com oder wg.example.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "IP Networks",
|
||||
"placeholder": "Network Addresses",
|
||||
"description": "Peers will get IP addresses from those subnets."
|
||||
"label": "IP-Netzwerke",
|
||||
"placeholder": "Netzwerkadressen",
|
||||
"description": "Peers erhalten IP-Adressen aus diesen Subnetzen."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Allowed IP Addresses",
|
||||
"placeholder": "Default Allowed IP Addresses"
|
||||
"label": "Erlaubte IP-Adressen",
|
||||
"placeholder": "Erlaubte IP-Adressen für Peers"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "The client MTU (0 = keep default)"
|
||||
"placeholder": "Die Client-MTU (0 = Standard beibehalten)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Keep Alive Interval",
|
||||
"placeholder": "Persistent Keepalive (0 = default)"
|
||||
"label": "Keepalive-Intervall",
|
||||
"placeholder": "Persistentes Keepalive (0 = Standard)"
|
||||
}
|
||||
},
|
||||
|
||||
"button-apply-defaults": "Apply Peer Defaults"
|
||||
"button-apply-defaults": "Peer-Standardeinstellungen anwenden"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "Peer:",
|
||||
"headline-endpoint": "Endpoint:",
|
||||
"section-info": "Peer Information",
|
||||
"section-status": "Current Status",
|
||||
"section-config": "Configuration",
|
||||
"identifier": "Identifier",
|
||||
"ip": "IP Addresses",
|
||||
"user": "Associated User",
|
||||
"notes": "Notes",
|
||||
"expiry-status": "Expires At",
|
||||
"disabled-status": "Disabled At",
|
||||
"traffic": "Traffic",
|
||||
"connection-status": "Connection Stats",
|
||||
"upload": "Uploaded Bytes (from Server to Peer)",
|
||||
"download": "Downloaded Bytes (from Peer to Server)",
|
||||
"pingable": "Is Pingable",
|
||||
"handshake": "Last Handshake",
|
||||
"connected-since": "Connected since",
|
||||
"endpoint": "Endpoint",
|
||||
"button-download": "Download configuration",
|
||||
"button-email": "Send configuration via E-Mail"
|
||||
"headline-endpoint": "Endpunkt:",
|
||||
"section-info": "Peer-Informationen",
|
||||
"section-status": "Aktueller Status",
|
||||
"section-config": "Konfiguration",
|
||||
"identifier": "Kennung",
|
||||
"ip": "IP-Adressen",
|
||||
"user": "Zugeordneter Benutzer",
|
||||
"notes": "Notizen",
|
||||
"expiry-status": "Läuft ab am",
|
||||
"disabled-status": "Deaktiviert am",
|
||||
"traffic": "Datenverkehr",
|
||||
"connection-status": "Verbindungsstatistiken",
|
||||
"upload": "Hochgeladene Bytes (vom Server zum Peer)",
|
||||
"download": "Heruntergeladene Bytes (vom Peer zum Server)",
|
||||
"pingable": "Pingbar",
|
||||
"handshake": "Letzter Handshake",
|
||||
"connected-since": "Verbunden seit",
|
||||
"endpoint": "Endpunkt",
|
||||
"button-download": "Konfiguration herunterladen",
|
||||
"button-email": "Konfiguration per E-Mail senden"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "Edit peer:",
|
||||
"headline-edit-endpoint": "Edit endpoint:",
|
||||
"headline-new-peer": "Create peer",
|
||||
"headline-new-endpoint": "Create endpoint",
|
||||
"header-general": "General",
|
||||
"header-network": "Network",
|
||||
"header-crypto": "Cryptography",
|
||||
"header-hooks": "Hooks (Executed on Peer)",
|
||||
"header-state": "State",
|
||||
"headline-edit-peer": "Peer bearbeiten:",
|
||||
"headline-edit-endpoint": "Endpunkt bearbeiten:",
|
||||
"headline-new-peer": "Peer erstellen",
|
||||
"headline-new-endpoint": "Endpunkt erstellen",
|
||||
"header-general": "Allgemein",
|
||||
"header-network": "Netzwerk",
|
||||
"header-crypto": "Kryptografie",
|
||||
"header-hooks": "Hooks (beim Peer ausgeführt)",
|
||||
"header-state": "Status",
|
||||
"display-name": {
|
||||
"label": "Display Name",
|
||||
"placeholder": "The descriptive name for the peer"
|
||||
"label": "Anzeigename",
|
||||
"placeholder": "Der beschreibende Name für den Peer"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "Linked User",
|
||||
"placeholder": "The user account which owns this peer"
|
||||
"label": "Verknüpfter Benutzer",
|
||||
"placeholder": "Das Benutzerkonto, dem dieser Peer gehört"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Private Key",
|
||||
"placeholder": "The private key"
|
||||
"label": "Privater Schlüssel",
|
||||
"placeholder": "Der private Schlüssel",
|
||||
"help": "Der private Schlüssel wird sicher auf dem Server gespeichert. Wenn der Benutzer bereits eine Kopie besitzt, kann dieses Feld entfallen. Der Server funktioniert auch ausschließlich mit dem öffentlichen Schlüssel des Peers."
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Public Key",
|
||||
"placeholder": "The public key"
|
||||
"label": "Öffentlicher Schlüssel",
|
||||
"placeholder": "Der öffentliche Schlüssel"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Preshared Key",
|
||||
"placeholder": "Optional pre-shared key"
|
||||
"label": "Pre-Shared Key",
|
||||
"placeholder": "Optionaler geteilter Schlüssel"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "Endpoint public Key",
|
||||
"placeholder": "The public key of the remote endpoint"
|
||||
"label": "Öffentlicher Endpunktschlüssel",
|
||||
"placeholder": "Der öffentliche Schlüssel des entfernten Endpunkts"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "Endpoint Address",
|
||||
"placeholder": "The address of the remote endpoint"
|
||||
"label": "Endpunktadresse",
|
||||
"placeholder": "Die Adresse des entfernten Endpunkts"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP Addresses",
|
||||
"placeholder": "IP Addresses (CIDR format)"
|
||||
"label": "IP-Adressen",
|
||||
"placeholder": "IP-Adressen (CIDR-Format)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Allowed IP Addresses",
|
||||
"placeholder": "Allowed IP Addresses (CIDR format)"
|
||||
"label": "Erlaubte IP-Adressen",
|
||||
"placeholder": "Erlaubte IP-Adressen (CIDR-Format)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "Extra allowed IP Addresses",
|
||||
"placeholder": "Extra allowed IP's (Server Sided)",
|
||||
"description": "Those IP's will be added on the remote WireGuard interface as allowed IP's."
|
||||
"label": "Zusätzliche erlaubte IP-Adressen",
|
||||
"placeholder": "Zusätzliche erlaubte IP's (Server-seitig)",
|
||||
"description": "Diese IPs werden an der entfernten WireGuard-Schnittstelle als erlaubte IPs hinzugefügt."
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS Server",
|
||||
"placeholder": "The DNS servers that should be used"
|
||||
"label": "DNS-Server",
|
||||
"placeholder": "Die zu verwendenden DNS-Server"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS Search Domains",
|
||||
"placeholder": "DNS search prefixes"
|
||||
"label": "DNS-Suchdomänen",
|
||||
"placeholder": "DNS-Suchpräfixe"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Keep Alive Interval",
|
||||
"placeholder": "Persistent Keepalive (0 = default)"
|
||||
"label": "Keepalive-Intervall",
|
||||
"placeholder": "Persistentes Keepalive (0 = Standard)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "The client MTU (0 = keep default)"
|
||||
"placeholder": "Die Client-MTU (0 = Standard beibehalten)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "One or multiple bash commands separated by ;"
|
||||
"placeholder": "Ein oder mehrere Bash-Befehle, getrennt durch ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Peer Disabled"
|
||||
"label": "Peer deaktiviert"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "Ignore global settings"
|
||||
"label": "Globale Einstellungen ignorieren"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "Expiry date"
|
||||
"label": "Ablaufdatum"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "Create multiple peers",
|
||||
"headline-endpoint": "Create multiple endpoints",
|
||||
"headline-peer": "Mehrere Peers erstellen",
|
||||
"headline-endpoint": "Mehrere Endpunkte erstellen",
|
||||
"identifiers": {
|
||||
"label": "User Identifiers",
|
||||
"placeholder": "User Identifiers",
|
||||
"description": "A user identifier (the username) for which a peer should be created."
|
||||
"label": "Benutzerkennungen",
|
||||
"placeholder": "Benutzerkennungen",
|
||||
"description": "Eine Benutzerkennung (der Benutzername), für die ein Peer erstellt werden soll."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "Peer:",
|
||||
"headline-endpoint": "Endpoint:",
|
||||
"label": "Display Name Prefix",
|
||||
"placeholder": "The prefix",
|
||||
"description": "A prefix that is added to the peers display name."
|
||||
"headline-endpoint": "Endpunkt:",
|
||||
"label": "Anzeigename-Präfix",
|
||||
"placeholder": "Das Präfix",
|
||||
"description": "Ein Präfix, das dem Anzeigenamen des Peers hinzugefügt wird."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"languages": {
|
||||
"en": "English"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Number of Elements",
|
||||
@@ -34,8 +37,11 @@
|
||||
"users": "Users",
|
||||
"lang": "Toggle Language",
|
||||
"profile": "My Profile",
|
||||
"settings": "Settings",
|
||||
"audit": "Audit Log",
|
||||
"login": "Login",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"keygen": "Key Generator"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® VPN Portal",
|
||||
@@ -164,6 +170,62 @@
|
||||
"button-show-peer": "Show Peer",
|
||||
"button-edit-peer": "Edit Peer"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Settings",
|
||||
"abstract": "Here you can change your personal settings.",
|
||||
"api": {
|
||||
"headline": "API Settings",
|
||||
"abstract": "Here you can configure the RESTful API settings.",
|
||||
"active-description": "The API is currently active for your user account. All API requests are authenticated with Basic Auth. Use the following credentials for authentication.",
|
||||
"inactive-description": "The API is currently inactive. Press the button below to activate it.",
|
||||
"user-label": "API Username:",
|
||||
"user-placeholder": "The API user",
|
||||
"token-label": "API Password:",
|
||||
"token-placeholder": "The API token",
|
||||
"token-created-label": "API access granted at: ",
|
||||
"button-disable-title": "Disable API, this will invalidate the current token.",
|
||||
"button-disable-text": "Disable API",
|
||||
"button-enable-title": "Enable API, this will generate a new token.",
|
||||
"button-enable-text": "Enable API",
|
||||
"api-link": "API Documentation"
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"headline": "Audit Log",
|
||||
"abstract": "Here you can find the audit log of all actions performed in the WireGuard Portal.",
|
||||
"no-entries": {
|
||||
"headline": "No log entries available",
|
||||
"abstract": "Currently, there are no audit logs recorded."
|
||||
},
|
||||
"entries-headline": "Log Entries",
|
||||
"table-heading": {
|
||||
"id": "#",
|
||||
"time": "Time",
|
||||
"user": "User",
|
||||
"severity": "Severity",
|
||||
"origin": "Origin",
|
||||
"message": "Message"
|
||||
}
|
||||
},
|
||||
"keygen": {
|
||||
"headline": "WireGuard Key Generator",
|
||||
"abstract": "Generate a new WireGuard keys. The keys are generated in your local browser and are never sent to the server.",
|
||||
"headline-keypair": "New Key Pair",
|
||||
"headline-preshared-key": "New Preshared Key",
|
||||
"button-generate": "Generate",
|
||||
"private-key": {
|
||||
"label": "Private Key",
|
||||
"placeholder": "The private key"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Public Key",
|
||||
"placeholder": "The public key"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Preshared Key",
|
||||
"placeholder": "The pre-shared key"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "User Account:",
|
||||
@@ -174,8 +236,9 @@
|
||||
"email": "E-Mail",
|
||||
"firstname": "Firstname",
|
||||
"lastname": "Lastname",
|
||||
"phone": "Phone number",
|
||||
"phone": "Phone Number",
|
||||
"department": "Department",
|
||||
"api-enabled": "API Access",
|
||||
"disabled": "Account Disabled",
|
||||
"locked": "Account Locked",
|
||||
"no-peers": "User has no associated peers.",
|
||||
@@ -330,7 +393,7 @@
|
||||
"endpoint": {
|
||||
"label": "Endpoint Address",
|
||||
"placeholder": "Endpoint Address",
|
||||
"description": "The endpoint address that peers will connect to."
|
||||
"description": "The endpoint address that peers will connect to. (e.g. wg.example.com or wg.example.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "IP Networks",
|
||||
@@ -396,7 +459,8 @@
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Private Key",
|
||||
"placeholder": "The private key"
|
||||
"placeholder": "The private key",
|
||||
"help": "The private key is stored securely on the server. If the user already holds a copy, you may omit this field. The server still functions exclusively with the peer’s public key."
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Public Key",
|
||||
|
515
frontend/src/lang/translations/fr.json
Normal file
515
frontend/src/lang/translations/fr.json
Normal file
@@ -0,0 +1,515 @@
|
||||
{
|
||||
"languages": {
|
||||
"fr": "Français"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Nombre d'éléments",
|
||||
"all": "Tous (lent)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Rechercher...",
|
||||
"button": "Rechercher"
|
||||
},
|
||||
"select-all": "Tout sélectionner",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"cancel": "Annuler",
|
||||
"close": "Fermer",
|
||||
"save": "Enregistrer",
|
||||
"delete": "Supprimer"
|
||||
},
|
||||
"login": {
|
||||
"headline": "Veuillez vous connecter",
|
||||
"username": {
|
||||
"label": "Nom d'utilisateur",
|
||||
"placeholder": "Veuillez entrer votre nom d'utilisateur"
|
||||
},
|
||||
"password": {
|
||||
"label": "Mot de passe",
|
||||
"placeholder": "Veuillez entrer votre mot de passe"
|
||||
},
|
||||
"button": "Se connecter"
|
||||
},
|
||||
"menu": {
|
||||
"home": "Accueil",
|
||||
"interfaces": "Interfaces",
|
||||
"users": "Utilisateurs",
|
||||
"lang": "Changer de langue",
|
||||
"profile": "Mon profil",
|
||||
"settings": "Paramètres",
|
||||
"login": "Se connecter",
|
||||
"logout": "Se déconnecter"
|
||||
},
|
||||
"home": {
|
||||
"headline": "Portail VPN WireGuard®",
|
||||
"info-headline": "Plus d'informations",
|
||||
"abstract": "WireGuard® est un VPN extrêmement simple mais rapide et moderne qui utilise une cryptographie de pointe. Il vise à être plus rapide, plus simple, plus léger et plus utile qu'IPsec, tout en évitant le casse-tête massif. Il se veut considérablement plus performant qu'OpenVPN.",
|
||||
"installation": {
|
||||
"box-header": "Installation de WireGuard",
|
||||
"headline": "Installation",
|
||||
"content": "Les instructions d'installation du logiciel client sont disponibles sur le site Web officiel de WireGuard.",
|
||||
"button": "Ouvrir les instructions"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "À propos de WireGuard",
|
||||
"headline": "À propos",
|
||||
"content": "WireGuard® est un VPN extrêmement simple mais rapide et moderne qui utilise une cryptographie de pointe.",
|
||||
"button": "Plus d'informations"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "À propos du Portail WireGuard",
|
||||
"headline": "Portail WireGuard",
|
||||
"content": "Le Portail WireGuard est un portail de configuration simple basé sur le Web pour WireGuard.",
|
||||
"button": "Plus d'informations"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "Profils VPN",
|
||||
"abstract": "Vous pouvez accéder et télécharger vos configurations VPN personnelles via votre profil utilisateur.",
|
||||
"content": "Pour trouver tous vos profils configurés, cliquez sur le bouton ci-dessous.",
|
||||
"button": "Ouvrir mon profil"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "Zone d'administration",
|
||||
"abstract": "Dans la zone d'administration, vous pouvez gérer les pairs WireGuard et l'interface du serveur, ainsi que les utilisateurs autorisés à se connecter au Portail WireGuard.",
|
||||
"content": "",
|
||||
"button-admin": "Ouvrir l'administration du serveur",
|
||||
"button-user": "Ouvrir l'administration des utilisateurs"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "Administration des interfaces",
|
||||
"headline-peers": "Pairs VPN actuels",
|
||||
"headline-endpoints": "Points de terminaison actuels",
|
||||
"no-interface": {
|
||||
"default-selection": "Aucune interface disponible",
|
||||
"headline": "Aucune interface trouvée...",
|
||||
"abstract": "Cliquez sur le bouton plus ci-dessus pour créer une nouvelle interface WireGuard."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Aucun pair disponible",
|
||||
"abstract": "Actuellement, aucun pair n'est disponible pour l'interface WireGuard sélectionnée."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "Nom",
|
||||
"user": "Utilisateur",
|
||||
"ip": "IP",
|
||||
"endpoint": "Point de terminaison",
|
||||
"status": "Statut"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "État de l'interface pour",
|
||||
"mode": "mode",
|
||||
"key": "Clé publique",
|
||||
"endpoint": "Point de terminaison public",
|
||||
"port": "Port d'écoute",
|
||||
"peers": "Pairs activés",
|
||||
"total-peers": "Total des pairs",
|
||||
"endpoints": "Points de terminaison activés",
|
||||
"total-endpoints": "Total des points de terminaison",
|
||||
"ip": "Adresse IP",
|
||||
"default-allowed-ip": "IP autorisées par défaut",
|
||||
"dns": "Serveurs DNS",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "Intervalle Keepalive par défaut",
|
||||
"button-show-config": "Afficher la configuration",
|
||||
"button-download-config": "Télécharger la configuration",
|
||||
"button-store-config": "Enregistrer la configuration pour wg-quick",
|
||||
"button-edit": "Modifier l'interface"
|
||||
},
|
||||
"button-add-interface": "Ajouter une interface",
|
||||
"button-add-peer": "Ajouter un pair",
|
||||
"button-add-peers": "Ajouter plusieurs pairs",
|
||||
"button-show-peer": "Afficher le pair",
|
||||
"button-edit-peer": "Modifier le pair",
|
||||
"peer-disabled": "Le pair est désactivé, raison :",
|
||||
"peer-expiring": "Le pair expire le",
|
||||
"peer-connected": "Connecté",
|
||||
"peer-not-connected": "Non connecté",
|
||||
"peer-handshake": "Dernière négociation :",
|
||||
"button-show-peer": "Afficher le pair",
|
||||
"button-edit-peer": "Modifier le pair"
|
||||
},
|
||||
"users": {
|
||||
"headline": "Administration des utilisateurs",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "E-mail",
|
||||
"firstname": "Prénom",
|
||||
"lastname": "Nom",
|
||||
"source": "Source",
|
||||
"peers": "Pairs",
|
||||
"admin": "Admin"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "Aucun utilisateur disponible",
|
||||
"abstract": "Actuellement, aucun utilisateur n'est enregistré auprès du Portail WireGuard."
|
||||
},
|
||||
"button-add-user": "Ajouter un utilisateur",
|
||||
"button-show-user": "Afficher l'utilisateur",
|
||||
"button-edit-user": "Modifier l'utilisateur",
|
||||
"user-disabled": "L'utilisateur est désactivé, raison :",
|
||||
"user-locked": "Le compte est verrouillé, raison :",
|
||||
"admin": "L'utilisateur a des privilèges d'administrateur",
|
||||
"no-admin": "L'utilisateur n'a pas de privilèges d'administrateur"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "Mes pairs VPN",
|
||||
"table-heading": {
|
||||
"name": "Nom",
|
||||
"ip": "IP",
|
||||
"stats": "Statut",
|
||||
"interface": "Interface serveur"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Aucun pair disponible",
|
||||
"abstract": "Actuellement, aucun pair n'est associé à votre profil utilisateur."
|
||||
},
|
||||
"peer-connected": "Connecté",
|
||||
"button-add-peer": "Ajouter un pair",
|
||||
"button-show-peer": "Afficher le pair",
|
||||
"button-edit-peer": "Modifier le pair"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Paramètres",
|
||||
"abstract": "Ici, vous pouvez modifier vos paramètres personnels.",
|
||||
"api": {
|
||||
"headline": "Paramètres de l'API",
|
||||
"abstract": "Ici, vous pouvez configurer les paramètres de l'API RESTful.",
|
||||
"active-description": "L'API est actuellement active pour votre compte utilisateur. Toutes les requêtes API sont authentifiées avec l'authentification de base. Utilisez les informations d'identification suivantes pour l'authentification.",
|
||||
"inactive-description": "L'API est actuellement inactive. Appuyez sur le bouton ci-dessous pour l'activer.",
|
||||
"user-label": "Nom d'utilisateur de l'API :",
|
||||
"user-placeholder": "L'utilisateur de l'API",
|
||||
"token-label": "Mot de passe de l'API :",
|
||||
"token-placeholder": "Le jeton de l'API",
|
||||
"token-created-label": "Accès API accordé le :",
|
||||
"button-disable-title": "Désactiver l'API, cela invalidera le jeton actuel.",
|
||||
"button-disable-text": "Désactiver l'API",
|
||||
"button-enable-title": "Activer l'API, cela générera un nouveau jeton.",
|
||||
"button-enable-text": "Activer l'API",
|
||||
"api-link": "Documentation de l'API"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "Compte utilisateur :",
|
||||
"tab-user": "Informations",
|
||||
"tab-peers": "Pairs",
|
||||
"headline-info": "Informations sur l'utilisateur :",
|
||||
"headline-notes": "Notes :",
|
||||
"email": "E-mail",
|
||||
"firstname": "Prénom",
|
||||
"lastname": "Nom",
|
||||
"phone": "Numéro de téléphone",
|
||||
"department": "Département",
|
||||
"api-enabled": "Accès API",
|
||||
"disabled": "Compte désactivé",
|
||||
"locked": "Compte verrouillé",
|
||||
"no-peers": "L'utilisateur n'a pas de pairs associés.",
|
||||
"peers": {
|
||||
"name": "Nom",
|
||||
"interface": "Interface",
|
||||
"ip": "IP"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "Modifier l'utilisateur :",
|
||||
"headline-new": "Nouvel utilisateur",
|
||||
"header-general": "Général",
|
||||
"header-personal": "Informations sur l'utilisateur",
|
||||
"header-notes": "Notes",
|
||||
"header-state": "État",
|
||||
"identifier": {
|
||||
"label": "Identifiant",
|
||||
"placeholder": "L'identifiant unique de l'utilisateur"
|
||||
},
|
||||
"source": {
|
||||
"label": "Source",
|
||||
"placeholder": "La source de l'utilisateur"
|
||||
},
|
||||
"password": {
|
||||
"label": "Mot de passe",
|
||||
"placeholder": "Un mot de passe super secret",
|
||||
"description": "Laissez ce champ vide pour conserver le mot de passe actuel."
|
||||
},
|
||||
"email": {
|
||||
"label": "E-mail",
|
||||
"placeholder": "L'adresse e-mail"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Téléphone",
|
||||
"placeholder": "Le numéro de téléphone"
|
||||
},
|
||||
"department": {
|
||||
"label": "Département",
|
||||
"placeholder": "Le département"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "Prénom",
|
||||
"placeholder": "Prénom"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "Nom",
|
||||
"placeholder": "Nom"
|
||||
},
|
||||
"notes": {
|
||||
"label": "Notes",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Désactivé (aucune connexion WireGuard et aucune connexion possible)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "Verrouillé (aucune connexion possible, les connexions WireGuard fonctionnent toujours)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "Est Admin"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "Configuration pour l'interface :"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "Modifier l'interface :",
|
||||
"headline-new": "Nouvelle interface",
|
||||
"tab-interface": "Interface",
|
||||
"tab-peerdef": "Valeurs par défaut des pairs",
|
||||
"header-general": "Général",
|
||||
"header-network": "Réseau",
|
||||
"header-crypto": "Cryptographie",
|
||||
"header-hooks": "Hooks d'interface",
|
||||
"header-peer-hooks": "Hooks",
|
||||
"header-state": "État",
|
||||
"identifier": {
|
||||
"label": "Identifiant",
|
||||
"placeholder": "L'identifiant unique de l'interface"
|
||||
},
|
||||
"mode": {
|
||||
"label": "Mode de l'interface",
|
||||
"server": "Mode serveur",
|
||||
"client": "Mode client",
|
||||
"any": "Mode inconnu"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "Nom d'affichage",
|
||||
"placeholder": "Le nom descriptif de l'interface"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Clé privée",
|
||||
"placeholder": "La clé privée"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Clé publique",
|
||||
"placeholder": "La clé publique"
|
||||
},
|
||||
"ip": {
|
||||
"label": "Adresses IP",
|
||||
"placeholder": "Adresses IP (format CIDR)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "Port d'écoute",
|
||||
"placeholder": "Le port d'écoute"
|
||||
},
|
||||
"dns": {
|
||||
"label": "Serveur DNS",
|
||||
"placeholder": "Les serveurs DNS qui doivent être utilisés"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "Domaines de recherche DNS",
|
||||
"placeholder": "Préfixes de recherche DNS"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "Le MTU de l'interface (0 = conserver la valeur par défaut)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "Marque de pare-feu",
|
||||
"placeholder": "Marque de pare-feu appliquée au trafic sortant. (0 = automatique)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "Table de routage",
|
||||
"placeholder": "L'ID de la table de routage",
|
||||
"description": "Cas particuliers : off = ne pas gérer les routes, 0 = automatique"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pré-Up",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pré-Down",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Interface désactivée"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "Enregistrer automatiquement la configuration wg-quick"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "Adresse du point de terminaison",
|
||||
"placeholder": "Adresse du point de terminaison",
|
||||
"description": "L'adresse du point de terminaison auquel les pairs se connecteront. (par exemple, wg.example.com ou wg.example.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "Réseaux IP",
|
||||
"placeholder": "Adresses de réseau",
|
||||
"description": "Les pairs recevront des adresses IP de ces sous-réseaux."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Adresses IP autorisées",
|
||||
"placeholder": "Adresses IP autorisées par défaut"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "Le MTU du client (0 = conserver la valeur par défaut)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Intervalle Keep Alive",
|
||||
"placeholder": "Persistent Keepalive (0 = par défaut)"
|
||||
}
|
||||
},
|
||||
"button-apply-defaults": "Appliquer les valeurs par défaut des pairs"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "Pair :",
|
||||
"headline-endpoint": "Point de terminaison :",
|
||||
"section-info": "Informations sur le pair",
|
||||
"section-status": "État actuel",
|
||||
"section-config": "Configuration",
|
||||
"identifier": "Identifiant",
|
||||
"ip": "Adresses IP",
|
||||
"user": "Utilisateur associé",
|
||||
"notes": "Notes",
|
||||
"expiry-status": "Expire le",
|
||||
"disabled-status": "Désactivé le",
|
||||
"traffic": "Trafic",
|
||||
"connection-status": "Statistiques de connexion",
|
||||
"upload": "Octets envoyés (du serveur au pair)",
|
||||
"download": "Octets téléchargés (du pair au serveur)",
|
||||
"pingable": "Peut être pingé",
|
||||
"handshake": "Dernière négociation",
|
||||
"connected-since": "Connecté depuis",
|
||||
"endpoint": "Point de terminaison",
|
||||
"button-download": "Télécharger la configuration",
|
||||
"button-email": "Envoyer la configuration par e-mail"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "Modifier le pair :",
|
||||
"headline-edit-endpoint": "Modifier le point de terminaison :",
|
||||
"headline-new-peer": "Créer un pair",
|
||||
"headline-new-endpoint": "Créer un point de terminaison",
|
||||
"header-general": "Général",
|
||||
"header-network": "Réseau",
|
||||
"header-crypto": "Cryptographie",
|
||||
"header-hooks": "Hooks (exécutés sur le pair)",
|
||||
"header-state": "État",
|
||||
"display-name": {
|
||||
"label": "Nom d'affichage",
|
||||
"placeholder": "Le nom descriptif du pair"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "Utilisateur lié",
|
||||
"placeholder": "Le compte utilisateur qui possède ce pair"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Clé privée",
|
||||
"placeholder": "La clé privée"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Clé publique",
|
||||
"placeholder": "La clé publique"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Clé pré-partagée",
|
||||
"placeholder": "Clé pré-partagée facultative"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "Clé publique du point de terminaison",
|
||||
"placeholder": "La clé publique du point de terminaison distant"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "Adresse du point de terminaison",
|
||||
"placeholder": "L'adresse du point de terminaison distant"
|
||||
},
|
||||
"ip": {
|
||||
"label": "Adresses IP",
|
||||
"placeholder": "Adresses IP (format CIDR)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Adresses IP autorisées",
|
||||
"placeholder": "Adresses IP autorisées (format CIDR)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "Adresses IP autorisées supplémentaires",
|
||||
"placeholder": "IP autorisées supplémentaires (côté serveur)",
|
||||
"description": "Ces IP seront ajoutées à l'interface WireGuard distante comme IP autorisées."
|
||||
},
|
||||
"dns": {
|
||||
"label": "Serveur DNS",
|
||||
"placeholder": "Les serveurs DNS qui doivent être utilisés"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "Domaines de recherche DNS",
|
||||
"placeholder": "Préfixes de recherche DNS"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Intervalle Keep Alive",
|
||||
"placeholder": "Persistent Keepalive (0 = par défaut)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "Le MTU du client (0 = conserver la valeur par défaut)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pré-Up",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pré-Down",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "Une ou plusieurs commandes bash séparées par ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Pair désactivé"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "Ignorer les paramètres globaux"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "Date d'expiration"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "Créer plusieurs pairs",
|
||||
"headline-endpoint": "Créer plusieurs points de terminaison",
|
||||
"identifiers": {
|
||||
"label": "Identifiants d'utilisateur",
|
||||
"placeholder": "Identifiants d'utilisateur",
|
||||
"description": "Un identifiant d'utilisateur (le nom d'utilisateur) pour lequel un pair doit être créé."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "Pair :",
|
||||
"headline-endpoint": "Point de terminaison :",
|
||||
"label": "Préfixe du nom d'affichage",
|
||||
"placeholder": "Le préfixe",
|
||||
"description": "Un préfixe qui est ajouté au nom d'affichage des pairs."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
532
frontend/src/lang/translations/ko.json
Normal file
532
frontend/src/lang/translations/ko.json
Normal file
@@ -0,0 +1,532 @@
|
||||
{
|
||||
"languages": {
|
||||
"ko": "한국어"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "항목 수",
|
||||
"all": "전체 (느림)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "검색...",
|
||||
"button": "검색"
|
||||
},
|
||||
"select-all": "모두 선택",
|
||||
"yes": "예",
|
||||
"no": "아니오",
|
||||
"cancel": "취소",
|
||||
"close": "닫기",
|
||||
"save": "저장",
|
||||
"delete": "삭제"
|
||||
},
|
||||
"login": {
|
||||
"headline": "로그인하세요",
|
||||
"username": {
|
||||
"label": "사용자 이름",
|
||||
"placeholder": "사용자 이름을 입력하세요"
|
||||
},
|
||||
"password": {
|
||||
"label": "비밀번호",
|
||||
"placeholder": "비밀번호를 입력하세요"
|
||||
},
|
||||
"button": "로그인"
|
||||
},
|
||||
"menu": {
|
||||
"home": "홈",
|
||||
"interfaces": "인터페이스",
|
||||
"users": "사용자",
|
||||
"lang": "언어 변경",
|
||||
"profile": "내 프로필",
|
||||
"settings": "설정",
|
||||
"audit": "감사 로그",
|
||||
"login": "로그인",
|
||||
"logout": "로그아웃"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® VPN 포털",
|
||||
"info-headline": "추가 정보",
|
||||
"abstract": "WireGuard®는 암호화 기술을 활용하는 매우 간단하면서도 빠르고 현대적인 VPN입니다. IPsec보다 빠르고, 간단하며, 가볍고, 더 유용하면서도 엄청난 골칫거리를 피하는 것을 목표로 합니다. OpenVPN보다 훨씬 더 성능이 뛰어날 것으로 예상됩니다.",
|
||||
"installation": {
|
||||
"box-header": "WireGuard 설치",
|
||||
"headline": "설치",
|
||||
"content": "클라이언트 소프트웨어 설치 지침은 공식 WireGuard 웹사이트에서 찾을 수 있습니다.",
|
||||
"button": "지침 열기"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "WireGuard 정보",
|
||||
"headline": "정보",
|
||||
"content": "WireGuard®는 암호화 기술을 활용하는 매우 간단하면서도 빠르고 현대적인 VPN입니다.",
|
||||
"button": "더 보기"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "WireGuard 포털 정보",
|
||||
"headline": "WireGuard 포털",
|
||||
"content": "WireGuard 포털은 WireGuard를 위한 간단한 웹 기반 구성 포털입니다.",
|
||||
"button": "더 보기"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "VPN 프로필",
|
||||
"abstract": "사용자 프로필을 통해 개인 VPN 구성에 액세스하고 다운로드할 수 있습니다.",
|
||||
"content": "구성된 모든 프로필을 찾으려면 아래 버튼을 클릭하세요.",
|
||||
"button": "내 프로필 열기"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "관리 영역",
|
||||
"abstract": "관리 영역에서는 WireGuard 피어 및 서버 인터페이스뿐만 아니라 WireGuard 포털에 로그인할 수 있는 사용자도 관리할 수 있습니다.",
|
||||
"content": "",
|
||||
"button-admin": "서버 관리 열기",
|
||||
"button-user": "사용자 관리 열기"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "인터페이스 관리",
|
||||
"headline-peers": "현재 VPN 피어",
|
||||
"headline-endpoints": "현재 엔드포인트",
|
||||
"no-interface": {
|
||||
"default-selection": "사용 가능한 인터페이스 없음",
|
||||
"headline": "인터페이스를 찾을 수 없습니다...",
|
||||
"abstract": "새 WireGuard 인터페이스를 만들려면 위의 플러스 버튼을 클릭하세요."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "사용 가능한 피어 없음",
|
||||
"abstract": "현재 선택한 WireGuard 인터페이스에 사용 가능한 피어가 없습니다."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "이름",
|
||||
"user": "사용자",
|
||||
"ip": "IP 주소",
|
||||
"endpoint": "엔드포인트",
|
||||
"status": "상태"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "인터페이스 상태:",
|
||||
"mode": "모드",
|
||||
"key": "공개 키",
|
||||
"endpoint": "공개 엔드포인트",
|
||||
"port": "수신 포트",
|
||||
"peers": "활성화된 피어",
|
||||
"total-peers": "총 피어 수",
|
||||
"endpoints": "활성화된 엔드포인트",
|
||||
"total-endpoints": "총 엔드포인트 수",
|
||||
"ip": "IP 주소",
|
||||
"default-allowed-ip": "기본 허용 IP",
|
||||
"dns": "DNS 서버",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "기본 Keepalive 간격",
|
||||
"button-show-config": "구성 보기",
|
||||
"button-download-config": "구성 다운로드",
|
||||
"button-store-config": "wg-quick용 구성 저장",
|
||||
"button-edit": "인터페이스 편집"
|
||||
},
|
||||
"button-add-interface": "인터페이스 추가",
|
||||
"button-add-peer": "피어 추가",
|
||||
"button-add-peers": "여러 피어 추가",
|
||||
"button-show-peer": "피어 보기",
|
||||
"button-edit-peer": "피어 편집",
|
||||
"peer-disabled": "피어가 비활성화됨, 이유:",
|
||||
"peer-expiring": "피어 만료 예정:",
|
||||
"peer-connected": "연결됨",
|
||||
"peer-not-connected": "연결되지 않음",
|
||||
"peer-handshake": "마지막 핸드셰이크:"
|
||||
},
|
||||
"users": {
|
||||
"headline": "사용자 관리",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "이메일",
|
||||
"firstname": "이름",
|
||||
"lastname": "성",
|
||||
"source": "소스",
|
||||
"peers": "피어",
|
||||
"admin": "관리자"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "사용 가능한 사용자 없음",
|
||||
"abstract": "현재 WireGuard 포털에 등록된 사용자가 없습니다."
|
||||
},
|
||||
"button-add-user": "사용자 추가",
|
||||
"button-show-user": "사용자 보기",
|
||||
"button-edit-user": "사용자 편집",
|
||||
"user-disabled": "사용자가 비활성화됨, 이유:",
|
||||
"user-locked": "계정이 잠김, 이유:",
|
||||
"admin": "사용자에게 관리자 권한이 있습니다",
|
||||
"no-admin": "사용자에게 관리자 권한이 없습니다"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "내 VPN 피어",
|
||||
"table-heading": {
|
||||
"name": "이름",
|
||||
"ip": "IP 주소",
|
||||
"stats": "상태",
|
||||
"interface": "서버 인터페이스"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "사용 가능한 피어 없음",
|
||||
"abstract": "현재 사용자 프로필과 연결된 피어가 없습니다."
|
||||
},
|
||||
"peer-connected": "연결됨",
|
||||
"button-add-peer": "피어 추가",
|
||||
"button-show-peer": "피어 보기",
|
||||
"button-edit-peer": "피어 편집"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "설정",
|
||||
"abstract": "여기에서 개인 설정을 변경할 수 있습니다.",
|
||||
"api": {
|
||||
"headline": "API 설정",
|
||||
"abstract": "여기에서 RESTful API 설정을 구성할 수 있습니다.",
|
||||
"active-description": "현재 사용자 계정에 대해 API가 활성화되어 있습니다. 모든 API 요청은 기본 인증(Basic Auth)으로 인증됩니다. 인증에 다음 자격 증명을 사용하세요.",
|
||||
"inactive-description": "현재 API가 비활성화되어 있습니다. 활성화하려면 아래 버튼을 누르세요.",
|
||||
"user-label": "API 사용자 이름:",
|
||||
"user-placeholder": "API 사용자",
|
||||
"token-label": "API 비밀번호:",
|
||||
"token-placeholder": "API 토큰",
|
||||
"token-created-label": "API 액세스 권한 부여 시각: ",
|
||||
"button-disable-title": "API를 비활성화합니다. 현재 토큰이 무효화됩니다.",
|
||||
"button-disable-text": "API 비활성화",
|
||||
"button-enable-title": "API를 활성화합니다. 새 토큰이 생성됩니다.",
|
||||
"button-enable-text": "API 활성화",
|
||||
"api-link": "API 문서"
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"headline": "감사 로그",
|
||||
"abstract": "여기에서 WireGuard 포털에서 수행된 모든 작업의 감사 로그를 찾을 수 있습니다.",
|
||||
"no-entries": {
|
||||
"headline": "로그 항목 없음",
|
||||
"abstract": "현재 기록된 감사 로그가 없습니다."
|
||||
},
|
||||
"entries-headline": "로그 항목",
|
||||
"table-heading": {
|
||||
"id": "#",
|
||||
"time": "시간",
|
||||
"user": "사용자",
|
||||
"severity": "심각도",
|
||||
"origin": "출처",
|
||||
"message": "메시지"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "사용자 계정:",
|
||||
"tab-user": "정보",
|
||||
"tab-peers": "피어",
|
||||
"headline-info": "사용자 정보:",
|
||||
"headline-notes": "메모:",
|
||||
"email": "이메일",
|
||||
"firstname": "이름",
|
||||
"lastname": "성",
|
||||
"phone": "전화번호",
|
||||
"department": "부서",
|
||||
"api-enabled": "API 액세스",
|
||||
"disabled": "계정 비활성화됨",
|
||||
"locked": "계정 잠김",
|
||||
"no-peers": "사용자에게 연결된 피어가 없습니다.",
|
||||
"peers": {
|
||||
"name": "이름",
|
||||
"interface": "인터페이스",
|
||||
"ip": "IP 주소"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "사용자 편집:",
|
||||
"headline-new": "새 사용자",
|
||||
"header-general": "일반",
|
||||
"header-personal": "사용자 정보",
|
||||
"header-notes": "메모",
|
||||
"header-state": "상태",
|
||||
"identifier": {
|
||||
"label": "식별자",
|
||||
"placeholder": "고유한 사용자 식별자"
|
||||
},
|
||||
"source": {
|
||||
"label": "소스",
|
||||
"placeholder": "사용자 소스"
|
||||
},
|
||||
"password": {
|
||||
"label": "비밀번호",
|
||||
"placeholder": "매우 비밀스러운 비밀번호",
|
||||
"description": "현재 비밀번호를 유지하려면 이 필드를 비워 두세요."
|
||||
},
|
||||
"email": {
|
||||
"label": "이메일",
|
||||
"placeholder": "이메일 주소"
|
||||
},
|
||||
"phone": {
|
||||
"label": "전화번호",
|
||||
"placeholder": "전화번호"
|
||||
},
|
||||
"department": {
|
||||
"label": "부서",
|
||||
"placeholder": "부서"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "이름",
|
||||
"placeholder": "이름"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "성",
|
||||
"placeholder": "성"
|
||||
},
|
||||
"notes": {
|
||||
"label": "메모",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "비활성화됨 (WireGuard 연결 및 로그인 불가)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "잠김 (로그인 불가, WireGuard 연결은 계속 작동)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "관리자 여부"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "인터페이스 구성:"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "인터페이스 편집:",
|
||||
"headline-new": "새 인터페이스",
|
||||
"tab-interface": "인터페이스",
|
||||
"tab-peerdef": "피어 기본값",
|
||||
"header-general": "일반",
|
||||
"header-network": "네트워크",
|
||||
"header-crypto": "암호화",
|
||||
"header-hooks": "인터페이스 후크",
|
||||
"header-peer-hooks": "후크",
|
||||
"header-state": "상태",
|
||||
"identifier": {
|
||||
"label": "식별자",
|
||||
"placeholder": "고유한 인터페이스 식별자"
|
||||
},
|
||||
"mode": {
|
||||
"label": "인터페이스 모드",
|
||||
"server": "서버 모드",
|
||||
"client": "클라이언트 모드",
|
||||
"any": "알 수 없는 모드"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "표시 이름",
|
||||
"placeholder": "인터페이스에 대한 설명적인 이름"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "개인 키",
|
||||
"placeholder": "개인 키"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "공개 키",
|
||||
"placeholder": "공개 키"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP 주소",
|
||||
"placeholder": "IP 주소 (CIDR 형식)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "수신 포트",
|
||||
"placeholder": "수신 포트"
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS 서버",
|
||||
"placeholder": "사용해야 하는 DNS 서버"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS 검색 도메인",
|
||||
"placeholder": "DNS 검색 접두사"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "인터페이스 MTU (0 = 기본값 유지)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "방화벽 표시",
|
||||
"placeholder": "나가는 트래픽에 적용되는 방화벽 표시. (0 = 자동)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "라우팅 테이블",
|
||||
"placeholder": "라우팅 테이블 ID",
|
||||
"description": "특수 사례: off = 경로 관리 안 함, 0 = 자동"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "인터페이스 비활성화됨"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "wg-quick 구성 자동 저장"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "엔드포인트 주소",
|
||||
"placeholder": "엔드포인트 주소",
|
||||
"description": "피어가 연결할 엔드포인트 주소. (예: wg.example.com 또는 wg.example.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "IP 네트워크",
|
||||
"placeholder": "네트워크 주소",
|
||||
"description": "피어는 해당 서브넷에서 IP 주소를 받습니다."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "허용된 IP 주소",
|
||||
"placeholder": "기본 허용 IP 주소"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "클라이언트 MTU (0 = 기본값 유지)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Keep Alive 간격",
|
||||
"placeholder": "영구 Keepalive (0 = 기본값)"
|
||||
}
|
||||
},
|
||||
"button-apply-defaults": "피어 기본값 적용"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "피어:",
|
||||
"headline-endpoint": "엔드포인트:",
|
||||
"section-info": "피어 정보",
|
||||
"section-status": "현재 상태",
|
||||
"section-config": "구성",
|
||||
"identifier": "식별자",
|
||||
"ip": "IP 주소",
|
||||
"user": "연결된 사용자",
|
||||
"notes": "메모",
|
||||
"expiry-status": "만료 시각",
|
||||
"disabled-status": "비활성화 시각",
|
||||
"traffic": "트래픽",
|
||||
"connection-status": "연결 통계",
|
||||
"upload": "업로드된 바이트 (서버에서 피어로)",
|
||||
"download": "다운로드된 바이트 (피어에서 서버로)",
|
||||
"pingable": "핑 가능 여부",
|
||||
"handshake": "마지막 핸드셰이크",
|
||||
"connected-since": "연결 시작 시각",
|
||||
"endpoint": "엔드포인트",
|
||||
"button-download": "구성 다운로드",
|
||||
"button-email": "이메일로 구성 보내기"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "피어 편집:",
|
||||
"headline-edit-endpoint": "엔드포인트 편집:",
|
||||
"headline-new-peer": "피어 생성",
|
||||
"headline-new-endpoint": "엔드포인트 생성",
|
||||
"header-general": "일반",
|
||||
"header-network": "네트워크",
|
||||
"header-crypto": "암호화",
|
||||
"header-hooks": "후크 (피어에서 실행됨)",
|
||||
"header-state": "상태",
|
||||
"display-name": {
|
||||
"label": "표시 이름",
|
||||
"placeholder": "피어에 대한 설명적인 이름"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "연결된 사용자",
|
||||
"placeholder": "이 피어를 소유한 사용자 계정"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "개인 키",
|
||||
"placeholder": "개인 키"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "공개 키",
|
||||
"placeholder": "공개 키"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "사전 공유 키",
|
||||
"placeholder": "선택적 사전 공유 키"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "엔드포인트 공개 키",
|
||||
"placeholder": "원격 엔드포인트의 공개 키"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "엔드포인트 주소",
|
||||
"placeholder": "원격 엔드포인트의 주소"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP 주소",
|
||||
"placeholder": "IP 주소 (CIDR 형식)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "허용된 IP 주소",
|
||||
"placeholder": "허용된 IP 주소 (CIDR 형식)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "추가 허용 IP 주소",
|
||||
"placeholder": "추가 허용 IP (서버 측)",
|
||||
"description": "이 IP 주소는 원격 WireGuard 인터페이스에 허용된 IP로 추가됩니다."
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS 서버",
|
||||
"placeholder": "사용해야 하는 DNS 서버"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS 검색 도메인",
|
||||
"placeholder": "DNS 검색 접두사"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Keep Alive 간격",
|
||||
"placeholder": "영구 Keepalive (0 = 기본값)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "클라이언트 MTU (0 = 기본값 유지)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "하나 이상의 bash 명령 (;으로 구분)"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "피어 비활성화됨"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "전역 설정 무시"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "만료 날짜"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "여러 피어 생성",
|
||||
"headline-endpoint": "여러 엔드포인트 생성",
|
||||
"identifiers": {
|
||||
"label": "사용자 식별자",
|
||||
"placeholder": "사용자 식별자",
|
||||
"description": "피어를 생성할 사용자 식별자 (사용자 이름)."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "피어:",
|
||||
"headline-endpoint": "엔드포인트:",
|
||||
"label": "표시 이름 접두사",
|
||||
"placeholder": "접두사",
|
||||
"description": "피어 표시 이름에 추가되는 접두사."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
552
frontend/src/lang/translations/pt.json
Normal file
552
frontend/src/lang/translations/pt.json
Normal file
@@ -0,0 +1,552 @@
|
||||
{
|
||||
"languages": {
|
||||
"pt": "Português"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Número de Elementos",
|
||||
"all": "Todos (lento)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Pesquisar...",
|
||||
"button": "Pesquisar"
|
||||
},
|
||||
"select-all": "Selecionar tudo",
|
||||
"yes": "Sim",
|
||||
"no": "Não",
|
||||
"cancel": "Cancelar",
|
||||
"close": "Fechar",
|
||||
"save": "Guardar",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"login": {
|
||||
"headline": "Por favor, inicie a sessão",
|
||||
"username": {
|
||||
"label": "Nome de utilizador",
|
||||
"placeholder": "Por favor, insira o seu nome de utilizador"
|
||||
},
|
||||
"password": {
|
||||
"label": "Palavra-passe",
|
||||
"placeholder": "Por favor, insira a sua palavra-passe"
|
||||
},
|
||||
"button": "Iniciar sessão"
|
||||
},
|
||||
"menu": {
|
||||
"home": "Início",
|
||||
"interfaces": "Interfaces",
|
||||
"users": "Utilizadores",
|
||||
"lang": "Alterar idioma",
|
||||
"profile": "O Meu Perfil",
|
||||
"settings": "Definições",
|
||||
"audit": "Registo de Auditoria",
|
||||
"login": "Iniciar Sessão",
|
||||
"logout": "Terminar Sessão",
|
||||
"keygen": "Gerador de Chave"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® Portal VPN",
|
||||
"info-headline": "Mais Informações",
|
||||
"abstract": "WireGuard® é uma VPN extremamente simples, mas rápida e moderna que utiliza criptografia de última geração. O seu objetivo é ser mais rápida, simples, leve e útil que o IPsec, enquanto evita grandes dores de cabeça. Pretende ser consideravelmente mais eficiente que o OpenVPN.",
|
||||
"installation": {
|
||||
"box-header": "Instalação do WireGuard",
|
||||
"headline": "Instalação",
|
||||
"content": "As instruções de instalação para o software cliente podem ser encontradas no site oficial do WireGuard.",
|
||||
"button": "Abrir Instruções"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "Sobre o WireGuard",
|
||||
"headline": "Sobre",
|
||||
"content": "WireGuard® é uma VPN extremamente simples, mas rápida e moderna que utiliza criptografia de última geração.",
|
||||
"button": "Mais"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "Sobre o WireGuard Portal",
|
||||
"headline": "WireGuard Portal",
|
||||
"content": "WireGuard Portal é um portal web de configuração simples para o WireGuard.",
|
||||
"button": "Mais"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "Perfis VPN",
|
||||
"abstract": "Pode aceder e baixar as suas configurações pessoais de VPN através do seu Perfil de Utilizador.",
|
||||
"content": "Para encontrar todos os seus perfis configurados, clique no botão abaixo.",
|
||||
"button": "Abrir meu perfil"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "Área de Administração",
|
||||
"abstract": "Na área de administração, pode gerir os peers do WireGuard, a interface do servidor e os utilizadores que têm permissão para aceder ao Portal WireGuard.",
|
||||
"content": "",
|
||||
"button-admin": "Abrir Administração do Servidor",
|
||||
"button-user": "Abrir Administração de Utilizadores"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "Administração de Interfaces",
|
||||
"headline-peers": "Peers VPN Atuais",
|
||||
"headline-endpoints": "Endpoints Atuais",
|
||||
"no-interface": {
|
||||
"default-selection": "Nenhuma interface disponível",
|
||||
"headline": "Nenhuma interface encontrada...",
|
||||
"abstract": "Clique no botão + acima para criar uma nova interface WireGuard."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Nenhum peer disponível",
|
||||
"abstract": "Atualmente, não há peers disponíveis para a interface WireGuard selecionada."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "Nome",
|
||||
"user": "Utilizador",
|
||||
"ip": "IPs",
|
||||
"endpoint": "Endpoint",
|
||||
"status": "Status"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "Status da interface para",
|
||||
"mode": "modo",
|
||||
"key": "Chave Pública",
|
||||
"endpoint": "Endpoint Público",
|
||||
"port": "Porta de Escuta",
|
||||
"peers": "Peers Ativados",
|
||||
"total-peers": "Total de Peers",
|
||||
"endpoints": "Endpoints Ativados",
|
||||
"total-endpoints": "Total de Endpoints",
|
||||
"ip": "Endereço IP",
|
||||
"default-allowed-ip": "IPs permitidos por padrão",
|
||||
"dns": "Servidores DNS",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "Intervalo de Keepalive Padrão",
|
||||
"button-show-config": "Mostrar configuração",
|
||||
"button-download-config": "Baixar configuração",
|
||||
"button-store-config": "Armazenar configuração para wg-quick",
|
||||
"button-edit": "Editar interface"
|
||||
},
|
||||
"button-add-interface": "Adicionar Interface",
|
||||
"button-add-peer": "Adicionar Peer",
|
||||
"button-add-peers": "Adicionar Vários Peers",
|
||||
"button-show-peer": "Mostrar Peer",
|
||||
"button-edit-peer": "Editar Peer",
|
||||
"peer-disabled": "Peer desativado, razão:",
|
||||
"peer-expiring": "Peer expira em",
|
||||
"peer-connected": "Conectado",
|
||||
"peer-not-connected": "Não Conectado",
|
||||
"peer-handshake": "Último handshake:"
|
||||
},
|
||||
"users": {
|
||||
"headline": "Administração de Utilizadores",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Primeiro Nome",
|
||||
"lastname": "Último Nome",
|
||||
"source": "Fonte",
|
||||
"peers": "Peers",
|
||||
"admin": "Administrador"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "Nenhum utilizador disponível",
|
||||
"abstract": "Atualmente, não há utilizadores registados no Portal WireGuard."
|
||||
},
|
||||
"button-add-user": "Adicionar Utilizador",
|
||||
"button-show-user": "Mostrar Utilizador",
|
||||
"button-edit-user": "Editar Utilizador",
|
||||
"user-disabled": "Utilizador desativado, razão:",
|
||||
"user-locked": "Conta bloqueada, razão:",
|
||||
"admin": "O utilizador tem privilégios de administrador",
|
||||
"no-admin": "O utilizador não tem privilégios de administrador"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "Os Meus Peers VPN",
|
||||
"table-heading": {
|
||||
"name": "Nome",
|
||||
"ip": "IPs",
|
||||
"stats": "Status",
|
||||
"interface": "Interface do Servidor"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Nenhum peer disponível",
|
||||
"abstract": "Atualmente, não há peers associados ao seu perfil de utilizador."
|
||||
},
|
||||
"peer-connected": "Conectado",
|
||||
"button-add-peer": "Adicionar Peer",
|
||||
"button-show-peer": "Mostrar Peer",
|
||||
"button-edit-peer": "Editar Peer"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Definições",
|
||||
"abstract": "Aqui pode alterar suas Definições pessoais.",
|
||||
"api": {
|
||||
"headline": "Definições da API",
|
||||
"abstract": "Aqui pode configurar as definições da API RESTful.",
|
||||
"active-description": "A API está atualmente ativa para a sua conta de utilizador. Todos os pedidos para a API são autenticadas com Basic Auth. Use as seguintes credenciais para autenticação.",
|
||||
"inactive-description": "A API está atualmente inativa. Pressione o botão abaixo para ativá-la.",
|
||||
"user-label": "Nome de utilizador API:",
|
||||
"user-placeholder": "O utilizador da API",
|
||||
"token-label": "Senha da API:",
|
||||
"token-placeholder": "O token da API",
|
||||
"token-created-label": "Acesso API concedido em: ",
|
||||
"button-disable-title": "Desativar API, invalidando o token atual.",
|
||||
"button-disable-text": "Desativar API",
|
||||
"button-enable-title": "Ativar API, gerando um novo token.",
|
||||
"button-enable-text": "Ativar API",
|
||||
"api-link": "Documentação da API"
|
||||
}
|
||||
},
|
||||
"audit": {
|
||||
"headline": "Registo de Auditoria",
|
||||
"abstract": "Aqui pode encontrar o registo de auditoria de todas as ações realizadas no WireGuard Portal.",
|
||||
"no-entries": {
|
||||
"headline": "Nenhuma entrada no registo",
|
||||
"abstract": "Atualmente, não há entradas de registo de auditoria gravadas."
|
||||
},
|
||||
"entries-headline": "Entradas do Registo",
|
||||
"table-heading": {
|
||||
"id": "#",
|
||||
"time": "Hora",
|
||||
"user": "Utilizador",
|
||||
"severity": "Gravidade",
|
||||
"origin": "Origem",
|
||||
"message": "Mensagem"
|
||||
}
|
||||
},
|
||||
"keygen": {
|
||||
"headline": "Gerador de Chaves WireGuard",
|
||||
"abstract": "Gere novas chaves WireGuard. As chaves são geradas no seu browser e nunca são enviadas para o servidor.",
|
||||
"headline-keypair": "Novo Par de Chaves",
|
||||
"headline-preshared-key": "Nova Chave Pré-Partilhada",
|
||||
"button-generate": "Gerar",
|
||||
"private-key": {
|
||||
"label": "Chave Privada",
|
||||
"placeholder": "A chave privada"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Chave Pública",
|
||||
"placeholder": "A chave pública"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Chave Pré-Partilhada",
|
||||
"placeholder": "A chave pré-partilhada"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "Conta de Utilizador:",
|
||||
"tab-user": "Informação",
|
||||
"tab-peers": "Peers",
|
||||
"headline-info": "Informação do Utilizador:",
|
||||
"headline-notes": "Notas:",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Primeiro Nome",
|
||||
"lastname": "Último Nome",
|
||||
"phone": "Número de Telefone",
|
||||
"department": "Departamento",
|
||||
"api-enabled": "Acesso API",
|
||||
"disabled": "Conta Desativada",
|
||||
"locked": "Conta Bloqueada",
|
||||
"no-peers": "O utilizador não tem peers associados.",
|
||||
"peers": {
|
||||
"name": "Nome",
|
||||
"interface": "Interface",
|
||||
"ip": "IP's"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "Editar utilizador:",
|
||||
"headline-new": "Novo utilizador",
|
||||
"header-general": "Geral",
|
||||
"header-personal": "Informação do Utilizador",
|
||||
"header-notes": "Notas",
|
||||
"header-state": "Estado",
|
||||
"identifier": {
|
||||
"label": "Identificador",
|
||||
"placeholder": "O identificador único do utilizador"
|
||||
},
|
||||
"source": {
|
||||
"label": "Fonte",
|
||||
"placeholder": "A fonte do utilizador"
|
||||
},
|
||||
"password": {
|
||||
"label": "Palavra-passe",
|
||||
"placeholder": "Uma palavra-passe super secreta",
|
||||
"description": "Deixe este campo em branco para manter a palavra-passe atual."
|
||||
},
|
||||
"email": {
|
||||
"label": "Email",
|
||||
"placeholder": "O endereço de e-mail"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Telefone",
|
||||
"placeholder": "O número de telefone"
|
||||
},
|
||||
"department": {
|
||||
"label": "Departamento",
|
||||
"placeholder": "O departamento"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "Primeiro Nome",
|
||||
"placeholder": "Primeiro Nome"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "Último Nome",
|
||||
"placeholder": "Último Nome"
|
||||
},
|
||||
"notes": {
|
||||
"label": "Notas",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Desativado (sem conexão WireGuard e login possível)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "Bloqueado (sem login possível, as conexões WireGuard ainda funcionam)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "É Administrador"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "Configuração para a Interface:"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "Editar Interface:",
|
||||
"headline-new": "Nova Interface",
|
||||
"tab-interface": "Interface",
|
||||
"tab-peerdef": "Padrões de Peer",
|
||||
"header-general": "Geral",
|
||||
"header-network": "Rede",
|
||||
"header-crypto": "Criptografia",
|
||||
"header-hooks": "Hooks da Interface",
|
||||
"header-peer-hooks": "Hooks",
|
||||
"header-state": "Estado",
|
||||
"identifier": {
|
||||
"label": "Identificador",
|
||||
"placeholder": "O identificador único da interface"
|
||||
},
|
||||
"mode": {
|
||||
"label": "Modo da Interface",
|
||||
"server": "Modo Servidor",
|
||||
"client": "Modo Cliente",
|
||||
"any": "Modo Desconhecido"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "Nome de Exibição",
|
||||
"placeholder": "O nome descritivo para a interface"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Chave Privada",
|
||||
"placeholder": "A chave privada"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Chave Pública",
|
||||
"placeholder": "A chave pública"
|
||||
},
|
||||
"ip": {
|
||||
"label": "Endereços IP",
|
||||
"placeholder": "Endereços IP (formato CIDR)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "Porta de Escuta",
|
||||
"placeholder": "A porta de escuta"
|
||||
},
|
||||
"dns": {
|
||||
"label": "Servidor DNS",
|
||||
"placeholder": "Os servidores DNS que devem ser usados"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "Domínios de Pesquisa DNS",
|
||||
"placeholder": "Prefixos de pesquisa DNS"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "O MTU da interface (0 = manter o valor padrão)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "Marca de Firewall",
|
||||
"placeholder": "Marca de firewall aplicada ao tráfego de saída. (0 = automático)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "Tabela de Roteamento",
|
||||
"placeholder": "O ID da tabela de roteamento",
|
||||
"description": "Casos especiais: off = não gerenciar rotas, 0 = automático"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Interface Desativada"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "Guardar configuração wg-quick automaticamente"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "Endereço do Endpoint",
|
||||
"placeholder": "Endereço do Endpoint",
|
||||
"description": "O endereço do endpoint ao qual os peers se irão conectar. (ex. wg.exemplo.com ou wg.exemplo.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "Redes IP",
|
||||
"placeholder": "Endereços de Rede",
|
||||
"description": "Os peers irão obter endereços IP a partir dessas sub-redes."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Endereços IP Permitidos",
|
||||
"placeholder": "Endereços IP Permitidos por padrão"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "O MTU do cliente (0 = manter o valor padrão)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Intervalo de Keep Alive",
|
||||
"placeholder": "Keepalive persistente (0 = padrão)"
|
||||
}
|
||||
},
|
||||
"button-apply-defaults": "Aplicar Padrões de Peer"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "Peer:",
|
||||
"headline-endpoint": "Endpoint:",
|
||||
"section-info": "Informação do Peer",
|
||||
"section-status": "Estado Atual",
|
||||
"section-config": "Configuração",
|
||||
"identifier": "Identificador",
|
||||
"ip": "Endereços IP",
|
||||
"user": "Utilizador Associado",
|
||||
"notes": "Notas",
|
||||
"expiry-status": "Expira em",
|
||||
"disabled-status": "Desativado em",
|
||||
"traffic": "Tráfego",
|
||||
"connection-status": "Estatísticas de Conexão",
|
||||
"upload": "Bytes Enviados (do Servidor para o Peer)",
|
||||
"download": "Bytes Recebidos (do Peer para o Servidor)",
|
||||
"pingable": "É Pingável",
|
||||
"handshake": "Último Handshake",
|
||||
"connected-since": "Conectado desde",
|
||||
"endpoint": "Endpoint",
|
||||
"button-download": "Baixar configuração",
|
||||
"button-email": "Enviar configuração por E-Mail"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "Editar peer:",
|
||||
"headline-edit-endpoint": "Editar endpoint:",
|
||||
"headline-new-peer": "Criar peer",
|
||||
"headline-new-endpoint": "Criar endpoint",
|
||||
"header-general": "Geral",
|
||||
"header-network": "Rede",
|
||||
"header-crypto": "Criptografia",
|
||||
"header-hooks": "Hooks (Executados no Peer)",
|
||||
"header-state": "Estado",
|
||||
"display-name": {
|
||||
"label": "Nome de Exibição",
|
||||
"placeholder": "O nome descritivo para o peer"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "Utilizador Associado",
|
||||
"placeholder": "A conta de utilizador que possui este peer"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Chave Privada",
|
||||
"placeholder": "A chave privada",
|
||||
"help": "A chave privada é armazenada de forma segura no servidor. Se o utilizador já tiver uma cópia, pode omitir este campo. O servidor ainda funciona exclusivamente com a chave pública do peer."
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Chave Pública",
|
||||
"placeholder": "A chave pública"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Chave Pré-Partilhada",
|
||||
"placeholder": "Chave pré-partilhada opcional"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "Chave Pública do Endpoint",
|
||||
"placeholder": "A chave pública do endpoint remoto"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "Endereço do Endpoint",
|
||||
"placeholder": "O endereço do endpoint remoto"
|
||||
},
|
||||
"ip": {
|
||||
"label": "Endereços IP",
|
||||
"placeholder": "Endereços IP (formato CIDR)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Endereços IP Permitidos",
|
||||
"placeholder": "Endereços IP permitidos"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "Endereços IP adicionais permitidos",
|
||||
"placeholder": "IPs adicionais permitidos (lado do servidor)",
|
||||
"description": "Esses IPs serão adicionados à interface WireGuard remota como IPs permitidos."
|
||||
},
|
||||
"dns": {
|
||||
"label": "Servidor DNS",
|
||||
"placeholder": "Os servidores DNS que devem ser utilizados"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "Domínios de Pesquisa DNS",
|
||||
"placeholder": "Prefixos de pesquisa DNS"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Intervalo de Keep Alive",
|
||||
"placeholder": "Keepalive persistente (0 = padrão)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "O MTU do cliente (0 = manter o padrão)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "Um ou vários comandos bash separados por ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Peer Desativado"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "Ignorar definições globais"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "Data de expiração"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "Criar múltiplos peers",
|
||||
"headline-endpoint": "Criar múltiplos endpoints",
|
||||
"identifiers": {
|
||||
"label": "Identificadores de utilizador",
|
||||
"placeholder": "Identificadores de utilizador",
|
||||
"description": "Um identificador de utilizador (nome de utilizador) para o qual um peer deve ser criado."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "Peer:",
|
||||
"headline-endpoint": "Endpoint:",
|
||||
"label": "Prefixo do nome exibido",
|
||||
"placeholder": "O prefixo",
|
||||
"description": "Um prefixo que será adicionado ao nome exibido do peer."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"languages": {
|
||||
"ru": "Русский"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Количество элементов",
|
||||
|
515
frontend/src/lang/translations/uk.json
Normal file
515
frontend/src/lang/translations/uk.json
Normal file
@@ -0,0 +1,515 @@
|
||||
{
|
||||
"languages": {
|
||||
"uk": "Українська"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Кількість елементів",
|
||||
"all": "Всі (повільно)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Пошук...",
|
||||
"button": "Пошук"
|
||||
},
|
||||
"select-all": "Вибрати все",
|
||||
"yes": "Так",
|
||||
"no": "Ні",
|
||||
"cancel": "Скасувати",
|
||||
"close": "Закрити",
|
||||
"save": "Зберегти",
|
||||
"delete": "Видалити"
|
||||
},
|
||||
"login": {
|
||||
"headline": "Будь ласка, увійдіть",
|
||||
"username": {
|
||||
"label": "Ім'я користувача",
|
||||
"placeholder": "Введіть ім'я користувача"
|
||||
},
|
||||
"password": {
|
||||
"label": "Пароль",
|
||||
"placeholder": "Введіть пароль"
|
||||
},
|
||||
"button": "Увійти"
|
||||
},
|
||||
"menu": {
|
||||
"home": "Головна",
|
||||
"interfaces": "Інтерфейси",
|
||||
"users": "Користувачі",
|
||||
"lang": "Змінити мову",
|
||||
"profile": "Мій профіль",
|
||||
"settings": "Налаштування",
|
||||
"login": "Вхід",
|
||||
"logout": "Вийти"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® VPN Портал",
|
||||
"info-headline": "Додаткова інформація",
|
||||
"abstract": "WireGuard® — це високоефективний, сучасний і легкий VPN, який використовує передову криптографію. Розроблений для простоти та швидкості, він перевершує IPsec, усуваючи зайву складність. Крім того, він прагне забезпечити значно кращу продуктивність, ніж OpenVPN.",
|
||||
"installation": {
|
||||
"box-header": "Встановлення WireGuard",
|
||||
"headline": "Встановлення",
|
||||
"content": "Інструкції щодо встановлення клієнтського програмного забезпечення можна знайти на офіційному сайті WireGuard.",
|
||||
"button": "Відкрити інструкції"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "Про WireGuard",
|
||||
"headline": "Про програму",
|
||||
"content": "WireGuard® — це надзвичайно простий, швидкий і сучасний VPN, що використовує передову криптографію.",
|
||||
"button": "Докладніше"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "Про портал WireGuard",
|
||||
"headline": "Портал WireGuard",
|
||||
"content": "Портал WireGuard — це простий веб-інтерфейс для налаштування WireGuard.",
|
||||
"button": "Докладніше"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "VPN профілі",
|
||||
"abstract": "Ви можете отримати доступ та завантажити свої особисті VPN-конфігурації через свій профіль користувача.",
|
||||
"content": "Щоб переглянути всі налаштовані профілі, натисніть кнопку нижче.",
|
||||
"button": "Відкрити мій профіль"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "Адміністративна панель",
|
||||
"abstract": "У адміністративній панелі ви можете керувати клієнтами WireGuard, серверним інтерфейсом і користувачами, які мають доступ до порталу WireGuard.",
|
||||
"content": "",
|
||||
"button-admin": "Відкрити адміністрування сервера",
|
||||
"button-user": "Відкрити адміністрування користувачів"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "Адміністрування інтерфейсів",
|
||||
"headline-peers": "Поточні VPN-піри",
|
||||
"headline-endpoints": "Поточні кінцеві точки",
|
||||
"no-interface": {
|
||||
"default-selection": "Немає доступного інтерфейсу",
|
||||
"headline": "Інтерфейси не знайдено...",
|
||||
"abstract": "Натисніть кнопку з плюсом вище, щоб створити новий інтерфейс WireGuard."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Немає доступних пірів",
|
||||
"abstract": "Наразі немає доступних пірів для вибраного інтерфейсу WireGuard."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "Ім'я",
|
||||
"user": "Користувач",
|
||||
"ip": "IP-адреси",
|
||||
"endpoint": "Кінцева точка",
|
||||
"status": "Статус"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "Статус інтерфейсу для",
|
||||
"mode": "режим",
|
||||
"key": "Публічний ключ",
|
||||
"endpoint": "Публічна кінцева точка",
|
||||
"port": "Порт прослуховування",
|
||||
"peers": "Увімкнені піри",
|
||||
"total-peers": "Загальна кількість пірів",
|
||||
"endpoints": "Увімкнені кінцеві точки",
|
||||
"total-endpoints": "Загальна кількість кінцевих точок",
|
||||
"ip": "IP-адреса",
|
||||
"default-allowed-ip": "Типові дозволені IP-адреси",
|
||||
"dns": "DNS-сервери",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "Типовий інтервал Keepalive",
|
||||
"button-show-config": "Показати конфігурацію",
|
||||
"button-download-config": "Завантажити конфігурацію",
|
||||
"button-store-config": "Зберегти конфігурацію для wg-quick",
|
||||
"button-edit": "Редагувати інтерфейс"
|
||||
},
|
||||
"button-add-interface": "Додати інтерфейс",
|
||||
"button-add-peer": "Додати пір",
|
||||
"button-add-peers": "Додати декілька пірів",
|
||||
"button-show-peer": "Показати пір",
|
||||
"button-edit-peer": "Редагувати пір",
|
||||
"peer-disabled": "Пір вимкнено, причина:",
|
||||
"peer-expiring": "Пір припиняє дію о",
|
||||
"peer-connected": "Підключено",
|
||||
"peer-not-connected": "Не підключено",
|
||||
"peer-handshake": "Останнє рукостискання:"
|
||||
},
|
||||
"users": {
|
||||
"headline": "Адміністрування користувачів",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Ім'я",
|
||||
"lastname": "Прізвище",
|
||||
"source": "Джерело",
|
||||
"peers": "Піри",
|
||||
"admin": "Адміністратор"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "Немає доступних користувачів",
|
||||
"abstract": "Наразі немає зареєстрованих користувачів у WireGuard Portal."
|
||||
},
|
||||
"button-add-user": "Додати користувача",
|
||||
"button-show-user": "Показати користувача",
|
||||
"button-edit-user": "Редагувати користувача",
|
||||
"user-disabled": "Користувача вимкнено, причина:",
|
||||
"user-locked": "Обліковий запис заблоковано, причина:",
|
||||
"admin": "Користувач має адміністративні привілеї",
|
||||
"no-admin": "Користувач не має адміністративних привілеїв"
|
||||
},
|
||||
|
||||
"profile": {
|
||||
"headline": "Мої VPN-піри",
|
||||
"table-heading": {
|
||||
"name": "Ім'я",
|
||||
"ip": "IP-адреси",
|
||||
"stats": "Статус",
|
||||
"interface": "Серверний інтерфейс"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Немає доступних пірів",
|
||||
"abstract": "Наразі немає пірів, пов'язаних із вашим профілем користувача."
|
||||
},
|
||||
"peer-connected": "Підключено",
|
||||
"button-add-peer": "Додати пір",
|
||||
"button-show-peer": "Показати пір",
|
||||
"button-edit-peer": "Редагувати пір"
|
||||
},
|
||||
"settings": {
|
||||
"headline": "Налаштування",
|
||||
"abstract": "Тут ви можете змінити особисті налаштування.",
|
||||
"api": {
|
||||
"headline": "Налаштування API",
|
||||
"abstract": "Тут ви можете налаштувати RESTful API.",
|
||||
"active-description": "API наразі активний для вашого облікового запису. Усі API-запити автентифікуються за допомогою Basic Auth. Використовуйте такі облікові дані для автентифікації.",
|
||||
"inactive-description": "API наразі неактивний. Натисніть кнопку нижче, щоб активувати його.",
|
||||
"user-label": "Ім'я користувача API:",
|
||||
"user-placeholder": "Користувач API",
|
||||
"token-label": "Пароль API:",
|
||||
"token-placeholder": "Токен API",
|
||||
"token-created-label": "Доступ до API надано:",
|
||||
"button-disable-title": "Вимкнути API, це зробить поточний токен недійсним.",
|
||||
"button-disable-text": "Вимкнути API",
|
||||
"button-enable-title": "Увімкнути API, це згенерує новий токен.",
|
||||
"button-enable-text": "Увімкнути API",
|
||||
"api-link": "Документація API"
|
||||
}
|
||||
},
|
||||
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "Обліковий запис користувача:",
|
||||
"tab-user": "Інформація",
|
||||
"tab-peers": "Піри",
|
||||
"headline-info": "Інформація про користувача:",
|
||||
"headline-notes": "Примітки:",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Ім'я",
|
||||
"lastname": "Прізвище",
|
||||
"phone": "Номер телефону",
|
||||
"department": "Відділ",
|
||||
"api-enabled": "Доступ до API",
|
||||
"disabled": "Обліковий запис вимкнено",
|
||||
"locked": "Обліковий запис заблоковано",
|
||||
"no-peers": "У користувача немає пов'язаних пірів.",
|
||||
"peers": {
|
||||
"name": "Ім'я",
|
||||
"interface": "Інтерфейс",
|
||||
"ip": "IP-адреси"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "Редагування користувача:",
|
||||
"headline-new": "Новий користувач",
|
||||
"header-general": "Загальні",
|
||||
"header-personal": "Інформація про користувача",
|
||||
"header-notes": "Примітки",
|
||||
"header-state": "Стан",
|
||||
"identifier": {
|
||||
"label": "Ідентифікатор",
|
||||
"placeholder": "Унікальний ідентифікатор користувача"
|
||||
},
|
||||
"source": {
|
||||
"label": "Джерело",
|
||||
"placeholder": "Джерело користувача"
|
||||
},
|
||||
"password": {
|
||||
"label": "Пароль",
|
||||
"placeholder": "Суперсекретний пароль",
|
||||
"description": "Залиште це поле порожнім, щоб зберегти поточний пароль."
|
||||
},
|
||||
"email": {
|
||||
"label": "Електронна адреса",
|
||||
"placeholder": "Електронна адреса"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Телефон",
|
||||
"placeholder": "Номер телефону"
|
||||
},
|
||||
"department": {
|
||||
"label": "Відділ",
|
||||
"placeholder": "Відділ"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "Ім'я",
|
||||
"placeholder": "Ім'я"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "Прізвище",
|
||||
"placeholder": "Прізвище"
|
||||
},
|
||||
"notes": {
|
||||
"label": "Примітки",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Вимкнено (неможливо підключитися до WireGuard і увійти в систему)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "Заблоковано (неможливо увійти, але підключення WireGuard працює)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "Адміністратор"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "Конфігурація для інтерфейсу:"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "Редагувати інтерфейс:",
|
||||
"headline-new": "Новий інтерфейс",
|
||||
"tab-interface": "Інтерфейс",
|
||||
"tab-peerdef": "За замовчуванням для пірів",
|
||||
"header-general": "Загальне",
|
||||
"header-network": "Мережа",
|
||||
"header-crypto": "Криптографія",
|
||||
"header-hooks": "Хуки інтерфейсу",
|
||||
"header-peer-hooks": "Хуки",
|
||||
"header-state": "Стан",
|
||||
"identifier": {
|
||||
"label": "Ідентифікатор",
|
||||
"placeholder": "Унікальний ідентифікатор інтерфейсу"
|
||||
},
|
||||
"mode": {
|
||||
"label": "Режим інтерфейсу",
|
||||
"server": "Серверний режим",
|
||||
"client": "Клієнтський режим",
|
||||
"any": "Невідомий режим"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "Відображуване ім'я",
|
||||
"placeholder": "Описове ім'я для інтерфейсу"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Приватний ключ",
|
||||
"placeholder": "Приватний ключ"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Публічний ключ",
|
||||
"placeholder": "Публічний ключ"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP-адреси",
|
||||
"placeholder": "IP-адреси (у CIDR форматі)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "Порт прослуховування",
|
||||
"placeholder": "Порт прослуховування"
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS сервер",
|
||||
"placeholder": "DNS сервери, які слід використовувати"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS пошукові домени",
|
||||
"placeholder": "DNS пошукові префікси"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "MTU інтерфейсу (0 = залишити за замовчуванням)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "Маркування Firewall",
|
||||
"placeholder": "Маркування firewall, що застосовується до вихідного трафіку. (0 = автоматично)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "Маршрутна таблиця",
|
||||
"placeholder": "ID маршрутної таблиці",
|
||||
"description": "Особливі випадки: off = не керувати маршрутами, 0 = автоматично"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Інтерфейс відключено"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "Автоматично зберігати конфігурацію wg-quick"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "Адреса кінцевої точки",
|
||||
"placeholder": "Адреса кінцевої точки",
|
||||
"description": "Адреса кінцевої точки, до якої підключатимуться піри. (наприклад, wg.example.com або wg.example.com:51820)"
|
||||
},
|
||||
"networks": {
|
||||
"label": "IP мережі",
|
||||
"placeholder": "Адреси мереж",
|
||||
"description": "Піри отримають IP-адреси з цих підмереж."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Дозволені IP-адреси",
|
||||
"placeholder": "За замовчуванням дозволені IP-адреси"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "MTU клієнта (0 = залишити за замовчуванням)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Інтервал Keep Alive",
|
||||
"placeholder": "Постійний Keepalive (0 = за замовчуванням)"
|
||||
}
|
||||
},
|
||||
"button-apply-defaults": "Застосувати значення за замовчуванням для пірів"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "Пір:",
|
||||
"headline-endpoint": "Кінцева точка:",
|
||||
"section-info": "Інформація про пір",
|
||||
"section-status": "Поточний стан",
|
||||
"section-config": "Налаштування",
|
||||
"identifier": "Ідентифікатор",
|
||||
"ip": "IP-адреси",
|
||||
"user": "Пов'язаний користувач",
|
||||
"notes": "Примітки",
|
||||
"expiry-status": "Закінчується",
|
||||
"disabled-status": "Відключено",
|
||||
"traffic": "Трафік",
|
||||
"connection-status": "Статистика з'єднання",
|
||||
"upload": "Передано байтів (з серверу до пір)",
|
||||
"download": "Завантажено байтів (з пір до серверу)",
|
||||
"pingable": "Відповідає на ping",
|
||||
"handshake": "Останній handshake",
|
||||
"connected-since": "Підключено з",
|
||||
"endpoint": "Кінцева точка",
|
||||
"button-download": "Завантажити конфігурацію",
|
||||
"button-email": "Надіслати конфігурацію електронною поштою"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "Редагувати пір:",
|
||||
"headline-edit-endpoint": "Редагувати кінцеву точку:",
|
||||
"headline-new-peer": "Створити пір",
|
||||
"headline-new-endpoint": "Створити кінцеву точку",
|
||||
"header-general": "Загальне",
|
||||
"header-network": "Мережа",
|
||||
"header-crypto": "Криптографія",
|
||||
"header-hooks": "Хуки (виконуються на пірі)",
|
||||
"header-state": "Стан",
|
||||
"display-name": {
|
||||
"label": "Відображуване ім'я",
|
||||
"placeholder": "Описове ім'я для пір"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "Пов'язаний користувач",
|
||||
"placeholder": "Обліковий запис користувача, що володіє цим піром"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Приватний ключ",
|
||||
"placeholder": "Приватний ключ"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Публічний ключ",
|
||||
"placeholder": "Публічний ключ"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Попередньо спільний ключ",
|
||||
"placeholder": "Опціональний попередньо спільний ключ"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "Публічний ключ кінцевої точки",
|
||||
"placeholder": "Публічний ключ віддаленої кінцевої точки"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "Адреса кінцевої точки",
|
||||
"placeholder": "Адреса віддаленої кінцевої точки"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP-адреси",
|
||||
"placeholder": "IP-адреси (у CIDR форматі)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Дозволені IP-адреси",
|
||||
"placeholder": "Дозволені IP-адреси (у CIDR форматі)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "Додаткові дозволені IP-адреси",
|
||||
"placeholder": "Додаткові дозволені IP (на стороні сервера)",
|
||||
"description": "Ці IP будуть додані на віддаленому інтерфейсі WireGuard як дозволені IP."
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS сервер",
|
||||
"placeholder": "DNS сервери, які слід використовувати"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS пошукові домени",
|
||||
"placeholder": "DNS пошукові префікси"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Інтервал збереження зв'язку",
|
||||
"placeholder": "Постійний Keepalive (0 = за замовчуванням)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "MTU клієнта (0 = залишити за замовчуванням)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Pre-Up",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Post-Up",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Pre-Down",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Post-Down",
|
||||
"placeholder": "Одна або декілька команд bash, розділених ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Пір відключено"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "Ігнорувати глобальні налаштування"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "Дата закінчення терміну дії"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "Створити декілька пір",
|
||||
"headline-endpoint": "Створити декілька кінцевих точок",
|
||||
"identifiers": {
|
||||
"label": "Ідентифікатори користувача",
|
||||
"placeholder": "Ідентифікатори користувача",
|
||||
"description": "Ідентифікатор користувача (ім'я користувача), для якого слід створити пір."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "Пір:",
|
||||
"headline-endpoint": "Кінцева точка:",
|
||||
"label": "Префікс відображуваного імені",
|
||||
"placeholder": "Префікс",
|
||||
"description": "Префікс, що додається до відображуваного імені пірів."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
492
frontend/src/lang/translations/vi.json
Normal file
492
frontend/src/lang/translations/vi.json
Normal file
@@ -0,0 +1,492 @@
|
||||
{
|
||||
"languages": {
|
||||
"vi": "Tiếng Việt"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "Số mục",
|
||||
"all": "Tất (chậm)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Tìm...",
|
||||
"button": "Tìm kiếm"
|
||||
},
|
||||
"select-all": "Chọn tất",
|
||||
"yes": "Có",
|
||||
"no": "Không",
|
||||
"cancel": "Hủy",
|
||||
"close": "Đóng",
|
||||
"save": "Lưu",
|
||||
"delete": "Xóa"
|
||||
},
|
||||
"login": {
|
||||
"headline": "Vui lòng đăng nhập",
|
||||
"username": {
|
||||
"label": "Tài khoản",
|
||||
"placeholder": "Vui lòng nhập tài khoản"
|
||||
},
|
||||
"password": {
|
||||
"label": "Mật khẩu",
|
||||
"placeholder": "Vui lòng nhập mật khẩu"
|
||||
},
|
||||
"button": "Đăng nhập"
|
||||
},
|
||||
"menu": {
|
||||
"home": "Trang chủ",
|
||||
"interfaces": "Giao diện",
|
||||
"users": "Người dùng",
|
||||
"lang": "Chuyển ngữ",
|
||||
"profile": "Hồ sơ của tôi",
|
||||
"login": "Đăng nhập",
|
||||
"logout": "Đăng xuất"
|
||||
},
|
||||
"home": {
|
||||
"headline": "Cổng VPN WireGuard®",
|
||||
"info-headline": "Thêm thông tin",
|
||||
"abstract": "WireGuard® là một VPN cực kỳ đơn giản nhưng nhanh chóng và hiện đại, sử dụng mật mã tiên tiến. Nó hướng đến mục tiêu nhanh hơn, đơn giản hơn, gọn nhẹ hơn và hữu ích hơn IPsec, cũng đỡ nhức đầu hơn. Nó có hiệu suất dự kiến là cao hơn đáng kể so với OpenVPN.",
|
||||
"installation": {
|
||||
"box-header": "Cài đặt WireGuard",
|
||||
"headline": "Cài đặt",
|
||||
"content": "Bạn có thể tìm thấy hướng dẫn cài đặt phần mềm máy khách trên trang web chính thức của WireGuard.",
|
||||
"button": "Mở hướng dẫn"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "Nói về WireGuard",
|
||||
"headline": "Về",
|
||||
"content": "WireGuard® là một VPN cực kỳ đơn giản nhưng nhanh chóng và hiện đại, sử dụng công nghệ mật mã tiên tiến.",
|
||||
"button": "Thêm"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "Giới thiệu về Cổng thông tin WireGuard",
|
||||
"headline": "Cổng thông tin WireGuard",
|
||||
"content": "Cổng thông tin WireGuard là một cổng cấu hình đơn giản, dựa trên web cho WireGuard.",
|
||||
"button": "Tìm hiểu thêm"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "Hồ sơ VPN",
|
||||
"abstract": "Bạn có thể truy cập và tải xuống các cấu hình VPN cá nhân của mình qua hồ sơ người dùng của bạn.",
|
||||
"content": "Để tìm tất cả các hồ sơ đã cấu hình của bạn, hãy nhấp vào nút dưới đây.",
|
||||
"button": "Mở hồ sơ của tôi"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "Khu vực Quản trị",
|
||||
"abstract": "Trong khu vực quản trị, bạn có thể quản lý các peer WireGuard và giao diện máy chủ cũng như người dùng được phép đăng nhập vào Cổng thông tin WireGuard.",
|
||||
"content": "",
|
||||
"button-admin": "Mở Quản trị Máy chủ",
|
||||
"button-user": "Mở Quản trị Người dùng"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "Quản trị Giao diện",
|
||||
"headline-peers": "Các Peer VPN Hiện tại",
|
||||
"headline-endpoints": "Các Điểm cuối Hiện tại",
|
||||
"no-interface": {
|
||||
"default-selection": "Không có giao diện nào",
|
||||
"headline": "Không tìm thấy giao diện...",
|
||||
"abstract": "Nhấp vào nút cộng trên để tạo một giao diện WireGuard mới."
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Không có peer nào",
|
||||
"abstract": "Hiện tại, không có peer nào khả dụng cho giao diện WireGuard đã chọn."
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "Tên",
|
||||
"user": "Người dùng",
|
||||
"ip": "Địa chỉ IP",
|
||||
"endpoint": "Điểm cuối",
|
||||
"status": "Trạng thái"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "Trạng thái giao diện cho",
|
||||
"mode": "chế độ",
|
||||
"key": "Khóa Công khai",
|
||||
"endpoint": "Điểm cuối Công khai",
|
||||
"port": "Cổng Nghe",
|
||||
"peers": "Các Peer Được Kích hoạt",
|
||||
"total-peers": "Tổng số Peer",
|
||||
"endpoints": "Các Điểm cuối Được Kích hoạt",
|
||||
"total-endpoints": "Tổng số Điểm cuối",
|
||||
"ip": "Địa chỉ IP",
|
||||
"default-allowed-ip": "IP được phép mặc định",
|
||||
"dns": "Máy chủ DNS",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "Khoảng thời gian giữ kết nối mặc định",
|
||||
"button-show-config": "Hiển thị cấu hình",
|
||||
"button-download-config": "Tải xuống cấu hình",
|
||||
"button-store-config": "Lưu cấu hình cho wg-quick",
|
||||
"button-edit": "Chỉnh sửa giao diện"
|
||||
},
|
||||
"button-add-interface": "Thêm Giao diện",
|
||||
"button-add-peer": "Thêm Peer",
|
||||
"button-add-peers": "Thêm Nhiều Peer",
|
||||
"button-show-peer": "Hiển thị Peer",
|
||||
"button-edit-peer": "Chỉnh sửa Peer",
|
||||
"peer-disabled": "Peer đã bị vô hiệu hóa, lý do:",
|
||||
"peer-expiring": "Peer sẽ hết hạn vào",
|
||||
"peer-connected": "Đã kết nối",
|
||||
"peer-not-connected": "Chưa kết nối",
|
||||
"peer-handshake": "Lần bắt tay cuối cùng:"
|
||||
},
|
||||
"users": {
|
||||
"headline": "Quản trị Người dùng",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Tên",
|
||||
"lastname": "Họ",
|
||||
"source": "Nguồn",
|
||||
"peers": "Peers",
|
||||
"admin": "Quản trị viên"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "Không có người dùng nào",
|
||||
"abstract": "Hiện tại, không có người dùng nào được đăng ký với WireGuard Portal."
|
||||
},
|
||||
"button-add-user": "Thêm Người dùng",
|
||||
"button-show-user": "Hiển thị Người dùng",
|
||||
"button-edit-user": "Chỉnh sửa Người dùng",
|
||||
"user-disabled": "Người dùng đã bị vô hiệu hóa, lý do:",
|
||||
"user-locked": "Tài khoản bị khóa, lý do:",
|
||||
"admin": "Người dùng có quyền quản trị",
|
||||
"no-admin": "Người dùng không có quyền quản trị"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "Các Peer VPN của Tôi",
|
||||
"table-heading": {
|
||||
"name": "Tên",
|
||||
"ip": "Địa chỉ IP",
|
||||
"stats": "Trạng thái",
|
||||
"interface": "Giao diện Máy chủ"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "Không có peer nào",
|
||||
"abstract": "Hiện tại, không có peer nào liên kết với hồ sơ người dùng của bạn."
|
||||
},
|
||||
"peer-connected": "Đã kết nối",
|
||||
"button-add-peer": "Thêm Peer",
|
||||
"button-show-peer": "Hiển thị Peer",
|
||||
"button-edit-peer": "Chỉnh sửa Peer"
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "Tài khoản Người dùng:",
|
||||
"tab-user": "Thông tin",
|
||||
"tab-peers": "Peers",
|
||||
"headline-info": "Thông tin Người dùng:",
|
||||
"headline-notes": "Ghi chú:",
|
||||
"email": "E-Mail",
|
||||
"firstname": "Tên",
|
||||
"lastname": "Họ",
|
||||
"phone": "Số điện thoại",
|
||||
"department": "Phòng ban",
|
||||
"disabled": "Tài khoản bị vô hiệu hóa",
|
||||
"locked": "Tài khoản bị khóa",
|
||||
"no-peers": "Người dùng không có peers liên kết.",
|
||||
"peers": {
|
||||
"name": "Tên",
|
||||
"interface": "Giao diện",
|
||||
"ip": "Địa chỉ IP"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "Chỉnh sửa người dùng:",
|
||||
"headline-new": "Người dùng mới",
|
||||
"header-general": "Chung",
|
||||
"header-personal": "Thông tin Người dùng",
|
||||
"header-notes": "Ghi chú",
|
||||
"header-state": "Trạng thái",
|
||||
"identifier": {
|
||||
"label": "Mã định danh",
|
||||
"placeholder": "Mã định danh người dùng duy nhất"
|
||||
},
|
||||
"source": {
|
||||
"label": "Nguồn",
|
||||
"placeholder": "Nguồn gốc của người dùng"
|
||||
},
|
||||
"password": {
|
||||
"label": "Mật khẩu",
|
||||
"placeholder": "Mật khẩu siêu bí mật",
|
||||
"description": "Để trống trường này để giữ nguyên mật khẩu hiện tại."
|
||||
},
|
||||
"email": {
|
||||
"label": "Email",
|
||||
"placeholder": "Địa chỉ email"
|
||||
},
|
||||
"phone": {
|
||||
"label": "Điện thoại",
|
||||
"placeholder": "Số điện thoại"
|
||||
},
|
||||
"department": {
|
||||
"label": "Phòng ban",
|
||||
"placeholder": "Phòng ban"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "Tên",
|
||||
"placeholder": "Tên"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "Họ",
|
||||
"placeholder": "Họ"
|
||||
},
|
||||
"notes": {
|
||||
"label": "Ghi chú",
|
||||
"placeholder": "Chú thích thêm"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Vô hiệu hóa (không thể kết nối WireGuard và không thể đăng nhập)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "Khóa (không thể đăng nhập, kết nối WireGuard vẫn hoạt động)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "Là Quản trị viên"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "Cấu hình cho Giao diện:"
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "Chỉnh sửa Giao diện:",
|
||||
"headline-new": "Giao diện Mới",
|
||||
"tab-interface": "Giao diện",
|
||||
"tab-peerdef": "Cài đặt Mặc định của Peer",
|
||||
"header-general": "Chung",
|
||||
"header-network": "Mạng",
|
||||
"header-crypto": "Mã hóa",
|
||||
"header-hooks": "Kẹp Giao diện",
|
||||
"header-peer-hooks": "Kẹp Peer",
|
||||
"header-state": "Trạng thái",
|
||||
"identifier": {
|
||||
"label": "Mã định danh",
|
||||
"placeholder": "Mã định danh giao diện duy nhất"
|
||||
},
|
||||
"mode": {
|
||||
"label": "Chế độ Giao diện",
|
||||
"server": "Chế độ Máy chủ",
|
||||
"client": "Chế độ Khách hàng",
|
||||
"any": "Chế độ Không xác định"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "Tên Hiển thị",
|
||||
"placeholder": "Tên mô tả cho giao diện"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Khóa Riêng",
|
||||
"placeholder": "Khóa riêng"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Khóa Công khai",
|
||||
"placeholder": "Khóa công khai"
|
||||
},
|
||||
"ip": {
|
||||
"label": "Địa chỉ IP",
|
||||
"placeholder": "Địa chỉ IP (định dạng CIDR)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "Cổng Nghe",
|
||||
"placeholder": "Cổng nghe"
|
||||
},
|
||||
"dns": {
|
||||
"label": "Máy chủ DNS",
|
||||
"placeholder": "Các máy chủ DNS sẽ được sử dụng"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "Tên miền Tìm kiếm DNS",
|
||||
"placeholder": "Tiền tố tìm kiếm DNS"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "MTU của giao diện (0 = giữ mặc định)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "Đánh dấu Tường lửa",
|
||||
"placeholder": "Đánh dấu tường lửa áp dụng cho lưu lượng đi. (0 = tự động)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "Bảng Định tuyến",
|
||||
"placeholder": "ID bảng định tuyến",
|
||||
"description": "Các trường hợp đặc biệt: off = không quản lý các tuyến đường, 0 = tự động"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Trước khi Bật",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Sau khi Bật",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Trước khi Tắt",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Sau khi Tắt",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Giao diện Bị vô hiệu hóa"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "Tự động lưu cấu hình wg-quick"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "Địa chỉ Endpoint",
|
||||
"placeholder": "Địa chỉ Endpoint",
|
||||
"description": "Địa chỉ endpoint mà các peer sẽ kết nối tới."
|
||||
},
|
||||
"networks": {
|
||||
"label": "Mạng IP",
|
||||
"placeholder": "Địa chỉ Mạng",
|
||||
"description": "Các peer sẽ nhận địa chỉ IP từ những mạng con này."
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Địa chỉ IP Được phép",
|
||||
"placeholder": "Địa chỉ IP Được phép mặc định"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "MTU của client (0 = giữ mặc định)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Khoảng thời gian Giữ kết nối",
|
||||
"placeholder": "Giữ kết nối liên tục (0 = mặc định)"
|
||||
}
|
||||
},
|
||||
|
||||
"button-apply-defaults": "Áp dụng Cài đặt Mặc định của Peer"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "Peer:",
|
||||
"headline-endpoint": "Endpoint:",
|
||||
"section-info": "Thông tin Peer",
|
||||
"section-status": "Trạng thái Hiện tại",
|
||||
"section-config": "Cấu hình",
|
||||
"identifier": "Mã định danh",
|
||||
"ip": "Địa chỉ IP",
|
||||
"user": "Người dùng Liên kết",
|
||||
"notes": "Ghi chú",
|
||||
"expiry-status": "Hết hạn vào",
|
||||
"disabled-status": "Bị Vô hiệu hóa vào",
|
||||
"traffic": "Lưu lượng",
|
||||
"connection-status": "Thông tin Kết nối",
|
||||
"upload": "Số Byte Tải lên (từ Máy chủ đến Peer)",
|
||||
"download": "Số Byte Tải xuống (từ Peer đến Máy chủ)",
|
||||
"pingable": "Có thể Ping",
|
||||
"handshake": "Lần bắt tay cuối cùng",
|
||||
"connected-since": "Kết nối từ",
|
||||
"endpoint": "Endpoint",
|
||||
"button-download": "Tải cấu hình",
|
||||
"button-email": "Gửi cấu hình qua Email"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "Chỉnh sửa Peer:",
|
||||
"headline-edit-endpoint": "Chỉnh sửa Endpoint:",
|
||||
"headline-new-peer": "Tạo Peer mới",
|
||||
"headline-new-endpoint": "Tạo Endpoint mới",
|
||||
"header-general": "Chung",
|
||||
"header-network": "Mạng",
|
||||
"header-crypto": "Mã hóa",
|
||||
"header-hooks": "Kẹp (Thực thi trên Peer)",
|
||||
"header-state": "Trạng thái",
|
||||
"display-name": {
|
||||
"label": "Tên Hiển thị",
|
||||
"placeholder": "Tên mô tả cho peer"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "Người dùng Liên kết",
|
||||
"placeholder": "Tài khoản người dùng sở hữu peer này"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "Khóa Riêng",
|
||||
"placeholder": "Khóa riêng"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "Khóa Công khai",
|
||||
"placeholder": "Khóa công khai"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "Khóa Preshared",
|
||||
"placeholder": "Khóa chia sẻ trước (tuỳ chọn)"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "Khóa Công khai của Endpoint",
|
||||
"placeholder": "Khóa công khai của endpoint từ xa"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "Địa chỉ Endpoint",
|
||||
"placeholder": "Địa chỉ của endpoint từ xa"
|
||||
},
|
||||
"ip": {
|
||||
"label": "Địa chỉ IP",
|
||||
"placeholder": "Địa chỉ IP (định dạng CIDR)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "Địa chỉ IP Được phép",
|
||||
"placeholder": "Địa chỉ IP Được phép (định dạng CIDR)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "Địa chỉ IP Được phép Thêm",
|
||||
"placeholder": "Địa chỉ IP Thêm (Phía Máy chủ)",
|
||||
"description": "Những địa chỉ IP này sẽ được thêm vào giao diện WireGuard từ xa dưới dạng địa chỉ IP được phép."
|
||||
},
|
||||
"dns": {
|
||||
"label": "Máy chủ DNS",
|
||||
"placeholder": "Các máy chủ DNS sẽ được sử dụng"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "Tên miền Tìm kiếm DNS",
|
||||
"placeholder": "Tiền tố tìm kiếm DNS"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "Khoảng thời gian Giữ kết nối",
|
||||
"placeholder": "Giữ kết nối liên tục (0 = mặc định)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "MTU của client (0 = giữ mặc định)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "Trước khi Bật",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "Sau khi Bật",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "Trước khi Tắt",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "Sau khi Tắt",
|
||||
"placeholder": "Một hoặc nhiều lệnh bash ngăn cách bằng ;"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "Peer Bị Vô hiệu hóa"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "Bỏ qua cài đặt toàn cầu"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "Ngày hết hạn"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "Tạo nhiều peer",
|
||||
"headline-endpoint": "Tạo nhiều endpoint",
|
||||
"identifiers": {
|
||||
"label": "Mã định danh Người dùng",
|
||||
"placeholder": "Mã định danh Người dùng",
|
||||
"description": "Một mã định danh người dùng (tên người dùng) cho mà một peer sẽ được tạo ra."
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "Peer:",
|
||||
"headline-endpoint": "Endpoint:",
|
||||
"label": "Tiền tố Tên Hiển thị",
|
||||
"placeholder": "Tiền tố",
|
||||
"description": "Một tiền tố được thêm vào tên hiển thị của các peer."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
492
frontend/src/lang/translations/zh.json
Normal file
492
frontend/src/lang/translations/zh.json
Normal file
@@ -0,0 +1,492 @@
|
||||
{
|
||||
"languages": {
|
||||
"zh": "中文"
|
||||
},
|
||||
"general": {
|
||||
"pagination": {
|
||||
"size": "每页显示数量",
|
||||
"all": "全部 (较慢)"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "搜索...",
|
||||
"button": "搜索"
|
||||
},
|
||||
"select-all": "全选",
|
||||
"yes": "是",
|
||||
"no": "否",
|
||||
"cancel": "取消",
|
||||
"close": "关闭",
|
||||
"save": "保存",
|
||||
"delete": "删除"
|
||||
},
|
||||
"login": {
|
||||
"headline": "请登录",
|
||||
"username": {
|
||||
"label": "用户名",
|
||||
"placeholder": "请输入用户名"
|
||||
},
|
||||
"password": {
|
||||
"label": "密码",
|
||||
"placeholder": "请输入密码"
|
||||
},
|
||||
"button": "登录"
|
||||
},
|
||||
"menu": {
|
||||
"home": "首页",
|
||||
"interfaces": "接口",
|
||||
"users": "用户",
|
||||
"lang": "切换语言",
|
||||
"profile": "个人资料",
|
||||
"login": "登录",
|
||||
"logout": "注销"
|
||||
},
|
||||
"home": {
|
||||
"headline": "WireGuard® VPN Portal",
|
||||
"info-headline": "更多信息",
|
||||
"abstract": "WireGuard® 是一种极其简单但又快速现代的 VPN,采用先进的加密技术。它旨在比 IPsec 更快、更简单、更轻量、更实用,同时避免了大量的麻烦。它的性能显著优于 OpenVPN。",
|
||||
"installation": {
|
||||
"box-header": "WireGuard 安装",
|
||||
"headline": "安装",
|
||||
"content": "客户端软件的安装说明可在官方 WireGuard 网站上找到。",
|
||||
"button": "打开说明"
|
||||
},
|
||||
"about-wg": {
|
||||
"box-header": "关于 WireGuard",
|
||||
"headline": "关于",
|
||||
"content": "WireGuard® 是一种极其简单但又快速现代的 VPN,采用先进的加密技术。",
|
||||
"button": "更多"
|
||||
},
|
||||
"about-portal": {
|
||||
"box-header": "关于 WireGuard Portal",
|
||||
"headline": "WireGuard Portal",
|
||||
"content": "WireGuard Portal 是一个简单的基于网页的 WireGuard 配置平台。",
|
||||
"button": "更多"
|
||||
},
|
||||
"profiles": {
|
||||
"headline": "VPN 配置文件",
|
||||
"abstract": "您可以通过您的用户个人资料访问并下载个人 VPN 配置。",
|
||||
"content": "要查找您所有的配置文件,请点击下面的按钮。",
|
||||
"button": "打开我的个人资料"
|
||||
},
|
||||
"admin": {
|
||||
"headline": "后台管理",
|
||||
"abstract": "在后台管理,您可以管理 WireGuard 节点和服务器接口,以及允许登录 WireGuard Portal 的用户。",
|
||||
"content": "",
|
||||
"button-admin": "打开服务器管理",
|
||||
"button-user": "打开用户管理"
|
||||
}
|
||||
},
|
||||
"interfaces": {
|
||||
"headline": "接口管理",
|
||||
"headline-peers": "当前 VPN 节点",
|
||||
"headline-endpoints": "当前节点",
|
||||
"no-interface": {
|
||||
"default-selection": "没有可用接口",
|
||||
"headline": "未找到接口...",
|
||||
"abstract": "点击上面的加号按钮以创建新的 WireGuard 接口。"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "没有可用节点",
|
||||
"abstract": "当前没有可用的节点与所选的 WireGuard 接口关联。"
|
||||
},
|
||||
"table-heading": {
|
||||
"name": "名称",
|
||||
"user": "用户",
|
||||
"ip": "IP 地址",
|
||||
"endpoint": "节点",
|
||||
"status": "状态"
|
||||
},
|
||||
"interface": {
|
||||
"headline": "接口状态",
|
||||
"mode": "模式",
|
||||
"key": "公钥",
|
||||
"endpoint": "公开节点",
|
||||
"port": "监听端口",
|
||||
"peers": "启用节点",
|
||||
"total-peers": "节点总数",
|
||||
"endpoints": "启用节点",
|
||||
"total-endpoints": "节点总数",
|
||||
"ip": "IP 地址",
|
||||
"default-allowed-ip": "默认允许的 IP",
|
||||
"dns": "DNS 服务器",
|
||||
"mtu": "MTU",
|
||||
"default-keep-alive": "默认心跳包间隔",
|
||||
"button-show-config": "显示配置",
|
||||
"button-download-config": "下载配置",
|
||||
"button-store-config": "为 wg-quick 保存配置",
|
||||
"button-edit": "编辑接口"
|
||||
},
|
||||
"button-add-interface": "添加接口",
|
||||
"button-add-peer": "添加节点",
|
||||
"button-add-peers": "添加多个节点",
|
||||
"button-show-peer": "显示节点",
|
||||
"button-edit-peer": "编辑节点",
|
||||
"peer-disabled": "节点已禁用,原因: ",
|
||||
"peer-expiring": "节点将在以下时间过期: ",
|
||||
"peer-connected": "已连接",
|
||||
"peer-not-connected": "未连接",
|
||||
"peer-handshake": "最后一次握手: "
|
||||
},
|
||||
"users": {
|
||||
"headline": "用户管理",
|
||||
"table-heading": {
|
||||
"id": "ID",
|
||||
"email": "电子邮件",
|
||||
"firstname": "名",
|
||||
"lastname": "姓",
|
||||
"source": "来源",
|
||||
"peers": "节点",
|
||||
"admin": "管理员"
|
||||
},
|
||||
"no-user": {
|
||||
"headline": "没有可用用户",
|
||||
"abstract": "当前没有用户注册 WireGuard 门户。"
|
||||
},
|
||||
"button-add-user": "添加用户",
|
||||
"button-show-user": "显示用户",
|
||||
"button-edit-user": "编辑用户",
|
||||
"user-disabled": "用户已禁用, 原因: ",
|
||||
"user-locked": "账户已锁定, 原因: ",
|
||||
"admin": "用户具有管理员权限",
|
||||
"no-admin": "用户没有管理员权限"
|
||||
},
|
||||
"profile": {
|
||||
"headline": "我的 VPN 节点列表",
|
||||
"table-heading": {
|
||||
"name": "名称",
|
||||
"ip": "IP 地址",
|
||||
"stats": "状态",
|
||||
"interface": "服务器接口"
|
||||
},
|
||||
"no-peer": {
|
||||
"headline": "没有可用节点",
|
||||
"abstract": "当前没有与您的用户个人资料关联的节点。"
|
||||
},
|
||||
"peer-connected": "已连接",
|
||||
"button-add-peer": "添加节点",
|
||||
"button-show-peer": "显示节点",
|
||||
"button-edit-peer": "编辑节点"
|
||||
},
|
||||
"modals": {
|
||||
"user-view": {
|
||||
"headline": "用户账户: ",
|
||||
"tab-user": "信息",
|
||||
"tab-peers": "节点",
|
||||
"headline-info": "用户信息: ",
|
||||
"headline-notes": "备注: ",
|
||||
"email": "电子邮件",
|
||||
"firstname": "名",
|
||||
"lastname": "姓",
|
||||
"phone": "电话号码",
|
||||
"department": "部门",
|
||||
"disabled": "账户已禁用",
|
||||
"locked": "账户已锁定",
|
||||
"no-peers": "用户没有关联的节点。",
|
||||
"peers": {
|
||||
"name": "名称",
|
||||
"interface": "接口",
|
||||
"ip": "IP 地址"
|
||||
}
|
||||
},
|
||||
"user-edit": {
|
||||
"headline-edit": "编辑用户: ",
|
||||
"headline-new": "新用户",
|
||||
"header-general": "常规",
|
||||
"header-personal": "用户信息",
|
||||
"header-notes": "备注",
|
||||
"header-state": "状态",
|
||||
"identifier": {
|
||||
"label": "标识符",
|
||||
"placeholder": "唯一用户标识符"
|
||||
},
|
||||
"source": {
|
||||
"label": "来源",
|
||||
"placeholder": "用户来源"
|
||||
},
|
||||
"password": {
|
||||
"label": "密码",
|
||||
"placeholder": "一个复杂的密码",
|
||||
"description": "留空保持当前密码不变。"
|
||||
},
|
||||
"email": {
|
||||
"label": "电子邮件",
|
||||
"placeholder": "电子邮件地址"
|
||||
},
|
||||
"phone": {
|
||||
"label": "电话",
|
||||
"placeholder": "电话号码"
|
||||
},
|
||||
"department": {
|
||||
"label": "部门",
|
||||
"placeholder": "部门"
|
||||
},
|
||||
"firstname": {
|
||||
"label": "名",
|
||||
"placeholder": "名"
|
||||
},
|
||||
"lastname": {
|
||||
"label": "姓",
|
||||
"placeholder": "姓"
|
||||
},
|
||||
"notes": {
|
||||
"label": "备注",
|
||||
"placeholder": ""
|
||||
},
|
||||
"disabled": {
|
||||
"label": "禁用 (无法连接 WireGuard 和登录)"
|
||||
},
|
||||
"locked": {
|
||||
"label": "锁定 (无法登录,但 WireGuard 仍然可以连接)"
|
||||
},
|
||||
"admin": {
|
||||
"label": "管理员"
|
||||
}
|
||||
},
|
||||
"interface-view": {
|
||||
"headline": "接口配置: "
|
||||
},
|
||||
"interface-edit": {
|
||||
"headline-edit": "编辑接口: ",
|
||||
"headline-new": "新接口",
|
||||
"tab-interface": "接口",
|
||||
"tab-peerdef": "节点默认值",
|
||||
"header-general": "常规",
|
||||
"header-network": "网络",
|
||||
"header-crypto": "加密",
|
||||
"header-hooks": "接口 Hooks",
|
||||
"header-peer-hooks": "Hooks",
|
||||
"header-state": "状态",
|
||||
"identifier": {
|
||||
"label": "标识符",
|
||||
"placeholder": "唯一接口标识符"
|
||||
},
|
||||
"mode": {
|
||||
"label": "接口模式",
|
||||
"server": "服务器模式",
|
||||
"client": "客户端模式",
|
||||
"any": "未知模式"
|
||||
},
|
||||
"display-name": {
|
||||
"label": "显示名称",
|
||||
"placeholder": "接口的描述性名称"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "私钥",
|
||||
"placeholder": "私钥"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "公钥",
|
||||
"placeholder": "公钥"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP 地址",
|
||||
"placeholder": "IP 地址 (CIDR 格式)"
|
||||
},
|
||||
"listen-port": {
|
||||
"label": "监听端口",
|
||||
"placeholder": "监听端口"
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS 服务器",
|
||||
"placeholder": "应使用的 DNS 服务器"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS 搜索域",
|
||||
"placeholder": "DNS 搜索前缀"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "接口 MTU (0 = 保持默认)"
|
||||
},
|
||||
"firewall-mark": {
|
||||
"label": "防火墙掩码",
|
||||
"placeholder": "应用于出站流量的防火墙掩码 (0 = 自动)"
|
||||
},
|
||||
"routing-table": {
|
||||
"label": "路由表",
|
||||
"placeholder": "路由表 ID",
|
||||
"description": "特殊情况: off = 不管理路由, 0 = 自动"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "启动前脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "启动后脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "停止前脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "停止后脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "接口已禁用"
|
||||
},
|
||||
"save-config": {
|
||||
"label": "自动保存 wg-quick 配置"
|
||||
},
|
||||
"defaults": {
|
||||
"endpoint": {
|
||||
"label": "服务器地址",
|
||||
"placeholder": "服务器地址",
|
||||
"description": "节点将连接到服务器的地址。"
|
||||
},
|
||||
"networks": {
|
||||
"label": "IP 地址",
|
||||
"placeholder": "IP 地址",
|
||||
"description": "节点将从这些子网获取 IP 地址。"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "允许的 IP 地址",
|
||||
"placeholder": "默认允许的 IP 地址"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "客户端 MTU (0 = 保持默认)"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "心跳包间隔",
|
||||
"placeholder": "持久保持连接 (0 = 默认)"
|
||||
}
|
||||
},
|
||||
"button-apply-defaults": "应用节点默认值"
|
||||
},
|
||||
"peer-view": {
|
||||
"headline-peer": "节点: ",
|
||||
"headline-endpoint": "节点: ",
|
||||
"section-info": "节点信息",
|
||||
"section-status": "当前状态",
|
||||
"section-config": "配置",
|
||||
"identifier": "标识符",
|
||||
"ip": "IP 地址",
|
||||
"user": "关联用户",
|
||||
"notes": "备注",
|
||||
"expiry-status": "过期时间",
|
||||
"disabled-status": "禁用时间",
|
||||
"traffic": "流量",
|
||||
"connection-status": "连接状态",
|
||||
"upload": "上传字节 (服务器到节点)",
|
||||
"download": "下载字节 (节点到服务器)",
|
||||
"pingable": "连通状态",
|
||||
"handshake": "最后握手",
|
||||
"connected-since": "首次成功连接",
|
||||
"endpoint": "节点地址",
|
||||
"button-download": "下载配置",
|
||||
"button-email": "通过电子邮件发送配置"
|
||||
},
|
||||
"peer-edit": {
|
||||
"headline-edit-peer": "编辑节点: ",
|
||||
"headline-edit-endpoint": "编辑节点: ",
|
||||
"headline-new-peer": "创建节点",
|
||||
"headline-new-endpoint": "创建节点",
|
||||
"header-general": "常规",
|
||||
"header-network": "网络",
|
||||
"header-crypto": "加密",
|
||||
"header-hooks": "Hooks (在节点执行)",
|
||||
"header-state": "状态",
|
||||
"display-name": {
|
||||
"label": "显示名称",
|
||||
"placeholder": "节点的描述性名称"
|
||||
},
|
||||
"linked-user": {
|
||||
"label": "关联用户",
|
||||
"placeholder": "拥有此节点的用户账户"
|
||||
},
|
||||
"private-key": {
|
||||
"label": "私钥",
|
||||
"placeholder": "私钥"
|
||||
},
|
||||
"public-key": {
|
||||
"label": "公钥",
|
||||
"placeholder": "公钥"
|
||||
},
|
||||
"preshared-key": {
|
||||
"label": "预共享密钥",
|
||||
"placeholder": "可选的预共享密钥"
|
||||
},
|
||||
"endpoint-public-key": {
|
||||
"label": "节点公钥",
|
||||
"placeholder": "远程节点的公钥"
|
||||
},
|
||||
"endpoint": {
|
||||
"label": "节点地址",
|
||||
"placeholder": "远程节点的地址"
|
||||
},
|
||||
"ip": {
|
||||
"label": "IP 地址",
|
||||
"placeholder": "IP 地址(CIDR 格式)"
|
||||
},
|
||||
"allowed-ip": {
|
||||
"label": "允许的 IP 地址",
|
||||
"placeholder": "允许的 IP 地址(CIDR 格式)"
|
||||
},
|
||||
"extra-allowed-ip": {
|
||||
"label": "额外允许的 IP 地址",
|
||||
"placeholder": "额外允许的 IP 地址(服务器端)",
|
||||
"description": "这些 IP 将作为允许的 IP 添加到远程 WireGuard 接口。"
|
||||
},
|
||||
"dns": {
|
||||
"label": "DNS 服务器",
|
||||
"placeholder": "要使用的 DNS 服务器"
|
||||
},
|
||||
"dns-search": {
|
||||
"label": "DNS 搜索域",
|
||||
"placeholder": "DNS 搜索前缀"
|
||||
},
|
||||
"keep-alive": {
|
||||
"label": "心跳包间隔",
|
||||
"placeholder": "持久保持连接 (0 = 默认)"
|
||||
},
|
||||
"mtu": {
|
||||
"label": "MTU",
|
||||
"placeholder": "客户端 MTU (0 = 保持默认)"
|
||||
},
|
||||
"pre-up": {
|
||||
"label": "启动前脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"post-up": {
|
||||
"label": "启动后脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"pre-down": {
|
||||
"label": "停止前脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"post-down": {
|
||||
"label": "停止后脚本",
|
||||
"placeholder": "一个或多个用分号分隔的 bash 命令"
|
||||
},
|
||||
"disabled": {
|
||||
"label": "节点已禁用"
|
||||
},
|
||||
"ignore-global": {
|
||||
"label": "忽略全局设置"
|
||||
},
|
||||
"expires-at": {
|
||||
"label": "过期日期"
|
||||
}
|
||||
},
|
||||
"peer-multi-create": {
|
||||
"headline-peer": "创建多个节点",
|
||||
"headline-endpoint": "创建多个节点",
|
||||
"identifiers": {
|
||||
"label": "用户标识符",
|
||||
"placeholder": "用户标识符",
|
||||
"description": "要为其创建节点 的用户标识符(用户名)。"
|
||||
},
|
||||
"prefix": {
|
||||
"headline-peer": "节点: ",
|
||||
"headline-endpoint": "节点: ",
|
||||
"label": "显示名称前缀",
|
||||
"placeholder": "前缀",
|
||||
"description": "添加到节点 显示名称的前缀。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -9,13 +9,14 @@ import i18n from "./lang";
|
||||
import Notifications from '@kyvg/vue3-notification'
|
||||
|
||||
// Bootstrap (and theme)
|
||||
//import "bootstrap/dist/css/bootstrap.min.css"
|
||||
import "bootswatch/dist/lux/bootstrap.min.css";
|
||||
import "@/assets/custom.scss";
|
||||
import "bootstrap";
|
||||
import "./assets/base.css";
|
||||
|
||||
// Fontawesome
|
||||
// Fonts
|
||||
import "@fortawesome/fontawesome-free/js/all.js"
|
||||
import "@fontsource/nunito-sans/400.css";
|
||||
import "@fontsource/nunito-sans/600.css";
|
||||
|
||||
// Flags
|
||||
import "flag-icons/css/flag-icons.min.css"
|
||||
|
@@ -4,6 +4,7 @@ import LoginView from '../views/LoginView.vue'
|
||||
import InterfaceView from '../views/InterfaceView.vue'
|
||||
|
||||
import {authStore} from '@/stores/auth'
|
||||
import {securityStore} from '@/stores/security'
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
const router = createRouter({
|
||||
@@ -47,6 +48,30 @@ const router = createRouter({
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/ProfileView.vue')
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/SettingsView.vue')
|
||||
},
|
||||
{
|
||||
path: '/audit',
|
||||
name: 'audit',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/AuditView.vue')
|
||||
},
|
||||
{
|
||||
path: '/key-generator',
|
||||
name: 'key-generator',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/KeyGeneraterView.vue')
|
||||
}
|
||||
],
|
||||
linkActiveClass: "active",
|
||||
@@ -97,13 +122,22 @@ router.beforeEach(async (to) => {
|
||||
}
|
||||
|
||||
// redirect to login page if not logged in and trying to access a restricted page
|
||||
const publicPages = ['/', '/login']
|
||||
const publicPages = ['/', '/login', '/key-generator']
|
||||
const authRequired = !publicPages.includes(to.path)
|
||||
|
||||
if (authRequired && !auth.IsAuthenticated) {
|
||||
auth.SetReturnUrl(to.fullPath) // store original destination before starting the auth process
|
||||
auth.SetReturnUrl(to.fullPath) // store the original destination before starting the auth process
|
||||
return '/login'
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(async (to, from) => {
|
||||
const sec = securityStore()
|
||||
const csrfPages = ['/', '/login']
|
||||
|
||||
if (csrfPages.includes(to.path)) {
|
||||
await sec.LoadSecurityProperties() // make sure we have a valid csrf token
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
87
frontend/src/stores/audit.js
Normal file
87
frontend/src/stores/audit.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import {apiWrapper} from "@/helpers/fetch-wrapper";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
import { base64_url_encode } from '@/helpers/encoding';
|
||||
|
||||
const baseUrl = `/audit`
|
||||
|
||||
export const auditStore = defineStore('audit', {
|
||||
state: () => ({
|
||||
entries: [],
|
||||
filter: "",
|
||||
pageSize: 10,
|
||||
pageOffset: 0,
|
||||
pages: [],
|
||||
fetching: false,
|
||||
}),
|
||||
getters: {
|
||||
Count: (state) => state.entries.length,
|
||||
FilteredCount: (state) => state.Filtered.length,
|
||||
All: (state) => state.entries,
|
||||
Filtered: (state) => {
|
||||
if (!state.filter) {
|
||||
return state.entries
|
||||
}
|
||||
return state.entries.filter((e) => {
|
||||
return e.Timestamp.includes(state.filter) ||
|
||||
e.Message.includes(state.filter) ||
|
||||
e.Severity.includes(state.filter) ||
|
||||
e.Origin.includes(state.filter)
|
||||
})
|
||||
},
|
||||
FilteredAndPaged: (state) => {
|
||||
return state.Filtered.slice(state.pageOffset, state.pageOffset + state.pageSize)
|
||||
},
|
||||
isFetching: (state) => state.fetching,
|
||||
hasNextPage: (state) => state.pageOffset < (state.FilteredCount - state.pageSize),
|
||||
hasPrevPage: (state) => state.pageOffset > 0,
|
||||
currentPage: (state) => (state.pageOffset / state.pageSize)+1,
|
||||
},
|
||||
actions: {
|
||||
afterPageSizeChange() {
|
||||
// reset pageOffset to avoid problems with new page sizes
|
||||
this.pageOffset = 0
|
||||
this.calculatePages()
|
||||
},
|
||||
calculatePages() {
|
||||
let pageCounter = 1;
|
||||
this.pages = []
|
||||
for (let i = 0; i < this.FilteredCount; i+=this.pageSize) {
|
||||
this.pages.push(pageCounter++)
|
||||
}
|
||||
},
|
||||
gotoPage(page) {
|
||||
this.pageOffset = (page-1) * this.pageSize
|
||||
|
||||
this.calculatePages()
|
||||
},
|
||||
nextPage() {
|
||||
this.pageOffset += this.pageSize
|
||||
|
||||
this.calculatePages()
|
||||
},
|
||||
previousPage() {
|
||||
this.pageOffset -= this.pageSize
|
||||
|
||||
this.calculatePages()
|
||||
},
|
||||
setEntries(entries) {
|
||||
this.entries = entries
|
||||
this.calculatePages()
|
||||
this.fetching = false
|
||||
},
|
||||
async LoadEntries() {
|
||||
this.fetching = true
|
||||
return apiWrapper.get(`${baseUrl}/entries`)
|
||||
.then(this.setEntries)
|
||||
.catch(error => {
|
||||
this.setEntries([])
|
||||
console.log("Failed to load audit entries: ", error)
|
||||
notify({
|
||||
title: "Backend Connection Failure",
|
||||
text: "Failed to load audit entries!",
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
@@ -4,8 +4,7 @@ import { notify } from "@kyvg/vue3-notification";
|
||||
import { apiWrapper } from '@/helpers/fetch-wrapper'
|
||||
import router from '../router'
|
||||
|
||||
export const authStore = defineStore({
|
||||
id: 'auth',
|
||||
export const authStore = defineStore('auth',{
|
||||
state: () => ({
|
||||
// initialize state from local storage to enable user to stay logged in
|
||||
user: JSON.parse(localStorage.getItem('user')),
|
||||
@@ -122,4 +121,4 @@ export const authStore = defineStore({
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -7,8 +7,7 @@ import { base64_url_encode } from '@/helpers/encoding';
|
||||
|
||||
const baseUrl = `/interface`
|
||||
|
||||
export const interfaceStore = defineStore({
|
||||
id: 'interfaces',
|
||||
export const interfaceStore = defineStore('interfaces', {
|
||||
state: () => ({
|
||||
interfaces: [],
|
||||
prepared: freshInterface(),
|
||||
|
@@ -4,11 +4,11 @@ import {notify} from "@kyvg/vue3-notification";
|
||||
import {interfaceStore} from "./interfaces";
|
||||
import {freshPeer, freshStats} from '@/helpers/models';
|
||||
import { base64_url_encode } from '@/helpers/encoding';
|
||||
import { ipToBigInt } from '@/helpers/utils';
|
||||
|
||||
const baseUrl = `/peer`
|
||||
|
||||
export const peerStore = defineStore({
|
||||
id: 'peers',
|
||||
export const peerStore = defineStore('peers', {
|
||||
state: () => ({
|
||||
peers: [],
|
||||
stats: {},
|
||||
@@ -21,6 +21,8 @@ export const peerStore = defineStore({
|
||||
pageOffset: 0,
|
||||
pages: [],
|
||||
fetching: false,
|
||||
sortKey: 'IsConnected', // Default sort key
|
||||
sortOrder: -1, // 1 for ascending, -1 for descending
|
||||
}),
|
||||
getters: {
|
||||
Find: (state) => {
|
||||
@@ -39,8 +41,30 @@ export const peerStore = defineStore({
|
||||
return p.DisplayName.includes(state.filter) || p.Identifier.includes(state.filter)
|
||||
})
|
||||
},
|
||||
Sorted: (state) => {
|
||||
return state.Filtered.slice().sort((a, b) => {
|
||||
let aValue = a[state.sortKey];
|
||||
let bValue = b[state.sortKey];
|
||||
if (state.sortKey === 'Addresses') {
|
||||
aValue = aValue.length > 0 ? ipToBigInt(aValue[0]) : 0;
|
||||
bValue = bValue.length > 0 ? ipToBigInt(bValue[0]) : 0;
|
||||
}
|
||||
if (state.sortKey === 'IsConnected') {
|
||||
aValue = state.statsEnabled && state.stats[a.Identifier]?.IsConnected ? 1 : 0;
|
||||
bValue = state.statsEnabled && state.stats[b.Identifier]?.IsConnected ? 1 : 0;
|
||||
}
|
||||
if (state.sortKey === 'Traffic') {
|
||||
aValue = state.statsEnabled ? (state.stats[a.Identifier].BytesReceived + state.stats[a.Identifier].BytesTransmitted) : 0;
|
||||
bValue = state.statsEnabled ? (state.stats[b.Identifier].BytesReceived + state.stats[b.Identifier].BytesTransmitted) : 0;
|
||||
}
|
||||
let result = 0;
|
||||
if (aValue > bValue) result = 1;
|
||||
if (aValue < bValue) result = -1;
|
||||
return state.sortOrder === 1 ? result : -result;
|
||||
});
|
||||
},
|
||||
FilteredAndPaged: (state) => {
|
||||
return state.Filtered.slice(state.pageOffset, state.pageOffset + state.pageSize)
|
||||
return state.Sorted.slice(state.pageOffset, state.pageOffset + state.pageSize);
|
||||
},
|
||||
ConfigQrUrl: (state) => {
|
||||
return (id) => state.peers.find((p) => p.Identifier === id) ? apiWrapper.url(`${baseUrl}/config-qr/${base64_url_encode(id)}`) : ''
|
||||
|
@@ -4,13 +4,15 @@ import {notify} from "@kyvg/vue3-notification";
|
||||
import {authStore} from "@/stores/auth";
|
||||
import { base64_url_encode } from '@/helpers/encoding';
|
||||
import {freshStats} from "@/helpers/models";
|
||||
import { ipToBigInt } from '@/helpers/utils';
|
||||
|
||||
const baseUrl = `/user`
|
||||
|
||||
export const profileStore = defineStore({
|
||||
id: 'profile',
|
||||
export const profileStore = defineStore('profile', {
|
||||
state: () => ({
|
||||
peers: [],
|
||||
interfaces: [],
|
||||
selectedInterfaceId: "",
|
||||
stats: {},
|
||||
statsEnabled: false,
|
||||
user: {},
|
||||
@@ -19,6 +21,8 @@ export const profileStore = defineStore({
|
||||
pageOffset: 0,
|
||||
pages: [],
|
||||
fetching: false,
|
||||
sortKey: 'IsConnected', // Default sort key
|
||||
sortOrder: -1, // 1 for ascending, -1 for descending
|
||||
}),
|
||||
getters: {
|
||||
FindPeers: (state) => {
|
||||
@@ -35,8 +39,30 @@ export const profileStore = defineStore({
|
||||
return p.DisplayName.includes(state.filter) || p.Identifier.includes(state.filter)
|
||||
})
|
||||
},
|
||||
Sorted: (state) => {
|
||||
return state.FilteredPeers.slice().sort((a, b) => {
|
||||
let aValue = a[state.sortKey];
|
||||
let bValue = b[state.sortKey];
|
||||
if (state.sortKey === 'Addresses') {
|
||||
aValue = aValue.length > 0 ? ipToBigInt(aValue[0]) : 0;
|
||||
bValue = bValue.length > 0 ? ipToBigInt(bValue[0]) : 0;
|
||||
}
|
||||
if (state.sortKey === 'IsConnected') {
|
||||
aValue = state.statsEnabled && state.stats[a.Identifier]?.IsConnected ? 1 : 0;
|
||||
bValue = state.statsEnabled && state.stats[b.Identifier]?.IsConnected ? 1 : 0;
|
||||
}
|
||||
if (state.sortKey === 'Traffic') {
|
||||
aValue = state.statsEnabled ? (state.stats[a.Identifier].BytesReceived + state.stats[a.Identifier].BytesTransmitted) : 0;
|
||||
bValue = state.statsEnabled ? (state.stats[b.Identifier].BytesReceived + state.stats[b.Identifier].BytesTransmitted) : 0;
|
||||
}
|
||||
let result = 0;
|
||||
if (aValue > bValue) result = 1;
|
||||
if (aValue < bValue) result = -1;
|
||||
return state.sortOrder === 1 ? result : -result;
|
||||
});
|
||||
},
|
||||
FilteredAndPagedPeers: (state) => {
|
||||
return state.FilteredPeers.slice(state.pageOffset, state.pageOffset + state.pageSize)
|
||||
return state.Sorted.slice(state.pageOffset, state.pageOffset + state.pageSize);
|
||||
},
|
||||
isFetching: (state) => state.fetching,
|
||||
hasNextPage: (state) => state.pageOffset < (state.FilteredPeerCount - state.pageSize),
|
||||
@@ -46,6 +72,7 @@ export const profileStore = defineStore({
|
||||
return (id) => state.statsEnabled && (id in state.stats) ? state.stats[id] : freshStats()
|
||||
},
|
||||
hasStatistics: (state) => state.statsEnabled,
|
||||
CountInterfaces: (state) => state.interfaces.length,
|
||||
},
|
||||
actions: {
|
||||
afterPageSizeChange() {
|
||||
@@ -91,6 +118,39 @@ export const profileStore = defineStore({
|
||||
this.stats = statsResponse.Stats
|
||||
this.statsEnabled = statsResponse.Enabled
|
||||
},
|
||||
setInterfaces(interfaces) {
|
||||
this.interfaces = interfaces
|
||||
this.selectedInterfaceId = interfaces.length > 0 ? interfaces[0].Identifier : ""
|
||||
this.fetching = false
|
||||
},
|
||||
async enableApi() {
|
||||
this.fetching = true
|
||||
let currentUser = authStore().user.Identifier
|
||||
return apiWrapper.post(`${baseUrl}/${base64_url_encode(currentUser)}/api/enable`)
|
||||
.then(this.setUser)
|
||||
.catch(error => {
|
||||
this.fetching = false
|
||||
console.log("Failed to activate API for ", currentUser, ": ", error)
|
||||
notify({
|
||||
title: "Backend Connection Failure",
|
||||
text: "Failed to activate API!",
|
||||
})
|
||||
})
|
||||
},
|
||||
async disableApi() {
|
||||
this.fetching = true
|
||||
let currentUser = authStore().user.Identifier
|
||||
return apiWrapper.post(`${baseUrl}/${base64_url_encode(currentUser)}/api/disable`)
|
||||
.then(this.setUser)
|
||||
.catch(error => {
|
||||
this.fetching = false
|
||||
console.log("Failed to deactivate API for ", currentUser, ": ", error)
|
||||
notify({
|
||||
title: "Backend Connection Failure",
|
||||
text: "Failed to deactivate API!",
|
||||
})
|
||||
})
|
||||
},
|
||||
async LoadPeers() {
|
||||
this.fetching = true
|
||||
let currentUser = authStore().user.Identifier
|
||||
@@ -133,5 +193,19 @@ export const profileStore = defineStore({
|
||||
})
|
||||
})
|
||||
},
|
||||
async LoadInterfaces() {
|
||||
this.fetching = true
|
||||
let currentUser = authStore().user.Identifier
|
||||
return apiWrapper.get(`${baseUrl}/${base64_url_encode(currentUser)}/interfaces`)
|
||||
.then(this.setInterfaces)
|
||||
.catch(error => {
|
||||
this.setInterfaces([])
|
||||
console.log("Failed to load interfaces for ", currentUser, ": ", error)
|
||||
notify({
|
||||
title: "Backend Connection Failure",
|
||||
text: "Failed to load interfaces!",
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@@ -3,8 +3,7 @@ import { defineStore } from 'pinia'
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
import { apiWrapper } from '@/helpers/fetch-wrapper'
|
||||
|
||||
export const securityStore = defineStore({
|
||||
id: 'security',
|
||||
export const securityStore = defineStore('security',{
|
||||
state: () => ({
|
||||
csrfToken: "",
|
||||
}),
|
||||
@@ -29,4 +28,4 @@ export const securityStore = defineStore({
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -5,8 +5,7 @@ import { apiWrapper } from '@/helpers/fetch-wrapper'
|
||||
|
||||
const baseUrl = `/config`
|
||||
|
||||
export const settingsStore = defineStore({
|
||||
id: 'settings',
|
||||
export const settingsStore = defineStore('settings', {
|
||||
state: () => ({
|
||||
settings: {},
|
||||
}),
|
||||
@@ -33,4 +32,4 @@ export const settingsStore = defineStore({
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@@ -5,8 +5,7 @@ import { base64_url_encode } from '@/helpers/encoding';
|
||||
|
||||
const baseUrl = `/user`
|
||||
|
||||
export const userStore = defineStore({
|
||||
id: 'users',
|
||||
export const userStore = defineStore('users', {
|
||||
state: () => ({
|
||||
userPeers: [],
|
||||
users: [],
|
||||
|
96
frontend/src/views/AuditView.vue
Normal file
96
frontend/src/views/AuditView.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import {auditStore} from "@/stores/audit";
|
||||
|
||||
const audit = auditStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await audit.LoadEntries()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<h1>{{ $t('audit.headline') }}</h1>
|
||||
</div>
|
||||
|
||||
<p class="lead">{{ $t('audit.abstract') }}</p>
|
||||
|
||||
<!-- Entry list -->
|
||||
<div class="mt-4 row">
|
||||
<div class="col-12 col-lg-6">
|
||||
<h3>{{ $t('audit.entries-headline') }}</h3>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 text-lg-end">
|
||||
<div class="form-group d-inline">
|
||||
<div class="input-group mb-3">
|
||||
<input v-model="audit.filter" class="form-control" :placeholder="$t('general.search.placeholder')" type="text" @keyup="audit.afterPageSizeChange">
|
||||
<button class="input-group-text btn btn-primary" :title="$t('general.search.button')"><i class="fa-solid fa-search"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 table-responsive">
|
||||
<div v-if="audit.Count===0">
|
||||
<h4>{{ $t('audit.no-entries.headline') }}</h4>
|
||||
<p>{{ $t('audit.no-entries.abstract') }}</p>
|
||||
</div>
|
||||
<table v-if="audit.Count!==0" id="auditTable" class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('audit.table-heading.id') }}</th>
|
||||
<th class="text-center" scope="col">{{ $t('audit.table-heading.time') }}</th>
|
||||
<th class="text-center" scope="col">{{ $t('audit.table-heading.severity') }}</th>
|
||||
<th scope="col">{{ $t('audit.table-heading.user') }}</th>
|
||||
<th scope="col">{{ $t('audit.table-heading.origin') }}</th>
|
||||
<th scope="col">{{ $t('audit.table-heading.message') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="entry in audit.FilteredAndPaged" :key="entry.Id">
|
||||
<td>{{entry.Id}}</td>
|
||||
<td>{{entry.Timestamp}}</td>
|
||||
<td class="text-center"><span class="badge rounded-pill" :class="[ entry.Severity === 'low' ? 'bg-light' : entry.Severity === 'medium' ? 'bg-warning' : 'bg-danger']">{{entry.Severity}}</span></td>
|
||||
<td>{{entry.ContextUser}}</td>
|
||||
<td>{{entry.Origin}}</td>
|
||||
<td>{{entry.Message}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="mt-3">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<ul class="pagination pagination-sm">
|
||||
<li :class="{disabled:audit.pageOffset===0}" class="page-item">
|
||||
<a class="page-link" @click="audit.previousPage">«</a>
|
||||
</li>
|
||||
|
||||
<li v-for="page in audit.pages" :key="page" :class="{active:audit.currentPage===page}" class="page-item">
|
||||
<a class="page-link" @click="audit.gotoPage(page)">{{page}}</a>
|
||||
</li>
|
||||
|
||||
<li :class="{disabled:!audit.hasNextPage}" class="page-item">
|
||||
<a class="page-link" @click="audit.nextPage">»</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-6 col-form-label text-end" for="paginationSelector">{{ $t('general.pagination.size') }}:</label>
|
||||
<div class="col-sm-6">
|
||||
<select id="paginationSelector" v-model.number="audit.pageSize" class="form-select" @click="audit.afterPageSizeChange()">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
<option value="999999999">{{ $t('general.pagination.all') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -10,6 +10,7 @@ import {peerStore} from "@/stores/peers";
|
||||
import {interfaceStore} from "@/stores/interfaces";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
import {settingsStore} from "@/stores/settings";
|
||||
import {humanFileSize} from '@/helpers/utils';
|
||||
|
||||
const settings = settingsStore()
|
||||
const interfaces = interfaceStore()
|
||||
@@ -21,6 +22,21 @@ const multiCreatePeerId = ref("")
|
||||
const editInterfaceId = ref("")
|
||||
const viewedInterfaceId = ref("")
|
||||
|
||||
const sortKey = ref("")
|
||||
const sortOrder = ref(1)
|
||||
const selectAll = ref(false)
|
||||
|
||||
function sortBy(key) {
|
||||
if (sortKey.value === key) {
|
||||
sortOrder.value = sortOrder.value * -1; // Toggle sort order
|
||||
} else {
|
||||
sortKey.value = key;
|
||||
sortOrder.value = 1; // Default to ascending
|
||||
}
|
||||
peers.sortKey = sortKey.value;
|
||||
peers.sortOrder = sortOrder.value;
|
||||
}
|
||||
|
||||
function calculateInterfaceName(id, name) {
|
||||
let result = id
|
||||
if (name) {
|
||||
@@ -33,12 +49,11 @@ async function download() {
|
||||
await interfaces.LoadInterfaceConfig(interfaces.GetSelected.Identifier)
|
||||
|
||||
// credit: https://www.bitdegree.org/learn/javascript-download
|
||||
let filename = interfaces.GetSelected.Identifier + ".conf"
|
||||
let text = interfaces.configuration
|
||||
|
||||
let element = document.createElement('a')
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text))
|
||||
element.setAttribute('download', filename)
|
||||
element.setAttribute('href', 'data:application/octet-stream;charset=utf-8,' + encodeURIComponent(text))
|
||||
element.setAttribute('download', interfaces.GetSelected.Filename)
|
||||
|
||||
element.style.display = 'none'
|
||||
document.body.appendChild(element)
|
||||
@@ -66,6 +81,12 @@ async function saveConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
peers.FilteredAndPaged.forEach(peer => {
|
||||
peer.IsSelected = selectAll.value;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await interfaces.LoadInterfaces()
|
||||
await peers.LoadPeers(undefined) // use default interface
|
||||
@@ -94,7 +115,7 @@ onMounted(async () => {
|
||||
<button class="input-group-text btn btn-primary" :title="$t('interfaces.button-add-interface')" @click.prevent="editInterfaceId='#NEW#'">
|
||||
<i class="fa-solid fa-plus-circle"></i>
|
||||
</button>
|
||||
<select v-model="interfaces.selected" :disabled="interfaces.Count===0" class="form-select" @change="peers.LoadPeers()">
|
||||
<select v-model="interfaces.selected" :disabled="interfaces.Count===0" class="form-select" @change="() => { peers.LoadPeers(); peers.LoadStats() }">
|
||||
<option v-if="interfaces.Count===0" value="nothing">{{ $t('interfaces.no-interface.default-selection') }}</option>
|
||||
<option v-for="iface in interfaces.All" :key="iface.Identifier" :value="iface.Identifier">{{ calculateInterfaceName(iface.Identifier,iface.DisplayName) }}</option>
|
||||
</select>
|
||||
@@ -138,7 +159,7 @@ onMounted(async () => {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ $t('interfaces.interface.key') }}:</td>
|
||||
<td>{{interfaces.GetSelected.PublicKey}}</td>
|
||||
<td class="text-wrap">{{interfaces.GetSelected.PublicKey}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('interfaces.interface.endpoint') }}:</td>
|
||||
@@ -192,7 +213,7 @@ onMounted(async () => {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ $t('interfaces.interface.key') }}:</td>
|
||||
<td>{{interfaces.GetSelected.PublicKey}}</td>
|
||||
<td class="text-wrap">{{interfaces.GetSelected.PublicKey}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('interfaces.interface.endpoints') }}:</td>
|
||||
@@ -230,7 +251,7 @@ onMounted(async () => {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ $t('interfaces.interface.key') }}:</td>
|
||||
<td>{{interfaces.GetSelected.PublicKey}}</td>
|
||||
<td class="text-wrap">{{interfaces.GetSelected.PublicKey}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ $t('interfaces.interface.endpoint') }}:</td>
|
||||
@@ -311,21 +332,38 @@ onMounted(async () => {
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox" value="">
|
||||
<input class="form-check-input" :title="$t('general.select-all')" type="checkbox" v-model="selectAll" @change="toggleSelectAll">
|
||||
</th><!-- select -->
|
||||
<th scope="col"></th><!-- status -->
|
||||
<th scope="col">{{ $t('interfaces.table-heading.name') }}</th>
|
||||
<th scope="col">{{ $t('interfaces.table-heading.user') }}</th>
|
||||
<th scope="col">{{ $t('interfaces.table-heading.ip') }}</th>
|
||||
<th v-if="interfaces.GetSelected.Mode==='client'" scope="col">{{ $t('interfaces.table-heading.endpoint') }}</th>
|
||||
<th v-if="peers.hasStatistics" scope="col">{{ $t('interfaces.table-heading.status') }}</th>
|
||||
<th scope="col" @click="sortBy('DisplayName')">
|
||||
{{ $t("interfaces.table-heading.name") }}
|
||||
<i v-if="sortKey === 'DisplayName'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th scope="col" @click="sortBy('UserIdentifier')">
|
||||
{{ $t("interfaces.table-heading.user") }}
|
||||
<i v-if="sortKey === 'UserIdentifier'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th scope="col" @click="sortBy('Addresses')">
|
||||
{{ $t("interfaces.table-heading.ip") }}
|
||||
<i v-if="sortKey === 'Addresses'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th v-if="interfaces.GetSelected.Mode === 'client'" scope="col">
|
||||
{{ $t("interfaces.table-heading.endpoint") }}
|
||||
</th>
|
||||
<th v-if="peers.hasStatistics" scope="col" @click="sortBy('IsConnected')">
|
||||
{{ $t("interfaces.table-heading.status") }}
|
||||
<i v-if="sortKey === 'IsConnected'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th v-if="peers.hasStatistics" scope="col" @click="sortBy('Traffic')">RX/TX
|
||||
<i v-if="sortKey === 'Traffic'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th scope="col"></th><!-- Actions -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="peer in peers.FilteredAndPaged" :key="peer.Identifier">
|
||||
<th scope="row">
|
||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||
<input class="form-check-input" type="checkbox" v-model="peer.IsSelected">
|
||||
</th>
|
||||
<td class="text-center">
|
||||
<span v-if="peer.Disabled" class="text-danger" :title="$t('interfaces.peer-disabled') + ' ' + peer.DisabledReason"><i class="fa fa-circle-xmark"></i></span>
|
||||
@@ -345,6 +383,9 @@ onMounted(async () => {
|
||||
<span class="badge rounded-pill bg-light" :title="$t('interfaces.peer-not-connected')"><i class="fa-solid fa-link-slash"></i></span>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="peers.hasStatistics" >
|
||||
<span class="text-center" >{{ humanFileSize(peers.Statistics(peer.Identifier).BytesReceived) }} / {{ humanFileSize(peers.Statistics(peer.Identifier).BytesTransmitted) }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="#" :title="$t('interfaces.button-show-peer')" @click.prevent="viewedPeerId=peer.Identifier"><i class="fas fa-eye me-2"></i></a>
|
||||
<a href="#" :title="$t('interfaces.button-edit-peer')" @click.prevent="editPeerId=peer.Identifier"><i class="fas fa-cog"></i></a>
|
||||
@@ -375,7 +416,7 @@ onMounted(async () => {
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-6 col-form-label text-end" for="paginationSelector">{{ $t('general.pagination.size') }}:</label>
|
||||
<div class="col-sm-6">
|
||||
<select v-model.number="peers.pageSize" class="form-select" @click="peers.afterPageSizeChange()">
|
||||
<select id="paginationSelector" v-model.number="peers.pageSize" class="form-select" @click="peers.afterPageSizeChange()">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
|
147
frontend/src/views/KeyGeneraterView.vue
Normal file
147
frontend/src/views/KeyGeneraterView.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<script setup>
|
||||
|
||||
import {ref} from "vue";
|
||||
|
||||
const privateKey = ref("")
|
||||
const publicKey = ref("")
|
||||
const presharedKey = ref("")
|
||||
|
||||
/**
|
||||
* Generate an X25519 keypair using the Web Crypto API and return Base64-encoded strings.
|
||||
* @async
|
||||
* @function generateKeypair
|
||||
* @returns {Promise<{ publicKey: string, privateKey: string }>} Resolves with an object containing
|
||||
* - publicKey: the Base64-encoded public key
|
||||
* - privateKey: the Base64-encoded private key
|
||||
*/
|
||||
async function generateKeypair() {
|
||||
// 1. Generate an X25519 key pair
|
||||
const keyPair = await crypto.subtle.generateKey(
|
||||
{ name: 'X25519', namedCurve: 'X25519' },
|
||||
true, // extractable
|
||||
['deriveBits'] // allowed usage for ECDH
|
||||
);
|
||||
|
||||
// 2. Export keys as JWK to access raw key material
|
||||
const pubJwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
|
||||
const privJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey);
|
||||
|
||||
// 3. Convert Base64URL to standard Base64 with padding
|
||||
return {
|
||||
publicKey: b64urlToB64(pubJwk.x),
|
||||
privateKey: b64urlToB64(privJwk.d)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 32-byte pre-shared key using crypto.getRandomValues.
|
||||
* @function generatePresharedKey
|
||||
* @returns {Uint8Array} A Uint8Array of length 32 with random bytes.
|
||||
*/
|
||||
function generatePresharedKey() {
|
||||
let privateKey = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Base64URL-encoded string to standard Base64 with padding.
|
||||
* @function b64urlToB64
|
||||
* @param {string} input - The Base64URL string.
|
||||
* @returns {string} The padded, standard Base64 string.
|
||||
*/
|
||||
function b64urlToB64(input) {
|
||||
let b64 = input.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (b64.length % 4) {
|
||||
b64 += '=';
|
||||
}
|
||||
return b64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ArrayBuffer or TypedArray buffer to a Base64-encoded string.
|
||||
* @function arrayBufferToBase64
|
||||
* @param {ArrayBuffer|Uint8Array} buffer - The buffer to convert.
|
||||
* @returns {string} Base64-encoded representation of the buffer.
|
||||
*/
|
||||
function arrayBufferToBase64(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.byteLength; ++i) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
// Window.btoa handles binary → Base64
|
||||
return btoa(binary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new keypair and update the corresponding Vue refs.
|
||||
* @async
|
||||
* @function generateNewKeyPair
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function generateNewKeyPair() {
|
||||
const keypair = await generateKeypair();
|
||||
|
||||
privateKey.value = keypair.privateKey;
|
||||
publicKey.value = keypair.publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new pre-shared key and update the Vue ref.
|
||||
* @function generateNewPresharedKey
|
||||
*/
|
||||
function generateNewPresharedKey() {
|
||||
const rawPsk = generatePresharedKey();
|
||||
presharedKey.value = arrayBufferToBase64(rawPsk);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<h1>{{ $t('keygen.headline') }}</h1>
|
||||
</div>
|
||||
|
||||
<p class="lead">{{ $t('keygen.abstract') }}</p>
|
||||
|
||||
<div class="mt-4 row">
|
||||
<div class="col-12 col-lg-5">
|
||||
<h1>{{ $t('keygen.headline-keypair') }}</h1>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('keygen.private-key.label') }}</label>
|
||||
<input class="form-control" v-model="privateKey" :placeholder="$t('keygen.private-key.placeholder')" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('keygen.public-key.label') }}</label>
|
||||
<input class="form-control" v-model="publicKey" :placeholder="$t('keygen.private-key.placeholder')" readonly>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<hr class="mt-4">
|
||||
<button class="btn btn-primary mb-4" type="button" @click.prevent="generateNewKeyPair">{{ $t('keygen.button-generate') }}</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-12 col-lg-2 mt-sm-4">
|
||||
</div>
|
||||
<div class="col-12 col-lg-5">
|
||||
<h1>{{ $t('keygen.headline-preshared-key') }}</h1>
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('keygen.preshared-key.label') }}</label>
|
||||
<input class="form-control" v-model="presharedKey" :placeholder="$t('keygen.preshared-key.placeholder')" readonly>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<hr class="mt-4">
|
||||
<button class="btn btn-primary mb-4" type="button" @click.prevent="generateNewPresharedKey">{{ $t('keygen.button-generate') }}</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@@ -3,8 +3,9 @@ import PeerViewModal from "../components/PeerViewModal.vue";
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import { profileStore } from "@/stores/profile";
|
||||
import PeerEditModal from "@/components/PeerEditModal.vue";
|
||||
import UserPeerEditModal from "@/components/UserPeerEditModal.vue";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
import { humanFileSize } from "@/helpers/utils";
|
||||
|
||||
const settings = settingsStore()
|
||||
const profile = profileStore()
|
||||
@@ -12,17 +13,47 @@ const profile = profileStore()
|
||||
const viewedPeerId = ref("")
|
||||
const editPeerId = ref("")
|
||||
|
||||
const sortKey = ref("")
|
||||
const sortOrder = ref(1)
|
||||
const selectAll = ref(false)
|
||||
|
||||
function sortBy(key) {
|
||||
if (sortKey.value === key) {
|
||||
sortOrder.value = sortOrder.value * -1; // Toggle sort order
|
||||
} else {
|
||||
sortKey.value = key;
|
||||
sortOrder.value = 1; // Default to ascending
|
||||
}
|
||||
profile.sortKey = sortKey.value;
|
||||
profile.sortOrder = sortOrder.value;
|
||||
}
|
||||
|
||||
function friendlyInterfaceName(id, name) {
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
profile.FilteredAndPagedPeers.forEach(peer => {
|
||||
peer.IsSelected = selectAll.value;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await profile.LoadUser()
|
||||
await profile.LoadPeers()
|
||||
await profile.LoadStats()
|
||||
await profile.LoadInterfaces()
|
||||
await profile.calculatePages(); // Forces to show initial page number
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PeerViewModal :peerId="viewedPeerId" :visible="viewedPeerId !== ''" @close="viewedPeerId = ''"></PeerViewModal>
|
||||
<PeerEditModal :peerId="editPeerId" :visible="editPeerId !== ''" @close="editPeerId = ''"></PeerEditModal>
|
||||
<UserPeerEditModal :peerId="editPeerId" :visible="editPeerId !== ''" @close="editPeerId = ''; profile.LoadPeers()"></UserPeerEditModal>
|
||||
|
||||
<!-- Peer list -->
|
||||
<div class="mt-4 row">
|
||||
@@ -40,9 +71,17 @@ onMounted(async () => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-3 text-lg-end">
|
||||
<a v-if="settings.Setting('SelfProvisioning')" class="btn btn-primary ms-2" href="#"
|
||||
:title="$t('general.search.button-add-peer')" @click.prevent="editPeerId = '#NEW#'"><i
|
||||
class="fa fa-plus me-1"></i><i class="fa fa-user"></i></a>
|
||||
<div class="form-group" v-if="settings.Setting('SelfProvisioning')">
|
||||
<div class="input-group mb-3">
|
||||
<button class="input-group-text btn btn-primary" :title="$t('interfaces.button-add-peer')" @click.prevent="editPeerId = '#NEW#'">
|
||||
<i class="fa fa-plus me-1"></i><i class="fa fa-user"></i>
|
||||
</button>
|
||||
<select v-model="profile.selectedInterfaceId" :disabled="profile.CountInterfaces===0" class="form-select">
|
||||
<option v-if="profile.CountInterfaces===0" value="nothing">{{ $t('interfaces.no-interface.default-selection') }}</option>
|
||||
<option v-for="iface in profile.interfaces" :key="iface.Identifier" :value="iface.Identifier">{{ friendlyInterfaceName(iface.Identifier,iface.DisplayName) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 table-responsive">
|
||||
@@ -54,13 +93,24 @@ onMounted(async () => {
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox"
|
||||
value="">
|
||||
<input class="form-check-input" :title="$t('general.select-all')" type="checkbox" v-model="selectAll" @change="toggleSelectAll">
|
||||
</th><!-- select -->
|
||||
<th scope="col"></th><!-- status -->
|
||||
<th scope="col">{{ $t('profile.table-heading.name') }}</th>
|
||||
<th scope="col">{{ $t('profile.table-heading.ip') }}</th>
|
||||
<th v-if="profile.hasStatistics" scope="col">{{ $t('profile.table-heading.stats') }}</th>
|
||||
<th scope="col" @click="sortBy('DisplayName')">
|
||||
{{ $t("profile.table-heading.name") }}
|
||||
<i v-if="sortKey === 'DisplayName'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th scope="col" @click="sortBy('Addresses')">
|
||||
{{ $t("profile.table-heading.ip") }}
|
||||
<i v-if="sortKey === 'Addresses'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th v-if="profile.hasStatistics" scope="col" @click="sortBy('IsConnected')">
|
||||
{{ $t("profile.table-heading.stats") }}
|
||||
<i v-if="sortKey === 'IsConnected'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th v-if="profile.hasStatistics" scope="col" @click="sortBy('Traffic')">RX/TX
|
||||
<i v-if="sortKey === 'Traffic'" :class="sortOrder === 1 ? 'asc' : 'desc'"></i>
|
||||
</th>
|
||||
<th scope="col">{{ $t('profile.table-heading.interface') }}</th>
|
||||
<th scope="col"></th><!-- Actions -->
|
||||
</tr>
|
||||
@@ -68,7 +118,7 @@ onMounted(async () => {
|
||||
<tbody>
|
||||
<tr v-for="peer in profile.FilteredAndPagedPeers" :key="peer.Identifier">
|
||||
<th scope="row">
|
||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||
<input class="form-check-input" type="checkbox" v-model="peer.IsSelected">
|
||||
</th>
|
||||
<td class="text-center">
|
||||
<span v-if="peer.Disabled" class="text-danger"><i class="fa fa-circle-xmark"
|
||||
@@ -90,6 +140,9 @@ onMounted(async () => {
|
||||
<span class="badge rounded-pill bg-light"><i class="fa-solid fa-link-slash"></i></span>
|
||||
</div>
|
||||
</td>
|
||||
<td v-if="profile.hasStatistics" >
|
||||
<span class="text-center" >{{ humanFileSize(profile.Statistics(peer.Identifier).BytesReceived) }} / {{ humanFileSize(profile.Statistics(peer.Identifier).BytesTransmitted) }}</span>
|
||||
</td>
|
||||
<td>{{ peer.InterfaceIdentifier }}</td>
|
||||
<td class="text-center">
|
||||
<a href="#" :title="$t('profile.button-show-peer')" @click.prevent="viewedPeerId = peer.Identifier"><i
|
||||
@@ -125,7 +178,7 @@ onMounted(async () => {
|
||||
{{ $t('general.pagination.size')}}:
|
||||
</label>
|
||||
<div class="col-sm-6">
|
||||
<select v-model.number="profile.pageSize" class="form-select" @click="profile.afterPageSizeChange()">
|
||||
<select id="paginationSelector" v-model.number="profile.pageSize" class="form-select" @click="profile.afterPageSizeChange()">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
|
72
frontend/src/views/SettingsView.vue
Normal file
72
frontend/src/views/SettingsView.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
import { onMounted } from "vue";
|
||||
import { profileStore } from "@/stores/profile";
|
||||
import { settingsStore } from "@/stores/settings";
|
||||
import { authStore } from "../stores/auth";
|
||||
|
||||
const profile = profileStore()
|
||||
const settings = settingsStore()
|
||||
const auth = authStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await profile.LoadUser()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-header">
|
||||
<h1>{{ $t('settings.headline') }}</h1>
|
||||
</div>
|
||||
|
||||
<p class="lead">{{ $t('settings.abstract') }}</p>
|
||||
|
||||
<div v-if="auth.IsAdmin || !settings.Setting('ApiAdminOnly')">
|
||||
<div class="bg-light p-5" v-if="profile.user.ApiToken">
|
||||
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
|
||||
<p class="lead">{{ $t('settings.api.abstract') }}</p>
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('settings.api.active-description') }}</p>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('settings.api.user-label') }}</label>
|
||||
<input v-model="profile.user.Identifier" class="form-control" :placeholder="$t('settings.api.user-placeholder')" type="text" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-group">
|
||||
<label class="form-label mt-4">{{ $t('settings.api.token-label') }}</label>
|
||||
<input v-model="profile.user.ApiToken" class="form-control" :placeholder="$t('settings.api.token-placeholder')" type="text" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-group">
|
||||
<p class="form-label mt-4">{{ $t('settings.api.token-created-label') }} {{profile.user.ApiTokenCreated}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<div class="col-6">
|
||||
<button class="input-group-text btn btn-primary" :title="$t('settings.api.button-disable-title')" @click.prevent="profile.disableApi()" :disabled="profile.isFetching">
|
||||
<i class="fa-solid fa-minus-circle"></i> {{ $t('settings.api.button-disable-text') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<a href="/api/v1/doc.html" target="_blank" :alt="$t('settings.api.api-link')">{{ $t('settings.api.api-link') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-light p-5" v-else>
|
||||
<h2 class="display-7">{{ $t('settings.api.headline') }}</h2>
|
||||
<p class="lead">{{ $t('settings.api.abstract') }}</p>
|
||||
<hr class="my-4">
|
||||
<p>{{ $t('settings.api.inactive-description') }}</p>
|
||||
<button class="input-group-text btn btn-primary" :title="$t('settings.api.button-enable-title')" @click.prevent="profile.enableApi()" :disabled="profile.isFetching">
|
||||
<i class="fa-solid fa-plus-circle"></i> {{ $t('settings.api.button-enable-text') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -3,15 +3,20 @@ import {userStore} from "@/stores/users";
|
||||
import {ref,onMounted} from "vue";
|
||||
import UserEditModal from "../components/UserEditModal.vue";
|
||||
import UserViewModal from "../components/UserViewModal.vue";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
import {settingsStore} from "@/stores/settings";
|
||||
|
||||
const settings = settingsStore()
|
||||
const users = userStore()
|
||||
|
||||
const editUserId = ref("")
|
||||
const viewedUserId = ref("")
|
||||
|
||||
const selectAll = ref(false)
|
||||
|
||||
function toggleSelectAll() {
|
||||
users.FilteredAndPaged.forEach(user => {
|
||||
user.IsSelected = selectAll.value;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
users.LoadUsers()
|
||||
})
|
||||
@@ -49,7 +54,7 @@ onMounted(() => {
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">
|
||||
<input id="flexCheckDefault" class="form-check-input" :title="$t('general.select-all')" type="checkbox" value="">
|
||||
<input class="form-check-input" :title="$t('general.select-all')" type="checkbox" v-model="selectAll" @change="toggleSelectAll">
|
||||
</th><!-- select -->
|
||||
<th scope="col"></th><!-- status -->
|
||||
<th scope="col">{{ $t('users.table-heading.id') }}</th>
|
||||
@@ -65,7 +70,7 @@ onMounted(() => {
|
||||
<tbody>
|
||||
<tr v-for="user in users.FilteredAndPaged" :key="user.Identifier">
|
||||
<th scope="row">
|
||||
<input id="flexCheckDefault" class="form-check-input" type="checkbox" value="">
|
||||
<input class="form-check-input" type="checkbox" v-model="user.IsSelected">
|
||||
</th>
|
||||
<td class="text-center">
|
||||
<span v-if="user.Disabled" class="text-danger" :title="$t('users.user-disabled') + ' ' + user.DisabledReason"><i class="fa fa-circle-xmark"></i></span>
|
||||
@@ -111,7 +116,7 @@ onMounted(() => {
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-6 col-form-label text-end" for="paginationSelector">{{ $t('general.pagination.size') }}:</label>
|
||||
<div class="col-sm-6">
|
||||
<select v-model.number="users.pageSize" class="form-select" @click="users.afterPageSizeChange()">
|
||||
<select id="paginationSelector" v-model.number="users.pageSize" class="form-select" @click="users.afterPageSizeChange()">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user