Compare commits

...

13 Commits

Author SHA1 Message Date
Daan Selen
ed896b6545 refac: intermediate commit 2025-09-20 01:02:58 +02:00
Daan Selen
6bebe89202 refac: rewrite first part of dashboard.py 2025-09-20 00:10:19 +02:00
dependabot[bot]
101ac5e985 Bump vite from 7.1.5 to 7.1.6 in /src/static/app (#912)
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docker Build and Push / docker_build (push) Has been cancelled
Docker Build and Push / docker_scan (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.5 to 7.1.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 21:00:15 +02:00
dependabot[bot]
113a780eec Bump @vue/language-server from 3.0.5 to 3.0.7 in /src/static/app (#902)
Bumps [@vue/language-server](https://github.com/vuejs/language-tools/tree/HEAD/packages/language-server) from 3.0.5 to 3.0.7.
- [Release notes](https://github.com/vuejs/language-tools/releases)
- [Changelog](https://github.com/vuejs/language-tools/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vuejs/language-tools/commits/v3.0.7/packages/language-server)

---
updated-dependencies:
- dependency-name: "@vue/language-server"
  dependency-version: 3.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-19 20:59:48 +02:00
Donald Zou
cf77610a56 Merge pull request #914 from WGDashboard/add-template
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docker Build and Push / docker_build (push) Has been cancelled
Docker Build and Push / docker_scan (push) Has been cancelled
2025-09-19 18:24:45 +08:00
Daan Selen
84675fe521 feat: add default wg-dashboard.ini config 2025-09-19 10:32:25 +02:00
Donald Zou
5db5b35311 Update README.md
Some checks failed
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docker Build and Push / docker_build (push) Has been cancelled
Docker Build and Push / docker_scan (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
2025-09-17 23:31:20 +08:00
DaanSelen
ff345c9609 style: readme update 2025-09-17 16:03:35 +02:00
Daan Selen
6cccfec923 fix: fix the docker building issue
I accidentally removed a character
  we are all human.
2025-09-17 15:58:16 +02:00
Donald Zou
8231dd1463 Merge pull request #906 from WGDashboard/docker-doc-refac
refac(docs): rewrite and check the docker documents
2025-09-17 21:49:25 +08:00
Daan Selen
d8ff020d8c refac(docs): rewrite and check the docker documents 2025-09-17 15:34:39 +02:00
Daan Selen
238fb91360 chore: further expand and change the compose file
For people that want to get started quickly
2025-09-17 15:20:01 +02:00
DaanSelen
9ecc16fcc1 chore: update dependabot config 2025-09-17 11:05:31 +02:00
10 changed files with 623 additions and 488 deletions

View File

@@ -11,7 +11,7 @@ updates:
interval: "daily" interval: "daily"
- package-ecosystem: "npm" - package-ecosystem: "npm"
directory: "/" directory: "/src/static/app"
schedule: schedule:
interval: "daily" interval: "daily"

1
.gitignore vendored
View File

@@ -19,6 +19,7 @@ node_modules/**
*/proxy.js */proxy.js
src/static/app/proxy.js src/static/app/proxy.js
.secrets .secrets
*.pid
# Logs # Logs
logs logs

View File

@@ -26,6 +26,8 @@
<a href="https://wakatime.com/badge/github/donaldzou/WGDashboard"><img src="https://wakatime.com/badge/github/donaldzou/WGDashboard.svg?style=for-the-badge" alt="wakatime"></a> <a href="https://wakatime.com/badge/github/donaldzou/WGDashboard"><img src="https://wakatime.com/badge/github/donaldzou/WGDashboard.svg?style=for-the-badge" alt="wakatime"></a>
<a href="https://hitscounter.dev"><img src="https://hitscounter.dev/api/hit?url=https%3A%2F%2Fgithub.com%2Fdonaldzou%2FWGDashboard&label=Visitor&icon=github&color=%230a58ca&style=for-the-badge"></a> <a href="https://hitscounter.dev"><img src="https://hitscounter.dev/api/hit?url=https%3A%2F%2Fgithub.com%2Fdonaldzou%2FWGDashboard&label=Visitor&icon=github&color=%230a58ca&style=for-the-badge"></a>
<img src="https://img.shields.io/docker/pulls/donaldzou/wgdashboard?logo=docker&label=Docker%20Image%20Pulls&labelColor=ffffff&style=for-the-badge"> <img src="https://img.shields.io/docker/pulls/donaldzou/wgdashboard?logo=docker&label=Docker%20Image%20Pulls&labelColor=ffffff&style=for-the-badge">
<img src="https://github.com/WGDashboard/WGDashboard/actions/workflows/docker.yml/badge.svg?style=for-the-badge">
<img src="https://github.com/WGDashboard/WGDashboard/actions/workflows/codeql-analyze.yaml/badge.svg">
</p> </p>
<p align="center"><b>This project is supported by</b></p> <p align="center"><b>This project is supported by</b></p>
<p align="center"> <p align="center">

View File

@@ -3,8 +3,10 @@
# Base: Alpine # Base: Alpine
# #
# Pull the current golang-alpine image.
FROM golang:1.25-alpine AS awg-go FROM golang:1.25-alpine AS awg-go
# Install build-dependencies.
RUN apk add --no-cache \ RUN apk add --no-cache \
git \ git \
gcc \ gcc \
@@ -14,9 +16,12 @@ RUN apk add --no-cache \
RUN mkdir -p /workspace && \ RUN mkdir -p /workspace && \
git clone https://github.com/WGDashboard/amneziawg-go /workspace/awg git clone https://github.com/WGDashboard/amneziawg-go /workspace/awg
# Enable CGO compilation for AmneziaWG
ENV CGO_ENABLED=1 ENV CGO_ENABLED=1
# Change directory
WORKDIR /workspace/awg WORKDIR /workspace/awg
# Compile the binaries
RUN go mod download && \ RUN go mod download && \
go mod verify && \ go mod verify && \
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
@@ -26,6 +31,7 @@ RUN go mod download && \
# #
FROM alpine:latest AS awg-tools FROM alpine:latest AS awg-tools
# Install needed dependencies.
RUN apk add --no-cache \ RUN apk add --no-cache \
make \ make \
git \ git \
@@ -33,19 +39,24 @@ RUN apk add --no-cache \
linux-headers \ linux-headers \
ca-certificates ca-certificates
# Get the workspace ready
RUN mkdir -p /workspace && \ RUN mkdir -p /workspace && \
git clone https://github.com/WGDashboard/amneziawg-tools /workspace/awg-tools git clone https://github.com/WGDashboard/amneziawg-tools /workspace/awg-tools
# Change directory
WORKDIR /workspace/awg-tools/src WORKDIR /workspace/awg-tools/src
# Compile and change permissions
RUN make && chmod +x wg* RUN make && chmod +x wg*
# #
# PIP DEPENDENCY BUILDING # PIP DEPENDENCY BUILDING
# Base: Alpine # Base: Alpine
# #
# Use the python-alpine image for building pip dependencies
FROM python:3.13-alpine AS pip-builder FROM python:3.13-alpine AS pip-builder
# Add the build dependencies and create a Python virtual environment.
RUN apk add --no-cache \ RUN apk add --no-cache \
build-base \ build-base \
pkgconfig \ pkgconfig \
@@ -57,7 +68,9 @@ RUN apk add --no-cache \
&& mkdir -p /opt/wgdashboard/src \ && mkdir -p /opt/wgdashboard/src \
&& python3 -m venv /opt/wgdashboard/src/venv && python3 -m venv /opt/wgdashboard/src/venv
# Copy the requirements file into the build layer.
COPY ./src/requirements.txt /opt/wgdashboard/src COPY ./src/requirements.txt /opt/wgdashboard/src
# Install the pip packages
RUN . /opt/wgdashboard/src/venv/bin/activate && \ RUN . /opt/wgdashboard/src/venv/bin/activate && \
pip3 install --upgrade pip && \ pip3 install --upgrade pip && \
pip3 install -r /opt/wgdashboard/src/requirements.txt pip3 install -r /opt/wgdashboard/src/requirements.txt
@@ -66,6 +79,8 @@ RUN . /opt/wgdashboard/src/venv/bin/activate && \
# WGDashboard RUNNING STAGE # WGDashboard RUNNING STAGE
# Base: Alpine # Base: Alpine
# #
# Running with the python-alpine image.
FROM python:3.13-alpine AS final FROM python:3.13-alpine AS final
LABEL maintainer="dselen@nerthus.nl" LABEL maintainer="dselen@nerthus.nl"
@@ -78,7 +93,7 @@ RUN apk add --no-cache \
tzdata wireguard-tools \ tzdata wireguard-tools \
openresolv openrc openresolv openrc
# Copy only the final binaries from the builders # Copy only the final binaries from the AWG builder stages
COPY --from=awg-go /usr/bin/amneziawg-go /usr/bin/amneziawg-go COPY --from=awg-go /usr/bin/amneziawg-go /usr/bin/amneziawg-go
COPY --from=awg-tools /workspace/awg-tools/src/wg /usr/bin/awg COPY --from=awg-tools /workspace/awg-tools/src/wg /usr/bin/awg
COPY --from=awg-tools /workspace/awg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick COPY --from=awg-tools /workspace/awg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
@@ -92,14 +107,14 @@ ENV TZ="Europe/Amsterdam" \
public_ip="" \ public_ip="" \
WGDASH=/opt/wgdashboard WGDASH=/opt/wgdashboard
# Create directories # Create directories needed for operation
RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg RUN mkdir /data /configs -p ${WGDASH}/src /etc/amnezia/amneziawg
# Copy app source and prebuilt venv only (no pip cache) # Copy the python virtual environment from the pip-builder stage
COPY ./src ${WGDASH}/src COPY ./src ${WGDASH}/src
COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv
# WireGuard interface template # First WireGuard interface template
SHELL ["/bin/bash", "-o", "pipefail", "-c"] SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN out_adapt=$(ip -o -4 route show to default | awk '{print $NF}') \ RUN out_adapt=$(ip -o -4 route show to default | awk '{print $NF}') \
&& echo -e "[Interface]\n\ && echo -e "[Interface]\n\
@@ -114,11 +129,14 @@ SaveConfig = true\n\
DNS = ${global_dns}" > /configs/wg0.conf.template \ DNS = ${global_dns}" > /configs/wg0.conf.template \
&& chmod 600 /configs/wg0.conf.template && chmod 600 /configs/wg0.conf.template
# Set a healthcheck to determine the container its health
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD sh -c 'pgrep gunicorn > /dev/null && pgrep tail > /dev/null' || exit 1 CMD sh -c 'pgrep gunicorn > /dev/null && pgrep tail > /dev/null' || exit 1
# Copy in the runtime script, essential.
COPY ./docker/entrypoint.sh /entrypoint.sh COPY ./docker/entrypoint.sh /entrypoint.sh
#
EXPOSE 10086 EXPOSE 10086
WORKDIR $WGDASH/src WORKDIR $WGDASH/src

View File

@@ -3,8 +3,8 @@ Author: @DaanSelen<br>
This document delves into how the WGDashboard Docker container has been built.<br> This document delves into how the WGDashboard Docker container has been built.<br>
Of course there are two stages (simply said), one before run-time and one at/after run-time.<br> Of course there are two stages (simply said), one before run-time and one at/after run-time.<br>
The `Dockerfile` describes how the container image is made, and the `entrypoint.sh` is executed after running the container. <br> The `Dockerfile` describes how the container image is made, and the `entrypoint.sh` is executed after the container is started. <br>
In this example, WireGuard is integrated into the container itself, so it should be a run-and-go(/out-of-the-box).<br> In this example, [WireGuard](https://www.wireguard.com/) is integrated into the container itself, so it should be a run-and-go(/out-of-the-box) experience.<br>
For more details on the source-code specific to this Docker image, refer to the source files, they have lots of comments. For more details on the source-code specific to this Docker image, refer to the source files, they have lots of comments.
<br> <br>
@@ -18,20 +18,24 @@ For more details on the source-code specific to this Docker image, refer to the
/> />
<br> <br>
To get the container running you either pull the image from the repository, (docker.io)`donaldzou/wgdashboard:latest`.<br> To get the container running you either pull the pre-made image from a remote repository, there are 2 official options.<br>
From there either use the environment variables describe below as parameters or use the Docker Compose file: `compose.yaml`.<br>
Be careful, the default generated WireGuard configuration file uses port 51820/udp. So use this port if you want to use it out of the box.<br> - ghcr.io/wgdashboard/wgdashboard:<tag>
Otherwise edit the configuration file in `/etc/wireguard/wg0.conf`. - docker.io/donaldzou/wgdashboard:<tag>
> tags should be either: latest, main, <version> or <commit-sha>.
From there either use the environment variables described below as parameters or use the Docker Compose file: `compose.yaml`.<br>
Be careful, the default generated WireGuard configuration file uses port 51820/udp. So make sure to use this port if you want to use it out of the box.<br>
Otherwise edit the configuration file in WGDashboard under `Configuration Settings` -> `Edit Raw Configuration File`.
> Otherwise you need to enter the container and edit: `/etc/wireguard/wg0.conf`.
# WGDashboard: 🐳 Docker Deployment Guide # WGDashboard: 🐳 Docker Deployment Guide
To run the container, you can either pull the image from Docker Hub or build it yourself. The image is available at: To run the container, you can either pull the image from the Github Container Registry (ghcr.io), Docker Hub (docker.io) or build it yourself. The image is available at:
``` > `docker.io` is in most cases automatically resolved by the Docker application. Therefor you can ofter specify: `donaldzou/wgdashboard:latest`
docker.io/donaldzou/wgdashboard:latest
```
> `docker.io` is in most cases automatically resolved by the Docker application.
### 🔧 Quick Docker Run Command ### 🔧 Quick Docker Run Command
@@ -44,7 +48,7 @@ docker run -d \
-p 10086:10086/tcp \ -p 10086:10086/tcp \
-p 51820:51820/udp \ -p 51820:51820/udp \
--cap-add NET_ADMIN \ --cap-add NET_ADMIN \
donaldzou/wgdashboard:latest ghcr.io/wgdashboard/wgdashboard:latest
``` ```
> ⚠️ The default WireGuard port is `51820/udp`. If you change this, update the `/etc/wireguard/wg0.conf` accordingly. > ⚠️ The default WireGuard port is `51820/udp`. If you change this, update the `/etc/wireguard/wg0.conf` accordingly.
@@ -58,23 +62,24 @@ You can also use Docker Compose for easier configuration:
```yaml ```yaml
services: services:
wgdashboard: wgdashboard:
image: donaldzou/wgdashboard:latest image: ghcr.io/wgdashboard/wgdashboard:latest
restart: unless-stopped restart: unless-stopped
container_name: wgdashboard container_name: wgdashboard
environment:
# - tz=Europe/Amsterdam
# - global_dns=1.1.1.1
# - public_ip=YOUR_PUBLIC_IP
ports: ports:
- 10086:10086/tcp - 10086:10086/tcp
- 51820:51820/udp - 51820:51820/udp
volumes: volumes:
- aconf:/etc/amnezia/amneziawg
- conf:/etc/wireguard - conf:/etc/wireguard
- data:/data - data:/data
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
volumes: volumes:
aconf:
conf: conf:
data: data:
``` ```
@@ -85,7 +90,7 @@ volumes:
## 🔄 Updating the Container ## 🔄 Updating the Container
Updating WGDashboard is currently in **alpha** stage. While the update process may work, it's still under testing. Updating the WGDashboard container should be through 'The Docker Way' - by pulling the newest/newer image and replacing this old one.
--- ---

View File

@@ -1,22 +1,41 @@
services: services:
wireguard-dashboard: wgdashboard:
image: donaldzou/wgdashboard:latest # Since the github organisation we recommend the ghcr.io.
# Alternatively we also still push to docker.io under donaldzou/wgdashboard.
# Both share the exact same tags. So they should be interchangable.
image: ghcr.io/wgdashboard/wgdashboard:latest
# Make sure to set the restart policy. Because for a VPN its important to come back IF it crashes.
restart: unless-stopped restart: unless-stopped
container_name: wgdashboard container_name: wgdashboard
# Environment variables can be used to configure certain values at startup. Without having to configure it from the dashboard.
# By default its all disabled, but uncomment the following lines to apply these. (uncommenting is removing the # character)
# Refer to the documentation on https://wgdashboard.dev/ for more info on what everything means.
#environment: #environment:
#- tz= # <--- Set container timezone, default: Europe/Amsterdam. #- tz= # <--- Set container timezone, default: Europe/Amsterdam.
#- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me. #- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me.
#- wgd_port= # <--- Set the port WGDashboard will use for its web-server. #- wgd_port= # <--- Set the port WGDashboard will use for its web-server.
# The following section, ports is very important for exposing more than one Wireguard/AmneziaWireguard interfaces.
# Once you create a new configuration and assign a port in the dashboard, don't forget to add it to the ports as well.
# Quick-tip: most Wireguard VPN tunnels use UDP. WGDashboard uses HTTP, so tcp.
ports: ports:
- 10086:10086/tcp - 10086:10086/tcp
- 51820:51820/udp - 51820:51820/udp
# Volumes can be configured however you'd like. The default is using docker volumes.
# If you want to use local paths, replace the path before the : with your path.
volumes: volumes:
- aconf:/etc/amnezia/amneziawg - aconf:/etc/amnezia/amneziawg
- conf:/etc/wireguard - conf:/etc/wireguard
- data:/data - data:/data
# Needed for network administration.
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
# The following configuration is linked to the above default volumes.
volumes: volumes:
aconf: aconf:
conf: conf:

View File

@@ -1,27 +1,45 @@
# --- Standard library imports ---
import configparser
import hashlib
import ipaddress
import json
import logging import logging
import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess import os
import time, re, uuid, bcrypt, psutil, pyotp, threading import random
import re
import secrets
import shutil
import sqlite3
import subprocess
import threading
import time
import traceback import traceback
import uuid
from datetime import datetime, timedelta
from itertools import islice
from uuid import uuid4 from uuid import uuid4
from zipfile import ZipFile from zipfile import ZipFile
from datetime import datetime, timedelta
# --- Third-party imports ---
import bcrypt
import psutil
import pyotp
import sqlalchemy import sqlalchemy
from jinja2 import Template from flask import Flask, request, render_template, session, send_file, Response
from flask import Flask, request, render_template, session, send_file
from flask_cors import CORS from flask_cors import CORS
from icmplib import ping, traceroute
from flask.json.provider import DefaultJSONProvider from flask.json.provider import DefaultJSONProvider
from itertools import islice from icmplib import ping, traceroute
from jinja2 import Template
from packaging import version
from sqlalchemy import RowMapping from sqlalchemy import RowMapping
# --- Local module imports ---
from client import createClientBlueprint
from modules.Utilities import ( from modules.Utilities import (
RegexMatch, StringToBoolean, RegexMatch, StringToBoolean,
ValidateIPAddressesWithRange, ValidateDNSAddress, ValidateIPAddressesWithRange, ValidateDNSAddress,
GenerateWireguardPublicKey, GenerateWireguardPrivateKey GenerateWireguardPublicKey, GenerateWireguardPrivateKey
) )
from packaging import version
from modules.Email import EmailSender from modules.Email import EmailSender
from modules.DashboardLogger import DashboardLogger from modules.DashboardLogger import DashboardLogger
from modules.PeerJob import PeerJob from modules.PeerJob import PeerJob
@@ -31,16 +49,15 @@ from modules.PeerJobs import PeerJobs
from modules.DashboardConfig import DashboardConfig from modules.DashboardConfig import DashboardConfig
from modules.WireguardConfiguration import WireguardConfiguration from modules.WireguardConfiguration import WireguardConfiguration
from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration
from client import createClientBlueprint
from logging.config import dictConfig
from modules.DashboardClients import DashboardClients from modules.DashboardClients import DashboardClients
from modules.DashboardPlugins import DashboardPlugins from modules.DashboardPlugins import DashboardPlugins
from modules.DashboardWebHooks import DashboardWebHooks from modules.DashboardWebHooks import DashboardWebHooks
from modules.NewConfigurationTemplates import NewConfigurationTemplates from modules.NewConfigurationTemplates import NewConfigurationTemplates
# --- Logging configuration ---
from logging.config import dictConfig
class CustomJsonEncoder(DefaultJSONProvider): class CustomJsonEncoder(DefaultJSONProvider):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
@@ -55,7 +72,6 @@ class CustomJsonEncoder(DefaultJSONProvider):
return super().default(self) return super().default(self)
''' '''
Response Object Response Object
''' '''
@@ -195,12 +211,12 @@ with app.app_context():
EmailSender = EmailSender(DashboardConfig) EmailSender = EmailSender(DashboardConfig)
AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig, WireguardConfigurations) AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig, WireguardConfigurations)
AllPeerJobs: PeerJobs = PeerJobs(DashboardConfig, WireguardConfigurations) AllPeerJobs: PeerJobs = PeerJobs(DashboardConfig, WireguardConfigurations)
DashboardLogger: DashboardLogger = DashboardLogger() DashboardLogger = DashboardLogger()
DashboardPlugins: DashboardPlugins = DashboardPlugins(app, WireguardConfigurations) DashboardPlugins = DashboardPlugins(app, WireguardConfigurations)
DashboardWebHooks: DashboardWebHooks = DashboardWebHooks(DashboardConfig) DashboardWebHooks = DashboardWebHooks(DashboardConfig)
NewConfigurationTemplates: NewConfigurationTemplates = NewConfigurationTemplates() NewConfigurationTemplates = NewConfigurationTemplates()
InitWireguardConfigurationsList(startup=True) InitWireguardConfigurationsList(startup=True)
DashboardClients: DashboardClients = DashboardClients(WireguardConfigurations) DashboardClients = DashboardClients(WireguardConfigurations)
app.register_blueprint(createClientBlueprint(WireguardConfigurations, DashboardConfig, DashboardClients)) app.register_blueprint(createClientBlueprint(WireguardConfigurations, DashboardConfig, DashboardClients))
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix") _, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
@@ -213,56 +229,34 @@ _, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
_, app_port = DashboardConfig.GetConfig("Server", "app_port") _, app_port = DashboardConfig.GetConfig("Server", "app_port")
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path") _, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
''' '''
API Routes API Routes
''' '''
@app.before_request def _enforce_session_auth():
def auth_req(): """Enforce session authentication for non-API key access."""
if request.method.lower() == 'options': white_list = [
return ResponseObject(True) '/static/',
'validateAuthentication',
DashboardConfig.APIAccessed = False 'authenticate',
if "api" in request.path: 'getDashboardConfiguration',
if str(request.method) == "GET": 'getDashboardTheme',
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args)) 'getDashboardVersion',
elif str(request.method) == "POST": 'sharePeer/get',
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Request Args: {str(request.args)} Body:{str(request.get_json())}") 'isTotpEnabled',
'locale',
authenticationRequired = DashboardConfig.GetConfig("Server", "auth_req")[1]
d = request.headers
if authenticationRequired:
apiKey = d.get('wg-dashboard-apikey')
apiKeyEnabled = DashboardConfig.GetConfig("Server", "dashboard_api_key")[1]
if apiKey is not None and len(apiKey) > 0 and apiKeyEnabled:
apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}")
if not apiKeyExist:
DashboardConfig.APIAccessed = False
response = Flask.make_response(app, {
"status": False,
"message": "API Key does not exist",
"data": None
})
response.content_type = "application/json"
response.status_code = 401
return response
DashboardConfig.APIAccessed = True
else:
DashboardConfig.APIAccessed = False
whiteList = [
'/static/', 'validateAuthentication', 'authenticate', 'getDashboardConfiguration',
'getDashboardTheme', 'getDashboardVersion', 'sharePeer/get', 'isTotpEnabled', 'locale',
'/fileDownload', '/fileDownload',
'/client' '/client'
] ]
if (("username" not in session or session.get("role") != "admin") path_ok = (
and (f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}/" != request.path ("username" in session and session.get("role") == "admin")
and f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}" != request.path) or (f"{APP_PREFIX}/" == request.path or f"{APP_PREFIX}" == request.path)
and len(list(filter(lambda x : x not in request.path, whiteList))) == len(whiteList) or not all(sub not in request.path for sub in white_list)
): )
if not path_ok:
response = Flask.make_response(app, { response = Flask.make_response(app, {
"status": False, "status": False,
"message": "Unauthorized access.", "message": "Unauthorized access.",
@@ -272,60 +266,118 @@ def auth_req():
response.status_code = 401 response.status_code = 401
return response return response
def _login_with_token(key):
auth_token = hashlib.sha256(f"{key}{datetime.now()}".encode()).hexdigest()
session.update({'role': 'admin', 'username': auth_token})
session.permanent = True
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
resp.set_cookie("authToken", auth_token)
return resp
def _login_with_credentials(data):
username = data.get('username')
password = data.get('password')
totp_code = data.get('totp')
valid_password = bcrypt.checkpw(password.encode("utf-8"),
DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
totp_enabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
totp_valid = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now() == totp_code if totp_enabled else True
if username == DashboardConfig.GetConfig("Account", "username")[1] and valid_password and totp_valid:
auth_token = hashlib.sha256(f"{username}{datetime.now()}".encode()).hexdigest()
session.update({'role': 'admin', 'username': auth_token})
session.permanent = True
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {username}")
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
resp.set_cookie("authToken", auth_token)
return resp
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {username}")
msg = "Sorry, your username, password or OTP is incorrect." if totp_enabled else "Sorry, your username or password is incorrect."
return ResponseObject(False, msg)
@app.before_request
def auth_req():
# Skip preflight requests
if request.method.lower() == 'options':
return ResponseObject(True)
DashboardConfig.APIAccessed = False
# Logging
if "api" in request.path:
log_message = str(request.args) if request.method.upper() == "GET" else f"Request Args: {str(request.args)} Body:{str(request.get_json())}"
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=log_message)
authentication_required = DashboardConfig.GetConfig("Server", "auth_req")[1]
headers = request.headers
if authentication_required:
api_key = headers.get('wg-dashboard-apikey')
api_key_enabled = DashboardConfig.GetConfig("Server", "dashboard_api_key")[1]
# API key authentication
if api_key and api_key_enabled:
api_key_exists = any(k.Key == api_key for k in DashboardConfig.DashboardAPIKeys)
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {api_key_exists} - Key: {api_key}")
if not api_key_exists:
DashboardConfig.APIAccessed = False
response = Flask.make_response(app, {
"status": False,
"message": "API Key does not exist",
"data": None
})
response.content_type = "application/json"
response.status_code = 401
return response
DashboardConfig.APIAccessed = True
else:
DashboardConfig.APIAccessed = False
_enforce_session_auth()
@app.route(f'{APP_PREFIX}/api/handshake', methods=["GET", "OPTIONS"]) @app.route(f'{APP_PREFIX}/api/handshake', methods=["GET", "OPTIONS"])
def API_Handshake(): def API_Handshake():
return ResponseObject(True) return ResponseObject(True)
@app.get(f'{APP_PREFIX}/api/validateAuthentication') @app.get(f'{APP_PREFIX}/api/validateAuthentication')
def API_ValidateAuthentication(): def API_ValidateAuthentication():
token = request.cookies.get("authToken") token = request.cookies.get("authToken")
if DashboardConfig.GetConfig("Server", "auth_req")[1]: auth_required = DashboardConfig.GetConfig("Server", "auth_req")[1]
if token is None or token == "" or "username" not in session or session["username"] != token:
return ResponseObject(False, "Invalid authentication.") if auth_required and (not token or "username" not in session or session["username"] != token):
return ResponseObject(False, "Invalid authentication.",
status_code=401)
return ResponseObject(True) return ResponseObject(True)
@app.get(f'{APP_PREFIX}/api/requireAuthentication') @app.get(f'{APP_PREFIX}/api/requireAuthentication')
def API_RequireAuthentication(): def API_RequireAuthentication():
return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1]) return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1])
@app.post(f'{APP_PREFIX}/api/authenticate') @app.post(f'{APP_PREFIX}/api/authenticate')
def API_AuthenticateLogin(): def API_AuthenticateLogin():
data = request.get_json() data = request.get_json()
if not DashboardConfig.GetConfig("Server", "auth_req")[1]: auth_req = DashboardConfig.GetConfig("Server", "auth_req")[1]
if not auth_req:
return ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1]) return ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
# API key login
if DashboardConfig.APIAccessed: if DashboardConfig.APIAccessed:
authToken = hashlib.sha256(f"{request.headers.get('wg-dashboard-apikey')}{datetime.now()}".encode()).hexdigest() return _login_with_token(request.headers.get('wg-dashboard-apikey'))
session['role'] = 'admin'
session['username'] = authToken
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
resp.set_cookie("authToken", authToken)
session.permanent = True
return resp
valid = bcrypt.checkpw(data['password'].encode("utf-8"),
DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
totpValid = False
if totpEnabled:
totpValid = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now() == data['totp']
if (valid # User login
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1] return _login_with_credentials(data)
and ((totpEnabled and totpValid) or not totpEnabled)
):
authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
session['role'] = 'admin'
session['username'] = authToken
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
resp.set_cookie("authToken", authToken)
session.permanent = True
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {data['username']}")
return resp
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {data['username']}")
if totpEnabled:
return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
else:
return ResponseObject(False, "Sorry, your username or password is incorrect.")
@app.get(f'{APP_PREFIX}/api/signout') @app.get(f'{APP_PREFIX}/api/signout')
def API_SignOut(): def API_SignOut():
@@ -337,7 +389,7 @@ def API_SignOut():
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurations') @app.get(f'{APP_PREFIX}/api/getWireguardConfigurations')
def API_getWireguardConfigurations(): def API_getWireguardConfigurations():
InitWireguardConfigurationsList() InitWireguardConfigurationsList()
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()]) return ResponseObject(data=list(WireguardConfigurations.values()))
@app.get(f'{APP_PREFIX}/api/newConfigurationTemplates') @app.get(f'{APP_PREFIX}/api/newConfigurationTemplates')
def API_NewConfigurationTemplates(): def API_NewConfigurationTemplates():
@@ -350,193 +402,255 @@ def API_NewConfigurationTemplates_CreateTemplate():
@app.post(f'{APP_PREFIX}/api/newConfigurationTemplates/updateTemplate') @app.post(f'{APP_PREFIX}/api/newConfigurationTemplates/updateTemplate')
def API_NewConfigurationTemplates_UpdateTemplate(): def API_NewConfigurationTemplates_UpdateTemplate():
data = request.get_json() data = request.get_json()
template = data.get('Template', None) template = data.get('Template')
if not template: if not template:
return ResponseObject(False, "Please provide template") return ResponseObject(False, "Please provide template",
status_code=400)
status, msg = NewConfigurationTemplates.UpdateTemplate(template) status, msg = NewConfigurationTemplates.UpdateTemplate(template)
return ResponseObject(status, msg) return ResponseObject(status, msg)
@app.post(f'{APP_PREFIX}/api/newConfigurationTemplates/deleteTemplate') @app.post(f'{APP_PREFIX}/api/newConfigurationTemplates/deleteTemplate')
def API_NewConfigurationTemplates_DeleteTemplate(): def API_NewConfigurationTemplates_DeleteTemplate():
data = request.get_json() data = request.get_json()
template = data.get('Template', None) template = data.get('Template')
if not template: if not template:
return ResponseObject(False, "Please provide template") return ResponseObject(False, "Please provide template",
status_code=400)
status, msg = NewConfigurationTemplates.DeleteTemplate(template) status, msg = NewConfigurationTemplates.DeleteTemplate(template)
return ResponseObject(status, msg) return ResponseObject(status, msg)
@app.post(f'{APP_PREFIX}/api/addWireguardConfiguration') @app.post(f'{APP_PREFIX}/api/addWireguardConfiguration')
def API_addWireguardConfiguration(): def API_addWireguardConfiguration():
data = request.get_json() data = request.get_json()
requiredKeys = [ protocol = data.get("Protocol")
"ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol"
]
for i in requiredKeys:
if i not in data.keys():
return ResponseObject(False, "Please provide all required parameters.")
if data.get("Protocol") not in ProtocolsEnabled(): required_keys = {"ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol"}
return ResponseObject(False, "Please provide a valid protocol: wg / awg.") if not required_keys.issubset(data.keys()):
return ResponseObject(False, "Please provide all required parameters.", status_code=400)
# Check duplicate names, ports, address if protocol not in ProtocolsEnabled():
for i in WireguardConfigurations.values(): return ResponseObject(False, "Please provide a valid protocol: wg / awg.", status_code=400)
if i.Name == data['ConfigurationName']:
return ResponseObject(False,
f"Already have a configuration with the name \"{data['ConfigurationName']}\"",
"ConfigurationName")
if str(i.ListenPort) == str(data["ListenPort"]): for cfg in WireguardConfigurations.values():
return ResponseObject(False, duplicates = {
f"Already have a configuration with the port \"{data['ListenPort']}\"", "ConfigurationName": cfg.Name == data['ConfigurationName'],
"ListenPort") "ListenPort": str(cfg.ListenPort) == str(data["ListenPort"]),
"Address": cfg.Address == data["Address"]
}
for key, is_duplicate in duplicates.items():
if is_duplicate:
return ResponseObject(
False,
f"Already have a configuration with the {key.lower()} \"{data[key]}\"",
key,
status_code=400
)
if i.Address == data["Address"]: paths = {
return ResponseObject(False,
f"Already have a configuration with the address \"{data['Address']}\"",
"Address")
if "Backup" in data.keys():
path = {
"wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1], "wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1] "awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
} }
if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and if "Backup" in data:
os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))): backup_file = data["Backup"]
protocol = "wg" protocol_detected = None
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and for proto, base_path in paths.items():
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))): conf_path = os.path.join(base_path, 'WGDashboard_Backup', backup_file)
protocol = "awg" sql_path = os.path.join(base_path, 'WGDashboard_Backup', backup_file.replace('.conf', '.sql'))
else: if os.path.exists(conf_path) and os.path.exists(sql_path):
return ResponseObject(False, "Backup does not exist") protocol_detected = proto
break
if not protocol_detected:
return ResponseObject(False, "Backup does not exist", status_code=400)
shutil.copy( shutil.copy(
os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]), os.path.join(paths[protocol_detected], 'WGDashboard_Backup', backup_file),
os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf') os.path.join(paths[protocol_detected], f'{data["ConfigurationName"]}.conf')
) )
WireguardConfigurations[data['ConfigurationName']] = ( protocol = protocol_detected # Use backup protocol
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else (
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName']))
else: else:
WireguardConfigurations[data['ConfigurationName']] = ( conf_path = os.path.join(paths[protocol], f'{data["ConfigurationName"]}.conf')
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else ( if not os.path.exists(conf_path):
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) with open(conf_path, 'w') as f:
f.write(
f"[Interface]\n"
f"Address = {data['Address']}\n"
f"ListenPort = {data['ListenPort']}\n"
f"PrivateKey = {data['PrivateKey']}\n"
)
os.chmod(conf_path, 0o600) # secure file permissions
ConfigClass = WireguardConfiguration if protocol == "wg" else AmneziaWireguardConfiguration
WireguardConfigurations[data['ConfigurationName']] = ConfigClass(
DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks,
data=data, name=data['ConfigurationName']
)
return ResponseObject() return ResponseObject()
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration') @app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
def API_toggleWireguardConfiguration(): def API_toggleWireguardConfiguration():
configurationName = request.args.get('configurationName') configuration_name = request.args.get('configurationName')
if configurationName is None or len(
configurationName) == 0 or configurationName not in WireguardConfigurations.keys(): if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Please provide a valid configuration name", status_code=404) return ResponseObject(False, "Please provide a valid configuration name",
toggleStatus, msg = WireguardConfigurations[configurationName].toggleConfiguration() status_code=404)
return ResponseObject(toggleStatus, msg, WireguardConfigurations[configurationName].Status)
target_configuration = WireguardConfigurations[configuration_name]
status, msg = target_configuration.toggleConfiguration()
configuration_status = target_configuration.Status
return ResponseObject(status, msg, configuration_status)
@app.post(f'{APP_PREFIX}/api/updateWireguardConfiguration') @app.post(f'{APP_PREFIX}/api/updateWireguardConfiguration')
def API_updateWireguardConfiguration(): def API_updateWireguardConfiguration():
data = request.get_json() data = request.get_json() or {}
requiredKeys = ["Name"]
for i in requiredKeys:
if i not in data.keys():
return ResponseObject(False, "Please provide these following field: " + ", ".join(requiredKeys))
name = data.get("Name") name = data.get("Name")
if name not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist", status_code=404)
status, msg = WireguardConfigurations[name].updateConfigurationSettings(data) if not name:
return ResponseObject(False, "Please provide the field: Name",
status_code=400)
if name not in WireguardConfigurations:
return ResponseObject(False, "Configuration does not exist",
status_code=404)
target_configuration = WireguardConfigurations[name]
status, msg = target_configuration.updateConfigurationSettings(data)
return ResponseObject(status, msg, target_configuration)
return ResponseObject(status, message=msg, data=WireguardConfigurations[name])
@app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationInfo') @app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationInfo')
def API_updateWireguardConfigurationInfo(): def API_updateWireguardConfigurationInfo():
data = request.get_json() data = request.get_json() or {}
name = data.get('Name') name = data.get('Name')
key = data.get('Key') key = data.get('Key')
value = data.get('Value') value = data.get('Value')
if not all([data, key, name]):
return ResponseObject(status=False, message="Please provide configuration name, key and value") if not all([name, key, value]): # Required values
if name not in WireguardConfigurations.keys(): return ResponseObject(False, "Please provide configuration name, key, and value")
if name not in WireguardConfigurations:
return ResponseObject(False, "Configuration does not exist", status_code=404) return ResponseObject(False, "Configuration does not exist", status_code=404)
status, msg, key = WireguardConfigurations[name].updateConfigurationInfo(key, value) target_configuration = WireguardConfigurations[name]
status, msg, key = target_configuration.updateConfigurationInfo(key, value)
return ResponseObject(status, msg, key)
return ResponseObject(status=status, message=msg, data=key)
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRawFile') @app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRawFile')
def API_GetWireguardConfigurationRawFile(): def API_getWireguardConfigurationRawFile():
configurationName = request.args.get('configurationName') configuration_name = request.args.get('configurationName')
if configurationName is None or len(
configurationName) == 0 or configurationName not in WireguardConfigurations.keys(): if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Please provide a valid configuration name", status_code=404) return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
config = WireguardConfigurations[configuration_name]
return ResponseObject(data={ return ResponseObject(data={
"path": WireguardConfigurations[configurationName].configPath, "path": config.configPath,
"content": WireguardConfigurations[configurationName].getRawConfigurationFile() "content": config.getRawConfigurationFile()
}) })
@app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationRawFile') @app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationRawFile')
def API_UpdateWireguardConfigurationRawFile(): def API_UpdateWireguardConfigurationRawFile():
data = request.get_json() data = request.get_json() or {}
configurationName = data.get('configurationName') configuration_name = data.get('configurationName')
rawConfiguration = data.get('rawConfiguration') raw_configuration = data.get('rawConfiguration')
if configurationName is None or len(
configurationName) == 0 or configurationName not in WireguardConfigurations.keys(): if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Please provide a valid configuration name") return ResponseObject(False, "Please provide a valid configuration name")
if rawConfiguration is None or len(rawConfiguration) == 0:
if not raw_configuration:
return ResponseObject(False, "Please provide content") return ResponseObject(False, "Please provide content")
status, err = WireguardConfigurations[configurationName].updateRawConfigurationFile(rawConfiguration) config = WireguardConfigurations[configuration_name]
status, err = config.updateRawConfigurationFile(raw_configuration)
return ResponseObject(status=status, message=err) return ResponseObject(status, err)
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfiguration') @app.post(f'{APP_PREFIX}/api/deleteWireguardConfiguration')
def API_deleteWireguardConfiguration(): def API_deleteWireguardConfiguration():
data = request.get_json() data = request.get_json() or {}
if "ConfigurationName" not in data.keys() or data.get("ConfigurationName") is None or data.get("ConfigurationName") not in WireguardConfigurations.keys(): configuration_name = data.get("ConfigurationName")
return ResponseObject(False, "Please provide the configuration name you want to delete", status_code=404)
rp = WireguardConfigurations.pop(data.get("ConfigurationName"))
if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Please provide the configuration name you want to delete", status_code=404)
rp = WireguardConfigurations.pop(configuration_name)
status = rp.deleteConfiguration() status = rp.deleteConfiguration()
if not status: if not status:
WireguardConfigurations[data.get("ConfigurationName")] = rp WireguardConfigurations[configuration_name] = rp
return ResponseObject(status) return ResponseObject(status)
@app.post(f'{APP_PREFIX}/api/renameWireguardConfiguration') @app.post(f'{APP_PREFIX}/api/renameWireguardConfiguration')
def API_renameWireguardConfiguration(): def API_renameWireguardConfiguration():
data = request.get_json() data = request.get_json() or {}
keys = ["ConfigurationName", "NewConfigurationName"]
for k in keys:
if (k not in data.keys() or data.get(k) is None or len(data.get(k)) == 0 or
(k == "ConfigurationName" and data.get(k) not in WireguardConfigurations.keys())):
return ResponseObject(False, "Please provide the configuration name you want to rename", status_code=404)
if data.get("NewConfigurationName") in WireguardConfigurations.keys(): old_name = data.get("ConfigurationName")
return ResponseObject(False, "Configuration name already exist", status_code=400) new_name = data.get("NewConfigurationName")
rc = WireguardConfigurations.pop(data.get("ConfigurationName")) if not old_name or old_name not in WireguardConfigurations:
return ResponseObject(False, "Please provide a valid configuration name to rename", status_code=404)
if not new_name:
return ResponseObject(False, "Please provide a new configuration name", status=400)
if new_name in WireguardConfigurations:
return ResponseObject(False, "The configuration name already exists", status_code=400)
rc = WireguardConfigurations.pop(old_name)
status, message = rc.renameConfiguration(new_name)
status, message = rc.renameConfiguration(data.get("NewConfigurationName"))
if status: if status:
WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName"))) if rc.Protocol == 'wg':
ConfigClass = WireguardConfiguration
else: else:
WireguardConfigurations[data.get("ConfigurationName")] = rc ConfigClass = AmneziaWireguardConfiguration
WireguardConfigurations[new_name] = ConfigClass(
DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, new_name
)
else:
WireguardConfigurations[old_name] = rc
return ResponseObject(status, message) return ResponseObject(status, message)
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRealtimeTraffic') @app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRealtimeTraffic')
def API_getWireguardConfigurationRealtimeTraffic(): def API_getWireguardConfigurationRealtimeTraffic():
configurationName = request.args.get('configurationName') configuration_name = requests.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Configuration does not exist", status_code=404) return ResponseObject(False, "Configuration does not exist", status_code=404)
return ResponseObject(data=WireguardConfigurations[configurationName].getRealtimeTrafficUsage())
rt_traffic_usage = WireguardConfigurations[configuration_name]
return ResponseObject(data=rt_traffic_usage)
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationBackup') @app.get(f'{APP_PREFIX}/api/getWireguardConfigurationBackup')
def API_getWireguardConfigurationBackup(): def API_getWireguardConfigurationBackup():
configurationName = request.args.get('configurationName') configuration_name = request.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Configuration does not exist", status_code=404) return ResponseObject(False, "Configuration does not exist", status_code=404)
return ResponseObject(data=WireguardConfigurations[configurationName].getBackups())
target_configuration = WireguardConfigurations[configuration_name]
return ResponseObject(data=target_configuration.getBackups())
@app.get(f'{APP_PREFIX}/api/getAllWireguardConfigurationBackup') @app.get(f'{APP_PREFIX}/api/getAllWireguardConfigurationBackup')
def API_getAllWireguardConfigurationBackup(): def API_getAllWireguardConfigurationBackup():
@@ -544,47 +658,86 @@ def API_getAllWireguardConfigurationBackup():
"ExistingConfigurations": {}, "ExistingConfigurations": {},
"NonExistingConfigurations": {} "NonExistingConfigurations": {}
} }
existingConfiguration = WireguardConfigurations.keys()
for i in existingConfiguration: existing_configurations = WireguardConfigurations.keys()
b = WireguardConfigurations[i].getBackups(True)
if len(b) > 0: for single_conf in existing_configurations:
data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True) backups = WireguardConfigurations[single_conf].getBackups(True)
if len(backups) > 0:
data['ExistingConfigurations'][single_conf] = WireguardConfigurations[single_conf].getBackups(True)
for protocol in ProtocolsEnabled(): for protocol in ProtocolsEnabled():
directory = os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup') config_path_info = DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")
if os.path.exists(directory): configuration_path = config_path_info[1]
files = [(file, os.path.getctime(os.path.join(directory, file))) backup_directory = os.path.join(configuration_path, 'WGDashboard_Backup')
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
files.sort(key=lambda x: x[1], reverse=True)
for f, ct in files: if not os.path.exists(backup_directory):
if RegexMatch(r"^(.*)_(.*)\.(conf)$", f): continue
s = re.search(r"^(.*)_(.*)\.(conf)$", f)
name = s.group(1)
if name not in existingConfiguration:
if name not in data['NonExistingConfigurations'].keys():
data['NonExistingConfigurations'][name] = []
date = s.group(2) backup_files = []
d = { for file_name in os.listdir(backup_directory):
full_file_path = os.path.join(backup_directory, file_name)
if os.path.isfile(full_file_path):
creation_time = os.path.getctime(full_file_path)
backup_files.append((file_name, creation_time))
backup_files.sort(key=lambda file_info: file_info[1], reverse=True)
for file_name, creation_time in backup_files:
pattern = r"^(.*)_(.*)\.conf$"
match_result = re.match(pattern, file_name)
if not match_result:
continue
configuration_name = match_result.group(1)
backup_date = match_result.group(2)
if configuration_name in existing_configurations:
continue
if 'NonExistingConfigurations' not in data:
data['NonExistingConfigurations'] = {}
if configuration_name not in data['NonExistingConfigurations']:
data['NonExistingConfigurations'][configuration_name] = []
configuration_file_path = os.path.join(backup_directory, file_name)
with open(configuration_file_path, 'r') as configuration_file:
configuration_content = configuration_file.read()
backup_data = {
"protocol": protocol, "protocol": protocol,
"filename": f, "filename": file_name,
"backupDate": date, "backupDate": backup_date,
"content": open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f), 'r').read() "content": configuration_content
} }
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
d['database'] = True sql_file_name = file_name.replace(".conf", ".sql")
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() sql_file_path = os.path.join(backup_directory, sql_file_name)
data['NonExistingConfigurations'][name].append(d)
if os.path.isfile(sql_file_path):
with open(sql_file_path, 'r') as sql_file:
sql_content = sql_file.read()
backup_data["database"] = True
backup_data["databaseContent"] = sql_content
data['NonExistingConfigurations'][configuration_name].append(backup_data)
return ResponseObject(data=data) return ResponseObject(data=data)
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup') @app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
def API_createWireguardConfigurationBackup(): def API_createWireguardConfigurationBackup():
configurationName = request.args.get('configurationName') configuration_name = request.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
if not configuration_name or configuration_name not in WireguardConfigurations:
return ResponseObject(False, "Configuration does not exist", status_code=404) return ResponseObject(False, "Configuration does not exist", status_code=404)
return ResponseObject(status=WireguardConfigurations[configurationName].backupConfigurationFile()[0],
data=WireguardConfigurations[configurationName].getBackups()) conf_backup_file = WireguardConfigurations[configuration_name].backupConfigurationFile()[0]
conf_backups = WireguardConfigurations[configuration_name].getBackups()
return ResponseObject(status=conf_backup_file,data=conf_backups)
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfigurationBackup') @app.post(f'{APP_PREFIX}/api/deleteWireguardConfigurationBackup')
def API_deleteWireguardConfigurationBackup(): def API_deleteWireguardConfigurationBackup():
@@ -616,18 +769,19 @@ def API_downloadWireguardConfigurationBackup():
@app.post(f'{APP_PREFIX}/api/restoreWireguardConfigurationBackup') @app.post(f'{APP_PREFIX}/api/restoreWireguardConfigurationBackup')
def API_restoreWireguardConfigurationBackup(): def API_restoreWireguardConfigurationBackup():
data = request.get_json() data = request.get_json()
configuration_name = data['ConfigurationName']
backup_file_name = data['BackupFileName']
if ("ConfigurationName" not in data.keys() or if ("ConfigurationName" not in data.keys() or
"BackupFileName" not in data.keys() or "BackupFileName" not in data.keys() or
len(data['ConfigurationName']) == 0 or len(data['ConfigurationName']) == 0 or
len(data['BackupFileName']) == 0): len(data['BackupFileName']) == 0):
return ResponseObject(False, return ResponseObject(False,"Please provide ConfigurationName and BackupFileName in body", status_code=400)
"Please provide ConfigurationName and BackupFileName in body", status_code=400)
configurationName = data['ConfigurationName'] if configuration_name not in WireguardConfigurations.keys():
backupFileName = data['BackupFileName']
if configurationName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist", status_code=404) return ResponseObject(False, "Configuration does not exist", status_code=404)
status = WireguardConfigurations[configurationName].restoreBackup(backupFileName) status = WireguardConfigurations[configuration_name].restoreBackup(backup_file_name)
return ResponseObject(status=status, message=(None if status else 'Restore backup failed')) return ResponseObject(status=status, message=(None if status else 'Restore backup failed'))
@app.get(f'{APP_PREFIX}/api/getDashboardConfiguration') @app.get(f'{APP_PREFIX}/api/getDashboardConfiguration')
@@ -1369,31 +1523,6 @@ def API_Welcome_Finish():
DashboardConfig.SetConfig("Other", "welcome_session", False) DashboardConfig.SetConfig("Other", "welcome_session", False)
return ResponseObject() return ResponseObject()
class Locale:
def __init__(self):
self.localePath = './static/locales/'
self.activeLanguages = {}
with open(os.path.join(f"{self.localePath}supported_locales.json"), "r") as f:
self.activeLanguages = sorted(json.loads(''.join(f.readlines())), key=lambda x : x['lang_name'])
def getLanguage(self) -> dict | None:
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
if currentLanguage == "en":
return None
if os.path.exists(os.path.join(f"{self.localePath}{currentLanguage}.json")):
with open(os.path.join(f"{self.localePath}{currentLanguage}.json"), "r") as f:
return dict(json.loads(''.join(f.readlines())))
else:
return None
def updateLanguage(self, lang_id):
if not os.path.exists(os.path.join(f"{self.localePath}{lang_id}.json")):
DashboardConfig.SetConfig("Server", "dashboard_language", "en-US")
else:
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
Locale = Locale()
@app.get(f'{APP_PREFIX}/api/locale') @app.get(f'{APP_PREFIX}/api/locale')
def API_Locale_CurrentLang(): def API_Locale_CurrentLang():
return ResponseObject(data=Locale.getLanguage()) return ResponseObject(data=Locale.getLanguage())
@@ -1478,6 +1607,31 @@ def API_SystemStatus():
def API_ProtocolsEnabled(): def API_ProtocolsEnabled():
return ResponseObject(data=ProtocolsEnabled()) return ResponseObject(data=ProtocolsEnabled())
class Locale:
def __init__(self):
self.localePath = './static/locales/'
self.activeLanguages = {}
with open(os.path.join(f"{self.localePath}supported_locales.json"), "r") as f:
self.activeLanguages = sorted(json.loads(''.join(f.readlines())), key=lambda x : x['lang_name'])
def getLanguage(self) -> dict | None:
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
if currentLanguage == "en":
return None
if os.path.exists(os.path.join(f"{self.localePath}{currentLanguage}.json")):
with open(os.path.join(f"{self.localePath}{currentLanguage}.json"), "r") as f:
return dict(json.loads(''.join(f.readlines())))
else:
return None
def updateLanguage(self, lang_id):
if not os.path.exists(os.path.join(f"{self.localePath}{lang_id}.json")):
DashboardConfig.SetConfig("Server", "dashboard_language", "en-US")
else:
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
Locale = Locale()
''' '''
OIDC Controller OIDC Controller
''' '''

