mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2026-04-12 07:46:18 +00:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cdd85b659c | ||
|
|
42f9460369 | ||
|
|
ba11a7a355 | ||
|
|
71f4449741 | ||
|
|
081c63cd43 | ||
|
|
ebd538e2d8 | ||
|
|
b36b0469e1 | ||
|
|
2b9a39f124 | ||
|
|
cd709c2807 | ||
|
|
6bc54e5d65 | ||
|
|
65c2e69ac6 | ||
|
|
96acc10153 | ||
|
|
1a667a60c1 | ||
|
|
bbc85d6a2d | ||
|
|
3995a5db47 | ||
|
|
b8750860a8 | ||
|
|
4711c750cc | ||
|
|
51bee91c32 | ||
|
|
5fcfe97fab | ||
|
|
74bee75df3 | ||
|
|
1aa1b8a51b | ||
|
|
833ee88340 | ||
|
|
6cb4bac673 | ||
|
|
e87c52b6f2 | ||
|
|
f53da6eacb | ||
|
|
8d35887950 | ||
|
|
ebf37c879d | ||
|
|
21bea56426 | ||
|
|
0a33132d26 | ||
|
|
f58226e398 | ||
|
|
eeedf705aa | ||
|
|
b9c271ff4c | ||
|
|
ba4ac7c1ec | ||
|
|
06097316ec | ||
|
|
e71179a2d4 | ||
|
|
bb18c55003 | ||
|
|
8e08d20497 | ||
|
|
bb5d45a66d | ||
|
|
f742f9de49 | ||
|
|
686fc139ec | ||
|
|
a56297bf87 | ||
|
|
80b1b8875d | ||
|
|
8ca0363fd0 | ||
|
|
07534b6f66 | ||
|
|
82f170cd57 | ||
|
|
515bd0f806 | ||
|
|
27742d65c6 | ||
|
|
ddf378d617 | ||
|
|
35c45b159b | ||
|
|
654bf6d779 | ||
|
|
fb66fd2dda | ||
|
|
44d274a998 | ||
|
|
b6e2f084e6 | ||
|
|
cb0619036f | ||
|
|
3a7e16ca9e | ||
|
|
64506c9088 | ||
|
|
d2b05b77c8 | ||
|
|
17e23685ec | ||
|
|
9a7b23748d | ||
|
|
6239b8237a | ||
|
|
dd6ffa694f | ||
|
|
5486b3aaf6 | ||
|
|
c8967b449b | ||
|
|
72effeecf0 | ||
|
|
dcaf5eff32 | ||
|
|
540a085821 | ||
|
|
06cdb0b59e | ||
|
|
faf9e663b3 | ||
|
|
a3058d2a28 | ||
|
|
0d70d13d0f | ||
|
|
a0bbace7ee | ||
|
|
53f7a32202 | ||
|
|
c35f06b262 | ||
|
|
6de90410d0 | ||
|
|
87ff8afefe | ||
|
|
9ae5fea7b4 | ||
|
|
40e2dddc00 | ||
|
|
a584bacdba | ||
|
|
e6dcef2b01 | ||
|
|
f58e572558 | ||
|
|
721ac89420 | ||
|
|
9e9f07408d | ||
|
|
b1d7199e84 | ||
|
|
0a64b1b377 | ||
|
|
bc20057de6 | ||
|
|
c7c9d62e80 | ||
|
|
f5ccbdf9d3 | ||
|
|
a09342ed2a | ||
|
|
26171ee091 |
@@ -2,4 +2,8 @@
|
||||
.github
|
||||
*.md
|
||||
tests/
|
||||
docs/
|
||||
docs/
|
||||
src/db
|
||||
src/wg-dashboard.ini
|
||||
src/static/app
|
||||
src/static/client
|
||||
|
||||
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@@ -7,25 +7,36 @@ version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/src"
|
||||
target-branch: "development"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/static/app"
|
||||
target-branch: "development"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/static/client"
|
||||
target-branch: "development"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github"
|
||||
target-branch: "development"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/docker"
|
||||
target-branch: "development"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "docker-compose"
|
||||
directory: "/docker"
|
||||
target-branch: "development"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
106
.github/workflows/docker-debug.yml
vendored
106
.github/workflows/docker-debug.yml
vendored
@@ -1,106 +0,0 @@
|
||||
name: Clone of Docker Build and Push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOCKERHUB_PREFIX: docker.io
|
||||
GITHUB_CONTAINER_PREFIX: ghcr.io
|
||||
DOCKER_IMAGE: WGDashboard
|
||||
|
||||
jobs:
|
||||
docker_build_debug:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_PREFIX }}
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.GITHUB_CONTAINER_PREFIX }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract Docker metadata from environment
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_PREFIX }}/donaldzou/${{ env.DOCKER_IMAGE }}
|
||||
${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
type=sha,format=short,prefix=
|
||||
|
||||
- name: Build and export Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64
|
||||
|
||||
docker_scan_debug:
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: docker_build_debug
|
||||
steps:
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_PREFIX }}
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.GITHUB_CONTAINER_PREFIX }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker Scout CVEs
|
||||
uses: docker/scout-action@v1
|
||||
with:
|
||||
command: cves
|
||||
image: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:main
|
||||
only-severities: critical,high
|
||||
only-fixed: true
|
||||
write-comment: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
exit-code: true
|
||||
|
||||
- name: Docker Scout Compare
|
||||
uses: docker/scout-action@v1
|
||||
with:
|
||||
command: compare
|
||||
# Set to Github for maximum compat
|
||||
image: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:main
|
||||
to: ${{ env.GITHUB_CONTAINER_PREFIX }}/${{ github.repository_owner }}/${{ env.DOCKER_IMAGE }}:latest
|
||||
only-severities: critical,high
|
||||
ignore-unchanged: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- 'development'
|
||||
- '**dev'
|
||||
tags:
|
||||
- '*'
|
||||
@@ -103,7 +104,6 @@ jobs:
|
||||
only-fixed: true
|
||||
write-comment: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
exit-code: true
|
||||
|
||||
- name: Docker Scout Compare
|
||||
uses: docker/scout-action@v1
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
> [!WARNING]
|
||||
> All users running WGDashboard v4.2.x or later and hosted on the public internet are strongly advised to update to the latest release immediately. For more information: [v4.3.2 Release](https://github.com/WGDashboard/WGDashboard/releases/tag/v4.3.2)
|
||||
|
||||
> [!TIP]
|
||||
> 🎉 To help us better understand and improve WGDashboard’s performance, we’re launching the **WGDashboard Testing Program**. As part of this program, participants will receive free WireGuard VPN access to our server in Toronto, Canada, valid for **24 hours** or up to **1GB of total traffic**—whichever comes first. If you’d like to join, visit [https://wg.wgdashboard.dev/](https://wg.wgdashboard.dev/) for more details!
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
|
||||
# Pull the current golang-alpine image.
|
||||
FROM golang:1.25-alpine AS awg-go
|
||||
FROM golang:1.26-alpine3.23 AS awg-go
|
||||
|
||||
# Install build-dependencies.
|
||||
RUN apk add --no-cache \
|
||||
@@ -30,7 +30,7 @@ RUN go version && \
|
||||
# AWG TOOLS BUILDING STAGE
|
||||
# Base: Alpine
|
||||
#
|
||||
FROM alpine:latest AS awg-tools
|
||||
FROM alpine:3.23 AS awg-tools
|
||||
|
||||
# Install needed dependencies.
|
||||
RUN apk add --no-cache \
|
||||
@@ -55,7 +55,7 @@ RUN make && chmod +x wg*
|
||||
#
|
||||
|
||||
# Use the python-alpine image for building pip dependencies
|
||||
FROM python:3.14-alpine AS pip-builder
|
||||
FROM python:3.14-alpine3.23 AS pip-builder
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
@@ -91,7 +91,7 @@ RUN . /opt/wgdashboard/src/venv/bin/activate && \
|
||||
#
|
||||
|
||||
# Running with the python-alpine image.
|
||||
FROM python:3.14-alpine AS final
|
||||
FROM python:3.14-alpine3.23 AS final
|
||||
LABEL maintainer="dselen@nerthus.nl"
|
||||
|
||||
# Install only the runtime dependencies
|
||||
@@ -114,15 +114,18 @@ ENV TZ="Europe/Amsterdam" \
|
||||
global_dns="9.9.9.9" \
|
||||
wgd_port="10086" \
|
||||
public_ip="" \
|
||||
WGDASH=/opt/wgdashboard
|
||||
WGDASH=/opt/wgdashboard \
|
||||
dynamic_config="true"
|
||||
|
||||
# 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 \
|
||||
&& echo "name_servers=${global_dns}" >> /etc/resolvconf.conf
|
||||
|
||||
# Copy the venv and source files from local compiled locations or repos
|
||||
COPY ./src ${WGDASH}/src
|
||||
COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv
|
||||
COPY ./docker/wg0.conf.template /tmp/wg0.conf.template
|
||||
COPY ./docker/wg-dashboard-oidc-providers.json.template /tmp/wg-dashboard-oidc-providers.json.template
|
||||
# Copy in the runtime script, essential.
|
||||
COPY ./docker/entrypoint.sh /entrypoint.sh
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ To get the container running you either pull the pre-made image from a remote re
|
||||
- ghcr.io/wgdashboard/wgdashboard:<tag>
|
||||
- docker.io/donaldzou/wgdashboard:<tag>
|
||||
|
||||
> tags should be either: latest, main, <version> or <commit-sha>.
|
||||
> tags should be either: latest, main, <version>, <branch-name> (if built) 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>
|
||||
@@ -95,23 +95,29 @@ Updating the WGDashboard container should be through 'The Docker Way' - by pulli
|
||||
|
||||
## ⚙️ Environment Variables
|
||||
|
||||
| Variable | Accepted Values | Default | Example | Description |
|
||||
| ------------------ | ---------------------------------------- | ----------------------- | ------------------------ | ----------------------------------------------------------------------- |
|
||||
| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. |
|
||||
| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. |
|
||||
| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. |
|
||||
| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. |
|
||||
| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. |
|
||||
| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). |
|
||||
| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. |
|
||||
| `wg_autostart` | Wireguard interface name | `-` | `wg0` or `wg0\|\|wg1\|\|wg2` | Auto‐start the WireGuard interface when the container launches. |
|
||||
| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. |
|
||||
| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. |
|
||||
| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. |
|
||||
| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. |
|
||||
| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. |
|
||||
| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. |
|
||||
| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. |
|
||||
| Variable | Accepted Values | Default | Example | Description |
|
||||
| ------------------ | ---------------------------------------- | ----------------------- | --------------------- | ----------------------------------------------------------------------- |
|
||||
| `dynamic_config` | true, yes, false, no | `true` | `true` or `no` | Turns on or off the dynamic configuration feature, on by default for Docker |
|
||||
| `tz` | Timezone | `Europe/Amsterdam` | `America/New_York` | Sets the container's timezone. Useful for accurate logs and scheduling. |
|
||||
| `global_dns` | IPv4 and IPv6 addresses | `9.9.9.9` | `8.8.8.8`, `1.1.1.1` | Default DNS for WireGuard clients. |
|
||||
| `public_ip` | Public IP address | Retrieved automatically | `253.162.134.73` | Used to generate accurate client configs. Needed if container is NAT’d. |
|
||||
| `wgd_port` | Any port that is allowed for the process | `10086` | `443` | This port is used to set the WGDashboard web port. |
|
||||
| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. |
|
||||
| `password` | Any non‐empty string | `-` | `s3cr3tP@ss` | Password for the WGDashboard web interface account (stored hashed). |
|
||||
| `enable_totp` | `true`, `false` | `true` | `false` | Enable TOTP‐based two‐factor authentication for the account. |
|
||||
| `wg_autostart` | Wireguard interface name | `false` | `true` | Auto‐start the WireGuard client when the container launches. |
|
||||
| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. |
|
||||
| `email_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. |
|
||||
| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. |
|
||||
| `email_username` | Any non-empty string | `-` | `user@example.com` | Username for SMTP authentication. |
|
||||
| `email_password` | Any non-empty string | `-` | `app_password` | Password for SMTP authentication. |
|
||||
| `email_from` | Valid email address | `-` | `noreply@example.com` | Email address used as the sender for notifications. |
|
||||
| `email_template` | Path to template file | `-` | `your-template` | Custom template for email notifications. |
|
||||
| `database_type` | `sqlite`, `postgresql`, `mariadb+mariadbconnector`, etc. | `-` | `postgresql` | Type of [sqlalchemy database engine](https://docs.sqlalchemy.org/en/21/core/engines.html). |
|
||||
| `database_host` | Any non-empty string | `-` | `localhost` | IP-Address or hostname of the SQL-database server. |
|
||||
| `database_port` | Any non-empty string (or int for port) | `-` | `5432` | Port for the database communication. |
|
||||
| `database_username`| Valid database username | `-` | `database_user` | Database user username. |
|
||||
| `database_password`| Valid database password | `-` | `database_password` | Database user password. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ services:
|
||||
# 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:
|
||||
#- wg_autostart=wg0
|
||||
#- 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.
|
||||
|
||||
@@ -85,8 +85,6 @@ echo "------------------------- START ----------------------------"
|
||||
echo "Starting the WGDashboard Docker container."
|
||||
|
||||
ensure_installation() {
|
||||
echo "Quick-installing..."
|
||||
|
||||
# Make the wgd.sh script executable.
|
||||
chmod +x "${WGDASH}"/src/wgd.sh
|
||||
cd "${WGDASH}"/src || exit
|
||||
@@ -102,23 +100,51 @@ ensure_installation() {
|
||||
echo "Removing clear command from wgd.sh for better Docker logging."
|
||||
sed -i '/clear/d' ./wgd.sh
|
||||
|
||||
# PERSISTENCE FOR databases directory
|
||||
# Create required directories and links
|
||||
if [ ! -d "/data/db" ]; then
|
||||
echo "Creating database dir"
|
||||
mkdir -p /data/db
|
||||
fi
|
||||
|
||||
if [ ! -d "${WGDASH}/src/db" ]; then
|
||||
ln -s /data/db "${WGDASH}/src/db"
|
||||
if [[ ! -L "${WGDASH}/src/db" ]] && [[ -d "${WGDASH}/src/db" ]]; then
|
||||
echo "Removing ${WGDASH}/src/db since its not a symbolic link."
|
||||
rm -rfv "${WGDASH}/src/db"
|
||||
fi
|
||||
if [[ -L "${WGDASH}/src/db" ]]; then
|
||||
echo "${WGDASH}/src/db is a symbolic link."
|
||||
else
|
||||
ln -sv /data/db "${WGDASH}/src/db"
|
||||
fi
|
||||
|
||||
# PERSISTENCE FOR wg-dashboard-oidc-providers.json
|
||||
if [ ! -f "/data/wg-dashboard-oidc-providers.json" ]; then
|
||||
echo "Creating wg-dashboard-oidc-providers.json file"
|
||||
cp -v /tmp/wg-dashboard-oidc-providers.json.template /data/wg-dashboard-oidc-providers.json
|
||||
fi
|
||||
if [[ ! -L "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]] && [[ -f "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]]; then
|
||||
echo "Removing ${WGDASH}/src/wg-dashboard-oidc-providers.json since its not a symbolic link."
|
||||
rm -fv "${WGDASH}/src/wg-dashboard-oidc-providers.json"
|
||||
fi
|
||||
if [[ -L "${WGDASH}/src/wg-dashboard-oidc-providers.json" ]]; then
|
||||
echo "${WGDASH}/src/wg-dashboard-oidc-providers.json is a symbolic link."
|
||||
else
|
||||
ln -sv /data/wg-dashboard-oidc-providers.json "${WGDASH}/src/wg-dashboard-oidc-providers.json"
|
||||
fi
|
||||
|
||||
# PERSISTENCE FOR wg-dashboard.ini
|
||||
if [ ! -f "${config_file}" ]; then
|
||||
echo "Creating wg-dashboard.ini file"
|
||||
touch "${config_file}"
|
||||
fi
|
||||
|
||||
if [ ! -f "${WGDASH}/src/wg-dashboard.ini" ]; then
|
||||
ln -s "${config_file}" "${WGDASH}/src/wg-dashboard.ini"
|
||||
if [[ ! -L "${WGDASH}/src/wg-dashboard.ini" ]] && [[ -f "${WGDASH}/src/wg-dashboard.ini" ]]; then
|
||||
echo "Removing ${WGDASH}/src/wg-dashboard.ini since its not a symbolic link."
|
||||
rm -fv "${WGDASH}/src/wg-dashboard.ini"
|
||||
fi
|
||||
if [[ -L "${WGDASH}/src/wg-dashboard.ini" ]]; then
|
||||
echo "${WGDASH}/src/wg-dashboard.ini is a symbolic link."
|
||||
else
|
||||
ln -sv "${config_file}" "${WGDASH}/src/wg-dashboard.ini"
|
||||
fi
|
||||
|
||||
# Setup WireGuard if needed
|
||||
@@ -142,14 +168,25 @@ set_envvars() {
|
||||
# Check if config file is empty
|
||||
if [ ! -s "${config_file}" ]; then
|
||||
echo "Config file is empty. Creating initial structure."
|
||||
elif [[ ${dynamic_config,,} =~ ^(false|no)$ ]]; then
|
||||
echo "Dynamic configuration feature turned off, not changing anything"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Checking basic configuration:"
|
||||
set_ini Peers peer_global_dns "${global_dns}"
|
||||
|
||||
if [ -z "${public_ip}" ]; then
|
||||
public_ip=$(curl -s ifconfig.me)
|
||||
echo "Automatically detected public IP: ${public_ip}"
|
||||
public_ip=$(curl -s https://ifconfig.me)
|
||||
if [ -z "${public_ip}" ]; then
|
||||
echo "Using fallback public IP resolution website"
|
||||
public_ip=$(curl -s https://api.ipify.org)
|
||||
fi
|
||||
if [ -z "${public_ip}" ]; then
|
||||
echo "Failed to resolve publicly. Using private address."
|
||||
public_ip=$(hostname -i)
|
||||
fi
|
||||
echo "Automatically detected public IP: ${public_ip}"
|
||||
fi
|
||||
|
||||
set_ini Peers remote_endpoint "${public_ip}"
|
||||
@@ -183,6 +220,24 @@ set_envvars() {
|
||||
set_ini WireGuardConfiguration autostart "${wg_autostart}"
|
||||
fi
|
||||
|
||||
# Database (check if any settings need to be configured)
|
||||
database_vars=("database_type" "database_host" "database_port" "database_username" "database_password")
|
||||
for var in "${database_vars[@]}"; do
|
||||
if [ -n "${!var}" ]; then
|
||||
echo "Configuring database settings:"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Database (iterate through all possible fields)
|
||||
database_fields=("type:database_type" "host:database_host" "port:database_port"
|
||||
"username:database_username" "password:database_password")
|
||||
|
||||
for field_pair in "${database_fields[@]}"; do
|
||||
IFS=: read -r field var <<< "$field_pair"
|
||||
[[ -n "${!var}" ]] && set_ini Database "$field" "${!var}"
|
||||
done
|
||||
|
||||
# Email (check if any settings need to be configured)
|
||||
email_vars=("email_server" "email_port" "email_encryption" "email_username" "email_password" "email_from" "email_template")
|
||||
for var in "${email_vars[@]}"; do
|
||||
@@ -207,6 +262,9 @@ set_envvars() {
|
||||
start_and_monitor() {
|
||||
printf "\n---------------------- STARTING CORE -----------------------\n"
|
||||
|
||||
# Due to resolvconf resetting the DNS we echo back the one we defined (or fallback to default).
|
||||
resolvconf -u
|
||||
|
||||
# Due to some instances complaining about this, making sure its there every time.
|
||||
mkdir -p /dev/net
|
||||
mknod /dev/net/tun c 10 200
|
||||
@@ -220,7 +278,9 @@ start_and_monitor() {
|
||||
|
||||
${WGDASH}/src/venv/bin/gunicorn --config ${WGDASH}/src/gunicorn.conf.py
|
||||
|
||||
cp /etc/resolv.conf /etc/resolv.conf.docker
|
||||
/usr/sbin/resolvconf -u
|
||||
cat /etc/resolv.conf.docker | resolvconf -a docker.inet
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Loading WGDashboard failed... Look above for details."
|
||||
|
||||
16
docker/wg-dashboard-oidc-providers.json.template
Normal file
16
docker/wg-dashboard-oidc-providers.json.template
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"Admin": {
|
||||
"Provider": {
|
||||
"client_id": "",
|
||||
"client_secret": "",
|
||||
"issuer": ""
|
||||
}
|
||||
},
|
||||
"Client": {
|
||||
"Provider": {
|
||||
"client_id": "",
|
||||
"client_secret": "",
|
||||
"issuer": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ from tzlocal import get_localzone
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for
|
||||
from flask import Blueprint, render_template, abort, request, Flask, current_app, session, redirect, url_for, send_from_directory
|
||||
import os
|
||||
import mimetypes
|
||||
|
||||
from modules.WireguardConfiguration import WireguardConfiguration
|
||||
from modules.DashboardConfig import DashboardConfig
|
||||
@@ -53,6 +54,8 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
||||
|
||||
@client.post(f'{prefix}/api/signup')
|
||||
def ClientAPI_SignUp():
|
||||
if not dashboardConfig.GetConfig("Clients", "sign_up")[1]:
|
||||
abort(404)
|
||||
data = request.get_json()
|
||||
status, msg = dashboardClients.SignUp(**data)
|
||||
return ResponseObject(status, msg)
|
||||
@@ -192,14 +195,26 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
||||
})
|
||||
return ResponseObject(status, msg)
|
||||
|
||||
@client.get(f'{prefix}/assets/<path:filename>')
|
||||
@client.get(f'{prefix}/img/<path:filename>')
|
||||
def serve_client_static(filename):
|
||||
client_dist_folder = os.path.abspath("./static/dist/WGDashboardClient")
|
||||
mimetype = mimetypes.guess_type(filename)[0]
|
||||
subfolder = 'assets' if 'assets' in request.path else 'img'
|
||||
return send_from_directory(os.path.join(client_dist_folder, subfolder), os.path.basename(filename), mimetype=mimetype)
|
||||
|
||||
@client.get(prefix)
|
||||
def ClientIndex():
|
||||
return render_template('client.html')
|
||||
app_prefix = dashboardConfig.GetConfig("Server", "app_prefix")[1]
|
||||
return render_template('client.html', APP_PREFIX=app_prefix)
|
||||
|
||||
@client.get(f'{prefix}/api/serverInformation')
|
||||
def ClientAPI_ServerInformation():
|
||||
return ResponseObject(data={
|
||||
"ServerTimezone": str(get_localzone())
|
||||
"ServerTimezone": str(get_localzone()),
|
||||
"SignUp": {
|
||||
"enable": dashboardConfig.GetConfig("Clients", "sign_up")[1]
|
||||
}
|
||||
})
|
||||
|
||||
@client.get(f'{prefix}/api/validateAuthentication')
|
||||
|
||||
123
src/dashboard.py
123
src/dashboard.py
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
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, current_app
|
||||
from flask_cors import CORS
|
||||
from icmplib import ping, traceroute
|
||||
from flask.json.provider import DefaultJSONProvider
|
||||
@@ -17,8 +17,7 @@ from itertools import islice
|
||||
from sqlalchemy import RowMapping
|
||||
|
||||
from modules.Utilities import (
|
||||
RegexMatch, StringToBoolean,
|
||||
ValidateIPAddressesWithRange, ValidateDNSAddress,
|
||||
RegexMatch, StringToBoolean, ValidateDNSAddress,
|
||||
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
||||
)
|
||||
from packaging import version
|
||||
@@ -30,7 +29,7 @@ from modules.PeerShareLinks import PeerShareLinks
|
||||
from modules.PeerJobs import PeerJobs
|
||||
from modules.DashboardConfig import DashboardConfig
|
||||
from modules.WireguardConfiguration import WireguardConfiguration
|
||||
from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration
|
||||
from modules.AmneziaConfiguration import AmneziaConfiguration
|
||||
|
||||
from client import createClientBlueprint
|
||||
|
||||
@@ -72,7 +71,11 @@ def ResponseObject(status=True, message=None, data=None, status_code = 200) -> F
|
||||
'''
|
||||
Flask App
|
||||
'''
|
||||
app = Flask("WGDashboard", template_folder=os.path.abspath("./static/dist/WGDashboardAdmin"))
|
||||
_, APP_PREFIX_INIT = DashboardConfig().GetConfig("Server", "app_prefix")
|
||||
app = Flask("WGDashboard",
|
||||
template_folder=os.path.abspath("./static/dist/WGDashboardAdmin"),
|
||||
static_folder=os.path.abspath("./static/dist/WGDashboardAdmin"),
|
||||
static_url_path=APP_PREFIX_INIT if APP_PREFIX_INIT else '')
|
||||
|
||||
def peerInformationBackgroundThread():
|
||||
global WireguardConfigurations
|
||||
@@ -92,11 +95,13 @@ def peerInformationBackgroundThread():
|
||||
c.getPeersTransfer()
|
||||
c.getPeersEndpoint()
|
||||
c.getPeers()
|
||||
if delay == 6:
|
||||
if c.configurationInfo.PeerTrafficTracking:
|
||||
c.logPeersTraffic()
|
||||
if c.configurationInfo.PeerHistoricalEndpointTracking:
|
||||
c.logPeersHistoryEndpoint()
|
||||
if DashboardConfig.GetConfig('WireGuardConfiguration', 'peer_tracking')[1] is True:
|
||||
print("[WGDashboard] Tracking Peers")
|
||||
if delay == 6:
|
||||
if c.configurationInfo.PeerTrafficTracking:
|
||||
c.logPeersTraffic()
|
||||
if c.configurationInfo.PeerHistoricalEndpointTracking:
|
||||
c.logPeersHistoryEndpoint()
|
||||
c.getRestrictedPeersList()
|
||||
except Exception as e:
|
||||
app.logger.error(f"[WGDashboard] Background Thread #1 Error", e)
|
||||
@@ -161,10 +166,10 @@ def InitWireguardConfigurationsList(startup: bool = False):
|
||||
if i in WireguardConfigurations.keys():
|
||||
if WireguardConfigurations[i].configurationFileChanged():
|
||||
with app.app_context():
|
||||
WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i)
|
||||
WireguardConfigurations[i] = AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i)
|
||||
else:
|
||||
with app.app_context():
|
||||
WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup)
|
||||
WireguardConfigurations[i] = AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, i, startup=startup)
|
||||
except WireguardConfiguration.InvalidConfigurationFileException as e:
|
||||
app.logger.error(f"{i} have an invalid configuration file.")
|
||||
|
||||
@@ -264,7 +269,9 @@ def auth_req():
|
||||
("username" not in session or session.get("role") != "admin")
|
||||
and (f"{appPrefix}/" != request.path and f"{appPrefix}" != request.path)
|
||||
and not request.path.startswith(f'{appPrefix}/client')
|
||||
and not request.path.startswith(f'{appPrefix}/static')
|
||||
and not request.path.startswith(f'{appPrefix}/img')
|
||||
and not request.path.startswith(f'{appPrefix}/json')
|
||||
and not request.path.startswith(f'{appPrefix}/assets')
|
||||
and request.path not in whiteList
|
||||
):
|
||||
response = Flask.make_response(app, {
|
||||
@@ -422,11 +429,11 @@ def API_addWireguardConfiguration():
|
||||
)
|
||||
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']))
|
||||
AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data, name=data['ConfigurationName']))
|
||||
else:
|
||||
WireguardConfigurations[data['ConfigurationName']] = (
|
||||
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data)) if data.get('Protocol') == 'wg' else (
|
||||
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data))
|
||||
AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data=data))
|
||||
return ResponseObject()
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
|
||||
@@ -523,7 +530,7 @@ def API_renameWireguardConfiguration():
|
||||
|
||||
status, message = rc.renameConfiguration(data.get("NewConfigurationName"))
|
||||
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")))
|
||||
WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, data.get("NewConfigurationName")))
|
||||
else:
|
||||
WireguardConfigurations[data.get("ConfigurationName")] = rc
|
||||
return ResponseObject(status, message)
|
||||
@@ -562,8 +569,8 @@ def API_getAllWireguardConfigurationBackup():
|
||||
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)
|
||||
if RegexMatch(r"^(.+)_(\d+)\.(conf)$", f):
|
||||
s = re.search(r"^(.+)_(\d+)\.(conf)$", f)
|
||||
name = s.group(1)
|
||||
if name not in existingConfiguration:
|
||||
if name not in data['NonExistingConfigurations'].keys():
|
||||
@@ -706,15 +713,30 @@ def API_updatePeerSettings(configName):
|
||||
preshared_key = data['preshared_key']
|
||||
mtu = data['mtu']
|
||||
keepalive = data['keepalive']
|
||||
notes = data.get('notes', '')
|
||||
wireguardConfig = WireguardConfigurations[configName]
|
||||
foundPeer, peer = wireguardConfig.searchPeer(id)
|
||||
if foundPeer:
|
||||
if wireguardConfig.Protocol == 'wg':
|
||||
status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses,
|
||||
allowed_ip, endpoint_allowed_ip, mtu, keepalive)
|
||||
status, msg = peer.updatePeer(name,
|
||||
private_key,
|
||||
preshared_key,
|
||||
dns_addresses,
|
||||
allowed_ip,
|
||||
endpoint_allowed_ip,
|
||||
mtu,
|
||||
keepalive,
|
||||
notes)
|
||||
else:
|
||||
status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses,
|
||||
allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off")
|
||||
status, msg = peer.updatePeer(name,
|
||||
private_key,
|
||||
preshared_key,
|
||||
dns_addresses,
|
||||
allowed_ip,
|
||||
endpoint_allowed_ip,
|
||||
mtu,
|
||||
keepalive,
|
||||
notes)
|
||||
wireguardConfig.getPeers()
|
||||
DashboardWebHooks.RunWebHook('peer_updated', {
|
||||
"configuration": wireguardConfig.Name,
|
||||
@@ -864,6 +886,7 @@ def API_addPeers(configName):
|
||||
|
||||
mtu: int = data.get('mtu', None)
|
||||
keep_alive: int = data.get('keepalive', None)
|
||||
notes: str = data.get('notes', '')
|
||||
preshared_key: str = data.get('preshared_key', "")
|
||||
|
||||
if type(mtu) is not int or mtu < 0 or mtu > 1460:
|
||||
@@ -919,7 +942,7 @@ def API_addPeers(configName):
|
||||
"endpoint_allowed_ip": endpoint_allowed_ip,
|
||||
"mtu": mtu,
|
||||
"keepalive": keep_alive,
|
||||
"advanced_security": "off"
|
||||
"notes": ""
|
||||
})
|
||||
if addedCount == bulkAddAmount:
|
||||
break
|
||||
@@ -962,8 +985,11 @@ def API_addPeers(configName):
|
||||
for i in allowed_ips:
|
||||
found = False
|
||||
for subnet in availableIps.keys():
|
||||
network = ipaddress.ip_network(subnet, False)
|
||||
ap = ipaddress.ip_network(i)
|
||||
try:
|
||||
network = ipaddress.ip_network(subnet, False)
|
||||
ap = ipaddress.ip_network(i)
|
||||
except ValueError as e:
|
||||
return ResponseObject(False, str(e))
|
||||
if network.version == ap.version and ap.subnet_of(network):
|
||||
found = True
|
||||
|
||||
@@ -981,14 +1007,13 @@ def API_addPeers(configName):
|
||||
"DNS": dns_addresses,
|
||||
"mtu": mtu,
|
||||
"keepalive": keep_alive,
|
||||
"advanced_security": "off"
|
||||
"notes": notes
|
||||
}]
|
||||
)
|
||||
return ResponseObject(status=status, message=message, data=addedPeers)
|
||||
except Exception as e:
|
||||
app.logger.error("Add peers failed", e)
|
||||
return ResponseObject(False,
|
||||
f"Add peers failed. Reason: {message}")
|
||||
return ResponseObject(False, f"Add peers failed.")
|
||||
|
||||
return ResponseObject(False, "Configuration does not exist")
|
||||
|
||||
@@ -1125,13 +1150,24 @@ def API_GetPeerTraffics():
|
||||
@app.get(f'{APP_PREFIX}/api/getPeerTrackingTableCounts')
|
||||
def API_GetPeerTrackingTableCounts():
|
||||
configurationName = request.args.get("configurationName")
|
||||
if configurationName not in WireguardConfigurations.keys():
|
||||
if configurationName and configurationName not in WireguardConfigurations.keys():
|
||||
return ResponseObject(False, "Configuration does not exist")
|
||||
c = WireguardConfigurations.get(configurationName)
|
||||
return ResponseObject(data={
|
||||
"TrafficTrackingTableSize": c.getTransferTableSize(),
|
||||
"HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize()
|
||||
})
|
||||
|
||||
if configurationName:
|
||||
c = WireguardConfigurations.get(configurationName)
|
||||
return ResponseObject(data={
|
||||
"TrafficTrackingTableSize": c.getTransferTableSize(),
|
||||
"HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize()
|
||||
})
|
||||
|
||||
d = {}
|
||||
for i in WireguardConfigurations.keys():
|
||||
c = WireguardConfigurations.get(i)
|
||||
d[i] = {
|
||||
"TrafficTrackingTableSize": c.getTransferTableSize(),
|
||||
"HistoricalTrackingTableSize": c.getHistoricalEndpointTableSize()
|
||||
}
|
||||
return ResponseObject(data=d)
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/downloadPeerTrackingTable')
|
||||
def API_DownloadPeerTackingTable():
|
||||
@@ -1325,12 +1361,17 @@ def API_traceroute_execute():
|
||||
data=json.dumps([x['ip'] for x in result]))
|
||||
d = r.json()
|
||||
for i in range(len(result)):
|
||||
result[i]['geo'] = d[i]
|
||||
result[i]['geo'] = d[i]
|
||||
|
||||
return ResponseObject(data=result)
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Failed to gather the geolocation data: {e}")
|
||||
return ResponseObject(data=result, message="Failed to request IP address geolocation")
|
||||
return ResponseObject(data=result)
|
||||
except Exception as exp:
|
||||
return ResponseObject(False, exp)
|
||||
|
||||
except Exception as e:
|
||||
app.logger.error(f"Failed to execute the traceroute: {e}")
|
||||
return ResponseObject(data=[], message="Failed to traceroute the given parameter")
|
||||
else:
|
||||
return ResponseObject(False, "Please provide ipAddress")
|
||||
|
||||
@@ -1446,7 +1487,7 @@ def API_Locale_Update():
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/email/ready')
|
||||
def API_Email_Ready():
|
||||
return ResponseObject(EmailSender.ready())
|
||||
return ResponseObject(EmailSender.is_ready())
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/email/send')
|
||||
def API_Email_Send():
|
||||
@@ -1700,9 +1741,9 @@ Index Page
|
||||
|
||||
@app.get(f'{APP_PREFIX}/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
return render_template('index.html', APP_PREFIX=APP_PREFIX)
|
||||
|
||||
if __name__ == "__main__":
|
||||
startThreads()
|
||||
DashboardPlugins.startThreads()
|
||||
app.run(host=app_ip, debug=False, port=app_port)
|
||||
app.run(host=app_ip, debug=False, port=app_port)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import dashboard
|
||||
import os
|
||||
from datetime import datetime
|
||||
global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger, Dash
|
||||
app_host, app_port = dashboard.gunicornConfig()
|
||||
@@ -16,7 +17,7 @@ daemon = True
|
||||
pidfile = './gunicorn.pid'
|
||||
wsgi_app = "dashboard:app"
|
||||
accesslog = f"./log/access_{date}.log"
|
||||
loglevel = "info"
|
||||
loglevel = os.environ['log_level'] if 'log_level' in os.environ else 'info'
|
||||
capture_output = True
|
||||
errorlog = f"./log/error_{date}.log"
|
||||
pythonpath = "., ./modules"
|
||||
|
||||
@@ -4,28 +4,39 @@ AmneziaWG Configuration
|
||||
import random, sqlalchemy, os, subprocess, re, uuid
|
||||
from flask import current_app
|
||||
from .PeerJobs import PeerJobs
|
||||
from .AmneziaWGPeer import AmneziaWGPeer
|
||||
from .AmneziaPeer import AmneziaPeer
|
||||
from .PeerShareLinks import PeerShareLinks
|
||||
from .Utilities import RegexMatch
|
||||
from .Utilities import RegexMatch, CheckAddress, CheckPeerKey
|
||||
from .WireguardConfiguration import WireguardConfiguration
|
||||
from .DashboardWebHooks import DashboardWebHooks
|
||||
|
||||
|
||||
class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
def __init__(self, DashboardConfig,
|
||||
class AmneziaConfiguration(WireguardConfiguration):
|
||||
def __init__(self,
|
||||
DashboardConfig,
|
||||
AllPeerJobs: PeerJobs,
|
||||
AllPeerShareLinks: PeerShareLinks,
|
||||
DashboardWebHooks: DashboardWebHooks,
|
||||
name: str = None, data: dict = None, backup: dict = None, startup: bool = False):
|
||||
name: str = None,
|
||||
data: dict = None,
|
||||
backup: dict = None,
|
||||
startup: bool = False):
|
||||
self.Jc = 0
|
||||
self.Jmin = 0
|
||||
self.Jmax = 0
|
||||
self.S1 = 0
|
||||
self.S2 = 0
|
||||
self.S3 = 0
|
||||
self.S4 = 0
|
||||
self.H1 = 1
|
||||
self.H2 = 2
|
||||
self.H3 = 3
|
||||
self.H4 = 4
|
||||
self.I1 = "0"
|
||||
self.I2 = "0"
|
||||
self.I3 = "0"
|
||||
self.I4 = "0"
|
||||
self.I5 = "0"
|
||||
|
||||
super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False)
|
||||
|
||||
@@ -58,65 +69,64 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
"Jmax": self.Jmax,
|
||||
"S1": self.S1,
|
||||
"S2": self.S2,
|
||||
"S3": self.S3,
|
||||
"S4": self.S4,
|
||||
"H1": self.H1,
|
||||
"H2": self.H2,
|
||||
"H3": self.H3,
|
||||
"H4": self.H4
|
||||
"H4": self.H4,
|
||||
"I1": self.I1,
|
||||
"I2": self.I2,
|
||||
"I3": self.I3,
|
||||
"I4": self.I4,
|
||||
"I5": self.I5
|
||||
}
|
||||
|
||||
def createDatabase(self, dbName = None):
|
||||
def generate_column_obj():
|
||||
return [
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('notes', sqlalchemy.Text),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255))
|
||||
]
|
||||
|
||||
if dbName is None:
|
||||
dbName = self.Name
|
||||
|
||||
|
||||
self.peersTable = sqlalchemy.Table(
|
||||
dbName, self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('advanced_security', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
|
||||
extend_existing=True
|
||||
f'{dbName}', self.metadata, *generate_column_obj(), extend_existing=True
|
||||
)
|
||||
|
||||
self.peersRestrictedTable = sqlalchemy.Table(
|
||||
f'{dbName}_restrict_access', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('advanced_security', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
|
||||
extend_existing=True
|
||||
f'{dbName}_restrict_access', self.metadata, *generate_column_obj(), extend_existing=True
|
||||
)
|
||||
|
||||
self.peersDeletedTable = sqlalchemy.Table(
|
||||
f'{dbName}_deleted', self.metadata, *generate_column_obj(), extend_existing=True
|
||||
)
|
||||
|
||||
if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite':
|
||||
time_col_type = sqlalchemy.DATETIME
|
||||
else:
|
||||
time_col_type = sqlalchemy.TIMESTAMP
|
||||
|
||||
self.peersTransferTable = sqlalchemy.Table(
|
||||
f'{dbName}_transfer', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||
@@ -126,38 +136,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP),
|
||||
server_default=sqlalchemy.func.now()),
|
||||
extend_existing=True
|
||||
)
|
||||
self.peersDeletedTable = sqlalchemy.Table(
|
||||
f'{dbName}_deleted', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('advanced_security', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
|
||||
extend_existing=True
|
||||
)
|
||||
self.infoTable = sqlalchemy.Table(
|
||||
'ConfigurationsInfo', self.metadata,
|
||||
sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True),
|
||||
sqlalchemy.Column('Info', sqlalchemy.Text),
|
||||
sqlalchemy.Column('time', time_col_type, server_default=sqlalchemy.func.now()),
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
@@ -165,15 +144,20 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
f'{dbName}_history_endpoint', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False),
|
||||
sqlalchemy.Column('time',
|
||||
(sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)),
|
||||
sqlalchemy.Column('time', time_col_type)
|
||||
)
|
||||
|
||||
self.infoTable = sqlalchemy.Table(
|
||||
'ConfigurationsInfo', self.metadata,
|
||||
sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True),
|
||||
sqlalchemy.Column('Info', sqlalchemy.Text),
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
self.metadata.create_all(self.engine)
|
||||
|
||||
def getPeers(self):
|
||||
self.Peers.clear()
|
||||
self.Peers.clear()
|
||||
if self.configurationFileChanged():
|
||||
with open(self.configPath, 'r') as configFile:
|
||||
p = []
|
||||
@@ -211,11 +195,9 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
if tempPeer is None:
|
||||
tempPeer = {
|
||||
"id": i['PublicKey'],
|
||||
"advanced_security": i.get('AdvancedSecurity', 'off'),
|
||||
"private_key": "",
|
||||
"DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1],
|
||||
"endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[
|
||||
1],
|
||||
"endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1],
|
||||
"name": i.get("name"),
|
||||
"total_receive": 0,
|
||||
"total_sent": 0,
|
||||
@@ -229,6 +211,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
"cumu_data": 0,
|
||||
"mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1],
|
||||
"keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1],
|
||||
"notes": "",
|
||||
"remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
|
||||
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
|
||||
}
|
||||
@@ -243,14 +226,14 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
self.peersTable.columns.id == i['PublicKey']
|
||||
)
|
||||
)
|
||||
self.Peers.append(AmneziaWGPeer(tempPeer, self))
|
||||
self.Peers.append(AmneziaPeer(tempPeer, self))
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"{self.Name} getPeers() Error", e)
|
||||
else:
|
||||
with self.engine.connect() as conn:
|
||||
existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall()
|
||||
for i in existingPeers:
|
||||
self.Peers.append(AmneziaWGPeer(i, self))
|
||||
self.Peers.append(AmneziaPeer(i, self))
|
||||
|
||||
def addPeers(self, peers: list) -> tuple[bool, list, str]:
|
||||
result = {
|
||||
@@ -258,6 +241,15 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
"peers": []
|
||||
}
|
||||
try:
|
||||
cleanedAllowedIPs = {}
|
||||
for p in peers:
|
||||
newAllowedIPs = p['allowed_ip'].replace(" ", "")
|
||||
if not CheckAddress(newAllowedIPs):
|
||||
return False, [], "Allowed IPs entry format is incorrect"
|
||||
if not CheckPeerKey(p["id"]):
|
||||
return False, [], "Peer key format is incorrect"
|
||||
cleanedAllowedIPs[p["id"]] = newAllowedIPs
|
||||
|
||||
with self.engine.begin() as conn:
|
||||
for i in peers:
|
||||
newPeer = {
|
||||
@@ -278,9 +270,9 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
"cumu_data": 0,
|
||||
"mtu": i['mtu'],
|
||||
"keepalive": i['keepalive'],
|
||||
"notes": i.get('notes', ''),
|
||||
"remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
|
||||
"preshared_key": i["preshared_key"],
|
||||
"advanced_security": i['advanced_security']
|
||||
"preshared_key": i["preshared_key"]
|
||||
}
|
||||
conn.execute(
|
||||
self.peersTable.insert().values(newPeer)
|
||||
@@ -293,13 +285,15 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
with open(uid, "w+") as f:
|
||||
f.write(p['preshared_key'])
|
||||
|
||||
subprocess.check_output(
|
||||
f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips", cleanedAllowedIPs[p["id"]], "preshared-key", uid if presharedKeyExist else "/dev/null"]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if presharedKeyExist:
|
||||
os.remove(uid)
|
||||
subprocess.check_output(
|
||||
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
|
||||
command = [f"{self.Protocol}-quick", "save", self.Name]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
self.getPeers()
|
||||
for p in peers:
|
||||
p = self.searchPeer(p['id'])
|
||||
@@ -311,7 +305,7 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
})
|
||||
except Exception as e:
|
||||
current_app.logger.error("Add peers error", e)
|
||||
return False, [], str(e)
|
||||
return False, [], "Internal server error"
|
||||
return True, result['peers'], ""
|
||||
|
||||
def getRestrictedPeers(self):
|
||||
@@ -319,4 +313,4 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
||||
with self.engine.connect() as conn:
|
||||
restricted = conn.execute(self.peersRestrictedTable.select()).mappings().fetchall()
|
||||
for i in restricted:
|
||||
self.RestrictedPeers.append(AmneziaWGPeer(i, self))
|
||||
self.RestrictedPeers.append(AmneziaPeer(i, self))
|
||||
120
src/modules/AmneziaPeer.py
Normal file
120
src/modules/AmneziaPeer.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import os
|
||||
from flask import current_app
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
from flask import current_app
|
||||
from .Peer import Peer
|
||||
from .Utilities import CheckAddress, ValidateDNSAddress, GenerateWireguardPublicKey
|
||||
|
||||
|
||||
class AmneziaPeer(Peer):
|
||||
def __init__(self, tableData, configuration):
|
||||
super().__init__(tableData, configuration)
|
||||
|
||||
|
||||
def updatePeer(self, name: str, private_key: str,
|
||||
preshared_key: str,
|
||||
dns_addresses: str,
|
||||
allowed_ip: str,
|
||||
endpoint_allowed_ip: str,
|
||||
mtu: int,
|
||||
keepalive: int,
|
||||
notes: str
|
||||
) -> tuple[bool, str | None]:
|
||||
|
||||
if not self.configuration.getStatus():
|
||||
self.configuration.toggleConfiguration()
|
||||
|
||||
# Before we do any compute, let us check if the given endpoint allowed ip is valid at all
|
||||
if not CheckAddress(endpoint_allowed_ip):
|
||||
return False, f"Endpoint Allowed IPs format is incorrect"
|
||||
|
||||
peers = []
|
||||
for peer in self.configuration.getPeersList():
|
||||
# Make sure to exclude your own data when updating since its not really relevant
|
||||
if peer.id != self.id:
|
||||
continue
|
||||
peers.append(peer)
|
||||
|
||||
used_allowed_ips = []
|
||||
for peer in peers:
|
||||
ips = peer.allowed_ip.split(',')
|
||||
ips = [ip.strip() for ip in ips]
|
||||
used_allowed_ips.append(ips)
|
||||
|
||||
if allowed_ip in used_allowed_ips:
|
||||
return False, "Allowed IP already taken by another peer"
|
||||
|
||||
if not ValidateDNSAddress(dns_addresses):
|
||||
return False, f"DNS IP-Address or FQDN is incorrect"
|
||||
|
||||
if isinstance(mtu, str):
|
||||
mtu = 0
|
||||
|
||||
if isinstance(keepalive, str):
|
||||
keepalive = 0
|
||||
|
||||
if mtu not in range(0, 1461):
|
||||
return False, "MTU format is not correct"
|
||||
|
||||
if keepalive < 0:
|
||||
return False, "Persistent Keepalive format is not correct"
|
||||
|
||||
if len(private_key) > 0:
|
||||
pubKey = GenerateWireguardPublicKey(private_key)
|
||||
if not pubKey[0] or pubKey[1] != self.id:
|
||||
return False, "Private key does not match with the public key"
|
||||
|
||||
try:
|
||||
rand = random.Random()
|
||||
uid = str(uuid.UUID(int=rand.getrandbits(128), version=4))
|
||||
psk_exist = len(preshared_key) > 0
|
||||
|
||||
if psk_exist:
|
||||
with open(uid, "w+") as f:
|
||||
f.write(preshared_key)
|
||||
|
||||
newAllowedIPs = allowed_ip.replace(" ", "")
|
||||
if not CheckAddress(newAllowedIPs):
|
||||
return False, "Allowed IPs entry format is incorrect"
|
||||
|
||||
command = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips", newAllowedIPs, "preshared-key", uid if psk_exist else "/dev/null"]
|
||||
|
||||
updateAllowedIp = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if psk_exist: os.remove(uid)
|
||||
|
||||
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
||||
current_app.logger.error(f"Update peer failed when updating Allowed IPs.\nInput: {newAllowedIPs}\nOutput: {updateAllowedIp.decode().strip('\n')}")
|
||||
return False, "Internal server error"
|
||||
|
||||
command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name]
|
||||
saveConfig = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
||||
current_app.logger.error("Update peer failed when saving the configuration")
|
||||
return False, "Internal server error"
|
||||
|
||||
with self.configuration.engine.begin() as conn:
|
||||
conn.execute(
|
||||
self.configuration.peersTable.update().values({
|
||||
"name": name,
|
||||
"private_key": private_key,
|
||||
"DNS": dns_addresses,
|
||||
"endpoint_allowed_ip": endpoint_allowed_ip,
|
||||
"mtu": mtu,
|
||||
"keepalive": keepalive,
|
||||
"notes": notes,
|
||||
"preshared_key": preshared_key
|
||||
}).where(
|
||||
self.configuration.peersTable.c.id == self.id
|
||||
)
|
||||
)
|
||||
self.configuration.getPeers()
|
||||
return True, None
|
||||
except subprocess.CalledProcessError as exc:
|
||||
current_app.logger.error(f"Subprocess call failed:\n{exc.output.decode('UTF-8')}")
|
||||
return False, "Internal server error"
|
||||
@@ -1,92 +0,0 @@
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import uuid
|
||||
|
||||
from .Peer import Peer
|
||||
from .Utilities import ValidateIPAddressesWithRange, ValidateDNSAddress, GenerateWireguardPublicKey
|
||||
|
||||
|
||||
class AmneziaWGPeer(Peer):
|
||||
def __init__(self, tableData, configuration):
|
||||
self.advanced_security = tableData["advanced_security"]
|
||||
super().__init__(tableData, configuration)
|
||||
|
||||
|
||||
def updatePeer(self, name: str, private_key: str,
|
||||
preshared_key: str,
|
||||
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
|
||||
keepalive: int, advanced_security: str) -> tuple[bool, str] or tuple[bool, None]:
|
||||
if not self.configuration.getStatus():
|
||||
self.configuration.toggleConfiguration()
|
||||
|
||||
existingAllowedIps = [item for row in list(
|
||||
map(lambda x: [q.strip() for q in x.split(',')],
|
||||
map(lambda y: y.allowed_ip,
|
||||
list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row]
|
||||
|
||||
if allowed_ip in existingAllowedIps:
|
||||
return False, "Allowed IP already taken by another peer"
|
||||
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
|
||||
return False, f"Endpoint Allowed IPs format is incorrect"
|
||||
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
|
||||
return False, f"DNS format is incorrect"
|
||||
|
||||
if type(mtu) is str:
|
||||
mtu = 0
|
||||
|
||||
if type(keepalive) is str:
|
||||
keepalive = 0
|
||||
|
||||
if mtu < 0 or mtu > 1460:
|
||||
return False, "MTU format is not correct"
|
||||
if keepalive < 0:
|
||||
return False, "Persistent Keepalive format is not correct"
|
||||
if advanced_security != "on" and advanced_security != "off":
|
||||
return False, "Advanced Security can only be on or off"
|
||||
if len(private_key) > 0:
|
||||
pubKey = GenerateWireguardPublicKey(private_key)
|
||||
if not pubKey[0] or pubKey[1] != self.id:
|
||||
return False, "Private key does not match with the public key"
|
||||
try:
|
||||
rd = random.Random()
|
||||
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
|
||||
pskExist = len(preshared_key) > 0
|
||||
|
||||
if pskExist:
|
||||
with open(uid, "w+") as f:
|
||||
f.write(preshared_key)
|
||||
newAllowedIPs = allowed_ip.replace(" ", "")
|
||||
updateAllowedIp = subprocess.check_output(
|
||||
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
|
||||
if pskExist: os.remove(uid)
|
||||
|
||||
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
||||
return False, "Update peer failed when updating Allowed IPs"
|
||||
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
||||
return False, "Update peer failed when saving the configuration"
|
||||
|
||||
with self.configuration.engine.begin() as conn:
|
||||
conn.execute(
|
||||
self.configuration.peersTable.update().values({
|
||||
"name": name,
|
||||
"private_key": private_key,
|
||||
"DNS": dns_addresses,
|
||||
"endpoint_allowed_ip": endpoint_allowed_ip,
|
||||
"mtu": mtu,
|
||||
"keepalive": keepalive,
|
||||
"preshared_key": preshared_key,
|
||||
"advanced_security": advanced_security
|
||||
}).where(
|
||||
self.configuration.peersTable.c.id == self.id
|
||||
)
|
||||
)
|
||||
self.configuration.getPeers()
|
||||
return True, None
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, exc.output.decode("UTF-8").strip()
|
||||
@@ -8,7 +8,7 @@ import pyotp
|
||||
import sqlalchemy as db
|
||||
import requests
|
||||
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .DashboardClientsPeerAssignment import DashboardClientsPeerAssignment
|
||||
from .DashboardClientsTOTP import DashboardClientsTOTP
|
||||
from .DashboardOIDC import DashboardOIDC
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .DashboardLogger import DashboardLogger
|
||||
import sqlalchemy as db
|
||||
from .WireguardConfiguration import WireguardConfiguration
|
||||
|
||||
@@ -3,7 +3,7 @@ import hashlib
|
||||
import uuid
|
||||
|
||||
import sqlalchemy as db
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
|
||||
|
||||
class DashboardClientsTOTP:
|
||||
|
||||
@@ -7,19 +7,15 @@ import sqlalchemy as db
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from flask import current_app
|
||||
from .ConnectionString import ConnectionString
|
||||
from .Utilities import (
|
||||
GetRemoteEndpoint, ValidateDNSAddress
|
||||
)
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .Utilities import (GetRemoteEndpoint, ValidateDNSAddress)
|
||||
from .DashboardAPIKey import DashboardAPIKey
|
||||
|
||||
|
||||
|
||||
class DashboardConfig:
|
||||
DashboardVersion = 'v4.3.2'
|
||||
DashboardVersion = 'v4.3.3'
|
||||
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
|
||||
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini')
|
||||
|
||||
|
||||
def __init__(self):
|
||||
if not os.path.exists(DashboardConfig.ConfigurationFilePath):
|
||||
open(DashboardConfig.ConfigurationFilePath, "x")
|
||||
@@ -83,9 +79,11 @@ class DashboardConfig:
|
||||
},
|
||||
"Clients": {
|
||||
"enable": "true",
|
||||
"sign_up": "true"
|
||||
},
|
||||
"WireGuardConfiguration": {
|
||||
"autostart": ""
|
||||
"autostart": "",
|
||||
"peer_tracking": "false"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +100,54 @@ class DashboardConfig:
|
||||
self.APIAccessed = False
|
||||
self.SetConfig("Server", "version", DashboardConfig.DashboardVersion)
|
||||
|
||||
def EnsureDatabaseIntegrity(self, wireguardConfigurations):
|
||||
expected_columns = {
|
||||
'id': db.String(255),
|
||||
'private_key': db.String(255),
|
||||
'DNS': db.Text,
|
||||
'endpoint_allowed_ip': db.Text,
|
||||
'name': db.Text,
|
||||
'total_receive': db.Float,
|
||||
'total_sent': db.Float,
|
||||
'total_data': db.Float,
|
||||
'endpoint': db.String(255),
|
||||
'status': db.String(255),
|
||||
'latest_handshake': db.String(255),
|
||||
'allowed_ip': db.String(255),
|
||||
'cumu_receive': db.Float,
|
||||
'cumu_sent': db.Float,
|
||||
'cumu_data': db.Float,
|
||||
'mtu': db.Integer,
|
||||
'keepalive': db.Integer,
|
||||
'notes': db.Text,
|
||||
'remote_endpoint': db.String(255),
|
||||
'preshared_key': db.String(255)
|
||||
}
|
||||
|
||||
inspector = db.inspect(self.engine)
|
||||
|
||||
with self.engine.begin() as conn:
|
||||
for cfg_name, cfg_obj in wireguardConfigurations.items():
|
||||
tables_to_check = [
|
||||
cfg_name,
|
||||
f'{cfg_name}_restrict_access',
|
||||
f'{cfg_name}_deleted'
|
||||
]
|
||||
|
||||
for table_name in tables_to_check:
|
||||
if not table_name:
|
||||
continue
|
||||
if not inspector.has_table(table_name):
|
||||
continue
|
||||
|
||||
existing_columns = [c['name'] for c in inspector.get_columns(table_name)]
|
||||
|
||||
for col_name, col_type in expected_columns.items():
|
||||
if col_name not in existing_columns:
|
||||
type_str = col_type().compile(dialect=self.engine.dialect)
|
||||
current_app.logger.info(f"Adding missing column '{col_name}' to table '{table_name}'")
|
||||
conn.execute(db.text(f'ALTER TABLE "{table_name}" ADD COLUMN "{col_name}" {type_str}'))
|
||||
|
||||
def getConnectionString(self, database) -> str or None:
|
||||
sqlitePath = os.path.join(DashboardConfig.ConfigurationPath, "db")
|
||||
|
||||
@@ -116,7 +162,7 @@ class DashboardConfig:
|
||||
cn = f'sqlite:///{os.path.join(sqlitePath, f"{database}.db")}'
|
||||
if not database_exists(cn):
|
||||
create_database(cn)
|
||||
return cn
|
||||
return cn
|
||||
|
||||
def __createAPIKeyTable(self):
|
||||
self.apiKeyTable = db.Table('DashboardAPIKeys', self.dbMetadata,
|
||||
|
||||
@@ -4,7 +4,7 @@ Dashboard Logger Class
|
||||
import uuid
|
||||
import sqlalchemy as db
|
||||
from flask import current_app
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
|
||||
|
||||
class DashboardLogger:
|
||||
|
||||
@@ -8,7 +8,7 @@ from datetime import datetime, timedelta
|
||||
import requests
|
||||
from pydantic import BaseModel, field_serializer
|
||||
import sqlalchemy as db
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from flask import current_app
|
||||
|
||||
WebHookActions = ['peer_created', 'peer_deleted', 'peer_updated']
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import configparser
|
||||
import os
|
||||
from sqlalchemy_utils import database_exists, create_database
|
||||
from flask import current_app
|
||||
|
||||
def ConnectionString(database) -> str:
|
||||
parser = configparser.ConfigParser(strict=False)
|
||||
parser.read_file(open('wg-dashboard.ini', "r+"))
|
||||
|
||||
sqlitePath = os.path.join("db")
|
||||
if not os.path.isdir(sqlitePath):
|
||||
os.mkdir(sqlitePath)
|
||||
|
||||
if parser.get("Database", "type") == "postgresql":
|
||||
cn = f'postgresql+psycopg://{parser.get("Database", "username")}:{parser.get("Database", "password")}@{parser.get("Database", "host")}/{database}'
|
||||
elif parser.get("Database", "type") == "mysql":
|
||||
@@ -19,7 +20,6 @@ def ConnectionString(database) -> str:
|
||||
if not database_exists(cn):
|
||||
create_database(cn)
|
||||
except Exception as e:
|
||||
current_app.logger.error("Database error. Terminating...", e)
|
||||
exit(1)
|
||||
|
||||
|
||||
return cn
|
||||
@@ -1,76 +1,101 @@
|
||||
import os.path
|
||||
import ssl
|
||||
import smtplib
|
||||
|
||||
# Email libaries
|
||||
from email import encoders
|
||||
from email.header import Header
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr
|
||||
from email.utils import formatdate
|
||||
|
||||
class EmailSender:
|
||||
def __init__(self, DashboardConfig):
|
||||
self.smtp = None
|
||||
self.DashboardConfig = DashboardConfig
|
||||
|
||||
if not os.path.exists('./attachments'):
|
||||
os.mkdir('./attachments')
|
||||
|
||||
def Server(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "server")[1]
|
||||
|
||||
def Port(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "port")[1]
|
||||
|
||||
def Encryption(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "encryption")[1]
|
||||
|
||||
def Username(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "username")[1]
|
||||
|
||||
def Password(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "email_password")[1]
|
||||
|
||||
def SendFrom(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "send_from")[1]
|
||||
|
||||
# Thank you, @gdeeble from GitHub
|
||||
def AuthenticationRequired(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "authentication_required")[1]
|
||||
|
||||
def ready(self):
|
||||
if self.AuthenticationRequired():
|
||||
return all([self.Server(), self.Port(), self.Encryption(), self.Username(), self.Password(), self.SendFrom()])
|
||||
return all([self.Server(), self.Port(), self.Encryption(), self.SendFrom()])
|
||||
self.refresh_vals()
|
||||
|
||||
def send(self, receiver, subject, body, includeAttachment = False, attachmentName = "") -> tuple[bool, str] | tuple[bool, None]:
|
||||
if self.ready():
|
||||
try:
|
||||
self.smtp = smtplib.SMTP(self.Server(), port=int(self.Port()))
|
||||
self.smtp.ehlo()
|
||||
if self.Encryption() == "STARTTLS":
|
||||
self.smtp.starttls()
|
||||
if self.AuthenticationRequired():
|
||||
self.smtp.login(self.Username(), self.Password())
|
||||
message = MIMEMultipart()
|
||||
message['Subject'] = subject
|
||||
message['From'] = self.SendFrom()
|
||||
message["To"] = receiver
|
||||
message.attach(MIMEText(body, "plain"))
|
||||
def refresh_vals(self) -> None:
|
||||
self.Server = self.DashboardConfig.GetConfig("Email", "server")[1]
|
||||
self.Port = self.DashboardConfig.GetConfig("Email", "port")[1]
|
||||
|
||||
if includeAttachment and len(attachmentName) > 0:
|
||||
attachmentPath = os.path.join('./attachments', attachmentName)
|
||||
if os.path.exists(attachmentPath):
|
||||
attachment = MIMEBase("application", "octet-stream")
|
||||
with open(os.path.join('./attachments', attachmentName), 'rb') as f:
|
||||
attachment.set_payload(f.read())
|
||||
encoders.encode_base64(attachment)
|
||||
attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",)
|
||||
message.attach(attachment)
|
||||
else:
|
||||
self.smtp.close()
|
||||
return False, "Attachment does not exist"
|
||||
self.smtp.sendmail(self.SendFrom(), receiver, message.as_string())
|
||||
self.smtp.close()
|
||||
return True, None
|
||||
except Exception as e:
|
||||
return False, f"Send failed | Reason: {e}"
|
||||
return False, "SMTP not configured"
|
||||
self.Encryption = self.DashboardConfig.GetConfig("Email", "encryption")[1]
|
||||
self.AuthRequired = self.DashboardConfig.GetConfig("Email", "authentication_required")[1]
|
||||
self.Username = self.DashboardConfig.GetConfig("Email", "username")[1]
|
||||
self.Password = self.DashboardConfig.GetConfig("Email", "email_password")[1]
|
||||
|
||||
self.SendFrom = self.DashboardConfig.GetConfig("Email", "send_from")[1]
|
||||
|
||||
def is_ready(self) -> bool:
|
||||
self.refresh_vals()
|
||||
|
||||
if self.AuthRequired:
|
||||
ready = all([
|
||||
self.Server, self.Port, self.Encryption,
|
||||
self.Username, self.Password, self.SendFrom
|
||||
])
|
||||
else:
|
||||
ready = all([
|
||||
self.Server, self.Port, self.Encryption, self.SendFrom
|
||||
])
|
||||
return ready
|
||||
|
||||
def send(self, receiver, subject, body, includeAttachment: bool = False, attachmentName: str = "") -> tuple[bool, str | None]:
|
||||
if not self.is_ready():
|
||||
return False, "SMTP not configured"
|
||||
|
||||
message = MIMEMultipart()
|
||||
message['Subject'] = subject
|
||||
message['From'] = self.SendFrom
|
||||
message["To"] = receiver
|
||||
message["Date"] = formatdate(localtime=True)
|
||||
message.attach(MIMEText(body, "plain"))
|
||||
|
||||
if includeAttachment and len(attachmentName) > 0:
|
||||
attachmentPath = os.path.join('./attachments', attachmentName)
|
||||
|
||||
if not os.path.exists(attachmentPath):
|
||||
return False, "Attachment does not exist"
|
||||
|
||||
attachment = MIMEBase("application", "octet-stream")
|
||||
with open(os.path.join('./attachments', attachmentName), 'rb') as f:
|
||||
attachment.set_payload(f.read())
|
||||
|
||||
encoders.encode_base64(attachment)
|
||||
attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",)
|
||||
message.attach(attachment)
|
||||
|
||||
smtp = None
|
||||
try:
|
||||
context = ssl.create_default_context()
|
||||
if self.Encryption == "IMPLICITTLS":
|
||||
smtp = smtplib.SMTP_SSL(self.Server, port=int(self.Port), context=context)
|
||||
else:
|
||||
smtp = smtplib.SMTP(self.Server, port=int(self.Port))
|
||||
smtp.ehlo()
|
||||
|
||||
# Configure SMTP encryption type
|
||||
if self.Encryption == "STARTTLS":
|
||||
smtp.starttls(context=context)
|
||||
smtp.ehlo()
|
||||
|
||||
# Log into the SMTP server if required
|
||||
if self.AuthRequired:
|
||||
smtp.login(self.Username, self.Password)
|
||||
|
||||
# Send the actual email from the SMTP object
|
||||
smtp.sendmail(self.SendFrom, receiver, message.as_string())
|
||||
return True, None
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Send failed | Reason: {e}"
|
||||
|
||||
finally:
|
||||
if smtp:
|
||||
try:
|
||||
smtp.quit()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -2,7 +2,7 @@ import uuid
|
||||
|
||||
from pydantic import BaseModel, field_serializer
|
||||
import sqlalchemy as db
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
|
||||
|
||||
class NewConfigurationTemplate(BaseModel):
|
||||
|
||||
@@ -10,8 +10,9 @@ from datetime import timedelta
|
||||
import jinja2
|
||||
import sqlalchemy as db
|
||||
from .PeerJob import PeerJob
|
||||
from flask import current_app
|
||||
from .PeerShareLink import PeerShareLink
|
||||
from .Utilities import GenerateWireguardPublicKey, ValidateIPAddressesWithRange, ValidateDNSAddress
|
||||
from .Utilities import GenerateWireguardPublicKey, CheckAddress, ValidateDNSAddress
|
||||
|
||||
|
||||
class Peer:
|
||||
@@ -34,6 +35,7 @@ class Peer:
|
||||
self.cumu_data = tableData["cumu_data"]
|
||||
self.mtu = tableData["mtu"]
|
||||
self.keepalive = tableData["keepalive"]
|
||||
self.notes = tableData.get("notes", "")
|
||||
self.remote_endpoint = tableData["remote_endpoint"]
|
||||
self.preshared_key = tableData["preshared_key"]
|
||||
self.jobs: list[PeerJob] = []
|
||||
@@ -49,62 +51,89 @@ class Peer:
|
||||
def __repr__(self):
|
||||
return str(self.toJson())
|
||||
|
||||
def updatePeer(self, name: str, private_key: str,
|
||||
def updatePeer(self, name: str,
|
||||
private_key: str,
|
||||
preshared_key: str,
|
||||
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
|
||||
keepalive: int) -> tuple[bool, str] or tuple[bool, None]:
|
||||
dns_addresses: str,
|
||||
allowed_ip: str,
|
||||
endpoint_allowed_ip: str,
|
||||
mtu: int,
|
||||
keepalive: int,
|
||||
notes: str
|
||||
) -> tuple[bool, str | None]:
|
||||
|
||||
if not self.configuration.getStatus():
|
||||
self.configuration.toggleConfiguration()
|
||||
|
||||
existingAllowedIps = [item for row in list(
|
||||
map(lambda x: [q.strip() for q in x.split(',')],
|
||||
map(lambda y: y.allowed_ip,
|
||||
list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row]
|
||||
|
||||
if allowed_ip in existingAllowedIps:
|
||||
return False, "Allowed IP already taken by another peer"
|
||||
|
||||
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
|
||||
# Before we do any compute, let us check if the given endpoint allowed ip is valid at all
|
||||
if not CheckAddress(endpoint_allowed_ip):
|
||||
return False, f"Endpoint Allowed IPs format is incorrect"
|
||||
|
||||
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
|
||||
return False, f"DNS format is incorrect"
|
||||
|
||||
if type(mtu) is str or mtu is None:
|
||||
|
||||
peers = []
|
||||
for peer in self.configuration.getPeersList():
|
||||
# Make sure to exclude your own data when updating since its not really relevant
|
||||
if peer.id == self.id:
|
||||
continue
|
||||
peers.append(peer)
|
||||
|
||||
used_allowed_ips = []
|
||||
for peer in peers:
|
||||
ips = peer.allowed_ip.split(',')
|
||||
ips = [ip.strip() for ip in ips]
|
||||
used_allowed_ips.append(ips)
|
||||
|
||||
if allowed_ip in used_allowed_ips:
|
||||
return False, "Allowed IP already taken by another peer"
|
||||
|
||||
if not ValidateDNSAddress(dns_addresses):
|
||||
return False, f"DNS IP-Address or FQDN is incorrect"
|
||||
|
||||
if isinstance(mtu, str):
|
||||
mtu = 0
|
||||
|
||||
if mtu < 0 or mtu > 1460:
|
||||
return False, "MTU format is not correct"
|
||||
|
||||
if type(keepalive) is str or keepalive is None:
|
||||
|
||||
if isinstance(keepalive, str):
|
||||
keepalive = 0
|
||||
|
||||
|
||||
if mtu not in range(0, 1461):
|
||||
return False, "MTU format is not correct"
|
||||
|
||||
if keepalive < 0:
|
||||
return False, "Persistent Keepalive format is not correct"
|
||||
|
||||
if len(private_key) > 0:
|
||||
pubKey = GenerateWireguardPublicKey(private_key)
|
||||
if not pubKey[0] or pubKey[1] != self.id:
|
||||
return False, "Private key does not match with the public key"
|
||||
try:
|
||||
rd = random.Random()
|
||||
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
|
||||
pskExist = len(preshared_key) > 0
|
||||
|
||||
if pskExist:
|
||||
try:
|
||||
rand = random.Random()
|
||||
uid = str(uuid.UUID(int=rand.getrandbits(128), version=4))
|
||||
psk_exist = len(preshared_key) > 0
|
||||
|
||||
if psk_exist:
|
||||
with open(uid, "w+") as f:
|
||||
f.write(preshared_key)
|
||||
newAllowedIPs = allowed_ip.replace(" ", "")
|
||||
updateAllowedIp = subprocess.check_output(
|
||||
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs} {f'preshared-key {uid}' if pskExist else 'preshared-key /dev/null'}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
|
||||
if pskExist: os.remove(uid)
|
||||
newAllowedIPs = allowed_ip.replace(" ", "")
|
||||
if not CheckAddress(newAllowedIPs):
|
||||
return False, "Allowed IPs entry format is incorrect"
|
||||
|
||||
command = [self.configuration.Protocol, "set", self.configuration.Name, "peer", self.id, "allowed-ips", newAllowedIPs, "preshared-key", uid if psk_exist else "/dev/null"]
|
||||
updateAllowedIp = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if psk_exist: os.remove(uid)
|
||||
|
||||
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
||||
return False, "Update peer failed when updating Allowed IPs"
|
||||
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
current_app.logger.error("Update peer failed when updating Allowed IPs")
|
||||
return False, "Internal server error"
|
||||
|
||||
command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name]
|
||||
saveConfig = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
||||
return False, "Update peer failed when saving the configuration"
|
||||
current_app.logger.error("Update peer failed when saving the configuration")
|
||||
return False, "Internal server error"
|
||||
|
||||
with self.configuration.engine.begin() as conn:
|
||||
conn.execute(
|
||||
self.configuration.peersTable.update().values({
|
||||
@@ -114,6 +143,7 @@ class Peer:
|
||||
"endpoint_allowed_ip": endpoint_allowed_ip,
|
||||
"mtu": mtu,
|
||||
"keepalive": keepalive,
|
||||
"notes": notes,
|
||||
"preshared_key": preshared_key
|
||||
}).where(
|
||||
self.configuration.peersTable.c.id == self.id
|
||||
@@ -121,7 +151,8 @@ class Peer:
|
||||
)
|
||||
return True, None
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, exc.output.decode("UTF-8").strip()
|
||||
current_app.logger.error(f"Subprocess call failed:\n{exc.output.decode('UTF-8')}")
|
||||
return False, "Internal server error"
|
||||
|
||||
def downloadPeer(self) -> dict[str, str]:
|
||||
final = {
|
||||
@@ -132,17 +163,19 @@ class Peer:
|
||||
if len(filename) == 0:
|
||||
filename = "UntitledPeer"
|
||||
filename = "".join(filename.split(' '))
|
||||
filename = f"{filename}"
|
||||
illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3",
|
||||
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
|
||||
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
|
||||
for i in illegal_filename:
|
||||
filename = filename.replace(i, "")
|
||||
|
||||
# use previous filtering code if code below is insufficient or faulty
|
||||
filename = re.sub(r'[.,/?<>\\:*|"]', '', filename).rstrip(". ") # remove special characters
|
||||
|
||||
reserved_pattern = r"^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\..*)?$" # match com1-9, lpt1-9, con, nul, prn, aux, nul
|
||||
|
||||
if re.match(reserved_pattern, filename, re.IGNORECASE):
|
||||
filename = f"file_{filename}" # prepend "file_" if it matches
|
||||
|
||||
for i in filename:
|
||||
if re.match("^[a-zA-Z0-9_=+.-]$", i):
|
||||
final["fileName"] += i
|
||||
|
||||
|
||||
interfaceSection = {
|
||||
"PrivateKey": self.private_key,
|
||||
"Address": self.allowed_ip,
|
||||
@@ -155,7 +188,7 @@ class Peer:
|
||||
if self.configuration.configurationInfo.OverridePeerSettings.DNS else self.DNS
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
if self.configuration.Protocol == "awg":
|
||||
interfaceSection.update({
|
||||
"Jc": self.configuration.Jc,
|
||||
@@ -163,12 +196,19 @@ class Peer:
|
||||
"Jmax": self.configuration.Jmax,
|
||||
"S1": self.configuration.S1,
|
||||
"S2": self.configuration.S2,
|
||||
"S3": self.configuration.S3,
|
||||
"S4": self.configuration.S4,
|
||||
"H1": self.configuration.H1,
|
||||
"H2": self.configuration.H2,
|
||||
"H3": self.configuration.H3,
|
||||
"H4": self.configuration.H4
|
||||
"H4": self.configuration.H4,
|
||||
"I1": self.configuration.I1,
|
||||
"I2": self.configuration.I2,
|
||||
"I3": self.configuration.I3,
|
||||
"I4": self.configuration.I4,
|
||||
"I5": self.configuration.I5
|
||||
})
|
||||
|
||||
|
||||
peerSection = {
|
||||
"PublicKey": self.configuration.PublicKey,
|
||||
"AllowedIPs": (
|
||||
@@ -192,7 +232,7 @@ class Peer:
|
||||
for (key, val) in combine[s]:
|
||||
if val is not None and ((type(val) is str and len(val) > 0) or (type(val) is int and val > 0)):
|
||||
final["file"] += f"{key} = {val}\n"
|
||||
|
||||
|
||||
final["file"] = jinja2.Template(final["file"]).render(configuration=self.configuration)
|
||||
|
||||
|
||||
@@ -351,4 +391,4 @@ class Peer:
|
||||
|
||||
hours, remainder = divmod(delta.total_seconds(), 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
|
||||
return f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}"
|
||||
|
||||
@@ -8,7 +8,7 @@ import sqlalchemy as db
|
||||
from flask import current_app
|
||||
from sqlalchemy import RowMapping
|
||||
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .Log import Log
|
||||
|
||||
class PeerJobLogger:
|
||||
|
||||
@@ -3,7 +3,7 @@ Peer Jobs
|
||||
"""
|
||||
import sqlalchemy
|
||||
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .PeerJob import PeerJob
|
||||
from .PeerJobLogger import PeerJobLogger
|
||||
import sqlalchemy as db
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .PeerShareLink import PeerShareLink
|
||||
import sqlalchemy as db
|
||||
from datetime import datetime
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import re, ipaddress
|
||||
import subprocess
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
def RegexMatch(regex, text) -> bool:
|
||||
"""
|
||||
@@ -18,10 +18,18 @@ def GetRemoteEndpoint() -> str:
|
||||
@return:
|
||||
"""
|
||||
import socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||
s.connect(("1.1.1.1", 80)) # Connecting to a public IP
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||
s.connect(("1.1.1.1", 80)) # Connecting to a public IP
|
||||
wgd_remote_endpoint = s.getsockname()[0]
|
||||
return str(wgd_remote_endpoint)
|
||||
except (socket.error, OSError):
|
||||
pass
|
||||
try:
|
||||
return socket.gethostbyname(socket.gethostname())
|
||||
except (socket.error, OSError):
|
||||
pass
|
||||
return "127.0.0.1"
|
||||
|
||||
|
||||
def StringToBoolean(value: str):
|
||||
@@ -33,31 +41,35 @@ def StringToBoolean(value: str):
|
||||
return (value.strip().replace(" ", "").lower() in
|
||||
("yes", "true", "t", "1", 1))
|
||||
|
||||
def ValidateIPAddressesWithRange(ips: str) -> bool:
|
||||
s = ips.replace(" ", "").split(",")
|
||||
for ip in s:
|
||||
def CheckAddress(ips_str: str) -> bool:
|
||||
if len(ips_str) == 0:
|
||||
return False
|
||||
|
||||
for ip in ips_str.split(','):
|
||||
stripped_ip = ip.strip()
|
||||
try:
|
||||
ipaddress.ip_network(ip)
|
||||
except ValueError as e:
|
||||
# Verify the IP-address, with the strict flag as false also allows for /32 and /128
|
||||
ipaddress.ip_network(stripped_ip, strict=False)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def ValidateIPAddresses(ips) -> bool:
|
||||
s = ips.replace(" ", "").split(",")
|
||||
for ip in s:
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
except ValueError as e:
|
||||
return False
|
||||
return True
|
||||
def CheckPeerKey(peer_key: str) -> bool:
|
||||
return re.match(r"^[A-Za-z0-9+/]{43}=$", peer_key)
|
||||
|
||||
def ValidateDNSAddress(addresses_str: str) -> tuple[bool, str | None]:
|
||||
if len(addresses_str) == 0:
|
||||
return False, "Got an empty list/string to check for valid DNS-addresses"
|
||||
|
||||
addresses = addresses_str.split(',')
|
||||
for address in addresses:
|
||||
stripped_address = address.strip()
|
||||
|
||||
if not CheckAddress(stripped_address) and not RegexMatch(r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", stripped_address):
|
||||
return False, f"{stripped_address} does not appear to be a valid IP-address or FQDN"
|
||||
|
||||
return True, None
|
||||
|
||||
def ValidateDNSAddress(addresses) -> tuple[bool, str]:
|
||||
s = addresses.replace(" ", "").split(",")
|
||||
for address in s:
|
||||
if not ValidateIPAddresses(address) and not RegexMatch(
|
||||
r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z]{0,61}[a-z]", address):
|
||||
return False, f"{address} does not appear to be an valid DNS address"
|
||||
return True, ""
|
||||
|
||||
def ValidateEndpointAllowedIPs(IPs) -> tuple[bool, str] | tuple[bool, None]:
|
||||
ips = IPs.replace(" ", "").split(",")
|
||||
@@ -101,4 +113,4 @@ def ValidatePasswordStrength(password: str) -> tuple[bool, str] | tuple[bool, No
|
||||
if not re.search(r'[$&+,:;=?@#|\'<>.\-^*()%!~_-]', password):
|
||||
return False, "Password must contain at least 1 special character from $&+,:;=?@#|'<>.-^*()%!~_-"
|
||||
|
||||
return True, None
|
||||
return True, None
|
||||
|
||||
@@ -10,13 +10,18 @@ from datetime import datetime, timedelta
|
||||
from itertools import islice
|
||||
from flask import current_app
|
||||
|
||||
from .ConnectionString import ConnectionString
|
||||
from .DatabaseConnection import ConnectionString
|
||||
from .DashboardConfig import DashboardConfig
|
||||
from .Peer import Peer
|
||||
from .PeerJobs import PeerJobs
|
||||
from .PeerShareLinks import PeerShareLinks
|
||||
from .Utilities import StringToBoolean, GenerateWireguardPublicKey, RegexMatch, ValidateDNSAddress, \
|
||||
ValidateEndpointAllowedIPs
|
||||
from .Utilities import StringToBoolean, \
|
||||
GenerateWireguardPublicKey, \
|
||||
RegexMatch, \
|
||||
ValidateDNSAddress, \
|
||||
ValidateEndpointAllowedIPs, \
|
||||
CheckAddress, \
|
||||
CheckPeerKey
|
||||
from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass
|
||||
from .DashboardWebHooks import DashboardWebHooks
|
||||
|
||||
@@ -61,13 +66,14 @@ class WireguardConfiguration:
|
||||
self.Protocol = "wg" if wg else "awg"
|
||||
self.AllPeerJobs = AllPeerJobs
|
||||
self.DashboardConfig = DashboardConfig
|
||||
self.DashboardConfig.EnsureDatabaseIntegrity({self.Name: self})
|
||||
self.AllPeerShareLinks = AllPeerShareLinks
|
||||
self.DashboardWebHooks = DashboardWebHooks
|
||||
self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf')
|
||||
self.engine: sqlalchemy.Engine = sqlalchemy.create_engine(ConnectionString("wgdashboard"))
|
||||
self.metadata: sqlalchemy.MetaData = sqlalchemy.MetaData()
|
||||
self.dbType = self.DashboardConfig.GetConfig("Database", "type")[1]
|
||||
|
||||
|
||||
if name is not None:
|
||||
if data is not None and "Backup" in data.keys():
|
||||
db = self.__importDatabase(
|
||||
@@ -109,10 +115,17 @@ class WireguardConfiguration:
|
||||
self.__parser["Interface"]["Jmax"] = self.Jmax
|
||||
self.__parser["Interface"]["S1"] = self.S1
|
||||
self.__parser["Interface"]["S2"] = self.S2
|
||||
self.__parser["Interface"]["S3"] = self.S3
|
||||
self.__parser["Interface"]["S4"] = self.S4
|
||||
self.__parser["Interface"]["H1"] = self.H1
|
||||
self.__parser["Interface"]["H2"] = self.H2
|
||||
self.__parser["Interface"]["H3"] = self.H3
|
||||
self.__parser["Interface"]["H4"] = self.H4
|
||||
self.__parser["Interface"]["I1"] = self.I1
|
||||
self.__parser["Interface"]["I2"] = self.I2
|
||||
self.__parser["Interface"]["I3"] = self.I3
|
||||
self.__parser["Interface"]["I4"] = self.I4
|
||||
self.__parser["Interface"]["I5"] = self.I5
|
||||
|
||||
if "Backup" not in data.keys():
|
||||
self.createDatabase()
|
||||
@@ -127,8 +140,11 @@ class WireguardConfiguration:
|
||||
current_app.logger.info(f"Initialized Configuration: {name}")
|
||||
self.__dumpDatabase()
|
||||
if self.getAutostartStatus() and not self.getStatus() and startup:
|
||||
self.toggleConfiguration()
|
||||
current_app.logger.info(f"Autostart Configuration: {name}")
|
||||
status, ext = self.toggleConfiguration()
|
||||
if not status:
|
||||
current_app.logger.error(f"Failed to autostart configuration: {name}. Reason: {ext}")
|
||||
else:
|
||||
current_app.logger.info(f"Autostart Configuration: {name}")
|
||||
|
||||
self.configurationInfo: WireguardConfigurationInfo | None = None
|
||||
configurationInfoJson = self.readConfigurationInfo()
|
||||
@@ -140,7 +156,6 @@ class WireguardConfiguration:
|
||||
|
||||
if self.Status:
|
||||
self.addAutostart()
|
||||
|
||||
|
||||
def __getProtocolPath(self) -> str:
|
||||
_, path = self.DashboardConfig.GetConfig("Server", "wg_conf_path") if self.Protocol == "wg" \
|
||||
@@ -232,54 +247,50 @@ class WireguardConfiguration:
|
||||
return True
|
||||
|
||||
def createDatabase(self, dbName = None):
|
||||
def generate_column_obj():
|
||||
return [
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('notes', sqlalchemy.Text),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255))
|
||||
]
|
||||
|
||||
if dbName is None:
|
||||
dbName = self.Name
|
||||
|
||||
self.peersTable = sqlalchemy.Table(
|
||||
dbName, self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
|
||||
extend_existing=True
|
||||
f'{dbName}', self.metadata, *generate_column_obj(), extend_existing=True
|
||||
)
|
||||
|
||||
self.peersRestrictedTable = sqlalchemy.Table(
|
||||
f'{dbName}_restrict_access', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
|
||||
extend_existing=True
|
||||
f'{dbName}_restrict_access', self.metadata, *generate_column_obj(), extend_existing=True
|
||||
)
|
||||
|
||||
self.peersDeletedTable = sqlalchemy.Table(
|
||||
f'{dbName}_deleted', self.metadata, *generate_column_obj(), extend_existing=True
|
||||
)
|
||||
|
||||
if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite':
|
||||
time_col_type = sqlalchemy.DATETIME
|
||||
else:
|
||||
time_col_type = sqlalchemy.TIMESTAMP
|
||||
|
||||
self.peersTransferTable = sqlalchemy.Table(
|
||||
f'{dbName}_transfer', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||
@@ -289,8 +300,7 @@ class WireguardConfiguration:
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('time', (sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP),
|
||||
server_default=sqlalchemy.func.now()),
|
||||
sqlalchemy.Column('time', time_col_type, server_default=sqlalchemy.func.now()),
|
||||
extend_existing=True
|
||||
)
|
||||
|
||||
@@ -298,34 +308,9 @@ class WireguardConfiguration:
|
||||
f'{dbName}_history_endpoint', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False),
|
||||
sqlalchemy.Column('time',
|
||||
(sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)),
|
||||
extend_existing=True
|
||||
sqlalchemy.Column('time', time_col_type)
|
||||
)
|
||||
|
||||
self.peersDeletedTable = sqlalchemy.Table(
|
||||
f'{dbName}_deleted', self.metadata,
|
||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False, primary_key=True),
|
||||
sqlalchemy.Column('private_key', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('DNS', sqlalchemy.Text),
|
||||
sqlalchemy.Column('endpoint_allowed_ip', sqlalchemy.Text),
|
||||
sqlalchemy.Column('name', sqlalchemy.Text),
|
||||
sqlalchemy.Column('total_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('total_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('status', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('latest_handshake', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('allowed_ip', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('cumu_receive', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_sent', sqlalchemy.Float),
|
||||
sqlalchemy.Column('cumu_data', sqlalchemy.Float),
|
||||
sqlalchemy.Column('mtu', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('keepalive', sqlalchemy.Integer),
|
||||
sqlalchemy.Column('remote_endpoint', sqlalchemy.String(255)),
|
||||
sqlalchemy.Column('preshared_key', sqlalchemy.String(255)),
|
||||
extend_existing=True
|
||||
)
|
||||
self.infoTable = sqlalchemy.Table(
|
||||
'ConfigurationsInfo', self.metadata,
|
||||
sqlalchemy.Column('ID', sqlalchemy.String(255), primary_key=True),
|
||||
@@ -404,6 +389,7 @@ class WireguardConfiguration:
|
||||
try:
|
||||
if "[Peer]" not in content:
|
||||
current_app.logger.info(f"{self.Name} config has no [Peer] section")
|
||||
self.Peers = []
|
||||
return
|
||||
|
||||
peerStarts = content.index("[Peer]")
|
||||
@@ -439,8 +425,7 @@ class WireguardConfiguration:
|
||||
"id": i['PublicKey'],
|
||||
"private_key": "",
|
||||
"DNS": self.DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1],
|
||||
"endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[
|
||||
1],
|
||||
"endpoint_allowed_ip": self.DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1],
|
||||
"name": i.get("name"),
|
||||
"total_receive": 0,
|
||||
"total_sent": 0,
|
||||
@@ -454,6 +439,7 @@ class WireguardConfiguration:
|
||||
"cumu_data": 0,
|
||||
"mtu": self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1] if len(self.DashboardConfig.GetConfig("Peers", "peer_mtu")[1]) > 0 else None,
|
||||
"keepalive": self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1] if len(self.DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]) > 0 else None,
|
||||
"notes": "",
|
||||
"remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
|
||||
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
|
||||
}
|
||||
@@ -526,6 +512,15 @@ class WireguardConfiguration:
|
||||
"peers": []
|
||||
}
|
||||
try:
|
||||
cleanedAllowedIPs = {}
|
||||
for p in peers:
|
||||
newAllowedIPs = p['allowed_ip'].replace(" ", "")
|
||||
if not CheckAddress(newAllowedIPs):
|
||||
return False, [], "Allowed IPs entry format is incorrect"
|
||||
if not CheckPeerKey(p["id"]):
|
||||
return False, [], "Peer key format is incorrect"
|
||||
cleanedAllowedIPs[p["id"]] = newAllowedIPs
|
||||
|
||||
with self.engine.begin() as conn:
|
||||
for i in peers:
|
||||
newPeer = {
|
||||
@@ -546,6 +541,7 @@ class WireguardConfiguration:
|
||||
"cumu_data": 0,
|
||||
"mtu": i['mtu'],
|
||||
"keepalive": i['keepalive'],
|
||||
"notes": i.get("notes", ""),
|
||||
"remote_endpoint": self.DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
|
||||
"preshared_key": i["preshared_key"]
|
||||
}
|
||||
@@ -560,12 +556,15 @@ class WireguardConfiguration:
|
||||
with open(uid, "w+") as f:
|
||||
f.write(p['preshared_key'])
|
||||
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [self.Protocol, "set", self.Name, "peer", p['id'], "allowed-ips", cleanedAllowedIPs[p["id"]], "preshared-key", uid if presharedKeyExist else "/dev/null"]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if presharedKeyExist:
|
||||
os.remove(uid)
|
||||
subprocess.check_output(
|
||||
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
|
||||
command = [f"{self.Protocol}-quick", "save", self.Name]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
self.getPeers()
|
||||
for p in peers:
|
||||
p = self.searchPeer(p['id'])
|
||||
@@ -577,7 +576,7 @@ class WireguardConfiguration:
|
||||
})
|
||||
except Exception as e:
|
||||
current_app.logger.error("Add peers error", e)
|
||||
return False, [], str(e)
|
||||
return False, [], "Internal server error"
|
||||
return True, result['peers'], ""
|
||||
|
||||
def searchPeer(self, publicKey):
|
||||
@@ -615,8 +614,16 @@ class WireguardConfiguration:
|
||||
with open(uid, "w+") as f:
|
||||
f.write(restrictedPeer['preshared_key'])
|
||||
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {restrictedPeer['id']} allowed-ips {restrictedPeer['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
newAllowedIPs = restrictedPeer['allowed_ip'].replace(" ", "")
|
||||
if not CheckAddress(newAllowedIPs):
|
||||
return False, "Allowed IPs entry format is incorrect"
|
||||
|
||||
if not CheckPeerKey(restrictedPeer["id"]):
|
||||
return False, "Peer key format is incorrect"
|
||||
|
||||
command = [self.Protocol, "set", self.Name, "peer", restrictedPeer["id"], "allowed-ips", newAllowedIPs, "preshared-key", uid if presharedKeyExist else "/dev/null"]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
if presharedKeyExist: os.remove(uid)
|
||||
else:
|
||||
return False, "Failed to allow access of peer " + i
|
||||
@@ -636,8 +643,9 @@ class WireguardConfiguration:
|
||||
found, pf = self.searchPeer(p)
|
||||
if found:
|
||||
try:
|
||||
subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [self.Protocol, "set", self.Name, "peer", pf.id, "remove"]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
conn.execute(
|
||||
self.peersRestrictedTable.insert().from_select(
|
||||
[c.name for c in self.peersTable.columns],
|
||||
@@ -665,9 +673,8 @@ class WireguardConfiguration:
|
||||
|
||||
if not self.__wgSave():
|
||||
return False, "Failed to save configuration through WireGuard"
|
||||
|
||||
self.getRestrictedPeers()
|
||||
self.getPeers()
|
||||
|
||||
if numOfRestrictedPeers == len(listOfPublicKeys):
|
||||
return True, f"Restricted {numOfRestrictedPeers} peer(s)"
|
||||
return False, f"Restricted {numOfRestrictedPeers} peer(s) successfully. Failed to restrict {numOfFailedToRestrictPeers} peer(s)"
|
||||
@@ -719,17 +726,20 @@ class WireguardConfiguration:
|
||||
|
||||
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:
|
||||
try:
|
||||
subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
command = [f"{self.Protocol}-quick", "save", self.Name]
|
||||
subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
return True, None
|
||||
except subprocess.CalledProcessError as e:
|
||||
return False, str(e)
|
||||
current_app.logger.error(f"Failed to process command:\n{str(e)}")
|
||||
return False, "Internal server error"
|
||||
|
||||
def getPeersLatestHandshake(self):
|
||||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
try:
|
||||
latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [self.Protocol, "show", self.Name, "latest-handshakes"]
|
||||
latestHandshake = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError:
|
||||
return "stopped"
|
||||
latestHandshake = latestHandshake.decode("UTF-8").split()
|
||||
@@ -768,8 +778,9 @@ class WireguardConfiguration:
|
||||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
# try:
|
||||
data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [self.Protocol, "show", self.Name, "transfer"]
|
||||
data_usage = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
data_usage = data_usage.decode("UTF-8").split("\n")
|
||||
|
||||
data_usage = [p.split("\t") for p in data_usage]
|
||||
@@ -783,15 +794,13 @@ class WireguardConfiguration:
|
||||
)
|
||||
).mappings().fetchone()
|
||||
if cur_i is not None:
|
||||
# print(cur_i is None)
|
||||
total_sent = cur_i['total_sent']
|
||||
# print(cur_i is None)
|
||||
total_receive = cur_i['total_receive']
|
||||
cur_total_sent = float(data_usage[i][2]) / (1024 ** 3)
|
||||
cur_total_receive = float(data_usage[i][1]) / (1024 ** 3)
|
||||
cumulative_receive = cur_i['cumu_receive'] + total_receive
|
||||
cumulative_sent = cur_i['cumu_sent'] + total_sent
|
||||
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
|
||||
if (total_sent * 0.999 ) <= cur_total_sent and (total_receive * 0.999) <= cur_total_receive: # An accuracy of 1K ppm is sufficient
|
||||
total_sent = cur_total_sent
|
||||
total_receive = cur_total_receive
|
||||
else:
|
||||
@@ -826,10 +835,11 @@ class WireguardConfiguration:
|
||||
if not self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
try:
|
||||
data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [self.Protocol, "show", self.Name, "endpoints"]
|
||||
data_usage = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError:
|
||||
return "stopped"
|
||||
|
||||
data_usage = data_usage.decode("UTF-8").split()
|
||||
count = 0
|
||||
with self.engine.begin() as conn:
|
||||
@@ -847,14 +857,17 @@ class WireguardConfiguration:
|
||||
self.getStatus()
|
||||
if self.Status:
|
||||
try:
|
||||
check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
command = [f"{self.Protocol}-quick", "down", self.Name]
|
||||
check = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
self.removeAutostart()
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, str(exc.output.strip().decode("utf-8"))
|
||||
else:
|
||||
try:
|
||||
check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||
command = [f"{self.Protocol}-quick", "up", self.Name]
|
||||
check = subprocess.check_output(command, stderr=subprocess.STDOUT)
|
||||
|
||||
self.addAutostart()
|
||||
except subprocess.CalledProcessError as exc:
|
||||
return False, str(exc.output.strip().decode("utf-8"))
|
||||
@@ -921,8 +934,8 @@ class WireguardConfiguration:
|
||||
files.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
for f, ct in files:
|
||||
if RegexMatch(f"^({self.Name})_(.*)\\.(conf)$", f):
|
||||
s = re.search(f"^({self.Name})_(.*)\\.(conf)$", f)
|
||||
if RegexMatch(rf"^({self.Name})_(\d+)\\.(conf)$", f):
|
||||
s = re.search(rf"^({self.Name})_(\d+)\\.(conf)$", f)
|
||||
date = s.group(2)
|
||||
d = {
|
||||
"filename": f,
|
||||
@@ -995,7 +1008,7 @@ class WireguardConfiguration:
|
||||
original = [l.rstrip("\n") for l in f.readlines()]
|
||||
allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort", "Table"]
|
||||
if self.Protocol == 'awg':
|
||||
allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4"]
|
||||
allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "S3", "S4", "H1", "H2", "H3", "H4", "I1", "I2", "I3", "I4", "I5"]
|
||||
start = original.index("[Interface]")
|
||||
try:
|
||||
end = original.index("[Peer]")
|
||||
@@ -1033,31 +1046,33 @@ class WireguardConfiguration:
|
||||
return True
|
||||
|
||||
def renameConfiguration(self, newConfigurationName) -> tuple[bool, str]:
|
||||
newConfigurationName = os.path.basename(newConfigurationName)
|
||||
|
||||
if len(newConfigurationName) > 15 or not re.match(r'^[a-zA-Z0-9_=\+\.\-]{1,15}$', newConfigurationName):
|
||||
return False, "Configuration name is either too long or contains an illegal character"
|
||||
|
||||
newConfigurationName = newConfigurationName.replace("`", "") # double check
|
||||
|
||||
try:
|
||||
if self.getStatus():
|
||||
self.toggleConfiguration()
|
||||
self.createDatabase(newConfigurationName)
|
||||
with self.engine.begin() as conn:
|
||||
conn.execute(
|
||||
sqlalchemy.text(
|
||||
f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"'
|
||||
def doRenameStatement(suffix):
|
||||
newConfig = f"{newConfigurationName}{suffix}"
|
||||
oldConfig = f"{self.Name}{suffix}"
|
||||
|
||||
conn.execute(
|
||||
sqlalchemy.text(
|
||||
f'INSERT INTO `{newConfig}` SELECT * FROM `{oldConfig}`'
|
||||
)
|
||||
)
|
||||
)
|
||||
conn.execute(
|
||||
sqlalchemy.text(
|
||||
f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"'
|
||||
)
|
||||
)
|
||||
conn.execute(
|
||||
sqlalchemy.text(
|
||||
f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"'
|
||||
)
|
||||
)
|
||||
conn.execute(
|
||||
sqlalchemy.text(
|
||||
f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"'
|
||||
)
|
||||
)
|
||||
|
||||
doRenameStatement("")
|
||||
doRenameStatement("_restrict_access")
|
||||
doRenameStatement("_deleted")
|
||||
doRenameStatement("_transfer")
|
||||
|
||||
self.AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName)
|
||||
shutil.copy(
|
||||
self.configPath,
|
||||
@@ -1065,8 +1080,8 @@ class WireguardConfiguration:
|
||||
)
|
||||
self.deleteConfiguration()
|
||||
except Exception as e:
|
||||
traceback.print_stack()
|
||||
return False, str(e)
|
||||
current_app.logger.error(f"Failed to rename configuration.\nNew Configuration Name: {newConfigurationName}\nError: {str(e)}")
|
||||
return False, "Internal server error"
|
||||
return True, None
|
||||
|
||||
def getNumberOfAvailableIP(self):
|
||||
@@ -1226,7 +1241,6 @@ class WireguardConfiguration:
|
||||
def __validateOverridePeerSettings(self, key: str, value: str | int) -> tuple[bool, None] | tuple[bool, str]:
|
||||
status = True
|
||||
msg = None
|
||||
print(value)
|
||||
if key == "DNS" and value:
|
||||
status, msg = ValidateDNSAddress(value)
|
||||
elif key == "EndpointAllowedIPs" and value:
|
||||
@@ -1291,4 +1305,4 @@ class WireguardConfiguration:
|
||||
conn.execute(sqlalchemy.text('VACUUM;'))
|
||||
except Exception as e:
|
||||
return False
|
||||
return True
|
||||
return True
|
||||
|
||||
@@ -10,7 +10,7 @@ requests==2.32.5
|
||||
tcconfig==0.30.1
|
||||
sqlalchemy==2.0.46
|
||||
sqlalchemy_utils==0.42.1
|
||||
psycopg[binary]==3.3.2
|
||||
psycopg[binary]==3.3.3
|
||||
PyMySQL==1.1.2
|
||||
tzlocal==5.3.1
|
||||
python-jose==3.5.0
|
||||
|
||||
@@ -6,14 +6,21 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="application-name" content="WGDashboard">
|
||||
<meta name="apple-mobile-web-app-title" content="WGDashboard">
|
||||
<link rel="manifest" href="/json/manifest.json">
|
||||
<link rel="icon" href="/img/Logo-2-512x512.png">
|
||||
<link rel="manifest" href="./json/manifest.json">
|
||||
<link rel="icon" href="./img/Logo-2-512x512.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WGDashboard</title>
|
||||
<base href="./">
|
||||
<script>
|
||||
const isViteDevMode = document.querySelector('script[src*="@vite/client"]') !== null;
|
||||
const base = document.querySelector('base');
|
||||
if (base && isViteDevMode) {
|
||||
base.href = '/';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="module" src="./src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
993
src/static/app/package-lock.json
generated
993
src/static/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.3.2",
|
||||
"version": "4.3.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"module": "es2022",
|
||||
@@ -15,12 +15,12 @@
|
||||
"@volar/language-server": "2.4.28",
|
||||
"@vue/language-server": "3.2.4",
|
||||
"@vuepic/vue-datepicker": "^12.1.0",
|
||||
"@vueuse/core": "^14.2.0",
|
||||
"@vueuse/core": "^14.2.1",
|
||||
"@vueuse/shared": "^14.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"cidr-tools": "^11.0.8",
|
||||
"cidr-tools": "^11.3.2",
|
||||
"css-color-converter": "^2.0.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"electron-builder": "^26.7.0",
|
||||
@@ -35,9 +35,9 @@
|
||||
"qrcodejs": "^1.0.0",
|
||||
"simple-code-editor": "^2.0.9",
|
||||
"uuid": "^13.0.0",
|
||||
"vue": "^3.5.28",
|
||||
"vue": "^3.5.31",
|
||||
"vue-chartjs": "^5.3.3",
|
||||
"vue-router": "^5.0.2"
|
||||
"vue-router": "^5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.4",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { ref, reactive } from "vue"
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import OidcSettings from "@/components/clientComponents/clientSettingComponents/oidcSettings.vue";
|
||||
import { fetchGet } from "@/utilities/fetch.js"
|
||||
import { fetchGet, fetchPost } from "@/utilities/fetch.js"
|
||||
const emits = defineEmits(['close'])
|
||||
import { DashboardConfigurationStore } from "@/stores/DashboardConfigurationStore"
|
||||
const dashboardConfigurationStore = DashboardConfigurationStore()
|
||||
@@ -12,12 +12,16 @@ const values = reactive({
|
||||
})
|
||||
|
||||
const toggling = ref(false)
|
||||
const toggleClientSideApp = async () => {
|
||||
const updateSettings = async (key: string) => {
|
||||
toggling.value = true
|
||||
await fetchGet("/api/clients/toggleStatus", {}, (res) => {
|
||||
values.enableClients = res.data
|
||||
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||
section: "Clients",
|
||||
key: key,
|
||||
value: dashboardConfigurationStore.Configuration.Clients[key]
|
||||
}, async (res) => {
|
||||
await dashboardConfigurationStore.getConfiguration()
|
||||
toggling.value = false
|
||||
})
|
||||
toggling.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,16 +41,43 @@ const toggleClientSideApp = async () => {
|
||||
</h6>
|
||||
<div class="form-check form-switch ms-auto">
|
||||
<label class="form-check-label" for="oidc_switch">
|
||||
<LocaleText :t="values.enableClients ? 'Enabled':'Disabled'"></LocaleText>
|
||||
<LocaleText :t="dashboardConfigurationStore.Configuration.Clients.enable ? 'Enabled':'Disabled'"></LocaleText>
|
||||
</label>
|
||||
<input
|
||||
:disabled="oidcStatusLoading"
|
||||
v-model="values.enableClients"
|
||||
@change="toggleClientSideApp()"
|
||||
:disabled="toggling"
|
||||
v-model="dashboardConfigurationStore.Configuration.Clients.enable"
|
||||
@change="updateSettings('enable')"
|
||||
class="form-check-input" type="checkbox" role="switch" id="oidc_switch">
|
||||
</div>
|
||||
</div>
|
||||
<OidcSettings mode="Client"></OidcSettings>
|
||||
<hr>
|
||||
<div>
|
||||
<div class="d-flex align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<LocaleText t="Sign Up as Local Client"></LocaleText>
|
||||
</h6>
|
||||
<div class="form-check form-switch ms-auto">
|
||||
<label class="form-check-label" for="sign_up_switch">
|
||||
<LocaleText :t="dashboardConfigurationStore.Configuration.Clients.sign_up ? 'Enabled':'Disabled'"></LocaleText>
|
||||
</label>
|
||||
<input
|
||||
:disabled="toggling"
|
||||
v-model="dashboardConfigurationStore.Configuration.Clients.sign_up"
|
||||
@change="updateSettings('sign_up')"
|
||||
class="form-check-input" type="checkbox" role="switch" id="sign_up_switch">
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted mb-0">
|
||||
<LocaleText t="Allow clients to sign up with Email and Password"></LocaleText>
|
||||
</small>
|
||||
</div>
|
||||
<div>
|
||||
<OidcSettings mode="Client"></OidcSettings>
|
||||
<small class="text-muted mb-0">
|
||||
<LocaleText t="Allow clients to access with OpenID"></LocaleText>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -197,14 +197,14 @@ const deleteConfigurationModal = ref(false)
|
||||
v-model="data[key]"
|
||||
:id="'configuration_' + key">
|
||||
</div>
|
||||
<div v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4']"
|
||||
<div v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5']"
|
||||
v-if="configurationInfo.Protocol === 'awg'">
|
||||
<label :for="'configuration_' + key" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText :t="key"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="number" class="form-control form-control-sm rounded-3"
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="saving"
|
||||
v-model="data[key]"
|
||||
:id="'configuration_' + key">
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<script>
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
|
||||
export default {
|
||||
name: "notesInput",
|
||||
components: {LocaleText},
|
||||
props: {
|
||||
bulk: Boolean,
|
||||
data: Object,
|
||||
saving: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{inactiveField: this.bulk}">
|
||||
<label for="peer_notes_textbox" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Notes"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="this.saving || this.bulk"
|
||||
v-model="this.data.notes"
|
||||
id="peer_notes_textbox" placeholder="">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -134,12 +134,17 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer" role="button" @click="$emit('details')">
|
||||
<div class="card-footer" role="button" @click="$emit('details')" v-if="!this.Peer.restricted">
|
||||
<small class="d-flex align-items-center">
|
||||
<LocaleText t="Details"></LocaleText>
|
||||
<i class="bi bi-chevron-right ms-auto"></i>
|
||||
</small>
|
||||
</div>
|
||||
<div class="card-footer" v-else>
|
||||
<small class="d-flex align-items-center text-muted">
|
||||
<LocaleText t="Allow access to view details"></LocaleText>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import MtuInput from "@/components/configurationComponents/newPeersComponents/mt
|
||||
import PersistentKeepAliveInput
|
||||
from "@/components/configurationComponents/newPeersComponents/persistentKeepAliveInput.vue";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import NotesInput from "./newPeersComponents/notesInput.vue";
|
||||
|
||||
const dashboardStore = DashboardConfigurationStore()
|
||||
const wireguardStore = WireguardConfigurationsStore()
|
||||
@@ -27,11 +28,11 @@ const peerData = ref({
|
||||
public_key: "",
|
||||
DNS: dashboardStore.Configuration.Peers.peer_global_dns,
|
||||
endpoint_allowed_ip: dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip,
|
||||
notes: "",
|
||||
keepalive: parseInt(dashboardStore.Configuration.Peers.peer_keep_alive),
|
||||
mtu: parseInt(dashboardStore.Configuration.Peers.peer_mtu),
|
||||
preshared_key: "",
|
||||
preshared_key_bulkAdd: false,
|
||||
advanced_security: "off",
|
||||
allowed_ips_validation: true,
|
||||
})
|
||||
const availableIp = ref([])
|
||||
@@ -105,6 +106,7 @@ watch(() => {
|
||||
<template v-if="!peerData.bulkAdd">
|
||||
<hr class="mb-0 mt-2">
|
||||
<NameInput :saving="saving" :data="peerData"></NameInput>
|
||||
<NotesInput :saving="saving" :data="peerData"></NotesInput>
|
||||
<PrivatePublicKeyInput :saving="saving" :data="peerData"></PrivatePublicKeyInput>
|
||||
<AllowedIPsInput :availableIp="availableIp" :saving="saving" :data="peerData"></AllowedIPsInput>
|
||||
</template>
|
||||
@@ -118,7 +120,7 @@ watch(() => {
|
||||
<LocaleText t="Advanced Options"></LocaleText>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="peerAddModalAccordionAdvancedOptions"
|
||||
<div id="peerAddModalAccordionAdvancedOptions"
|
||||
class="accordion-collapse collapse collapsed" data-bs-parent="#peerAddModalAccordion">
|
||||
<div class="accordion-body rounded-bottom-3">
|
||||
<div class="d-flex flex-column gap-2">
|
||||
|
||||
@@ -37,7 +37,6 @@ export default {
|
||||
mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu),
|
||||
preshared_key: "",
|
||||
preshared_key_bulkAdd: false,
|
||||
advanced_security: "off",
|
||||
},
|
||||
availableIp: undefined,
|
||||
availableIpSearchString: "",
|
||||
|
||||
@@ -32,6 +32,7 @@ Chart.register(
|
||||
import PeerSessions from "@/components/peerDetailsModalComponents/peerSessions.vue";
|
||||
import PeerTraffics from "@/components/peerDetailsModalComponents/peerTraffics.vue";
|
||||
import PeerEndpoints from "@/components/peerDetailsModalComponents/peerEndpoints.vue";
|
||||
import { GetLocale } from "@/utilities/locale"
|
||||
const props = defineProps(['selectedPeer'])
|
||||
const selectedDate = ref(undefined)
|
||||
defineEmits(['close'])
|
||||
@@ -49,13 +50,18 @@ defineEmits(['close'])
|
||||
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
|
||||
</div>
|
||||
<div class="card-body px-4">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small>
|
||||
<LocaleText t="Peer"></LocaleText>
|
||||
</small></p>
|
||||
<h2>
|
||||
{{ selectedPeer.name }}
|
||||
</h2>
|
||||
<div class="d-flex justify-content-between align-items-start mb-2 flex-column flex-md-row">
|
||||
<div>
|
||||
<p class="mb-0 text-muted"><small><LocaleText t="Peer" /></small></p>
|
||||
<h2 :class="{'text-muted': selectedPeer.name.length === 0 }">
|
||||
{{ selectedPeer.name.length > 0 ? selectedPeer.name : GetLocale("Untitled Peer") }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedPeer.notes" class="text-start text-md-end">
|
||||
<p class="mb-0 text-muted"><small><LocaleText t="Notes" /></small></p>
|
||||
<p class="mb-0" style="white-space: pre-wrap">{{ selectedPeer.notes }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 gy-2 gx-2 mb-2">
|
||||
<div class="col-12 col-lg-3">
|
||||
|
||||
@@ -99,6 +99,17 @@ export default {
|
||||
v-model="this.data.name"
|
||||
id="peer_name_textbox" placeholder="">
|
||||
</div>
|
||||
<div>
|
||||
<label for="peer_notes_textbox" class="form-label">
|
||||
<small class="text-muted">
|
||||
<LocaleText t="Notes"></LocaleText>
|
||||
</small>
|
||||
</label>
|
||||
<input type="text" class="form-control form-control-sm rounded-3"
|
||||
:disabled="this.saving"
|
||||
v-model="this.data.notes"
|
||||
id="peer_notes_textbox" placeholder="">
|
||||
</div>
|
||||
<div>
|
||||
<div class="d-flex position-relative">
|
||||
<label for="peer_private_key_textbox" class="form-label">
|
||||
|
||||
@@ -61,7 +61,7 @@ const sendTestEmail = async () => {
|
||||
<div class="card-header">
|
||||
<h6 class="my-2 d-flex">
|
||||
<i class="bi bi-envelope-fill me-2"></i>
|
||||
<LocaleText t="Email Account"></LocaleText>
|
||||
<LocaleText t="Email Server Settings"></LocaleText>
|
||||
<span class="text-success ms-auto" v-if="emailIsReady">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
<LocaleText t="Ready"></LocaleText>
|
||||
@@ -115,6 +115,9 @@ const sendTestEmail = async () => {
|
||||
<select class="form-select rounded-3"
|
||||
v-model="store.Configuration.Email.encryption"
|
||||
id="encryption">
|
||||
<option value="IMPLICITTLS">
|
||||
IMPLICIT TLS
|
||||
</option>
|
||||
<option value="STARTTLS">
|
||||
STARTTLS
|
||||
</option>
|
||||
@@ -208,4 +211,4 @@ const sendTestEmail = async () => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -4,19 +4,69 @@ import LocaleText from "@/components/text/localeText.vue";
|
||||
import ConfigurationTracking
|
||||
from "@/components/settingsComponent/dashboardWireguardConfigurationTrackingComponents/configurationTracking.vue";
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
import {onMounted, ref, watch} from "vue";
|
||||
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
|
||||
const store = WireguardConfigurationsStore()
|
||||
const dashboardStore = DashboardConfigurationStore()
|
||||
const peerTrackingStatus = ref(dashboardStore.Configuration.WireGuardConfiguration.peer_tracking)
|
||||
const loaded = ref(false)
|
||||
const trackingData = ref({})
|
||||
onMounted(async () => {
|
||||
if (peerTrackingStatus.value){
|
||||
await loadData()
|
||||
}
|
||||
})
|
||||
|
||||
const loadData = async () => {
|
||||
await fetchGet("/api/getPeerTrackingTableCounts", {}, (ref) => {
|
||||
if (ref.status){
|
||||
trackingData.value = ref.data
|
||||
}
|
||||
loaded.value = true
|
||||
})
|
||||
}
|
||||
|
||||
watch(peerTrackingStatus, async (newVal) => {
|
||||
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||
section: "WireGuardConfiguration",
|
||||
key: "peer_tracking",
|
||||
value: newVal
|
||||
}, async (res) => {
|
||||
if (res.status){
|
||||
dashboardStore.newMessage("Server", newVal ? "Peer tracking enabled" : "Peer tracking disabled", "success")
|
||||
if (newVal) await loadData()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<h6 class="my-2">
|
||||
<LocaleText t="Peer Tracking"></LocaleText>
|
||||
</h6>
|
||||
<div class="form-check form-switch ms-auto">
|
||||
<input class="form-check-input"
|
||||
v-model="peerTrackingStatus"
|
||||
type="checkbox" role="switch" id="peerTrackingStatus">
|
||||
<label class="form-check-label" for="peerTrackingStatus">
|
||||
<LocaleText t="Enabled" v-if="peerTrackingStatus"></LocaleText>
|
||||
<LocaleText t="Disabled" v-else></LocaleText>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column gap-3">
|
||||
<ConfigurationTracking :configuration="configuration" v-for="configuration in store.Configurations"/>
|
||||
<div class="card-body d-flex flex-column gap-3" v-if="peerTrackingStatus">
|
||||
<template v-if="!loaded">
|
||||
<div class="spinner-border text-body m-auto"></div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ConfigurationTracking :configuration="configuration"
|
||||
:trackingData="trackingData"
|
||||
v-for="configuration in store.Configurations"/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -4,7 +4,7 @@ import {onMounted, ref, watch} from "vue";
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
|
||||
const props = defineProps(['configuration'])
|
||||
const props = defineProps(['configuration', 'trackingData'])
|
||||
const sizes = ref({
|
||||
HistoricalTrackingTableSize: 0,
|
||||
TrafficTrackingTableSize: 0
|
||||
@@ -13,16 +13,15 @@ const sizeDataLoaded = ref(false)
|
||||
const toggling = ref(false)
|
||||
|
||||
await onMounted(async () => {
|
||||
await loadSizeData();
|
||||
sizes.value = props.trackingData[props.configuration.Name]
|
||||
})
|
||||
|
||||
const loadSizeData = async () => {
|
||||
sizeDataLoaded.value = false;
|
||||
|
||||
await fetchGet("/api/getPeerTrackingTableCounts", {
|
||||
configurationName: props.configuration.Name
|
||||
}, (res) => {
|
||||
sizes.value = res.data;
|
||||
sizeDataLoaded.value = true;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -148,8 +147,7 @@ const deleteRecord = async (key) => {
|
||||
<hr />
|
||||
<div class="d-flex align-items-start align-items-md-center flex-column flex-md-row gap-2">
|
||||
<div>
|
||||
<h6 class="placeholder animate__animated animate__flash animate__infinite animate__slower w-100 mb-0" v-if="!sizeDataLoaded"></h6>
|
||||
<h6 v-else class="mb-0">
|
||||
<h6 class="mb-0">
|
||||
{{ sizes.HistoricalTrackingTableSize }} <span class="text-muted fw-normal"><LocaleText t="Records"></LocaleText></span>
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,6 @@ const squareHeight = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="flex-grow-1 square rounded-3 border position-relative p-2"
|
||||
|
||||
@mouseenter="show = true"
|
||||
@mouseleave="show = false"
|
||||
:style="{'background-color': `rgb(13 110 253 / ${percentage*10}%)`}">
|
||||
|
||||
@@ -49,7 +49,7 @@ const data = computed(() => {
|
||||
<div class="progress" role="progressbar" style="height: 6px">
|
||||
<div class="progress-bar" :style="{width: `${data?.CPU.cpu_percent}%` }"></div>
|
||||
</div>
|
||||
<div class="d-flex mt-2 gap-1">
|
||||
<div class="d-grid mt-2 gap-1" style="grid-template-columns: repeat(10, 1fr)">
|
||||
<CpuCore
|
||||
v-for="(cpu, count) in data?.CPU.cpu_percent_per_cpu"
|
||||
:key="count"
|
||||
@@ -74,7 +74,7 @@ const data = computed(() => {
|
||||
<div class="progress" role="progressbar" style="height: 6px">
|
||||
<div class="progress-bar bg-success" :style="{width: `${data?.Disks.find(x => x.mountPoint === '/') ? data?.Disks.find(x => x.mountPoint === '/').percent : data?.Disks[0].percent}%` }"></div>
|
||||
</div>
|
||||
<div class="d-flex mt-2 gap-1">
|
||||
<div class="d-grid mt-2 gap-1" style="grid-template-columns: repeat(10, 1fr)">
|
||||
<StorageMount v-for="(disk, count) in data?.Disks"
|
||||
v-if="data"
|
||||
:key="disk.mountPoint"
|
||||
|
||||
@@ -27,8 +27,11 @@ export const getUrl = (url) => {
|
||||
if (apiKey){
|
||||
return `${apiKey.host}${url}`
|
||||
}
|
||||
return import.meta.env.MODE === 'development' ? url
|
||||
: `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
return url;
|
||||
}
|
||||
// const appPrefix = window.APP_PREFIX || '';
|
||||
return `./.${url}`;
|
||||
}
|
||||
|
||||
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
||||
|
||||
@@ -43,10 +43,17 @@ export default {
|
||||
Jmax: 998,
|
||||
S1: 17,
|
||||
S2: 110,
|
||||
S3: 1,
|
||||
S4: 2,
|
||||
H1: 0,
|
||||
H2: 0,
|
||||
H3: 0,
|
||||
H4: 0
|
||||
H4: 0,
|
||||
I1: "0",
|
||||
I2: "0",
|
||||
I3: "0",
|
||||
I4: "0",
|
||||
I5: "0"
|
||||
},
|
||||
numberOfAvailableIPs: "0",
|
||||
error: false,
|
||||
@@ -59,14 +66,16 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.wireguardGenerateKeypair();
|
||||
let hValue = []
|
||||
while ([...new Set(hValue)].length !== 4){
|
||||
hValue = [this.rand(1, (2**31) - 1), this.rand(1, (2**31) - 1), this.rand(1, (2**31) - 1), this.rand(1, (2**31) - 1)]
|
||||
}
|
||||
this.newConfiguration.H1 = hValue[0]
|
||||
this.newConfiguration.H2 = hValue[1]
|
||||
this.newConfiguration.H3 = hValue[2]
|
||||
this.newConfiguration.H4 = hValue[3]
|
||||
|
||||
// Generate 4 random numbers for H1, H2, H3, H4
|
||||
['H1', 'H2', 'H3', 'H4'].forEach(key => {
|
||||
this.newConfiguration[key] = this.rand(1, 2**31);
|
||||
});
|
||||
|
||||
// Initialize I1 to I5 as "0"
|
||||
['I1', 'I2', 'I3', 'I4', 'I5'].forEach(key => {
|
||||
this.newConfiguration[key] = "0";
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
rand(min, max){
|
||||
@@ -379,7 +388,7 @@ export default {
|
||||
|
||||
<div class="card rounded-3"
|
||||
v-if="this.newConfiguration.Protocol === 'awg'"
|
||||
v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4']">
|
||||
v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5']">
|
||||
<div class="card-header">{{ key }}</div>
|
||||
<div class="card-body">
|
||||
<input type="text"
|
||||
|
||||
@@ -176,7 +176,10 @@ export default {
|
||||
<input
|
||||
v-model="this.store.CrossServerConfiguration.Enable"
|
||||
:disabled="loading"
|
||||
class="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckChecked">
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="flexSwitchCheckChecked">
|
||||
<label class="form-check-label" for="flexSwitchCheckChecked">
|
||||
<LocaleText t="Access Remote Server"></LocaleText>
|
||||
</label>
|
||||
@@ -184,10 +187,20 @@ export default {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-muted pb-3 d-block w-100 text-center mt-3">
|
||||
WGDashboard {{ this.version }} | Developed with ❤️ by
|
||||
<a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a>
|
||||
</small>
|
||||
<div class="d-flex container-fluid align-items-center my-1 w-100">
|
||||
<small class="text-muted">
|
||||
WGDashboard <strong>{{ this.version }}</strong> | Made with ❤️ by
|
||||
<a href="https://github.com/WGDashboard"
|
||||
class="text-decoration-none text-body"
|
||||
target="_blank"><strong>WGDashboard</strong></a>
|
||||
</small>
|
||||
<a href="./client" target="_blank"
|
||||
class="text-decoration-none ms-auto text-body"
|
||||
style="white-space: nowrap">
|
||||
<small><i class="bi bi-box-arrow-up-right me-1"></i>
|
||||
<LocaleText t="Client App" /></small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="messageCentre text-body position-absolute d-flex">
|
||||
<TransitionGroup name="message" tag="div"
|
||||
class="position-relative flex-sm-grow-0 flex-grow-1 d-flex align-items-end ms-sm-auto flex-column gap-2">
|
||||
|
||||
@@ -176,7 +176,7 @@ const memoryHistoricalChartData = computed(() => {
|
||||
<div class="card rounded-3 h-100 shadow">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex flex-column gap-3">
|
||||
<div class="d-flex flex-column gap-3" style="height: 130px">
|
||||
<div class="d-flex flex-column gap-3" style="min-height: 130px">
|
||||
<div class="d-flex align-items-center">
|
||||
<h3 class="text-muted mb-0">
|
||||
<i class="bi bi-cpu-fill me-2"></i>
|
||||
@@ -192,7 +192,7 @@ const memoryHistoricalChartData = computed(() => {
|
||||
<div class="progress" role="progressbar" style="height: 10px">
|
||||
<div class="progress-bar" :style="{width: `${data?.CPU.cpu_percent}%` }"></div>
|
||||
</div>
|
||||
<div class="d-flex gap-1">
|
||||
<div class="d-grid gap-1" style="grid-template-columns: repeat(10, 1fr)">
|
||||
<CpuCore
|
||||
v-for="(cpu, count) in data?.CPU.cpu_percent_per_cpu"
|
||||
:square="true"
|
||||
|
||||
@@ -32,7 +32,7 @@ export default defineConfig(({mode}) => {
|
||||
}
|
||||
|
||||
return {
|
||||
base: "/static/dist/WGDashboardAdmin",
|
||||
base: "./",
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/img/Logo-2-128x128.png">
|
||||
<link rel="icon" href="./img/Logo-2-128x128.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WGDashboard Client</title>
|
||||
<base href="./client/">
|
||||
<style>
|
||||
*{
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
@@ -28,15 +29,22 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
const isViteDevMode = document.querySelector('script[src*="@vite/client"]') !== null;
|
||||
const base = document.querySelector('base');
|
||||
if (base && isViteDevMode) {
|
||||
base.href = '/';
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="preloader">
|
||||
<div id="preloader_placeholder">
|
||||
<img style="width: 100%" src="/img/Logo-2-128x128.png" alt="WGDashboard Client" />
|
||||
<img style="width: 100%" src="./img/Logo-2-128x128.png" alt="WGDashboard Client" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script type="module" src="./src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
390
src/static/client/package-lock.json
generated
390
src/static/client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "0.0.0",
|
||||
"version": "v1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "client",
|
||||
"version": "0.0.0",
|
||||
"version": "v1.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"bootstrap": "^5.3.6",
|
||||
@@ -1013,9 +1013,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
|
||||
"integrity": "sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
|
||||
"integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -1027,9 +1027,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz",
|
||||
"integrity": "sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
|
||||
"integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1041,9 +1041,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz",
|
||||
"integrity": "sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
|
||||
"integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1055,9 +1055,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz",
|
||||
"integrity": "sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
|
||||
"integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1069,9 +1069,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz",
|
||||
"integrity": "sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
|
||||
"integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1083,9 +1083,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz",
|
||||
"integrity": "sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
|
||||
"integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1097,13 +1097,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz",
|
||||
"integrity": "sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
|
||||
"integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1111,13 +1114,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz",
|
||||
"integrity": "sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
|
||||
"integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1125,13 +1131,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1139,41 +1148,84 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz",
|
||||
"integrity": "sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
|
||||
"integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==",
|
||||
"node_modules/@rollup/rollup-linux-loong64-gnu": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==",
|
||||
"node_modules/@rollup/rollup-linux-loong64-musl": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
|
||||
"integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-ppc64-musl": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
|
||||
"integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1181,13 +1233,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1195,13 +1250,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz",
|
||||
"integrity": "sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
|
||||
"integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1209,13 +1267,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1223,13 +1284,16 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz",
|
||||
"integrity": "sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1237,9 +1301,26 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz",
|
||||
"integrity": "sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
|
||||
"integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openbsd-x64": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
|
||||
"integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1247,13 +1328,27 @@
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
"openbsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-openharmony-arm64": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
|
||||
"integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz",
|
||||
"integrity": "sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
|
||||
"integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1265,9 +1360,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz",
|
||||
"integrity": "sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
|
||||
"integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -1278,10 +1373,24 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-gnu": {
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
|
||||
"integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz",
|
||||
"integrity": "sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
|
||||
"integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1313,9 +1422,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -1582,19 +1691,19 @@
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz",
|
||||
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
|
||||
"integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/birpc": {
|
||||
@@ -1692,7 +1801,7 @@
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1764,7 +1873,7 @@
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1818,9 +1927,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.13",
|
||||
"resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz",
|
||||
"integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -1895,7 +2004,7 @@
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1910,7 +2019,7 @@
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1959,7 +2068,7 @@
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1968,7 +2077,7 @@
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1977,7 +2086,7 @@
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1989,7 +2098,7 @@
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2131,9 +2240,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -2151,14 +2260,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2197,7 +2307,7 @@
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -2225,7 +2335,7 @@
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2249,7 +2359,7 @@
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2289,7 +2399,7 @@
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2308,7 +2418,7 @@
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2320,7 +2430,7 @@
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2335,7 +2445,7 @@
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2574,7 +2684,7 @@
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2583,7 +2693,7 @@
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2592,7 +2702,7 @@
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2799,9 +2909,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2886,10 +2996,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
@@ -2930,13 +3043,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.41.1.tgz",
|
||||
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
|
||||
"version": "4.60.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
|
||||
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -2946,26 +3059,31 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.41.1",
|
||||
"@rollup/rollup-android-arm64": "4.41.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.41.1",
|
||||
"@rollup/rollup-darwin-x64": "4.41.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.41.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.41.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.41.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.41.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.41.1",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.41.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.41.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.41.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.41.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.41.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.41.1",
|
||||
"@rollup/rollup-android-arm-eabi": "4.60.1",
|
||||
"@rollup/rollup-android-arm64": "4.60.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.60.1",
|
||||
"@rollup/rollup-darwin-x64": "4.60.1",
|
||||
"@rollup/rollup-freebsd-arm64": "4.60.1",
|
||||
"@rollup/rollup-freebsd-x64": "4.60.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.60.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.60.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.60.1",
|
||||
"@rollup/rollup-linux-loong64-gnu": "4.60.1",
|
||||
"@rollup/rollup-linux-loong64-musl": "4.60.1",
|
||||
"@rollup/rollup-linux-ppc64-gnu": "4.60.1",
|
||||
"@rollup/rollup-linux-ppc64-musl": "4.60.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.60.1",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.60.1",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.60.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.60.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.60.1",
|
||||
"@rollup/rollup-openbsd-x64": "4.60.1",
|
||||
"@rollup/rollup-openharmony-arm64": "4.60.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.60.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.60.1",
|
||||
"@rollup/rollup-win32-x64-gnu": "4.60.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.60.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@@ -3213,9 +3331,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmmirror.com/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
<script setup async>
|
||||
<script setup>
|
||||
import './assets/main.css'
|
||||
import NotificationList from "@/components/Notification/notificationList.vue";
|
||||
import {clientStore} from "@/stores/clientStore.js";
|
||||
|
||||
const store = clientStore()
|
||||
fetch("/client/api/serverInformation")
|
||||
.then(res => res.json())
|
||||
.then(res => store.serverInformation = res.data)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-bs-theme="dark" class="text-body bg-body vw-100 vh-100 bg-body">
|
||||
<div class="d-flex vw-100 p-sm-4 overflow-y-scroll innerContainer d-flex flex-column">
|
||||
<div class="mx-auto my-sm-auto position-relative"
|
||||
id="listContainer"
|
||||
>
|
||||
<div class="mx-auto my-sm-auto position-relative" id="listContainer">
|
||||
<Suspense>
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Transition name="app" type="transition" mode="out-in">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import {computed, ref} from "vue";
|
||||
import ConfigurationQRCode from "@/components/Configuration/configurationQRCode.vue";
|
||||
import dayjs from "dayjs";
|
||||
import Duration from 'dayjs/plugin/Duration'
|
||||
import Duration from 'dayjs/plugin/duration'
|
||||
dayjs.extend(Duration);
|
||||
const props = defineProps([
|
||||
'config'
|
||||
@@ -113,4 +113,4 @@ const emits = defineEmits(['select'])
|
||||
background-color: #28a745 !important;
|
||||
box-shadow: 0 0 0 .2rem #28a74545;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -96,7 +96,7 @@ if (route.query.Email){
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
<div>
|
||||
<div v-if="store.serverInformation.SignUp.enable">
|
||||
<hr class="my-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="text-muted">
|
||||
|
||||
@@ -6,43 +6,43 @@ import router from "@/router/router.js";
|
||||
import {createPinia} from "pinia";
|
||||
|
||||
import 'bootstrap/dist/js/bootstrap.bundle.js'
|
||||
import {axiosPost} from "@/utilities/request.js";
|
||||
import {axiosGet, axiosPost} from "@/utilities/request.js";
|
||||
import {clientStore} from "@/stores/clientStore.js";
|
||||
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const state = params.get('state')
|
||||
const code = params.get('code')
|
||||
|
||||
const initApp = () => {
|
||||
const initApp = async () => {
|
||||
const app = createApp(App)
|
||||
const serverInformation = await axiosGet("/api/serverInformation", {})
|
||||
app.use(createPinia())
|
||||
if (serverInformation){
|
||||
const store = clientStore()
|
||||
store.serverInformation = serverInformation.data;
|
||||
}
|
||||
app.use(router)
|
||||
app.mount("#app")
|
||||
}
|
||||
|
||||
function removeSearchString() {
|
||||
let url = new URL(window.location.href);
|
||||
url.search = ''; // Remove all query parameters
|
||||
history.replaceState({}, document.title, url.toString());
|
||||
}
|
||||
|
||||
if (state && code){
|
||||
axiosPost("/api/signin/oidc", {
|
||||
await axiosPost("/api/signin/oidc", {
|
||||
provider: state,
|
||||
code: code,
|
||||
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname
|
||||
}).then(data => {
|
||||
}).then(async (data) => {
|
||||
let url = new URL(window.location.href);
|
||||
url.search = '';
|
||||
history.replaceState({}, document.title, url.toString());
|
||||
|
||||
initApp()
|
||||
await initApp()
|
||||
if (!data.status){
|
||||
const store = clientStore()
|
||||
store.newNotification(data.message, 'danger')
|
||||
}
|
||||
})
|
||||
}else{
|
||||
initApp()
|
||||
await initApp()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ const router = createRouter({
|
||||
})
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const store = clientStore()
|
||||
if (to.path === "/signup" && !store.serverInformation.SignUp.enable){
|
||||
next('/signin')
|
||||
store.newNotification("Sign up is disabled. Please contact administrator for more information", "warning")
|
||||
}
|
||||
|
||||
if (to.path === '/signout'){
|
||||
await axios.get(requestURl('/api/signout')).then(() => {
|
||||
next('/signin')
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
import axios from "axios";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
export const requestURl = (url) => {
|
||||
return import.meta.env.MODE === 'development' ? '/client' + url
|
||||
: `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
return '/client' + url;
|
||||
}
|
||||
return `./.${url}`;
|
||||
}
|
||||
|
||||
// const router = useRouter()
|
||||
|
||||
export const axiosPost = async (URL, body = {}) => {
|
||||
try{
|
||||
const res = await axios.post(requestURl(URL), body)
|
||||
return res.data
|
||||
} catch (error){
|
||||
console.log(error)
|
||||
// if (error.status === 401){
|
||||
// await router.push('/signin')
|
||||
// }
|
||||
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -28,9 +23,6 @@ export const axiosGet = async (URL, query = {}) => {
|
||||
return res.data
|
||||
} catch (error){
|
||||
console.log(error)
|
||||
// if (error.status === 401){
|
||||
// await router.push('/signin')
|
||||
// }
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -40,5 +40,5 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
},
|
||||
base: '/static/dist/WGDashboardClient'
|
||||
base: './'
|
||||
})
|
||||
|
||||
@@ -1 +1 @@
|
||||
import{a5 as A,r as n,D as S,g as l,z as v}from"./index-CmClDcBF.js";const b=A("DashboardClientAssignmentStore",()=>{const f=n({}),d=n([]),o=n({}),c=n([]),g=n(!1),r=n(""),i=S(),w=async()=>{await l("/api/clients/allClients",{},s=>{o.value=s.data})},y=async()=>{await l("/api/clients/allClientsRaw",{},s=>{c.value=s.data,console.log(c.value)})},m=s=>Object.values(o.value).flat().find(e=>e.ClientID===s),u=async(s,e)=>{await l("/api/clients/assignedClients",{ConfigurationName:s,Peer:e},a=>{d.value=a.data})};return{assignments:d,getAssignedClients:u,getClients:w,getClientsRaw:y,clients:o,unassignClient:async(s,e,a)=>{g.value=!0,await v("/api/clients/unassignClient",{AssignmentID:a},async t=>{t.status?(i.newMessage("Server","Unassign successfully!","success"),s&&e&&await u(s,e)):(i.newMessage("Server","Unassign Failed. Reason: "+t.message,"success"),console.error("Unassign Failed. Reason: "+t.message)),g.value=!1})},assignClient:async(s,e,a,t=!0)=>{r.value=a,await v("/api/clients/assignClient",{ConfigurationName:s,Peer:e,ClientID:a},async C=>{C.status?(i.newMessage("Server","Assign successfully!","success"),t&&await u(s,e)):(i.newMessage("Server","Assign Failed. Reason: "+C.message,"success"),console.error("Assign Failed. Reason: "+C.message)),r.value=""})},getClientById:m,unassigning:g,assigning:r,clientsRaw:c,allConfigurationsPeers:f,getAllConfigurationsPeers:async()=>{await l("/api/clients/allConfigurationsPeers",{},s=>{f.value=s.data})}}});export{b as D};
|
||||
import{a5 as A,r as n,D as S,g as l,z as v}from"./index-DXzxfcZW.js";const b=A("DashboardClientAssignmentStore",()=>{const f=n({}),d=n([]),o=n({}),c=n([]),g=n(!1),r=n(""),i=S(),w=async()=>{await l("/api/clients/allClients",{},s=>{o.value=s.data})},y=async()=>{await l("/api/clients/allClientsRaw",{},s=>{c.value=s.data,console.log(c.value)})},m=s=>Object.values(o.value).flat().find(e=>e.ClientID===s),u=async(s,e)=>{await l("/api/clients/assignedClients",{ConfigurationName:s,Peer:e},a=>{d.value=a.data})};return{assignments:d,getAssignedClients:u,getClients:w,getClientsRaw:y,clients:o,unassignClient:async(s,e,a)=>{g.value=!0,await v("/api/clients/unassignClient",{AssignmentID:a},async t=>{t.status?(i.newMessage("Server","Unassign successfully!","success"),s&&e&&await u(s,e)):(i.newMessage("Server","Unassign Failed. Reason: "+t.message,"success"),console.error("Unassign Failed. Reason: "+t.message)),g.value=!1})},assignClient:async(s,e,a,t=!0)=>{r.value=a,await v("/api/clients/assignClient",{ConfigurationName:s,Peer:e,ClientID:a},async C=>{C.status?(i.newMessage("Server","Assign successfully!","success"),t&&await u(s,e)):(i.newMessage("Server","Assign Failed. Reason: "+C.message,"success"),console.error("Assign Failed. Reason: "+C.message)),r.value=""})},getClientById:m,unassigning:g,assigning:r,clientsRaw:c,allConfigurationsPeers:f,getAllConfigurationsPeers:async()=>{await l("/api/clients/allConfigurationsPeers",{},s=>{f.value=s.data})}}});export{b as D};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
src/static/dist/WGDashboardAdmin/assets/clientViewer-zQHAiseB.js
vendored
Normal file
1
src/static/dist/WGDashboardAdmin/assets/clientViewer-zQHAiseB.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
src/static/dist/WGDashboardAdmin/assets/clients-dPj1ZA29.js
vendored
Normal file
1
src/static/dist/WGDashboardAdmin/assets/clients-dPj1ZA29.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{_ as r,c as i,b as o,w as e,k as l,j as a,l as _,S as u,h as d,f as t}from"./index-CmClDcBF.js";const m={name:"configuration"},f={class:"mt-md-5 mt-3 text-body"};function p(h,k,x,w,$,v){const n=d("RouterView");return t(),i("div",f,[o(n,null,{default:e(({Component:s,route:c})=>[o(l,{name:"fade2",mode:"out-in"},{default:e(()=>[(t(),a(u,null,{default:e(()=>[(t(),a(_(s),{key:c.path,class:"z-1"}))]),_:2},1024))]),_:2},1024)]),_:1})])}const B=r(m,[["render",p]]);export{B as default};
|
||||
import{_ as r,c as i,b as o,w as e,k as l,j as a,l as _,S as u,h as d,f as t}from"./index-DXzxfcZW.js";const m={name:"configuration"},f={class:"mt-md-5 mt-3 text-body"};function p(h,k,x,w,$,v){const n=d("RouterView");return t(),i("div",f,[o(n,null,{default:e(({Component:s,route:c})=>[o(l,{name:"fade2",mode:"out-in"},{default:e(()=>[(t(),a(u,null,{default:e(()=>[(t(),a(_(s),{key:c.path,class:"z-1"}))]),_:2},1024))]),_:2},1024)]),_:1})])}const B=r(m,[["render",p]]);export{B as default};
|
||||
1
src/static/dist/WGDashboardAdmin/assets/configurationList-BcQfpCge.js
vendored
Normal file
1
src/static/dist/WGDashboardAdmin/assets/configurationList-BcQfpCge.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
.fade-enter-active[data-v-9f596f5e]{transition-delay:var(--v0d365bfc)!important}.progress-bar[data-v-851170e4]{width:0;transition:all 1s cubic-bezier(.42,0,.22,1)}.filter a[data-v-7ed053f0]{text-decoration:none}
|
||||
.fade-enter-active[data-v-9f596f5e]{transition-delay:var(--v0d365bfc)!important}.progress-bar[data-v-01ef60a9]{width:0;transition:all 1s cubic-bezier(.42,0,.22,1)}.filter a[data-v-7ed053f0]{text-decoration:none}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{_ as f,c as i,a as t,b as u,h as w,d as k,m as x,y,n as p,t as v,z as _,D as m,W as b,A as S,f as n,r as D,q as $,F as W,i as V}from"./index-CmClDcBF.js";import{L as C}from"./localeText-TzABauzQ.js";const F={name:"dashboardSettingsInputWireguardConfigurationPath",components:{LocaleText:C},props:{targetData:String,title:String,warning:!1,warningText:""},setup(){const o=m(),s=b(),r=`input_${S()}`;return{store:o,uuid:r,WireguardConfigurationStore:s}},data(){return{value:"",invalidFeedback:"",showInvalidFeedback:!1,isValid:!1,timeout:void 0,changed:!1,updating:!1}},mounted(){this.value=this.store.Configuration.Server[this.targetData]},methods:{async useValidation(){this.changed&&(this.updating=!0,await _("/api/updateDashboardConfigurationItem",{section:"Server",key:this.targetData,value:this.value},o=>{o.status?(this.isValid=!0,this.showInvalidFeedback=!1,this.store.Configuration.Account[this.targetData]=this.value,clearTimeout(this.timeout),this.timeout=setTimeout(()=>this.isValid=!1,5e3),this.WireguardConfigurationStore.getConfigurations(),this.store.newMessage("Server","WireGuard configuration path saved","success")):(this.isValid=!1,this.showInvalidFeedback=!0,this.invalidFeedback=o.message),this.changed=!1,this.updating=!1}))}}},I={class:"card"},T={class:"card-header"},A={class:"my-2"},L={class:"card-body"},M={class:"form-group"},N=["for"],P={class:"d-flex gap-2 align-items-start"},B={class:"flex-grow-1"},G=["id","disabled"],z={class:"invalid-feedback fw-bold"},U=["disabled"],q={key:0,class:"bi bi-save2-fill"},E={key:1,class:"spinner-border spinner-border-sm"},K={key:0,class:"px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1 mb-2"};function j(o,s,r,a,c,g){const d=w("LocaleText");return n(),i("div",I,[t("div",T,[t("h6",A,[u(d,{t:"Path"})])]),t("div",L,[t("div",M,[t("label",{for:this.uuid,class:"text-muted mb-1"},[t("strong",null,[t("small",null,[u(d,{t:this.title},null,8,["t"])])])],8,N),t("div",P,[t("div",B,[x(t("input",{type:"text",class:p(["form-control rounded-3",{"is-invalid":this.showInvalidFeedback,"is-valid":this.isValid}]),id:this.uuid,"onUpdate:modelValue":s[0]||(s[0]=e=>this.value=e),onKeydown:s[1]||(s[1]=e=>this.changed=!0),disabled:this.updating},null,42,G),[[y,this.value]]),t("div",z,v(this.invalidFeedback),1)]),t("button",{onClick:s[2]||(s[2]=e=>this.useValidation()),disabled:!this.changed,class:"ms-auto btn rounded-3 border-success-subtle bg-success-subtle text-success-emphasis"},[this.updating?(n(),i("span",E)):(n(),i("i",q))],8,U)]),r.warning?(n(),i("div",K,[t("small",null,[s[3]||(s[3]=t("i",{class:"bi bi-exclamation-triangle-fill me-2"},null,-1)),u(d,{t:r.warningText},null,8,["t"])])])):k("",!0)])])])}const et=f(F,[["render",j]]),H={class:"card rounded-3"},J={class:"card-header"},O={class:"my-2"},Q={class:"card-body d-flex gap-2"},R={class:"list-group w-100"},X=["onClick"],Y={__name:"dashboardSettingsWireguardConfigurationAutostart",setup(o){const s=m(),r=b(),a=D(s.Configuration.WireGuardConfiguration.autostart),c=$(()=>r.Configurations.map(e=>e.Name)),g=async()=>{await _("/api/updateDashboardConfigurationItem",{section:"WireGuardConfiguration",key:"autostart",value:a.value},async e=>{e.status?(s.newMessage("Server","Start up configurations saved","success"),a.value=e.data):s.newMessage("Server","Start up configurations failed to save","danger")})},d=e=>{a.value.includes(e)?a.value=a.value.filter(h=>h!==e):a.value.push(e),g()};return(e,h)=>(n(),i("div",H,[t("div",J,[t("h6",O,[u(C,{t:"Toggle When Start Up"})])]),t("div",Q,[t("div",R,[(n(!0),i(W,null,V(c.value,l=>(n(),i("button",{type:"button",key:l,onClick:Z=>d(l),class:"list-group-item list-group-item-action py-2 w-100 d-flex align-items-center"},[t("samp",null,v(l),1),t("i",{class:p(["ms-auto",[a.value.includes(l)?"bi-check-circle-fill":"bi-circle"]])},null,2)],8,X))),128))])])]))}},at=f(Y,[["__scopeId","data-v-4aa2aed9"]]);export{et as D,at as a};
|
||||
import{_ as f,c as i,a as t,b as u,h as w,d as k,m as x,y,n as p,t as v,z as _,D as m,W as b,A as S,f as n,r as D,q as $,F as W,i as V}from"./index-DXzxfcZW.js";import{L as C}from"./localeText-Dmcj5qqx.js";const F={name:"dashboardSettingsInputWireguardConfigurationPath",components:{LocaleText:C},props:{targetData:String,title:String,warning:!1,warningText:""},setup(){const o=m(),s=b(),r=`input_${S()}`;return{store:o,uuid:r,WireguardConfigurationStore:s}},data(){return{value:"",invalidFeedback:"",showInvalidFeedback:!1,isValid:!1,timeout:void 0,changed:!1,updating:!1}},mounted(){this.value=this.store.Configuration.Server[this.targetData]},methods:{async useValidation(){this.changed&&(this.updating=!0,await _("/api/updateDashboardConfigurationItem",{section:"Server",key:this.targetData,value:this.value},o=>{o.status?(this.isValid=!0,this.showInvalidFeedback=!1,this.store.Configuration.Account[this.targetData]=this.value,clearTimeout(this.timeout),this.timeout=setTimeout(()=>this.isValid=!1,5e3),this.WireguardConfigurationStore.getConfigurations(),this.store.newMessage("Server","WireGuard configuration path saved","success")):(this.isValid=!1,this.showInvalidFeedback=!0,this.invalidFeedback=o.message),this.changed=!1,this.updating=!1}))}}},I={class:"card"},T={class:"card-header"},A={class:"my-2"},L={class:"card-body"},M={class:"form-group"},N=["for"],P={class:"d-flex gap-2 align-items-start"},B={class:"flex-grow-1"},G=["id","disabled"],z={class:"invalid-feedback fw-bold"},U=["disabled"],q={key:0,class:"bi bi-save2-fill"},E={key:1,class:"spinner-border spinner-border-sm"},K={key:0,class:"px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1 mb-2"};function j(o,s,r,a,c,g){const d=w("LocaleText");return n(),i("div",I,[t("div",T,[t("h6",A,[u(d,{t:"Path"})])]),t("div",L,[t("div",M,[t("label",{for:this.uuid,class:"text-muted mb-1"},[t("strong",null,[t("small",null,[u(d,{t:this.title},null,8,["t"])])])],8,N),t("div",P,[t("div",B,[x(t("input",{type:"text",class:p(["form-control rounded-3",{"is-invalid":this.showInvalidFeedback,"is-valid":this.isValid}]),id:this.uuid,"onUpdate:modelValue":s[0]||(s[0]=e=>this.value=e),onKeydown:s[1]||(s[1]=e=>this.changed=!0),disabled:this.updating},null,42,G),[[y,this.value]]),t("div",z,v(this.invalidFeedback),1)]),t("button",{onClick:s[2]||(s[2]=e=>this.useValidation()),disabled:!this.changed,class:"ms-auto btn rounded-3 border-success-subtle bg-success-subtle text-success-emphasis"},[this.updating?(n(),i("span",E)):(n(),i("i",q))],8,U)]),r.warning?(n(),i("div",K,[t("small",null,[s[3]||(s[3]=t("i",{class:"bi bi-exclamation-triangle-fill me-2"},null,-1)),u(d,{t:r.warningText},null,8,["t"])])])):k("",!0)])])])}const et=f(F,[["render",j]]),H={class:"card rounded-3"},J={class:"card-header"},O={class:"my-2"},Q={class:"card-body d-flex gap-2"},R={class:"list-group w-100"},X=["onClick"],Y={__name:"dashboardSettingsWireguardConfigurationAutostart",setup(o){const s=m(),r=b(),a=D(s.Configuration.WireGuardConfiguration.autostart),c=$(()=>r.Configurations.map(e=>e.Name)),g=async()=>{await _("/api/updateDashboardConfigurationItem",{section:"WireGuardConfiguration",key:"autostart",value:a.value},async e=>{e.status?(s.newMessage("Server","Start up configurations saved","success"),a.value=e.data):s.newMessage("Server","Start up configurations failed to save","danger")})},d=e=>{a.value.includes(e)?a.value=a.value.filter(h=>h!==e):a.value.push(e),g()};return(e,h)=>(n(),i("div",H,[t("div",J,[t("h6",O,[u(C,{t:"Toggle When Start Up"})])]),t("div",Q,[t("div",R,[(n(!0),i(W,null,V(c.value,l=>(n(),i("button",{type:"button",key:l,onClick:Z=>d(l),class:"list-group-item list-group-item-action py-2 w-100 d-flex align-items-center"},[t("samp",null,v(l),1),t("i",{class:p(["ms-auto",[a.value.includes(l)?"bi-check-circle-fill":"bi-circle"]])},null,2)],8,X))),128))])])]))}},at=f(Y,[["__scopeId","data-v-4aa2aed9"]]);export{et as D,at as a};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{I as j,q as w,P as S,J as k,Q as L,u as R}from"./index-CmClDcBF.js";const W=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const X=Object.prototype.toString,Y=t=>X.call(t)==="[object Object]",$=()=>{};function C(t){return Array.isArray(t)?t:[t]}function q(t,a,r){return j(t,a,{...r,immediate:!0})}const I=W?window:void 0;function P(t){var a;const r=S(t);return(a=r?.$el)!==null&&a!==void 0?a:r}function T(...t){const a=(o,u,s,d)=>(o.addEventListener(u,s,d),()=>o.removeEventListener(u,s,d)),r=w(()=>{const o=C(S(t[0])).filter(u=>u!=null);return o.every(u=>typeof u!="string")?o:void 0});return q(()=>{var o,u;return[(o=(u=r.value)===null||u===void 0?void 0:u.map(s=>P(s)))!==null&&o!==void 0?o:[I].filter(s=>s!=null),C(S(r.value?t[1]:t[0])),C(R(r.value?t[2]:t[1])),S(r.value?t[3]:t[2])]},([o,u,s,d],p,c)=>{if(!o?.length||!u?.length||!s?.length)return;const f=Y(d)?{...d}:d,v=o.flatMap(b=>u.flatMap(h=>s.map(y=>a(b,h,y,f))));c(()=>{v.forEach(b=>b())})},{flush:"post"})}function B(t,a,r={}){const{window:o=I,ignore:u=[],capture:s=!0,detectIframe:d=!1,controls:p=!1}=r;if(!o)return p?{stop:$,cancel:$,trigger:$}:$;let c=!0;const f=e=>S(u).some(n=>{if(typeof n=="string")return Array.from(o.document.querySelectorAll(n)).some(i=>i===e.target||e.composedPath().includes(i));{const i=P(n);return i&&(e.target===i||e.composedPath().includes(i))}});function v(e){const n=S(e);return n&&n.$.subTree.shapeFlag===16}function b(e,n){const i=S(e),m=i.$.subTree&&i.$.subTree.children;return m==null||!Array.isArray(m)?!1:m.some(A=>A.el===n.target||n.composedPath().includes(A.el))}const h=e=>{const n=P(t);if(e.target!=null&&!(!(n instanceof Element)&&v(t)&&b(t,e))&&!(!n||n===e.target||e.composedPath().includes(n))){if("detail"in e&&e.detail===0&&(c=!f(e)),!c){c=!0;return}a(e)}};let y=!1;const E=[T(o,"click",e=>{y||(y=!0,setTimeout(()=>{y=!1},0),h(e))},{passive:!0,capture:s}),T(o,"pointerdown",e=>{const n=P(t);c=!f(e)&&!!(n&&!e.composedPath().includes(n))},{passive:!0}),d&&T(o,"blur",e=>{setTimeout(()=>{var n;const i=P(t);((n=o.document.activeElement)===null||n===void 0?void 0:n.tagName)==="IFRAME"&&!i?.contains(o.document.activeElement)&&a(e)},0)},{passive:!0})].filter(Boolean),x=()=>E.forEach(e=>e());return p?{stop:x,cancel:()=>{c=!1},trigger:e=>{c=!0,h(e),c=!1}}:x}function D(t,a={}){const{threshold:r=50,onSwipe:o,onSwipeEnd:u,onSwipeStart:s,passive:d=!0}=a,p=k({x:0,y:0}),c=k({x:0,y:0}),f=w(()=>p.x-c.x),v=w(()=>p.y-c.y),{max:b,abs:h}=Math,y=w(()=>b(h(f.value),h(v.value))>=r),E=L(!1),x=w(()=>y.value?h(f.value)>h(v.value)?f.value>0?"left":"right":v.value>0?"up":"down":"none"),e=l=>[l.touches[0].clientX,l.touches[0].clientY],n=(l,g)=>{p.x=l,p.y=g},i=(l,g)=>{c.x=l,c.y=g},m={passive:d,capture:!d},A=l=>{E.value&&u?.(l,x.value),E.value=!1},O=[T(t,"touchstart",l=>{if(l.touches.length!==1)return;const[g,M]=e(l);n(g,M),i(g,M),s?.(l)},m),T(t,"touchmove",l=>{if(l.touches.length!==1)return;const[g,M]=e(l);i(g,M),m.capture&&!m.passive&&Math.abs(f.value)>Math.abs(v.value)&&l.preventDefault(),!E.value&&y.value&&(E.value=!0),E.value&&o?.(l)},m),T(t,["touchend","touchcancel"],A,m)];return{isSwiping:E,direction:x,coordsStart:p,coordsEnd:c,lengthX:f,lengthY:v,stop:()=>O.forEach(l=>l())}}export{D as a,B as o,P as u};
|
||||
import{H as I,q as w,P as S,J as k,Q as L,u as R}from"./index-DXzxfcZW.js";const W=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const X=Object.prototype.toString,Y=t=>X.call(t)==="[object Object]",$=()=>{};function C(t){return Array.isArray(t)?t:[t]}function q(t,a,r){return I(t,a,{...r,immediate:!0})}const O=W?window:void 0;function P(t){var a;const r=S(t);return(a=r?.$el)!==null&&a!==void 0?a:r}function T(...t){const a=(o,u,s,d)=>(o.addEventListener(u,s,d),()=>o.removeEventListener(u,s,d)),r=w(()=>{const o=C(S(t[0])).filter(u=>u!=null);return o.every(u=>typeof u!="string")?o:void 0});return q(()=>{var o,u;return[(o=(u=r.value)===null||u===void 0?void 0:u.map(s=>P(s)))!==null&&o!==void 0?o:[O].filter(s=>s!=null),C(S(r.value?t[1]:t[0])),C(R(r.value?t[2]:t[1])),S(r.value?t[3]:t[2])]},([o,u,s,d],p,c)=>{if(!o?.length||!u?.length||!s?.length)return;const f=Y(d)?{...d}:d,v=o.flatMap(b=>u.flatMap(h=>s.map(y=>a(b,h,y,f))));c(()=>{v.forEach(b=>b())})},{flush:"post"})}function B(t,a,r={}){const{window:o=O,ignore:u=[],capture:s=!0,detectIframe:d=!1,controls:p=!1}=r;if(!o)return p?{stop:$,cancel:$,trigger:$}:$;let c=!0;const f=e=>S(u).some(n=>{if(typeof n=="string")return Array.from(o.document.querySelectorAll(n)).some(i=>i===e.target||e.composedPath().includes(i));{const i=P(n);return i&&(e.target===i||e.composedPath().includes(i))}});function v(e){const n=S(e);return n&&n.$.subTree.shapeFlag===16}function b(e,n){const i=S(e),m=i.$.subTree&&i.$.subTree.children;return m==null||!Array.isArray(m)?!1:m.some(A=>A.el===n.target||n.composedPath().includes(A.el))}const h=e=>{const n=P(t);if(e.target!=null&&!(!(n instanceof Element)&&v(t)&&b(t,e))&&!(!n||n===e.target||e.composedPath().includes(n))){if("detail"in e&&e.detail===0&&(c=!f(e)),!c){c=!0;return}a(e)}};let y=!1;const E=[T(o,"click",e=>{y||(y=!0,setTimeout(()=>{y=!1},0),h(e))},{passive:!0,capture:s}),T(o,"pointerdown",e=>{const n=P(t);c=!f(e)&&!!(n&&!e.composedPath().includes(n))},{passive:!0}),d&&T(o,"blur",e=>{setTimeout(()=>{var n;const i=P(t);((n=o.document.activeElement)===null||n===void 0?void 0:n.tagName)==="IFRAME"&&!i?.contains(o.document.activeElement)&&a(e)},0)},{passive:!0})].filter(Boolean),x=()=>E.forEach(e=>e());return p?{stop:x,cancel:()=>{c=!1},trigger:e=>{c=!0,h(e),c=!1}}:x}function D(t,a={}){const{threshold:r=50,onSwipe:o,onSwipeEnd:u,onSwipeStart:s,passive:d=!0}=a,p=k({x:0,y:0}),c=k({x:0,y:0}),f=w(()=>p.x-c.x),v=w(()=>p.y-c.y),{max:b,abs:h}=Math,y=w(()=>b(h(f.value),h(v.value))>=r),E=L(!1),x=w(()=>y.value?h(f.value)>h(v.value)?f.value>0?"left":"right":v.value>0?"up":"down":"none"),e=l=>[l.touches[0].clientX,l.touches[0].clientY],n=(l,g)=>{p.x=l,p.y=g},i=(l,g)=>{c.x=l,c.y=g},m={passive:d,capture:!d},A=l=>{E.value&&u?.(l,x.value),E.value=!1},j=[T(t,"touchstart",l=>{if(l.touches.length!==1)return;const[g,M]=e(l);n(g,M),i(g,M),s?.(l)},m),T(t,"touchmove",l=>{if(l.touches.length!==1)return;const[g,M]=e(l);i(g,M),m.capture&&!m.passive&&Math.abs(f.value)>Math.abs(v.value)&&l.preventDefault(),!E.value&&y.value&&(E.value=!0),E.value&&o?.(l)},m),T(t,["touchend","touchcancel"],A,m)];return{isSwiping:E,direction:x,coordsStart:p,coordsEnd:c,lengthX:f,lengthY:v,stop:()=>j.forEach(l=>l())}}export{D as a,B as o,P as u};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{_ as e,G as t,c as o,t as a,f as c}from"./index-CmClDcBF.js";const s={name:"localeText",props:{t:""},computed:{getLocaleText(){return t(this.t)}}};function n(r,p,l,_,i,x){return c(),o("span",null,a(this.getLocaleText),1)}const m=e(s,[["render",n]]);export{m as L};
|
||||
import{_ as e,G as t,c as o,t as a,f as c}from"./index-DXzxfcZW.js";const s={name:"localeText",props:{t:""},computed:{getLocaleText(){return t(this.t)}}};function n(r,p,l,_,i,x){return c(),o("span",null,a(this.getLocaleText),1)}const m=e(s,[["render",n]]);export{m as L};
|
||||
@@ -1 +1 @@
|
||||
import{L as l}from"./localeText-TzABauzQ.js";import{d as c}from"./dayjs.min-DFlK6ZAc.js";import{_ as h,c as o,a as e,b as a,w as u,e as p,h as g,t as i,k as f,n as _,f as n}from"./index-CmClDcBF.js";const x={name:"message",methods:{dayjs:c,hide(){this.ct(),this.message.show=!1},show(){this.timeout=setTimeout(()=>{this.message.show=!1},5e3)},ct(){clearTimeout(this.timeout)}},components:{LocaleText:l},props:{message:Object},mounted(){this.show()},data(){return{dismiss:!1,timeout:null}}},v=["id"],b={key:0,class:"d-flex"},w={class:"fw-bold d-block",style:{"text-transform":"uppercase"}},y={class:"ms-auto"},k={key:1},T={class:"card-body d-flex align-items-center gap-3"};function M(C,s,L,j,t,m){const d=g("LocaleText");return n(),o("div",{onMouseenter:s[1]||(s[1]=r=>{t.dismiss=!0,this.ct()}),onMouseleave:s[2]||(s[2]=r=>{t.dismiss=!1,this.show()}),class:"card shadow rounded-3 position-relative message ms-auto",id:this.message.id},[e("div",{class:_([{"text-bg-danger":this.message.type==="danger","text-bg-success":this.message.type==="success","text-bg-warning":this.message.type==="warning"},"card-header pos"])},[a(f,{name:"zoom",mode:"out-in"},{default:u(()=>[t.dismiss?(n(),o("div",k,[e("small",{onClick:s[0]||(s[0]=r=>m.hide()),class:"d-block mx-auto w-100 text-center",style:{cursor:"pointer"}},[s[3]||(s[3]=e("i",{class:"bi bi-x-lg me-2"},null,-1)),a(d,{t:"Dismiss"})])])):(n(),o("div",b,[e("small",w,[a(d,{t:"FROM "}),p(" "+i(this.message.from),1)]),e("small",y,i(m.dayjs().format("hh:mm A")),1)]))]),_:1})],2),e("div",T,[e("div",null,i(this.message.content),1)])],40,v)}const z=h(x,[["render",M],["__scopeId","data-v-94c76b54"]]);export{z as M};
|
||||
import{L as l}from"./localeText-Dmcj5qqx.js";import{d as c}from"./dayjs.min-C-brjxlJ.js";import{_ as h,c as o,a as e,b as a,w as u,e as p,h as g,t as i,k as f,n as _,f as n}from"./index-DXzxfcZW.js";const x={name:"message",methods:{dayjs:c,hide(){this.ct(),this.message.show=!1},show(){this.timeout=setTimeout(()=>{this.message.show=!1},5e3)},ct(){clearTimeout(this.timeout)}},components:{LocaleText:l},props:{message:Object},mounted(){this.show()},data(){return{dismiss:!1,timeout:null}}},v=["id"],b={key:0,class:"d-flex"},w={class:"fw-bold d-block",style:{"text-transform":"uppercase"}},y={class:"ms-auto"},k={key:1},T={class:"card-body d-flex align-items-center gap-3"};function M(C,s,L,j,t,m){const d=g("LocaleText");return n(),o("div",{onMouseenter:s[1]||(s[1]=r=>{t.dismiss=!0,this.ct()}),onMouseleave:s[2]||(s[2]=r=>{t.dismiss=!1,this.show()}),class:"card shadow rounded-3 position-relative message ms-auto",id:this.message.id},[e("div",{class:_([{"text-bg-danger":this.message.type==="danger","text-bg-success":this.message.type==="success","text-bg-warning":this.message.type==="warning"},"card-header pos"])},[a(f,{name:"zoom",mode:"out-in"},{default:u(()=>[t.dismiss?(n(),o("div",k,[e("small",{onClick:s[0]||(s[0]=r=>m.hide()),class:"d-block mx-auto w-100 text-center",style:{cursor:"pointer"}},[s[3]||(s[3]=e("i",{class:"bi bi-x-lg me-2"},null,-1)),a(d,{t:"Dismiss"})])])):(n(),o("div",b,[e("small",w,[a(d,{t:"FROM "}),p(" "+i(this.message.from),1)]),e("small",y,i(m.dayjs().format("hh:mm A")),1)]))]),_:1})],2),e("div",T,[e("div",null,i(this.message.content),1)])],40,v)}const z=h(x,[["render",M],["__scopeId","data-v-94c76b54"]]);export{z as M};
|
||||
@@ -1 +0,0 @@
|
||||
.protocolBtnGroup a[data-v-c25dcde1]{transition:all .2s ease-in-out}
|
||||
3
src/static/dist/WGDashboardAdmin/assets/newConfiguration-Cu3W-j8E.js
vendored
Normal file
3
src/static/dist/WGDashboardAdmin/assets/newConfiguration-Cu3W-j8E.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/static/dist/WGDashboardAdmin/assets/newConfiguration-DKjGLwK7.css
vendored
Normal file
1
src/static/dist/WGDashboardAdmin/assets/newConfiguration-DKjGLwK7.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.protocolBtnGroup a[data-v-14fcf0ee]{transition:all .2s ease-in-out}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{S as C,e as y,c as w,m as _,a as L,f as S,l as v,i as M,b as k,d as x,g as A,h as F,j as R,M as D,V as P,T as b,k as l,O as E,n as O,F as h,P as f,o as T,p as c,C as V,q as u,r as X}from"./Vector-CuSZivra.js";import{_ as Y,D as G,c as $,d as j,f as q}from"./index-CmClDcBF.js";class r extends C{constructor(t,e){super(),this.flatMidpoint_=null,this.flatMidpointRevision_=-1,this.maxDelta_=-1,this.maxDeltaRevision_=-1,e!==void 0&&!Array.isArray(t[0])?this.setFlatCoordinates(e,t):this.setCoordinates(t,e)}appendCoordinate(t){y(this.flatCoordinates,t),this.changed()}clone(){const t=new r(this.flatCoordinates.slice(),this.layout);return t.applyProperties(this),t}closestPointXY(t,e,o,n){return n<w(this.getExtent(),t,e)?n:(this.maxDeltaRevision_!=this.getRevision()&&(this.maxDelta_=Math.sqrt(_(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,0)),this.maxDeltaRevision_=this.getRevision()),L(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,this.maxDelta_,!1,t,e,o,n))}forEachSegment(t){return S(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t)}getCoordinateAtM(t,e){return this.layout!="XYM"&&this.layout!="XYZM"?null:(e=e!==void 0?e:!1,v(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,e))}getCoordinates(){return M(this.flatCoordinates,0,this.flatCoordinates.length,this.stride)}getCoordinateAt(t,e){return k(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,e,this.stride)}getLength(){return x(this.flatCoordinates,0,this.flatCoordinates.length,this.stride)}getFlatMidpoint(){return this.flatMidpointRevision_!=this.getRevision()&&(this.flatMidpoint_=this.getCoordinateAt(.5,this.flatMidpoint_??void 0),this.flatMidpointRevision_=this.getRevision()),this.flatMidpoint_}getSimplifiedGeometryInternal(t){const e=[];return e.length=A(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,e,0),new r(e,"XY")}getType(){return"LineString"}intersectsExtent(t){return F(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,this.getExtent())}setCoordinates(t,e){this.setLayout(e,t,1),this.flatCoordinates||(this.flatCoordinates=[]),this.flatCoordinates.length=R(this.flatCoordinates,0,t,this.stride),this.changed()}}const B={name:"osmap",props:{type:"",d:Object||Array},data(){return{osmAvailable:!0}},setup(){return{store:G()}},methods:{getLastLonLat(){if(this.type==="traceroute"){const i=this.d.findLast(t=>t.geo&&t.geo.lat&&t.geo.lon);return i?[i.geo.lon,i.geo.lat]:[0,0]}return[this.d.geo.lon,this.d.geo.lat]}},async mounted(){await fetch("https://tile.openstreetmap.org/",{signal:AbortSignal.timeout(1500)}).then(i=>{const t=new D({target:"map",layers:[new b({source:new E})],view:new P({center:l(this.getLastLonLat()),zoom:this.type==="traceroute"?3:10})}),e=[],o=new O;if(this.type==="traceroute")this.d.forEach(s=>{if(s.geo&&s.geo.lat&&s.geo.lon){const a=l([s.geo.lon,s.geo.lat]);e.push(a);const g=this.getLastLonLat(),m=new h({geometry:new f(a),last:s.geo.lon===g[0]&&s.geo.lat===g[1]});o.addFeature(m)}});else{const s=l([this.d.geo.lon,this.d.geo.lat]);e.push(s);const a=new h({geometry:new f(s)});o.addFeature(a)}const n=new r(e),d=new h({geometry:n});o.addFeature(d);const p=new T({source:o,style:function(s){if(s.getGeometry().getType()==="Point")return new c({image:new V({radius:10,fill:new X({color:s.get("last")?"#dc3545":"#0d6efd"}),stroke:new u({color:"white",width:5})})});if(s.getGeometry().getType()==="LineString")return new c({stroke:new u({color:"#0d6efd",width:2})})}});t.addLayer(p)}).catch(i=>{this.osmAvailable=!1})}},z={key:0,id:"map",class:"w-100 rounded-3"};function I(i,t,e,o,n,d){return this.osmAvailable?(q(),$("div",z)):j("",!0)}const H=Y(B,[["render",I]]);export{H as O};
|
||||
import{S as C,e as y,c as w,m as _,a as L,f as S,l as v,i as M,b as k,d as x,g as A,h as F,j as R,M as D,V as P,T as b,k as l,O as E,n as O,F as h,P as f,o as T,p as c,C as V,q as u,r as X}from"./Vector-CuSZivra.js";import{_ as Y,D as G,c as $,d as j,f as q}from"./index-DXzxfcZW.js";class r extends C{constructor(t,e){super(),this.flatMidpoint_=null,this.flatMidpointRevision_=-1,this.maxDelta_=-1,this.maxDeltaRevision_=-1,e!==void 0&&!Array.isArray(t[0])?this.setFlatCoordinates(e,t):this.setCoordinates(t,e)}appendCoordinate(t){y(this.flatCoordinates,t),this.changed()}clone(){const t=new r(this.flatCoordinates.slice(),this.layout);return t.applyProperties(this),t}closestPointXY(t,e,o,n){return n<w(this.getExtent(),t,e)?n:(this.maxDeltaRevision_!=this.getRevision()&&(this.maxDelta_=Math.sqrt(_(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,0)),this.maxDeltaRevision_=this.getRevision()),L(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,this.maxDelta_,!1,t,e,o,n))}forEachSegment(t){return S(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t)}getCoordinateAtM(t,e){return this.layout!="XYM"&&this.layout!="XYZM"?null:(e=e!==void 0?e:!1,v(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,e))}getCoordinates(){return M(this.flatCoordinates,0,this.flatCoordinates.length,this.stride)}getCoordinateAt(t,e){return k(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,e,this.stride)}getLength(){return x(this.flatCoordinates,0,this.flatCoordinates.length,this.stride)}getFlatMidpoint(){return this.flatMidpointRevision_!=this.getRevision()&&(this.flatMidpoint_=this.getCoordinateAt(.5,this.flatMidpoint_??void 0),this.flatMidpointRevision_=this.getRevision()),this.flatMidpoint_}getSimplifiedGeometryInternal(t){const e=[];return e.length=A(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,e,0),new r(e,"XY")}getType(){return"LineString"}intersectsExtent(t){return F(this.flatCoordinates,0,this.flatCoordinates.length,this.stride,t,this.getExtent())}setCoordinates(t,e){this.setLayout(e,t,1),this.flatCoordinates||(this.flatCoordinates=[]),this.flatCoordinates.length=R(this.flatCoordinates,0,t,this.stride),this.changed()}}const B={name:"osmap",props:{type:"",d:Object||Array},data(){return{osmAvailable:!0}},setup(){return{store:G()}},methods:{getLastLonLat(){if(this.type==="traceroute"){const i=this.d.findLast(t=>t.geo&&t.geo.lat&&t.geo.lon);return i?[i.geo.lon,i.geo.lat]:[0,0]}return[this.d.geo.lon,this.d.geo.lat]}},async mounted(){await fetch("https://tile.openstreetmap.org/",{signal:AbortSignal.timeout(1500)}).then(i=>{const t=new D({target:"map",layers:[new b({source:new E})],view:new P({center:l(this.getLastLonLat()),zoom:this.type==="traceroute"?3:10})}),e=[],o=new O;if(this.type==="traceroute")this.d.forEach(s=>{if(s.geo&&s.geo.lat&&s.geo.lon){const a=l([s.geo.lon,s.geo.lat]);e.push(a);const g=this.getLastLonLat(),m=new h({geometry:new f(a),last:s.geo.lon===g[0]&&s.geo.lat===g[1]});o.addFeature(m)}});else{const s=l([this.d.geo.lon,this.d.geo.lat]);e.push(s);const a=new h({geometry:new f(s)});o.addFeature(a)}const n=new r(e),d=new h({geometry:n});o.addFeature(d);const p=new T({source:o,style:function(s){if(s.getGeometry().getType()==="Point")return new c({image:new V({radius:10,fill:new X({color:s.get("last")?"#dc3545":"#0d6efd"}),stroke:new u({color:"white",width:5})})});if(s.getGeometry().getType()==="LineString")return new c({stroke:new u({color:"#0d6efd",width:2})})}});t.addLayer(p)}).catch(i=>{this.osmAvailable=!1})}},z={key:0,id:"map",class:"w-100 rounded-3"};function I(i,t,e,o,n,d){return this.osmAvailable?(q(),$("div",z)):j("",!0)}const H=Y(B,[["render",I]]);export{H as O};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{_ as v,D as g,r as o,o as h,L as x,g as y,c as i,f as n,a as s,b as c,d as w,n as C,w as k,k as F}from"./index-CmClDcBF.js";import{L as T}from"./localeText-TzABauzQ.js";import"./browser-Bjk3Qpx-.js";import"./galois-field-I2lBzzs-.js";const M={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},S={class:"container d-flex h-100 w-100"},D={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},L={class:"card rounded-3 shadow w-100"},P={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},B={class:"mb-0"},G={class:"card-body p-4 d-flex flex-column gap-3"},N={style:{height:"300px"},class:"d-flex"},V=["value"],j={key:0,class:"spinner-border m-auto",role:"status"},I={class:"d-flex"},W=["disabled"],$={key:0,class:"d-block"},q={key:1,class:"d-block",id:"check"},z={__name:"peerConfigurationFile",props:{selectedPeer:Object},emits:["close"],setup(u,{emit:p}){const m=p,f=u,r=g(),t=o(!1),l=o(""),a=o(!0);o({error:!1,message:void 0}),h(()=>{const d=x();y("/api/downloadPeer/"+d.params.id,{id:f.selectedPeer.id},e=>{e.status?(l.value=e.data.file,a.value=!1):this.dashboardStore.newMessage("Server",e.message,"danger")})});const b=async()=>{navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(l.value).then(()=>{t.value=!0,setTimeout(()=>{t.value=!1},3e3)}).catch(()=>{r.newMessage("WGDashboard","Failed to copy","danger")}):(document.querySelector("#peerConfigurationFile").select(),document.execCommand("copy")?(t.value=!0,setTimeout(()=>{t.value=!1},3e3)):r.newMessage("WGDashboard","Failed to copy","danger"))};return(d,e)=>(n(),i("div",M,[s("div",S,[s("div",D,[s("div",L,[s("div",P,[s("h4",B,[c(T,{t:"Peer Configuration File"})]),s("button",{type:"button",class:"btn-close ms-auto",onClick:e[0]||(e[0]=_=>m("close"))})]),s("div",G,[s("div",N,[s("textarea",{style:{height:"300px"},class:C(["form-control w-100 rounded-3 animate__fadeIn animate__faster animate__animated",{"d-none":a.value}]),id:"peerConfigurationFile",value:l.value},null,10,V),a.value?(n(),i("div",j,[...e[2]||(e[2]=[s("span",{class:"visually-hidden"},"Loading...",-1)])])):w("",!0)]),s("div",I,[s("button",{onClick:e[1]||(e[1]=_=>b()),disabled:t.value||a.value,class:"ms-auto btn bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 position-relative"},[c(F,{name:"slide-up",mode:"out-in"},{default:k(()=>[t.value?(n(),i("span",q,[...e[4]||(e[4]=[s("i",{class:"bi bi-check-circle-fill"},null,-1)])])):(n(),i("span",$,[...e[3]||(e[3]=[s("i",{class:"bi bi-clipboard-fill"},null,-1)])]))]),_:1})],8,W)])])])])])]))}},H=v(z,[["__scopeId","data-v-b0ea2d46"]]);export{H as default};
|
||||
import{_ as v,D as g,r as o,o as h,L as x,g as y,c as i,f as n,a as s,b as c,d as w,n as C,w as k,k as F}from"./index-DXzxfcZW.js";import{L as T}from"./localeText-Dmcj5qqx.js";import"./browser-DFwZaPoQ.js";import"./galois-field-I2lBzzs-.js";const M={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},S={class:"container d-flex h-100 w-100"},D={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},L={class:"card rounded-3 shadow w-100"},P={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},B={class:"mb-0"},G={class:"card-body p-4 d-flex flex-column gap-3"},N={style:{height:"300px"},class:"d-flex"},V=["value"],j={key:0,class:"spinner-border m-auto",role:"status"},I={class:"d-flex"},W=["disabled"],$={key:0,class:"d-block"},q={key:1,class:"d-block",id:"check"},z={__name:"peerConfigurationFile",props:{selectedPeer:Object},emits:["close"],setup(u,{emit:p}){const m=p,f=u,r=g(),t=o(!1),l=o(""),a=o(!0);o({error:!1,message:void 0}),h(()=>{const d=x();y("/api/downloadPeer/"+d.params.id,{id:f.selectedPeer.id},e=>{e.status?(l.value=e.data.file,a.value=!1):this.dashboardStore.newMessage("Server",e.message,"danger")})});const b=async()=>{navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(l.value).then(()=>{t.value=!0,setTimeout(()=>{t.value=!1},3e3)}).catch(()=>{r.newMessage("WGDashboard","Failed to copy","danger")}):(document.querySelector("#peerConfigurationFile").select(),document.execCommand("copy")?(t.value=!0,setTimeout(()=>{t.value=!1},3e3)):r.newMessage("WGDashboard","Failed to copy","danger"))};return(d,e)=>(n(),i("div",M,[s("div",S,[s("div",D,[s("div",L,[s("div",P,[s("h4",B,[c(T,{t:"Peer Configuration File"})]),s("button",{type:"button",class:"btn-close ms-auto",onClick:e[0]||(e[0]=_=>m("close"))})]),s("div",G,[s("div",N,[s("textarea",{style:{height:"300px"},class:C(["form-control w-100 rounded-3 animate__fadeIn animate__faster animate__animated",{"d-none":a.value}]),id:"peerConfigurationFile",value:l.value},null,10,V),a.value?(n(),i("div",j,[...e[2]||(e[2]=[s("span",{class:"visually-hidden"},"Loading...",-1)])])):w("",!0)]),s("div",I,[s("button",{onClick:e[1]||(e[1]=_=>b()),disabled:t.value||a.value,class:"ms-auto btn bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 position-relative"},[c(F,{name:"slide-up",mode:"out-in"},{default:k(()=>[t.value?(n(),i("span",q,[...e[4]||(e[4]=[s("i",{class:"bi bi-check-circle-fill"},null,-1)])])):(n(),i("span",$,[...e[3]||(e[3]=[s("i",{class:"bi bi-clipboard-fill"},null,-1)])]))]),_:1})],8,W)])])])])])]))}},H=v(z,[["__scopeId","data-v-b0ea2d46"]]);export{H as default};
|
||||
@@ -1 +1 @@
|
||||
import{L as o}from"./localeText-TzABauzQ.js";import{P as t}from"./peersDefaultSettingsInput-B_ngK8i0.js";import{B as s,c as l,a,b as e,f as n}from"./index-CmClDcBF.js";const r={class:"d-flex gap-3 flex-column"},i={class:"card rounded-3"},d={class:"card-header"},c={class:"my-2"},_={class:"card-body"},D=s({__name:"peerDefaultSettings",setup(p){return(g,m)=>(n(),l("div",r,[a("div",i,[a("div",d,[a("h6",c,[e(o,{t:"Peer Default Settings"})])]),a("div",_,[a("div",null,[e(t,{targetData:"peer_global_dns",title:"DNS"}),e(t,{targetData:"peer_endpoint_allowed_ip",title:"Endpoint Allowed IPs"}),e(t,{targetData:"peer_mtu",title:"MTU"}),e(t,{targetData:"peer_keep_alive",title:"Persistent Keepalive"}),e(t,{targetData:"remote_endpoint",title:"Peer Remote Endpoint",warning:!0,warningText:"This will be changed globally, and will be apply to all peer's QR code and configuration file."})])])])]))}});export{D as default};
|
||||
import{L as o}from"./localeText-Dmcj5qqx.js";import{P as t}from"./peersDefaultSettingsInput-KXSGcg6g.js";import{B as s,c as l,a,b as e,f as n}from"./index-DXzxfcZW.js";const r={class:"d-flex gap-3 flex-column"},i={class:"card rounded-3"},d={class:"card-header"},c={class:"my-2"},_={class:"card-body"},D=s({__name:"peerDefaultSettings",setup(p){return(g,m)=>(n(),l("div",r,[a("div",i,[a("div",d,[a("h6",c,[e(o,{t:"Peer Default Settings"})])]),a("div",_,[a("div",null,[e(t,{targetData:"peer_global_dns",title:"DNS"}),e(t,{targetData:"peer_endpoint_allowed_ip",title:"Endpoint Allowed IPs"}),e(t,{targetData:"peer_mtu",title:"MTU"}),e(t,{targetData:"peer_keep_alive",title:"Persistent Keepalive"}),e(t,{targetData:"remote_endpoint",title:"Peer Remote Endpoint",warning:!0,warningText:"This will be changed globally, and will be apply to all peer's QR code and configuration file."})])])])]))}});export{D as default};
|
||||
@@ -1 +1 @@
|
||||
import{a as p,S as b}from"./schedulePeerJob-u-FqcbUM.js";import{_ as h,h as i,c as a,f as s,a as e,b as r,w as u,d as m,F as _,i as f,j as v,T as J,A as x,W as g}from"./index-CmClDcBF.js";import{L as w}from"./localeText-TzABauzQ.js";import"./vue-datepicker-BYHO-v3J.js";import"./index-DZpKhbG8.js";import"./dayjs.min-DFlK6ZAc.js";const P={name:"peerJobs",setup(){return{store:g()}},props:{selectedPeer:Object},components:{LocaleText:w,SchedulePeerJob:b,ScheduleDropdown:p},data(){return{}},methods:{deleteJob(d){this.selectedPeer.jobs=this.selectedPeer.jobs.filter(t=>t.JobID!==d.JobID)},addJob(){this.selectedPeer.jobs.unshift(JSON.parse(JSON.stringify({JobID:x().toString(),Configuration:this.selectedPeer.configuration.Name,Peer:this.selectedPeer.id,Field:this.store.PeerScheduleJobs.dropdowns.Field[0].value,Operator:this.store.PeerScheduleJobs.dropdowns.Operator[0].value,Value:"",CreationDate:"",ExpireDate:"",Action:this.store.PeerScheduleJobs.dropdowns.Action[0].value})))}}},S={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},y={class:"container d-flex h-100 w-100"},$={class:"m-auto modal-dialog-centered dashboardModal"},C={class:"card rounded-3 shadow",style:{width:"700px"}},D={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},k={class:"card-body px-4 pb-4 pt-2 position-relative"},T={class:"d-flex align-items-center mb-3"},N={class:"card shadow-sm",key:"none",style:{height:"153px"}},I={class:"card-body text-muted text-center d-flex"},L={class:"m-auto"};function O(d,t,B,F,V,A){const n=i("LocaleText"),l=i("SchedulePeerJob");return s(),a("div",S,[e("div",y,[e("div",$,[e("div",C,[e("div",D,[e("h4",j,[r(n,{t:"Schedule Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=o=>this.$emit("close"))})]),e("div",k,[e("div",T,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow",onClick:t[1]||(t[1]=o=>this.addJob())},[t[3]||(t[3]=e("i",{class:"bi bi-plus-lg me-2"},null,-1)),r(n,{t:"Job"})])]),r(J,{name:"schedulePeerJobTransition",tag:"div",class:"position-relative"},{default:u(()=>[(s(!0),a(_,null,f(this.selectedPeer.jobs,(o,E)=>(s(),v(l,{onRefresh:t[2]||(t[2]=c=>this.$emit("refresh")),onDelete:c=>this.deleteJob(o),dropdowns:this.store.PeerScheduleJobs.dropdowns,key:o.JobID,pjob:o},null,8,["onDelete","dropdowns","pjob"]))),128)),this.selectedPeer.jobs.length===0?(s(),a("div",N,[e("div",I,[e("h6",L,[r(n,{t:"This peer does not have any job yet."})])])])):m("",!0)]),_:1})])])])])])}const H=h(P,[["render",O],["__scopeId","data-v-5bbdd42b"]]);export{H as default};
|
||||
import{a as p,S as b}from"./schedulePeerJob-DOBEE-kC.js";import{_ as h,h as i,c as a,f as s,a as e,b as r,w as u,d as m,F as _,i as f,j as v,T as J,A as x,W as g}from"./index-DXzxfcZW.js";import{L as w}from"./localeText-Dmcj5qqx.js";import"./vue-datepicker-vren6E8j.js";import"./index-CRsyV-e7.js";import"./dayjs.min-C-brjxlJ.js";const P={name:"peerJobs",setup(){return{store:g()}},props:{selectedPeer:Object},components:{LocaleText:w,SchedulePeerJob:b,ScheduleDropdown:p},data(){return{}},methods:{deleteJob(d){this.selectedPeer.jobs=this.selectedPeer.jobs.filter(t=>t.JobID!==d.JobID)},addJob(){this.selectedPeer.jobs.unshift(JSON.parse(JSON.stringify({JobID:x().toString(),Configuration:this.selectedPeer.configuration.Name,Peer:this.selectedPeer.id,Field:this.store.PeerScheduleJobs.dropdowns.Field[0].value,Operator:this.store.PeerScheduleJobs.dropdowns.Operator[0].value,Value:"",CreationDate:"",ExpireDate:"",Action:this.store.PeerScheduleJobs.dropdowns.Action[0].value})))}}},S={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},y={class:"container d-flex h-100 w-100"},$={class:"m-auto modal-dialog-centered dashboardModal"},C={class:"card rounded-3 shadow",style:{width:"700px"}},D={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},k={class:"card-body px-4 pb-4 pt-2 position-relative"},T={class:"d-flex align-items-center mb-3"},N={class:"card shadow-sm",key:"none",style:{height:"153px"}},I={class:"card-body text-muted text-center d-flex"},L={class:"m-auto"};function O(d,t,B,F,V,A){const n=i("LocaleText"),l=i("SchedulePeerJob");return s(),a("div",S,[e("div",y,[e("div",$,[e("div",C,[e("div",D,[e("h4",j,[r(n,{t:"Schedule Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=o=>this.$emit("close"))})]),e("div",k,[e("div",T,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow",onClick:t[1]||(t[1]=o=>this.addJob())},[t[3]||(t[3]=e("i",{class:"bi bi-plus-lg me-2"},null,-1)),r(n,{t:"Job"})])]),r(J,{name:"schedulePeerJobTransition",tag:"div",class:"position-relative"},{default:u(()=>[(s(!0),a(_,null,f(this.selectedPeer.jobs,(o,E)=>(s(),v(l,{onRefresh:t[2]||(t[2]=c=>this.$emit("refresh")),onDelete:c=>this.deleteJob(o),dropdowns:this.store.PeerScheduleJobs.dropdowns,key:o.JobID,pjob:o},null,8,["onDelete","dropdowns","pjob"]))),128)),this.selectedPeer.jobs.length===0?(s(),a("div",N,[e("div",I,[e("h6",L,[r(n,{t:"This peer does not have any job yet."})])])])):m("",!0)]),_:1})])])])])])}const H=h(P,[["render",O],["__scopeId","data-v-5bbdd42b"]]);export{H as default};
|
||||
@@ -1 +1 @@
|
||||
import{S as _}from"./schedulePeerJob-u-FqcbUM.js";import{_ as g,h as c,c as r,f as t,a as e,b as l,F as p,i as b,d as f,t as m,j as v,W as y}from"./index-CmClDcBF.js";import{L as x}from"./localeText-TzABauzQ.js";import"./vue-datepicker-BYHO-v3J.js";import"./index-DZpKhbG8.js";import"./dayjs.min-DFlK6ZAc.js";const J={name:"peerJobsAllModal",setup(){return{store:y()}},components:{LocaleText:x,SchedulePeerJob:_},props:{configurationPeers:Array[Object]},computed:{getAllJobs(){return this.configurationPeers.filter(a=>a.jobs.length>0)}}},w={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},$={class:"container d-flex h-100 w-100"},k={class:"m-auto modal-dialog-centered dashboardModal"},A={class:"card rounded-3 shadow",style:{width:"900px"}},L={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},S={class:"mb-0 fw-normal"},j={class:"card-body px-4 pb-4 pt-2"},C={key:0,class:"accordion",id:"peerJobsLogsModalAccordion"},P={class:"accordion-header"},M=["data-bs-target"],B={key:0},N={class:"text-muted"},D=["id"],T={class:"accordion-body"},V={key:1,class:"card shadow-sm",style:{height:"153px"}},F={class:"card-body text-muted text-center d-flex"},O={class:"m-auto"};function W(a,o,E,I,R,q){const n=c("LocaleText"),u=c("SchedulePeerJob");return t(),r("div",w,[e("div",$,[e("div",k,[e("div",A,[e("div",L,[e("h4",S,[l(n,{t:"All Active Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:o[0]||(o[0]=s=>this.$emit("close"))})]),e("div",j,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow mb-2",onClick:o[1]||(o[1]=s=>this.$emit("allLogs"))},[o[4]||(o[4]=e("i",{class:"bi bi-clock me-2"},null,-1)),l(n,{t:"Logs"})]),this.getAllJobs.length>0?(t(),r("div",C,[(t(!0),r(p,null,b(this.getAllJobs,(s,d)=>(t(),r("div",{class:"accordion-item",key:s.id},[e("h2",P,[e("button",{class:"accordion-button collapsed",type:"button","data-bs-toggle":"collapse","data-bs-target":"#collapse_"+d},[e("small",null,[e("strong",null,[s.name?(t(),r("span",B,m(s.name)+" • ",1)):f("",!0),e("samp",N,m(s.id),1)])])],8,M)]),e("div",{id:"collapse_"+d,class:"accordion-collapse collapse","data-bs-parent":"#peerJobsLogsModalAccordion"},[e("div",T,[(t(!0),r(p,null,b(s.jobs,i=>(t(),v(u,{onDelete:o[2]||(o[2]=h=>this.$emit("refresh")),onRefresh:o[3]||(o[3]=h=>this.$emit("refresh")),dropdowns:this.store.PeerScheduleJobs.dropdowns,viewOnly:!0,key:i.JobID,pjob:i},null,8,["dropdowns","pjob"]))),128))])],8,D)]))),128))])):(t(),r("div",V,[e("div",F,[e("span",O,[l(n,{t:"No active job at the moment."})])])]))])])])])])}const X=g(J,[["render",W]]);export{X as default};
|
||||
import{S as _}from"./schedulePeerJob-DOBEE-kC.js";import{_ as g,h as c,c as r,f as t,a as e,b as l,F as p,i as b,d as f,t as m,j as v,W as y}from"./index-DXzxfcZW.js";import{L as x}from"./localeText-Dmcj5qqx.js";import"./vue-datepicker-vren6E8j.js";import"./index-CRsyV-e7.js";import"./dayjs.min-C-brjxlJ.js";const J={name:"peerJobsAllModal",setup(){return{store:y()}},components:{LocaleText:x,SchedulePeerJob:_},props:{configurationPeers:Array[Object]},computed:{getAllJobs(){return this.configurationPeers.filter(a=>a.jobs.length>0)}}},w={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},$={class:"container d-flex h-100 w-100"},k={class:"m-auto modal-dialog-centered dashboardModal"},A={class:"card rounded-3 shadow",style:{width:"900px"}},L={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},S={class:"mb-0 fw-normal"},j={class:"card-body px-4 pb-4 pt-2"},C={key:0,class:"accordion",id:"peerJobsLogsModalAccordion"},P={class:"accordion-header"},M=["data-bs-target"],B={key:0},N={class:"text-muted"},D=["id"],T={class:"accordion-body"},V={key:1,class:"card shadow-sm",style:{height:"153px"}},F={class:"card-body text-muted text-center d-flex"},O={class:"m-auto"};function W(a,o,E,I,R,q){const n=c("LocaleText"),u=c("SchedulePeerJob");return t(),r("div",w,[e("div",$,[e("div",k,[e("div",A,[e("div",L,[e("h4",S,[l(n,{t:"All Active Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:o[0]||(o[0]=s=>this.$emit("close"))})]),e("div",j,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow mb-2",onClick:o[1]||(o[1]=s=>this.$emit("allLogs"))},[o[4]||(o[4]=e("i",{class:"bi bi-clock me-2"},null,-1)),l(n,{t:"Logs"})]),this.getAllJobs.length>0?(t(),r("div",C,[(t(!0),r(p,null,b(this.getAllJobs,(s,d)=>(t(),r("div",{class:"accordion-item",key:s.id},[e("h2",P,[e("button",{class:"accordion-button collapsed",type:"button","data-bs-toggle":"collapse","data-bs-target":"#collapse_"+d},[e("small",null,[e("strong",null,[s.name?(t(),r("span",B,m(s.name)+" • ",1)):f("",!0),e("samp",N,m(s.id),1)])])],8,M)]),e("div",{id:"collapse_"+d,class:"accordion-collapse collapse","data-bs-parent":"#peerJobsLogsModalAccordion"},[e("div",T,[(t(!0),r(p,null,b(s.jobs,i=>(t(),v(u,{onDelete:o[2]||(o[2]=h=>this.$emit("refresh")),onRefresh:o[3]||(o[3]=h=>this.$emit("refresh")),dropdowns:this.store.PeerScheduleJobs.dropdowns,viewOnly:!0,key:i.JobID,pjob:i},null,8,["dropdowns","pjob"]))),128))])],8,D)]))),128))])):(t(),r("div",V,[e("div",F,[e("span",O,[l(n,{t:"No active job at the moment."})])])]))])])])])])}const X=g(J,[["render",W]]);export{X as default};
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
.icon[data-v-3c48f50e]{flex:1;min-width:30px;max-width:30px;width:30px;aspect-ratio:1 / 1}.icon[data-v-accdf15e]{flex:1;aspect-ratio:1 / 1}#peerTag[data-v-ab3e5c4e]{width:300px;position:absolute;right:0;z-index:9999;margin-top:2px}.animation__fadeInDropdown[data-v-71502547]{animation-name:fadeInDropdown-71502547;animation-duration:.2s;animation-timing-function:cubic-bezier(.82,.58,.17,.9)}@keyframes fadeInDropdown-71502547{0%{opacity:0;filter:blur(3px);transform:translateY(-60px)}to{opacity:1;filter:blur(0px);transform:translateY(-40px)}}.displayModal .dashboardModal[data-v-71502547]{width:400px!important}@media screen and (max-width: 992px){.peerSearchContainer[data-v-71502547]{flex-direction:column}.peerSettingContainer .dashboardModal[data-v-71502547]{width:100%!important}}.peerSearchContainer>button[data-v-71502547],.peerSearchContainer .dropdown>button[data-v-71502547]{text-align:left;display:flex;align-items:center}span[data-v-d4e41a56]{top:-34px;left:0}.dropdown-menu[data-v-18549c26]{right:0;min-width:200px}.dropdown-menu.dropup[data-v-18549c26]{bottom:100%}.dropdown-item.disabled[data-v-18549c26],.dropdown-item[data-v-18549c26]:disabled{opacity:.7}.confirmDelete[data-v-18549c26]{padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x)}.subMenuBtn.active[data-v-06609b08]{background-color:#ffffff20}.peerCard[data-v-06609b08]{transition:box-shadow .1s cubic-bezier(.82,.58,.17,.9)}.peerCard[data-v-06609b08]:hover{box-shadow:var(--bs-box-shadow)!important}@media screen and (max-width: 992px){.calendar-day .session-badge-list[data-v-5178a57b],.sessions-label[data-v-5178a57b]{display:none}}.session-list[data-v-5178a57b]{aspect-ratio:1 / 1}@media screen and (min-width: 992px){.session-dot[data-v-5178a57b]{display:none!important}.session-list[data-v-5178a57b]{height:12.5vh;overflow:scroll;aspect-ratio:auto!important}}.calendar-grid[data-v-3b03c7a5]{display:grid;grid-template-areas:"sun mon tue wed thu fri sat";grid-template-columns:repeat(7,1fr)}.calendar-day.day-6[data-v-3b03c7a5]{border-right:none!important}.calendar-day[data-v-3b03c7a5]{min-height:150px}@media screen and (max-width: 992px){.calendar-day[data-v-3b03c7a5]{min-height:100px!important}}@media screen and (min-width: 992px){.dayDetail[data-v-3b03c7a5]{display:none}}.extra-day .day-label[data-v-3b03c7a5]{opacity:.5}.peerNav .nav-link{&.active[data-v-b4fba9bc]{background-color:#efefef}}th[data-v-b4fba9bc],td[data-v-b4fba9bc]{background-color:transparent!important}@media screen and (max-width: 576px){.titleBtn[data-v-b4fba9bc]{flex-basis:100%}}
|
||||
.icon[data-v-3c48f50e]{flex:1;min-width:30px;max-width:30px;width:30px;aspect-ratio:1 / 1}.icon[data-v-accdf15e]{flex:1;aspect-ratio:1 / 1}#peerTag[data-v-ab3e5c4e]{width:300px;position:absolute;right:0;z-index:9999;margin-top:2px}.animation__fadeInDropdown[data-v-71502547]{animation-name:fadeInDropdown-71502547;animation-duration:.2s;animation-timing-function:cubic-bezier(.82,.58,.17,.9)}@keyframes fadeInDropdown-71502547{0%{opacity:0;filter:blur(3px);transform:translateY(-60px)}to{opacity:1;filter:blur(0px);transform:translateY(-40px)}}.displayModal .dashboardModal[data-v-71502547]{width:400px!important}@media screen and (max-width: 992px){.peerSearchContainer[data-v-71502547]{flex-direction:column}.peerSettingContainer .dashboardModal[data-v-71502547]{width:100%!important}}.peerSearchContainer>button[data-v-71502547],.peerSearchContainer .dropdown>button[data-v-71502547]{text-align:left;display:flex;align-items:center}span[data-v-d4e41a56]{top:-34px;left:0}.dropdown-menu[data-v-18549c26]{right:0;min-width:200px}.dropdown-menu.dropup[data-v-18549c26]{bottom:100%}.dropdown-item.disabled[data-v-18549c26],.dropdown-item[data-v-18549c26]:disabled{opacity:.7}.confirmDelete[data-v-18549c26]{padding:var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x)}.subMenuBtn.active[data-v-f38d3291]{background-color:#ffffff20}.peerCard[data-v-f38d3291]{transition:box-shadow .1s cubic-bezier(.82,.58,.17,.9)}.peerCard[data-v-f38d3291]:hover{box-shadow:var(--bs-box-shadow)!important}@media screen and (max-width: 992px){.calendar-day .session-badge-list[data-v-5178a57b],.sessions-label[data-v-5178a57b]{display:none}}.session-list[data-v-5178a57b]{aspect-ratio:1 / 1}@media screen and (min-width: 992px){.session-dot[data-v-5178a57b]{display:none!important}.session-list[data-v-5178a57b]{height:12.5vh;overflow:scroll;aspect-ratio:auto!important}}.calendar-grid[data-v-3b03c7a5]{display:grid;grid-template-areas:"sun mon tue wed thu fri sat";grid-template-columns:repeat(7,1fr)}.calendar-day.day-6[data-v-3b03c7a5]{border-right:none!important}.calendar-day[data-v-3b03c7a5]{min-height:150px}@media screen and (max-width: 992px){.calendar-day[data-v-3b03c7a5]{min-height:100px!important}}@media screen and (min-width: 992px){.dayDetail[data-v-3b03c7a5]{display:none}}.extra-day .day-label[data-v-3b03c7a5]{opacity:.5}.peerNav .nav-link{&.active[data-v-b4fba9bc]{background-color:#efefef}}th[data-v-b4fba9bc],td[data-v-b4fba9bc]{background-color:transparent!important}@media screen and (max-width: 576px){.titleBtn[data-v-b4fba9bc]{flex-basis:100%}}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
import{Q as l}from"./browser-Bjk3Qpx-.js";import{L as _}from"./localeText-TzABauzQ.js";import{_ as h,h as f,c,f as s,a as e,b as p,d as i,j as m,n as u,g,D as v}from"./index-CmClDcBF.js";import"./galois-field-I2lBzzs-.js";const w={name:"peerQRCode",components:{LocaleText:_},props:{selectedPeer:Object},setup(){return{dashboardStore:v()}},data(){return{loading:!0}},mounted(){g("/api/downloadPeer/"+this.$route.params.id,{id:this.selectedPeer.id},o=>{if(this.loading=!1,o.status){let t="";if(this.selectedPeer.configuration.Protocol==="awg"){let a={containers:[{awg:{isThirdPartyConfig:!0,last_config:o.data.file,port:this.selectedPeer.configuration.ListenPort,transport_proto:"udp"},container:"amnezia-awg"}],defaultContainer:"amnezia-awg",description:this.selectedPeer.name,hostName:this.dashboardStore.Configuration.Peers.remote_endpoint};l.toCanvas(document.querySelector("#awg_vpn_qrcode"),btoa(JSON.stringify(a)),d=>{d&&console.error(d)})}t=o.data.file,l.toCanvas(document.querySelector("#qrcode"),t,a=>{a&&console.error(a)})}else this.dashboardStore.newMessage("Server",o.message,"danger")})}},b={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},x={class:"container d-flex h-100 w-100"},P={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},C={class:"card rounded-3 shadow"},y={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},S={class:"mb-0"},k={class:"card-body p-4"},q={class:"d-flex gap-2 flex-column"},L={class:"d-flex flex-column gap-2 align-items-center"},N={key:0,class:"d-flex flex-column gap-2 align-items-center"},Q={key:1,class:"spinner-border m-auto",role:"status"};function z(o,t,a,d,r,A){const n=f("LocaleText");return s(),c("div",b,[e("div",x,[e("div",P,[e("div",C,[e("div",y,[e("h4",S,[p(n,{t:"QR Code"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=R=>this.$emit("close"))})]),e("div",k,[e("div",q,[e("div",L,[e("canvas",{id:"qrcode",style:{width:"200px !important",height:"200px !important"},class:u(["rounded-3 shadow animate__animated animate__fadeIn animate__faster qrcode",{"d-none":r.loading}])},null,2),this.selectedPeer.configuration.Protocol==="wg"?(s(),m(n,{key:0,t:"Scan with WireGuard App",class:"text-muted"})):i("",!0),this.selectedPeer.configuration.Protocol==="awg"?(s(),m(n,{key:1,t:"Scan with AmneziaWG App",class:"text-muted"})):i("",!0)]),this.selectedPeer.configuration.Protocol==="awg"?(s(),c("div",N,[e("canvas",{id:"awg_vpn_qrcode",class:u(["rounded-3 shadow animate__animated animate__fadeIn animate__faster qrcode",{"d-none":r.loading}])},null,2),p(n,{t:"Scan with AmneziaVPN App",class:"text-muted"})])):i("",!0),r.loading?(s(),c("div",Q,[...t[1]||(t[1]=[e("span",{class:"visually-hidden"},"Loading...",-1)])])):i("",!0)])])])])])])}const $=h(w,[["render",z],["__scopeId","data-v-02f2240d"]]);export{$ as default};
|
||||
import{Q as l}from"./browser-DFwZaPoQ.js";import{L as _}from"./localeText-Dmcj5qqx.js";import{_ as h,h as f,c,f as s,a as e,b as p,d as i,j as m,n as u,g,D as v}from"./index-DXzxfcZW.js";import"./galois-field-I2lBzzs-.js";const w={name:"peerQRCode",components:{LocaleText:_},props:{selectedPeer:Object},setup(){return{dashboardStore:v()}},data(){return{loading:!0}},mounted(){g("/api/downloadPeer/"+this.$route.params.id,{id:this.selectedPeer.id},o=>{if(this.loading=!1,o.status){let t="";if(this.selectedPeer.configuration.Protocol==="awg"){let a={containers:[{awg:{isThirdPartyConfig:!0,last_config:o.data.file,port:this.selectedPeer.configuration.ListenPort,transport_proto:"udp"},container:"amnezia-awg"}],defaultContainer:"amnezia-awg",description:this.selectedPeer.name,hostName:this.dashboardStore.Configuration.Peers.remote_endpoint};l.toCanvas(document.querySelector("#awg_vpn_qrcode"),btoa(JSON.stringify(a)),d=>{d&&console.error(d)})}t=o.data.file,l.toCanvas(document.querySelector("#qrcode"),t,a=>{a&&console.error(a)})}else this.dashboardStore.newMessage("Server",o.message,"danger")})}},b={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},x={class:"container d-flex h-100 w-100"},P={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},C={class:"card rounded-3 shadow"},y={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},S={class:"mb-0"},k={class:"card-body p-4"},q={class:"d-flex gap-2 flex-column"},L={class:"d-flex flex-column gap-2 align-items-center"},N={key:0,class:"d-flex flex-column gap-2 align-items-center"},Q={key:1,class:"spinner-border m-auto",role:"status"};function z(o,t,a,d,r,A){const n=f("LocaleText");return s(),c("div",b,[e("div",x,[e("div",P,[e("div",C,[e("div",y,[e("h4",S,[p(n,{t:"QR Code"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=R=>this.$emit("close"))})]),e("div",k,[e("div",q,[e("div",L,[e("canvas",{id:"qrcode",style:{width:"200px !important",height:"200px !important"},class:u(["rounded-3 shadow animate__animated animate__fadeIn animate__faster qrcode",{"d-none":r.loading}])},null,2),this.selectedPeer.configuration.Protocol==="wg"?(s(),m(n,{key:0,t:"Scan with WireGuard App",class:"text-muted"})):i("",!0),this.selectedPeer.configuration.Protocol==="awg"?(s(),m(n,{key:1,t:"Scan with AmneziaWG App",class:"text-muted"})):i("",!0)]),this.selectedPeer.configuration.Protocol==="awg"?(s(),c("div",N,[e("canvas",{id:"awg_vpn_qrcode",class:u(["rounded-3 shadow animate__animated animate__fadeIn animate__faster qrcode",{"d-none":r.loading}])},null,2),p(n,{t:"Scan with AmneziaVPN App",class:"text-muted"})])):i("",!0),r.loading?(s(),c("div",Q,[...t[1]||(t[1]=[e("span",{class:"visually-hidden"},"Loading...",-1)])])):i("",!0)])])])])])])}const $=h(w,[["render",z],["__scopeId","data-v-02f2240d"]]);export{$ as default};
|
||||
@@ -1 +1 @@
|
||||
import{_ as p,q as m,G as f,W as h,r as u,a0 as _,L as v,K as g,o as x,a1 as S,c as y,d as b,f as B,a as s,m as w,y as T}from"./index-CmClDcBF.js";const q={key:0,class:"fixed-bottom w-100 bottom-0 z-2 p-3",style:{"z-index":"1"}},C={class:"d-flex flex-column searchPeersContainer ms-auto p-2 rounded-5",style:{width:"300px"}},P={class:"rounded-5 border border-white p-2 d-flex align-items-center gap-1 w-100"},R=["placeholder"],k={__name:"peerSearchBar",props:["ConfigurationInfo"],emits:["close"],setup(V,{emit:z}){const l=m(()=>f("Search Peers..."));let r;const t=h(),e=u(t.searchString),d=()=>{r?(clearTimeout(r),r=setTimeout(()=>{t.searchString=e.value},300)):r=setTimeout(()=>{t.searchString=e.value},300)};_("searchBar");const a=v(),i=g();a.query.peer&&(e.value=a.query.peer,i.replace({query:null}));const n=u(!0);return x(()=>{document.querySelector("#searchPeers").focus()}),S(()=>{n.value=!1}),(G,o)=>n.value?(B(),y("div",q,[s("div",C,[s("div",P,[w(s("input",{ref:"searchBar",class:"flex-grow-1 form-control form-control-sm rounded-5 bg-transparent border-0 border-secondary-subtle",placeholder:l.value,id:"searchPeers",onKeyup:o[0]||(o[0]=c=>d()),"onUpdate:modelValue":o[1]||(o[1]=c=>e.value=c)},null,40,R),[[T,e.value]])])])])):b("",!0)}},K=p(k,[["__scopeId","data-v-576347d8"]]);export{K as default};
|
||||
import{_ as p,q as m,G as f,W as h,r as u,a0 as _,L as v,K as g,o as x,a1 as S,c as y,d as b,f as B,a as s,m as w,y as T}from"./index-DXzxfcZW.js";const q={key:0,class:"fixed-bottom w-100 bottom-0 z-2 p-3",style:{"z-index":"1"}},C={class:"d-flex flex-column searchPeersContainer ms-auto p-2 rounded-5",style:{width:"300px"}},P={class:"rounded-5 border border-white p-2 d-flex align-items-center gap-1 w-100"},R=["placeholder"],k={__name:"peerSearchBar",props:["ConfigurationInfo"],emits:["close"],setup(V,{emit:z}){const l=m(()=>f("Search Peers..."));let r;const t=h(),e=u(t.searchString),d=()=>{r?(clearTimeout(r),r=setTimeout(()=>{t.searchString=e.value},300)):r=setTimeout(()=>{t.searchString=e.value},300)};_("searchBar");const a=v(),i=g();a.query.peer&&(e.value=a.query.peer,i.replace({query:null}));const n=u(!0);return x(()=>{document.querySelector("#searchPeers").focus()}),S(()=>{n.value=!1}),(G,o)=>n.value?(B(),y("div",q,[s("div",C,[s("div",P,[w(s("input",{ref:"searchBar",class:"flex-grow-1 form-control form-control-sm rounded-5 bg-transparent border-0 border-secondary-subtle",placeholder:l.value,id:"searchPeers",onKeyup:o[0]||(o[0]=c=>d()),"onUpdate:modelValue":o[1]||(o[1]=c=>e.value=c)},null,40,R),[[T,e.value]])])])])):b("",!0)}},K=p(k,[["__scopeId","data-v-576347d8"]]);export{K as default};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user