mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2026-10-30 09:22:05 +00:00
Compare commits
16
Commits
v4.3.3
...
development
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa13569d4b | ||
|
|
d4661a8f0f | ||
|
|
fefde651d2 | ||
|
|
e6f6201d25 | ||
|
|
c4cbbdb51b | ||
|
|
2df57f9405 | ||
|
|
0018a9b1e2 | ||
|
|
dded6a86df | ||
|
|
4ee6b462dc | ||
|
|
e27dd986bc | ||
|
|
e6448f2ffd | ||
|
|
e2b5a59291 | ||
|
+2 |
b83c0f85ba | ||
|
|
b6da11ec2d | ||
|
|
58fa826862 | ||
|
|
fedf7db8a4 |
+8
-3
@@ -1,9 +1,14 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
*.md
|
|
||||||
tests/
|
tests/
|
||||||
docs/
|
docs/
|
||||||
|
|
||||||
|
*.md
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
|
**/venv
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
|
||||||
src/db
|
src/db
|
||||||
src/wg-dashboard.ini
|
src/wg-dashboard.ini
|
||||||
src/static/app
|
|
||||||
src/static/client
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|
||||||
docker_scan:
|
docker_scan:
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ yarn-error.log*
|
|||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
|
dist
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist-ssr
|
dist-ssr
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
FROM golang:1.24 AS awg-go
|
|
||||||
|
|
||||||
RUN git clone https://github.com/WGDashboard/amneziawg-go /awg
|
|
||||||
WORKDIR /awg
|
|
||||||
RUN go mod download && \
|
|
||||||
go mod verify && \
|
|
||||||
go build -ldflags '-linkmode external -extldflags "-fno-PIC -static"' -v -o /usr/bin
|
|
||||||
|
|
||||||
FROM alpine:latest AS awg-tools
|
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache \
|
|
||||||
make git build-base linux-headers \
|
|
||||||
&& git clone https://github.com/WGDashboard/amneziawg-tools \
|
|
||||||
&& cd amneziawg-tools/src \
|
|
||||||
&& make \
|
|
||||||
&& chmod +x wg*
|
|
||||||
|
|
||||||
FROM alpine:latest
|
|
||||||
LABEL maintainer="dselen@nerthus.nl"
|
|
||||||
|
|
||||||
RUN apk update && apk add --no-cache \
|
|
||||||
iproute2 iptables bash curl wget unzip procps sudo \
|
|
||||||
tzdata wireguard-tools python3 py3-psutil py3-bcrypt openresolv
|
|
||||||
|
|
||||||
COPY --from=awg-go /usr/bin/amneziawg-go /usr/bin/amneziawg-go
|
|
||||||
COPY --from=awg-tools /amneziawg-tools/src/wg /usr/bin/awg
|
|
||||||
COPY --from=awg-tools /amneziawg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
|
|
||||||
|
|
||||||
# Declaring environment variables, change Peernet to an address you like, standard is a 24 bit subnet.
|
|
||||||
ARG wg_net="10.0.0.1" \
|
|
||||||
wg_port="51820"
|
|
||||||
|
|
||||||
# Following ENV variables are changable on container runtime because /entrypoint.sh handles that. See compose.yaml for more info.
|
|
||||||
ENV TZ="Europe/Amsterdam" \
|
|
||||||
global_dns="9.9.9.9" \
|
|
||||||
wgd_port="10086" \
|
|
||||||
public_ip=""
|
|
||||||
|
|
||||||
# Using WGDASH -- like wg_net functionally as a ARG command. But it is needed in entrypoint.sh so it needs to be exported as environment variable.
|
|
||||||
ENV WGDASH=/opt/wgdashboard
|
|
||||||
|
|
||||||
# Doing WireGuard Dashboard installation measures. Modify the git clone command to get the preferred version, with a specific branch for example.
|
|
||||||
RUN mkdir /data \
|
|
||||||
&& mkdir /configs \
|
|
||||||
&& mkdir -p ${WGDASH}/src \
|
|
||||||
&& mkdir -p /etc/amnezia/amneziawg
|
|
||||||
COPY ./src ${WGDASH}/src
|
|
||||||
|
|
||||||
# Generate basic WireGuard interface. Echoing the WireGuard interface config for readability, adjust if you want it for efficiency.
|
|
||||||
# Also setting the pipefail option, verbose: https://github.com/hadolint/hadolint/wiki/DL4006.
|
|
||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
|
||||||
RUN out_adapt=$(ip -o -4 route show to default | awk '{print $NF}') \
|
|
||||||
&& echo -e "[Interface]\n\
|
|
||||||
Address = ${wg_net}/24\n\
|
|
||||||
PrivateKey =\n\
|
|
||||||
PostUp = iptables -t nat -I POSTROUTING 1 -s ${wg_net}/24 -o ${out_adapt} -j MASQUERADE\n\
|
|
||||||
PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP\n\
|
|
||||||
PreDown = iptables -t nat -D POSTROUTING -s ${wg_net}/24 -o ${out_adapt} -j MASQUERADE\n\
|
|
||||||
PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP\n\
|
|
||||||
ListenPort = ${wg_port}\n\
|
|
||||||
SaveConfig = true\n\
|
|
||||||
DNS = ${global_dns}" > /configs/wg0.conf.template \
|
|
||||||
&& chmod 600 /configs/wg0.conf.template
|
|
||||||
|
|
||||||
# Defining a way for Docker to check the health of the container. In this case: checking the gunicorn process.
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
||||||
CMD sh -c 'pgrep gunicorn > /dev/null && pgrep tail > /dev/null' || exit 1
|
|
||||||
|
|
||||||
# Copy the basic entrypoint.sh script.
|
|
||||||
COPY ./docker/entrypoint.sh /entrypoint.sh
|
|
||||||
|
|
||||||
# Exposing the default WireGuard Dashboard port for web access.
|
|
||||||
EXPOSE 10086
|
|
||||||
WORKDIR $WGDASH
|
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
|
||||||
+42
-9
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Pull the current golang-alpine image.
|
# Pull the current golang-alpine image.
|
||||||
FROM golang:1.26-alpine3.23 AS awg-go
|
FROM golang:1.27-alpine3.24 AS awg-go-builder
|
||||||
|
|
||||||
# Install build-dependencies.
|
# Install build-dependencies.
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
@@ -30,7 +30,7 @@ RUN go version && \
|
|||||||
# AWG TOOLS BUILDING STAGE
|
# AWG TOOLS BUILDING STAGE
|
||||||
# Base: Alpine
|
# Base: Alpine
|
||||||
#
|
#
|
||||||
FROM alpine:3.23 AS awg-tools
|
FROM alpine:3.24 AS awg-tools-builder
|
||||||
|
|
||||||
# Install needed dependencies.
|
# Install needed dependencies.
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
@@ -55,7 +55,7 @@ RUN make && chmod +x wg*
|
|||||||
#
|
#
|
||||||
|
|
||||||
# Use the python-alpine image for building pip dependencies
|
# Use the python-alpine image for building pip dependencies
|
||||||
FROM python:3.14-alpine3.23 AS pip-builder
|
FROM python:3.15-alpine3.24 AS pip-builder
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
@@ -85,13 +85,32 @@ RUN . /opt/wgdashboard/src/venv/bin/activate && \
|
|||||||
pip3 install --upgrade pip && \
|
pip3 install --upgrade pip && \
|
||||||
pip3 install -r /opt/wgdashboard/src/requirements.txt
|
pip3 install -r /opt/wgdashboard/src/requirements.txt
|
||||||
|
|
||||||
|
#
|
||||||
|
# WGDashboard FRONTEND BUILDING
|
||||||
|
# Base: Node Alpine
|
||||||
|
#
|
||||||
|
FROM node:26-alpine3.24 AS frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
# Copy frontend source, package files and lockfiles
|
||||||
|
COPY ./src/static/ ./
|
||||||
|
|
||||||
|
# Build the admin frontend
|
||||||
|
WORKDIR /workspace/app
|
||||||
|
RUN npm ci && npm run build
|
||||||
|
|
||||||
|
# Build the client frontend
|
||||||
|
WORKDIR /workspace/client
|
||||||
|
RUN npm ci && npm run build
|
||||||
|
|
||||||
#
|
#
|
||||||
# WGDashboard RUNNING STAGE
|
# WGDashboard RUNNING STAGE
|
||||||
# Base: Alpine
|
# Base: Alpine
|
||||||
#
|
#
|
||||||
|
|
||||||
# Running with the python-alpine image.
|
# Running with the python-alpine image.
|
||||||
FROM python:3.14-alpine3.23 AS final
|
FROM python:3.15-alpine3.24 AS final
|
||||||
LABEL maintainer="dselen@nerthus.nl"
|
LABEL maintainer="dselen@nerthus.nl"
|
||||||
|
|
||||||
# Install only the runtime dependencies
|
# Install only the runtime dependencies
|
||||||
@@ -102,9 +121,9 @@ RUN apk add --no-cache \
|
|||||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
# Copy only the final binaries from the AWG builder stages
|
# Copy only the final binaries from the AWG builder stages
|
||||||
COPY --from=awg-go /usr/bin/amneziawg-go /usr/bin/amneziawg-go
|
COPY --from=awg-go-builder /usr/bin/amneziawg-go /usr/bin/amneziawg-go
|
||||||
COPY --from=awg-tools /workspace/awg-tools/src/wg /usr/bin/awg
|
COPY --from=awg-tools-builder /workspace/awg-tools/src/wg /usr/bin/awg
|
||||||
COPY --from=awg-tools /workspace/awg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
|
COPY --from=awg-tools-builder /workspace/awg-tools/src/wg-quick/linux.bash /usr/bin/awg-quick
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
ARG wg_net="10.0.0.1"
|
ARG wg_net="10.0.0.1"
|
||||||
@@ -121,11 +140,25 @@ ENV TZ="Europe/Amsterdam" \
|
|||||||
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
|
&& echo "name_servers=${global_dns}" >> /etc/resolvconf.conf
|
||||||
|
|
||||||
# Copy the venv and source files from local compiled locations or repos
|
# Copy application source
|
||||||
COPY ./src ${WGDASH}/src
|
COPY ./src ${WGDASH}/src
|
||||||
COPY --from=pip-builder /opt/wgdashboard/src/venv /opt/wgdashboard/src/venv
|
|
||||||
|
# Copy Python dependencies
|
||||||
|
COPY --from=pip-builder \
|
||||||
|
/opt/wgdashboard/src/venv \
|
||||||
|
${WGDASH}/src/venv
|
||||||
|
|
||||||
|
RUN rm -rf ${WGDASH}/src/static/dist \
|
||||||
|
${WGDASH}/src/static/app \
|
||||||
|
${WGDASH}/src/static/client
|
||||||
|
|
||||||
|
COPY --from=frontend-builder \
|
||||||
|
/workspace/dist/ \
|
||||||
|
${WGDASH}/src/static/dist/
|
||||||
|
|
||||||
COPY ./docker/wg0.conf.template /tmp/wg0.conf.template
|
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 ./docker/wg-dashboard-oidc-providers.json.template /tmp/wg-dashboard-oidc-providers.json.template
|
||||||
|
|
||||||
# Copy in the runtime script, essential.
|
# Copy in the runtime script, essential.
|
||||||
COPY ./docker/entrypoint.sh /entrypoint.sh
|
COPY ./docker/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
|||||||
+70
-1
@@ -105,7 +105,7 @@ Updating the WGDashboard container should be through 'The Docker Way' - by pulli
|
|||||||
| `username` | Any non‐empty string | `-` | `admin` | Username for the WGDashboard web interface account. |
|
| `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). |
|
| `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. |
|
| `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. |
|
| `wg_autostart` | Wireguard interface name | `- ` | `wg0` | Automatically start the specified WireGuard interface when the container starts |
|
||||||
| `email_server` | SMTP server address | `-` | `smtp.gmail.com` | SMTP server for sending email notifications. |
|
| `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_port` | SMTP port number | `-` | `587` | Port for connecting to the SMTP server. |
|
||||||
| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. |
|
| `email_encryption` | `TLS`, `SSL`, etc. | `-` | `TLS` | Encryption method for email communication. |
|
||||||
@@ -213,6 +213,75 @@ ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
|||||||
- Access the web interface via `http://your-ip:10086` (or whichever port you specified in the compose).
|
- Access the web interface via `http://your-ip:10086` (or whichever port you specified in the compose).
|
||||||
- The first time run will auto-generate WireGuard keys and configs (configs are generated from the template).
|
- The first time run will auto-generate WireGuard keys and configs (configs are generated from the template).
|
||||||
|
|
||||||
|
## 🧑💻 Local Development with Docker
|
||||||
|
|
||||||
|
You can develop against WGDashboard locally by mounting the `src/` directory into the container. This lets you edit Python and frontend code on your host and see changes reflected immediately (with a service restart for Python).
|
||||||
|
|
||||||
|
Create a `docker/compose-local.yaml` alongside the existing `compose.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
wgdashboard:
|
||||||
|
image: ghcr.io/wgdashboard/wgdashboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: wgdashboard
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- 10086:10086/tcp
|
||||||
|
- 51820:51820/udp
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- aconf:/etc/amnezia/amneziawg
|
||||||
|
- conf:/etc/wireguard
|
||||||
|
- data:/data
|
||||||
|
# Mount local src for live editing
|
||||||
|
- ../src:/opt/wgdashboard/src
|
||||||
|
# Keep venv in a named volume so it isn't overwritten by the mount
|
||||||
|
- venv:/opt/wgdashboard/src/venv
|
||||||
|
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
aconf:
|
||||||
|
conf:
|
||||||
|
data:
|
||||||
|
venv:
|
||||||
|
```
|
||||||
|
|
||||||
|
The key additions compared to the production compose file:
|
||||||
|
- `../src:/opt/wgdashboard/src` — mounts your local `src/` directory into the container so code changes are reflected without rebuilding the image.
|
||||||
|
- `venv:/opt/wgdashboard/src/venv` — keeps the Python virtual environment in a named Docker volume. Without this, the host mount would overwrite the venv created during image build.
|
||||||
|
|
||||||
|
To start the development container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docker
|
||||||
|
docker compose -f compose-local.yaml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
After editing Python files (e.g. `src/dashboard.py`), restart the container to pick up changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker restart wgdashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
For frontend changes, install dependencies and rebuild the Vue app on your host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd src/static/app
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart the container so it serves the updated dist files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker restart wgdashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Closing remarks:
|
## Closing remarks:
|
||||||
|
|
||||||
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.
|
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.
|
||||||
|
|||||||
+1
-1
@@ -100,7 +100,7 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
|
|||||||
date = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
|
date = datetime.datetime.now(tz=datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
|
||||||
|
|
||||||
emailSender = EmailSender(dashboardConfig)
|
emailSender = EmailSender(dashboardConfig)
|
||||||
if not emailSender.ready():
|
if not emailSender.is_ready():
|
||||||
return ResponseObject(False, "We can't send you an email due to your Administrator has not setup email service. Please contact your administrator.")
|
return ResponseObject(False, "We can't send you an email due to your Administrator has not setup email service. Please contact your administrator.")
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|||||||
+191
-74
@@ -2,12 +2,13 @@ import logging
|
|||||||
import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess
|
import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess
|
||||||
import time, re, uuid, bcrypt, psutil, pyotp, threading
|
import time, re, uuid, bcrypt, psutil, pyotp, threading
|
||||||
import traceback
|
import traceback
|
||||||
|
from functools import wraps
|
||||||
|
from urllib.parse import unquote
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
from jinja2 import Template
|
|
||||||
from flask import Flask, request, render_template, session, send_file, current_app
|
from flask import Flask, request, render_template, session, send_file, current_app
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from icmplib import ping, traceroute
|
from icmplib import ping, traceroute
|
||||||
@@ -18,7 +19,8 @@ from sqlalchemy import RowMapping
|
|||||||
|
|
||||||
from modules.Utilities import (
|
from modules.Utilities import (
|
||||||
RegexMatch, StringToBoolean, ValidateDNSAddress,
|
RegexMatch, StringToBoolean, ValidateDNSAddress,
|
||||||
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
GenerateWireguardPublicKey, GenerateWireguardPrivateKey,
|
||||||
|
RenderSandboxedTemplate
|
||||||
)
|
)
|
||||||
from packaging import version
|
from packaging import version
|
||||||
from modules.Email import EmailSender
|
from modules.Email import EmailSender
|
||||||
@@ -30,6 +32,7 @@ from modules.PeerJobs import PeerJobs
|
|||||||
from modules.DashboardConfig import DashboardConfig
|
from modules.DashboardConfig import DashboardConfig
|
||||||
from modules.WireguardConfiguration import WireguardConfiguration
|
from modules.WireguardConfiguration import WireguardConfiguration
|
||||||
from modules.AmneziaConfiguration import AmneziaConfiguration
|
from modules.AmneziaConfiguration import AmneziaConfiguration
|
||||||
|
from modules.DashboardOIDC import DashboardOIDC
|
||||||
|
|
||||||
from client import createClientBlueprint
|
from client import createClientBlueprint
|
||||||
|
|
||||||
@@ -68,6 +71,21 @@ def ResponseObject(status=True, message=None, data=None, status_code = 200) -> F
|
|||||||
response.content_type = "application/json"
|
response.content_type = "application/json"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def require_fields(*fields):
|
||||||
|
"""Decorator that validates required fields in request.json."""
|
||||||
|
def decorator(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
data = request.json
|
||||||
|
if data is None:
|
||||||
|
return ResponseObject(False, "Request body must be JSON", status_code=400)
|
||||||
|
missing = [field for field in fields if field not in data]
|
||||||
|
if missing:
|
||||||
|
return ResponseObject(False, f"Missing required fields: {', '.join(missing)}", status_code=400)
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated
|
||||||
|
return decorator
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Flask App
|
Flask App
|
||||||
'''
|
'''
|
||||||
@@ -208,6 +226,7 @@ with app.app_context():
|
|||||||
NewConfigurationTemplates: NewConfigurationTemplates = NewConfigurationTemplates()
|
NewConfigurationTemplates: NewConfigurationTemplates = NewConfigurationTemplates()
|
||||||
InitWireguardConfigurationsList(startup=True)
|
InitWireguardConfigurationsList(startup=True)
|
||||||
DashboardClients: DashboardClients = DashboardClients(WireguardConfigurations)
|
DashboardClients: DashboardClients = DashboardClients(WireguardConfigurations)
|
||||||
|
AdminOIDC = DashboardOIDC("Admin")
|
||||||
app.register_blueprint(createClientBlueprint(WireguardConfigurations, DashboardConfig, DashboardClients))
|
app.register_blueprint(createClientBlueprint(WireguardConfigurations, DashboardConfig, DashboardClients))
|
||||||
|
|
||||||
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
||||||
@@ -262,6 +281,8 @@ def auth_req():
|
|||||||
f'{appPrefix}/api/sharePeer/get',
|
f'{appPrefix}/api/sharePeer/get',
|
||||||
f'{appPrefix}/api/isTotpEnabled',
|
f'{appPrefix}/api/isTotpEnabled',
|
||||||
f'{appPrefix}/api/locale',
|
f'{appPrefix}/api/locale',
|
||||||
|
f'{appPrefix}/api/oidc/providers',
|
||||||
|
f'{appPrefix}/api/oidc/authenticate',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -299,6 +320,38 @@ def API_ValidateAuthentication():
|
|||||||
def API_RequireAuthentication():
|
def API_RequireAuthentication():
|
||||||
return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1])
|
return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1])
|
||||||
|
|
||||||
|
# OIDC for Admin
|
||||||
|
@app.get(f'{APP_PREFIX}/api/oidc/providers')
|
||||||
|
def API_OIDC_GetProviders():
|
||||||
|
_, oidc = DashboardConfig.GetConfig("OIDC", "admin_enable")
|
||||||
|
if not oidc:
|
||||||
|
return ResponseObject(status=False, message="OIDC is disabled")
|
||||||
|
|
||||||
|
return ResponseObject(data=AdminOIDC.GetProviders())
|
||||||
|
|
||||||
|
@app.post(f'{APP_PREFIX}/api/oidc/authenticate')
|
||||||
|
def API_OIDC_Authenticate():
|
||||||
|
_, oidc = DashboardConfig.GetConfig("OIDC", "admin_enable")
|
||||||
|
if not oidc:
|
||||||
|
return ResponseObject(False, "OIDC is disabled")
|
||||||
|
|
||||||
|
requestData = request.get_json()
|
||||||
|
status, data = AdminOIDC.VerifyToken(**requestData)
|
||||||
|
if not status:
|
||||||
|
return ResponseObject(False, "OIDC Authentication Failed. Reason: " + data)
|
||||||
|
session['role'] = 'admin'
|
||||||
|
authToken = hashlib.sha256(f'${data['sid']}{datetime.now()}{app.secret_key}'.encode()).hexdigest()
|
||||||
|
session['username'] = authToken
|
||||||
|
session['signInMethod'] = 'OIDC'
|
||||||
|
session['signInPayload'] = {
|
||||||
|
"Provider": requestData.get('provider'),
|
||||||
|
"Payload": data
|
||||||
|
}
|
||||||
|
resp = ResponseObject()
|
||||||
|
resp.set_cookie('authToken', authToken)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
@app.post(f'{APP_PREFIX}/api/authenticate')
|
@app.post(f'{APP_PREFIX}/api/authenticate')
|
||||||
def API_AuthenticateLogin():
|
def API_AuthenticateLogin():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
@@ -318,7 +371,10 @@ def API_AuthenticateLogin():
|
|||||||
totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
|
totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
|
||||||
totpValid = False
|
totpValid = False
|
||||||
if totpEnabled:
|
if totpEnabled:
|
||||||
totpValid = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now() == data['totp']
|
totp_code = str(data.get("totp", "")).strip()
|
||||||
|
totpValid = pyotp.TOTP(
|
||||||
|
DashboardConfig.GetConfig("Account", "totp_key")[1]
|
||||||
|
).verify(totp_code, valid_window=1)
|
||||||
|
|
||||||
if (valid
|
if (valid
|
||||||
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]
|
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]
|
||||||
@@ -327,6 +383,7 @@ def API_AuthenticateLogin():
|
|||||||
authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
|
authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
|
||||||
session['role'] = 'admin'
|
session['role'] = 'admin'
|
||||||
session['username'] = authToken
|
session['username'] = authToken
|
||||||
|
session['signInMethod'] = 'local'
|
||||||
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
||||||
resp.set_cookie("authToken", authToken)
|
resp.set_cookie("authToken", authToken)
|
||||||
session.permanent = True
|
session.permanent = True
|
||||||
@@ -342,6 +399,14 @@ def API_AuthenticateLogin():
|
|||||||
def API_SignOut():
|
def API_SignOut():
|
||||||
resp = ResponseObject(True, "")
|
resp = ResponseObject(True, "")
|
||||||
resp.delete_cookie("authToken")
|
resp.delete_cookie("authToken")
|
||||||
|
if session.get('signInMethod') == "OIDC":
|
||||||
|
status, oidc_config = AdminOIDC.GetProviderConfiguration(session.get('signInPayload').get("Provider"))
|
||||||
|
signOut = requests.get(
|
||||||
|
oidc_config.get("end_session_endpoint"),
|
||||||
|
params={
|
||||||
|
'id_token_hint': session.get('signInPayload').get("Payload").get('sid')
|
||||||
|
}
|
||||||
|
)
|
||||||
session.clear()
|
session.clear()
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@@ -880,8 +945,17 @@ def API_addPeers(configName):
|
|||||||
allowed_ips: list[str] = data.get('allowed_ips', [])
|
allowed_ips: list[str] = data.get('allowed_ips', [])
|
||||||
allowed_ips_validation: bool = data.get('allowed_ips_validation', True)
|
allowed_ips_validation: bool = data.get('allowed_ips_validation', True)
|
||||||
|
|
||||||
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
|
config = WireguardConfigurations.get(configName)
|
||||||
dns_addresses: str = data.get('DNS', DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1])
|
overridePeerSettings = config.configurationInfo.OverridePeerSettings
|
||||||
|
|
||||||
|
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', (
|
||||||
|
overridePeerSettings.EndpointAllowedIPs
|
||||||
|
if overridePeerSettings.EndpointAllowedIPs else DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1]
|
||||||
|
))
|
||||||
|
dns_addresses: str = data.get('DNS', (
|
||||||
|
overridePeerSettings.DNS
|
||||||
|
if overridePeerSettings.DNS else DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1]
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
mtu: int = data.get('mtu', None)
|
mtu: int = data.get('mtu', None)
|
||||||
@@ -890,7 +964,10 @@ def API_addPeers(configName):
|
|||||||
preshared_key: str = data.get('preshared_key', "")
|
preshared_key: str = data.get('preshared_key', "")
|
||||||
|
|
||||||
if type(mtu) is not int or mtu < 0 or mtu > 1460:
|
if type(mtu) is not int or mtu < 0 or mtu > 1460:
|
||||||
default: str = DashboardConfig.GetConfig("Peers", "peer_mtu")[1]
|
default: str = (
|
||||||
|
str(overridePeerSettings.MTU)
|
||||||
|
if overridePeerSettings.MTU else DashboardConfig.GetConfig("Peers", "peer_mtu")[1]
|
||||||
|
)
|
||||||
if default.isnumeric():
|
if default.isnumeric():
|
||||||
try:
|
try:
|
||||||
mtu = int(default)
|
mtu = int(default)
|
||||||
@@ -899,7 +976,10 @@ def API_addPeers(configName):
|
|||||||
else:
|
else:
|
||||||
mtu = 0
|
mtu = 0
|
||||||
if type(keep_alive) is not int or keep_alive < 0:
|
if type(keep_alive) is not int or keep_alive < 0:
|
||||||
default = DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]
|
default = (
|
||||||
|
str(overridePeerSettings.PersistentKeepalive)
|
||||||
|
if overridePeerSettings.PersistentKeepalive else DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]
|
||||||
|
)
|
||||||
if default.isnumeric():
|
if default.isnumeric():
|
||||||
try:
|
try:
|
||||||
keep_alive = int(default)
|
keep_alive = int(default)
|
||||||
@@ -908,7 +988,6 @@ def API_addPeers(configName):
|
|||||||
else:
|
else:
|
||||||
keep_alive = 0
|
keep_alive = 0
|
||||||
|
|
||||||
config = WireguardConfigurations.get(configName)
|
|
||||||
if not config.getStatus():
|
if not config.getStatus():
|
||||||
config.toggleConfiguration()
|
config.toggleConfiguration()
|
||||||
ipStatus, availableIps = config.getAvailableIP(-1)
|
ipStatus, availableIps = config.getAvailableIP(-1)
|
||||||
@@ -975,8 +1054,7 @@ def API_addPeers(configName):
|
|||||||
if ipStatus:
|
if ipStatus:
|
||||||
for subnet in availableIps.keys():
|
for subnet in availableIps.keys():
|
||||||
for ip in availableIps[subnet]:
|
for ip in availableIps[subnet]:
|
||||||
allowed_ips = [ip]
|
allowed_ips.append(ip)
|
||||||
break
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
return ResponseObject(False, "No more available IP can assign")
|
return ResponseObject(False, "No more available IP can assign")
|
||||||
@@ -1075,23 +1153,36 @@ def API_GetPeerHistoricalEndpoints():
|
|||||||
if not configurationName or not id:
|
if not configurationName or not id:
|
||||||
return ResponseObject(False, "Please provide configurationName and id")
|
return ResponseObject(False, "Please provide configurationName and id")
|
||||||
fp, p = WireguardConfigurations.get(configurationName).searchPeer(id)
|
fp, p = WireguardConfigurations.get(configurationName).searchPeer(id)
|
||||||
if fp:
|
if not fp:
|
||||||
result = p.getEndpoints()
|
|
||||||
geo = {}
|
|
||||||
try:
|
|
||||||
r = requests.post(f"http://ip-api.com/batch?fields=city,country,lat,lon,query",
|
|
||||||
data=json.dumps([x['endpoint'] for x in result]))
|
|
||||||
d = r.json()
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return ResponseObject(data=result, message="Failed to request IP address geolocation. " + str(e))
|
|
||||||
|
|
||||||
return ResponseObject(data={
|
|
||||||
"endpoints": p.getEndpoints(),
|
|
||||||
"geolocation": d
|
|
||||||
})
|
|
||||||
return ResponseObject(False, "Peer does not exist")
|
return ResponseObject(False, "Peer does not exist")
|
||||||
|
endpoints = p.getEndpoints()
|
||||||
|
ips = [x['endpoint'] for x in endpoints]
|
||||||
|
unique_ips = list(set(ips))
|
||||||
|
def chunks(lst, size=100):
|
||||||
|
for i in range(0, len(lst), size):
|
||||||
|
yield lst[i:i + size]
|
||||||
|
geo_cache = {}
|
||||||
|
try:
|
||||||
|
for batch in chunks(unique_ips, 100):
|
||||||
|
r = requests.post(
|
||||||
|
"http://ip-api.com/batch?fields=city,country,lat,lon,query",
|
||||||
|
data=json.dumps(batch),
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
for item in data:
|
||||||
|
geo_cache[item["query"]] = item
|
||||||
|
geolocation = [geo_cache[ip] for ip in ips]
|
||||||
|
except Exception as e:
|
||||||
|
return ResponseObject(
|
||||||
|
data={"endpoints": endpoints},
|
||||||
|
message="Failed to request IP address geolocation. " + str(e)
|
||||||
|
)
|
||||||
|
return ResponseObject(data={
|
||||||
|
"endpoints": endpoints,
|
||||||
|
"geolocation": geolocation
|
||||||
|
})
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/getPeerSessions')
|
@app.get(f'{APP_PREFIX}/api/getPeerSessions')
|
||||||
def API_GetPeerSessions():
|
def API_GetPeerSessions():
|
||||||
@@ -1204,52 +1295,75 @@ def API_getDashboardTheme():
|
|||||||
def API_getDashboardVersion():
|
def API_getDashboardVersion():
|
||||||
return ResponseObject(data=DashboardConfig.GetConfig("Server", "version")[1])
|
return ResponseObject(data=DashboardConfig.GetConfig("Server", "version")[1])
|
||||||
|
|
||||||
@app.post(f'{APP_PREFIX}/api/savePeerScheduleJob')
|
@app.post(f'{APP_PREFIX}/api/PeerScheduleJob')
|
||||||
|
@require_fields('Configuration', 'Peer', 'Field', 'Operator', 'Value', 'Action')
|
||||||
def API_savePeerScheduleJob():
|
def API_savePeerScheduleJob():
|
||||||
data = request.json
|
data = request.json
|
||||||
if "Job" not in data.keys():
|
|
||||||
return ResponseObject(False, "Please specify job")
|
configuration = WireguardConfigurations.get(data['Configuration'])
|
||||||
job: dict = data['Job']
|
|
||||||
if "Peer" not in job.keys() or "Configuration" not in job.keys():
|
|
||||||
return ResponseObject(False, "Please specify peer and configuration")
|
|
||||||
configuration = WireguardConfigurations.get(job['Configuration'])
|
|
||||||
if configuration is None:
|
if configuration is None:
|
||||||
return ResponseObject(False, "Configuration does not exist")
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
||||||
f, fp = configuration.searchPeer(job['Peer'])
|
|
||||||
if not f:
|
|
||||||
return ResponseObject(False, "Peer does not exist")
|
|
||||||
|
|
||||||
|
peerKey = unquote(data['Peer'])
|
||||||
|
found, _ = configuration.searchPeer(peerKey)
|
||||||
|
if not found:
|
||||||
|
return ResponseObject(False, "Peer does not exist", status_code=404)
|
||||||
|
|
||||||
s, p = AllPeerJobs.saveJob(PeerJob(
|
jobID = data.get('JobID', str(uuid4()))
|
||||||
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
|
if len(AllPeerJobs.searchJobById(jobID)) > 0:
|
||||||
job['CreationDate'], job['ExpireDate'], job['Action']))
|
return ResponseObject(False, "Job already exists", status_code=409)
|
||||||
if s:
|
|
||||||
return ResponseObject(s, data=p)
|
|
||||||
return ResponseObject(s, message=p)
|
|
||||||
|
|
||||||
@app.post(f'{APP_PREFIX}/api/deletePeerScheduleJob')
|
success, result = AllPeerJobs.saveJob(PeerJob(
|
||||||
|
jobID, data['Configuration'], peerKey, data['Field'], data['Operator'], data['Value'],
|
||||||
|
datetime.now(), data.get('ExpireDate'), data['Action']))
|
||||||
|
if success:
|
||||||
|
return ResponseObject(success, data=result)
|
||||||
|
return ResponseObject(success, message=result)
|
||||||
|
|
||||||
|
@app.put(f'{APP_PREFIX}/api/PeerScheduleJob')
|
||||||
|
@require_fields('JobID', 'Configuration', 'Peer', 'Field', 'Operator', 'Value', 'Action')
|
||||||
|
def API_updatePeerScheduleJob():
|
||||||
|
data = request.json
|
||||||
|
|
||||||
|
configuration = WireguardConfigurations.get(data['Configuration'])
|
||||||
|
if configuration is None:
|
||||||
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
||||||
|
|
||||||
|
peerKey = unquote(data['Peer'])
|
||||||
|
found, _ = configuration.searchPeer(peerKey)
|
||||||
|
if not found:
|
||||||
|
return ResponseObject(False, "Peer does not exist", status_code=404)
|
||||||
|
|
||||||
|
existing = AllPeerJobs.searchJobById(data['JobID'])
|
||||||
|
if len(existing) == 0:
|
||||||
|
return ResponseObject(False, "Job does not exist", status_code=404)
|
||||||
|
|
||||||
|
success, result = AllPeerJobs.saveJob(PeerJob(
|
||||||
|
data['JobID'], data['Configuration'], peerKey, data['Field'], data['Operator'], data['Value'],
|
||||||
|
datetime.now(), data.get('ExpireDate'), data['Action']))
|
||||||
|
if success:
|
||||||
|
return ResponseObject(success, data=result)
|
||||||
|
return ResponseObject(success, message=result)
|
||||||
|
|
||||||
|
@app.delete(f'{APP_PREFIX}/api/PeerScheduleJob')
|
||||||
|
@require_fields('JobID', 'Configuration', 'Peer')
|
||||||
def API_deletePeerScheduleJob():
|
def API_deletePeerScheduleJob():
|
||||||
data = request.json
|
data = request.json
|
||||||
if "Job" not in data.keys():
|
|
||||||
return ResponseObject(False, "Please specify job")
|
configuration = WireguardConfigurations.get(data['Configuration'])
|
||||||
job: dict = data['Job']
|
|
||||||
if "Peer" not in job.keys() or "Configuration" not in job.keys():
|
|
||||||
return ResponseObject(False, "Please specify peer and configuration")
|
|
||||||
configuration = WireguardConfigurations.get(job['Configuration'])
|
|
||||||
if configuration is None:
|
if configuration is None:
|
||||||
return ResponseObject(False, "Configuration does not exist")
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
||||||
# f, fp = configuration.searchPeer(job['Peer'])
|
|
||||||
# if not f:
|
|
||||||
# return ResponseObject(False, "Peer does not exist")
|
|
||||||
|
|
||||||
s, p = AllPeerJobs.deleteJob(PeerJob(
|
peerKey = unquote(data['Peer'])
|
||||||
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
|
success, result = AllPeerJobs.deleteJob(PeerJob(
|
||||||
job['CreationDate'], job['ExpireDate'], job['Action']))
|
data['JobID'], data['Configuration'], peerKey, data.get('Field', ''),
|
||||||
if s:
|
data.get('Operator', ''), data.get('Value', ''),
|
||||||
return ResponseObject(s)
|
datetime.now(), data.get('ExpireDate'), data.get('Action', '')))
|
||||||
return ResponseObject(s, message=p)
|
if success:
|
||||||
|
return ResponseObject(success, message="Job deleted successfully")
|
||||||
|
return ResponseObject(success, message=result)
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/getPeerScheduleJobLogs/<configName>')
|
@app.get(f'{APP_PREFIX}/api/PeerScheduleJobLogs/<configName>')
|
||||||
def API_getPeerScheduleJobLogs(configName):
|
def API_getPeerScheduleJobLogs(configName):
|
||||||
if configName not in WireguardConfigurations.keys():
|
if configName not in WireguardConfigurations.keys():
|
||||||
return ResponseObject(False, "Configuration does not exist")
|
return ResponseObject(False, "Configuration does not exist")
|
||||||
@@ -1274,11 +1388,11 @@ def API_ping_getAllPeersIpAddress():
|
|||||||
for x in allowed_ip:
|
for x in allowed_ip:
|
||||||
try:
|
try:
|
||||||
ip = ipaddress.ip_network(x, strict=False)
|
ip = ipaddress.ip_network(x, strict=False)
|
||||||
|
if ip.num_addresses == 1:
|
||||||
|
parsed.append(str(ip.network_address))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
app.logger.error(f"Failed to parse IP address of {p.id} - {c.Name}")
|
app.logger.error(f"Failed to parse IP address of {p.id} - {c.Name}")
|
||||||
host = list(ip.hosts())
|
|
||||||
if len(host) == 1:
|
|
||||||
parsed.append(str(host[0]))
|
|
||||||
endpoint = p.endpoint.replace(" ", "").replace("(none)", "")
|
endpoint = p.endpoint.replace(" ", "").replace("(none)", "")
|
||||||
if len(p.name) > 0:
|
if len(p.name) > 0:
|
||||||
cips[f"{p.name} - {p.id}"] = {
|
cips[f"{p.name} - {p.id}"] = {
|
||||||
@@ -1415,11 +1529,15 @@ def API_Welcome_GetTotpLink():
|
|||||||
@app.post(f'{APP_PREFIX}/api/Welcome_VerifyTotpLink')
|
@app.post(f'{APP_PREFIX}/api/Welcome_VerifyTotpLink')
|
||||||
def API_Welcome_VerifyTotpLink():
|
def API_Welcome_VerifyTotpLink():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
totp = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now()
|
totp_code = str(data.get("totp", "")).strip()
|
||||||
if totp == data['totp']:
|
totpValid = pyotp.TOTP(
|
||||||
|
DashboardConfig.GetConfig("Account", "totp_key")[1]
|
||||||
|
).verify(totp_code, valid_window=1)
|
||||||
|
|
||||||
|
if totpValid:
|
||||||
DashboardConfig.SetConfig("Account", "totp_verified", "true")
|
DashboardConfig.SetConfig("Account", "totp_verified", "true")
|
||||||
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
||||||
return ResponseObject(totp == data['totp'])
|
return ResponseObject(totpValid)
|
||||||
|
|
||||||
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
|
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
|
||||||
def API_Welcome_Finish():
|
def API_Welcome_Finish():
|
||||||
@@ -1505,13 +1623,13 @@ def API_Email_Send():
|
|||||||
if configuration is not None:
|
if configuration is not None:
|
||||||
fp, p = configuration.searchPeer(data.get('Peer'))
|
fp, p = configuration.searchPeer(data.get('Peer'))
|
||||||
if fp:
|
if fp:
|
||||||
template = Template(body)
|
|
||||||
download = p.downloadPeer()
|
download = p.downloadPeer()
|
||||||
body = template.render(peer=p.toJson(), configurationFile=download)
|
body = RenderSandboxedTemplate(body, peer=p.toJson(), configurationFile=download)
|
||||||
subject = Template(data.get('Subject', '')).render(peer=p.toJson(), configurationFile=download)
|
subject = RenderSandboxedTemplate(data.get('Subject', ''), peer=p.toJson(), configurationFile=download)
|
||||||
if data.get('IncludeAttachment', False):
|
if data.get('IncludeAttachment', False):
|
||||||
u = str(uuid4())
|
u = str(uuid4())
|
||||||
attachmentName = f'{u}.conf'
|
peerName = p.toJson().get('name', '').strip()
|
||||||
|
attachmentName = f'{peerName if peerName else u}.conf'
|
||||||
with open(os.path.join('./attachments', attachmentName,), 'w+') as f:
|
with open(os.path.join('./attachments', attachmentName,), 'w+') as f:
|
||||||
f.write(download['file'])
|
f.write(download['file'])
|
||||||
|
|
||||||
@@ -1536,11 +1654,10 @@ def API_Email_PreviewBody():
|
|||||||
return ResponseObject(False, "Peer does not exist")
|
return ResponseObject(False, "Peer does not exist")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
template = Template(body)
|
|
||||||
download = p.downloadPeer()
|
download = p.downloadPeer()
|
||||||
return ResponseObject(data={
|
return ResponseObject(data={
|
||||||
"Body": Template(body).render(peer=p.toJson(), configurationFile=download),
|
"Body": RenderSandboxedTemplate(body, peer=p.toJson(), configurationFile=download),
|
||||||
"Subject": Template(subject).render(peer=p.toJson(), configurationFile=download)
|
"Subject": RenderSandboxedTemplate(subject, peer=p.toJson(), configurationFile=download)
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return ResponseObject(False, message=str(e))
|
return ResponseObject(False, message=str(e))
|
||||||
|
|||||||
@@ -32,11 +32,24 @@ class AmneziaConfiguration(WireguardConfiguration):
|
|||||||
self.H2 = 2
|
self.H2 = 2
|
||||||
self.H3 = 3
|
self.H3 = 3
|
||||||
self.H4 = 4
|
self.H4 = 4
|
||||||
self.I1 = "0"
|
self.I1 = ""
|
||||||
self.I2 = "0"
|
self.I2 = ""
|
||||||
self.I3 = "0"
|
self.I3 = ""
|
||||||
self.I4 = "0"
|
self.I4 = ""
|
||||||
self.I5 = "0"
|
self.I5 = ""
|
||||||
|
self.HeaderProtectionKey = ""
|
||||||
|
self.ContentPaddingAddition = 0
|
||||||
|
self.RekeyAfterTime = 0
|
||||||
|
self.RekeyTimeout = 0
|
||||||
|
self.RejectAfterTime = 0
|
||||||
|
self.KeepaliveTimeout = 0
|
||||||
|
self.MaxHandshakeAttempts = 0
|
||||||
|
self.RandomTrailers = 0
|
||||||
|
self.DisableCookies = 0
|
||||||
|
self.J1 = ""
|
||||||
|
self.J2 = ""
|
||||||
|
self.J3 = ""
|
||||||
|
self.Itime = ""
|
||||||
|
|
||||||
super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False)
|
super().__init__(DashboardConfig, AllPeerJobs, AllPeerShareLinks, DashboardWebHooks, name, data, backup, startup, wg=False)
|
||||||
|
|
||||||
@@ -79,7 +92,20 @@ class AmneziaConfiguration(WireguardConfiguration):
|
|||||||
"I2": self.I2,
|
"I2": self.I2,
|
||||||
"I3": self.I3,
|
"I3": self.I3,
|
||||||
"I4": self.I4,
|
"I4": self.I4,
|
||||||
"I5": self.I5
|
"I5": self.I5,
|
||||||
|
"HeaderProtectionKey": self.HeaderProtectionKey,
|
||||||
|
"ContentPaddingAddition": self.ContentPaddingAddition,
|
||||||
|
"RekeyAfterTime": self.RekeyAfterTime,
|
||||||
|
"RekeyTimeout": self.RekeyTimeout,
|
||||||
|
"RejectAfterTime": self.RejectAfterTime,
|
||||||
|
"KeepaliveTimeout": self.KeepaliveTimeout,
|
||||||
|
"MaxHandshakeAttempts": self.MaxHandshakeAttempts,
|
||||||
|
"RandomTrailers": self.RandomTrailers,
|
||||||
|
"DisableCookies": self.DisableCookies,
|
||||||
|
"J1": self.J1,
|
||||||
|
"J2": self.J2,
|
||||||
|
"J3": self.J3,
|
||||||
|
"Itime": self.Itime
|
||||||
}
|
}
|
||||||
|
|
||||||
def createDatabase(self, dbName = None):
|
def createDatabase(self, dbName = None):
|
||||||
|
|||||||
@@ -87,8 +87,9 @@ class AmneziaPeer(Peer):
|
|||||||
|
|
||||||
if psk_exist: os.remove(uid)
|
if psk_exist: os.remove(uid)
|
||||||
|
|
||||||
if len(updateAllowedIp.decode().strip("\n")) != 0:
|
output = updateAllowedIp.decode().strip("\n")
|
||||||
current_app.logger.error(f"Update peer failed when updating Allowed IPs.\nInput: {newAllowedIPs}\nOutput: {updateAllowedIp.decode().strip('\n')}")
|
if len(output) != 0:
|
||||||
|
current_app.logger.error(f"Update peer failed when updating Allowed IPs.\nInput: {newAllowedIPs}\nOutput: {output}")
|
||||||
return False, "Internal server error"
|
return False, "Internal server error"
|
||||||
|
|
||||||
command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name]
|
command = [f"{self.configuration.Protocol}-quick", "save", self.configuration.Name]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from .Utilities import (GetRemoteEndpoint, ValidateDNSAddress)
|
|||||||
from .DashboardAPIKey import DashboardAPIKey
|
from .DashboardAPIKey import DashboardAPIKey
|
||||||
|
|
||||||
class DashboardConfig:
|
class DashboardConfig:
|
||||||
DashboardVersion = 'v4.3.3'
|
DashboardVersion = 'v4.3.4'
|
||||||
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
|
ConfigurationPath = os.getenv('CONFIGURATION_PATH', '.')
|
||||||
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini')
|
ConfigurationFilePath = os.path.join(ConfigurationPath, 'wg-dashboard.ini')
|
||||||
|
|
||||||
@@ -51,7 +51,8 @@ class DashboardConfig:
|
|||||||
"peer_display_mode": "grid",
|
"peer_display_mode": "grid",
|
||||||
"remote_endpoint": GetRemoteEndpoint(),
|
"remote_endpoint": GetRemoteEndpoint(),
|
||||||
"peer_MTU": "1420",
|
"peer_MTU": "1420",
|
||||||
"peer_keep_alive": "21"
|
"peer_keep_alive": "21",
|
||||||
|
"peer_preshared_key_default": "false"
|
||||||
},
|
},
|
||||||
"Other": {
|
"Other": {
|
||||||
"welcome_session": "true"
|
"welcome_session": "true"
|
||||||
@@ -146,7 +147,10 @@ class DashboardConfig:
|
|||||||
if col_name not in existing_columns:
|
if col_name not in existing_columns:
|
||||||
type_str = col_type().compile(dialect=self.engine.dialect)
|
type_str = col_type().compile(dialect=self.engine.dialect)
|
||||||
current_app.logger.info(f"Adding missing column '{col_name}' to table '{table_name}'")
|
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}'))
|
preparer = self.engine.dialect.identifier_preparer
|
||||||
|
quoted_table = preparer.quote_identifier(table_name)
|
||||||
|
quoted_column = preparer.quote_identifier(col_name)
|
||||||
|
conn.execute(db.text(f"ALTER TABLE {quoted_table} ADD COLUMN {quoted_column} {type_str}"))
|
||||||
|
|
||||||
def getConnectionString(self, database) -> str or None:
|
def getConnectionString(self, database) -> str or None:
|
||||||
sqlitePath = os.path.join(DashboardConfig.ConfigurationPath, "db")
|
sqlitePath = os.path.join(DashboardConfig.ConfigurationPath, "db")
|
||||||
|
|||||||
@@ -85,10 +85,15 @@ class DashboardOIDC:
|
|||||||
|
|
||||||
key = next(k for k in jwks["keys"] if k["kid"] == kid)
|
key = next(k for k in jwks["keys"] if k["kid"] == kid)
|
||||||
|
|
||||||
|
alg = key.get("alg") or headers.get("alg")
|
||||||
|
supported_algs = oidc_config.get("id_token_signing_alg_values_supported")
|
||||||
|
if supported_algs and alg not in supported_algs:
|
||||||
|
return False, f"Unsupported token signing algorithm: {alg}"
|
||||||
|
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
id_token,
|
id_token,
|
||||||
key,
|
key,
|
||||||
algorithms=[key["alg"]],
|
algorithms=[alg],
|
||||||
audience=provider_info.get('client_id'),
|
audience=provider_info.get('client_id'),
|
||||||
issuer=issuer,
|
issuer=issuer,
|
||||||
access_token=access_token
|
access_token=access_token
|
||||||
@@ -134,7 +139,7 @@ class DashboardOIDC:
|
|||||||
'openid_configuration': oidc_config
|
'openid_configuration': oidc_config
|
||||||
}
|
}
|
||||||
self.provider_secret[k] = providers[k]['client_secret']
|
self.provider_secret[k] = providers[k]['client_secret']
|
||||||
current_app.logger.info(f"Registered OIDC Provider: {k}")
|
current_app.logger.info(f"Registered OIDC Provider[{self.mode}]: {k}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.error(f"Failed to register OIDC config for {k}", exc_info=e)
|
current_app.logger.error(f"Failed to register OIDC config for {k}", exc_info=e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+17
-4
@@ -7,12 +7,12 @@ import json
|
|||||||
import os, subprocess, uuid, random, re
|
import os, subprocess, uuid, random, re
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import sqlalchemy as db
|
import sqlalchemy as db
|
||||||
from .PeerJob import PeerJob
|
from .PeerJob import PeerJob
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
from .PeerShareLink import PeerShareLink
|
from .PeerShareLink import PeerShareLink
|
||||||
from .Utilities import GenerateWireguardPublicKey, CheckAddress, ValidateDNSAddress
|
from .Utilities import GenerateWireguardPublicKey, CheckAddress, ValidateDNSAddress, \
|
||||||
|
RenderSandboxedTemplate
|
||||||
|
|
||||||
|
|
||||||
class Peer:
|
class Peer:
|
||||||
@@ -206,7 +206,20 @@ class Peer:
|
|||||||
"I2": self.configuration.I2,
|
"I2": self.configuration.I2,
|
||||||
"I3": self.configuration.I3,
|
"I3": self.configuration.I3,
|
||||||
"I4": self.configuration.I4,
|
"I4": self.configuration.I4,
|
||||||
"I5": self.configuration.I5
|
"I5": self.configuration.I5,
|
||||||
|
"HeaderProtectionKey": self.configuration.HeaderProtectionKey,
|
||||||
|
"ContentPaddingAddition": self.configuration.ContentPaddingAddition,
|
||||||
|
"RekeyAfterTime": self.configuration.RekeyAfterTime,
|
||||||
|
"RekeyTimeout": self.configuration.RekeyTimeout,
|
||||||
|
"RejectAfterTime": self.configuration.RejectAfterTime,
|
||||||
|
"KeepaliveTimeout": self.configuration.KeepaliveTimeout,
|
||||||
|
"MaxHandshakeAttempts": self.configuration.MaxHandshakeAttempts,
|
||||||
|
"RandomTrailers": self.configuration.RandomTrailers,
|
||||||
|
"DisableCookies": self.configuration.DisableCookies,
|
||||||
|
"J1": self.configuration.J1,
|
||||||
|
"J2": self.configuration.J2,
|
||||||
|
"J3": self.configuration.J3,
|
||||||
|
"Itime": self.configuration.Itime
|
||||||
})
|
})
|
||||||
|
|
||||||
peerSection = {
|
peerSection = {
|
||||||
@@ -233,7 +246,7 @@ class Peer:
|
|||||||
if val is not None and ((type(val) is str and len(val) > 0) or (type(val) is int and val > 0)):
|
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"] += f"{key} = {val}\n"
|
||||||
|
|
||||||
final["file"] = jinja2.Template(final["file"]).render(configuration=self.configuration)
|
final["file"] = RenderSandboxedTemplate(final["file"], configuration=self.configuration)
|
||||||
|
|
||||||
|
|
||||||
if self.configuration.Protocol == "awg":
|
if self.configuration.Protocol == "awg":
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
import re, ipaddress
|
import re, ipaddress
|
||||||
import subprocess
|
import subprocess
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
from jinja2.sandbox import SandboxedEnvironment
|
||||||
|
|
||||||
|
sandboxEnvironment = SandboxedEnvironment()
|
||||||
|
|
||||||
|
def RenderSandboxedTemplate(source: str, **context) -> str:
|
||||||
|
"""
|
||||||
|
Render a Jinja2 template within a sandboxed environment. Template sources built with
|
||||||
|
user-provided content (e.g. email body, peer configuration file) must go through this
|
||||||
|
function, so unsafe attribute access (such as __globals__) is denied and template
|
||||||
|
injection cannot escalate into arbitrary code execution.
|
||||||
|
@param source: Template source string, may contain user-provided content
|
||||||
|
@param context: Variables passed into the template
|
||||||
|
@return: Rendered template
|
||||||
|
"""
|
||||||
|
return sandboxEnvironment.from_string(source).render(**context)
|
||||||
|
|
||||||
def RegexMatch(regex, text) -> bool:
|
def RegexMatch(regex, text) -> bool:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ WireGuard Configuration
|
|||||||
"""
|
"""
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import sqlalchemy, random, shutil, configparser, ipaddress, os, subprocess, time, re, uuid, psutil, traceback
|
import sqlalchemy, random, shutil, configparser, ipaddress, os, subprocess, time, re, uuid, psutil, traceback
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@@ -21,7 +20,8 @@ from .Utilities import StringToBoolean, \
|
|||||||
ValidateDNSAddress, \
|
ValidateDNSAddress, \
|
||||||
ValidateEndpointAllowedIPs, \
|
ValidateEndpointAllowedIPs, \
|
||||||
CheckAddress, \
|
CheckAddress, \
|
||||||
CheckPeerKey
|
CheckPeerKey, \
|
||||||
|
RenderSandboxedTemplate
|
||||||
from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass
|
from .WireguardConfigurationInfo import WireguardConfigurationInfo, PeerGroupsClass
|
||||||
from .DashboardWebHooks import DashboardWebHooks
|
from .DashboardWebHooks import DashboardWebHooks
|
||||||
|
|
||||||
@@ -109,23 +109,40 @@ class WireguardConfiguration:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.Protocol == 'awg':
|
if self.Protocol == 'awg':
|
||||||
self.__parser["Interface"]["Jc"] = self.Jc
|
values = {
|
||||||
self.__parser["Interface"]["Jc"] = self.Jc
|
"Jc": self.Jc,
|
||||||
self.__parser["Interface"]["Jmin"] = self.Jmin
|
"Jmin": self.Jmin,
|
||||||
self.__parser["Interface"]["Jmax"] = self.Jmax
|
"Jmax": self.Jmax,
|
||||||
self.__parser["Interface"]["S1"] = self.S1
|
"S1": self.S1,
|
||||||
self.__parser["Interface"]["S2"] = self.S2
|
"S2": self.S2,
|
||||||
self.__parser["Interface"]["S3"] = self.S3
|
"S3": self.S3,
|
||||||
self.__parser["Interface"]["S4"] = self.S4
|
"S4": self.S4,
|
||||||
self.__parser["Interface"]["H1"] = self.H1
|
"H1": self.H1,
|
||||||
self.__parser["Interface"]["H2"] = self.H2
|
"H2": self.H2,
|
||||||
self.__parser["Interface"]["H3"] = self.H3
|
"H3": self.H3,
|
||||||
self.__parser["Interface"]["H4"] = self.H4
|
"H4": self.H4,
|
||||||
self.__parser["Interface"]["I1"] = self.I1
|
"I1": self.I1,
|
||||||
self.__parser["Interface"]["I2"] = self.I2
|
"I2": self.I2,
|
||||||
self.__parser["Interface"]["I3"] = self.I3
|
"I3": self.I3,
|
||||||
self.__parser["Interface"]["I4"] = self.I4
|
"I4": self.I4,
|
||||||
self.__parser["Interface"]["I5"] = self.I5
|
"I5": self.I5,
|
||||||
|
"HeaderProtectionKey": self.HeaderProtectionKey,
|
||||||
|
"ContentPaddingAddition": self.ContentPaddingAddition,
|
||||||
|
"RekeyAfterTime": self.RekeyAfterTime,
|
||||||
|
"RekeyTimeout": self.RekeyTimeout,
|
||||||
|
"RejectAfterTime": self.RejectAfterTime,
|
||||||
|
"KeepaliveTimeout": self.KeepaliveTimeout,
|
||||||
|
"MaxHandshakeAttempts": self.MaxHandshakeAttempts,
|
||||||
|
"RandomTrailers": self.RandomTrailers,
|
||||||
|
"DisableCookies": self.DisableCookies,
|
||||||
|
"J1": self.J1,
|
||||||
|
"J2": self.J2,
|
||||||
|
"J3": self.J3,
|
||||||
|
"Itime": self.Itime
|
||||||
|
}
|
||||||
|
for key, value in values.items():
|
||||||
|
if value != None and str(value).strip():
|
||||||
|
self.__parser["Interface"][key] = str(value)
|
||||||
|
|
||||||
if "Backup" not in data.keys():
|
if "Backup" not in data.keys():
|
||||||
self.createDatabase()
|
self.createDatabase()
|
||||||
@@ -232,17 +249,16 @@ class WireguardConfiguration:
|
|||||||
self.Status = self.getStatus()
|
self.Status = self.getStatus()
|
||||||
|
|
||||||
def __dropDatabase(self):
|
def __dropDatabase(self):
|
||||||
existingTables = [self.Name, f'{self.Name}_restrict_access', f'{self.Name}_transfer', f'{self.Name}_deleted']
|
existingTables = [self.Name, f'{self.Name}_restrict_access', f'{self.Name}_transfer', f'{self.Name}_deleted', f'{self.Name}_history_endpoint']
|
||||||
try:
|
try:
|
||||||
with self.engine.begin() as conn:
|
with self.engine.begin() as conn:
|
||||||
for t in existingTables:
|
for name in existingTables:
|
||||||
|
table = sqlalchemy.Table(name, sqlalchemy.MetaData())
|
||||||
conn.execute(
|
conn.execute(
|
||||||
sqlalchemy.text(
|
sqlalchemy.schema.DropTable(table, if_exists=True)
|
||||||
f'DROP TABLE "{t}"'
|
|
||||||
)
|
)
|
||||||
)
|
except Exception:
|
||||||
except Exception as e:
|
current_app.logger.exception("Dropping table failed")
|
||||||
current_app.logger.error("Dropping table failed")
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -308,7 +324,8 @@ class WireguardConfiguration:
|
|||||||
f'{dbName}_history_endpoint', self.metadata,
|
f'{dbName}_history_endpoint', self.metadata,
|
||||||
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||||
sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False),
|
sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False),
|
||||||
sqlalchemy.Column('time', time_col_type)
|
sqlalchemy.Column('time', time_col_type),
|
||||||
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self.infoTable = sqlalchemy.Table(
|
self.infoTable = sqlalchemy.Table(
|
||||||
@@ -327,11 +344,19 @@ class WireguardConfiguration:
|
|||||||
rows = conn.execute(i.select()).mappings().fetchall()
|
rows = conn.execute(i.select()).mappings().fetchall()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
insert_stmt = i.insert().values(dict(row))
|
insert_stmt = i.insert().values(dict(row))
|
||||||
yield str(insert_stmt.compile(compile_kwargs={"literal_binds": True}))
|
yield str(
|
||||||
|
insert_stmt.compile(
|
||||||
|
dialect=self.engine.dialect,
|
||||||
|
compile_kwargs={"literal_binds": True},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def __importDatabase(self, sqlFilePath, restore = False) -> bool:
|
def __importDatabase(self, sqlFilePath, restore = False) -> bool:
|
||||||
if not restore:
|
if not restore:
|
||||||
self.__dropDatabase()
|
if not self.__dropDatabase():
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Failed to drop tables for configuration {self.Name}"
|
||||||
|
)
|
||||||
self.createDatabase()
|
self.createDatabase()
|
||||||
if not os.path.exists(sqlFilePath):
|
if not os.path.exists(sqlFilePath):
|
||||||
return False
|
return False
|
||||||
@@ -934,8 +959,8 @@ class WireguardConfiguration:
|
|||||||
files.sort(key=lambda x: x[1], reverse=True)
|
files.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
for f, ct in files:
|
for f, ct in files:
|
||||||
if RegexMatch(rf"^({self.Name})_(\d+)\\.(conf)$", f):
|
if RegexMatch(rf"^({self.Name})_(\d+)\.(conf)$", f):
|
||||||
s = re.search(rf"^({self.Name})_(\d+)\\.(conf)$", f)
|
s = re.search(rf"^({self.Name})_(\d+)\.(conf)$", f)
|
||||||
date = s.group(2)
|
date = s.group(2)
|
||||||
d = {
|
d = {
|
||||||
"filename": f,
|
"filename": f,
|
||||||
@@ -976,7 +1001,11 @@ class WireguardConfiguration:
|
|||||||
if backupFileName not in backups:
|
if backupFileName not in backups:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
os.remove(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName))
|
backupPath = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')
|
||||||
|
for ext in ('.conf', '.sql'):
|
||||||
|
f = os.path.join(backupPath, os.path.splitext(backupFileName)[0] + ext)
|
||||||
|
if os.path.exists(f):
|
||||||
|
os.remove(f)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -1007,8 +1036,10 @@ class WireguardConfiguration:
|
|||||||
with open(self.configPath, 'r') as f:
|
with open(self.configPath, 'r') as f:
|
||||||
original = [l.rstrip("\n") for l in f.readlines()]
|
original = [l.rstrip("\n") for l in f.readlines()]
|
||||||
allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort", "Table"]
|
allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort", "Table"]
|
||||||
|
awgKeys = []
|
||||||
if self.Protocol == 'awg':
|
if self.Protocol == 'awg':
|
||||||
allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "S3", "S4", "H1", "H2", "H3", "H4", "I1", "I2", "I3", "I4", "I5"]
|
awgKeys = ["Jc", "Jmin", "Jmax", "S1", "S2", "S3", "S4", "H1", "H2", "H3", "H4", "I1", "I2", "I3", "I4", "I5", "HeaderProtectionKey", "ContentPaddingAddition", "RekeyAfterTime", "RekeyTimeout", "RejectAfterTime", "KeepaliveTimeout", "MaxHandshakeAttempts", "RandomTrailers", "DisableCookies", "J1", "J2", "J3", "Itime"]
|
||||||
|
allowEdit += awgKeys
|
||||||
start = original.index("[Interface]")
|
start = original.index("[Interface]")
|
||||||
try:
|
try:
|
||||||
end = original.index("[Peer]")
|
end = original.index("[Peer]")
|
||||||
@@ -1022,7 +1053,10 @@ class WireguardConfiguration:
|
|||||||
if split[0] not in allowEdit:
|
if split[0] not in allowEdit:
|
||||||
new.append(original[line])
|
new.append(original[line])
|
||||||
for key in allowEdit:
|
for key in allowEdit:
|
||||||
new.insert(1, f"{key} = {str(newData[key]).strip()}")
|
val = str(newData.get(key, "")).strip()
|
||||||
|
if key in awgKeys and val == "":
|
||||||
|
continue
|
||||||
|
new.insert(1, f"{key} = {val}")
|
||||||
new.append("")
|
new.append("")
|
||||||
for line in range(end, len(original)):
|
for line in range(end, len(original)):
|
||||||
new.append(original[line])
|
new.append(original[line])
|
||||||
@@ -1072,6 +1106,12 @@ class WireguardConfiguration:
|
|||||||
doRenameStatement("_restrict_access")
|
doRenameStatement("_restrict_access")
|
||||||
doRenameStatement("_deleted")
|
doRenameStatement("_deleted")
|
||||||
doRenameStatement("_transfer")
|
doRenameStatement("_transfer")
|
||||||
|
doRenameStatement("_history_endpoint")
|
||||||
|
conn.execute(
|
||||||
|
self.infoTable.update()
|
||||||
|
.where(self.infoTable.c.ID == self.Name)
|
||||||
|
.values(ID=newConfigurationName)
|
||||||
|
)
|
||||||
|
|
||||||
self.AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName)
|
self.AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName)
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
@@ -1217,7 +1257,7 @@ class WireguardConfiguration:
|
|||||||
elif key == "OverridePeerSettings":
|
elif key == "OverridePeerSettings":
|
||||||
for (key, val) in value.items():
|
for (key, val) in value.items():
|
||||||
try:
|
try:
|
||||||
status, msg = self.__validateOverridePeerSettings(key, jinja2.Template(val).render(configuration=self.toJson()))
|
status, msg = self.__validateOverridePeerSettings(key, RenderSandboxedTemplate(val, configuration=self.toJson()))
|
||||||
if not status:
|
if not status:
|
||||||
return False, msg, key
|
return False, msg, key
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
+11
-11
@@ -1,17 +1,17 @@
|
|||||||
bcrypt==5.0.0
|
bcrypt==5.0.0
|
||||||
ifcfg==0.24
|
ifcfg==0.24
|
||||||
psutil==7.2.2
|
psutil==7.2.2
|
||||||
pyotp==2.9.0
|
pyotp==2.10.0
|
||||||
Flask==3.1.2
|
Flask==3.1.2
|
||||||
flask-cors==6.0.2
|
flask-cors==6.0.5
|
||||||
icmplib==3.0.4
|
icmplib==3.0.4
|
||||||
gunicorn==25.0.3
|
gunicorn==26.2.0
|
||||||
requests==2.32.5
|
requests==2.34.2
|
||||||
tcconfig==0.30.1
|
tcconfig==0.30.1
|
||||||
sqlalchemy==2.0.46
|
sqlalchemy==2.1.6
|
||||||
sqlalchemy_utils==0.42.1
|
sqlalchemy_utils==0.43.0
|
||||||
psycopg[binary]==3.3.3
|
psycopg[binary]==3.3.6
|
||||||
PyMySQL==1.1.2
|
PyMySQL==1.2.3
|
||||||
tzlocal==5.3.1
|
tzlocal==5.4.5
|
||||||
python-jose==3.5.0
|
python-jose[cryptography]==3.5.0
|
||||||
pydantic==2.12.5
|
pydantic==2.14.1
|
||||||
|
|||||||
@@ -11,6 +11,26 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>WGDashboard</title>
|
<title>WGDashboard</title>
|
||||||
<base href="./">
|
<base href="./">
|
||||||
|
<style>
|
||||||
|
#preloader{
|
||||||
|
width: 100vw; height: 100vh; display: flex
|
||||||
|
}
|
||||||
|
#preloader_placeholder{
|
||||||
|
width: 50px; height: 50px; background-color: #000; margin: auto;
|
||||||
|
animation: 3s ease-in-out fadeInPreloader infinite;
|
||||||
|
}
|
||||||
|
@keyframes fadeInPreloader {
|
||||||
|
0%{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50%{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<script>
|
<script>
|
||||||
const isViteDevMode = document.querySelector('script[src*="@vite/client"]') !== null;
|
const isViteDevMode = document.querySelector('script[src*="@vite/client"]') !== null;
|
||||||
const base = document.querySelector('base');
|
const base = document.querySelector('base');
|
||||||
@@ -20,7 +40,13 @@
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app">
|
||||||
|
<div id="preloader">
|
||||||
|
<div id="preloader_placeholder">
|
||||||
|
<img style="width: 100%" src="./img/Logo-2-128x128.png" alt="WGDashboard Admin" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script type="module" src="./src/main.js"></script>
|
<script type="module" src="./src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Generated
+2212
-3086
File diff suppressed because it is too large
Load Diff
+21
-21
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "4.3.3",
|
"version": "4.3.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"module": "es2022",
|
"module": "es2022",
|
||||||
@@ -12,36 +12,36 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@volar/language-server": "2.4.28",
|
"@volar/language-server": "2.4.30",
|
||||||
"@vue/language-server": "3.2.4",
|
"@vue/language-server": "3.3.13",
|
||||||
"@vuepic/vue-datepicker": "^12.1.0",
|
"@vuepic/vue-datepicker": "^14.0.0",
|
||||||
"@vueuse/core": "^14.2.1",
|
"@vueuse/core": "^15.0.0",
|
||||||
"@vueuse/shared": "^14.1.0",
|
"@vueuse/shared": "^15.0.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"bootstrap": "^5.3.2",
|
"bootstrap": "^5.3.2",
|
||||||
"bootstrap-icons": "^1.11.3",
|
"bootstrap-icons": "^1.13.2",
|
||||||
"cidr-tools": "^11.3.2",
|
"cidr-tools": "^13.0.4",
|
||||||
"css-color-converter": "^2.0.0",
|
"css-color-converter": "^2.0.0",
|
||||||
"dayjs": "^1.11.19",
|
"dayjs": "^1.11.23",
|
||||||
"electron-builder": "^26.7.0",
|
"electron-builder": "^26.18.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.5.0",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"is-cidr": "^6.0.3",
|
"is-cidr": "^7.0.3",
|
||||||
"npm": "^11.8.0",
|
"npm": "^12.2.1",
|
||||||
"ol": "^10.7.0",
|
"ol": "^10.11.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^4.1.0",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"qrcodejs": "^1.0.0",
|
"qrcodejs": "^1.0.0",
|
||||||
"simple-code-editor": "^2.0.9",
|
"simple-code-editor": "^2.0.9",
|
||||||
"uuid": "^13.0.0",
|
"uuid": "^14.0.3",
|
||||||
"vue": "^3.5.31",
|
"vue": "^3.5.32",
|
||||||
"vue-chartjs": "^5.3.3",
|
"vue-chartjs": "^5.3.4",
|
||||||
"vue-router": "^5.0.4"
|
"vue-router": "^5.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^6.0.4",
|
"@vitejs/plugin-vue": "^6.0.10",
|
||||||
"vite": "^7.3.1"
|
"vite": "^8.3.5"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"tar": "^7.5.6"
|
"tar": "^7.5.6"
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ const deleteConfigurationModal = ref(false)
|
|||||||
v-model="data[key]"
|
v-model="data[key]"
|
||||||
:id="'configuration_' + key">
|
:id="'configuration_' + key">
|
||||||
</div>
|
</div>
|
||||||
<div v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5']"
|
<div v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5', 'HeaderProtectionKey', 'ContentPaddingAddition', 'RekeyAfterTime', 'RekeyTimeout', 'RejectAfterTime', 'KeepaliveTimeout', 'MaxHandshakeAttempts', 'RandomTrailers', 'DisableCookies']"
|
||||||
v-if="configurationInfo.Protocol === 'awg'">
|
v-if="configurationInfo.Protocol === 'awg'">
|
||||||
<label :for="'configuration_' + key" class="form-label">
|
<label :for="'configuration_' + key" class="form-label">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
|
|||||||
+10
-1
@@ -6,17 +6,26 @@ export default {
|
|||||||
components: {LocaleText},
|
components: {LocaleText},
|
||||||
props: {
|
props: {
|
||||||
data: Object,
|
data: Object,
|
||||||
saving: Boolean
|
saving: Boolean,
|
||||||
|
defaultEnabled: Boolean
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return{
|
return{
|
||||||
enable: false
|
enable: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
const hasKey = !!(this.data && this.data.preshared_key && this.data.preshared_key.length > 0)
|
||||||
|
if (hasKey || this.defaultEnabled){
|
||||||
|
this.enable = true
|
||||||
|
}
|
||||||
|
},
|
||||||
watch:{
|
watch:{
|
||||||
enable(){
|
enable(){
|
||||||
if (this.enable) {
|
if (this.enable) {
|
||||||
|
if (!this.data.preshared_key){
|
||||||
this.data.preshared_key = window.wireguard.generateKeypair().presharedKey
|
this.data.preshared_key = window.wireguard.generateKeypair().presharedKey
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.data.preshared_key = ""
|
this.data.preshared_key = ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const peerData = ref({
|
|||||||
keepalive: parseInt(dashboardStore.Configuration.Peers.peer_keep_alive),
|
keepalive: parseInt(dashboardStore.Configuration.Peers.peer_keep_alive),
|
||||||
mtu: parseInt(dashboardStore.Configuration.Peers.peer_mtu),
|
mtu: parseInt(dashboardStore.Configuration.Peers.peer_mtu),
|
||||||
preshared_key: "",
|
preshared_key: "",
|
||||||
preshared_key_bulkAdd: false,
|
preshared_key_bulkAdd: Boolean(dashboardStore.Configuration.Peers.peer_preshared_key_default),
|
||||||
allowed_ips_validation: true,
|
allowed_ips_validation: true,
|
||||||
})
|
})
|
||||||
const availableIp = ref([])
|
const availableIp = ref([])
|
||||||
@@ -128,7 +128,7 @@ watch(() => {
|
|||||||
<EndpointAllowedIps :saving="saving" :data="peerData"></EndpointAllowedIps>
|
<EndpointAllowedIps :saving="saving" :data="peerData"></EndpointAllowedIps>
|
||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
<div class="col-sm" v-if="!peerData.bulkAdd">
|
<div class="col-sm" v-if="!peerData.bulkAdd">
|
||||||
<PresharedKeyInput :saving="saving" :data="peerData" :bulk="peerData.bulkAdd"></PresharedKeyInput>
|
<PresharedKeyInput :saving="saving" :data="peerData" :bulk="peerData.bulkAdd" :defaultEnabled="Boolean(dashboardStore.Configuration.Peers.peer_preshared_key_default)"></PresharedKeyInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default {
|
|||||||
keepalive: parseInt(this.dashboardStore.Configuration.Peers.peer_keep_alive),
|
keepalive: parseInt(this.dashboardStore.Configuration.Peers.peer_keep_alive),
|
||||||
mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu),
|
mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu),
|
||||||
preshared_key: "",
|
preshared_key: "",
|
||||||
preshared_key_bulkAdd: false,
|
preshared_key_bulkAdd: Boolean(this.dashboardStore.Configuration.Peers.peer_preshared_key_default),
|
||||||
},
|
},
|
||||||
availableIp: undefined,
|
availableIp: undefined,
|
||||||
availableIpSearchString: "",
|
availableIpSearchString: "",
|
||||||
@@ -133,7 +133,7 @@ export default {
|
|||||||
<hr class="mb-0 mt-2">
|
<hr class="mb-0 mt-2">
|
||||||
<div class="row gy-3">
|
<div class="row gy-3">
|
||||||
<div class="col-sm" v-if="!this.data.bulkAdd">
|
<div class="col-sm" v-if="!this.data.bulkAdd">
|
||||||
<PresharedKeyInput :saving="saving" :data="data" :bulk="this.data.bulkAdd"></PresharedKeyInput>
|
<PresharedKeyInput :saving="saving" :data="data" :bulk="this.data.bulkAdd" :defaultEnabled="Boolean(this.dashboardStore.Configuration.Peers.peer_preshared_key_default)"></PresharedKeyInput>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async fetchLog(){
|
async fetchLog(){
|
||||||
this.dataLoading = true;
|
this.dataLoading = true;
|
||||||
await fetchGet(`/api/getPeerScheduleJobLogs/${this.configurationInfo.Name}`, {}, (res) => {
|
await fetchGet(`/api/PeerScheduleJobLogs/${this.configurationInfo.Name}`, {}, (res) => {
|
||||||
this.data = res.data;
|
this.data = res.data;
|
||||||
this.logFetchTime = dayjs().format("YYYY-MM-DD HH:mm:ss")
|
this.logFetchTime = dayjs().format("YYYY-MM-DD HH:mm:ss")
|
||||||
this.dataLoading = false;
|
this.dataLoading = false;
|
||||||
|
|||||||
+4
-7
@@ -2,7 +2,7 @@
|
|||||||
import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJobsComponents/scheduleDropdown.vue";
|
import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJobsComponents/scheduleDropdown.vue";
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
import {fetchPost} from "@/utilities/fetch.js";
|
import {fetchPost, fetchPut, fetchDelete} from "@/utilities/fetch.js";
|
||||||
import { VueDatePicker } from "@vuepic/vue-datepicker";
|
import { VueDatePicker } from "@vuepic/vue-datepicker";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
@@ -46,9 +46,8 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
save(){
|
save(){
|
||||||
if (this.job.Field && this.job.Operator && this.job.Action && this.job.Value){
|
if (this.job.Field && this.job.Operator && this.job.Action && this.job.Value){
|
||||||
fetchPost(`/api/savePeerScheduleJob`, {
|
const fn = this.newJob ? fetchPost : fetchPut;
|
||||||
Job: this.job
|
fn(`/api/PeerScheduleJob`, this.job, (res) => {
|
||||||
}, (res) => {
|
|
||||||
if (res.status){
|
if (res.status){
|
||||||
this.edit = false;
|
this.edit = false;
|
||||||
this.store.newMessage("Server", "Peer job saved", "success")
|
this.store.newMessage("Server", "Peer job saved", "success")
|
||||||
@@ -84,9 +83,7 @@ export default {
|
|||||||
},
|
},
|
||||||
delete(){
|
delete(){
|
||||||
if(this.job.CreationDate){
|
if(this.job.CreationDate){
|
||||||
fetchPost(`/api/deletePeerScheduleJob`, {
|
fetchDelete(`/api/PeerScheduleJob`, this.job, (res) => {
|
||||||
Job: this.job
|
|
||||||
}, (res) => {
|
|
||||||
if (!res.status){
|
if (!res.status){
|
||||||
this.store.newMessage("Server", res.message, "danger")
|
this.store.newMessage("Server", res.message, "danger")
|
||||||
this.$emit('delete')
|
this.$emit('delete')
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import PeersDefaultSettingsInput from "@/components/settingsComponent/peersDefau
|
|||||||
targetData="peer_mtu" title="MTU"></PeersDefaultSettingsInput>
|
targetData="peer_mtu" title="MTU"></PeersDefaultSettingsInput>
|
||||||
<PeersDefaultSettingsInput
|
<PeersDefaultSettingsInput
|
||||||
targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
|
targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
|
||||||
|
<PeersDefaultSettingsInput
|
||||||
|
targetData="peer_preshared_key_default" title="Pre-Shared Key Default"></PeersDefaultSettingsInput>
|
||||||
<PeersDefaultSettingsInput
|
<PeersDefaultSettingsInput
|
||||||
targetData="remote_endpoint" title="Peer Remote Endpoint"
|
targetData="remote_endpoint" title="Peer Remote Endpoint"
|
||||||
:warning="true" warningText="This will be changed globally, and will be apply to all peer's QR code and configuration file."
|
:warning="true" warningText="This will be changed globally, and will be apply to all peer's QR code and configuration file."
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.value = this.store.Configuration.Peers[this.targetData];
|
this.value = this.store.Configuration.Peers[this.targetData];
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
isBoolean(){
|
||||||
|
return typeof this.value === "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
methods:{
|
methods:{
|
||||||
async useValidation(){
|
async useValidation(){
|
||||||
if(this.changed){
|
if(this.changed){
|
||||||
@@ -67,7 +72,14 @@ export default {
|
|||||||
<LocaleText :t="this.title"></LocaleText>
|
<LocaleText :t="this.title"></LocaleText>
|
||||||
</small></strong>
|
</small></strong>
|
||||||
</label>
|
</label>
|
||||||
<input type="text" class="form-control"
|
<div v-if="isBoolean" class="form-check form-switch">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch"
|
||||||
|
v-model="this.value"
|
||||||
|
:id="this.uuid"
|
||||||
|
@change="this.changed = true; useValidation()"
|
||||||
|
:disabled="this.updating">
|
||||||
|
</div>
|
||||||
|
<input v-else type="text" class="form-control"
|
||||||
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
|
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
|
||||||
:id="this.uuid"
|
:id="this.uuid"
|
||||||
v-model="this.value"
|
v-model="this.value"
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<script setup async>
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
|
const providers = ref([])
|
||||||
|
const oidcEnabled = ref(false)
|
||||||
|
|
||||||
|
await fetchGet("/api/oidc/providers", {}, async (res) => {
|
||||||
|
if (res.status){
|
||||||
|
await Promise.all(Object.entries(res.data).map(([key, value]) => {
|
||||||
|
return fetch(`${value.issuer}/.well-known/openid-configuration`).then((res) => {
|
||||||
|
providers.value.push({
|
||||||
|
Provider: key,
|
||||||
|
ProviderInfo: value
|
||||||
|
})
|
||||||
|
}).catch(() => {
|
||||||
|
console.log("Failed to request " + key)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
oidcEnabled.value = providers.value.length > 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(providers.value)
|
||||||
|
const oidcUrl = (provider) => {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
client_id: provider.ProviderInfo.client_id,
|
||||||
|
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname,
|
||||||
|
response_type: 'code',
|
||||||
|
state: provider.Provider,
|
||||||
|
scope: 'openid email profile'
|
||||||
|
})
|
||||||
|
console.log(params.entries())
|
||||||
|
const url = new URL(provider.ProviderInfo.openid_configuration.authorization_endpoint)
|
||||||
|
url.search = params.toString()
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-if="oidcEnabled">
|
||||||
|
<hr class="mb-4"/>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a class="btn bg-body border w-100 rounded-3"
|
||||||
|
:href="oidcUrl(provider)"
|
||||||
|
v-for="provider in providers" style="flex: 1 1 0;">
|
||||||
|
<LocaleText :t="'Sign In With ' + provider.Provider"/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<script setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -7,9 +7,23 @@ import '@vuepic/vue-datepicker/dist/main.css'
|
|||||||
import {createApp, markRaw} from 'vue'
|
import {createApp, markRaw} from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router/router.js'
|
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
const state = params.get('state')
|
||||||
|
const code = params.get('code')
|
||||||
|
|
||||||
|
if (state && code) {
|
||||||
|
window.history.replaceState({}, '', window.location.pathname);
|
||||||
|
console.log('After replaceState:', window.location.href);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const initApp = async () => {
|
||||||
|
const { default: router } = await import('./router/router.js')
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
@@ -22,3 +36,36 @@ pinia.use(({ store }) => {
|
|||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
console.log('After router import:', window.location.href)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getUrl = (url) => {
|
||||||
|
if (import.meta.env.MODE === 'development') {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return `./.${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state && code){
|
||||||
|
await fetch(getUrl('/api/oidc/authenticate'), {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
provider: state,
|
||||||
|
code: code,
|
||||||
|
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname
|
||||||
|
})
|
||||||
|
}).then(res => res.json()).then( async (res) => {
|
||||||
|
if (res.status){
|
||||||
|
window.location.replace(window.location.protocol + '//' + window.location.host + window.location.pathname)
|
||||||
|
}else{
|
||||||
|
await initApp()
|
||||||
|
const store = DashboardConfigurationStore()
|
||||||
|
store.newMessage('Server OIDC', res.message, 'danger')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
await initApp()
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const getHeaders = () => {
|
|||||||
let headers = {
|
let headers = {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
try{
|
||||||
const store = DashboardConfigurationStore();
|
const store = DashboardConfigurationStore();
|
||||||
const crossServer = store.getActiveCrossServer();
|
const crossServer = store.getActiveCrossServer();
|
||||||
if (crossServer){
|
if (crossServer){
|
||||||
@@ -16,17 +17,22 @@ const getHeaders = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getUrl = (url) => {
|
export const getUrl = (url) => {
|
||||||
|
try{
|
||||||
const store = DashboardConfigurationStore();
|
const store = DashboardConfigurationStore();
|
||||||
const apiKey = store.getActiveCrossServer();
|
const apiKey = store.getActiveCrossServer();
|
||||||
if (apiKey){
|
if (apiKey){
|
||||||
return `${apiKey.host}${url}`
|
return `${apiKey.host}${url}`
|
||||||
}
|
}
|
||||||
|
}catch (e){}
|
||||||
if (import.meta.env.MODE === 'development') {
|
if (import.meta.env.MODE === 'development') {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@@ -80,3 +86,49 @@ export const fetchPost = async (url, body, callback) => {
|
|||||||
router.push({path: '/signin'})
|
router.push({path: '/signin'})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const fetchPut = async (url, body, callback) => {
|
||||||
|
await fetch(`${getUrl(url)}`, {
|
||||||
|
headers: getHeaders(),
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
}).then((x) => {
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
if (!x.ok){
|
||||||
|
if (x.status !== 200){
|
||||||
|
if (x.status === 401){
|
||||||
|
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
||||||
|
}
|
||||||
|
throw new Error(x.statusText)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return x.json()
|
||||||
|
}
|
||||||
|
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||||
|
console.log("Error:", x)
|
||||||
|
router.push({path: '/signin'})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchDelete = async (url, body, callback) => {
|
||||||
|
await fetch(`${getUrl(url)}`, {
|
||||||
|
headers: getHeaders(),
|
||||||
|
method: "DELETE",
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
}).then((x) => {
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
if (!x.ok){
|
||||||
|
if (x.status !== 200){
|
||||||
|
if (x.status === 401){
|
||||||
|
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
||||||
|
}
|
||||||
|
throw new Error(x.statusText)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return x.json()
|
||||||
|
}
|
||||||
|
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||||
|
console.log("Error:", x)
|
||||||
|
router.push({path: '/signin'})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -49,11 +49,20 @@ export default {
|
|||||||
H2: 0,
|
H2: 0,
|
||||||
H3: 0,
|
H3: 0,
|
||||||
H4: 0,
|
H4: 0,
|
||||||
I1: "0",
|
I1: "",
|
||||||
I2: "0",
|
I2: "",
|
||||||
I3: "0",
|
I3: "",
|
||||||
I4: "0",
|
I4: "",
|
||||||
I5: "0"
|
I5: "",
|
||||||
|
HeaderProtectionKey: "",
|
||||||
|
ContentPaddingAddition: "0-64",
|
||||||
|
RekeyAfterTime: "100-140",
|
||||||
|
RekeyTimeout: "4-7",
|
||||||
|
RejectAfterTime: "160-200",
|
||||||
|
KeepaliveTimeout: "8-12",
|
||||||
|
MaxHandshakeAttempts: "14-20",
|
||||||
|
RandomTrailers: "on",
|
||||||
|
DisableCookies: "on"
|
||||||
},
|
},
|
||||||
numberOfAvailableIPs: "0",
|
numberOfAvailableIPs: "0",
|
||||||
error: false,
|
error: false,
|
||||||
@@ -72,9 +81,15 @@ export default {
|
|||||||
this.newConfiguration[key] = this.rand(1, 2**31);
|
this.newConfiguration[key] = this.rand(1, 2**31);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize I1 to I5 as "0"
|
// Initialize HeaderProtectionKey
|
||||||
['I1', 'I2', 'I3', 'I4', 'I5'].forEach(key => {
|
let headerProtectionKey = new Uint8Array(32);
|
||||||
this.newConfiguration[key] = "0";
|
window.crypto.getRandomValues(headerProtectionKey);
|
||||||
|
this.newConfiguration.HeaderProtectionKey = btoa(String.fromCharCode(...headerProtectionKey));
|
||||||
|
|
||||||
|
// S1-S4 are recommended be the same to work with RandomTrailers and must 12 or more to work with HeaderProtectionKey
|
||||||
|
let constantS = 12 + this.rand(1, 2**5);
|
||||||
|
['S1', 'S2', 'S3', 'S4'].forEach(key => {
|
||||||
|
this.newConfiguration[key] = constantS;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -388,7 +403,7 @@ export default {
|
|||||||
|
|
||||||
<div class="card rounded-3"
|
<div class="card rounded-3"
|
||||||
v-if="this.newConfiguration.Protocol === 'awg'"
|
v-if="this.newConfiguration.Protocol === 'awg'"
|
||||||
v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5']">
|
v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5', 'HeaderProtectionKey', 'ContentPaddingAddition', 'RekeyAfterTime', 'RekeyTimeout', 'RejectAfterTime', 'KeepaliveTimeout', 'MaxHandshakeAttempts', 'RandomTrailers', 'DisableCookies']">
|
||||||
<div class="card-header">{{ key }}</div>
|
<div class="card-header">{{ key }}</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import {GetLocale} from "@/utilities/locale.js";
|
|||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
import SignInInput from "@/components/signIn/signInInput.vue";
|
import SignInInput from "@/components/signIn/signInInput.vue";
|
||||||
import SignInTOTP from "@/components/signIn/signInTOTP.vue";
|
import SignInTOTP from "@/components/signIn/signInTOTP.vue";
|
||||||
|
import SignInOIDC from "@/components/signIn/signInOIDC.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "signin",
|
name: "signin",
|
||||||
components: {SignInTOTP, SignInInput, LocaleText, RemoteServerList, Message},
|
components: {SignInOIDC, SignInTOTP, SignInInput, LocaleText, RemoteServerList, Message},
|
||||||
async setup(){
|
async setup(){
|
||||||
const store = DashboardConfigurationStore()
|
const store = DashboardConfigurationStore()
|
||||||
let theme = "dark"
|
let theme = "dark"
|
||||||
@@ -171,7 +172,7 @@ export default {
|
|||||||
</form>
|
</form>
|
||||||
<RemoteServerList v-else></RemoteServerList>
|
<RemoteServerList v-else></RemoteServerList>
|
||||||
|
|
||||||
<div class="d-flex mt-3" v-if="!this.store.IsElectronApp">
|
<div class="d-flex mt-3 flex-column" v-if="!this.store.IsElectronApp">
|
||||||
<div class="form-check form-switch ms-auto">
|
<div class="form-check form-switch ms-auto">
|
||||||
<input
|
<input
|
||||||
v-model="this.store.CrossServerConfiguration.Enable"
|
v-model="this.store.CrossServerConfiguration.Enable"
|
||||||
@@ -184,6 +185,8 @@ export default {
|
|||||||
<LocaleText t="Access Remote Server"></LocaleText>
|
<LocaleText t="Access Remote Server"></LocaleText>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SignInOIDC />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Generated
+1461
-1772
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "v1.0",
|
"version": "v1.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,20 +9,20 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.20.0",
|
||||||
"bootstrap": "^5.3.6",
|
"bootstrap": "^5.3.8",
|
||||||
"bootstrap-icons": "^1.13.1",
|
"bootstrap-icons": "^1.13.2",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.23",
|
||||||
"oidc-client-ts": "^3.2.1",
|
"oidc-client-ts": "^3.5.0",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^4.1.0",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^14.0.3",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.44",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^5.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^6.0.10",
|
||||||
"vite": "^6.2.4",
|
"vite": "^8.3.5",
|
||||||
"vite-plugin-vue-devtools": "^7.7.2"
|
"vite-plugin-vue-devtools": "^8.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import './assets/main.css'
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from "@/router/router.js";
|
|
||||||
import {createPinia} from "pinia";
|
import {createPinia} from "pinia";
|
||||||
|
|
||||||
import 'bootstrap/dist/js/bootstrap.bundle.js'
|
import 'bootstrap/dist/js/bootstrap.bundle.js'
|
||||||
@@ -13,7 +12,13 @@ const params = new URLSearchParams(window.location.search)
|
|||||||
const state = params.get('state')
|
const state = params.get('state')
|
||||||
const code = params.get('code')
|
const code = params.get('code')
|
||||||
|
|
||||||
|
if (state && code) {
|
||||||
|
window.history.replaceState({}, '', window.location.pathname);
|
||||||
|
}
|
||||||
|
|
||||||
const initApp = async () => {
|
const initApp = async () => {
|
||||||
|
debugger
|
||||||
|
const { default: router } = await import('./router/router.js')
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const serverInformation = await axiosGet("/api/serverInformation", {})
|
const serverInformation = await axiosGet("/api/serverInformation", {})
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
@@ -31,12 +36,10 @@ if (state && code){
|
|||||||
code: code,
|
code: code,
|
||||||
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname
|
redirect_uri: window.location.protocol + '//' + window.location.host + window.location.pathname
|
||||||
}).then(async (data) => {
|
}).then(async (data) => {
|
||||||
let url = new URL(window.location.href);
|
if (data.status){
|
||||||
url.search = '';
|
window.location.replace(window.location.protocol + '//' + window.location.host + window.location.pathname)
|
||||||
history.replaceState({}, document.title, url.toString());
|
}else {
|
||||||
|
|
||||||
await initApp()
|
await initApp()
|
||||||
if (!data.status){
|
|
||||||
const store = clientStore()
|
const store = clientStore()
|
||||||
store.newNotification(data.message, 'danger')
|
store.newNotification(data.message, 'danger')
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -1 +0,0 @@
|
|||||||
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
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
@media screen and (min-width: 576px){.backLink[data-v-f874264d]{display:none}}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
@media screen and (max-width: 576px){.clientListContainer.hide[data-v-a8650ee3],.clientViewerContainer.hide[data-v-a8650ee3]{display:none!important}.clientListContainer[data-v-a8650ee3]{border-right:none!important;animation:blurIn-a8650ee3 .2s ease-in-out forwards}.clientViewerContainer[data-v-a8650ee3]{animation:blurIn-a8650ee3 .2s ease-in-out forwards}}@keyframes blurIn-a8650ee3{0%{filter:blur(8px)}to{filter:blur(0px)}}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
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};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.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 +0,0 @@
|
|||||||
@media screen and (max-width: 992px){.apiKey-card-body{&[data-v-a76253c8]{flex-direction:column!important;align-items:start!important}div.ms-auto[data-v-a76253c8]{margin-left:0!important}div[data-v-a76253c8]{width:100%;align-items:start!important}small[data-v-a76253c8]{margin-right:auto}}}.apiKey-move[data-v-f7e62927],.apiKey-enter-active[data-v-f7e62927],.apiKey-leave-active[data-v-f7e62927]{transition:all .5s ease}.apiKey-enter-from[data-v-f7e62927],.apiKey-leave-to[data-v-f7e62927]{opacity:0;transform:translateY(30px) scale(.9)}.apiKey-leave-active[data-v-f7e62927]{position:absolute;width:100%}.dropdown-menu[data-v-4e34593e]{width:100%}
|
|
||||||
-1
@@ -1 +0,0 @@
|
|||||||
.list-group{&[data-v-4aa2aed9]:first-child{border-top-left-radius:var(--bs-border-radius-lg);border-top-right-radius:var(--bs-border-radius-lg)}&[data-v-4aa2aed9]:last-child{border-bottom-left-radius:var(--bs-border-radius-lg);border-bottom-right-radius:var(--bs-border-radius-lg)}}
|
|
||||||
src/static/dist/WGDashboardAdmin/assets/dashboardSettingsWireguardConfigurationAutostart-DWZoKQw6.js
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
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
@@ -1 +0,0 @@
|
|||||||
.table[data-v-7b6e949e]>:not(caption)>*>*{padding-left:0!important;padding-right:1rem!important}.list-group-item[data-v-e0f0e683]{border-radius:0!important;border-left:0!important;border-right:0!important}.list-group-item[data-v-e0f0e683]:first-child{border-top:0!important}.url[data-v-e0f0e683]{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;font-size:.9rem}
|
|
||||||
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 +0,0 @@
|
|||||||
var n={},l;function u(){if(l)return n;l=1;const i=new Uint8Array(512),t=new Uint8Array(256);return(function(){let r=1;for(let e=0;e<255;e++)i[e]=r,t[r]=e,r<<=1,r&256&&(r^=285);for(let e=255;e<512;e++)i[e]=i[e-255]})(),n.log=function(r){if(r<1)throw new Error("log("+r+")");return t[r]},n.exp=function(r){return i[r]},n.mul=function(r,e){return r===0||e===0?0:i[t[r]+t[e]]},n}export{u as r};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
function x(n){return n.includes(":")?6:n.includes(".")?4:0}function p(n){const t=x(n);if(!t)throw new Error(`Invalid IP address: ${n}`);let e=0n,r=0n;const s=Object.create(null);if(t===4)for(const i of n.split(".").map(BigInt).reverse())e+=i*2n**r,r+=8n;else{if(n.includes(".")&&(s.ipv4mapped=!0,n=n.split(":").map(o=>{if(o.includes(".")){const[u,l,f,a]=o.split(".").map(d=>Number(d).toString(16).padStart(2,"0"));return`${u}${l}:${f}${a}`}else return o}).join(":")),n.includes("%")){let o;[,n,o]=/(.+)%(.+)/.exec(n)||[],s.scopeid=o}const i=n.split(":"),c=i.indexOf("");if(c!==-1)for(;i.length<8;)i.splice(c,0,"");for(const o of i.map(u=>BigInt(parseInt(u||"0",16))).reverse())e+=o*2n**r,r+=16n}return s.number=e,s.version=t,s}function m({number:n,version:t,ipv4mapped:e,scopeid:r},{compress:s=!0,hexify:i=!1}={}){let c=t===4?24n:112n;const o=t===4?8n:16n;let u=n;const l=[];for(;c>0n;){const f=2n**c;l.push(u/f),u=n%f,c-=o}if(l.push(u),t===4)return l.join(".");{let f="";if(e&&!i){for(const[a,d]of l.entries())a<6?f+=`${d.toString(16)}:`:f+=`${String(d>>8n)}.${String(d&255n)}${a===6?".":""}`;s&&(f=y(f.split(":")))}else s?f=y(l.map(a=>a.toString(16))):f=l.map(a=>a.toString(16)).join(":");return r?`${f}%${r}`:f}}function B(n,{compress:t=!0,hexify:e=!1}={}){return m(p(n),{compress:t,hexify:e})}function y(n){let t=null,e=null;for(const[r,s]of n.entries())s==="0"?e?e.add(r):e=new Set([r]):e&&((!t||e.size>t.size)&&(t=e),e=null);(!t&&e||e&&t&&e.size>t.size)&&(t=e);for(const r of t||[])n[r]=":";return n.filter(Boolean).join(":").replace(/:{2,}/,"::")}const v={4:32,6:128};function b(n){return Array.from(new Set(n))}function N(n){return n.includes("/")?x(n):0}function I(n,t){const{number:e,version:r}=p(n.replace(/\/.+/,"")),{number:s,version:i}=p(t.replace(/\/.+/,""));return r===i?e-s>0n?1:e-s<0n?-1:0:r>i?1:0}function P(n,{compress:t=!0,hexify:e=!1}={}){const{start:r,end:s,prefix:i,version:c,prefixPresent:o}=g(n);return r!==s||o?`${B(m({number:r,version:c}),{compress:t,hexify:e})}/${i}`:B(n,{compress:t,hexify:e})}function h(n,t){return Array.isArray(n)?n.map(e=>h(e,t)):P(n,t)}function g(n){const t=N(n),e=Object.create(null);let r;if(t)r=n,e.version=t;else{const S=x(n);if(S)r=`${n}/${v[S]}`,e.version=S;else throw new Error(`Network is not a CIDR or IP: ${n}`)}const[s,i]=r.split("/");if(!/^[0-9]+$/.test(i))throw new Error(`Network is not a CIDR or IP: ${n}`);const{number:c,version:o,ipv4mapped:u,scopeid:l}=p(s);e.ip=m({number:c,version:o,ipv4mapped:u,scopeid:l}),e.cidr=`${e.ip}/${i}`,e.prefix=i,e.prefixPresent=!!t;const f=v[o],a=c.toString(2).padStart(f,"0"),d=Number(f-Number(i)),w=a.substring(0,f-d);return e.start=BigInt(`0b${w}${"0".repeat(d)}`),e.end=BigInt(`0b${w}${"1".repeat(d)}`),e}function z(n,t){return!(t.start<n.start||t.end>n.end)}function C(n){return n===0n?0n:2n**BigInt(String(n.toString(2).length-1))}function $(n){if(n.end-n.start===1n)return n.end%2n===0n?[{start:n.start,end:n.start},{start:n.end,end:n.end}]:[{start:n.start,end:n.end}];const t=A(n.end,n.start);let e=C(t),r,s;if(t===e&&n.start+t===n.end)return[n];if(n.start%e===0n)r=n.start,s=r+e-1n;else if(r=n.end/e*e,r+e-1n>n.end){for(r=(n.end/e-1n)*e;r<n.start;)e/=2n,r=(n.end/e-1n)*e;s=r+e-1n}else r=n.end/e*e,s=r+e-1n;let i=[{start:r,end:s}];return r!==n.start&&(i=i.concat($({start:n.start,end:r-1n}))),s!==n.end&&(i=i.concat($({start:s+1n,end:n.end}))),i}function A(n,t){return n+1n-t}function j(n,t){const e=h(m({number:BigInt(n.start.toString()),version:t})),r=A(n.end,n.start).toString(2),s=v[t]-(r.match(/0/g)||[]).length;return`${e}/${s}`}function O(n){const t={4:{},6:{}};for(const{start:e,end:r,version:s}of n)t[s][String(e)]||(t[s][String(e)]={start:0,end:0}),t[s][String(r)]||(t[s][String(r)]={start:0,end:0}),t[s][String(e)].start+=1,t[s][String(r)].end+=1;return t}function V(n){let t=null,e=null;const r=Object.keys(n);let s=0;const i=[];for(const[c,o]of r.entries()){const u=n[String(o)];if(t===null&&u.start&&(t=BigInt(o)),u.end&&(e=BigInt(o)),t===null)continue;u.start&&(s+=u.start),u.end&&(s-=u.end);const l=r[c+1];if(u.end&&s===0&&l&&BigInt(l)-BigInt(o)>1){for(const f of $({start:t,end:e}))i.push(f);t=null,e=null}else if(c===r.length-1)for(const f of $({start:t,end:e}))i.push(f)}return i}function k(n){const t=b((Array.isArray(n)?n:[n]).sort(I).map(g)),e=O(t),r={4:[],6:[]};for(const s of[4,6])r[s]=V(e[s]).map(i=>j(i,s));return[...r[4].sort(I),...r[6].sort(I)]}function*E(n){const t=b(Array.isArray(n)?n:[n]);for(const e of k(t)){const{start:r,end:s,version:i}=g(e);for(let c=r;c<=s;c++)yield h(m({number:c,version:i}))}}function R(n,t){const e=b(Array.isArray(n)?n:[n]),r=b(Array.isArray(t)?t:[t]),s=r.length;let i=0;for(const c of e){const o=g(c);for(const u of r){const l=g(u);if(o.version===l.version&&z(o,l)){i++;continue}}}return i===s}export{R as c,E as e,k as m,g as p};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.agentContainer[data-v-f37f608d]{--agentHeight: 100vh;position:absolute;z-index:9999;top:0;left:100%;width:450px;box-shadow:0 10px 30px #0000004d;backdrop-filter:blur(8px);background:linear-gradient(var(--degree),#009dff52 var(--distance2),#F9464752 100%)}.agentContainer.enabled[data-v-f37f608d]{height:calc(var(--agentHeight) - 1rem)}@media screen and (max-width: 768px){.agentContainer[data-v-f37f608d]{--agentHeight: 100vh !important;top:0;left:0;max-height:calc(var(--agentHeight) - 58px - 1rem);width:calc(100% - 1rem)}}.agentChatroomBody[data-v-f37f608d]{flex:1 1 auto;overflow-y:auto;max-height:calc(var(--agentHeight) - 70px - 244px)}@media screen and (max-width: 768px){.navbar-container[data-v-982f1a52]{position:absolute!important;z-index:1000;animation-duration:.4s;animation-fill-mode:both;display:none;animation-timing-function:cubic-bezier(.82,.58,.17,.9)}.navbar-container.active[data-v-982f1a52]{animation-direction:normal;display:block!important;animation-name:zoomInFade-982f1a52}}.navbar-container[data-v-982f1a52]{height:100vh;position:relative}@supports (height: 100dvh){@media screen and (max-width: 768px){.navbar-container[data-v-982f1a52]{height:calc(100dvh - 58px)}}}@keyframes zoomInFade-982f1a52{0%{opacity:0;transform:translateY(60px);filter:blur(3px)}to{opacity:1;transform:translateY(0);filter:blur(0px)}}.slideIn-enter-active[data-v-982f1a52],.slideIn-leave-active[data-v-982f1a52]{transition:all .3s cubic-bezier(.82,.58,.17,1)}.slideIn-enter-from[data-v-982f1a52],.slideIn-leave-to[data-v-982f1a52]{transform:translateY(30px);filter:blur(3px);opacity:0}main[data-v-0c6a5068]{height:100vh}@supports (height: 100dvh){@media screen and (max-width: 768px){main[data-v-0c6a5068]{height:calc(100dvh - 58px)}}}
|
|
||||||
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 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
.message[data-v-94c76b54]{width:100%}@media screen and (min-width: 576px){.message[data-v-94c76b54]{width:400px}}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.protocolBtnGroup a[data-v-14fcf0ee]{transition:all .2s ease-in-out}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.ol-layer canvas{border-radius:var(--bs-border-radius-lg)!important}#map{height:300px}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.list-move[data-v-ed72944d],.list-enter-active[data-v-ed72944d],.list-leave-active[data-v-ed72944d]{transition:all .3s ease}.list-enter-from[data-v-ed72944d],.list-leave-to[data-v-ed72944d]{opacity:0;transform:translateY(10px)}.list-leave-active[data-v-ed72944d]{position:absolute}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.list-move[data-v-99c0844e],.list-enter-active[data-v-99c0844e],.list-leave-active[data-v-99c0844e]{transition:all .5s ease}.list-enter-from[data-v-99c0844e],.list-leave-to[data-v-99c0844e]{opacity:0;transform:scale(.9)}.list-leave-active[data-v-99c0844e]{position:absolute;width:100%}.assignment[data-v-99c0844e]:last-child{margin-bottom:0!important}[data-v-b52659b4]:focus{outline:none}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
.slide-up-enter-active[data-v-b0ea2d46],.slide-up-leave-active[data-v-b0ea2d46]{transition:all .2s cubic-bezier(.42,0,.22,1)}.slide-up-enter-from[data-v-b0ea2d46],.slide-up-leave-to[data-v-b0ea2d46]{opacity:0;transform:scale(.9)}@keyframes spin-b0ea2d46{0%{transform:rotate(0)}to{transform:rotate(360deg)}}#check[data-v-b0ea2d46]{animation:cubic-bezier(.42,0,.22,1.3) .7s spin-b0ea2d46}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
.schedulePeerJobTransition-move[data-v-5bbdd42b],.schedulePeerJobTransition-enter-active[data-v-5bbdd42b],.schedulePeerJobTransition-leave-active[data-v-5bbdd42b]{transition:all .4s cubic-bezier(.82,.58,.17,.9)}.schedulePeerJobTransition-enter-from[data-v-5bbdd42b],.schedulePeerJobTransition-leave-to[data-v-5bbdd42b]{opacity:0;transform:scale(.9)}.schedulePeerJobTransition-leave-active[data-v-5bbdd42b]{position:absolute;width:100%}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
.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 +0,0 @@
|
|||||||
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 +0,0 @@
|
|||||||
@media screen and (max-width: 768px){.qrcode[data-v-02f2240d]{width:100%!important;height:auto!important;aspect-ratio:1/1}}.qrcode[data-v-02f2240d]{width:200px!important;height:200px!important}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
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};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.searchPeersContainer[data-v-576347d8]{backdrop-filter:blur(8px);width:100%;background:linear-gradient(var(--degree),rgba(45,173,255,.4),rgba(255,108,109,.4),var(--brandColor2) 100%)}#searchPeers[data-v-576347d8]::placeholder{color:#fff}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.toggleShowKey[data-v-f702a2b1]{position:absolute;top:35px;right:12px}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.card[data-v-1a7765d4]{border-color:var(--bs-border-color)!important}textarea[data-v-01e380d2]:focus,input[data-v-01e380d2]:focus{box-shadow:none;border-color:var(--bs-border-color)!important}textarea[data-v-01e380d2]{padding:var(--bs-card-spacer-y) var(--bs-card-spacer-x)}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import{_ as h,c as o,a as e,m as c,d as m,b as d,h as f,y as g,n as v,t as p,z as b,D as w,A as x,f as r}from"./index-DXzxfcZW.js";import{L as _}from"./localeText-Dmcj5qqx.js";const k={components:{LocaleText:_},props:{targetData:String,title:String,warning:!1,warningText:""},setup(){const s=w(),t=`input_${x()}`;return{store:s,uuid:t}},data(){return{value:"",invalidFeedback:"",showInvalidFeedback:!1,isValid:!1,timeout:void 0,changed:!1,updating:!1}},mounted(){this.value=this.store.Configuration.Peers[this.targetData]},methods:{async useValidation(){this.changed&&await b("/api/updateDashboardConfigurationItem",{section:"Peers",key:this.targetData,value:this.value},s=>{s.status?(this.isValid=!0,this.showInvalidFeedback=!1,this.store.Configuration.Peers[this.targetData]=this.value,clearTimeout(this.timeout),this.timeout=setTimeout(()=>this.isValid=!1,5e3)):(this.isValid=!1,this.showInvalidFeedback=!0,this.invalidFeedback=s.message),this.changed=!1,this.updating=!1})}}},V={class:"form-group mb-2"},D=["for"],y=["id","disabled"],T={class:"invalid-feedback"},C={key:0,class:"px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1"};function F(s,t,a,I,n,u){const l=f("LocaleText");return r(),o("div",V,[e("label",{for:this.uuid,class:"text-muted mb-1"},[e("strong",null,[e("small",null,[d(l,{t:this.title},null,8,["t"])])])],8,D),c(e("input",{type:"text",class:v(["form-control",{"is-invalid":n.showInvalidFeedback,"is-valid":n.isValid}]),id:this.uuid,"onUpdate:modelValue":t[0]||(t[0]=i=>this.value=i),onKeydown:t[1]||(t[1]=i=>this.changed=!0),onBlur:t[2]||(t[2]=i=>u.useValidation()),disabled:this.updating},null,42,y),[[g,this.value]]),e("div",T,p(this.invalidFeedback),1),a.warning?(r(),o("div",C,[e("small",null,[t[3]||(t[3]=e("i",{class:"bi bi-exclamation-triangle-fill me-2"},null,-1)),d(l,{t:a.warningText},null,8,["t"])])])):m("",!0)])}const B=h(k,[["render",F]]);export{B as P};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.pingPlaceholder[data-v-a08ce97e]{width:100%;height:79.98px}.ping-move[data-v-a08ce97e],.ping-enter-active[data-v-a08ce97e],.ping-leave-active[data-v-a08ce97e]{transition:all .4s cubic-bezier(.82,.58,.17,.9)}.ping-leave-active[data-v-a08ce97e]{position:absolute;width:100%}.ping-enter-from[data-v-a08ce97e],.ping-leave-to[data-v-a08ce97e]{opacity:0;filter:blur(3px)}
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
import{L as n}from"./localeText-Dmcj5qqx.js";import{c as a,d as r,e as s,j as i,f as e}from"./index-DXzxfcZW.js";const d={class:"position-relative"},c={key:0,class:"badge wireguardBg rounded-3 shadow z-1"},l={key:1,class:"badge amneziawgBg rounded-3 shadow"},p={__name:"protocolBadge",props:{protocol:String,mini:!1},setup(o){return(m,t)=>(e(),a("div",d,[o.protocol==="wg"?(e(),a("span",c,[t[0]||(t[0]=s(" WireGuard ",-1)),o.mini?r("",!0):(e(),i(n,{key:0,t:"Configuration"}))])):o.protocol==="awg"?(e(),a("span",l,[t[1]||(t[1]=s(" AmneziaWG ",-1)),o.mini?r("",!0):(e(),i(n,{key:0,t:"Configuration"}))])):r("",!0)]))}};export{p as _};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
|||||||
.dropdownIcon[data-v-ccf48ac7]{transition:all .2s ease-in-out}.dropdownIcon.active[data-v-ccf48ac7]{transform:rotate(180deg)}.steps{&[data-v-324df2b1]{transition:all .3s ease-in-out;opacity:.3}&.active[data-v-324df2b1]{opacity:1}}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user