View File

@@ -9,7 +9,7 @@
"version": "4.3.0.1", "version": "4.3.0.1",
"dependencies": { "dependencies": {
"@volar/language-server": "2.4.23", "@volar/language-server": "2.4.23",
"@vue/language-server": "3.0.5", "@vue/language-server": "3.0.7",
"@vuepic/vue-datepicker": "^11.0.2", "@vuepic/vue-datepicker": "^11.0.2",
"@vueuse/core": "^13.5.0", "@vueuse/core": "^13.5.0",
"@vueuse/shared": "^13.5.0", "@vueuse/shared": "^13.5.0",
@@ -37,7 +37,7 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.0.5" "vite": "^7.1.6"
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
@@ -411,7 +411,7 @@
}, },
"node_modules/@emmetio/abbreviation": { "node_modules/@emmetio/abbreviation": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmmirror.com/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz", "resolved": "https://registry.npmjs.org/@emmetio/abbreviation/-/abbreviation-2.3.3.tgz",
"integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==", "integrity": "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -420,7 +420,7 @@
}, },
"node_modules/@emmetio/css-abbreviation": { "node_modules/@emmetio/css-abbreviation": {
"version": "2.1.8", "version": "2.1.8",
"resolved": "https://registry.npmmirror.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz", "resolved": "https://registry.npmjs.org/@emmetio/css-abbreviation/-/css-abbreviation-2.1.8.tgz",
"integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==", "integrity": "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -438,7 +438,7 @@
}, },
"node_modules/@emmetio/html-matcher": { "node_modules/@emmetio/html-matcher": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmmirror.com/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@emmetio/html-matcher/-/html-matcher-1.3.0.tgz",
"integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==", "integrity": "sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
@@ -447,19 +447,19 @@
}, },
"node_modules/@emmetio/scanner": { "node_modules/@emmetio/scanner": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmmirror.com/@emmetio/scanner/-/scanner-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@emmetio/scanner/-/scanner-1.0.4.tgz",
"integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==", "integrity": "sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@emmetio/stream-reader": { "node_modules/@emmetio/stream-reader": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmmirror.com/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@emmetio/stream-reader/-/stream-reader-2.2.0.tgz",
"integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==", "integrity": "sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@emmetio/stream-reader-utils": { "node_modules/@emmetio/stream-reader-utils": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmmirror.com/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@emmetio/stream-reader-utils/-/stream-reader-utils-0.1.0.tgz",
"integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==", "integrity": "sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==",
"license": "MIT" "license": "MIT"
}, },
@@ -1030,7 +1030,7 @@
}, },
"node_modules/@johnsoncodehk/pug-beautify": { "node_modules/@johnsoncodehk/pug-beautify": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmmirror.com/@johnsoncodehk/pug-beautify/-/pug-beautify-0.2.2.tgz", "resolved": "https://registry.npmjs.org/@johnsoncodehk/pug-beautify/-/pug-beautify-0.2.2.tgz",
"integrity": "sha512-qqNS/YD0Nck5wtQLCPHAfGVgWbbGafxSPjNh0ekYPFSNNqnDH2kamnduzYly8IiADmeVx/MfAE1njMEjVeHTMA==", "integrity": "sha512-qqNS/YD0Nck5wtQLCPHAfGVgWbbGafxSPjNh0ekYPFSNNqnDH2kamnduzYly8IiADmeVx/MfAE1njMEjVeHTMA==",
"license": "MIT" "license": "MIT"
}, },
@@ -1667,7 +1667,7 @@
}, },
"node_modules/@vscode/emmet-helper": { "node_modules/@vscode/emmet-helper": {
"version": "2.11.0", "version": "2.11.0",
"resolved": "https://registry.npmmirror.com/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz", "resolved": "https://registry.npmjs.org/@vscode/emmet-helper/-/emmet-helper-2.11.0.tgz",
"integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==", "integrity": "sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1680,7 +1680,7 @@
}, },
"node_modules/@vscode/l10n": { "node_modules/@vscode/l10n": {
"version": "0.0.18", "version": "0.0.18",
"resolved": "https://registry.npmmirror.com/@vscode/l10n/-/l10n-0.0.18.tgz", "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.18.tgz",
"integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==", "integrity": "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==",
"license": "MIT" "license": "MIT"
}, },
@@ -1736,7 +1736,7 @@
}, },
"node_modules/@vue/compiler-vue2": { "node_modules/@vue/compiler-vue2": {
"version": "2.7.16", "version": "2.7.16",
"resolved": "https://registry.npmmirror.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
"integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1778,12 +1778,12 @@
} }
}, },
"node_modules/@vue/language-core": { "node_modules/@vue/language-core": {
"version": "3.0.5", "version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.0.7.tgz",
"integrity": "sha512-gCEjn9Ik7I/seHVNIEipOm8W+f3/kg60e8s1IgIkMYma2wu9ZGUTMv3mSL2bX+Md2L8fslceJ4SU8j1fgSRoiw==", "integrity": "sha512-0sqqyqJ0Gn33JH3TdIsZLCZZ8Gr4kwlg8iYOnOrDDkJKSjFurlQY/bEFQx5zs7SX2C/bjMkmPYq/NiyY1fTOkw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-core": "2.4.22", "@volar/language-core": "2.4.23",
"@vue/compiler-dom": "^3.5.0", "@vue/compiler-dom": "^3.5.0",
"@vue/compiler-vue2": "^2.7.16", "@vue/compiler-vue2": "^2.7.16",
"@vue/shared": "^3.5.0", "@vue/shared": "^3.5.0",
@@ -1801,31 +1801,16 @@
} }
} }
}, },
"node_modules/@vue/language-core/node_modules/@volar/language-core": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.22.tgz",
"integrity": "sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==",
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.22"
}
},
"node_modules/@vue/language-core/node_modules/@volar/source-map": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.22.tgz",
"integrity": "sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==",
"license": "MIT"
},
"node_modules/@vue/language-server": { "node_modules/@vue/language-server": {
"version": "3.0.5", "version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@vue/language-server/-/language-server-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/language-server/-/language-server-3.0.7.tgz",
"integrity": "sha512-f3SeLh8w4qjE82hZWVTDjTXnJ6PcKuqoDSXs69JPGfSH4yuy7VXC2IIoMhtvnupMxXTYjtJQJKjhe0p/h4whhw==", "integrity": "sha512-bEEAHJhBqWXAaYtqexRH8kggKm98L8/q69A1DvrIY8E2TwLMS1MKBVSMxerTmZ7Zsstl0ZHL6a/q9duMoBKpUQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-server": "2.4.22", "@volar/language-server": "2.4.23",
"@vue/language-core": "3.0.5", "@vue/language-core": "3.0.7",
"@vue/language-service": "3.0.5", "@vue/language-service": "3.0.7",
"@vue/typescript-plugin": "3.0.5", "@vue/typescript-plugin": "3.0.7",
"vscode-uri": "^3.0.8" "vscode-uri": "^3.0.8"
}, },
"bin": { "bin": {
@@ -1835,69 +1820,14 @@
"typescript": "*" "typescript": "*"
} }
}, },
"node_modules/@vue/language-server/node_modules/@volar/language-core": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.22.tgz",
"integrity": "sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==",
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.22"
}
},
"node_modules/@vue/language-server/node_modules/@volar/language-server": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-server/-/language-server-2.4.22.tgz",
"integrity": "sha512-THIGWcQsEJKZU7SjVKPcy4MIamX4qpusKErj33ru7fi2WcD+FmFjYY/F2LIk/C15xEcb34JT1uZBlbO2dfzYSQ==",
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.22",
"@volar/language-service": "2.4.22",
"@volar/typescript": "2.4.22",
"path-browserify": "^1.0.1",
"request-light": "^0.7.0",
"vscode-languageserver": "^9.0.1",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/language-server/node_modules/@volar/language-service": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-service/-/language-service-2.4.22.tgz",
"integrity": "sha512-8TmvOf/6uqaJMBVQIP9kgVpRzMrqLI3nCmWuSIPAldlmwjZTOiN17GA4AL4sTFJUg61xCSyMQWbProNFQ88yew==",
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.22",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/language-server/node_modules/@volar/source-map": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.22.tgz",
"integrity": "sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==",
"license": "MIT"
},
"node_modules/@vue/language-server/node_modules/@volar/typescript": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.22.tgz",
"integrity": "sha512-6ZczlJW1/GWTrNnkmZxJp4qyBt/SGVlcTuCWpI5zLrdPdCZsj66Aff9ZsfFaT3TyjG8zVYgBMYPuCm/eRkpcpQ==",
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.22",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/language-service": { "node_modules/@vue/language-service": {
"version": "3.0.5", "version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@vue/language-service/-/language-service-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/language-service/-/language-service-3.0.7.tgz",
"integrity": "sha512-3EFghz2F8oqQtqrSQjZiDZ+6jjCOrgdqPyJThPsy46EQfwslwFTW1ks87FzJtZR9xyfIpF3+0rxrFUyrWNFU3w==", "integrity": "sha512-v+XLXuWvk4QgHu0TTJjaIwVu/jrjDpX7ISFu+IrihDusVQ80dm68yrH02Rr4xqKhQos0mwpTbjyC56Ld1eI5Uw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/language-service": "2.4.22", "@volar/language-service": "2.4.23",
"@vue/language-core": "3.0.5", "@vue/language-core": "3.0.7",
"@vue/shared": "^3.5.0", "@vue/shared": "^3.5.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"volar-service-css": "0.0.65", "volar-service-css": "0.0.65",
@@ -1911,33 +1841,6 @@
"vscode-uri": "^3.0.8" "vscode-uri": "^3.0.8"
} }
}, },
"node_modules/@vue/language-service/node_modules/@volar/language-core": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.22.tgz",
"integrity": "sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==",
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.22"
}
},
"node_modules/@vue/language-service/node_modules/@volar/language-service": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-service/-/language-service-2.4.22.tgz",
"integrity": "sha512-8TmvOf/6uqaJMBVQIP9kgVpRzMrqLI3nCmWuSIPAldlmwjZTOiN17GA4AL4sTFJUg61xCSyMQWbProNFQ88yew==",
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.22",
"vscode-languageserver-protocol": "^3.17.5",
"vscode-languageserver-textdocument": "^1.0.11",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue/language-service/node_modules/@volar/source-map": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.22.tgz",
"integrity": "sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==",
"license": "MIT"
},
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.21", "version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz", "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz",
@@ -1989,43 +1892,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/typescript-plugin": { "node_modules/@vue/typescript-plugin": {
"version": "3.0.5", "version": "3.0.7",
"resolved": "https://registry.npmmirror.com/@vue/typescript-plugin/-/typescript-plugin-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-3.0.7.tgz",
"integrity": "sha512-iR34R1dtgU75r2zxl62mGXyJVznh4Ne+1cUVQeBN6Ci3R3AAzP3v3vVVNu+QwdS37CtQAiTXfOJdiySQQ64NwQ==", "integrity": "sha512-WBvIkdrRTRPUhcxxqjkqY56rdUkcb+hWSM1lhCko2H9SXPZoDPUN7ZhtX5rrSZWFvduq3lUmahJFW4IT522tIg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@volar/typescript": "2.4.22", "@volar/typescript": "2.4.23",
"@vue/language-core": "3.0.5", "@vue/language-core": "3.0.7",
"@vue/shared": "^3.5.0", "@vue/shared": "^3.5.0",
"path-browserify": "^1.0.1" "path-browserify": "^1.0.1"
} }
}, },
"node_modules/@vue/typescript-plugin/node_modules/@volar/language-core": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.22.tgz",
"integrity": "sha512-gp4M7Di5KgNyIyO903wTClYBavRt6UyFNpc5LWfyZr1lBsTUY+QrVZfmbNF2aCyfklBOVk9YC4p+zkwoyT7ECg==",
"license": "MIT",
"dependencies": {
"@volar/source-map": "2.4.22"
}
},
"node_modules/@vue/typescript-plugin/node_modules/@volar/source-map": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.22.tgz",
"integrity": "sha512-L2nVr/1vei0xKRgO2tYVXtJYd09HTRjaZi418e85Q+QdbbqA8h7bBjfNyPPSsjnrOO4l4kaAo78c8SQUAdHvgA==",
"license": "MIT"
},
"node_modules/@vue/typescript-plugin/node_modules/@volar/typescript": {
"version": "2.4.22",
"resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.22.tgz",
"integrity": "sha512-6ZczlJW1/GWTrNnkmZxJp4qyBt/SGVlcTuCWpI5zLrdPdCZsj66Aff9ZsfFaT3TyjG8zVYgBMYPuCm/eRkpcpQ==",
"license": "MIT",
"dependencies": {
"@volar/language-core": "2.4.22",
"path-browserify": "^1.0.1",
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vuepic/vue-datepicker": { "node_modules/@vuepic/vue-datepicker": {
"version": "11.0.2", "version": "11.0.2",
"resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-11.0.2.tgz", "resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-11.0.2.tgz",
@@ -2102,7 +1979,7 @@
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "7.4.1", "version": "7.4.1",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -2173,7 +2050,7 @@
}, },
"node_modules/alien-signals": { "node_modules/alien-signals": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-2.0.7.tgz", "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-2.0.7.tgz",
"integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==", "integrity": "sha512-wE7y3jmYeb0+h6mr5BOovuqhFv22O/MV9j5p0ndJsa7z1zJNPGQ4ph5pQk/kTTCWRC3xsA4SmtwmkzQO+7NCNg==",
"license": "MIT" "license": "MIT"
}, },
@@ -2609,7 +2486,7 @@
}, },
"node_modules/call-bound": { "node_modules/call-bound": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2650,7 +2527,7 @@
}, },
"node_modules/character-parser": { "node_modules/character-parser": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmmirror.com/character-parser/-/character-parser-2.2.0.tgz", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz",
"integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -3014,7 +2891,7 @@
}, },
"node_modules/de-indent": { "node_modules/de-indent": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"license": "MIT" "license": "MIT"
}, },
@@ -3378,7 +3255,7 @@
}, },
"node_modules/emmet": { "node_modules/emmet": {
"version": "2.4.11", "version": "2.4.11",
"resolved": "https://registry.npmmirror.com/emmet/-/emmet-2.4.11.tgz", "resolved": "https://registry.npmjs.org/emmet/-/emmet-2.4.11.tgz",
"integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==", "integrity": "sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
@@ -3962,7 +3839,7 @@
}, },
"node_modules/he": { "node_modules/he": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -4204,7 +4081,7 @@
}, },
"node_modules/is-expression": { "node_modules/is-expression": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/is-expression/-/is-expression-4.0.0.tgz", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz",
"integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4238,7 +4115,7 @@
}, },
"node_modules/is-regex": { "node_modules/is-regex": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -4366,7 +4243,7 @@
}, },
"node_modules/jsonc-parser": { "node_modules/jsonc-parser": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz",
"integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==",
"license": "MIT" "license": "MIT"
}, },
@@ -4742,7 +4619,7 @@
}, },
"node_modules/muggle-string": { "node_modules/muggle-string": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
"license": "MIT" "license": "MIT"
}, },
@@ -7304,7 +7181,7 @@
}, },
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -7745,13 +7622,13 @@
}, },
"node_modules/pug-error": { "node_modules/pug-error": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/pug-error/-/pug-error-2.1.0.tgz", "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz",
"integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/pug-lexer": { "node_modules/pug-lexer": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmmirror.com/pug-lexer/-/pug-lexer-5.0.1.tgz", "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz",
"integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -7762,7 +7639,7 @@
}, },
"node_modules/pug-parser": { "node_modules/pug-parser": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmmirror.com/pug-parser/-/pug-parser-6.0.0.tgz", "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz",
"integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8529,7 +8406,7 @@
}, },
"node_modules/token-stream": { "node_modules/token-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmmirror.com/token-stream/-/token-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz",
"integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==",
"license": "MIT" "license": "MIT"
}, },
@@ -8557,7 +8434,7 @@
}, },
"node_modules/typescript-auto-import-cache": { "node_modules/typescript-auto-import-cache": {
"version": "0.3.6", "version": "0.3.6",
"resolved": "https://registry.npmmirror.com/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz", "resolved": "https://registry.npmjs.org/typescript-auto-import-cache/-/typescript-auto-import-cache-0.3.6.tgz",
"integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==", "integrity": "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8653,9 +8530,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "7.1.5", "version": "7.1.6",
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.5.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz",
"integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==", "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8729,7 +8606,7 @@
}, },
"node_modules/volar-service-css": { "node_modules/volar-service-css": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-css/-/volar-service-css-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-css/-/volar-service-css-0.0.65.tgz",
"integrity": "sha512-oaImNguZF/8NfQh5jJZ2lJYBtF3aFS5H2w+6GmH7ykESAgBJ1UC7DrhmH5smBGGF7OOzzc9AzrrnriafoFJBdA==", "integrity": "sha512-oaImNguZF/8NfQh5jJZ2lJYBtF3aFS5H2w+6GmH7ykESAgBJ1UC7DrhmH5smBGGF7OOzzc9AzrrnriafoFJBdA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8748,7 +8625,7 @@
}, },
"node_modules/volar-service-emmet": { "node_modules/volar-service-emmet": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-emmet/-/volar-service-emmet-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-emmet/-/volar-service-emmet-0.0.65.tgz",
"integrity": "sha512-YkAPlkJnjyAAUZGtG7STgy3ENFy7C0n3dl6MffUYkcovosfUUNgpUOmsj4t1qw1c7t5KMvLfAZHsEC3Ig5Qs3w==", "integrity": "sha512-YkAPlkJnjyAAUZGtG7STgy3ENFy7C0n3dl6MffUYkcovosfUUNgpUOmsj4t1qw1c7t5KMvLfAZHsEC3Ig5Qs3w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8768,7 +8645,7 @@
}, },
"node_modules/volar-service-html": { "node_modules/volar-service-html": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-html/-/volar-service-html-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-html/-/volar-service-html-0.0.65.tgz",
"integrity": "sha512-AxXckCTbCr5j5z81d3bNiRRL32xCaBSa8lmYhq0QfzBPVPaRv06YYaxp22XizM061f96iizM7ZkSHCu1RuSwRA==", "integrity": "sha512-AxXckCTbCr5j5z81d3bNiRRL32xCaBSa8lmYhq0QfzBPVPaRv06YYaxp22XizM061f96iizM7ZkSHCu1RuSwRA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8787,7 +8664,7 @@
}, },
"node_modules/volar-service-json": { "node_modules/volar-service-json": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-json/-/volar-service-json-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-json/-/volar-service-json-0.0.65.tgz",
"integrity": "sha512-fqm4aIVkXtoQhOcD2pBBEbnloC1ULEHBm2lZRfZloRAKiIJxakw0jDdQh5F/ClzUzQgegkniLbfPsVOubtLM0Q==", "integrity": "sha512-fqm4aIVkXtoQhOcD2pBBEbnloC1ULEHBm2lZRfZloRAKiIJxakw0jDdQh5F/ClzUzQgegkniLbfPsVOubtLM0Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8805,7 +8682,7 @@
}, },
"node_modules/volar-service-pug": { "node_modules/volar-service-pug": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-pug/-/volar-service-pug-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-pug/-/volar-service-pug-0.0.65.tgz",
"integrity": "sha512-QTpxpLmpwNjSPASM4n5d+MF9DHKaBUuHeoV7SXVH5wmrx513da6wljlXNk34IU1npBglNp9sO0qaNokP1Gn42g==", "integrity": "sha512-QTpxpLmpwNjSPASM4n5d+MF9DHKaBUuHeoV7SXVH5wmrx513da6wljlXNk34IU1npBglNp9sO0qaNokP1Gn42g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8820,7 +8697,7 @@
}, },
"node_modules/volar-service-pug-beautify": { "node_modules/volar-service-pug-beautify": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-pug-beautify/-/volar-service-pug-beautify-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-pug-beautify/-/volar-service-pug-beautify-0.0.65.tgz",
"integrity": "sha512-dD8kyuZvrRqOccBIWIBupc3gi9jx/Y8bIDdFJ7IxtEOqGclJxlzvutJckJSVjyz+TrzFXTcI7hCyEV3SCcir+A==", "integrity": "sha512-dD8kyuZvrRqOccBIWIBupc3gi9jx/Y8bIDdFJ7IxtEOqGclJxlzvutJckJSVjyz+TrzFXTcI7hCyEV3SCcir+A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8837,7 +8714,7 @@
}, },
"node_modules/volar-service-typescript": { "node_modules/volar-service-typescript": {
"version": "0.0.65", "version": "0.0.65",
"resolved": "https://registry.npmmirror.com/volar-service-typescript/-/volar-service-typescript-0.0.65.tgz", "resolved": "https://registry.npmjs.org/volar-service-typescript/-/volar-service-typescript-0.0.65.tgz",
"integrity": "sha512-zPJuLIMs7lkQCvL+Rza8+3/EIoXEIkX8+DL7bNNfPgnbalbvRDhqWLVMJ6Zk3pINjLJafDqyhSbw8srfkUv97w==", "integrity": "sha512-zPJuLIMs7lkQCvL+Rza8+3/EIoXEIkX8+DL7bNNfPgnbalbvRDhqWLVMJ6Zk3pINjLJafDqyhSbw8srfkUv97w==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8859,7 +8736,7 @@
}, },
"node_modules/vscode-css-languageservice": { "node_modules/vscode-css-languageservice": {
"version": "6.3.7", "version": "6.3.7",
"resolved": "https://registry.npmmirror.com/vscode-css-languageservice/-/vscode-css-languageservice-6.3.7.tgz", "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.7.tgz",
"integrity": "sha512-5TmXHKllPzfkPhW4UE9sODV3E0bIOJPOk+EERKllf2SmAczjfTmYeq5txco+N3jpF8KIZ6loj/JptpHBQuVQRA==", "integrity": "sha512-5TmXHKllPzfkPhW4UE9sODV3E0bIOJPOk+EERKllf2SmAczjfTmYeq5txco+N3jpF8KIZ6loj/JptpHBQuVQRA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8871,7 +8748,7 @@
}, },
"node_modules/vscode-html-languageservice": { "node_modules/vscode-html-languageservice": {
"version": "5.5.1", "version": "5.5.1",
"resolved": "https://registry.npmmirror.com/vscode-html-languageservice/-/vscode-html-languageservice-5.5.1.tgz", "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.5.1.tgz",
"integrity": "sha512-/ZdEtsZ3OiFSyL00kmmu7crFV9KwWR+MgpzjsxO60DQH7sIfHZM892C/E4iDd11EKocr+NYuvOA4Y7uc3QzLEA==", "integrity": "sha512-/ZdEtsZ3OiFSyL00kmmu7crFV9KwWR+MgpzjsxO60DQH7sIfHZM892C/E4iDd11EKocr+NYuvOA4Y7uc3QzLEA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8883,7 +8760,7 @@
}, },
"node_modules/vscode-json-languageservice": { "node_modules/vscode-json-languageservice": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmmirror.com/vscode-json-languageservice/-/vscode-json-languageservice-5.6.1.tgz", "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.6.1.tgz",
"integrity": "sha512-IQIURBF2VMKBdWcMunbHSI3G2WmJ9H7613E1hRxIXX7YsAPSdBxnEiIUrTnsSW/3fk+QW1kfsvSigqgAFYIYtg==", "integrity": "sha512-IQIURBF2VMKBdWcMunbHSI3G2WmJ9H7613E1hRxIXX7YsAPSdBxnEiIUrTnsSW/3fk+QW1kfsvSigqgAFYIYtg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -8896,7 +8773,7 @@
}, },
"node_modules/vscode-json-languageservice/node_modules/jsonc-parser": { "node_modules/vscode-json-languageservice/node_modules/jsonc-parser": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
"license": "MIT" "license": "MIT"
}, },
@@ -8945,7 +8822,7 @@
}, },
"node_modules/vscode-nls": { "node_modules/vscode-nls": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmmirror.com/vscode-nls/-/vscode-nls-5.2.0.tgz", "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-5.2.0.tgz",
"integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==", "integrity": "sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==",
"license": "MIT" "license": "MIT"
}, },

