mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-10-03 15:56:17 +00:00
Compare commits
13 Commits
v4.3.0.1
...
cleanup-da
Author | SHA1 | Date | |
---|---|---|---|
|
ed896b6545 | ||
|
6bebe89202 | ||
|
101ac5e985 | ||
|
113a780eec | ||
|
cf77610a56 | ||
|
84675fe521 | ||
|
5db5b35311 | ||
|
ff345c9609 | ||
|
6cccfec923 | ||
|
8231dd1463 | ||
|
d8ff020d8c | ||
|
238fb91360 | ||
|
9ecc16fcc1 |
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@@ -11,7 +11,7 @@ updates:
|
||||
interval: "daily"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
directory: "/src/static/app"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@ node_modules/**
|
||||
*/proxy.js
|
||||
src/static/app/proxy.js
|
||||
.secrets
|
||||
*.pid
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
@@ -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://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://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 align="center"><b>This project is supported by</b></p>
|
||||
<p align="center">
|
||||
|
@@ -3,8 +3,10 @@
|
||||
# Base: Alpine
|
||||
#
|
||||
|
||||
# Pull the current golang-alpine image.
|
||||
FROM golang:1.25-alpine AS awg-go
|
||||
|
||||
# Install build-dependencies.
|
||||
RUN apk add --no-cache \
|
||||
git \
|
||||
gcc \
|
||||
@@ -14,9 +16,12 @@ RUN apk add --no-cache \
|
||||
RUN mkdir -p /workspace && \
|
||||
git clone https://github.com/WGDashboard/amneziawg-go /workspace/awg
|
||||
|
||||
# Enable CGO compilation for AmneziaWG
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
# Change directory
|
||||
WORKDIR /workspace/awg
|
||||
# Compile the binaries
|
||||
RUN go mod download && \
|
||||
go mod verify && \
|
||||
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
|
||||
|
||||
# Install needed dependencies.
|
||||
RUN apk add --no-cache \
|
||||
make \
|
||||
git \
|
||||
@@ -33,19 +39,24 @@ RUN apk add --no-cache \
|
||||
linux-headers \
|
||||
ca-certificates
|
||||
|
||||
# Get the workspace ready
|
||||
RUN mkdir -p /workspace && \
|
||||
git clone https://github.com/WGDashboard/amneziawg-tools /workspace/awg-tools
|
||||
|
||||
# Change directory
|
||||
WORKDIR /workspace/awg-tools/src
|
||||
# Compile and change permissions
|
||||
RUN make && chmod +x wg*
|
||||
|
||||
#
|
||||
# PIP DEPENDENCY BUILDING
|
||||
# Base: Alpine
|
||||
#
|
||||
|
||||
# Use the python-alpine image for building pip dependencies
|
||||
FROM python:3.13-alpine AS pip-builder
|
||||
|
||||
|
||||
# Add the build dependencies and create a Python virtual environment.
|
||||
RUN apk add --no-cache \
|
||||
build-base \
|
||||
pkgconfig \
|
||||
@@ -57,7 +68,9 @@ RUN apk add --no-cache \
|
||||
&& mkdir -p /opt/wgdashboard/src \
|
||||
&& python3 -m venv /opt/wgdashboard/src/venv
|
||||
|
||||
# Copy the requirements file into the build layer.
|
||||
COPY ./src/requirements.txt /opt/wgdashboard/src
|
||||
# Install the pip packages
|
||||
RUN . /opt/wgdashboard/src/venv/bin/activate && \
|
||||
pip3 install --upgrade pip && \
|
||||
pip3 install -r /opt/wgdashboard/src/requirements.txt
|
||||
@@ -66,6 +79,8 @@ RUN . /opt/wgdashboard/src/venv/bin/activate && \
|
||||
# WGDashboard RUNNING STAGE
|
||||
# Base: Alpine
|
||||
#
|
||||
|
||||
# Running with the python-alpine image.
|
||||
FROM python:3.13-alpine AS final
|
||||
LABEL maintainer="dselen@nerthus.nl"
|
||||
|
||||
@@ -78,7 +93,7 @@ RUN apk add --no-cache \
|
||||
tzdata wireguard-tools \
|
||||
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-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
|
||||
@@ -92,14 +107,14 @@ ENV TZ="Europe/Amsterdam" \
|
||||
public_ip="" \
|
||||
WGDASH=/opt/wgdashboard
|
||||
|
||||
# Create directories
|
||||
# Create directories needed for operation
|
||||
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 --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv
|
||||
|
||||
# WireGuard interface template
|
||||
# First WireGuard interface template
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN out_adapt=$(ip -o -4 route show to default | awk '{print $NF}') \
|
||||
&& echo -e "[Interface]\n\
|
||||
@@ -114,11 +129,14 @@ SaveConfig = true\n\
|
||||
DNS = ${global_dns}" > /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 \
|
||||
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
|
||||
|
||||
#
|
||||
EXPOSE 10086
|
||||
WORKDIR $WGDASH/src
|
||||
|
||||
|
@@ -3,8 +3,8 @@ Author: @DaanSelen<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>
|
||||
The `Dockerfile` describes how the container image is made, and the `entrypoint.sh` is executed after running the container. <br>
|
||||
In this example, WireGuard is integrated into the container itself, so it should be a run-and-go(/out-of-the-box).<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](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.
|
||||
|
||||
<br>
|
||||
@@ -18,20 +18,24 @@ For more details on the source-code specific to this Docker image, refer to the
|
||||
/>
|
||||
<br>
|
||||
|
||||
To get the container running you either pull the image from the repository, (docker.io)`donaldzou/wgdashboard:latest`.<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>
|
||||
Otherwise edit the configuration file in `/etc/wireguard/wg0.conf`.
|
||||
To get the container running you either pull the pre-made image from a remote repository, there are 2 official options.<br>
|
||||
|
||||
- ghcr.io/wgdashboard/wgdashboard:<tag>
|
||||
- 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
|
||||
|
||||
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/donaldzou/wgdashboard:latest
|
||||
```
|
||||
|
||||
> `docker.io` is in most cases automatically resolved by the Docker application.
|
||||
> `docker.io` is in most cases automatically resolved by the Docker application. Therefor you can ofter specify: `donaldzou/wgdashboard:latest`
|
||||
|
||||
### 🔧 Quick Docker Run Command
|
||||
|
||||
@@ -44,7 +48,7 @@ docker run -d \
|
||||
-p 10086:10086/tcp \
|
||||
-p 51820:51820/udp \
|
||||
--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.
|
||||
@@ -58,23 +62,24 @@ You can also use Docker Compose for easier configuration:
|
||||
```yaml
|
||||
services:
|
||||
wgdashboard:
|
||||
image: donaldzou/wgdashboard:latest
|
||||
image: ghcr.io/wgdashboard/wgdashboard:latest
|
||||
restart: unless-stopped
|
||||
container_name: wgdashboard
|
||||
environment:
|
||||
# - tz=Europe/Amsterdam
|
||||
# - global_dns=1.1.1.1
|
||||
# - public_ip=YOUR_PUBLIC_IP
|
||||
|
||||
ports:
|
||||
- 10086:10086/tcp
|
||||
- 51820:51820/udp
|
||||
|
||||
volumes:
|
||||
- aconf:/etc/amnezia/amneziawg
|
||||
- conf:/etc/wireguard
|
||||
- data:/data
|
||||
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
|
||||
volumes:
|
||||
aconf:
|
||||
conf:
|
||||
data:
|
||||
```
|
||||
@@ -85,7 +90,7 @@ volumes:
|
||||
|
||||
## 🔄 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -205,4 +210,4 @@ ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||
|
||||
## Closing remarks:
|
||||
|
||||
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.
|
||||
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.
|
||||
|
@@ -1,22 +1,41 @@
|
||||
services:
|
||||
wireguard-dashboard:
|
||||
image: donaldzou/wgdashboard:latest
|
||||
wgdashboard:
|
||||
# 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
|
||||
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:
|
||||
#- 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.
|
||||
#- 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:
|
||||
- 10086:10086/tcp
|
||||
- 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:
|
||||
- aconf:/etc/amnezia/amneziawg
|
||||
- conf:/etc/wireguard
|
||||
- data:/data
|
||||
|
||||
# Needed for network administration.
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
|
||||
# The following configuration is linked to the above default volumes.
|
||||
volumes:
|
||||
aconf:
|
||||
conf:
|
||||
|
694
src/dashboard.py
694
src/dashboard.py
@@ -1,27 +1,45 @@
|
||||
# --- Standard library imports ---
|
||||
import configparser
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess
|
||||
import time, re, uuid, bcrypt, psutil, pyotp, threading
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import secrets
|
||||
import shutil
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import islice
|
||||
from uuid import uuid4
|
||||
from zipfile import ZipFile
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# --- Third-party imports ---
|
||||
import bcrypt
|
||||
import psutil
|
||||
import pyotp
|
||||
import sqlalchemy
|
||||
from jinja2 import Template
|
||||
from flask import Flask, request, render_template, session, send_file
|
||||
from flask import Flask, request, render_template, session, send_file, Response
|
||||
from flask_cors import CORS
|
||||
from icmplib import ping, traceroute
|
||||
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
|
||||
|
||||
# --- Local module imports ---
|
||||
from client import createClientBlueprint
|
||||
from modules.Utilities import (
|
||||
RegexMatch, StringToBoolean,
|
||||
ValidateIPAddressesWithRange, ValidateDNSAddress,
|
||||
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
||||
)
|
||||
from packaging import version
|
||||
from modules.Email import EmailSender
|
||||
from modules.DashboardLogger import DashboardLogger
|
||||
from modules.PeerJob import PeerJob
|
||||
@@ -31,16 +49,15 @@ from modules.PeerJobs import PeerJobs
|
||||
from modules.DashboardConfig import DashboardConfig
|
||||
from modules.WireguardConfiguration import WireguardConfiguration
|
||||
from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration
|
||||
|
||||
from client import createClientBlueprint
|
||||
|
||||
from logging.config import dictConfig
|
||||
|
||||
from modules.DashboardClients import DashboardClients
|
||||
from modules.DashboardPlugins import DashboardPlugins
|
||||
from modules.DashboardWebHooks import DashboardWebHooks
|
||||
from modules.NewConfigurationTemplates import NewConfigurationTemplates
|
||||
|
||||
# --- Logging configuration ---
|
||||
from logging.config import dictConfig
|
||||
|
||||
|
||||
class CustomJsonEncoder(DefaultJSONProvider):
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
@@ -55,7 +72,6 @@ class CustomJsonEncoder(DefaultJSONProvider):
|
||||
return super().default(self)
|
||||
|
||||
|
||||
|
||||
'''
|
||||
Response Object
|
||||
'''
|
||||
@@ -195,12 +211,12 @@ with app.app_context():
|
||||
EmailSender = EmailSender(DashboardConfig)
|
||||
AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig, WireguardConfigurations)
|
||||
AllPeerJobs: PeerJobs = PeerJobs(DashboardConfig, WireguardConfigurations)
|
||||
DashboardLogger: DashboardLogger = DashboardLogger()
|
||||
DashboardPlugins: DashboardPlugins = DashboardPlugins(app, WireguardConfigurations)
|
||||
DashboardWebHooks: DashboardWebHooks = DashboardWebHooks(DashboardConfig)
|
||||
NewConfigurationTemplates: NewConfigurationTemplates = NewConfigurationTemplates()
|
||||
DashboardLogger = DashboardLogger()
|
||||
DashboardPlugins = DashboardPlugins(app, WireguardConfigurations)
|
||||
DashboardWebHooks = DashboardWebHooks(DashboardConfig)
|
||||
NewConfigurationTemplates = NewConfigurationTemplates()
|
||||
InitWireguardConfigurationsList(startup=True)
|
||||
DashboardClients: DashboardClients = DashboardClients(WireguardConfigurations)
|
||||
DashboardClients = DashboardClients(WireguardConfigurations)
|
||||
app.register_blueprint(createClientBlueprint(WireguardConfigurations, DashboardConfig, DashboardClients))
|
||||
|
||||
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
||||
@@ -213,32 +229,103 @@ _, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
||||
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
||||
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
|
||||
|
||||
|
||||
'''
|
||||
API Routes
|
||||
'''
|
||||
|
||||
def _enforce_session_auth():
|
||||
"""Enforce session authentication for non-API key access."""
|
||||
white_list = [
|
||||
'/static/',
|
||||
'validateAuthentication',
|
||||
'authenticate',
|
||||
'getDashboardConfiguration',
|
||||
'getDashboardTheme',
|
||||
'getDashboardVersion',
|
||||
'sharePeer/get',
|
||||
'isTotpEnabled',
|
||||
'locale',
|
||||
'/fileDownload',
|
||||
'/client'
|
||||
]
|
||||
|
||||
path_ok = (
|
||||
("username" in session and session.get("role") == "admin")
|
||||
or (f"{APP_PREFIX}/" == request.path or f"{APP_PREFIX}" == request.path)
|
||||
or not all(sub not in request.path for sub in white_list)
|
||||
)
|
||||
|
||||
if not path_ok:
|
||||
response = Flask.make_response(app, {
|
||||
"status": False,
|
||||
"message": "Unauthorized access.",
|
||||
"data": None
|
||||
})
|
||||
response.content_type = "application/json"
|
||||
response.status_code = 401
|
||||
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)
|
||||
return ResponseObject(True)
|
||||
|
||||
DashboardConfig.APIAccessed = False
|
||||
|
||||
# Logging
|
||||
if "api" in request.path:
|
||||
if str(request.method) == "GET":
|
||||
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args))
|
||||
elif str(request.method) == "POST":
|
||||
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Request Args: {str(request.args)} Body:{str(request.get_json())}")
|
||||
|
||||
|
||||
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:
|
||||
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,
|
||||
@@ -248,84 +335,49 @@ def auth_req():
|
||||
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',
|
||||
'/client'
|
||||
]
|
||||
|
||||
if (("username" not in session or session.get("role") != "admin")
|
||||
and (f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}/" != request.path
|
||||
and f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}" != request.path)
|
||||
and len(list(filter(lambda x : x not in request.path, whiteList))) == len(whiteList)
|
||||
):
|
||||
response = Flask.make_response(app, {
|
||||
"status": False,
|
||||
"message": "Unauthorized access.",
|
||||
"data": None
|
||||
})
|
||||
response.content_type = "application/json"
|
||||
response.status_code = 401
|
||||
return response
|
||||
_enforce_session_auth()
|
||||
|
||||
|
||||
@app.route(f'{APP_PREFIX}/api/handshake', methods=["GET", "OPTIONS"])
|
||||
def API_Handshake():
|
||||
return ResponseObject(True)
|
||||
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/validateAuthentication')
|
||||
def API_ValidateAuthentication():
|
||||
token = request.cookies.get("authToken")
|
||||
if 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.")
|
||||
auth_required = DashboardConfig.GetConfig("Server", "auth_req")[1]
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/requireAuthentication')
|
||||
def API_RequireAuthentication():
|
||||
return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1])
|
||||
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/authenticate')
|
||||
def API_AuthenticateLogin():
|
||||
data = request.get_json()
|
||||
if not DashboardConfig.GetConfig("Server", "auth_req")[1]:
|
||||
return ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
||||
|
||||
if DashboardConfig.APIAccessed:
|
||||
authToken = hashlib.sha256(f"{request.headers.get('wg-dashboard-apikey')}{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
|
||||
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']
|
||||
auth_req = DashboardConfig.GetConfig("Server", "auth_req")[1]
|
||||
|
||||
if (valid
|
||||
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]
|
||||
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.")
|
||||
if not auth_req:
|
||||
return ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
||||
|
||||
# API key login
|
||||
if DashboardConfig.APIAccessed:
|
||||
return _login_with_token(request.headers.get('wg-dashboard-apikey'))
|
||||
|
||||
# User login
|
||||
return _login_with_credentials(data)
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/signout')
|
||||
def API_SignOut():
|
||||
@@ -337,7 +389,7 @@ def API_SignOut():
|
||||
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurations')
|
||||
def API_getWireguardConfigurations():
|
||||
InitWireguardConfigurationsList()
|
||||
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()])
|
||||
return ResponseObject(data=list(WireguardConfigurations.values()))
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/newConfigurationTemplates')
|
||||
def API_NewConfigurationTemplates():
|
||||
@@ -350,193 +402,255 @@ def API_NewConfigurationTemplates_CreateTemplate():
|
||||
@app.post(f'{APP_PREFIX}/api/newConfigurationTemplates/updateTemplate')
|
||||
def API_NewConfigurationTemplates_UpdateTemplate():
|
||||
data = request.get_json()
|
||||
template = data.get('Template', None)
|
||||
template = data.get('Template')
|
||||
|
||||
if not template:
|
||||
return ResponseObject(False, "Please provide template")
|
||||
|
||||
return ResponseObject(False, "Please provide template",
|
||||
status_code=400)
|
||||
|
||||
status, msg = NewConfigurationTemplates.UpdateTemplate(template)
|
||||
|
||||
return ResponseObject(status, msg)
|
||||
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/newConfigurationTemplates/deleteTemplate')
|
||||
def API_NewConfigurationTemplates_DeleteTemplate():
|
||||
data = request.get_json()
|
||||
template = data.get('Template', None)
|
||||
if not template:
|
||||
return ResponseObject(False, "Please provide template")
|
||||
template = data.get('Template')
|
||||
|
||||
if not template:
|
||||
return ResponseObject(False, "Please provide template",
|
||||
status_code=400)
|
||||
|
||||
status, msg = NewConfigurationTemplates.DeleteTemplate(template)
|
||||
|
||||
return ResponseObject(status, msg)
|
||||
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/addWireguardConfiguration')
|
||||
def API_addWireguardConfiguration():
|
||||
data = request.get_json()
|
||||
requiredKeys = [
|
||||
"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():
|
||||
return ResponseObject(False, "Please provide a valid protocol: wg / awg.")
|
||||
protocol = data.get("Protocol")
|
||||
|
||||
# Check duplicate names, ports, address
|
||||
for i in WireguardConfigurations.values():
|
||||
if i.Name == data['ConfigurationName']:
|
||||
return ResponseObject(False,
|
||||
f"Already have a configuration with the name \"{data['ConfigurationName']}\"",
|
||||
"ConfigurationName")
|
||||
required_keys = {"ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol"}
|
||||
if not required_keys.issubset(data.keys()):
|
||||
return ResponseObject(False, "Please provide all required parameters.", status_code=400)
|
||||
|
||||
if str(i.ListenPort) == str(data["ListenPort"]):
|
||||
return ResponseObject(False,
|
||||
f"Already have a configuration with the port \"{data['ListenPort']}\"",
|
||||
"ListenPort")
|
||||
if protocol not in ProtocolsEnabled():
|
||||
return ResponseObject(False, "Please provide a valid protocol: wg / awg.", status_code=400)
|
||||
|
||||
if i.Address == data["Address"]:
|
||||
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],
|
||||
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
|
||||
for cfg in WireguardConfigurations.values():
|
||||
duplicates = {
|
||||
"ConfigurationName": cfg.Name == data['ConfigurationName'],
|
||||
"ListenPort": str(cfg.ListenPort) == str(data["ListenPort"]),
|
||||
"Address": cfg.Address == data["Address"]
|
||||
}
|
||||
|
||||
if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and
|
||||
os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
|
||||
protocol = "wg"
|
||||
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and
|
||||
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
|
||||
protocol = "awg"
|
||||
else:
|
||||
return ResponseObject(False, "Backup does not exist")
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
paths = {
|
||||
"wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
|
||||
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
|
||||
}
|
||||
|
||||
if "Backup" in data:
|
||||
backup_file = data["Backup"]
|
||||
protocol_detected = None
|
||||
for proto, base_path in paths.items():
|
||||
conf_path = os.path.join(base_path, 'WGDashboard_Backup', backup_file)
|
||||
sql_path = os.path.join(base_path, 'WGDashboard_Backup', backup_file.replace('.conf', '.sql'))
|
||||
if os.path.exists(conf_path) and os.path.exists(sql_path):
|
||||
protocol_detected = proto
|
||||
break
|
||||
|
||||
if not protocol_detected:
|
||||
return ResponseObject(False, "Backup does not exist", status_code=400)
|
||||
|
||||
shutil.copy(
|
||||
os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]),
|
||||
os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf')
|
||||
os.path.join(paths[protocol_detected], 'WGDashboard_Backup', backup_file),
|
||||
os.path.join(paths[protocol_detected], f'{data["ConfigurationName"]}.conf')
|
||||
)
|
||||
WireguardConfigurations[data['ConfigurationName']] = (
|
||||
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else (
|
||||
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName']))
|
||||
protocol = protocol_detected # Use backup protocol
|
||||
else:
|
||||
WireguardConfigurations[data['ConfigurationName']] = (
|
||||
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else (
|
||||
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data))
|
||||
conf_path = os.path.join(paths[protocol], f'{data["ConfigurationName"]}.conf')
|
||||
if not os.path.exists(conf_path):
|
||||
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()
|
||||
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
|
||||
def API_toggleWireguardConfiguration():
|
||||
configurationName = request.args.get('configurationName')
|
||||
if configurationName is None or len(
|
||||
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
|
||||
return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
|
||||
toggleStatus, msg = WireguardConfigurations[configurationName].toggleConfiguration()
|
||||
return ResponseObject(toggleStatus, msg, WireguardConfigurations[configurationName].Status)
|
||||
configuration_name = request.args.get('configurationName')
|
||||
|
||||
if not configuration_name or configuration_name not in WireguardConfigurations:
|
||||
return ResponseObject(False, "Please provide a valid configuration name",
|
||||
status_code=404)
|
||||
|
||||
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')
|
||||
def API_updateWireguardConfiguration():
|
||||
data = request.get_json()
|
||||
requiredKeys = ["Name"]
|
||||
for i in requiredKeys:
|
||||
if i not in data.keys():
|
||||
return ResponseObject(False, "Please provide these following field: " + ", ".join(requiredKeys))
|
||||
data = request.get_json() or {}
|
||||
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)
|
||||
|
||||
return ResponseObject(status, message=msg, data=WireguardConfigurations[name])
|
||||
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)
|
||||
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationInfo')
|
||||
def API_updateWireguardConfigurationInfo():
|
||||
data = request.get_json()
|
||||
data = request.get_json() or {}
|
||||
name = data.get('Name')
|
||||
key = data.get('Key')
|
||||
value = data.get('Value')
|
||||
if not all([data, key, name]):
|
||||
return ResponseObject(status=False, message="Please provide configuration name, key and value")
|
||||
if name not in WireguardConfigurations.keys():
|
||||
|
||||
if not all([name, key, value]): # Required values
|
||||
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)
|
||||
|
||||
status, msg, key = WireguardConfigurations[name].updateConfigurationInfo(key, value)
|
||||
|
||||
return ResponseObject(status=status, message=msg, data=key)
|
||||
target_configuration = WireguardConfigurations[name]
|
||||
status, msg, key = target_configuration.updateConfigurationInfo(key, value)
|
||||
|
||||
return ResponseObject(status, msg, key)
|
||||
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRawFile')
|
||||
def API_GetWireguardConfigurationRawFile():
|
||||
configurationName = request.args.get('configurationName')
|
||||
if configurationName is None or len(
|
||||
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
|
||||
def API_getWireguardConfigurationRawFile():
|
||||
configuration_name = request.args.get('configurationName')
|
||||
|
||||
if not configuration_name or configuration_name not in WireguardConfigurations:
|
||||
return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
|
||||
|
||||
config = WireguardConfigurations[configuration_name]
|
||||
|
||||
return ResponseObject(data={
|
||||
"path": WireguardConfigurations[configurationName].configPath,
|
||||
"content": WireguardConfigurations[configurationName].getRawConfigurationFile()
|
||||
"path": config.configPath,
|
||||
"content": config.getRawConfigurationFile()
|
||||
})
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationRawFile')
|
||||
def API_UpdateWireguardConfigurationRawFile():
|
||||
data = request.get_json()
|
||||
configurationName = data.get('configurationName')
|
||||
rawConfiguration = data.get('rawConfiguration')
|
||||
if configurationName is None or len(
|
||||
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
|
||||
data = request.get_json() or {}
|
||||
configuration_name = data.get('configurationName')
|
||||
raw_configuration = data.get('rawConfiguration')
|
||||
|
||||
if not configuration_name or configuration_name not in WireguardConfigurations:
|
||||
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")
|
||||
|
||||
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')
|
||||
def API_deleteWireguardConfiguration():
|
||||
data = request.get_json()
|
||||
if "ConfigurationName" not in data.keys() or data.get("ConfigurationName") is None or data.get("ConfigurationName") not in WireguardConfigurations.keys():
|
||||
data = request.get_json() or {}
|
||||
configuration_name = 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(data.get("ConfigurationName"))
|
||||
|
||||
rp = WireguardConfigurations.pop(configuration_name)
|
||||
status = rp.deleteConfiguration()
|
||||
|
||||
if not status:
|
||||
WireguardConfigurations[data.get("ConfigurationName")] = rp
|
||||
WireguardConfigurations[configuration_name] = rp
|
||||
|
||||
return ResponseObject(status)
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/renameWireguardConfiguration')
|
||||
def API_renameWireguardConfiguration():
|
||||
data = request.get_json()
|
||||
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)
|
||||
data = request.get_json() or {}
|
||||
|
||||
old_name = data.get("ConfigurationName")
|
||||
new_name = data.get("NewConfigurationName")
|
||||
|
||||
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 data.get("NewConfigurationName") in WireguardConfigurations.keys():
|
||||
return ResponseObject(False, "Configuration name already exist", status_code=400)
|
||||
if not new_name:
|
||||
return ResponseObject(False, "Please provide a new configuration name", status=400)
|
||||
|
||||
rc = WireguardConfigurations.pop(data.get("ConfigurationName"))
|
||||
if new_name in WireguardConfigurations:
|
||||
return ResponseObject(False, "The configuration name already exists", status_code=400)
|
||||
|
||||
status, message = rc.renameConfiguration(data.get("NewConfigurationName"))
|
||||
rc = WireguardConfigurations.pop(old_name)
|
||||
status, message = rc.renameConfiguration(new_name)
|
||||
|
||||
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:
|
||||
ConfigClass = AmneziaWireguardConfiguration
|
||||
|
||||
WireguardConfigurations[new_name] = ConfigClass(
|
||||
DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, new_name
|
||||
)
|
||||
else:
|
||||
WireguardConfigurations[data.get("ConfigurationName")] = rc
|
||||
WireguardConfigurations[old_name] = rc
|
||||
|
||||
return ResponseObject(status, message)
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRealtimeTraffic')
|
||||
def API_getWireguardConfigurationRealtimeTraffic():
|
||||
configurationName = request.args.get('configurationName')
|
||||
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
||||
configuration_name = requests.args.get('configurationName')
|
||||
|
||||
if not configuration_name or configuration_name not in WireguardConfigurations:
|
||||
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')
|
||||
def API_getWireguardConfigurationBackup():
|
||||
configurationName = request.args.get('configurationName')
|
||||
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
||||
configuration_name = request.args.get('configurationName')
|
||||
|
||||
if not configuration_name or configuration_name not in WireguardConfigurations:
|
||||
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')
|
||||
def API_getAllWireguardConfigurationBackup():
|
||||
@@ -544,47 +658,86 @@ def API_getAllWireguardConfigurationBackup():
|
||||
"ExistingConfigurations": {},
|
||||
"NonExistingConfigurations": {}
|
||||
}
|
||||
existingConfiguration = WireguardConfigurations.keys()
|
||||
for i in existingConfiguration:
|
||||
b = WireguardConfigurations[i].getBackups(True)
|
||||
if len(b) > 0:
|
||||
data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True)
|
||||
|
||||
|
||||
existing_configurations = WireguardConfigurations.keys()
|
||||
|
||||
for single_conf in existing_configurations:
|
||||
backups = WireguardConfigurations[single_conf].getBackups(True)
|
||||
if len(backups) > 0:
|
||||
data['ExistingConfigurations'][single_conf] = WireguardConfigurations[single_conf].getBackups(True)
|
||||
|
||||
for protocol in ProtocolsEnabled():
|
||||
directory = os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup')
|
||||
if os.path.exists(directory):
|
||||
files = [(file, os.path.getctime(os.path.join(directory, file)))
|
||||
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 RegexMatch(r"^(.*)_(.*)\.(conf)$", f):
|
||||
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)
|
||||
d = {
|
||||
"protocol": protocol,
|
||||
"filename": f,
|
||||
"backupDate": date,
|
||||
"content": open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
|
||||
}
|
||||
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
|
||||
d['database'] = True
|
||||
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
|
||||
data['NonExistingConfigurations'][name].append(d)
|
||||
config_path_info = DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")
|
||||
configuration_path = config_path_info[1]
|
||||
backup_directory = os.path.join(configuration_path, 'WGDashboard_Backup')
|
||||
|
||||
if not os.path.exists(backup_directory):
|
||||
continue
|
||||
|
||||
backup_files = []
|
||||
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,
|
||||
"filename": file_name,
|
||||
"backupDate": backup_date,
|
||||
"content": configuration_content
|
||||
}
|
||||
|
||||
sql_file_name = file_name.replace(".conf", ".sql")
|
||||
sql_file_path = os.path.join(backup_directory, sql_file_name)
|
||||
|
||||
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)
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
|
||||
def API_createWireguardConfigurationBackup():
|
||||
configurationName = request.args.get('configurationName')
|
||||
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
||||
configuration_name = request.args.get('configurationName')
|
||||
|
||||
if not configuration_name or configuration_name not in WireguardConfigurations:
|
||||
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')
|
||||
def API_deleteWireguardConfigurationBackup():
|
||||
@@ -616,18 +769,19 @@ def API_downloadWireguardConfigurationBackup():
|
||||
@app.post(f'{APP_PREFIX}/api/restoreWireguardConfigurationBackup')
|
||||
def API_restoreWireguardConfigurationBackup():
|
||||
data = request.get_json()
|
||||
configuration_name = data['ConfigurationName']
|
||||
backup_file_name = data['BackupFileName']
|
||||
|
||||
if ("ConfigurationName" not in data.keys() or
|
||||
"BackupFileName" not in data.keys() or
|
||||
len(data['ConfigurationName']) == 0 or
|
||||
len(data['BackupFileName']) == 0):
|
||||
return ResponseObject(False,
|
||||
"Please provide ConfigurationName and BackupFileName in body", status_code=400)
|
||||
configurationName = data['ConfigurationName']
|
||||
backupFileName = data['BackupFileName']
|
||||
if configurationName not in WireguardConfigurations.keys():
|
||||
return ResponseObject(False,"Please provide ConfigurationName and BackupFileName in body", status_code=400)
|
||||
|
||||
if configuration_name not in WireguardConfigurations.keys():
|
||||
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'))
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/getDashboardConfiguration')
|
||||
@@ -1369,31 +1523,6 @@ def API_Welcome_Finish():
|
||||
DashboardConfig.SetConfig("Other", "welcome_session", False)
|
||||
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')
|
||||
def API_Locale_CurrentLang():
|
||||
return ResponseObject(data=Locale.getLanguage())
|
||||
@@ -1478,6 +1607,31 @@ def API_SystemStatus():
|
||||
def API_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
|
||||
'''
|
||||
|
253
src/static/app/package-lock.json
generated
253
src/static/app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "4.3.0.1",
|
||||
"dependencies": {
|
||||
"@volar/language-server": "2.4.23",
|
||||
"@vue/language-server": "3.0.5",
|
||||
"@vue/language-server": "3.0.7",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"@vueuse/shared": "^13.5.0",
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"vite": "^7.0.5"
|
||||
"vite": "^7.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-string-parser": {
|
||||
@@ -411,7 +411,7 @@
|
||||
},
|
||||
"node_modules/@emmetio/abbreviation": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -420,7 +420,7 @@
|
||||
},
|
||||
"node_modules/@emmetio/css-abbreviation": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -438,7 +438,7 @@
|
||||
},
|
||||
"node_modules/@emmetio/html-matcher": {
|
||||
"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==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -447,19 +447,19 @@
|
||||
},
|
||||
"node_modules/@emmetio/scanner": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emmetio/stream-reader": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@emmetio/stream-reader-utils": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1030,7 +1030,7 @@
|
||||
},
|
||||
"node_modules/@johnsoncodehk/pug-beautify": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1667,7 +1667,7 @@
|
||||
},
|
||||
"node_modules/@vscode/emmet-helper": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1680,7 +1680,7 @@
|
||||
},
|
||||
"node_modules/@vscode/l10n": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1736,7 +1736,7 @@
|
||||
},
|
||||
"node_modules/@vue/compiler-vue2": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1778,12 +1778,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/language-core": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.0.5.tgz",
|
||||
"integrity": "sha512-gCEjn9Ik7I/seHVNIEipOm8W+f3/kg60e8s1IgIkMYma2wu9ZGUTMv3mSL2bX+Md2L8fslceJ4SU8j1fgSRoiw==",
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.0.7.tgz",
|
||||
"integrity": "sha512-0sqqyqJ0Gn33JH3TdIsZLCZZ8Gr4kwlg8iYOnOrDDkJKSjFurlQY/bEFQx5zs7SX2C/bjMkmPYq/NiyY1fTOkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-core": "2.4.22",
|
||||
"@volar/language-core": "2.4.23",
|
||||
"@vue/compiler-dom": "^3.5.0",
|
||||
"@vue/compiler-vue2": "^2.7.16",
|
||||
"@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": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-server/-/language-server-3.0.5.tgz",
|
||||
"integrity": "sha512-f3SeLh8w4qjE82hZWVTDjTXnJ6PcKuqoDSXs69JPGfSH4yuy7VXC2IIoMhtvnupMxXTYjtJQJKjhe0p/h4whhw==",
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-server/-/language-server-3.0.7.tgz",
|
||||
"integrity": "sha512-bEEAHJhBqWXAaYtqexRH8kggKm98L8/q69A1DvrIY8E2TwLMS1MKBVSMxerTmZ7Zsstl0ZHL6a/q9duMoBKpUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-server": "2.4.22",
|
||||
"@vue/language-core": "3.0.5",
|
||||
"@vue/language-service": "3.0.5",
|
||||
"@vue/typescript-plugin": "3.0.5",
|
||||
"@volar/language-server": "2.4.23",
|
||||
"@vue/language-core": "3.0.7",
|
||||
"@vue/language-service": "3.0.7",
|
||||
"@vue/typescript-plugin": "3.0.7",
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"bin": {
|
||||
@@ -1835,69 +1820,14 @@
|
||||
"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": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/language-service/-/language-service-3.0.5.tgz",
|
||||
"integrity": "sha512-3EFghz2F8oqQtqrSQjZiDZ+6jjCOrgdqPyJThPsy46EQfwslwFTW1ks87FzJtZR9xyfIpF3+0rxrFUyrWNFU3w==",
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/language-service/-/language-service-3.0.7.tgz",
|
||||
"integrity": "sha512-v+XLXuWvk4QgHu0TTJjaIwVu/jrjDpX7ISFu+IrihDusVQ80dm68yrH02Rr4xqKhQos0mwpTbjyC56Ld1eI5Uw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/language-service": "2.4.22",
|
||||
"@vue/language-core": "3.0.5",
|
||||
"@volar/language-service": "2.4.23",
|
||||
"@vue/language-core": "3.0.7",
|
||||
"@vue/shared": "^3.5.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"volar-service-css": "0.0.65",
|
||||
@@ -1911,33 +1841,6 @@
|
||||
"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": {
|
||||
"version": "3.5.21",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz",
|
||||
@@ -1989,43 +1892,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/typescript-plugin": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@vue/typescript-plugin/-/typescript-plugin-3.0.5.tgz",
|
||||
"integrity": "sha512-iR34R1dtgU75r2zxl62mGXyJVznh4Ne+1cUVQeBN6Ci3R3AAzP3v3vVVNu+QwdS37CtQAiTXfOJdiySQQ64NwQ==",
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/typescript-plugin/-/typescript-plugin-3.0.7.tgz",
|
||||
"integrity": "sha512-WBvIkdrRTRPUhcxxqjkqY56rdUkcb+hWSM1lhCko2H9SXPZoDPUN7ZhtX5rrSZWFvduq3lUmahJFW4IT522tIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@volar/typescript": "2.4.22",
|
||||
"@vue/language-core": "3.0.5",
|
||||
"@volar/typescript": "2.4.23",
|
||||
"@vue/language-core": "3.0.7",
|
||||
"@vue/shared": "^3.5.0",
|
||||
"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": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@vuepic/vue-datepicker/-/vue-datepicker-11.0.2.tgz",
|
||||
@@ -2102,7 +1979,7 @@
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -2173,7 +2050,7 @@
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -2609,7 +2486,7 @@
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2650,7 +2527,7 @@
|
||||
},
|
||||
"node_modules/character-parser": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3014,7 +2891,7 @@
|
||||
},
|
||||
"node_modules/de-indent": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -3378,7 +3255,7 @@
|
||||
},
|
||||
"node_modules/emmet": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -3962,7 +3839,7 @@
|
||||
},
|
||||
"node_modules/he": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
@@ -4204,7 +4081,7 @@
|
||||
},
|
||||
"node_modules/is-expression": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4238,7 +4115,7 @@
|
||||
},
|
||||
"node_modules/is-regex": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4366,7 +4243,7 @@
|
||||
},
|
||||
"node_modules/jsonc-parser": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -4742,7 +4619,7 @@
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -7304,7 +7181,7 @@
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -7745,13 +7622,13 @@
|
||||
},
|
||||
"node_modules/pug-error": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pug-lexer": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -7762,7 +7639,7 @@
|
||||
},
|
||||
"node_modules/pug-parser": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8529,7 +8406,7 @@
|
||||
},
|
||||
"node_modules/token-stream": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -8557,7 +8434,7 @@
|
||||
},
|
||||
"node_modules/typescript-auto-import-cache": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8653,9 +8530,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "7.1.5",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-7.1.5.tgz",
|
||||
"integrity": "sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==",
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz",
|
||||
"integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8729,7 +8606,7 @@
|
||||
},
|
||||
"node_modules/volar-service-css": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8748,7 +8625,7 @@
|
||||
},
|
||||
"node_modules/volar-service-emmet": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8768,7 +8645,7 @@
|
||||
},
|
||||
"node_modules/volar-service-html": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8787,7 +8664,7 @@
|
||||
},
|
||||
"node_modules/volar-service-json": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8805,7 +8682,7 @@
|
||||
},
|
||||
"node_modules/volar-service-pug": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8820,7 +8697,7 @@
|
||||
},
|
||||
"node_modules/volar-service-pug-beautify": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8837,7 +8714,7 @@
|
||||
},
|
||||
"node_modules/volar-service-typescript": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8859,7 +8736,7 @@
|
||||
},
|
||||
"node_modules/vscode-css-languageservice": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8871,7 +8748,7 @@
|
||||
},
|
||||
"node_modules/vscode-html-languageservice": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8883,7 +8760,7 @@
|
||||
},
|
||||
"node_modules/vscode-json-languageservice": {
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8896,7 +8773,7 @@
|
||||
},
|
||||
"node_modules/vscode-json-languageservice/node_modules/jsonc-parser": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -8945,7 +8822,7 @@
|
||||
},
|
||||
"node_modules/vscode-nls": {
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
|
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@volar/language-server": "2.4.23",
|
||||
"@vue/language-server": "3.0.5",
|
||||
"@vue/language-server": "3.0.7",
|
||||
"@vuepic/vue-datepicker": "^11.0.2",
|
||||
"@vueuse/core": "^13.5.0",
|
||||
"@vueuse/shared": "^13.5.0",
|
||||
@@ -41,6 +41,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"vite": "^7.0.5"
|
||||
"vite": "^7.1.6"
|
||||
}
|
||||
}
|
||||
|
59
templates/wg-dashboard.ini.template
Normal file
59
templates/wg-dashboard.ini.template
Normal 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 =
|
Reference in New Issue
Block a user