5 Commits

310 changed files with 10866 additions and 24448 deletions

View File

@@ -1,3 +1 @@
/src/node_modules /src/node_modules
/src/.nuxt
/src/.output

5
.github/CODEOWNERS vendored
View File

@@ -1,5 +1,4 @@
# Copyright (c) Emile Nijssen (WeeJeWel) # Copyright (c) Emile Nijssen (WeeJeWel)
# Founder and Codeowner of WireGuard Easy (wg-easy) # Founder and Codeowner of WireGuard Easy (wg-easy)
# Maintained by Bernd Storath (kaaax0815) # Maintained by Philip Heiduck (pheiduck)
* @WeeJeWel * @pheiduck
* @kaaax0815

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,3 @@
# These are supported funding model platforms # These are supported funding model platforms
github: [weejewel, kaaax0815] github: weejewel

View File

@@ -1,37 +0,0 @@
---
name: 🐛 Bug Report
description: Create a report to help us improve
title: "[Bug]: "
type: Bug
body:
- type: markdown
attributes:
value: |
**Thanks :heart: for taking the time to fill out this bug report!**
We kindly ask that you search to see if an issue [already exists](https://github.com/wg-easy/wg-easy/issues?q=is%3Aissue+sort%3Acreated-desc+) for the bug you encountered.
- type: textarea
id: what-happened
attributes:
label: Describe the bug
placeholder: Tell us what you see!
value: "A bug happened!"
validations:
required: true
- type: textarea
id: what-should-happen
attributes:
label: Expected behavior
placeholder: Tell us what you expected!
value: "Work just fine!"
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
render: shell

View File

@@ -1,47 +0,0 @@
---
name: 🛠️ Feature Request
description: Suggest an idea to help us improve
title: "[Feat]: "
type: Feature
body:
- type: markdown
attributes:
value: |
**Thanks :heart: for taking the time to fill out this feature request report!**
We kindly ask that you search to see if an issue [already exists](https://github.com/wg-easy/wg-easy/issues?q=is%3Aissue+sort%3Acreated-desc+) for your feature.
We are also happy to accept contributions from our users. For more details see [here](https://github.com/wg-easy/wg-easy/blob/master/contributing.md).
- type: textarea
attributes:
label: Description
description: |
A clear and concise description of the feature you're interested in.
validations:
required: true
- type: textarea
attributes:
label: Suggested Solution
description: |
Describe the solution you'd like. A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Alternatives
description: |
Describe alternatives you've considered.
A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
- type: textarea
attributes:
label: Additional Context
description: |
Add any other context about the problem here.
validations:
required: false

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. macOS 12.1]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS 8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,5 +0,0 @@
contact_links:
- name: Get Help
url: https://github.com/wg-easy/wg-easy/discussions/new?category=q-a
about: If you can't get something to work the way you expect, open a question in the discussions.
blank_issues_enabled: false

View File

@@ -5,13 +5,3 @@ updates:
schedule: schedule:
interval: "weekly" interval: "weekly"
rebase-strategy: "auto" rebase-strategy: "auto"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
rebase-strategy: "auto"
- package-ecosystem: "npm"
directory: "/src/"
schedule:
interval: "weekly"
rebase-strategy: "auto"

View File

@@ -1,12 +1,10 @@
name: CodeQL name: "CodeQL"
on: on:
push: push:
branches: branches: [ "master" ]
- master
pull_request: pull_request:
branches: branches: [ "master" ]
- master
schedule: schedule:
- cron: "15 0 * * *" - cron: "15 0 * * *"
@@ -23,7 +21,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: ["javascript-typescript"] language: [ 'javascript-typescript' ]
steps: steps:
- name: Checkout repository - name: Checkout repository

View File

@@ -1,46 +1,20 @@
name: Development name: Build & Publish Development
on: on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
docker-build: deploy:
name: Build Docker name: Build & Deploy
runs-on: ${{ matrix.arch.os }} runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy' if: github.repository_owner == 'wg-easy'
permissions: permissions:
packages: write packages: write
strategy: contents: read
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with: with:
images: | ref: production
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@@ -48,47 +22,6 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: ${{ matrix.arch.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/wg-easy/wg-easy
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
docker-merge:
name: Merge & Deploy Docker
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
needs: docker-build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -96,59 +29,9 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Build & Publish Docker Image
uses: docker/setup-buildx-action@v3 uses: docker/build-push-action@v6
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with: with:
images: | push: true
ghcr.io/wg-easy/wg-easy platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
flavor: | tags: ghcr.io/wg-easy/wg-easy:development
latest=false
tags: |
type=raw,value=development
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
docs:
name: Build & Deploy Docs
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
contents: write
needs: docker-merge
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.11.9
cache: "pip"
cache-dependency-path: docs/requirements.txt
- name: Install Dependencies
run: |
pip install -r docs/requirements.txt
- name: Setup Git User
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Build Docs Website
run: |
cd docs
git fetch origin gh-pages --depth=1 || true
mike deploy --push --update-aliases development

View File

@@ -1,165 +0,0 @@
name: Edge
on:
workflow_dispatch:
push:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
docker-build:
name: Build Docker
runs-on: ${{ matrix.arch.os }}
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
strategy:
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Prepare
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: ${{ matrix.arch.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/wg-easy/wg-easy
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
docker-merge:
name: Merge & Deploy Docker
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
needs: docker-build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
tags: |
type=raw,value=edge
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
docs:
name: Build & Deploy Docs
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
contents: write
needs: docker-merge
steps:
- uses: actions/checkout@v4
with:
ref: master
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.11.9
cache: "pip"
cache-dependency-path: docs/requirements.txt
- name: Install Dependencies
run: |
pip install -r docs/requirements.txt
- name: Setup Git User
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Build Docs Website
run: |
cd docs
git fetch origin gh-pages --depth=1 || true
mike deploy --push --update-aliases edge

39
.github/workflows/deploy-nightly.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Build & Publish Nightly
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
jobs:
deploy:
name: Build & Deploy
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v4
with:
ref: production
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Publish Docker Image
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
tags: ghcr.io/wg-easy/wg-easy:nightly

View File

@@ -1,33 +1,21 @@
name: Pull Request name: Build Pull Request
on: on:
workflow_dispatch: workflow_dispatch:
pull_request: pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
docker: deploy:
name: Build Docker name: Build & Deploy
runs-on: ${{ matrix.arch.os }} runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy' if: github.repository_owner == 'wg-easy'
strategy: permissions:
fail-fast: false packages: write
matrix: contents: read
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
- name: Prepare ref: production
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@@ -45,9 +33,6 @@ jobs:
- name: Build Docker Image - name: Build Docker Image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: .
push: false push: false
platforms: ${{ matrix.arch.platform }} platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
tags: ghcr.io/wg-easy/wg-easy:pr tags: ghcr.io/wg-easy/wg-easy:pr
cache-from: type=gha
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}

View File

@@ -1,54 +1,23 @@
name: Production name: Build & Publish Latest
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
tags: branches:
- "v*" - production
# This workflow does not support fixing old versions
# as this will break the latest and major tags
jobs: jobs:
docker-build: deploy:
name: Build Docker name: Build & Deploy
runs-on: ${{ matrix.arch.os }} runs-on: ubuntu-latest
if: | if: github.repository_owner == 'wg-easy'
github.repository_owner == 'wg-easy' &&
startsWith(github.ref, 'refs/tags/v')
permissions: permissions:
packages: write packages: write
strategy: contents: read
fail-fast: false
matrix:
arch:
- platform: linux/amd64
os: ubuntu-latest
- platform: linux/arm64
os: ubuntu-24.04-arm
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Prepare
run: |
platform=${{ matrix.arch.platform }}
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with: with:
images: | ref: production
ghcr.io/wg-easy/wg-easy
flavor: |
latest=false
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
@@ -56,49 +25,6 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build and push by digest
id: build
uses: docker/build-push-action@v6
with:
context: .
platforms: ${{ matrix.arch.platform }}
labels: ${{ steps.meta.outputs.labels }}
tags: ghcr.io/wg-easy/wg-easy
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }}
cache-to: type=gha,mode=min,scope=build-${{ env.PLATFORM_PAIR }}
- name: Export digest
run: |
mkdir -p ${{ runner.temp }}/digests
digest="${{ steps.build.outputs.digest }}"
touch "${{ runner.temp }}/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: ${{ runner.temp }}/digests/*
if-no-files-found: error
retention-days: 1
docker-merge:
name: Merge & Deploy Docker
runs-on: ubuntu-latest
if: |
github.repository_owner == 'wg-easy' &&
startsWith(github.ref, 'refs/tags/v')
permissions:
packages: write
needs: docker-build
steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
path: ${{ runner.temp }}/digests
pattern: digests-*
merge-multiple: true
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -106,74 +32,12 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set environment variables
uses: docker/setup-buildx-action@v3 run: echo RELEASE=$(cat ./src/package.json | jq -r .release | jq -r .version) >> $GITHUB_ENV
- name: Docker meta - name: Build & Publish Docker Image
id: meta uses: docker/build-push-action@v6
uses: docker/metadata-action@v5
with: with:
images: | push: true
ghcr.io/wg-easy/wg-easy platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
flavor: | tags: ghcr.io/wg-easy/wg-easy:latest, ghcr.io/wg-easy/wg-easy:${{ env.RELEASE }}
latest=false
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
- name: Create manifest list and push
working-directory: ${{ runner.temp }}/digests
run: |
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf 'ghcr.io/wg-easy/wg-easy@sha256:%s ' *)
- name: Inspect image
run: |
docker buildx imagetools inspect ghcr.io/wg-easy/wg-easy:${{ steps.meta.outputs.version }}
docs:
name: Build & Deploy Docs
runs-on: ubuntu-latest
if: |
github.repository_owner == 'wg-easy' &&
startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
needs: docker-merge
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: 3.11.9
cache: "pip"
cache-dependency-path: docs/requirements.txt
- name: Install Dependencies
run: |
pip install -r docs/requirements.txt
- name: Setup Git User
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Build Docs Website
run: |
cd docs
git fetch origin gh-pages --depth=1 || true
# Extract version numbers
DOCS_VERSION=${GITHUB_REF#refs/tags/} # e.g. v1.2.3 or v1.2.3-beta
MINOR_VERSION=$(echo $DOCS_VERSION | cut -d. -f1,2) # e.g. v1.2
# Check if it's a stable release (only numbers, no '-')
if [[ "$DOCS_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Stable release detected: $DOCS_VERSION"
mike deploy --push --update-aliases $MINOR_VERSION latest
else
echo "Pre-release detected: $DOCS_VERSION"
mike deploy --push --update-aliases Pre-release
fi

View File

@@ -4,65 +4,26 @@ on:
push: push:
branches: branches:
- master - master
- production
pull_request: pull_request:
jobs: jobs:
docs:
name: Check Docs
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "lts/*"
check-latest: true
cache: "pnpm"
- name: Check docs formatting
run: |
pnpm install
pnpm format:check:docs
lint: lint:
name: Lint name: Lint
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: docs
if: github.repository_owner == 'wg-easy' if: github.repository_owner == 'wg-easy'
strategy:
fail-fast: false
max-parallel: 3
matrix:
command: ["lint", "typecheck", "format:check"]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
run_install: false
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: "lts/*" node-version: '20'
check-latest: true check-latest: true
cache: "pnpm" cache: 'npm'
- name: pnpm ${{ matrix.command }} - name: npm run lint
run: | run: |
cd src cd src
pnpm install npm ci
pnpm ${{ matrix.command }} npm run lint

40
.github/workflows/npm-update-bot.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: NPM Update Bot 🤖
on:
push:
branches: [ "master" ]
schedule:
- cron: "0 0 * * 1"
jobs:
npmupbot:
name: NPM Update Bot 🤖
runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
repository: wg-easy/wg-easy
ref: master
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
check-latest: true
cache: 'npm'
- name: Bot 🤖 "Updating NPM Packages..."
run: |
npm install -g --silent npm-check-updates
ncu -u
npm update
cd src
ncu -u
npm update
npm run buildcss
git config --global user.name 'NPM Update Bot'
git config --global user.email 'npmupbot@users.noreply.github.com'
git add .
git commit -am "npm: package updates" || true
git push

View File

@@ -8,10 +8,11 @@ name: Mark stale issues and pull requests
on: on:
workflow_dispatch: workflow_dispatch:
schedule: schedule:
- cron: "*/5 * * * *" - cron: '*/5 * * * *'
jobs: jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository_owner == 'wg-easy' if: github.repository_owner == 'wg-easy'
permissions: permissions:

5
.gitignore vendored
View File

@@ -1,3 +1,6 @@
/config
/wg0.conf
/wg0.json
/src/node_modules
.DS_Store .DS_Store
*.swp *.swp
node_modules

View File

@@ -1,15 +0,0 @@
{
"recommendations": [
"aaron-bond.better-comments",
"dbaeumer.vscode-eslint",
"antfu.goto-alias",
"visualstudioexptteam.vscodeintellicode",
"Nuxtr.nuxtr-vscode",
"esbenp.prettier-vscode",
"yoavbls.pretty-ts-errors",
"bradlc.vscode-tailwindcss",
"vue.volar",
"lokalise.i18n-ally",
"DavidAnson.vscode-markdownlint"
]
}

33
.vscode/settings.json vendored
View File

@@ -1,33 +0,0 @@
{
"editor.tabSize": 2,
"editor.useTabStops": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"nuxtr.vueFiles.style.addStyleTag": false,
"nuxtr.piniaFiles.defaultTemplate": "setup",
"nuxtr.monorepoMode.DirectoryName": "src",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 4,
"editor.useTabStops": false
},
"typescript.tsdk": "./src/node_modules/typescript/lib",
"i18n-ally.enabledFrameworks": ["vue"],
"i18n-ally.localesPaths": ["src/i18n/locales"],
"i18n-ally.sortKeys": false,
"i18n-ally.keepFulfilled": false,
"i18n-ally.keystyle": "nested",
"editor.gotoLocation.multipleDefinitions": "goto"
}

View File

@@ -1,42 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
We're super excited to announce v15!
This update is an entire rewrite to make it even easier to set up your own VPN.
## Breaking Changes
As the whole setup has changed, we recommend to start from scratch. And import your existing configs.
## Major Changes
- Almost all Environment variables removed
- New and Improved UI
- API Basic Authentication
- Added Docs
- Incrementing Version -> Semantic Versioning
- CIDR Support
- IPv6 Support
- Changed API Structure
- SQLite Database
- Deprecated Dockerless Installations
- Added Docker Volume Mount (`/lib/modules`)
- Removed ARMv6 and ARMv7 support
- Connections over HTTP require setting the `INSECURE` env var
- Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only
- Added 2FA using TOTP
- Improved mobile support
- CLI
- Replaced `nightly` with `edge`
## [14.0.0] - 2024-09-04
### Major changes
- `PASSWORD` has been replaced by `PASSWORD_HASH`

View File

@@ -1,61 +1,49 @@
FROM docker.io/library/node:lts-alpine AS build # As a workaround we have to build on nodejs 18
WORKDIR /app # nodejs 20 hangs on build with armv6/armv7
FROM docker.io/library/node:18-alpine AS build_node_modules
# update corepack # Update npm to latest
RUN npm install --global corepack@latest RUN npm install -g npm@latest
# Install pnpm
RUN corepack enable pnpm
# Copy Web UI # Copy Web UI
COPY src/package.json src/pnpm-lock.yaml ./ COPY src /app
RUN pnpm install WORKDIR /app
RUN npm ci --omit=dev &&\
# Build UI mv node_modules /node_modules
COPY src ./
RUN pnpm build
# Copy build result to a new image. # Copy build result to a new image.
# This saves a lot of disk space. # This saves a lot of disk space.
FROM docker.io/library/node:lts-alpine FROM docker.io/library/node:20-alpine
WORKDIR /app HEALTHCHECK CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1" --interval=1m --timeout=5s --retries=3
COPY --from=build_node_modules /app /app
HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1" # Move node_modules one directory up, so during development
# we don't have to mount it in a volume.
# This results in much faster reloading!
#
# Also, some node_modules might be native, and
# the architecture & OS of your development machine might differ
# than what runs inside of docker.
COPY --from=build_node_modules /node_modules /node_modules
# Copy build # Copy the needed wg-password scripts
COPY --from=build /app/.output /app COPY --from=build_node_modules /app/wgpw.sh /bin/wgpw
# Copy migrations RUN chmod +x /bin/wgpw
COPY --from=build /app/server/database/migrations /app/server/database/migrations
# libsql (https://github.com/nitrojs/nitro/issues/3328)
RUN cd /app/server && \
npm install --no-save libsql && \
npm cache clean --force
# cli
COPY --from=build /app/cli/cli.sh /usr/local/bin/cli
RUN chmod +x /usr/local/bin/cli
# Install Linux packages # Install Linux packages
RUN apk add --no-cache \ RUN apk add --no-cache \
dpkg \ dpkg \
dumb-init \ dumb-init \
iptables \ iptables \
ip6tables \
nftables \
kmod \
iptables-legacy \ iptables-legacy \
wireguard-tools wireguard-tools
# Use iptables-legacy # Use iptables-legacy
RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables-legacy 10 --slave /usr/sbin/iptables-restore iptables-restore /usr/sbin/iptables-legacy-restore --slave /usr/sbin/iptables-save iptables-save /usr/sbin/iptables-legacy-save RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save
# Set Environment # Set Environment
ENV DEBUG=Server,WireGuard,Database,CMD ENV DEBUG=Server,WireGuard
ENV PORT=51821
ENV HOST=0.0.0.0
ENV INSECURE=false
ENV INIT_ENABLED=false
LABEL org.opencontainers.image.source=https://github.com/wg-easy/wg-easy
# Run Web UI # Run Web UI
CMD ["/usr/bin/dumb-init", "node", "server/index.mjs"] WORKDIR /app
CMD ["/usr/bin/dumb-init", "node", "server.js"]

View File

@@ -1,39 +0,0 @@
FROM docker.io/library/node:lts-alpine
WORKDIR /app
# update corepack
RUN npm install --global corepack@latest
# Install pnpm
RUN corepack enable pnpm
HEALTHCHECK --interval=1m --timeout=5s --retries=3 CMD /usr/bin/timeout 5s /bin/sh -c "/usr/bin/wg show | /bin/grep -q interface || exit 1"
# Install Linux packages
RUN apk add --no-cache \
dpkg \
dumb-init \
iptables \
ip6tables \
kmod \
iptables-legacy \
wireguard-tools
# Use iptables-legacy
RUN update-alternatives --install /usr/sbin/iptables iptables /usr/sbin/iptables-legacy 10 --slave /usr/sbin/iptables-restore iptables-restore /usr/sbin/iptables-legacy-restore --slave /usr/sbin/iptables-save iptables-save /usr/sbin/iptables-legacy-save
RUN update-alternatives --install /usr/sbin/ip6tables ip6tables /usr/sbin/ip6tables-legacy 10 --slave /usr/sbin/ip6tables-restore ip6tables-restore /usr/sbin/ip6tables-legacy-restore --slave /usr/sbin/ip6tables-save ip6tables-save /usr/sbin/ip6tables-legacy-save
# Set Environment
ENV DEBUG=Server,WireGuard,Database,CMD
ENV PORT=51821
ENV HOST=0.0.0.0
ENV INSECURE=true
ENV INIT_ENABLED=false
# Install Dependencies
COPY src/package.json src/pnpm-lock.yaml ./
RUN pnpm install
# Copy Project
COPY src ./
ENTRYPOINT [ "pnpm", "run" ]

View File

@@ -0,0 +1,28 @@
# wg-password
`wg-password` (wgpw) is a script that generates bcrypt password hashes for use with `wg-easy`, enhancing security by requiring passwords.
## Features
- Generate bcrypt password hashes.
- Easily integrate with `wg-easy` to enforce password requirements.
## Usage with Docker
To generate a bcrypt password hash using docker, run the following command :
```sh
docker run ghcr.io/wg-easy/wg-easy wgpw YOUR_PASSWORD
PASSWORD_HASH='$2b$12$coPqCsPtcFO.Ab99xylBNOW4.Iu7OOA2/ZIboHN6/oyxca3MWo7fW' // literally YOUR_PASSWORD
```
*Important* : make sure to enclose your password in single quotes when you run `docker run` command :
```bash
$ echo $2b$12$coPqCsPtcF
b2
$ echo "$2b$12$coPqCsPtcF"
b2
$ echo '$2b$12$coPqCsPtcF'
$2b$12$coPqCsPtcF
```

1098
LICENSE

File diff suppressed because it is too large Load Diff

205
README.md
View File

@@ -1,75 +1,56 @@
# WireGuard Easy # WireGuard Easy
[![Build & Publish latest Image](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg?branch=production)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml) [![Build & Publish Docker Image to Docker Hub](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml/badge.svg?branch=production)](https://github.com/wg-easy/wg-easy/actions/workflows/deploy.yml)
[![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml) [![Lint](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/wg-easy/wg-easy/actions/workflows/lint.yml)
[![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/stargazers) ![Docker](https://img.shields.io/docker/pulls/weejewel/wg-easy.svg)
[![License](https://img.shields.io/github/license/wg-easy/wg-easy)](LICENSE) [![Sponsor](https://img.shields.io/github/sponsors/weejewel)](https://github.com/sponsors/WeeJeWel)
[![GitHub Release](https://img.shields.io/github/v/release/wg-easy/wg-easy)](https://github.com/wg-easy/wg-easy/releases/latest) ![GitHub Stars](https://img.shields.io/github/stars/wg-easy/wg-easy)
[![Image Pulls](https://img.shields.io/badge/image_pulls-12M+-blue)](https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy)
<!-- TODO: remove after release -->
> [!WARNING]
> You are viewing the README of the pre-release of v15.
> If you want to setup wg-easy right now. Read the README in the production branch here: [README](https://github.com/wg-easy/wg-easy/tree/production) or here for the last nightly: [README](https://github.com/wg-easy/wg-easy/tree/c6dce0f6fb2e28e7e40ddac1498bd67e9bb17cba)
You have found the easiest way to install & manage WireGuard on any Linux host! You have found the easiest way to install & manage WireGuard on any Linux host!
<!-- TOOD: update screenshot -->
<p align="center"> <p align="center">
<img src="./assets/screenshot.png" width="802" /> <img src="./assets/screenshot.png" width="802" />
</p> </p>
## Features ## Features
* All-in-one: WireGuard + Web UI.
* Easy installation, simple to use.
* List, create, edit, delete, enable & disable clients.
* Show a client's QR code.
* Download a client's configuration file.
* Statistics for which clients are connected.
* Tx/Rx charts for each connected client.
* Gravatar support.
* Automatic Light / Dark Mode
* Multilanguage Support
* UI_TRAFFIC_STATS (default off)
- All-in-one: WireGuard + Web UI. ## Requirements
- Easy installation, simple to use.
- List, create, edit, delete, enable & disable clients.
- Show a client's QR code.
- Download a client's configuration file.
- Statistics for which clients are connected.
- Tx/Rx charts for each connected client.
- Gravatar support.
- Automatic Light / Dark Mode
- Multilanguage Support
- One Time Links
- Client Expiration
- Prometheus metrics support
- IPv6 support
- CIDR support
- 2FA support
> [!NOTE] * A host with a kernel that supports WireGuard (all modern kernels).
> To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest) * A host with Docker installed.
<!-- TODO: remove after release --> ## Versions
> [!WARNING] We provide more then 1 docker image to get, this will help you decide which one is best for you. <br>
> As the Docs are still in Pre-release, you can access them here [https://wg-easy.github.io/wg-easy/Pre-release](https://wg-easy.github.io/wg-easy/Pre-release) For **stable** versions instead of nightly or development please read **README** from the **production** branch!
- [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/) | tag | Branch | Example | Description |
- [Basic Installation](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/basic-installation/) | - | - | - | - |
- [Caddy](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/caddy/) | `latest` | production | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | stable as possbile get bug fixes quickly when needed, deployed against `production`. |
- [Traefik](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/traefik/) | `13` | production | `ghcr.io/wg-easy/wg-easy:13` | same as latest, stick to a version tag. |
- [Podman](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/podman-nft/) | `nightly` | master | `ghcr.io/wg-easy/wg-easy:nightly` | mostly unstable gets frequent package and code updates, deployed against `master`. |
- [AdGuard Home](https://wg-easy.github.io/wg-easy/latest/examples/tutorials/adguard/) | `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs before landing into `master`. |
> [!NOTE]
> If you want to migrate from the old version to the new version, you can find the migration guide here: [Migration Guide](https://wg-easy.github.io/wg-easy/latest/advanced/migrate/)
## Installation ## Installation
This is a quick start guide to get you up and running with WireGuard Easy.
For a more detailed installation guide, please refer to the [Getting Started](https://wg-easy.github.io/wg-easy/latest/getting-started/) page.
### 1. Install Docker ### 1. Install Docker
If you haven't installed Docker yet, install it by running as root: If you haven't installed Docker yet, install it by running:
```shell ```bash
curl -sSL https://get.docker.com | sh curl -sSL https://get.docker.com | sh
sudo usermod -aG docker $(whoami)
exit exit
``` ```
@@ -77,60 +58,94 @@ And log in again.
### 2. Run WireGuard Easy ### 2. Run WireGuard Easy
The easiest way to run WireGuard Easy is with Docker Compose. To automatically install & run wg-easy, simply run:
Just download [`docker-compose.yml`](docker-compose.yml) and execute `sudo docker compose up -d`. ```
docker run -d \
Now setup a reverse proxy to be able to access the Web UI securely from the internet. --name=wg-easy \
-e LANG=de \
If you want to access the Web UI over HTTP, change the env var `INSECURE` to `true`. This is not recommended. Only use this for testing -e WG_HOST=<🚨YOUR_SERVER_IP> \
-e PASSWORD_HASH=<🚨YOUR_ADMIN_PASSWORD_HASH> \
## Donate -e PORT=51821 \
-e WG_PORT=51820 \
Are you enjoying this project? Consider donating. -v ~/.wg-easy:/etc/wireguard \
-p 51820:51820/udp \
Founder: [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻 -p 51821:51821/tcp \
--cap-add=NET_ADMIN \
Maintainer: [Buy kaaax0815 a coffee!](https://github.com/sponsors/kaaax0815) ☕ --cap-add=SYS_MODULE \
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \
## Development --sysctl="net.ipv4.ip_forward=1" \
--restart unless-stopped \
### Prerequisites ghcr.io/wg-easy/wg-easy
- Docker
- Node LTS & corepack enabled
- Visual Studio Code
### Dev Server
This starts the development server with docker
```shell
pnpm dev
``` ```
### Update Auto Imports > 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname.
>
> 💡 Replace `YOUR_ADMIN_PASSWORD_HASH` with a bcrypt password hash to log in on the Web UI. See [How_to_generate_an_bcrypt_hash.md](./How_to_generate_an_bcrypt_hash.md) for know how generate the hash.
If you add something that should be auto-importable and VSCode complains, run: The Web UI will now be available on `http://0.0.0.0:51821`.
```shell > 💡 Your configuration files will be saved in `~/.wg-easy`
cd src
pnpm install WireGuard Easy can be launched with Docker Compose as well - just download
cd .. [`docker-compose.yml`](docker-compose.yml), make necessary adjustments and
execute `docker compose up --detach`.
### 3. Sponsor
Are you enjoying this project? [Buy Emile a beer!](https://github.com/sponsors/WeeJeWel) 🍻
## Options
These options can be configured by setting environment variables using `-e KEY="VALUE"` in the `docker run` command.
| Env | Default | Example | Description |
| - | - | - |------------------------------------------------------------------------------------------------------------------------------------------------------|
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `WEBUI_HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `PASSWORD_HASH` | - | `$2y$05$Ci...` | When set, requires a password when logging in to the Web UI. See [How to generate an bcrypt hash.md]("https://github.com/wg-easy/wg-easy/blob/master/How_to_generate_an_bcrypt_hash.md") for know how generate the hash. |
| `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. |
| `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. |
| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will listen on that (othwise default) inside the Docker container. |
| `WG_CONFIG_PORT`| `51820` | `12345` | The UDP port used on [Home Assistant Plugin](https://github.com/adriy-be/homeassistant-addons-jdeath/tree/main/wgeasy)
| `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. |
| `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. |
| `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. |
| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. If set to blank value, clients will not use any DNS. |
| `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. |
| `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. |
| `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. |
| `WG_PRE_DOWN` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L27) for the default value. |
| `WG_POST_DOWN` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L28) for the default value. |
| `LANG` | `en` | `de` | Web UI language (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi). |
| `UI_TRAFFIC_STATS` | `false` | `true` | Enable detailed RX / TX client stats in Web UI |
| `UI_CHART_TYPE` | `0` | `1` | UI_CHART_TYPE=0 # Charts disabled, UI_CHART_TYPE=1 # Line chart, UI_CHART_TYPE=2 # Area chart, UI_CHART_TYPE=3 # Bar chart |
> If you change `WG_PORT`, make sure to also change the exposed port.
## Updating
To update to the latest version, simply run:
```bash
docker stop wg-easy
docker rm wg-easy
docker pull ghcr.io/wg-easy/wg-easy
``` ```
### Test Cli And then run the `docker run -d \ ...` command above again.
This starts the cli with docker With Docker Compose WireGuard Easy can be updated with a single command:
`docker compose up --detach --pull always` (if an image tag is specified in the
Compose file and it is not `latest`, make sure that it is changed to the desired
one; by default it is omitted and
[defaults to `latest`](https://docs.docker.com/engine/reference/run/#image-references)). \
The WireGuared Easy container will be automatically recreated if a newer image
was pulled.
```shell ## Common Use Cases
pnpm cli:dev
```
## License * [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole)
* [Using WireGuard-Easy with nginx/SSL](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-nginx-SSL)
This project is licensed under the AGPL-3.0-only License - see the [LICENSE](LICENSE) file for details For less common or specific edge-case scenarios, please refer to the detailed information provided in the [Wiki](https://github.com/wg-easy/wg-easy/wiki).
This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security
"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld

View File

@@ -1,44 +0,0 @@
volumes:
etc_wireguard:
services:
wg-easy:
#environment:
# Optional:
# - PORT=51821
# - HOST=0.0.0.0
# - INSECURE=false
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
# - NET_RAW # ⚠️ Uncomment if using Podman Compose
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64

View File

@@ -1,13 +1,10 @@
services: services:
wg-easy: wg-easy:
build: build:
dockerfile: ./Dockerfile.dev dockerfile: ./Dockerfile
command: dev command: npm run serve
volumes: volumes:
- ./src/:/app/ - ./src/:/app/
- temp:/app/.nuxt/
- temp1:/app/node_modules/
- /lib/modules:/lib/modules:ro
# - ./data/:/etc/wireguard # - ./data/:/etc/wireguard
ports: ports:
- "51820:51820/udp" - "51820:51820/udp"
@@ -16,15 +13,5 @@ services:
- NET_ADMIN - NET_ADMIN
- SYS_MODULE - SYS_MODULE
environment: environment:
- INIT_ENABLED=true # - PASSWORD_HASH=p
- INIT_HOST=test - WG_HOST=192.168.1.233
- INIT_PORT=51820
- INIT_USERNAME=testtest
- INIT_PASSWORD=Qweasdyxcv!2
# folders should be generated inside container
volumes:
temp:
driver: local
temp1:
driver: local

View File

@@ -6,13 +6,14 @@ services:
environment: environment:
# Change Language: # Change Language:
# (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi) # (Supports: en, ua, ru, tr, no, pl, fr, de, ca, es, ko, vi, nl, is, pt, chs, cht, it, th, hi)
- LANG=de # - LANG=de
# ⚠️ Required: # ⚠️ Required:
# Change this to your host's public address # Change this to your host's public address
- WG_HOST=raspberrypi.local - WG_HOST=192.168.11.3
# Optional: # Optional:
# - PASSWORD_HASH=$$2y$$10$$hBCoykrB95WSzuV4fafBzOHWKu9sbyVa34GJr8VV5R/pIelfEMYyG # (needs double $$, hash of 'foobar123'; see "How_to_generate_an_bcrypt_hash.md" for generate the hash) - PASSWORD_HASH=$$2a$$12$$ZwvA8aZH/3JCrRr448G2rOy0Tb8xHHfV4wjN6Q33eWVACKdfMAgRq
# (needs double $$, see "How_to_generate_an_bcrypt_hash.md" for generate the hash)
# - PORT=51821 # - PORT=51821
# - WG_PORT=51820 # - WG_PORT=51820
# - WG_CONFIG_PORT=92820 # - WG_CONFIG_PORT=92820
@@ -29,7 +30,7 @@ services:
# - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart) # - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart)
image: ghcr.io/wg-easy/wg-easy:14 image: ghcr.io/wg-easy/wg-easy:14
container_name: wg-easy container_name: wireguard
volumes: volumes:
- etc_wireguard:/etc/wireguard - etc_wireguard:/etc/wireguard
ports: ports:

View File

@@ -1,5 +0,0 @@
{
"tabWidth": 4,
"semi": true,
"singleQuote": true
}

16
docs/changelog.json Normal file
View File

@@ -0,0 +1,16 @@
{
"1": "Initial version. Enjoy!",
"2": "You can now rename a client & update the address. Enjoy!",
"3": "Many improvements and small changes. Enjoy!",
"4": "Now with pretty charts for client's network speed. Enjoy!",
"5": "Many small improvements & feature requests. Enjoy!",
"6": "Many small performance improvements & bug fixes. Enjoy!",
"7": "Improved the look & performance of the upload/download chart.",
"8": "Updated to Node.js v18.",
"9": "Fixed issue running on devices with older kernels.",
"10": "Added sessionless HTTP API auth & automatic dark mode.",
"11": "Multilanguage Support & various bugfixes.",
"12": "UI_TRAFFIC_STATS, Import json configurations with no PreShared-Key, allow clients with no privateKey & more.",
"13": "New framework (h3), UI_CHART_TYPE, some bugfixes & more.",
"14": "Home Assistent support, PASSWORD_HASH (inc. Helper), translation updates bugfixes & more."
}

View File

@@ -1,43 +0,0 @@
---
title: API
---
/// warning | Breaking Changes
This API is not yet stable and may change in the future. The API is currently in development and is subject to change without notice. The API is not yet documented, but we will add documentation as the API stabilizes.
///
You can use the API to interact with the application programmatically. The API is available at `/api` and supports both GET and POST requests. The API is designed to be simple and easy to use, with a focus on providing a consistent interface for all endpoints.
There is no documentation for the API yet, but this will be added as the underlying library supports it.
## Authentication
To use the API, you need to authenticate using Basic Authentication. The username and password are the same as the ones you use to log in to the web application.
If you use 2FA, the API will not work. You need to disable 2FA in the web application to use the API.
### Authentication Example
```python
import requests
from requests.auth import HTTPBasicAuth
url = "https://example.com:51821/api/client"
response = requests.get(url, auth=HTTPBasicAuth('username', 'password'))
if response.status_code == 200:
data = response.json()
print(data)
else:
print(f"Error: {response.status_code}")
```
## Endpoints
The Endpoints are not yet documented. But as file-based routing is used, you can find the endpoints in the `src/server/api` folder. The method is defined in the file name.
### Endpoints Example
| File Name | Endpoint | Method |
| -------------------------------- | -------------- | ------ |
| `src/server/api/client.get.ts` | `/api/client` | GET |
| `src/server/api/setup/2.post.ts` | `/api/setup/2` | POST |

View File

@@ -1,11 +0,0 @@
---
title: Optional Configuration
---
You can set these environment variables to configure the container. They are not required, but can be useful in some cases.
| Env | Default | Example | Description |
| ---------- | --------- | ----------- | ------------------------------ |
| `PORT` | `51821` | `6789` | TCP port for Web UI. |
| `HOST` | `0.0.0.0` | `localhost` | IP address web UI binds to. |
| `INSECURE` | `false` | `true` | If access over http is allowed |

View File

@@ -1,32 +0,0 @@
---
title: Unattended Setup
---
If you want to run the setup without any user interaction, e.g. with a tool like Ansible, you can use these environment variables to configure the setup.
These will only be used during the first start of the container. After that, the setup will be disabled.
| Env | Example | Description | Group |
| ---------------- | ----------------- | --------------------------------------------------------- | ----- |
| `INIT_ENABLED` | `true` | Enables the below env vars | 0 |
| `INIT_USERNAME` | `admin` | Sets admin username | 1 |
| `INIT_PASSWORD` | `Se!ureP%ssw` | Sets admin password | 1 |
| `INIT_HOST` | `vpn.example.com` | Host clients will connect to | 1 |
| `INIT_PORT` | `51820` | Port clients will connect to and wireguard will listen on | 1 |
| `INIT_DNS` | `1.1.1.1,8.8.8.8` | Sets global dns setting | 2 |
| `INIT_IPV4_CIDR` | `10.8.0.0/24` | Sets IPv4 cidr | 3 |
| `INIT_IPV6_CIDR` | `2001:0DB8::/32` | Sets IPv6 cidr | 3 |
/// warning | Variables have to be used together
If variables are in the same group, you have to set all of them. For example, if you set `INIT_IPV4_CIDR`, you also have to set `INIT_IPV6_CIDR`.
If you want to skip the setup process, you have to configure group `1`
///
/// note | Security
The initial username and password is not checked for complexity. Make sure to set a long enough username and password. Otherwise, the user won't be able to log in.
It's recommended to remove the variables after the setup is done to prevent the password from being exposed.
///

View File

@@ -1,42 +0,0 @@
---
title: Prometheus
---
To monitor the WireGuard server, you can use [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/). The container exposes a `/metrics/prometheus` endpoint that can be scraped by Prometheus.
## Enable Prometheus
To enable Prometheus metrics, go to Admin Panel > General and enable Prometheus.
You can optionally set a Bearer Password for the metrics endpoints. This is useful if you want to expose the metrics endpoint to the internet.
## Configure Prometheus
You need to add a scrape config to your Prometheus configuration file. Here is an example:
```yaml
scrape_configs:
- job_name: 'wg-easy'
scrape_interval: 30s
metrics_path: /metrics/prometheus
static_configs:
- targets:
- 'localhost:51821'
authorization:
type: Bearer
credentials: 'SuperSecurePassword'
```
## Grafana Dashboard
You can use the following Grafana dashboard to visualize the metrics:
[![Grafana Dashboard](https://grafana.com/api/dashboards/21733/images/16863/image)](https://grafana.com/grafana/dashboards/21733-wireguard/)
[21733](https://grafana.com/grafana/dashboards/21733-wireguard/)
/// note | Unofficial
The Grafana dashboard is not official and is not maintained by the `wg-easy` team. If you have any issues with the dashboard, please contact the author of the dashboard.
See [#1299](https://github.com/wg-easy/wg-easy/pull/1299) for more information.
///

View File

@@ -1,52 +0,0 @@
---
title: Migrate from v14 to v15
---
This guide will help you migrate from `v14` to version `v15` of `wg-easy`.
## Changes
- This is a complete rewrite of the `wg-easy` project. Therefore the configuration files and the way you interact with the project have changed.
- If you use armv6 or armv7, you can't migrate to `v15` yet. We are working on it.
- If you are connecting to the web ui via HTTP, you need to set the `INSECURE` environment variable to `true` in the new container.
## Migration
### Backup
Before you start the migration, make sure to backup your existing configuration files.
Go into the Web Ui and click the Backup button, this should download a `wg0.json` file.
Or download the `wg0.json` file from your container volume to your pc.
You will need this file for the migration
### Remove old container
1. Stop the running container
If you are using `docker run`
```shell
docker stop wg-easy
```
If you are using `docker-compose`
```shell
docker-compose down
```
### Start new container
Follow the instructions in the [Getting Started][docs-getting-started] or [Basic Installation][docs-examples] guide to start the new container.
In the setup wizard, select that you already already have a configuration file and upload the `wg0.json` file you downloaded in the backup step.
[docs-getting-started]: ../../getting-started.md
[docs-examples]: ../../examples/tutorials/basic-installation.md
### Done
You have now successfully migrated to `v15` of `wg-easy`.

View File

@@ -1,7 +0,0 @@
---
title: Migrate
---
If you want to migrate from an older version of `wg-easy` to the new version, you can find the migration guides listed below.
- [Migrate from v14 to v15](./from-14-to-15.md) : This guide should also work for any version before `v14`.

View File

@@ -1,23 +0,0 @@
---
title: General Information
---
## Coding Style
When refactoring, writing or altering files, adhere to these rules:
1. **Adjust your style of coding to the style that is already present**! Even if you do not like it, this is due to consistency. There was a lot of work involved in making all files consistent.
2. **Use `pnpm lint` to check your scripts**! Your contributions are checked by GitHub Actions too, so you will need to do this.
3. **Use the provided `.vscode/settings.json`** file.
## Documentation
Make sure to select `edge` in the dropdown menu at the top. Navigate to the page you would like to edit and click the edit button in the top right. This allows you to make changes and create a pull-request.
Alternatively you can make the changes locally. For that you'll need to have Docker installed. Run
```sh
pnpm docs:serve
```
This serves the documentation on your local machine on port `8080`. Each change will be hot-reloaded onto the page you view, just edit, save and look at the result.

View File

@@ -1,58 +0,0 @@
---
title: Issues and Pull Requests
---
This project is Open Source. That means that you can contribute on enhancements, bug fixing or improving the documentation.
## Opening an Issue
/// note | Attention
**Before opening an issue**, read the [`README`][github-file-readme] carefully, study the docs for your version (maybe [latest][docs-latest]) and your search engine you trust. The issue tracker is not meant to be used for unrelated questions!
///
When opening an issue, please provide details use case to let the community reproduce your problem.
/// note | Attention
**Use the issue templates** to provide the necessary information. Issues which do not use these templates are not worked on and closed.
///
By raising issues, I agree to these terms and I understand, that the rules set for the issue tracker will help both maintainers as well as everyone to find a solution.
Maintainers take the time to improve on this project and help by solving issues together. It is therefore expected from others to make an effort and **comply with the rules**.
### Filing a Bug Report
Thank you for participating in this project and reporting a bug. `wg-easy` is a community-driven project, and each contribution counts!
Maintainers and moderators are volunteers. We greatly appreciate reports that take the time to provide detailed information via the template, enabling us to help you in the best and quickest way. Ignoring the template provided may seem easier, but discourages receiving any support.
Markdown formatting can be used in almost all text fields (_unless stated otherwise in the description_).
Be as precise as possible, and if in doubt, it's best to add more information that too few.
When an option is marked with "not officially supported" / "unsupported", then support is dependent on availability from specific maintainers.
## Pull Requests
/// question | Motivation
You want to add a feature? Feel free to start creating an issue explaining what you want to do and how you're thinking doing it. Other users may have the same need and collaboration may lead to better results.
///
### Submit a Pull-Request
The development workflow is the following:
1. Fork the project
2. Write the code that is needed :D
3. Document your improvements if necessary
4. [Commit][commit] (and [sign your commit][gpg]), push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist.
Pull requests are automatically tested against the CI and will be reviewed when tests pass. When your changes are validated, your branch is merged. CI builds the new `:edge` image on every push to the `master` branch and your changes will be included in the next version release.
[docs-latest]: https://wg-easy.github.io/wg-easy/latest
[github-file-readme]: https://github.com/wg-easy/wg-easy/blob/master/README.md
[commit]: https://help.github.com/articles/closing-issues-via-commit-messages/
[gpg]: https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key

View File

@@ -1,9 +0,0 @@
---
title: AdGuard Home
---
It seems like the Docs on how to setup AdGuard Home are not available yet.
Feel free to create a PR and add them here.
<!-- TODO -->

View File

@@ -1,72 +0,0 @@
---
title: Auto Updates
---
## Docker Compose
With Docker Compose `wg-easy` can be updated with a single command:
```shell
cd /etc/docker/containers/wg-easy
sudo docker compose up -d --pull always
```
### Watchtower
If you want the updates to be fully automatic you can install Watchtower. This will check for updates every day at 4:00 AM and update the container if a new version is available.
File: `/etc/docker/containers/watchtower/docker-compose.yml`
```yaml
services:
watchtower:
image: containrrr/watchtower:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock
env_file:
- watchtower.env
restart: unless-stopped
```
File: `/etc/docker/containers/watchtower/watchtower.env`
```env
WATCHTOWER_CLEANUP=true
WATCHTOWER_SCHEDULE=0 0 4 * * *
TZ=Europe/Berlin
# Email
# WATCHTOWER_NOTIFICATIONS_LEVEL=info
# WATCHTOWER_NOTIFICATIONS=email
# WATCHTOWER_NOTIFICATION_EMAIL_FROM=mail@example.com
# WATCHTOWER_NOTIFICATION_EMAIL_TO=mail@example.com
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER=smtp.example.com
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_USER=mail@example.com
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PASSWORD="SuperSecurePassword"
# WATCHTOWER_NOTIFICATION_EMAIL_SERVER_PORT=587
```
```shell
cd /etc/docker/containers/watchtower
sudo docker compose up -d
```
## Docker Run
```shell
sudo docker stop wg-easy
sudo docker rm wg-easy
sudo docker pull ghcr.io/wg-easy/wg-easy
```
And then run the `docker run -d \ ...` command from [Docker Run][docker-run] again.
[docker-run]: ./docker-run.md
## Podman
To update `wg-easy` (and every container that has auto updates enabled), you can run the following command:
```shell
sudo podman auto-update
```

View File

@@ -1,67 +0,0 @@
---
title: Basic Installation
---
<!-- TOOD: add docs for pihole, nginx, caddy, traefik -->
## Requirements
1. You need to have a host that you can manage
2. You need to have a domain name or a public IP address
3. You need a supported architecture (x86_64, arm64)
4. You need curl installed on your host
## Install Docker
Follow the Docs here: <https://docs.docker.com/engine/install/> and install Docker on your host.
## Install `wg-easy`
1. Create a directory for the configuration files (you can choose any directory you like):
```shell
sudo mkdir -p /etc/docker/containers/wg-easy
```
2. Download docker compose file
```shell
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml
```
3. Start `wg-easy`
```shell
cd /etc/docker/containers/wg-easy
sudo docker-compose up -d
```
## Setup Firewall
If you are using a firewall, you need to open the following ports:
- UDP 51820 (WireGuard)
These ports can be changed, so if you change them you have to update your firewall rules accordingly.
## Setup Reverse Proxy
- To setup traefik follow the instructions here: [Traefik](./traefik.md)
- To setup caddy follow the instructions here: [Caddy](./caddy.md)
- If you do not want to use a reverse proxy follow the instructions here: [No Reverse Proxy](./reverse-proxyless.md)
## Update `wg-easy`
To update `wg-easy` to the latest version, run:
```shell
cd /etc/docker/containers/wg-easy
sudo docker-compose pull
sudo docker-compose up -d
```
## Auto Update
If you want to enable auto-updates, follow the instructions here: [Auto Updates][auto-updates]
[auto-updates]: ./auto-updates.md

View File

@@ -1,9 +0,0 @@
---
title: Caddy
---
It seems like the Docs on how to setup Caddy are not available yet.
Feel free to create a PR and add them here.
<!-- TODO -->

View File

@@ -1,41 +0,0 @@
---
title: Docker Run
---
To setup the IPv6 Network, simply run once:
```shell
docker network create \
-d bridge --ipv6 \
-d default \
--subnet 10.42.42.0/24 \
--subnet fdcc:ad94:bacf:61a3::/64 wg \
```
<!-- ref: major version -->
To automatically install & run `wg-easy`, simply run:
```shell
docker run -d \
--net wg \
-e INSECURE=true \
--name wg-easy \
--ip6 fdcc:ad94:bacf:61a3::2a \
--ip 10.42.42.42 \
-v ~/.wg-easy:/etc/wireguard \
-v /lib/modules:/lib/modules:ro \
-p 51820:51820/udp \
-p 51821:51821/tcp \
--cap-add NET_ADMIN \
--cap-add SYS_MODULE \
--sysctl net.ipv4.ip_forward=1 \
--sysctl net.ipv4.conf.all.src_valid_mark=1 \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
--sysctl net.ipv6.conf.all.forwarding=1 \
--sysctl net.ipv6.conf.default.forwarding=1 \
--restart unless-stopped \
ghcr.io/wg-easy/wg-easy:15
```
The Web UI will now be available at <http://0.0.0.0:51821>.

View File

@@ -1,7 +0,0 @@
---
title: Without Docker
---
This is currently not yet supported.
<!-- TODO -->

View File

@@ -1,108 +0,0 @@
---
title: Podman + nftables
---
This guide will show you how to run `wg-easy` with rootful Podman and nftables.
## Requirements
1. Podman installed with version 4.4 or higher
## Configuration
Create a Folder for the configuration files:
```shell
sudo mkdir -p /etc/containers/systemd/wg-easy
sudo mkdir -p /etc/containers/volumes/wg-easy
```
Create a file `/etc/containers/systemd/wg-easy/wg-easy.container` with the following content:
<!-- ref: major version -->
```ini
[Container]
ContainerName=wg-easy
Image=ghcr.io/wg-easy/wg-easy:15
AutoUpdate=registry
Volume=/etc/containers/volumes/wg-easy:/etc/wireguard:Z
Network=wg-easy.network
PublishPort=51820:51820/udp
PublishPort=51821:51821/tcp
# this is used to allow access over HTTP
# remove this when using a reverse proxy
Environment=INSECURE=true
AddCapability=NET_ADMIN
AddCapability=SYS_MODULE
AddCapability=NET_RAW
Sysctl=net.ipv4.ip_forward=1
Sysctl=net.ipv4.conf.all.src_valid_mark=1
Sysctl=net.ipv6.conf.all.disable_ipv6=0
Sysctl=net.ipv6.conf.all.forwarding=1
Sysctl=net.ipv6.conf.default.forwarding=1
[Install]
# this is used to start the container on boot
WantedBy=default.target
```
Create a file `/etc/containers/systemd/wg-easy/wg-easy.network` with the following content:
```ini
[Network]
NetworkName=wg-easy
IPv6=true
```
## Load Kernel Modules
You will need to load the following kernel modules
```txt
wireguard
nft_masq
```
Create a file `/etc/modules-load.d/wg-easy.conf` with the following content:
```txt
wireguard
nft_masq
```
## Start the Container
```shell
sudo systemctl daemon-reload
sudo systemctl start wg-easy
```
## Edit Hooks
In the Admin Panel of your WireGuard server, go to the `Hooks` tab and add the following hook:
1. PostUp
```shell
nft add table inet wg_table; nft add chain inet wg_table prerouting { type nat hook prerouting priority 100 \; }; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy accept \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add rule inet wg_table input tcp dport {{uiPort}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy accept \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept;
```
2. PostDown
```shell
nft delete table inet wg_table
```
If you don't have iptables loaded on your server, you could see many errors in the logs or in the UI. You can ignore them.
## Restart the Container
Restart the container to apply the new hooks:
```shell
sudo systemctl restart wg-easy
```

View File

@@ -1,29 +0,0 @@
---
title: No Reverse Proxy
---
/// warning | Insecure
This is insecure. You should use a reverse proxy to secure the connection.
Only use this method if you know what you are doing.
///
If you only allow access to the web UI from your local network, you can skip the reverse proxy setup. This is not recommended, but it is possible.
## Setup
- Edit the `docker-compose.yml` file and uncomment `environment` and `INSECURE`
- Set `INSECURE` to `true` to allow access to the web UI over a non-secure connection.
- The `docker-compose.yml` file should look something like this:
```yaml
environment:
- INSECURE=true
```
- Save the file and restart `wg-easy`.
- Make sure that the Web UI is not accessible from outside your local network.

View File

@@ -1,184 +0,0 @@
---
title: Traefik
---
/// note | Opinionated
This guide is opinionated. If you use other conventions or folder layouts, feel free to change the commands and paths.
///
## Create docker compose project
```shell
sudo mkdir -p /etc/docker/containers/traefik
cd /etc/docker/containers/traefik
```
## Create docker compose file
File: `/etc/docker/containers/traefik/docker-compose.yml`
```yaml
services:
traefik:
image: traefik:3.3
container_name: traefik
restart: unless-stopped
ports:
- '80:80'
- '443:443/tcp'
- '443:443/udp'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/docker/volumes/traefik/traefik.yml:/traefik.yml:ro
- /etc/docker/volumes/traefik/traefik_dynamic.yml:/traefik_dynamic.yml:ro
- /etc/docker/volumes/traefik/acme.json:/acme.json
networks:
- traefik
networks:
traefik:
external: true
```
## Create traefik.yml
File: `/etc/docker/volumes/traefik/traefik.yml`
```yaml
log:
level: INFO
entryPoints:
web:
address: ':80/tcp'
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ':443/tcp'
http:
middlewares:
- compress@file
- hsts@file
tls:
certResolver: letsencrypt
http3: {}
api:
dashboard: true
certificatesResolvers:
letsencrypt:
acme:
email: $mail@example.com$
storage: acme.json
httpChallenge:
entryPoint: web
providers:
docker:
watch: true
network: traefik
exposedByDefault: false
file:
filename: traefik_dynamic.yml
serversTransport:
insecureSkipVerify: true
```
## Create traefik_dynamic.yml
File: `/etc/docker/volumes/traefik/traefik_dynamic.yml`
```yaml
http:
middlewares:
services:
basicAuth:
users:
- '$username$:$password$'
compress:
compress: {}
hsts:
headers:
stsSeconds: 2592000
routers:
api:
rule: Host(`traefik.$example.com$`)
entrypoints:
- websecure
middlewares:
- services
service: api@internal
tls:
options:
default:
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
sniStrict: true
```
## Create acme.json
```shell
sudo touch /etc/docker/volumes/traefik/acme.json
sudo chmod 600 /etc/docker/volumes/traefik/acme.json
```
## Create network
```shell
sudo docker network create traefik
```
## Start traefik
```shell
sudo docker-compose up -d
```
You can no access the Traefik dashboard at `https://traefik.$example.com$` with the credentials you set in `traefik_dynamic.yml`.
## Add Labels to `wg-easy`
To add labels to your `wg-easy` service, you can add the following to your `docker-compose.yml` file:
File: `/etc/docker/containers/wg-easy/docker-compose.yml`
```yaml
services:
wg-easy:
...
container_name: wg-easy
networks:
...
traefik: {}
labels:
- "traefik.enable=true"
- "traefik.http.routers.wg-easy.rule=Host(`wg-easy.$example.com$`)"
- "traefik.http.routers.wg-easy.entrypoints=websecure"
- "traefik.http.routers.wg-easy.service=wg-easy"
- "traefik.http.services.wg-easy.loadbalancer.server.port=51821"
...
networks:
...
traefik:
external: true
```
## Restart `wg-easy`
```shell
cd /etc/docker/containers/wg-easy
sudo docker-compose up -d
```
You can now access `wg-easy` at `https://wg-easy.$example.com$` and start the setup.

View File

@@ -1,97 +0,0 @@
---
title: FAQ
hide:
- navigation
---
Here are some frequently asked questions or errors about `wg-easy`. If you have a question that is not answered here, please feel free to open a discussion on GitHub.
## Error: WireGuard exited with the error: Cannot find device "wg0"
This error indicates that the WireGuard interface `wg0` does not exist. This can happen if the WireGuard kernel module is not loaded or if the interface was not created properly.
To resolve this issue, you can try the following steps:
1. **Load the WireGuard kernel module**: If the WireGuard kernel module is not loaded, you can load it manually by running:
```shell
sudo modprobe wireguard
```
2. **Load the WireGuard kernel module on boot**: If you want to ensure that the WireGuard kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
```shell
echo "wireguard" | sudo tee -a /etc/modules
```
## can't initialize iptables table `nat': Table does not exist (do you need to insmod?)
This error indicates that the `nat` table in `iptables` does not exist. This can happen if the `iptables` kernel module is not loaded or if the `nat` table is not supported by your kernel.
To resolve this issue, you can try the following steps:
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
```shell
sudo modprobe iptable_nat
```
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
```shell
echo "iptable_nat" | sudo tee -a /etc/modules
```
## can't initialize ip6tables table `nat': Table does not exist (do you need to insmod?)
This error indicates that the `nat` table in `ip6tables` does not exist. This can happen if the `ip6tables` kernel module is not loaded or if the `nat` table is not supported by your kernel.
To resolve this issue, you can try the following steps:
1. **Load the `nat` kernel module**: If the `nat` kernel module is not loaded, you can load it manually by running:
```shell
sudo modprobe ip6table_nat
```
2. **Load the `nat` kernel module on boot**: If you want to ensure that the `nat` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
```shell
echo "ip6table_nat" | sudo tee -a /etc/modules
```
## can't initialize iptables table `filter': Permission denied
This error indicates that the `filter` table in `iptables` cannot be initialized due to permission issues. This can happen if you are not running the command with sufficient privileges.
To resolve this issue, you can try the following steps:
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
```shell
sudo modprobe iptable_filter
```
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
```shell
echo "iptable_filter" | sudo tee -a /etc/modules
```
## can't initialize ip6tables table `filter': Permission denied
This error indicates that the `filter` table in `ip6tables` cannot be initialized due to permission issues. This can happen if you are not running the command with sufficient privileges.
To resolve this issue, you can try the following steps:
1. **Load the `filter` kernel module**: If the `filter` kernel module is not loaded, you can load it manually by running:
```shell
sudo modprobe ip6table_filter
```
2. **Load the `filter` kernel module on boot**: If you want to ensure that the `filter` kernel module is loaded automatically on boot, you can add it to the `/etc/modules` file:
```shell
echo "ip6table_filter" | sudo tee -a /etc/modules
```

View File

@@ -1,72 +0,0 @@
---
title: Getting Started
hide:
- navigation
---
This page explains how to get started with `wg-easy`. The guide uses Docker Compose as a reference. In our examples, we mount the named volume `etc_wireguard` to `/etc/wireguard` inside the container.
## Preliminary Steps
Before you can get started with deploying your own VPN, there are some requirements to be met:
1. You need to have a host that you can manage
2. You need to have a domain name or a public IP address
3. You need a supported architecture (x86_64, arm64)
### Host Setup
There are a few requirements for a suitable host system:
1. You need to have a container runtime installed
/// note | About the Container Runtime
On the host, you need to have a suitable container runtime (like _Docker_ or _Podman_) installed. We assume [_Docker Compose_][docker-compose] is [installed][docker-compose-installation]. We have aligned file names and configuration conventions with the latest [Docker Compose specification][docker-compose-specification].
If you're using podman, make sure to read the related [documentation][docs-podman].
///
[docker-compose]: https://docs.docker.com/compose/
[docker-compose-installation]: https://docs.docker.com/compose/install/
[docker-compose-specification]: https://docs.docker.com/compose/compose-file/
[docs-podman]: ./examples/tutorials/podman-nft.md
## Deploying the Actual Image
### Tagging Convention
To understand which tags you should use, read this section carefully. [Our CI][github-ci] will automatically build, test and push new images to the following container registry:
1. GitHub Container Registry ([`ghcr.io/wg-easy/wg-easy`][ghcr-image])
All workflows are using the tagging convention listed below. It is subsequently applied to all images.
| tag | Type | Example | Description |
| ------------- | ------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `15` | latest minor for that major tag | `ghcr.io/wg-easy/wg-easy:15` | latest features for specific major versions, no breaking changes, recommended |
| `latest` | latest tag | `ghcr.io/wg-easy/wg-easy:latest` or `ghcr.io/wg-easy/wg-easy` | points to latest release, can include breaking changes |
| `15.0` | latest patch for that minor tag | `ghcr.io/wg-easy/wg-easy:15.0` | latest patches for specific minor version |
| `15.0.0` | specific tag | `ghcr.io/wg-easy/wg-easy:15.0.0` | specific release, no updates |
| `edge` | push to `master` | `ghcr.io/wg-easy/wg-easy:edge` | mostly unstable, gets frequent package and code updates |
| `development` | pull requests | `ghcr.io/wg-easy/wg-easy:development` | used for development, testing code from PRs |
<!-- ref: major version -->
When publishing a tag we follow the [Semantic Versioning][semver] specification. The `latest` tag is always pointing to the latest stable release. If you want to avoid breaking changes, use the major version tag (e.g. `15`).
[github-ci]: https://github.com/wg-easy/wg-easy/actions
[ghcr-image]: https://github.com/wg-easy/wg-easy/pkgs/container/wg-easy
[semver]: https://semver.org/
### Follow tutorials
- [Basic Installation with Docker Compose (Recommended)](./examples/tutorials/basic-installation.md)
- [Simple Installation with Docker Run](./examples/tutorials/docker-run.md)
- [Advanced Installation with Podman](./examples/tutorials/podman-nft.md)
/// danger | Use the Correct Commands For Stopping and Starting `wg-easy`
**Use `sudo docker compose up / down`, not `sudo docker compose start / stop`**. Otherwise, the container is not properly destroyed and you may experience problems during startup because of inconsistent state.
///
**That's it! It really is that easy**.

View File

@@ -1,26 +0,0 @@
---
title: 2FA
---
The user can enable 2FA from the Account page. The Account page is accessible from the dropdown menu in the top right corner of the application.
## Enable TOTP
- **Enable Two Factor Authentication**: Enable TOTP for the user.
## Configure TOTP
A QR code will be displayed. Scan the QR code with your TOTP application (e.g., Google Authenticator, Authy, etc.) to add the account.
To verify that the TOTP key is working, the user must enter the TOTP code generated by the TOTP application.
- **TOTP Key**: The TOTP key for the user. This key is used to generate the TOTP code.
- **TOTP Code**: The current TOTP code for the user. This code is used to verify the TOTP key.
- **Enable Two Factor Authentication**: Enable TOTP for the user.
## Disable TOTP
To disable TOTP, the user must enter the current password.
- **Current Password**: The current password of the user.
- **Disable Two Factor Authentication**: Disable TOTP for the user.

View File

@@ -1,5 +0,0 @@
---
title: Admin Panel
---
TODO

View File

@@ -1,43 +0,0 @@
---
title: CLI
---
If you want to use the CLI, you can run it with
### Docker Compose
```shell
cd /etc/docker/containers/wg-easy
docker compose exec -it wg-easy cli
```
### Docker Run
```shell
docker run --rm -it \
-v ~/.wg-easy:/etc/wireguard \
ghcr.io/wg-easy/wg-easy:15 \
cli
```
### Reset Password
If you want to reset the password for the admin user, you can run the following command:
#### By Prompt
```shell
cd /etc/docker/containers/wg-easy
docker compose exec -it wg-easy cli db:admin:reset
```
You are asked to provide the new password
#### By Argument
```shell
cd /etc/docker/containers/wg-easy
docker compose exec -it wg-easy cli db:admin:reset --password <new_password>
```
This will reset the password for the admin user to the new password you provided. If you include special characters in the password, make sure to escape them properly.

View File

@@ -1,50 +0,0 @@
---
title: Edit Client
---
## General
- **Name**: The name of the client.
- **Enabled**: Whether the client can connect to the VPN.
- **Expire Date**: The date the client will be disabled.
## Address
- **IPv4**: The IPv4 address of the client.
- **IPv6**: The IPv6 address of the client.
## Allowed IPs
Which IPs will be routed through the VPN.
This will not prevent the user from modifying it locally and accessing IP ranges that they should not be able to access.
Use firewall rules to prevent access to IP ranges that the user should not be able to access.
## Server Allowed IPs
Which IPs will be routed to the client.
## DNS
The DNS server that the client will use.
## Advanced
- **MTU**: The maximum transmission unit for the client.
- **Persistent Keepalive**: The interval for sending keepalive packets to the server.
## Hooks
This can only be used for clients that use `wg-quick`. Setting this will throw a error when importing the config on other clients.
- **PreUp**: Commands to run before the interface is brought up.
- **PostUp**: Commands to run after the interface is brought up.
- **PreDown**: Commands to run before the interface is brought down.
- **PostDown**: Commands to run after the interface is brought down.
## Actions
- **Save**: Save the changes made in the form.
- **Revert**: Revert the changes made in the form.
- **Delete**: Delete the client.

View File

@@ -1,24 +0,0 @@
---
title: Setup
---
## User Setup
- **Username**: The username of the user.
- **Password**: The password of the user.
- **Confirm Password**: The password of the user.
## Existing Setup
If you have the config from the previous version, you can import it by clicking "Yes". This currently expects a config from v14.
If this is the first time you are using this, you can click "No" to create a new config.
### No - Host Setup
- **Host**: The host of the server. The clients will connect to this address. This can be a domain name or an IP address. Make sure to wrap it in brackets if it is an IPv6 address. For example: `[::1]` or `[2001:db8::1]`.
- **Port**: The port of the server. The clients will connect to this port. The server will listen on this port.
### Yes - Migration
Select the `wg0.json` file from the previous version. Read [Migrate from v14 to v15](../advanced/migrate/from-14-to-15.md) for more information.

View File

@@ -1,41 +0,0 @@
---
title: Home
hide:
- navigation
---
# Welcome to the Documentation for `wg-easy`
/// info | This Documentation is Versioned
**Make sure** to select the correct version of this documentation! It should match the version of the image you are using. The default version corresponds to the `:latest` image tag - [the most recent stable release][docs-tagging].
///
This documentation provides you not only with the basic setup and configuration of `wg-easy` but also with advanced configuration, elaborate usage scenarios, detailed examples, hints and more.
[docs-tagging]: ./getting-started.md#tagging-convention
## About
`wg-easy` is the easiest way to run WireGuard VPN + Web-based Admin UI.
## Contents
### Getting Started
If you're new to wg-easy, make sure to read the [_Getting Started_ chapter][docs-getting-started] first. If you want to look at examples for Docker Run and Compose, we have an [_Examples_ page][docs-examples].
[docs-getting-started]: ./getting-started.md
[docs-examples]: ./examples/tutorials/basic-installation.md
### Contributing
We are always happy to welcome new contributors. For guidelines and entrypoints please have a look at the [Contributing section][docs-contributing].
[docs-contributing]: ./contributing/issues-and-pull-requests.md
### Migration
If you are migrating from an older version of `wg-easy`, please read the [_Migration_ chapter][docs-migration].
[docs-migration]: ./advanced/migrate/from-14-to-15.md

View File

@@ -1,87 +0,0 @@
site_name: 'wg-easy'
site_description: 'The easiest way to run WireGuard VPN + Web-based Admin UI.'
site_author: 'WireGuard Easy'
copyright: >
<p>
&copy <a href="https://github.com/wg-easy"><em>Wireguard Easy</em></a><br/>
<span>This project is licensed under AGPL-3.0-only.</span><br/>
<span>This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with Jason A. Donenfeld, ZX2C4 or Edge Security</span><br/>
<span>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</span>
</p>
repo_url: https://github.com/wg-easy/wg-easy
repo_name: wg-easy
edit_uri: 'edit/master/docs/content'
docs_dir: 'content/'
site_url: https://wg-easy.github.io/wg-easy
theme:
name: material
favicon: assets/logo/favicon.png
logo: assets/logo/logo.png
icon:
repo: fontawesome/brands/github
features:
- navigation.tabs
- navigation.top
- navigation.expand
- navigation.instant
- content.action.edit
- content.action.view
- content.code.annotate
palette:
# Light mode
- media: '(prefers-color-scheme: light)'
scheme: default
primary: grey
accent: red
toggle:
icon: material/weather-night
name: Switch to dark mode
# Dark mode
- media: '(prefers-color-scheme: dark)'
scheme: slate
primary: grey
accent: red
toggle:
icon: material/weather-sunny
name: Switch to light mode
extra:
version:
provider: mike
markdown_extensions:
- toc:
anchorlink: true
- abbr
- attr_list
- pymdownx.blocks.admonition:
types:
- danger
- note
- info
- question
- warning
- pymdownx.details
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
slugify: !!python/object/apply:pymdownx.slugs.slugify
kwds:
case: lower
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.magiclink
- pymdownx.inlinehilite
- pymdownx.tilde
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg

View File

@@ -1,4 +0,0 @@
mkdocs-material
pillow
cairosvg
mike

11
package-lock.json generated Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "wg-easy",
"version": "1.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"version": "1.0.1"
}
}
}

View File

@@ -1,16 +1,10 @@
{ {
"version": "1.0.0", "version": "1.0.1",
"private": true,
"scripts": { "scripts": {
"dev": "docker compose -f docker-compose.dev.yml up wg-easy --build", "sudobuild": "DOCKER_BUILDKIT=1 sudo docker build --tag wg-easy .",
"cli:dev": "docker compose -f docker-compose.dev.yml run --build --rm -it wg-easy cli:dev", "build": "DOCKER_BUILDKIT=1 docker build --tag wg-easy .",
"build": "docker build -t wg-easy .", "serve": "docker compose -f docker-compose.yml -f docker-compose.dev.yml up",
"docs:preview": "docker run --rm -it -p 8080:8080 -v ./docs:/docs squidfunk/mkdocs-material serve -a 0.0.0.0:8080", "sudostart": "sudo docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy",
"scripts:version": "bash scripts/version.sh", "start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy"
"format:check:docs": "prettier --check docs" }
},
"devDependencies": {
"prettier": "^3.5.3"
},
"packageManager": "pnpm@10.10.0"
} }

24
pnpm-lock.yaml generated
View File

@@ -1,24 +0,0 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
prettier:
specifier: ^3.5.3
version: 3.5.3
packages:
prettier@3.5.3:
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
engines: {node: '>=14'}
hasBin: true
snapshots:
prettier@3.5.3: {}

View File

@@ -1,54 +0,0 @@
#!/bin/bash
package_json="src/package.json"
# Function to update the version in package.json
update_version() {
local new_version=$1
jq --arg new_version "$new_version" '.version = $new_version' $package_json > tmp.json && mv tmp.json $package_json
}
# Get the current version from package.json
current_version=$(jq -r '.version' $package_json)
echo "Current version: $current_version"
# Prompt the user for the new version
read -p "Enter the new version (following SemVer): " new_version
# Official SemVer regex for validation
semver_regex="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
# Validate the new version
if ! echo "$new_version" | grep -Eq "$semver_regex"; then
echo "Invalid version format. Please use SemVer format (e.g., 1.0.0 or 1.0.0-alpha)."
exit 1
fi
# Update the version in package.json
update_version $new_version
echo "Updated package.json to version $new_version"
echo "----"
echo "If you changed the major version, remember to update the docker-compose.yml file and docs (search for: ref: major version)"
echo "----"
echo "If you did everything press 'y' to commit the changes and create a new tag"
read -p "Do you want to continue? (y/n): " confirm
if [ "$confirm" != "y" ]; then
echo "Aborted."
exit 1
fi
# Commit the changes
git add $package_json
git commit -m "Bump version to $new_version"
echo "Committed the changes"
# Create a new Git tag
git tag -a "v$new_version" -m "Release version $new_version"
echo "Created Git tag v$new_version"
# Push the commit & tag to the remote repository
git push origin master --follow-tags
echo "Pushed Git commit and tag v$new_version to remote repository"

11
src/.eslintrc.json Normal file
View File

@@ -0,0 +1,11 @@
{
"extends": "athom",
"ignorePatterns": [
"**/vendor/*.js"
],
"rules": {
"consistent-return": "off",
"no-shadow": "off",
"max-len": "off"
}
}

26
src/.gitignore vendored
View File

@@ -1,26 +0,0 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example
wg-easy.db

View File

@@ -1,2 +0,0 @@
pnpm-lock.yaml
server/database/migrations/meta

View File

@@ -1,7 +0,0 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@@ -1,57 +0,0 @@
<template>
<ToastProvider>
<NuxtLayout>
<NuxtPage />
<ToastViewport
class="fixed bottom-0 right-0 z-[2147483647] m-0 flex w-[390px] max-w-[100vw] list-none flex-col gap-[10px] p-[var(--viewport-padding)] outline-none [--viewport-padding:_25px]"
>
<BaseToast ref="toastRef" />
</ToastViewport>
</NuxtLayout>
</ToastProvider>
</template>
<script setup lang="ts">
const toast = useToast();
const toastRef = useTemplateRef('toastRef');
toast.setToast(toastRef);
// make sure to fetch release early
useGlobalStore();
useHead({
bodyAttrs: {
class: 'bg-gray-50 dark:bg-neutral-800',
},
link: [
{
rel: 'manifest',
href: '/manifest.json',
},
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
},
{
rel: 'apple-touch-icon',
href: '/apple-touch-icon.png',
},
],
meta: [
{
name: 'mobile-web-app-capable',
content: 'yes',
},
{
name: 'apple-mobile-web-app-capable',
content: 'yes',
},
{
name: 'apple-mobile-web-app-status-bar-style',
content: 'black-translucent',
},
],
title: 'WireGuard',
});
</script>

View File

@@ -1,34 +0,0 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger><slot /></template>
<template #title>{{ $t('admin.interface.changeCidr') }}</template>
<template #description>
<FormGroup>
<FormTextField id="ipv4Cidr" v-model="ipv4Cidr" label="IPv4" />
<FormTextField id="ipv6Cidr" v-model="ipv6Cidr" label="IPv6" />
</FormGroup>
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('change', ipv4Cidr, ipv6Cidr)">
{{ $t('dialog.change') }}
</BaseButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script lang="ts" setup>
defineEmits(['change']);
const props = defineProps<{
triggerClass?: string;
ipv4Cidr: string;
ipv6Cidr: string;
}>();
const ipv4Cidr = ref(props.ipv4Cidr);
const ipv6Cidr = ref(props.ipv6Cidr);
</script>

View File

@@ -1,24 +0,0 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger><slot /></template>
<template #title>{{ $t('admin.interface.restart') }}</template>
<template #description>
{{ $t('admin.interface.restartWarn') }}
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('restart')">
{{ $t('admin.interface.restart') }}
</BaseButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script lang="ts" setup>
defineEmits(['restart']);
defineProps<{ triggerClass?: string }>();
</script>

View File

@@ -1,39 +0,0 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger><slot /></template>
<template #title>{{ $t('admin.config.suggest') }}</template>
<template #description>
<div class="flex flex-col items-start gap-2">
<p>{{ $t('admin.config.suggestDesc') }}</p>
<p v-if="!data">
{{ $t('general.loading') }}
</p>
<BaseSelect v-else v-model="selected" :options="data" />
</div>
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('change', selected)">
{{ $t('dialog.change') }}
</BaseButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script lang="ts" setup>
defineEmits(['change']);
const props = defineProps<{
triggerClass?: string;
url: '/api/admin/ip-info' | '/api/setup/4';
}>();
const { data } = useFetch(props.url, {
method: 'get',
});
const selected = ref<string>();
</script>

View File

@@ -1,20 +0,0 @@
<template>
<AvatarRoot
class="mr-2 inline-flex select-none items-center justify-center overflow-hidden rounded-full align-middle"
>
<AvatarImage
class="h-full w-full rounded-[inherit] object-cover"
:src="img ?? ''"
/>
<AvatarFallback
class="leading-1 flex h-full w-full items-center justify-center bg-white text-sm font-medium"
:delay-ms="600"
>
<slot />
</AvatarFallback>
</AvatarRoot>
</template>
<script lang="ts" setup>
defineProps<{ img?: string }>();
</script>

View File

@@ -1,26 +0,0 @@
<template>
<component
:is="elementType"
role="button"
class="inline-flex items-center rounded border-2 border-gray-100 px-4 py-2 text-gray-700 transition hover:border-red-800 hover:bg-red-800 hover:text-white dark:border-neutral-600 dark:text-neutral-200"
v-bind="attrs"
>
<slot />
</component>
</template>
<script setup lang="ts">
const props = defineProps({
as: {
type: String,
default: 'button',
},
});
const elementType = computed(() => props.as);
const attrs = computed(() => {
const { as, ...attrs } = props;
return attrs;
});
</script>

View File

@@ -1,20 +0,0 @@
<template>
<ClientOnly>
<apexchart
width="100%"
height="100%"
v-bind="$attrs"
:options="options"
:series="series"
/>
</ClientOnly>
</template>
<script setup lang="ts">
import type { VueApexChartsComponent } from 'vue3-apexcharts';
defineProps<{
options: VueApexChartsComponent['options'];
series: VueApexChartsComponent['series'];
}>();
</script>

View File

@@ -1,31 +0,0 @@
<template>
<DialogRoot :modal="true">
<DialogTrigger :class="triggerClass"><slot name="trigger" /></DialogTrigger>
<DialogPortal>
<DialogOverlay
class="fixed inset-0 z-30 bg-gray-500 opacity-75 dark:bg-black dark:opacity-50"
/>
<DialogContent
class="fixed left-1/2 top-1/2 z-[100] max-h-[85vh] w-[90vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-md bg-white p-6 shadow-2xl focus:outline-none dark:bg-neutral-700"
>
<DialogTitle
class="m-0 text-lg font-semibold text-gray-900 dark:text-neutral-200"
>
<slot name="title" />
</DialogTitle>
<DialogDescription
class="mb-5 mt-2 text-sm leading-normal text-gray-500 dark:text-neutral-300"
>
<slot name="description" />
</DialogDescription>
<div class="mt-6 flex justify-end gap-2">
<slot name="actions" />
</div>
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>
<script lang="ts" setup>
defineProps<{ triggerClass?: string }>();
</script>

View File

@@ -1,10 +0,0 @@
<template>
<input
v-model="data"
class="rounded-lg border-2 border-gray-100 text-gray-500 focus:border-red-800 focus:outline-0 focus:ring-0 dark:border-neutral-800 dark:bg-neutral-700 dark:text-neutral-200 dark:placeholder:text-neutral-400"
/>
</template>
<script lang="ts" setup>
const data = defineModel<unknown>();
</script>

View File

@@ -1,37 +0,0 @@
<template>
<SelectRoot v-model="selected">
<SelectTrigger
class="inline-flex h-8 items-center justify-around gap-2 rounded bg-gray-200 px-3 text-sm leading-none dark:bg-neutral-500 dark:text-neutral-200"
aria-label="Choose option"
>
<SelectValue placeholder="Select..." />
<IconsArrowDown class="size-3" />
</SelectTrigger>
<SelectPortal>
<SelectContent
class="z-[100] min-w-28 rounded bg-gray-300 dark:bg-neutral-500"
>
<SelectViewport class="p-2">
<SelectItem
v-for="(option, index) in options"
:key="index"
:value="option.value"
class="relative flex h-6 items-center rounded px-3 text-sm leading-none outline-none hover:bg-red-800 hover:text-white dark:text-white"
>
<SelectItemText>
{{ option.value }} - {{ option.label }}
</SelectItemText>
</SelectItem>
</SelectViewport>
</SelectContent>
</SelectPortal>
</SelectRoot>
</template>
<script lang="ts" setup>
defineProps<{
options: { label: string; value: string }[];
}>();
const selected = defineModel<string>();
</script>

View File

@@ -1,17 +0,0 @@
<template>
<SwitchRoot
:id="id"
v-model:checked="data"
:name="id"
class="relative flex h-6 w-10 cursor-default rounded-full bg-gray-200 shadow-sm focus-within:outline focus-within:outline-red-700 data-[state=checked]:bg-red-800 dark:bg-neutral-400"
>
<SwitchThumb
class="my-auto block h-4 w-4 translate-x-1 rounded-full bg-white shadow-sm transition-transform duration-100 will-change-transform data-[state=checked]:translate-x-[20px]"
/>
</SwitchRoot>
</template>
<script lang="ts" setup>
defineProps<{ id?: string }>();
const data = defineModel<boolean>();
</script>

View File

@@ -1,46 +0,0 @@
<template>
<ToastRoot
v-for="(e, i) in count"
:key="i"
:class="[
`grid grid-cols-[auto_max-content] items-center gap-x-3 rounded-md p-3 text-neutral-200 shadow-lg [grid-template-areas:_'title_action'_'description_action'] data-[swipe=cancel]:translate-x-0 data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)]`,
{
'bg-green-800': e.type === 'success',
'bg-red-800': e.type === 'error',
},
]"
>
<ToastTitle class="mb-1 text-sm font-medium [grid-area:_title]">
{{ e.title }}
</ToastTitle>
<ToastDescription class="m-0 text-sm [grid-area:_description]">{{
e.message
}}</ToastDescription>
<ToastAction as-child alt-text="toast" class="[grid-area:_action]">
<slot />
</ToastAction>
<ToastClose aria-label="Close">
<span aria-hidden>×</span>
</ToastClose>
</ToastRoot>
</template>
<script setup lang="ts">
import {
ToastAction,
ToastClose,
ToastDescription,
ToastRoot,
ToastTitle,
} from 'radix-vue';
defineExpose({
publish,
});
const count = reactive<ToastParams[]>([]);
function publish(e: ToastParams) {
count.push({ type: e.type, title: e.title, message: e.message });
}
</script>

View File

@@ -1,29 +0,0 @@
<template>
<TooltipProvider>
<TooltipRoot :open="open" @update:open="open = $event">
<TooltipTrigger
class="mx-2 inline-flex h-4 w-4 items-center justify-center rounded-full text-gray-400 outline-none focus:shadow-sm focus:shadow-black"
as-child
>
<button type="button" @click="open = !open">
<slot />
</button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent
class="select-none whitespace-pre-line rounded bg-gray-600 px-3 py-2 text-sm leading-none text-white shadow-lg will-change-[transform,opacity]"
:side-offset="5"
>
{{ text }}
<TooltipArrow class="fill-gray-600" :width="8" />
</TooltipContent>
</TooltipPortal>
</TooltipRoot>
</TooltipProvider>
</template>
<script lang="ts" setup>
defineProps<{ text: string }>();
const open = ref(false);
</script>

View File

@@ -1,11 +0,0 @@
<template>
<span class="inline-block">
{{ client.ipv4Address }}, {{ client.ipv6Address }}
</span>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,30 +0,0 @@
<template>
<div class="relative mt-2 h-10 w-10 self-start rounded-full bg-gray-50">
<BaseAvatar :img="client.avatar" class="h-10 w-10">
<IconsAvatar class="h-6 w-6 text-gray-300" />
</BaseAvatar>
<div
v-if="
isPeerConnected({
latestHandshakeAt: client.latestHandshakeAt
? new Date(client.latestHandshakeAt)
: null,
})
"
>
<div
class="absolute -bottom-1 -right-1 h-4 w-4 animate-ping rounded-full bg-red-100 p-1 dark:bg-red-100"
/>
<div
class="absolute bottom-0 right-0 h-2 w-2 rounded-full bg-red-800 dark:bg-red-600"
/>
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,136 +0,0 @@
<template>
<div
:class="`absolute bottom-0 left-0 right-0 z-0 h-6 ${globalStore.uiChartType === 'line' && 'line-chart'}`"
>
<BaseChart :options="chartOptionsTX" :series="client.transferTxSeries" />
</div>
<div
:class="`absolute left-0 right-0 top-0 z-0 h-6 ${globalStore.uiChartType === 'line' && 'line-chart'}`"
>
<BaseChart
:options="chartOptionsRX"
:series="client.transferRxSeries"
style="transform: scaleY(-1)"
/>
</div>
</template>
<script setup lang="ts">
import type { ApexOptions } from 'apexcharts';
defineProps<{
client: LocalClient;
}>();
const globalStore = useGlobalStore();
const theme = useTheme();
const chartOptionsTX = computed(() => {
const opts = {
...chartOptions,
colors: [CHART_COLORS.tx[theme.value]],
};
opts.chart.type = globalStore.uiChartType;
opts.stroke.width = UI_CHART_PROPS[globalStore.uiChartType].strokeWidth;
return opts;
});
const chartOptionsRX = computed(() => {
const opts = {
...chartOptions,
colors: [CHART_COLORS.rx[theme.value]],
};
opts.chart.type = globalStore.uiChartType;
opts.stroke.width = UI_CHART_PROPS[globalStore.uiChartType].strokeWidth;
return opts;
});
const chartOptions = {
chart: {
type: undefined as ApexChart['type'],
background: 'transparent',
stacked: false,
toolbar: {
show: false,
},
animations: {
enabled: false,
},
parentHeightOffset: 0,
sparkline: {
enabled: true,
},
},
colors: [],
stroke: {
curve: 'smooth',
width: 0,
},
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
type: 'vertical',
shadeIntensity: 0,
gradientToColors: CHART_COLORS.gradient[theme.value],
inverseColors: false,
opacityTo: 0,
stops: [0, 100],
},
},
dataLabels: {
enabled: false,
},
plotOptions: {
bar: {
horizontal: false,
},
},
xaxis: {
labels: {
show: false,
},
axisTicks: {
show: false,
},
axisBorder: {
show: false,
},
},
yaxis: {
labels: {
show: false,
},
min: 0,
},
tooltip: {
enabled: false,
},
legend: {
show: false,
},
grid: {
show: false,
padding: {
left: -10,
right: 0,
bottom: -15,
top: -15,
},
column: {
opacity: 0,
},
xaxis: {
lines: {
show: false,
},
},
},
} satisfies ApexOptions;
</script>
<style scoped lang="css">
.line-chart .apexcharts-svg {
transform: translateY(3px);
}
</style>

View File

@@ -1,51 +0,0 @@
<template>
<ClientCardCharts :client="client" />
<div
class="relative z-10 flex flex-col justify-between gap-3 px-3 py-3 sm:flex-row md:py-5"
>
<div class="flex w-full items-center gap-3 md:gap-4">
<ClientCardAvatar :client="client" />
<div class="flex w-full flex-col gap-2 xxs:flex-row">
<div class="flex flex-grow flex-col gap-1">
<ClientCardName :client="client" />
<div
class="flex flex-col pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400"
>
<div>
<ClientCardAddress :client="client" />
</div>
<div>
<ClientCardLastSeen :client="client" />
</div>
</div>
<ClientCardOneTimeLink :client="client" />
<ClientCardExpireDate :client="client" />
</div>
<div
class="mt-px flex shrink-0 items-center justify-end gap-2 text-xs text-gray-400 dark:text-neutral-400"
>
<ClientCardTransfer :client="client" />
</div>
</div>
</div>
<div class="flex items-center justify-end">
<div
class="flex items-center justify-between gap-1 text-gray-400 dark:text-neutral-400"
>
<ClientCardSwitch :client="client" />
<ClientCardEdit :client="client" />
<ClientCardQRCode :client="client" />
<ClientCardConfig :client="client" />
<ClientCardOneTimeLinkBtn :client="client" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,16 +0,0 @@
<template>
<a
:href="'/api/client/' + client.id + '/configuration'"
download
class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:title="$t('client.downloadConfig')"
>
<IconsDownload class="w-5" />
</a>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,14 +0,0 @@
<template>
<NuxtLink
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:to="`/clients/${client.id}`"
>
<IconsEdit class="w-5" />
</NuxtLink>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,23 +0,0 @@
<template>
<div
class="block pb-1 text-xs text-gray-500 md:inline-block md:pb-0 dark:text-neutral-400"
>
<span class="inline-block">{{ expiredDateFormat(client.expiresAt) }}</span>
</div>
</template>
<script setup lang="ts">
defineProps<{ client: LocalClient }>();
const { t, locale } = useI18n();
function expiredDateFormat(value: string | null) {
if (value === null) return t('client.permanent');
const dateTime = new Date(value);
return dateTime.toLocaleDateString(locale.value, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
</script>

View File

@@ -1,16 +0,0 @@
<template>
<span
v-if="client.latestHandshakeAt"
:title="$t('client.lastSeen') + $d(new Date(client.latestHandshakeAt))"
>
{{ timeago(new Date(client.latestHandshakeAt)) }}
</span>
</template>
<script setup lang="ts">
import { format as timeago } from 'timeago.js';
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,16 +0,0 @@
<template>
<div
class="text-sm text-gray-700 md:text-base dark:text-neutral-200"
:title="$t('client.createdOn') + $d(new Date(client.createdAt))"
>
<span class="border-b-2 border-t-2 border-transparent">
{{ client.name }}
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,51 +0,0 @@
<template>
<div v-if="client.oneTimeLink !== null" class="text-xs text-gray-400">
<a :href="'./cnf/' + client.oneTimeLink.oneTimeLink">{{ path }}</a>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{ client: LocalClient }>();
const path = ref('Loading...');
const timer = ref<NodeJS.Timeout | null>(null);
const { localeProperties } = useI18n();
onMounted(() => {
timer.value = setIntervalImmediately(() => {
if (props.client.oneTimeLink === null) {
return;
}
const timeLeft =
new Date(props.client.oneTimeLink.expiresAt).getTime() - Date.now();
if (timeLeft <= 0) {
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (00:00)`;
return;
}
const formatter = new Intl.DateTimeFormat(localeProperties.value.language, {
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23',
});
const minutes = Math.floor(timeLeft / 60000);
const seconds = Math.floor((timeLeft % 60000) / 1000);
const date = new Date(0);
date.setMinutes(minutes);
date.setSeconds(seconds);
path.value = `${document.location.protocol}//${document.location.host}/cnf/${props.client.oneTimeLink.oneTimeLink} (${formatter.format(date)})`;
}, 1000);
});
onUnmounted(() => {
if (timer.value) {
clearTimeout(timer.value);
}
});
</script>

View File

@@ -1,32 +0,0 @@
<template>
<button
class="inline-block rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:title="$t('client.otlDesc')"
@click="showOneTimeLink"
>
<IconsLink class="w-5" />
</button>
</template>
<script setup lang="ts">
const props = defineProps<{ client: LocalClient }>();
const clientsStore = useClientsStore();
const _showOneTimeLink = useSubmit(
`/api/client/${props.client.id}/generateOneTimeLink`,
{
method: 'post',
},
{
revert: async () => {
await clientsStore.refresh();
},
noSuccessToast: true,
}
);
function showOneTimeLink() {
return _showOneTimeLink(undefined);
}
</script>

View File

@@ -1,16 +0,0 @@
<template>
<ClientsQRCodeDialog :qr-code="`./api/client/${client.id}/qrcode.svg`">
<div
class="rounded bg-gray-100 p-2 align-middle transition hover:bg-red-800 hover:text-white dark:bg-neutral-600 dark:text-neutral-300 dark:hover:bg-red-800 dark:hover:text-white"
:title="$t('client.showQR')"
>
<IconsQRCode class="w-5" />
</div>
</ClientsQRCodeDialog>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,53 +0,0 @@
<template>
<BaseSwitch
v-model="enabled"
:title="
client.enabled ? $t('client.disableClient') : $t('client.enableClient')
"
@click="toggleClient"
/>
</template>
<script setup lang="ts">
const props = defineProps<{
client: LocalClient;
}>();
const enabled = ref(props.client.enabled);
const clientsStore = useClientsStore();
const _disableClient = useSubmit(
`/api/client/${props.client.id}/disable`,
{
method: 'post',
},
{
revert: async () => {
await clientsStore.refresh();
},
noSuccessToast: true,
}
);
const _enableClient = useSubmit(
`/api/client/${props.client.id}/enable`,
{
method: 'post',
},
{
revert: async () => {
await clientsStore.refresh();
},
noSuccessToast: true,
}
);
async function toggleClient() {
if (props.client.enabled) {
await _disableClient(undefined);
} else {
await _enableClient(undefined);
}
}
</script>

View File

@@ -1,45 +0,0 @@
<template>
<!-- Transfer TX -->
<div v-if="client.transferTx" class="min-w-20 md:min-w-24">
<span
class="flex gap-1"
:title="$t('client.totalDownload') + bytes(client.transferTx)"
>
<IconsArrowDown class="mt-0.5 inline h-3 align-middle" />
<div>
<span class="text-gray-700 dark:text-neutral-200"
>{{ bytes(client.transferTxCurrent) }}/s</span
>
<!-- Total TX -->
<br /><span class="font-regular" style="font-size: 0.85em">{{
bytes(client.transferTx)
}}</span>
</div>
</span>
</div>
<!-- Transfer RX -->
<div v-if="client.transferRx" class="min-w-20 md:min-w-24">
<span
class="flex gap-1"
:title="$t('client.totalUpload') + bytes(client.transferRx)"
>
<IconsArrowUp class="mt-0.5 inline h-3 align-middle" />
<div>
<span class="text-gray-700 dark:text-neutral-200"
>{{ bytes(client.transferRxCurrent) }}/s</span
>
<!-- Total RX -->
<br /><span class="font-regular" style="font-size: 0.85em">{{
bytes(client.transferRx)
}}</span>
</div>
</span>
</div>
</template>
<script setup lang="ts">
defineProps<{
client: LocalClient;
}>();
</script>

View File

@@ -1,53 +0,0 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger>
<slot />
</template>
<template #title>
{{ $t('client.new') }}
</template>
<template #description>
<div class="flex flex-col">
<FormTextField id="name" v-model="name" :label="$t('client.name')" />
<FormDateField
id="expiresAt"
v-model="expiresAt"
:label="$t('client.expireDate')"
/>
</div>
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="createClient">{{ $t('client.create') }}</BaseButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script lang="ts" setup>
const name = ref<string>('');
const expiresAt = ref<string | null>(null);
const clientsStore = useClientsStore();
const { t } = useI18n();
defineProps<{ triggerClass?: string }>();
function createClient() {
return _createClient({ name: name.value, expiresAt: expiresAt.value });
}
const _createClient = useSubmit(
'/api/client',
{
method: 'post',
},
{
revert: () => clientsStore.refresh(),
successMsg: t('client.created'),
}
);
</script>

View File

@@ -1,26 +0,0 @@
<template>
<BaseDialog :trigger-class="triggerClass">
<template #trigger><slot /></template>
<template #title>{{ $t('client.deleteClient') }}</template>
<template #description>
{{ $t('client.deleteDialog1') }}
<strong>{{ clientName }}</strong
>? {{ $t('client.deleteDialog2') }}
</template>
<template #actions>
<DialogClose as-child>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
<DialogClose as-child>
<BaseButton @click="$emit('delete')">{{
$t('client.deleteClient')
}}</BaseButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script lang="ts" setup>
defineEmits(['delete']);
defineProps<{ triggerClass?: string; clientName: string }>();
</script>

View File

@@ -1,11 +0,0 @@
<template>
<p class="m-10 text-center text-sm text-gray-400 dark:text-neutral-400">
{{ $t('client.empty') }}<br /><br />
<ClientsCreateDialog>
<BaseButton as="span">
<IconsPlus class="w-4 md:mr-2" />
<span class="text-sm">{{ $t('client.new') }}</span>
</BaseButton>
</ClientsCreateDialog>
</p>
</template>

View File

@@ -1,13 +0,0 @@
<template>
<div
v-for="client in clientsStore.clients"
:key="client.id"
class="relative overflow-hidden border-b border-solid border-gray-100 last:border-b-0 dark:border-neutral-600"
>
<ClientCard :client="client" />
</div>
</template>
<script setup lang="ts">
const clientsStore = useClientsStore();
</script>

View File

@@ -1,8 +0,0 @@
<template>
<ClientsCreateDialog>
<BaseButton as="span">
<IconsPlus class="w-4 md:mr-2" />
<span class="text-sm max-md:hidden">{{ $t('client.newShort') }}</span>
</BaseButton>
</ClientsCreateDialog>
</template>

View File

@@ -1,21 +0,0 @@
<template>
<BaseDialog>
<template #trigger>
<slot />
</template>
<template #description>
<div class="bg-white">
<img :src="qrCode" />
</div>
</template>
<template #actions>
<DialogClose>
<BaseButton>{{ $t('dialog.cancel') }}</BaseButton>
</DialogClose>
</template>
</BaseDialog>
</template>
<script setup lang="ts">
defineProps<{ qrCode: string }>();
</script>

Some files were not shown because too many files have changed in this diff Show More