View File

@@ -13,7 +13,7 @@
}, },
"dependencies": { "dependencies": {
"@volar/language-server": "2.4.23", "@volar/language-server": "2.4.23",
"@vue/language-server": "3.0.5", "@vue/language-server": "3.0.7",
"@vuepic/vue-datepicker": "^11.0.2", "@vuepic/vue-datepicker": "^11.0.2",
"@vueuse/core": "^13.5.0", "@vueuse/core": "^13.5.0",
"@vueuse/shared": "^13.5.0", "@vueuse/shared": "^13.5.0",
@@ -41,6 +41,6 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
"vite": "^7.0.5" "vite": "^7.1.6"
} }
} }

View File

@@ -0,0 +1,59 @@
[Peers]
remote_endpoint = <your-public-ip>
peer_global_dns = <your-chosen-dns>
peer_endpoint_allowed_ip = 0.0.0.0/0
peer_display_mode = grid
peer_mtu = 1420
peer_keep_alive = 21
[Server]
app_port = 10086
wg_conf_path = /etc/wireguard
awg_conf_path = /etc/amnezia/amneziawg
app_prefix =
app_ip = 0.0.0.0
auth_req = true
version = v4.3.0.1
dashboard_refresh_interval = 60000
dashboard_peer_list_display = grid
dashboard_sort = status
dashboard_theme = dark
dashboard_api_key = false
dashboard_language = en-US
[Account]
username = admin
password = $2b$12$nWgPW.4adylN2oMhTyS5AeoiAvDj9SZxnXS.lCMkJYCV6jytmHKzu
enable_totp = false
totp_verified = false
totp_key = UOXAUPDDUNFTTHXZNQI4J4BWCEJZ63HF
[Other]
welcome_session = true
[Database]
type = sqlite
host =
port =
username =
password =
[Email]
server =
port =
encryption =
username =
email_password =
authentication_required = true
send_from =
email_template =
[OIDC]
admin_enable = false
client_enable = false
[Clients]
enable = true
[WireGuardConfiguration]
autostart =