mirror of
https://github.com/WGDashboard/WGDashboard.git
synced 2026-08-04 06:53:00 +00:00
Compare commits
46 Commits
v4.0.beta1
...
v4.0.beta2
| Author | SHA1 | Date | |
|---|---|---|---|
| 410b81f46f | |||
| aa3711c5cc | |||
| d6b1f97a04 | |||
| b4e8e57a22 | |||
| 9644e6195c | |||
| 764e0c7607 | |||
| 97d640dd40 | |||
| d2915b5b05 | |||
| f274f6fd18 | |||
| f507ac2569 | |||
| 208cbd6d89 | |||
| fa2d7fa3da | |||
| 7463767781 | |||
| 958bc864c9 | |||
| 4484668750 | |||
| d5dea4b87f | |||
| 0fdef6a0a2 | |||
| bd71b6bad8 | |||
| 9b7887b279 | |||
| 3960e43872 | |||
| 201c8f9ec9 | |||
| 8c8374a08c | |||
| 467595afc9 | |||
| acb54f098c | |||
| 5755d13460 | |||
| 2c3500315d | |||
| 47ea60c0cd | |||
| 18b18c1396 | |||
| ff227de5fa | |||
| 6799692811 | |||
| c6173f7f6f | |||
| d0e4dabc44 | |||
| f815dae300 | |||
| b3bd6bb39e | |||
| 71df6409c2 | |||
| e4f9a1e0cf | |||
| ca6a05e393 | |||
| c0d26164dc | |||
| 76fe2a1ba9 | |||
| 8cbdb54402 | |||
| 764ef80a62 | |||
| 0c37d93c01 | |||
| c57a5128e5 | |||
| 6cf4eba20a | |||
| 6825d728c2 | |||
| 6d3091b2a2 |
@@ -106,9 +106,7 @@
|
|||||||
# Must have for each peer
|
# Must have for each peer
|
||||||
```
|
```
|
||||||
|
|
||||||
- Python 3.7+ & Pip3
|
- **Python 3.10** for v4.0+, **Python 3.7 - 3.9** for v2.0 - v3.0.6.2
|
||||||
|
|
||||||
- Browser support CSS3 and ES6
|
|
||||||
|
|
||||||
## 🛠 Install
|
## 🛠 Install
|
||||||
1. Download WGDashboard
|
1. Download WGDashboard
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# WG-Dashboard Docker Explanation:
|
||||||
|
|
||||||
|
Author: DaanSelen<br>
|
||||||
|
|
||||||
|
This document delves into how the WG-Dashboard Docker container has been built.<br>
|
||||||
|
Of course there are two stages, one before run-time and one at/after run-time.<br>
|
||||||
|
The `Dockerfile` describes how the container image is made, and the `entrypoint.sh` is executed after running the container. <br>
|
||||||
|
In this example, WireGuard is integrated into the container itself, so it should be a run-and-go.<br>
|
||||||
|
For more details on the source-code specific to this Docker image, refer to the source files, they have lots of comments.
|
||||||
|
|
||||||
|
I have tried to embed some new features such as `isolated_peers` and interface startup on container-start (through `enable_wg0`).
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/donaldzou/WGDashboard/main/img/logo.png" alt="WG-Dashboard Logo" title="WG-Dashboard Logo" width="150" height="150" />
|
||||||
|
|
||||||
|
## Getting the container running:
|
||||||
|
|
||||||
|
To get the container running you either pull the image from the repository, at the moment: `repo.nerthus.nl/app/wireguard-dashboard:latest`.<br>
|
||||||
|
From there either use the environment variables describe below as parameters or use the Docker Compose file: `compose.yaml`.
|
||||||
|
|
||||||
|
An example of a simple command to get the container running is show below:<br>
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker run -d \
|
||||||
|
--name wireguard-dashboard \
|
||||||
|
--restart unless-stopped \
|
||||||
|
-e enable_wg0=true \
|
||||||
|
-e isolated_peers=true \
|
||||||
|
-p 10086:10086/tcp \
|
||||||
|
-p 51820:51820/udp \
|
||||||
|
--cap-add NET_ADMIN \
|
||||||
|
repo.nerthus.nl/app/wireguard-dashboard:latest
|
||||||
|
```
|
||||||
|
<br>
|
||||||
|
If you want to use Compose instead of a raw Docker command, refer to the example in the `compose.yaml` or the one pasted below:
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
wireguard-dashboard:
|
||||||
|
image: repo.nerthus.nl/app/wireguard-dashboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: wire-dash
|
||||||
|
environment:
|
||||||
|
#- tz=
|
||||||
|
#- global_dns=
|
||||||
|
- enable_wg0=true
|
||||||
|
- isolated_peers=false
|
||||||
|
#- public_ip=
|
||||||
|
ports:
|
||||||
|
- 10086:10086/tcp
|
||||||
|
- 51820:51820/udp
|
||||||
|
volumes:
|
||||||
|
- conf:/etc/wireguard
|
||||||
|
- app:/opt/wireguarddashboard/app
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
conf:
|
||||||
|
app:
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to customize the yaml, make sure the core stays the same, but for example volume PATHs can be freely changed.<br>
|
||||||
|
This setup is just generic and will use the Docker volumes.
|
||||||
|
|
||||||
|
## Working with the container and environment variables:
|
||||||
|
|
||||||
|
Once the container is running, the installation process is essentially the same as running it on bare-metal.<br>
|
||||||
|
So go to the assign TCP port in this case HTTP, like the default 10086 one in the example and log into the WEB-GUI.<br>
|
||||||
|
|
||||||
|
| Environment variable | Accepted arguments | Default value | Verbose |
|
||||||
|
| -------------- | ------- | ------- | ------- |
|
||||||
|
| tz | Europe/Amsterdam or any confirming timezone notation. | Europe/Amsterdam | Sets the timezone of the Docker container. This is to timesync the container to any other processes which would need it. |
|
||||||
|
| global_dns | Any IPv4 address, such as my personal recommendation: 9.9.9.9 (QUAD9) | 1.1.1.1 | Set the default DNS given to clients once they connect to the WireGuard tunnel (VPN).
|
||||||
|
| enable_wg0 | `true` or `false` | `false` | Enables or disables the starting of the WireGuard interface on container 'boot-up'.
|
||||||
|
| isolated_peers | `true` or `false` | `true` | For security the default is true, and it disables peers to ping or reach eachother, the WireGuard interface IS able to reach the peers (Done through `iptables`).
|
||||||
|
| public_ip | Any IPv4 (public recommended) address, such as the one returned by default | Default uses the return of `curl ifconfig.me` | To reach your VPN from outside your own network, you need WG-Dashboard to know what your public IP-address is, otherwise it will generate faulty config files for clients.
|
||||||
|
|
||||||
|
## Closing remarks:
|
||||||
|
|
||||||
|
For feedback please submit an issue to the repository. Or message dselen@nerthus.nl.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
# Pull from small Debian stable image.
|
||||||
|
FROM debian:stable-slim
|
||||||
|
LABEL maintainer="dselen@nerthus.nl"
|
||||||
|
|
||||||
|
# Declaring environment variables, change Peernet to an address you like, standard is a 24 bit subnet.
|
||||||
|
ENV wg_net="10.0.0.1"
|
||||||
|
# wg_net is used functionally as an ARG for its environment variable nature, do not change unless you know what you are doing.
|
||||||
|
|
||||||
|
# Following ENV variables are changable on container runtime because /entrypoint.sh handles that. See compose.yaml for more info.
|
||||||
|
ENV tz="Europe/Amsterdam"
|
||||||
|
ENV global_dns="1.1.1.1"
|
||||||
|
ENV enable_wg0="false"
|
||||||
|
ENV isolated_peers="true"
|
||||||
|
ENV public_ip="0.0.0.0"
|
||||||
|
|
||||||
|
# Doing basic system maintenance. Change the timezone to the desired timezone.
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/${tz} /etc/localtime
|
||||||
|
|
||||||
|
# Doing package management operations, such as upgrading
|
||||||
|
RUN apt-get update && apt-get upgrade -y \
|
||||||
|
&& apt-get install -y --no-install-recommends curl \
|
||||||
|
git \
|
||||||
|
iproute2 \
|
||||||
|
iptables \
|
||||||
|
iputils-ping \
|
||||||
|
openresolv \
|
||||||
|
procps \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
python3-venv \
|
||||||
|
traceroute \
|
||||||
|
wireguard \
|
||||||
|
wireguard-tools \
|
||||||
|
&& apt-get remove linux-image-* --autoremove -y \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
# Removing the Linux Image package to preserve space on the image, for this reason also deleting apt lists, to be able to install packages: run apt update.
|
||||||
|
|
||||||
|
# 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/wireguarddashboard
|
||||||
|
RUN python3 -m venv ${WGDASH}/venv
|
||||||
|
|
||||||
|
# Doing WireGuard Dashboard installation measures. Modify the git clone command to get the preferred version, with a specific branch for example.
|
||||||
|
RUN . ${WGDASH}/venv/bin/activate \
|
||||||
|
&& git clone https://github.com/donaldzou/WGDashboard.git ${WGDASH}/app \
|
||||||
|
&& pip3 install -r ${WGDASH}/app/src/requirements.txt \
|
||||||
|
&& chmod +x ${WGDASH}/app/src/wgd.sh \
|
||||||
|
&& .${WGDASH}/app/src/wgd.sh install
|
||||||
|
|
||||||
|
# Set the volume to be used for persistency.
|
||||||
|
VOLUME /etc/wireguard
|
||||||
|
|
||||||
|
# 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 wg genkey | tee /etc/wireguard/wg0_privatekey \
|
||||||
|
&& echo "[Interface]" > /wg0.conf \
|
||||||
|
&& echo "SaveConfig = true" >> /wg0.conf \
|
||||||
|
&& echo "Address = ${wg_net}/24" >> /wg0.conf \
|
||||||
|
&& echo "PrivateKey = $(cat /etc/wireguard/wg0_privatekey)" >> /wg0.conf \
|
||||||
|
&& echo "PostUp = iptables -t nat -I POSTROUTING 1 -s ${wg_net}/24 -o $(ip -o -4 route show to default | awk '{print $NF}') -j MASQUERADE" >> /wg0.conf \
|
||||||
|
&& echo "PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP" >> /wg0.conf \
|
||||||
|
&& echo "PreDown = iptables -t nat -D POSTROUTING 1" >> /wg0.conf \
|
||||||
|
&& echo "PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP" >> /wg0.conf \
|
||||||
|
&& echo "ListenPort = 51820" >> /wg0.conf \
|
||||||
|
#&& echo "DNS = ${global_dns}" >> /wg0.conf \
|
||||||
|
&& rm /etc/wireguard/wg0_privatekey
|
||||||
|
|
||||||
|
# Defining a way for Docker to check the health of the container. In this case: checking the login URL.
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl -f http://localhost:10086/signin || exit 1
|
||||||
|
|
||||||
|
# Copy the basic entrypoint.sh script.
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
# Exposing the default WireGuard Dashboard port for web access.
|
||||||
|
EXPOSE 10086
|
||||||
|
ENTRYPOINT ["/bin/bash", "/entrypoint.sh"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
services:
|
||||||
|
wireguard-dashboard:
|
||||||
|
image: repo.nerthus.nl/app/wireguard-dashboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: wire-dash
|
||||||
|
environment:
|
||||||
|
#- tz= # <--- Set container timezone, default: Europe/Amsterdam.
|
||||||
|
#- global_dns= # <--- Set global DNS address, default: 1.1.1.1.
|
||||||
|
- enable_wg0=true # <--- If true, wg0 will be started on container startup. default: false.
|
||||||
|
- isolated_peers=false # <--- When set to true, it disallows peers to talk to eachother, setting to false, allows it, default: true.
|
||||||
|
#- public_ip= # <--- Set public IP to ensure the correct one is chosen, defaulting to the IP give by ifconfig.me.
|
||||||
|
ports:
|
||||||
|
- 10086:10086/tcp
|
||||||
|
- 51820:51820/udp
|
||||||
|
volumes:
|
||||||
|
- conf:/etc/wireguard
|
||||||
|
- app:/opt/wireguarddashboard/app
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
conf:
|
||||||
|
app:
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "Starting the WireGuard Dashboard Docker container."
|
||||||
|
|
||||||
|
clean_up() {
|
||||||
|
# Cleaning out previous data such as the .pid file and starting the WireGuard Dashboard. Making sure to use the python venv.
|
||||||
|
echo "Looking for remains of previous instances..."
|
||||||
|
if [ -f "/opt/wireguarddashboard/app/src/gunicorn.pid" ]; then
|
||||||
|
echo "Found old .pid file, removing."
|
||||||
|
rm /opt/wireguarddashboard/app/src/gunicorn.pid
|
||||||
|
else
|
||||||
|
echo "No remains found, continuing."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
start_core() {
|
||||||
|
# This first step is to ensure the wg0.conf file exists, and if not, then its copied over from the ephemeral container storage.
|
||||||
|
if [ ! -f "/etc/wireguard/wg0.conf" ]; then
|
||||||
|
cp "/wg0.conf" "/etc/wireguard/wg0.conf"
|
||||||
|
echo "WireGuard interface file copied over."
|
||||||
|
else
|
||||||
|
echo "WireGuard interface file looks to already be existing."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Activating Python venv and executing the WireGuard Dashboard service."
|
||||||
|
|
||||||
|
. "${WGDASH}"/venv/bin/activate
|
||||||
|
cd "${WGDASH}"/app/src || return # If changing the directory fails (permission or presence error), then bash will exist this function, causing the WireGuard Dashboard to not be succesfully launched.
|
||||||
|
bash wgd.sh start
|
||||||
|
|
||||||
|
# The following section takes care of the firewall rules regarding the 'isolated_peers' feature, which allows or drops packets destined from the wg0 to the wg0 interface.
|
||||||
|
if [ "${isolated_peers,,}" = "false" ]; then
|
||||||
|
echo "Isolated peers disabled, adjusting."
|
||||||
|
|
||||||
|
sed -i '/PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP/d' /etc/wireguard/wg0.conf
|
||||||
|
sed -i '/PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP/d' /etc/wireguard/wg0.conf
|
||||||
|
elif [ "${isolated_peers,,}" = "true" ]; then
|
||||||
|
upblocking=$(grep -c "PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP" /etc/wireguard/wg0.conf)
|
||||||
|
downblocking=$(grep -c "PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP" /etc/wireguard/wg0.conf)
|
||||||
|
if [ "$upblocking" -lt 1 ] && [ "$downblocking" -lt 1 ]; then
|
||||||
|
echo "Isolated peers enabled, adjusting."
|
||||||
|
|
||||||
|
sed -i '/PostUp = iptables -t nat -I POSTROUTING 1 -s/a PostUp = iptables -I FORWARD -i wg0 -o wg0 -j DROP' /etc/wireguard/wg0.conf
|
||||||
|
sed -i '/PreDown = iptables -t nat -D POSTROUTING 1 -s/a PreDown = iptables -D FORWARD -i wg0 -o wg0 -j DROP' /etc/wireguard/wg0.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The following section takes care of
|
||||||
|
if [ "${enable_wg0,,}" = "true" ]; then
|
||||||
|
echo "Preference for wg0 to be turned on found."
|
||||||
|
|
||||||
|
wg-quick up wg0
|
||||||
|
else
|
||||||
|
echo "Preference for wg0 to be turned off found."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_envvars() {
|
||||||
|
echo "Setting relevant variables for operation."
|
||||||
|
|
||||||
|
# If the timezone is different, for example in North-America or Asia.
|
||||||
|
if [ "${tz}" != "$(cat /etc/timezone)" ]; then
|
||||||
|
echo "Changing timezone."
|
||||||
|
|
||||||
|
ln -sf /usr/share/zoneinfo/"${tz}" /etc/localtime
|
||||||
|
echo "${tz}" > /etc/timezone
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Changing the DNS used for clients and the dashboard itself.
|
||||||
|
if [ "${global_dns}" != "$(grep "peer_global_dns = " /opt/wireguarddashboard/app/src/wg-dashboard.ini | awk '{print $NF}')" ]; then
|
||||||
|
echo "Changing default dns."
|
||||||
|
|
||||||
|
#sed -i "s/^DNS = .*/DNS = ${global_dns}/" /etc/wireguard/wg0.conf # Uncomment if you want to have DNS on server-level.
|
||||||
|
sed -i "s/^peer_global_dns = .*/peer_global_dns = ${global_dns}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Setting the public IP of the WireGuard Dashboard container host. If not defined, it will trying fetching it using a curl to ifconfig.me.
|
||||||
|
if [ "${public_ip}" = "0.0.0.0" ]; then
|
||||||
|
default_ip=$(curl -s ifconfig.me)
|
||||||
|
echo "Trying to fetch the Public-IP using ifconfig.me: ${default_ip}"
|
||||||
|
|
||||||
|
sed -i "s/^remote_endpoint = .*/remote_endpoint = ${default_ip}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini
|
||||||
|
elif [ "${public_ip}" != "$(grep "remote_endpoint = " /opt/wireguarddashboard/app/src/wg-dashboard.ini | awk '{print $NF}')" ]; then
|
||||||
|
echo "Setting the Public-IP using given variable: ${public_ip}"
|
||||||
|
|
||||||
|
sed -i "s/^remote_endpoint = .*/remote_endpoint = ${public_ip}/" /opt/wireguarddashboard/app/src/wg-dashboard.ini
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_blocking() {
|
||||||
|
sleep 1s
|
||||||
|
echo "Ensuring container continuation."
|
||||||
|
|
||||||
|
# This function checks if the latest error log is created and tails it for docker logs uses.
|
||||||
|
if find "/opt/wireguarddashboard/app/src/log" -mindepth 1 -maxdepth 1 -type f | read -r; then
|
||||||
|
latestErrLog=$(find /opt/wireguarddashboard/app/src/log -name "error_*.log" | head -n 1)
|
||||||
|
latestAccLog=$(find /opt/wireguarddashboard/app/src/log -name "access_*.log" | head -n 1)
|
||||||
|
tail -f "${latestErrLog}" "${latestAccLog}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Blocking command in case of erroring. So the container does not quit.
|
||||||
|
sleep infinity
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute functions for the WireGuard Dashboard services, then set the environment variables
|
||||||
|
clean_up
|
||||||
|
start_core
|
||||||
|
set_envvars
|
||||||
|
ensure_blocking
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# API Documents for WGDashboard
|
||||||
|
|
||||||
|
**Version: v4.0**
|
||||||
|
|
||||||
|
**Created by: Donald Zou**
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
+328
-104
@@ -77,7 +77,12 @@ class CustomJsonEncoder(DefaultJSONProvider):
|
|||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
|
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, WireguardConfiguration) or isinstance(o, Peer) or isinstance(o, PeerJob) or isinstance(o, Log) or isinstance(o, DashboardAPIKey):
|
if (isinstance(o, WireguardConfiguration)
|
||||||
|
or isinstance(o, Peer)
|
||||||
|
or isinstance(o, PeerJob)
|
||||||
|
or isinstance(o, Log)
|
||||||
|
or isinstance(o, DashboardAPIKey)
|
||||||
|
or isinstance(o, PeerShareLink)):
|
||||||
return o.toJson()
|
return o.toJson()
|
||||||
return super().default(self, o)
|
return super().default(self, o)
|
||||||
|
|
||||||
@@ -104,7 +109,36 @@ class Log:
|
|||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return self.toJson()
|
return self.toJson()
|
||||||
|
|
||||||
class Logger:
|
class DashboardLogger:
|
||||||
|
def __init__(self):
|
||||||
|
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
|
||||||
|
check_same_thread=False)
|
||||||
|
self.loggerdb.row_factory = sqlite3.Row
|
||||||
|
self.loggerdbCursor = self.loggerdb.cursor()
|
||||||
|
self.__createLogDatabase()
|
||||||
|
self.log(Message="WGDashboard started")
|
||||||
|
def __createLogDatabase(self):
|
||||||
|
existingTable = self.loggerdbCursor.execute("SELECT name from sqlite_master where type='table'").fetchall()
|
||||||
|
existingTable = [t['name'] for t in existingTable]
|
||||||
|
|
||||||
|
if "DashboardLog" not in existingTable:
|
||||||
|
self.loggerdbCursor.execute(
|
||||||
|
"CREATE TABLE DashboardLog (LogID VARCHAR NOT NULL, LogDate DATETIME DEFAULT (strftime('%Y-%m-%d %H:%M:%S','now', 'localtime')), URL VARCHAR, IP VARCHAR, Status VARCHAR, Message VARCHAR, PRIMARY KEY (LogID))")
|
||||||
|
self.loggerdb.commit()
|
||||||
|
|
||||||
|
def log(self, URL: str = "", IP: str = "", Status: str = "true", Message: str = "") -> bool:
|
||||||
|
try:
|
||||||
|
self.loggerdbCursor.execute(
|
||||||
|
"INSERT INTO DashboardLog (LogID, URL, IP, Status, Message) VALUES (?, ?, ?, ?, ?)", (str(uuid.uuid4()), URL, IP, Status, Message,))
|
||||||
|
self.loggerdb.commit()
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PeerJobLogger:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
|
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
|
||||||
check_same_thread=False)
|
check_same_thread=False)
|
||||||
@@ -266,7 +300,6 @@ class PeerJobs:
|
|||||||
def runJob(self):
|
def runJob(self):
|
||||||
needToDelete = []
|
needToDelete = []
|
||||||
for job in self.Jobs:
|
for job in self.Jobs:
|
||||||
print(job.toJson())
|
|
||||||
c = WireguardConfigurations.get(job.Configuration)
|
c = WireguardConfigurations.get(job.Configuration)
|
||||||
if c is not None:
|
if c is not None:
|
||||||
f, fp = c.searchPeer(job.Peer)
|
f, fp = c.searchPeer(job.Peer)
|
||||||
@@ -277,11 +310,8 @@ class PeerJobs:
|
|||||||
y: float = float(job.Value)
|
y: float = float(job.Value)
|
||||||
else:
|
else:
|
||||||
x: datetime = datetime.now()
|
x: datetime = datetime.now()
|
||||||
y: datetime = datetime.strptime(job.Value, "%Y-%m-%dT%H:%M")
|
y: datetime = datetime.strptime(job.Value, "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
|
||||||
runAction: bool = self.__runJob_Compare(x, y, job.Operator)
|
runAction: bool = self.__runJob_Compare(x, y, job.Operator)
|
||||||
print("Running Job:" + str(runAction) + "\n")
|
|
||||||
if runAction:
|
if runAction:
|
||||||
s = False
|
s = False
|
||||||
if job.Action == "restrict":
|
if job.Action == "restrict":
|
||||||
@@ -298,7 +328,6 @@ class PeerJobs:
|
|||||||
JobLogger.log(job.JobID, s["status"],
|
JobLogger.log(job.JobID, s["status"],
|
||||||
f"Peer {fp.id} from {c.Name} failed {job.Action}ed."
|
f"Peer {fp.id} from {c.Name} failed {job.Action}ed."
|
||||||
)
|
)
|
||||||
print(f'''[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Peer Job Schedule: Ran {len(needToDelete)} job(s)''')
|
|
||||||
for j in needToDelete:
|
for j in needToDelete:
|
||||||
self.deleteJob(j)
|
self.deleteJob(j)
|
||||||
|
|
||||||
@@ -312,6 +341,74 @@ class PeerJobs:
|
|||||||
if operator == "lst":
|
if operator == "lst":
|
||||||
return x < y
|
return x < y
|
||||||
|
|
||||||
|
class PeerShareLink:
|
||||||
|
def __init__(self, ShareID:str, Configuration: str, Peer: str, ExpireDate: datetime, ShareDate: datetime):
|
||||||
|
self.ShareID = ShareID
|
||||||
|
self.Peer = Peer
|
||||||
|
self.Configuration = Configuration
|
||||||
|
self.ShareDate = ShareDate
|
||||||
|
self.ExpireDate = ExpireDate
|
||||||
|
|
||||||
|
|
||||||
|
def toJson(self):
|
||||||
|
return {
|
||||||
|
"ShareID": self.ShareID,
|
||||||
|
"Peer": self.Peer,
|
||||||
|
"Configuration": self.Configuration,
|
||||||
|
"ExpireDate": self.ExpireDate
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeerShareLinks:
|
||||||
|
def __init__(self):
|
||||||
|
self.Links: list[PeerShareLink] = []
|
||||||
|
self.PeerShareLinkCursor = sqldb.cursor()
|
||||||
|
existingTables = self.PeerShareLinkCursor.execute("SELECT name FROM sqlite_master WHERE type='table' and name = 'PeerShareLinks'").fetchall()
|
||||||
|
if len(existingTables) == 0:
|
||||||
|
self.PeerShareLinkCursor.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE PeerShareLinks (
|
||||||
|
ShareID VARCHAR NOT NULL PRIMARY KEY, Configuration VARCHAR NOT NULL, Peer VARCHAR NOT NULL,
|
||||||
|
ExpireDate DATETIME,
|
||||||
|
SharedDate DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
sqldb.commit()
|
||||||
|
self.__getSharedLinks()
|
||||||
|
# print(self.Links)
|
||||||
|
def __getSharedLinks(self):
|
||||||
|
self.Links.clear()
|
||||||
|
allLinks = self.PeerShareLinkCursor.execute("SELECT * FROM PeerShareLinks WHERE ExpireDate IS NULL OR ExpireDate > datetime('now', 'localtime')").fetchall()
|
||||||
|
for link in allLinks:
|
||||||
|
self.Links.append(PeerShareLink(*link))
|
||||||
|
|
||||||
|
def getLink(self, Configuration: str, Peer: str) -> list[PeerShareLink]:
|
||||||
|
self.__getSharedLinks()
|
||||||
|
return list(filter(lambda x : x.Configuration == Configuration and x.Peer == Peer, self.Links))
|
||||||
|
|
||||||
|
def getLinkByID(self, ShareID: str) -> list[PeerShareLink]:
|
||||||
|
self.__getSharedLinks()
|
||||||
|
return list(filter(lambda x : x.ShareID == ShareID, self.Links))
|
||||||
|
|
||||||
|
def addLink(self, Configuration: str, Peer: str, ExpireDate: datetime = None) -> tuple[bool, str]:
|
||||||
|
try:
|
||||||
|
newShareID = str(uuid.uuid4())
|
||||||
|
if len(self.getLink(Configuration, Peer)) > 0:
|
||||||
|
self.PeerShareLinkCursor.execute("UPDATE PeerShareLinks SET ExpireDate = datetime('now', 'localtime') WHERE Configuration = ? AND Peer = ?", (Configuration, Peer, ))
|
||||||
|
self.PeerShareLinkCursor.execute("INSERT INTO PeerShareLinks (ShareID, Configuration, Peer, ExpireDate) VALUES (?, ?, ?, ?)", (newShareID, Configuration, Peer, ExpireDate, ))
|
||||||
|
sqldb.commit()
|
||||||
|
self.__getSharedLinks()
|
||||||
|
except Exception as e:
|
||||||
|
return False, str(e)
|
||||||
|
return True, newShareID
|
||||||
|
|
||||||
|
def updateLinkExpireDate(self, ShareID, ExpireDate: datetime = None) -> tuple[bool, str]:
|
||||||
|
|
||||||
|
self.PeerShareLinkCursor.execute("UPDATE PeerShareLinks SET ExpireDate = ? WHERE ShareID = ?;", (ExpireDate, ShareID, ))
|
||||||
|
sqldb.commit()
|
||||||
|
self.__getSharedLinks()
|
||||||
|
return True, ""
|
||||||
|
|
||||||
class WireguardConfiguration:
|
class WireguardConfiguration:
|
||||||
class InvalidConfigurationFileException(Exception):
|
class InvalidConfigurationFileException(Exception):
|
||||||
def __init__(self, m):
|
def __init__(self, m):
|
||||||
@@ -391,10 +488,10 @@ class WireguardConfiguration:
|
|||||||
self.getPeersList()
|
self.getPeersList()
|
||||||
|
|
||||||
def __createDatabase(self):
|
def __createDatabase(self):
|
||||||
existingTables = cursor.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
existingTables = sqldb.cursor().execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
|
||||||
existingTables = [t['name'] for t in existingTables]
|
existingTables = [t['name'] for t in existingTables]
|
||||||
if self.Name not in existingTables:
|
if self.Name not in existingTables:
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE %s (
|
CREATE TABLE %s (
|
||||||
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
||||||
@@ -410,7 +507,7 @@ class WireguardConfiguration:
|
|||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
|
|
||||||
if f'{self.Name}_restrict_access' not in existingTables:
|
if f'{self.Name}_restrict_access' not in existingTables:
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE %s_restrict_access (
|
CREATE TABLE %s_restrict_access (
|
||||||
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
||||||
@@ -425,7 +522,7 @@ class WireguardConfiguration:
|
|||||||
)
|
)
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
if f'{self.Name}_transfer' not in existingTables:
|
if f'{self.Name}_transfer' not in existingTables:
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE %s_transfer (
|
CREATE TABLE %s_transfer (
|
||||||
id VARCHAR NOT NULL, total_receive FLOAT NULL,
|
id VARCHAR NOT NULL, total_receive FLOAT NULL,
|
||||||
@@ -436,7 +533,7 @@ class WireguardConfiguration:
|
|||||||
)
|
)
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
if f'{self.Name}_deleted' not in existingTables:
|
if f'{self.Name}_deleted' not in existingTables:
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"""
|
"""
|
||||||
CREATE TABLE %s_deleted (
|
CREATE TABLE %s_deleted (
|
||||||
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
||||||
@@ -451,6 +548,8 @@ class WireguardConfiguration:
|
|||||||
)
|
)
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __getPublicKey(self) -> str:
|
def __getPublicKey(self) -> str:
|
||||||
return _generatePublicKey(self.PrivateKey)[1]
|
return _generatePublicKey(self.PrivateKey)[1]
|
||||||
|
|
||||||
@@ -460,7 +559,7 @@ class WireguardConfiguration:
|
|||||||
|
|
||||||
def __getRestrictedPeers(self):
|
def __getRestrictedPeers(self):
|
||||||
self.RestrictedPeers = []
|
self.RestrictedPeers = []
|
||||||
restricted = cursor.execute("SELECT * FROM %s_restrict_access" % self.Name).fetchall()
|
restricted = sqldb.cursor().execute("SELECT * FROM %s_restrict_access" % self.Name).fetchall()
|
||||||
for i in restricted:
|
for i in restricted:
|
||||||
self.RestrictedPeers.append(Peer(i, self))
|
self.RestrictedPeers.append(Peer(i, self))
|
||||||
|
|
||||||
@@ -485,7 +584,7 @@ class WireguardConfiguration:
|
|||||||
p[pCounter][split[0]] = split[1]
|
p[pCounter][split[0]] = split[1]
|
||||||
for i in p:
|
for i in p:
|
||||||
if "PublicKey" in i.keys():
|
if "PublicKey" in i.keys():
|
||||||
checkIfExist = cursor.execute("SELECT * FROM %s WHERE id = ?" % self.Name,
|
checkIfExist = sqldb.cursor().execute("SELECT * FROM %s WHERE id = ?" % self.Name,
|
||||||
((i['PublicKey']),)).fetchone()
|
((i['PublicKey']),)).fetchone()
|
||||||
if checkIfExist is None:
|
if checkIfExist is None:
|
||||||
newPeer = {
|
newPeer = {
|
||||||
@@ -511,7 +610,7 @@ class WireguardConfiguration:
|
|||||||
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
|
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
|
||||||
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
|
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
|
||||||
}
|
}
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO %s
|
INSERT INTO %s
|
||||||
VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
|
VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
|
||||||
@@ -522,13 +621,21 @@ class WireguardConfiguration:
|
|||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
self.Peers.append(Peer(newPeer, self))
|
self.Peers.append(Peer(newPeer, self))
|
||||||
else:
|
else:
|
||||||
cursor.execute("UPDATE %s SET allowed_ip = ? WHERE id = ?" % self.Name,
|
sqldb.cursor().execute("UPDATE %s SET allowed_ip = ? WHERE id = ?" % self.Name,
|
||||||
(i.get("AllowedIPs", "N/A"), i['PublicKey'],))
|
(i.get("AllowedIPs", "N/A"), i['PublicKey'],))
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
self.Peers.append(Peer(checkIfExist, self))
|
self.Peers.append(Peer(checkIfExist, self))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def addPeers(self, peers: list):
|
||||||
|
for p in peers:
|
||||||
|
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}",
|
||||||
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
|
subprocess.check_output(
|
||||||
|
f"wg-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||||
|
self.getPeersList()
|
||||||
|
|
||||||
def searchPeer(self, publicKey):
|
def searchPeer(self, publicKey):
|
||||||
for i in self.Peers:
|
for i in self.Peers:
|
||||||
if i.id == publicKey:
|
if i.id == publicKey:
|
||||||
@@ -538,12 +645,15 @@ class WireguardConfiguration:
|
|||||||
def allowAccessPeers(self, listOfPublicKeys):
|
def allowAccessPeers(self, listOfPublicKeys):
|
||||||
# numOfAllowedPeers = 0
|
# numOfAllowedPeers = 0
|
||||||
# numOfFailedToAllowPeers = 0
|
# numOfFailedToAllowPeers = 0
|
||||||
|
if not self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
|
|
||||||
for i in listOfPublicKeys:
|
for i in listOfPublicKeys:
|
||||||
p = cursor.execute("SELECT * FROM %s_restrict_access WHERE id = ?" % self.Name, (i,)).fetchone()
|
p = sqldb.cursor().execute("SELECT * FROM %s_restrict_access WHERE id = ?" % self.Name, (i,)).fetchone()
|
||||||
if p is not None:
|
if p is not None:
|
||||||
cursor.execute("INSERT INTO %s SELECT * FROM %s_restrict_access WHERE id = ?"
|
sqldb.cursor().execute("INSERT INTO %s SELECT * FROM %s_restrict_access WHERE id = ?"
|
||||||
% (self.Name, self.Name,), (p['id'],))
|
% (self.Name, self.Name,), (p['id'],))
|
||||||
cursor.execute("DELETE FROM %s_restrict_access WHERE id = ?"
|
sqldb.cursor().execute("DELETE FROM %s_restrict_access WHERE id = ?"
|
||||||
% self.Name, (p['id'],))
|
% self.Name, (p['id'],))
|
||||||
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}",
|
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}",
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
@@ -558,17 +668,19 @@ class WireguardConfiguration:
|
|||||||
def restrictPeers(self, listOfPublicKeys):
|
def restrictPeers(self, listOfPublicKeys):
|
||||||
numOfRestrictedPeers = 0
|
numOfRestrictedPeers = 0
|
||||||
numOfFailedToRestrictPeers = 0
|
numOfFailedToRestrictPeers = 0
|
||||||
|
if not self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
for p in listOfPublicKeys:
|
for p in listOfPublicKeys:
|
||||||
found, pf = self.searchPeer(p)
|
found, pf = self.searchPeer(p)
|
||||||
if found:
|
if found:
|
||||||
try:
|
try:
|
||||||
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
cursor.execute("INSERT INTO %s_restrict_access SELECT * FROM %s WHERE id = ?" %
|
sqldb.cursor().execute("INSERT INTO %s_restrict_access SELECT * FROM %s WHERE id = ?" %
|
||||||
(self.Name, self.Name,), (pf.id,))
|
(self.Name, self.Name,), (pf.id,))
|
||||||
cursor.execute("UPDATE %s_restrict_access SET status = 'stopped' WHERE id = ?" %
|
sqldb.cursor().execute("UPDATE %s_restrict_access SET status = 'stopped' WHERE id = ?" %
|
||||||
(self.Name,), (pf.id,))
|
(self.Name,), (pf.id,))
|
||||||
cursor.execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,))
|
sqldb.cursor().execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,))
|
||||||
numOfRestrictedPeers += 1
|
numOfRestrictedPeers += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
numOfFailedToRestrictPeers += 1
|
numOfFailedToRestrictPeers += 1
|
||||||
@@ -587,13 +699,15 @@ class WireguardConfiguration:
|
|||||||
def deletePeers(self, listOfPublicKeys):
|
def deletePeers(self, listOfPublicKeys):
|
||||||
numOfDeletedPeers = 0
|
numOfDeletedPeers = 0
|
||||||
numOfFailedToDeletePeers = 0
|
numOfFailedToDeletePeers = 0
|
||||||
|
if not self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
for p in listOfPublicKeys:
|
for p in listOfPublicKeys:
|
||||||
found, pf = self.searchPeer(p)
|
found, pf = self.searchPeer(p)
|
||||||
if found:
|
if found:
|
||||||
try:
|
try:
|
||||||
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
cursor.execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,))
|
sqldb.cursor().execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,))
|
||||||
numOfDeletedPeers += 1
|
numOfDeletedPeers += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
numOfFailedToDeletePeers += 1
|
numOfFailedToDeletePeers += 1
|
||||||
@@ -632,6 +746,8 @@ class WireguardConfiguration:
|
|||||||
return False, str(e)
|
return False, str(e)
|
||||||
|
|
||||||
def getPeersLatestHandshake(self):
|
def getPeersLatestHandshake(self):
|
||||||
|
if not self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
try:
|
try:
|
||||||
latestHandshake = subprocess.check_output(f"wg show {self.Name} latest-handshakes",
|
latestHandshake = subprocess.check_output(f"wg show {self.Name} latest-handshakes",
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
@@ -657,6 +773,8 @@ class WireguardConfiguration:
|
|||||||
count += 2
|
count += 2
|
||||||
|
|
||||||
def getPeersTransfer(self):
|
def getPeersTransfer(self):
|
||||||
|
if not self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
try:
|
try:
|
||||||
data_usage = subprocess.check_output(f"wg show {self.Name} transfer",
|
data_usage = subprocess.check_output(f"wg show {self.Name} transfer",
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
@@ -664,50 +782,40 @@ class WireguardConfiguration:
|
|||||||
data_usage = [p.split("\t") for p in data_usage]
|
data_usage = [p.split("\t") for p in data_usage]
|
||||||
for i in range(len(data_usage)):
|
for i in range(len(data_usage)):
|
||||||
if len(data_usage[i]) == 3:
|
if len(data_usage[i]) == 3:
|
||||||
cur_i = cursor.execute(
|
cur_i = sqldb.cursor().execute(
|
||||||
"SELECT total_receive, total_sent, cumu_receive, cumu_sent, status FROM %s WHERE id= ? "
|
"SELECT total_receive, total_sent, cumu_receive, cumu_sent, status FROM %s WHERE id= ? "
|
||||||
% self.Name, (data_usage[i][0],)).fetchone()
|
% self.Name, (data_usage[i][0],)).fetchone()
|
||||||
if cur_i is not None:
|
if cur_i is not None:
|
||||||
total_sent = cur_i['total_sent']
|
total_sent = cur_i['total_sent']
|
||||||
total_receive = cur_i['total_receive']
|
total_receive = cur_i['total_receive']
|
||||||
cur_total_sent = round(int(data_usage[i][2]) / (1024 ** 3), 4)
|
cur_total_sent = float(data_usage[i][2]) / (1024 ** 3)
|
||||||
cur_total_receive = round(int(data_usage[i][1]) / (1024 ** 3), 4)
|
cur_total_receive = float(data_usage[i][1]) / (1024 ** 3)
|
||||||
cumulative_receive = cur_i['cumu_receive'] + total_receive
|
cumulative_receive = cur_i['cumu_receive'] + total_receive
|
||||||
cumulative_sent = cur_i['cumu_sent'] + total_sent
|
cumulative_sent = cur_i['cumu_sent'] + total_sent
|
||||||
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
|
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
|
||||||
total_sent = cur_total_sent
|
total_sent = cur_total_sent
|
||||||
total_receive = cur_total_receive
|
total_receive = cur_total_receive
|
||||||
else:
|
else:
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"UPDATE %s SET cumu_receive = ?, cumu_sent = ?, cumu_data = ? WHERE id = ?" %
|
"UPDATE %s SET cumu_receive = ?, cumu_sent = ?, cumu_data = ? WHERE id = ?" %
|
||||||
self.Name, (round(cumulative_receive, 4), round(cumulative_sent, 4),
|
self.Name, (cumulative_receive, cumulative_sent,
|
||||||
round(cumulative_sent + cumulative_receive, 4),
|
cumulative_sent + cumulative_receive,
|
||||||
data_usage[i][0],))
|
data_usage[i][0],))
|
||||||
total_sent = 0
|
total_sent = 0
|
||||||
total_receive = 0
|
total_receive = 0
|
||||||
|
|
||||||
_, p = self.searchPeer(data_usage[i][0])
|
_, p = self.searchPeer(data_usage[i][0])
|
||||||
if p.total_receive != round(total_receive, 4) or p.total_sent != round(total_sent, 4):
|
if p.total_receive != total_receive or p.total_sent != total_sent:
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
|
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
|
||||||
% self.Name, (round(total_receive, 4), round(total_sent, 4),
|
% self.Name, (total_receive, total_sent,
|
||||||
round(total_receive + total_sent, 4), data_usage[i][0],))
|
total_receive + total_sent, data_usage[i][0],))
|
||||||
now = datetime.now()
|
|
||||||
now_string = now.strftime("%d/%m/%Y %H:%M:%S")
|
|
||||||
# cursor.execute(f'''
|
|
||||||
# INSERT INTO %s_transfer
|
|
||||||
# (id, total_receive, total_sent, total_data,
|
|
||||||
# cumu_receive, cumu_sent, cumu_data, time)
|
|
||||||
# VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
# ''' % self.Name, (data_usage[i][0], round(total_receive, 4), round(total_sent, 4),
|
|
||||||
# round(total_receive + total_sent, 4), round(cumulative_receive, 4),
|
|
||||||
# round(cumulative_sent, 4),
|
|
||||||
# round(cumulative_sent + cumulative_receive, 4), now_string,))
|
|
||||||
# sqldb.commit()
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error" + str(e))
|
print("Error: " + str(e))
|
||||||
|
|
||||||
def getPeersEndpoint(self):
|
def getPeersEndpoint(self):
|
||||||
|
if not self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
try:
|
try:
|
||||||
data_usage = subprocess.check_output(f"wg show {self.Name} endpoints",
|
data_usage = subprocess.check_output(f"wg show {self.Name} endpoints",
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
shell=True, stderr=subprocess.STDOUT)
|
||||||
@@ -723,7 +831,6 @@ class WireguardConfiguration:
|
|||||||
|
|
||||||
def toggleConfiguration(self) -> [bool, str]:
|
def toggleConfiguration(self) -> [bool, str]:
|
||||||
self.getStatus()
|
self.getStatus()
|
||||||
print("Status: ", self.getStatus())
|
|
||||||
if self.Status:
|
if self.Status:
|
||||||
try:
|
try:
|
||||||
check = subprocess.check_output(f"wg-quick down {self.Name}",
|
check = subprocess.check_output(f"wg-quick down {self.Name}",
|
||||||
@@ -760,7 +867,13 @@ class WireguardConfiguration:
|
|||||||
"PreDown": self.PreDown,
|
"PreDown": self.PreDown,
|
||||||
"PostUp": self.PostUp,
|
"PostUp": self.PostUp,
|
||||||
"PostDown": self.PostDown,
|
"PostDown": self.PostDown,
|
||||||
"SaveConfig": self.SaveConfig
|
"SaveConfig": self.SaveConfig,
|
||||||
|
"DataUsage": {
|
||||||
|
"Total": sum(list(map(lambda x: x.cumu_data + x.total_data, self.Peers))),
|
||||||
|
"Sent": sum(list(map(lambda x: x.cumu_sent + x.total_sent, self.Peers))),
|
||||||
|
"Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers)))
|
||||||
|
},
|
||||||
|
"ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers)))
|
||||||
}
|
}
|
||||||
|
|
||||||
class Peer:
|
class Peer:
|
||||||
@@ -786,10 +899,13 @@ class Peer:
|
|||||||
self.remote_endpoint = tableData["remote_endpoint"]
|
self.remote_endpoint = tableData["remote_endpoint"]
|
||||||
self.preshared_key = tableData["preshared_key"]
|
self.preshared_key = tableData["preshared_key"]
|
||||||
self.jobs: list[PeerJob] = []
|
self.jobs: list[PeerJob] = []
|
||||||
|
self.ShareLink: list[PeerShareLink] = []
|
||||||
self.getJobs()
|
self.getJobs()
|
||||||
|
self.getShareLink()
|
||||||
|
|
||||||
def toJson(self):
|
def toJson(self):
|
||||||
self.getJobs()
|
self.getJobs()
|
||||||
|
self.getShareLink()
|
||||||
return self.__dict__
|
return self.__dict__
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -799,6 +915,8 @@ class Peer:
|
|||||||
preshared_key: str,
|
preshared_key: str,
|
||||||
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
|
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
|
||||||
keepalive: int) -> ResponseObject:
|
keepalive: int) -> ResponseObject:
|
||||||
|
if not self.configuration.getStatus():
|
||||||
|
self.configuration.toggleConfiguration()
|
||||||
|
|
||||||
existingAllowedIps = [item for row in list(
|
existingAllowedIps = [item for row in list(
|
||||||
map(lambda x: [q.strip() for q in x.split(',')],
|
map(lambda x: [q.strip() for q in x.split(',')],
|
||||||
@@ -843,12 +961,13 @@ class Peer:
|
|||||||
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
|
||||||
return ResponseObject(False,
|
return ResponseObject(False,
|
||||||
"Update peer failed when saving the configuration.")
|
"Update peer failed when saving the configuration.")
|
||||||
cursor.execute(
|
sqldb.cursor().execute(
|
||||||
'''UPDATE %s SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?,
|
'''UPDATE %s SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?,
|
||||||
keepalive = ?, preshared_key = ? WHERE id = ?''' % self.configuration.Name,
|
keepalive = ?, preshared_key = ? WHERE id = ?''' % self.configuration.Name,
|
||||||
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
|
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
|
||||||
keepalive, preshared_key, self.id,)
|
keepalive, preshared_key, self.id,)
|
||||||
)
|
)
|
||||||
|
sqldb.commit()
|
||||||
return ResponseObject()
|
return ResponseObject()
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
return ResponseObject(False, exc.output.decode("UTF-8").strip())
|
return ResponseObject(False, exc.output.decode("UTF-8").strip())
|
||||||
@@ -888,7 +1007,24 @@ PersistentKeepalive = {str(self.keepalive)}
|
|||||||
|
|
||||||
def getJobs(self):
|
def getJobs(self):
|
||||||
self.jobs = AllPeerJobs.searchJob(self.configuration.Name, self.id)
|
self.jobs = AllPeerJobs.searchJob(self.configuration.Name, self.id)
|
||||||
# print(AllPeerJobs.searchJob(self.configuration.Name, self.id))
|
|
||||||
|
def getShareLink(self):
|
||||||
|
self.ShareLink = AllPeerShareLinks.getLink(self.configuration.Name, self.id)
|
||||||
|
|
||||||
|
def resetDataUsage(self, type):
|
||||||
|
try:
|
||||||
|
if type == "total":
|
||||||
|
sqldb.cursor().execute("UPDATE %s SET total_data = 0, cumu_data = 0, total_receive = 0, cumu_receive = 0, total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
|
||||||
|
elif type == "receive":
|
||||||
|
sqldb.cursor().execute("UPDATE %s SET total_receive = 0, cumu_receive = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
|
||||||
|
elif type == "sent":
|
||||||
|
sqldb.cursor().execute("UPDATE %s SET total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
# Regex Match
|
# Regex Match
|
||||||
def regex_match(regex, text):
|
def regex_match(regex, text):
|
||||||
@@ -922,6 +1058,7 @@ class DashboardConfig:
|
|||||||
"username": "admin",
|
"username": "admin",
|
||||||
"password": "admin",
|
"password": "admin",
|
||||||
"enable_totp": "false",
|
"enable_totp": "false",
|
||||||
|
"totp_verified": "false",
|
||||||
"totp_key": pyotp.random_base32()
|
"totp_key": pyotp.random_base32()
|
||||||
},
|
},
|
||||||
"Server": {
|
"Server": {
|
||||||
@@ -957,13 +1094,13 @@ class DashboardConfig:
|
|||||||
self.DashboardAPIKeys = self.__getAPIKeys()
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
def __createAPIKeyTable(self):
|
def __createAPIKeyTable(self):
|
||||||
existingTable = cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall()
|
existingTable = sqldb.cursor().execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall()
|
||||||
if len(existingTable) == 0:
|
if len(existingTable) == 0:
|
||||||
cursor.execute("CREATE TABLE DashboardAPIKeys (Key VARCHAR NOT NULL PRIMARY KEY, CreatedAt DATETIME NOT NULL DEFAULT (datetime('now', 'localtime')), ExpiredAt VARCHAR)")
|
sqldb.cursor().execute("CREATE TABLE DashboardAPIKeys (Key VARCHAR NOT NULL PRIMARY KEY, CreatedAt DATETIME NOT NULL DEFAULT (datetime('now', 'localtime')), ExpiredAt VARCHAR)")
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
|
|
||||||
def __getAPIKeys(self) -> list[DashboardAPIKey]:
|
def __getAPIKeys(self) -> list[DashboardAPIKey]:
|
||||||
keys = cursor.execute("SELECT * FROM DashboardAPIKeys WHERE ExpiredAt IS NULL OR ExpiredAt > datetime('now', 'localtime') ORDER BY CreatedAt DESC").fetchall()
|
keys = sqldb.cursor().execute("SELECT * FROM DashboardAPIKeys WHERE ExpiredAt IS NULL OR ExpiredAt > datetime('now', 'localtime') ORDER BY CreatedAt DESC").fetchall()
|
||||||
fKeys = []
|
fKeys = []
|
||||||
for k in keys:
|
for k in keys:
|
||||||
fKeys.append(DashboardAPIKey(*k))
|
fKeys.append(DashboardAPIKey(*k))
|
||||||
@@ -971,12 +1108,12 @@ class DashboardConfig:
|
|||||||
|
|
||||||
def createAPIKeys(self, ExpiredAt = None):
|
def createAPIKeys(self, ExpiredAt = None):
|
||||||
newKey = secrets.token_urlsafe(32)
|
newKey = secrets.token_urlsafe(32)
|
||||||
cursor.execute('INSERT INTO DashboardAPIKeys (Key, ExpiredAt) VALUES (?, ?)', (newKey, ExpiredAt,))
|
sqldb.cursor().execute('INSERT INTO DashboardAPIKeys (Key, ExpiredAt) VALUES (?, ?)', (newKey, ExpiredAt,))
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
self.DashboardAPIKeys = self.__getAPIKeys()
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
def deleteAPIKey(self, key):
|
def deleteAPIKey(self, key):
|
||||||
cursor.execute("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
|
sqldb.cursor().execute("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
self.DashboardAPIKeys = self.__getAPIKeys()
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
@@ -1208,6 +1345,13 @@ API Routes
|
|||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def auth_req():
|
def auth_req():
|
||||||
|
if "api" in request.path:
|
||||||
|
if str(request.method) == "GET":
|
||||||
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args))
|
||||||
|
elif str(request.method) == "POST":
|
||||||
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Request Args: {str(request.args)} Body:{str(request.get_json())}")
|
||||||
|
|
||||||
|
|
||||||
authenticationRequired = DashboardConfig.GetConfig("Server", "auth_req")[1]
|
authenticationRequired = DashboardConfig.GetConfig("Server", "auth_req")[1]
|
||||||
d = request.headers
|
d = request.headers
|
||||||
if authenticationRequired:
|
if authenticationRequired:
|
||||||
@@ -1215,6 +1359,7 @@ def auth_req():
|
|||||||
apiKeyEnabled = DashboardConfig.GetConfig("Server", "dashboard_api_key")[1]
|
apiKeyEnabled = DashboardConfig.GetConfig("Server", "dashboard_api_key")[1]
|
||||||
if apiKey is not None and len(apiKey) > 0 and apiKeyEnabled:
|
if apiKey is not None and len(apiKey) > 0 and apiKeyEnabled:
|
||||||
apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1
|
apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1
|
||||||
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}")
|
||||||
if not apiKeyExist:
|
if not apiKeyExist:
|
||||||
response = Flask.make_response(app, {
|
response = Flask.make_response(app, {
|
||||||
"status": False,
|
"status": False,
|
||||||
@@ -1228,6 +1373,7 @@ def auth_req():
|
|||||||
if ('/static/' not in request.path and "username" not in session and "/" != request.path
|
if ('/static/' not in request.path and "username" not in session and "/" != request.path
|
||||||
and "validateAuthentication" not in request.path and "authenticate" not in request.path
|
and "validateAuthentication" not in request.path and "authenticate" not in request.path
|
||||||
and "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path
|
and "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path
|
||||||
|
and "sharePeer/get" not in request.path
|
||||||
and "isTotpEnabled" not in request.path
|
and "isTotpEnabled" not in request.path
|
||||||
):
|
):
|
||||||
response = Flask.make_response(app, {
|
response = Flask.make_response(app, {
|
||||||
@@ -1268,8 +1414,10 @@ def API_AuthenticateLogin():
|
|||||||
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
|
||||||
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {data['username']}")
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {data['username']}")
|
||||||
if totpEnabled:
|
if totpEnabled:
|
||||||
return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
|
return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
|
||||||
else:
|
else:
|
||||||
@@ -1286,8 +1434,6 @@ def API_SignOut():
|
|||||||
@app.route('/api/getWireguardConfigurations', methods=["GET"])
|
@app.route('/api/getWireguardConfigurations', methods=["GET"])
|
||||||
def API_getWireguardConfigurations():
|
def API_getWireguardConfigurations():
|
||||||
# WireguardConfigurations = _getConfigurationList()
|
# WireguardConfigurations = _getConfigurationList()
|
||||||
print("in request::::")
|
|
||||||
print(list(WireguardConfigurations.keys()))
|
|
||||||
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()])
|
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()])
|
||||||
|
|
||||||
|
|
||||||
@@ -1390,7 +1536,7 @@ def API_newDashboardAPIKey():
|
|||||||
if data['neverExpire']:
|
if data['neverExpire']:
|
||||||
expiredAt = None
|
expiredAt = None
|
||||||
else:
|
else:
|
||||||
expiredAt = datetime.strptime(data['ExpiredAt'], '%Y-%m-%dT%H:%M:%S')
|
expiredAt = datetime.strptime(data['ExpiredAt'], '%Y-%m-%d %H:%M:%S')
|
||||||
DashboardConfig.createAPIKeys(expiredAt)
|
DashboardConfig.createAPIKeys(expiredAt)
|
||||||
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
|
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1427,6 +1573,20 @@ def API_updatePeerSettings(configName):
|
|||||||
allowed_ip, endpoint_allowed_ip, mtu, keepalive)
|
allowed_ip, endpoint_allowed_ip, mtu, keepalive)
|
||||||
return ResponseObject(False, "Peer does not exist")
|
return ResponseObject(False, "Peer does not exist")
|
||||||
|
|
||||||
|
@app.route('/api/resetPeerData/<configName>', methods=['POST'])
|
||||||
|
def API_resetPeerData(configName):
|
||||||
|
data = request.get_json()
|
||||||
|
id = data['id']
|
||||||
|
type = data['type']
|
||||||
|
if len(id) == 0 or configName not in WireguardConfigurations.keys():
|
||||||
|
return ResponseObject(False, "Configuration/Peer does not exist")
|
||||||
|
wgc = WireguardConfigurations.get(configName)
|
||||||
|
foundPeer, peer = wgc.searchPeer(id)
|
||||||
|
if not foundPeer:
|
||||||
|
return ResponseObject(False, "Configuration/Peer does not exist")
|
||||||
|
return ResponseObject(status=peer.resetDataUsage(type))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/deletePeers/<configName>', methods=['POST'])
|
@app.route('/api/deletePeers/<configName>', methods=['POST'])
|
||||||
def API_deletePeers(configName: str) -> ResponseObject:
|
def API_deletePeers(configName: str) -> ResponseObject:
|
||||||
@@ -1452,6 +1612,61 @@ def API_restrictPeers(configName: str) -> ResponseObject:
|
|||||||
return configuration.restrictPeers(peers)
|
return configuration.restrictPeers(peers)
|
||||||
return ResponseObject(False, "Configuration does not exist")
|
return ResponseObject(False, "Configuration does not exist")
|
||||||
|
|
||||||
|
@app.route('/api/sharePeer/create', methods=['POST'])
|
||||||
|
def API_sharePeer_create():
|
||||||
|
data: dict[str, str] = request.get_json()
|
||||||
|
Configuration = data.get('Configuration')
|
||||||
|
Peer = data.get('Peer')
|
||||||
|
ExpireDate = data.get('ExpireDate')
|
||||||
|
if Configuration is None or Peer is None:
|
||||||
|
return ResponseObject(False, "Please specify configuration and peer")
|
||||||
|
activeLink = AllPeerShareLinks.getLink(Configuration, Peer)
|
||||||
|
if len(activeLink) > 0:
|
||||||
|
return ResponseObject(False, "This peer is already sharing, please stop sharing first.")
|
||||||
|
status, message = AllPeerShareLinks.addLink(Configuration, Peer, ExpireDate)
|
||||||
|
if not status:
|
||||||
|
return ResponseObject(status, message)
|
||||||
|
return ResponseObject(data=AllPeerShareLinks.getLinkByID(message))
|
||||||
|
|
||||||
|
@app.route('/api/sharePeer/update', methods=['POST'])
|
||||||
|
def API_sharePeer_update():
|
||||||
|
data: dict[str, str] = request.get_json()
|
||||||
|
ShareID: str = data.get("ShareID")
|
||||||
|
ExpireDate: str = data.get("ExpireDate")
|
||||||
|
print(ShareID)
|
||||||
|
print(ExpireDate)
|
||||||
|
|
||||||
|
if ShareID is None:
|
||||||
|
return ResponseObject(False, "Please specify ShareID")
|
||||||
|
|
||||||
|
if len(AllPeerShareLinks.getLinkByID(ShareID)) == 0:
|
||||||
|
return ResponseObject(False, "ShareID does not exist")
|
||||||
|
|
||||||
|
status, message = AllPeerShareLinks.updateLinkExpireDate(ShareID, ExpireDate)
|
||||||
|
if not status:
|
||||||
|
return ResponseObject(status, message)
|
||||||
|
return ResponseObject(data=AllPeerShareLinks.getLinkByID(ShareID))
|
||||||
|
|
||||||
|
@app.route('/api/sharePeer/get', methods=['GET'])
|
||||||
|
def API_sharePeer_get():
|
||||||
|
data = request.args
|
||||||
|
ShareID = data.get("ShareID")
|
||||||
|
if ShareID is None or len(ShareID) == 0:
|
||||||
|
return ResponseObject(False, "Please provide ShareID")
|
||||||
|
link = AllPeerShareLinks.getLinkByID(ShareID)
|
||||||
|
if len(link) == 0:
|
||||||
|
return ResponseObject(False, "This link is either expired to invalid")
|
||||||
|
l = link[0]
|
||||||
|
if l.Configuration not in WireguardConfigurations.keys():
|
||||||
|
return ResponseObject(False, "The peer you're looking for does not exist")
|
||||||
|
c = WireguardConfigurations.get(l.Configuration)
|
||||||
|
fp, p = c.searchPeer(l.Peer)
|
||||||
|
if not fp:
|
||||||
|
return ResponseObject(False, "The peer you're looking for does not exist")
|
||||||
|
|
||||||
|
return ResponseObject(data=p.downloadPeer())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/allowAccessPeers/<configName>', methods=['POST'])
|
@app.route('/api/allowAccessPeers/<configName>', methods=['POST'])
|
||||||
def API_allowAccessPeers(configName: str) -> ResponseObject:
|
def API_allowAccessPeers(configName: str) -> ResponseObject:
|
||||||
@@ -1483,8 +1698,7 @@ def API_addPeers(configName):
|
|||||||
if (not bulkAdd and (len(public_key) == 0 or len(allowed_ips) == 0)) or len(endpoint_allowed_ip) == 0:
|
if (not bulkAdd and (len(public_key) == 0 or len(allowed_ips) == 0)) or len(endpoint_allowed_ip) == 0:
|
||||||
return ResponseObject(False, "Please fill in all required box.")
|
return ResponseObject(False, "Please fill in all required box.")
|
||||||
if not config.getStatus():
|
if not config.getStatus():
|
||||||
return ResponseObject(False,
|
config.toggleConfiguration()
|
||||||
f"{configName} is not running, please turn on the configuration before adding peers.")
|
|
||||||
if bulkAdd:
|
if bulkAdd:
|
||||||
if bulkAddAmount < 1:
|
if bulkAddAmount < 1:
|
||||||
return ResponseObject(False, "Please specify amount of peers you want to add")
|
return ResponseObject(False, "Please specify amount of peers you want to add")
|
||||||
@@ -1497,28 +1711,24 @@ def API_addPeers(configName):
|
|||||||
|
|
||||||
keyPairs = []
|
keyPairs = []
|
||||||
for i in range(bulkAddAmount):
|
for i in range(bulkAddAmount):
|
||||||
key = _generatePrivateKey()[1]
|
newPrivateKey = _generatePrivateKey()[1]
|
||||||
keyPairs.append([key, _generatePublicKey(key)[1], _generatePrivateKey()[1], availableIps[1][i],
|
keyPairs.append({
|
||||||
f"{config.Name}_{datetime.now().strftime('%m%d%Y%H%M%S')}_Peer_#_{(i + 1)}"])
|
"private_key": newPrivateKey,
|
||||||
|
"id": _generatePublicKey(newPrivateKey)[1],
|
||||||
|
"preshared_key": _generatePrivateKey()[1],
|
||||||
|
"allowed_ip": availableIps[1][i],
|
||||||
|
"name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
|
})
|
||||||
if len(keyPairs) == 0:
|
if len(keyPairs) == 0:
|
||||||
return ResponseObject(False, "Generating key pairs by bulk failed")
|
return ResponseObject(False, "Generating key pairs by bulk failed")
|
||||||
|
config.addPeers(keyPairs)
|
||||||
|
|
||||||
for i in range(bulkAddAmount):
|
for kp in keyPairs:
|
||||||
subprocess.check_output(
|
found, peer = config.searchPeer(kp['id'])
|
||||||
f"wg set {config.Name} peer {keyPairs[i][1]} allowed-ips {keyPairs[i][3]}",
|
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
|
||||||
subprocess.check_output(
|
|
||||||
f"wg-quick save {config.Name}", shell=True, stderr=subprocess.STDOUT)
|
|
||||||
config.getPeersList()
|
|
||||||
|
|
||||||
for i in range(bulkAddAmount):
|
|
||||||
found, peer = config.searchPeer(keyPairs[i][1])
|
|
||||||
if found:
|
if found:
|
||||||
if not peer.updatePeer(keyPairs[i][4], keyPairs[i][0], preshared_key, dns_addresses,
|
if not peer.updatePeer(kp['name'], kp['private_key'], kp['preshared_key'], dns_addresses,
|
||||||
keyPairs[i][3],
|
kp['allowed_ip'], endpoint_allowed_ip, mtu, keep_alive):
|
||||||
endpoint_allowed_ip, mtu, keep_alive).status:
|
|
||||||
return ResponseObject(False, "Failed to add peers in bulk")
|
return ResponseObject(False, "Failed to add peers in bulk")
|
||||||
|
|
||||||
return ResponseObject()
|
return ResponseObject()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -1526,16 +1736,17 @@ def API_addPeers(configName):
|
|||||||
return ResponseObject(False, f"This peer already exist.")
|
return ResponseObject(False, f"This peer already exist.")
|
||||||
name = data['name']
|
name = data['name']
|
||||||
private_key = data['private_key']
|
private_key = data['private_key']
|
||||||
subprocess.check_output(
|
config.addPeers([{"id": public_key, "allowed_ip": ''.join(allowed_ips)}])
|
||||||
f"wg set {config.Name} peer {public_key} allowed-ips {''.join(allowed_ips)}",
|
# subprocess.check_output(
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
# f"wg set {config.Name} peer {public_key} allowed-ips {''.join(allowed_ips)}",
|
||||||
if len(preshared_key) > 0:
|
# shell=True, stderr=subprocess.STDOUT)
|
||||||
subprocess.check_output(
|
# if len(preshared_key) > 0:
|
||||||
f"wg set {config.Name} peer {public_key} preshared-key {preshared_key}",
|
# subprocess.check_output(
|
||||||
shell=True, stderr=subprocess.STDOUT)
|
# f"wg set {config.Name} peer {public_key} preshared-key {preshared_key}",
|
||||||
subprocess.check_output(
|
# shell=True, stderr=subprocess.STDOUT)
|
||||||
f"wg-quick save {config.Name}", shell=True, stderr=subprocess.STDOUT)
|
# subprocess.check_output(
|
||||||
config.getPeersList()
|
# f"wg-quick save {config.Name}", shell=True, stderr=subprocess.STDOUT)
|
||||||
|
# config.getPeersList()
|
||||||
found, peer = config.searchPeer(public_key)
|
found, peer = config.searchPeer(public_key)
|
||||||
if found:
|
if found:
|
||||||
return peer.updatePeer(name, private_key, preshared_key, dns_addresses, ",".join(allowed_ips),
|
return peer.updatePeer(name, private_key, preshared_key, dns_addresses, ",".join(allowed_ips),
|
||||||
@@ -1750,12 +1961,14 @@ Sign Up
|
|||||||
|
|
||||||
@app.route('/api/isTotpEnabled')
|
@app.route('/api/isTotpEnabled')
|
||||||
def API_isTotpEnabled():
|
def API_isTotpEnabled():
|
||||||
return ResponseObject(data=DashboardConfig.GetConfig("Account", "enable_totp")[1])
|
return (
|
||||||
|
ResponseObject(data=DashboardConfig.GetConfig("Account", "enable_totp")[1] and DashboardConfig.GetConfig("Account", "totp_verified")[1]))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/Welcome_GetTotpLink')
|
@app.route('/api/Welcome_GetTotpLink')
|
||||||
def API_Welcome_GetTotpLink():
|
def API_Welcome_GetTotpLink():
|
||||||
if DashboardConfig.GetConfig("Other", "welcome_session")[1]:
|
if not DashboardConfig.GetConfig("Account", "totp_verified")[1]:
|
||||||
|
DashboardConfig.SetConfig("Account", "totp_key", pyotp.random_base32())
|
||||||
return ResponseObject(
|
return ResponseObject(
|
||||||
data=pyotp.totp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).provisioning_uri(
|
data=pyotp.totp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).provisioning_uri(
|
||||||
issuer_name="WGDashboard"))
|
issuer_name="WGDashboard"))
|
||||||
@@ -1765,11 +1978,11 @@ def API_Welcome_GetTotpLink():
|
|||||||
@app.route('/api/Welcome_VerifyTotpLink', methods=["POST"])
|
@app.route('/api/Welcome_VerifyTotpLink', methods=["POST"])
|
||||||
def API_Welcome_VerifyTotpLink():
|
def API_Welcome_VerifyTotpLink():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if DashboardConfig.GetConfig("Other", "welcome_session")[1]:
|
|
||||||
totp = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now()
|
totp = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now()
|
||||||
print(totp)
|
if totp == data['totp']:
|
||||||
|
DashboardConfig.SetConfig("Account", "totp_verified", "true")
|
||||||
|
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
||||||
return ResponseObject(totp == data['totp'])
|
return ResponseObject(totp == data['totp'])
|
||||||
return ResponseObject(False)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/Welcome_Finish', methods=["POST"])
|
@app.route('/api/Welcome_Finish', methods=["POST"])
|
||||||
@@ -1789,10 +2002,10 @@ def API_Welcome_Finish():
|
|||||||
"repeatNewPassword": data["repeatNewPassword"],
|
"repeatNewPassword": data["repeatNewPassword"],
|
||||||
"currentPassword": "admin"
|
"currentPassword": "admin"
|
||||||
})
|
})
|
||||||
updateEnableTotp, updateEnableTotpErr = DashboardConfig.SetConfig("Account", "enable_totp", data["enable_totp"])
|
# updateEnableTotp, updateEnableTotpErr = DashboardConfig.SetConfig("Account", "enable_totp", data["enable_totp"])
|
||||||
|
|
||||||
if not updateUsername or not updatePassword or not updateEnableTotp:
|
if not updateUsername or not updatePassword:
|
||||||
return ResponseObject(False, f"{updateUsernameErr},{updatePasswordErr},{updateEnableTotpErr}".strip(","))
|
return ResponseObject(False, f"{updateUsernameErr},{updatePasswordErr}".strip(","))
|
||||||
|
|
||||||
DashboardConfig.SetConfig("Other", "welcome_session", False)
|
DashboardConfig.SetConfig("Other", "welcome_session", False)
|
||||||
|
|
||||||
@@ -1810,8 +2023,8 @@ def index():
|
|||||||
|
|
||||||
def backGroundThread():
|
def backGroundThread():
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
print("Waiting 5 sec")
|
print(f"[WGDashboard] Background Thread #1 Started", flush=True)
|
||||||
time.sleep(5)
|
time.sleep(10)
|
||||||
while True:
|
while True:
|
||||||
for c in WireguardConfigurations.values():
|
for c in WireguardConfigurations.values():
|
||||||
if c.getStatus():
|
if c.getStatus():
|
||||||
@@ -1819,19 +2032,19 @@ def backGroundThread():
|
|||||||
c.getPeersTransfer()
|
c.getPeersTransfer()
|
||||||
c.getPeersLatestHandshake()
|
c.getPeersLatestHandshake()
|
||||||
c.getPeersEndpoint()
|
c.getPeersEndpoint()
|
||||||
|
c.getPeersList()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error: " + str(e))
|
print(f"[WGDashboard] Background Thread #1 Error: {str(e)}", flush=True)
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
|
|
||||||
|
|
||||||
def peerJobScheduleBackgroundThread():
|
def peerJobScheduleBackgroundThread():
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
print(f'''[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Peer Job Schedule: Waiting for 10 Seconds''')
|
print(f"[WGDashboard] Background Thread #2 Started", flush=True)
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
while True:
|
while True:
|
||||||
print(f'''[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Peer Job Schedule: Running''')
|
|
||||||
AllPeerJobs.runJob()
|
AllPeerJobs.runJob()
|
||||||
time.sleep(10)
|
time.sleep(180)
|
||||||
|
|
||||||
|
|
||||||
def gunicornConfig():
|
def gunicornConfig():
|
||||||
@@ -1839,21 +2052,30 @@ def gunicornConfig():
|
|||||||
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
||||||
return app_ip, app_port
|
return app_ip, app_port
|
||||||
|
|
||||||
|
import sys
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
from typing_extensions import ParamSpec
|
||||||
|
else:
|
||||||
|
from typing import ParamSpec
|
||||||
|
|
||||||
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
||||||
sqldb.row_factory = sqlite3.Row
|
sqldb.row_factory = sqlite3.Row
|
||||||
cursor = sqldb.cursor()
|
cursor = sqldb.cursor()
|
||||||
DashboardConfig = DashboardConfig()
|
DashboardConfig = DashboardConfig()
|
||||||
|
|
||||||
|
AllPeerShareLinks: PeerShareLinks = PeerShareLinks()
|
||||||
AllPeerJobs: PeerJobs = PeerJobs()
|
AllPeerJobs: PeerJobs = PeerJobs()
|
||||||
JobLogger: Logger = Logger()
|
JobLogger: PeerJobLogger = PeerJobLogger()
|
||||||
|
DashboardLogger: DashboardLogger = DashboardLogger()
|
||||||
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
||||||
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
||||||
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
|
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
|
||||||
|
|
||||||
WireguardConfigurations: dict[str, WireguardConfiguration] = {}
|
WireguardConfigurations: dict[str, WireguardConfiguration] = {}
|
||||||
WireguardConfigurations = _getConfigurationList()
|
WireguardConfigurations = _getConfigurationList()
|
||||||
|
|
||||||
|
|
||||||
|
def startThreads():
|
||||||
bgThread = threading.Thread(target=backGroundThread)
|
bgThread = threading.Thread(target=backGroundThread)
|
||||||
bgThread.daemon = True
|
bgThread.daemon = True
|
||||||
bgThread.start()
|
bgThread.start()
|
||||||
@@ -1862,5 +2084,7 @@ scheduleJobThread = threading.Thread(target=peerJobScheduleBackgroundThread)
|
|||||||
scheduleJobThread.daemon = True
|
scheduleJobThread.daemon = True
|
||||||
scheduleJobThread.start()
|
scheduleJobThread.start()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
startThreads()
|
||||||
app.run(host=app_ip, debug=False, port=app_port)
|
app.run(host=app_ip, debug=False, port=app_port)
|
||||||
|
|||||||
+15
-1
@@ -1,8 +1,14 @@
|
|||||||
import multiprocessing
|
|
||||||
import dashboard
|
import dashboard
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger
|
global sqldb, cursor, DashboardConfig, WireguardConfigurations, AllPeerJobs, JobLogger
|
||||||
app_host, app_port = dashboard.gunicornConfig()
|
app_host, app_port = dashboard.gunicornConfig()
|
||||||
|
date = datetime.today().strftime('%Y_%m_%d_%H_%M_%S')
|
||||||
|
|
||||||
|
|
||||||
|
def post_worker_init(worker):
|
||||||
|
dashboard.startThreads()
|
||||||
|
|
||||||
|
|
||||||
worker_class = 'gthread'
|
worker_class = 'gthread'
|
||||||
workers = 1
|
workers = 1
|
||||||
@@ -10,3 +16,11 @@ threads = 1
|
|||||||
bind = f"{app_host}:{app_port}"
|
bind = f"{app_host}:{app_port}"
|
||||||
daemon = True
|
daemon = True
|
||||||
pidfile = './gunicorn.pid'
|
pidfile = './gunicorn.pid'
|
||||||
|
wsgi_app = "dashboard:app"
|
||||||
|
accesslog = f"./log/access_{date}.log"
|
||||||
|
log_level = "debug"
|
||||||
|
capture_output = True
|
||||||
|
errorlog = f"./log/error_{date}.log"
|
||||||
|
print(f"[WGDashboard] WGDashboard w/ Gunicorn will be running on {bind}", flush=True)
|
||||||
|
print(f"[WGDashboard] Access log file is at {accesslog}", flush=True)
|
||||||
|
print(f"[WGDashboard] Error log file is at {errorlog}", flush=True)
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+30
-30
File diff suppressed because one or more lines are too long
Generated
+24
@@ -8,6 +8,7 @@
|
|||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vuepic/vue-datepicker": "^9.0.1",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@vueuse/shared": "^10.9.0",
|
"@vueuse/shared": "^10.9.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
@@ -721,6 +722,20 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz",
|
||||||
"integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA=="
|
"integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@vuepic/vue-datepicker": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vuepic/vue-datepicker/-/vue-datepicker-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-5sSdwib5cY8cE4Y7SCh+Zemfp+U/m6BDcgaPwd5Vmdv5LAASyV0wugn9sTb6NWX0sIQEdrGDl/RmD9EjcIke3A==",
|
||||||
|
"dependencies": {
|
||||||
|
"date-fns": "^3.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.12.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": ">=3.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vueuse/core": {
|
"node_modules/@vueuse/core": {
|
||||||
"version": "10.9.0",
|
"version": "10.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz",
|
||||||
@@ -937,6 +952,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dayjs": {
|
"node_modules/dayjs": {
|
||||||
"version": "1.11.12",
|
"version": "1.11.12",
|
||||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
|
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vuepic/vue-datepicker": "^9.0.1",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
"@vueuse/shared": "^10.9.0",
|
"@vueuse/shared": "^10.9.0",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const store = DashboardConfigurationStore();
|
|||||||
</nav>
|
</nav>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<Transition name="fade2" mode="out-in">
|
<Transition name="app" mode="out-in">
|
||||||
<Component :is="Component"></Component>
|
<Component :is="Component"></Component>
|
||||||
</Transition>
|
</Transition>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
@@ -22,14 +22,16 @@ const store = DashboardConfigurationStore();
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.app-enter-active,
|
.app-enter-active,
|
||||||
.app-leave-active {
|
.app-leave-active {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||||
/*position: absolute;*/
|
}
|
||||||
/*padding-top: 50px*/
|
|
||||||
|
.app-enter-from{
|
||||||
|
transform: translateY(20px);
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-enter-from,
|
|
||||||
.app-leave-to {
|
.app-leave-to {
|
||||||
transform: translateX(-30px);
|
transform: translateY(-20px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
+8
-1
@@ -14,7 +14,6 @@ export default {
|
|||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
allowedIp: [],
|
allowedIp: [],
|
||||||
|
|
||||||
availableIpSearchString: "",
|
availableIpSearchString: "",
|
||||||
customAvailableIp: "",
|
customAvailableIp: "",
|
||||||
allowedIpFormatError: false
|
allowedIpFormatError: false
|
||||||
@@ -45,8 +44,16 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
customAvailableIp(){
|
customAvailableIp(){
|
||||||
this.allowedIpFormatError = false;
|
this.allowedIpFormatError = false;
|
||||||
|
},
|
||||||
|
availableIp(){
|
||||||
|
if (this.availableIp !== undefined && this.availableIp.length > 0){
|
||||||
|
this.addAllowedIp(this.availableIp[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export default {
|
|||||||
@setting="this.$emit('setting')"
|
@setting="this.$emit('setting')"
|
||||||
@jobs="this.$emit('jobs')"
|
@jobs="this.$emit('jobs')"
|
||||||
@refresh="this.$emit('refresh')"
|
@refresh="this.$emit('refresh')"
|
||||||
|
@share="this.$emit('share')"
|
||||||
:Peer="Peer"
|
:Peer="Peer"
|
||||||
v-if="this.subMenuOpened"
|
v-if="this.subMenuOpened"
|
||||||
ref="target"
|
ref="target"
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import PeerJobs from "@/components/configurationComponents/peerJobs.vue";
|
|||||||
import PeerJobsAllModal from "@/components/configurationComponents/peerJobsAllModal.vue";
|
import PeerJobsAllModal from "@/components/configurationComponents/peerJobsAllModal.vue";
|
||||||
import PeerJobsLogsModal from "@/components/configurationComponents/peerJobsLogsModal.vue";
|
import PeerJobsLogsModal from "@/components/configurationComponents/peerJobsLogsModal.vue";
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
|
import PeerShareLinkModal from "@/components/configurationComponents/peerShareLinkModal.vue";
|
||||||
|
|
||||||
Chart.register(
|
Chart.register(
|
||||||
ArcElement,
|
ArcElement,
|
||||||
@@ -70,6 +71,7 @@ Chart.register(
|
|||||||
export default {
|
export default {
|
||||||
name: "peerList",
|
name: "peerList",
|
||||||
components: {
|
components: {
|
||||||
|
PeerShareLinkModal,
|
||||||
PeerJobsLogsModal,
|
PeerJobsLogsModal,
|
||||||
PeerJobsAllModal, PeerJobs, PeerCreate, PeerQRCode, PeerSettings, PeerSearch, Peer, Line, Bar},
|
PeerJobsAllModal, PeerJobs, PeerCreate, PeerQRCode, PeerSettings, PeerSearch, Peer, Line, Bar},
|
||||||
setup(){
|
setup(){
|
||||||
@@ -131,6 +133,10 @@ export default {
|
|||||||
},
|
},
|
||||||
peerScheduleJobsLogs: {
|
peerScheduleJobsLogs: {
|
||||||
modalOpen: false
|
modalOpen: false
|
||||||
|
},
|
||||||
|
peerShare:{
|
||||||
|
modalOpen: false,
|
||||||
|
selectedPeer: undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -141,26 +147,21 @@ export default {
|
|||||||
'$route': {
|
'$route': {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
handler(){
|
handler(){
|
||||||
console.log(this.dashboardConfigurationStore.Peers.RefreshInterval)
|
|
||||||
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
||||||
console.log(this.dashboardConfigurationStore.Peers.RefreshInterval)
|
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
let id = this.$route.params.id;
|
let id = this.$route.params.id;
|
||||||
this.configurationInfo = [];
|
this.configurationInfo = [];
|
||||||
this.configurationPeers = [];
|
this.configurationPeers = [];
|
||||||
if (id){
|
if (id){
|
||||||
this.getPeers(id)
|
this.getPeers(id)
|
||||||
console.log("Changed..")
|
|
||||||
this.setPeerInterval();
|
this.setPeerInterval();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval'(){
|
'dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval'(){
|
||||||
console.log("Changed?")
|
|
||||||
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
||||||
this.setPeerInterval();
|
this.setPeerInterval();
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
beforeRouteLeave(){
|
beforeRouteLeave(){
|
||||||
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
clearInterval(this.dashboardConfigurationStore.Peers.RefreshInterval);
|
||||||
@@ -211,7 +212,8 @@ export default {
|
|||||||
{
|
{
|
||||||
label: 'Data Sent',
|
label: 'Data Sent',
|
||||||
data: [...this.historySentData.datasets[0].data,
|
data: [...this.historySentData.datasets[0].data,
|
||||||
((sent - this.historyDataSentDifference[this.historyDataSentDifference.length - 1])*1000).toFixed(4)],
|
((sent - this.historyDataSentDifference[this.historyDataSentDifference.length - 1])*1000)
|
||||||
|
.toFixed(4)],
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: '#198754',
|
borderColor: '#198754',
|
||||||
tension: 0
|
tension: 0
|
||||||
@@ -231,7 +233,8 @@ export default {
|
|||||||
{
|
{
|
||||||
label: 'Data Received',
|
label: 'Data Received',
|
||||||
data: [...this.historyReceiveData.datasets[0].data,
|
data: [...this.historyReceiveData.datasets[0].data,
|
||||||
((receive - this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1])*1000).toFixed(4)],
|
((receive - this.historyDataReceivedDifference[this.historyDataReceivedDifference.length - 1])*1000)
|
||||||
|
.toFixed(4)],
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: '#0d6efd',
|
borderColor: '#0d6efd',
|
||||||
tension: 0
|
tension: 0
|
||||||
@@ -248,17 +251,24 @@ export default {
|
|||||||
this.dashboardConfigurationStore.Peers.RefreshInterval = setInterval(() => {
|
this.dashboardConfigurationStore.Peers.RefreshInterval = setInterval(() => {
|
||||||
this.getPeers()
|
this.getPeers()
|
||||||
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
}, parseInt(this.dashboardConfigurationStore.Configuration.Server.dashboard_refresh_interval))
|
||||||
console.log(this.dashboardConfigurationStore.Peers.RefreshInterval)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
configurationSummary(){
|
configurationSummary(){
|
||||||
return {
|
const k = {
|
||||||
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
||||||
totalUsage: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b) : 0,
|
totalUsage: this.configurationPeers.length > 0 ?
|
||||||
totalReceive: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b) : 0,
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
totalSent: this.configurationPeers.length > 0 ? this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b) : 0
|
.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b).toFixed(4) : 0,
|
||||||
|
totalReceive: this.configurationPeers.length > 0 ?
|
||||||
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
|
.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b).toFixed(4) : 0,
|
||||||
|
totalSent: this.configurationPeers.length > 0 ?
|
||||||
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
|
.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b).toFixed(4) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return k
|
||||||
},
|
},
|
||||||
receiveData(){
|
receiveData(){
|
||||||
return this.historyReceiveData
|
return this.historyReceiveData
|
||||||
@@ -468,7 +478,7 @@ export default {
|
|||||||
<div class="card-body d-flex">
|
<div class="card-body d-flex">
|
||||||
<div>
|
<div>
|
||||||
<p class="mb-0 text-muted"><small>Total Usage</small></p>
|
<p class="mb-0 text-muted"><small>Total Usage</small></p>
|
||||||
<strong class="h4">{{configurationSummary.totalUsage.toFixed(4)}} GB</strong>
|
<strong class="h4">{{configurationSummary.totalUsage}} GB</strong>
|
||||||
</div>
|
</div>
|
||||||
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
|
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -479,7 +489,7 @@ export default {
|
|||||||
<div class="card-body d-flex">
|
<div class="card-body d-flex">
|
||||||
<div>
|
<div>
|
||||||
<p class="mb-0 text-muted"><small>Total Received</small></p>
|
<p class="mb-0 text-muted"><small>Total Received</small></p>
|
||||||
<strong class="h4 text-primary">{{configurationSummary.totalReceive.toFixed(4)}} GB</strong>
|
<strong class="h4 text-primary">{{configurationSummary.totalReceive}} GB</strong>
|
||||||
</div>
|
</div>
|
||||||
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
|
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -490,7 +500,7 @@ export default {
|
|||||||
<div class="card-body d-flex">
|
<div class="card-body d-flex">
|
||||||
<div>
|
<div>
|
||||||
<p class="mb-0 text-muted"><small>Total Sent</small></p>
|
<p class="mb-0 text-muted"><small>Total Sent</small></p>
|
||||||
<strong class="h4 text-success">{{configurationSummary.totalSent.toFixed(4)}} GB</strong>
|
<strong class="h4 text-success">{{configurationSummary.totalSent}} GB</strong>
|
||||||
</div>
|
</div>
|
||||||
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
|
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
|
||||||
</div>
|
</div>
|
||||||
@@ -548,7 +558,7 @@ export default {
|
|||||||
:key="peer.id"
|
:key="peer.id"
|
||||||
v-for="peer in this.searchPeers">
|
v-for="peer in this.searchPeers">
|
||||||
<Peer :Peer="peer"
|
<Peer :Peer="peer"
|
||||||
|
@share="this.peerShare.selectedPeer = peer.id; this.peerShare.modalOpen = true;"
|
||||||
@refresh="this.getPeers()"
|
@refresh="this.getPeers()"
|
||||||
@jobs="peerScheduleJobs.modalOpen = true; peerScheduleJobs.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
|
@jobs="peerScheduleJobs.modalOpen = true; peerScheduleJobs.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
|
||||||
@setting="peerSetting.modalOpen = true; peerSetting.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
|
@setting="peerSetting.modalOpen = true; peerSetting.selectedPeer = this.configurationPeers.find(x => x.id === peer.id)"
|
||||||
@@ -595,6 +605,12 @@ export default {
|
|||||||
>
|
>
|
||||||
</PeerJobsLogsModal>
|
</PeerJobsLogsModal>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
<Transition name="zoom">
|
||||||
|
<PeerShareLinkModal
|
||||||
|
v-if="this.peerShare.modalOpen"
|
||||||
|
@close="this.peerShare.modalOpen = false; this.peerShare.selectedPeer = undefined;"
|
||||||
|
:peer="this.configurationPeers.find(x => x.id === this.peerShare.selectedPeer)"></PeerShareLinkModal>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
QRCode.toCanvas(document.querySelector("#qrcode"), this.peerConfigData , (error) => {
|
QRCode.toCanvas(document.querySelector("#qrcode"), this.peerConfigData , (error) => {
|
||||||
console.log(this.peerConfigData)
|
|
||||||
if (error) console.error(error)
|
if (error) console.error(error)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-5
@@ -3,10 +3,12 @@ import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJ
|
|||||||
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} from "@/utilities/fetch.js";
|
||||||
|
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "schedulePeerJob",
|
name: "schedulePeerJob",
|
||||||
components: {ScheduleDropdown},
|
components: {VueDatePicker, ScheduleDropdown},
|
||||||
props: {
|
props: {
|
||||||
dropdowns: Array[Object],
|
dropdowns: Array[Object],
|
||||||
pjob: Object,
|
pjob: Object,
|
||||||
@@ -94,6 +96,11 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.$emit('delete')
|
this.$emit('delete')
|
||||||
|
},
|
||||||
|
parseTime(modelData){
|
||||||
|
if(modelData){
|
||||||
|
this.job.Value = dayjs(modelData).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -128,12 +135,26 @@ export default {
|
|||||||
:data="this.job.Operator"
|
:data="this.job.Operator"
|
||||||
@update="(value) => this.job.Operator = value"
|
@update="(value) => this.job.Operator = value"
|
||||||
></ScheduleDropdown>
|
></ScheduleDropdown>
|
||||||
<input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"
|
|
||||||
|
<VueDatePicker
|
||||||
|
:is24="true"
|
||||||
|
:min-date="new Date()"
|
||||||
|
:model-value="this.job.Value"
|
||||||
|
@update:model-value="this.parseTime" time-picker-inline
|
||||||
|
format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
preview-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
:clearable="false"
|
||||||
:disabled="!edit"
|
:disabled="!edit"
|
||||||
type="datetime-local"
|
|
||||||
v-if="this.job.Field === 'date'"
|
v-if="this.job.Field === 'date'"
|
||||||
v-model="this.job.Value"
|
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
|
||||||
style="width: auto">
|
/>
|
||||||
|
|
||||||
|
<!-- <input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"-->
|
||||||
|
<!-- :disabled="!edit"-->
|
||||||
|
<!-- type="datetime-local"-->
|
||||||
|
<!-- v-if="this.job.Field === 'date'"-->
|
||||||
|
<!-- v-model="this.job.Value"-->
|
||||||
|
<!-- style="width: auto">-->
|
||||||
<input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"
|
<input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"
|
||||||
:disabled="!edit"
|
:disabled="!edit"
|
||||||
v-else
|
v-else
|
||||||
@@ -188,4 +209,12 @@ input:disabled{
|
|||||||
background-color: rgba(13, 110, 253, 0.09);
|
background-color: rgba(13, 110, 253, 0.09);
|
||||||
color: #0d6efd;
|
color: #0d6efd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dp__main{
|
||||||
|
width: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
--dp-input-padding: 2.5px 30px 2.5px 12px;
|
||||||
|
--dp-border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -89,67 +89,59 @@ export default {
|
|||||||
class="text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm">
|
class="text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm">
|
||||||
<i class="bi bi-plus-lg me-2"></i>Peer
|
<i class="bi bi-plus-lg me-2"></i>Peer
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
<!-- <RouterLink-->
|
|
||||||
<!-- to="jobs"-->
|
|
||||||
<!-- class="text-decoration-none btn btn-primary rounded-3 btn-sm">-->
|
|
||||||
<!-- <i class="bi bi-app-indicator me-2"></i>Jobs-->
|
|
||||||
<!-- </RouterLink>-->
|
|
||||||
<button class="btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm"
|
<button class="btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm"
|
||||||
@click="this.downloadAllPeer()">
|
@click="this.downloadAllPeer()">
|
||||||
<i class="bi bi-download me-2"></i> Download All
|
<i class="bi bi-download me-2"></i> Download All
|
||||||
</button>
|
</button>
|
||||||
<div class="d-flex align-items-center ms-auto">
|
<div class="flex-grow-1">
|
||||||
<!-- <label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>-->
|
<input class="form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle shadow-sm w-100"
|
||||||
<input class="form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle shadow-sm"
|
|
||||||
placeholder="Search..."
|
placeholder="Search..."
|
||||||
id="searchPeers"
|
id="searchPeers"
|
||||||
@keyup="this.debounce()"
|
@keyup="this.debounce()"
|
||||||
v-model="this.searchString">
|
v-model="this.searchString">
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown">
|
<div class="dropdown dropup">
|
||||||
<button class="btn dropdown-toggle text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
||||||
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-filter-circle me-2"></i>
|
<i class="bi bi-filter-circle me-2"></i>
|
||||||
Sort
|
Display
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu mt-2 shadow rounded-3">
|
<ul class="dropdown-menu mt-2 shadow rounded-3 animate__animated animation__fadeInDropdown dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<small class="dropdown-header">Sort by</small>
|
||||||
|
</li>
|
||||||
<li v-for="(value, key) in this.sort">
|
<li v-for="(value, key) in this.sort">
|
||||||
<a class="dropdown-item d-flex" role="button" @click="this.updateSort(key)">
|
<a class="dropdown-item d-flex align-items-center" role="button" @click="this.updateSort(key)">
|
||||||
<span class="me-auto">{{value}}</span>
|
<small class="me-auto">{{value}}</small>
|
||||||
<i class="bi bi-check text-primary"
|
<i class="bi bi-check text-primary"
|
||||||
v-if="store.Configuration.Server.dashboard_sort === key"></i>
|
v-if="store.Configuration.Server.dashboard_sort === key"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
<li><hr class="dropdown-divider"></li>
|
||||||
</div>
|
<li>
|
||||||
<div class="dropdown">
|
<small class="dropdown-header">Refresh Interval</small>
|
||||||
<button class="btn dropdown-toggle text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
</li>
|
||||||
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
||||||
<i class="bi bi-arrow-repeat me-2"></i>Refresh Interval
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu shadow mt-2 rounded-3">
|
|
||||||
<li v-for="(value, key) in this.interval">
|
<li v-for="(value, key) in this.interval">
|
||||||
<a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)">
|
<a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)">
|
||||||
<span class="me-auto">{{value}}</span>
|
<small class="me-auto">{{value}}</small>
|
||||||
<i class="bi bi-check text-primary"
|
<i class="bi bi-check text-primary"
|
||||||
v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i>
|
v-if="store.Configuration.Server.dashboard_refresh_interval === key"></i>
|
||||||
</a></li>
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dropdown dropup">
|
||||||
<div class="dropdown">
|
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
||||||
<button class="btn dropdown-toggle text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
|
||||||
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
<i class="bi bi-three-dots me-2"></i>More
|
<i class="bi bi-three-dots"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu shadow mt-2 rounded-3">
|
<ul class="dropdown-menu shadow mt-2 rounded-3 animate__animated animation__fadeInDropdown">
|
||||||
<li>
|
<li>
|
||||||
<h6 class="dropdown-header">Peer Jobs</h6>
|
<h6 class="dropdown-header">Peer Jobs</h6>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a role="button" class="dropdown-item" @click="this.$emit('jobsAll')">
|
<a role="button" class="dropdown-item" @click="this.$emit('jobsAll')">
|
||||||
All Active Jobs
|
Active Jobs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -165,4 +157,23 @@ export default {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
.animation__fadeInDropdown{
|
||||||
|
animation-name: fadeInDropdown;
|
||||||
|
animation-duration: 0.2s;
|
||||||
|
animation-timing-function: cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInDropdown{
|
||||||
|
0%{
|
||||||
|
opacity: 0;
|
||||||
|
filter: blur(3px);
|
||||||
|
transform: translateY(-60px);
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
opacity: 1;
|
||||||
|
filter: blur(0px);
|
||||||
|
transform: translateY(-40px);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -37,6 +37,21 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$emit("refresh")
|
this.$emit("refresh")
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
resetPeerData(type){
|
||||||
|
this.saving = true
|
||||||
|
fetchPost(`/api/resetPeerData/${this.$route.params.id}`, {
|
||||||
|
id: this.data.id,
|
||||||
|
type: type
|
||||||
|
}, (res) => {
|
||||||
|
this.saving = false;
|
||||||
|
if (res.status){
|
||||||
|
this.dashboardConfigurationStore.newMessage("Server", "Peer data usage reset successfully.", "success")
|
||||||
|
}else{
|
||||||
|
this.dashboardConfigurationStore.newMessage("Server", res.message, "danger")
|
||||||
|
}
|
||||||
|
this.$emit("refresh")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
@@ -57,15 +72,15 @@ export default {
|
|||||||
<div class="container d-flex h-100 w-100">
|
<div class="container d-flex h-100 w-100">
|
||||||
<div class="m-auto modal-dialog-centered dashboardModal">
|
<div class="m-auto modal-dialog-centered dashboardModal">
|
||||||
<div class="card rounded-3 shadow flex-grow-1">
|
<div class="card rounded-3 shadow flex-grow-1">
|
||||||
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
|
||||||
<h4 class="mb-0">Peer Settings</h4>
|
<h4 class="mb-0">Peer Settings</h4>
|
||||||
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
|
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body px-4 pb-4" v-if="this.data">
|
<div class="card-body px-4 pb-4" v-if="this.data">
|
||||||
<div class="d-flex flex-column gap-2 mb-4">
|
<div class="d-flex flex-column gap-2 mb-4">
|
||||||
<div>
|
<div class="d-flex align-items-center">
|
||||||
<small class="text-muted">Public Key</small><br>
|
<small class="text-muted">Public Key</small>
|
||||||
<small><samp>{{this.data.id}}</samp></small>
|
<small class="ms-auto"><samp>{{this.data.id}}</samp></small>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="peer_name_textbox" class="form-label">
|
<label for="peer_name_textbox" class="form-label">
|
||||||
@@ -121,8 +136,7 @@ export default {
|
|||||||
v-model="this.data.DNS"
|
v-model="this.data.DNS"
|
||||||
id="peer_DNS_textbox">
|
id="peer_DNS_textbox">
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<div class="accordion mt-3" id="peerSettingsAccordion">
|
||||||
<div class="accordion mt-2" id="peerSettingsAccordion">
|
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<h2 class="accordion-header">
|
<h2 class="accordion-header">
|
||||||
<button class="accordion-button rounded-3 collapsed" type="button"
|
<button class="accordion-button rounded-3 collapsed" type="button"
|
||||||
@@ -162,12 +176,37 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex gap-2 align-items-center">
|
||||||
|
<strong>Reset Data Usage</strong>
|
||||||
|
<div class="d-flex gap-2 ms-auto">
|
||||||
|
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
|
||||||
|
@click="this.resetPeerData('total')"
|
||||||
|
>
|
||||||
|
<i class="bi bi-arrow-down-up me-2"></i>
|
||||||
|
Total
|
||||||
|
</button>
|
||||||
|
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
|
||||||
|
@click="this.resetPeerData('receive')"
|
||||||
|
>
|
||||||
|
<i class="bi bi-arrow-down me-2"></i>
|
||||||
|
Received
|
||||||
|
</button>
|
||||||
|
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
|
||||||
|
@click="this.resetPeerData('sent')"
|
||||||
|
>
|
||||||
|
<i class="bi bi-arrow-up me-2"></i>
|
||||||
|
Sent
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<button class="btn btn-secondary rounded-3 shadow"
|
<button class="btn btn-secondary rounded-3 shadow"
|
||||||
@click="this.reset()"
|
@click="this.reset()"
|
||||||
:disabled="!this.dataChanged || this.saving">
|
:disabled="!this.dataChanged || this.saving">
|
||||||
Reset <i class="bi bi-arrow-clockwise ms-2"></i>
|
Revert <i class="bi bi-arrow-clockwise ms-2"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
|
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
|
||||||
|
|||||||
@@ -93,8 +93,23 @@ export default {
|
|||||||
set for this peer
|
set for this peer
|
||||||
</small>
|
</small>
|
||||||
</li>
|
</li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<li class="d-flex" style="padding-left: var(--bs-dropdown-item-padding-x); padding-right: var(--bs-dropdown-item-padding-x);">
|
||||||
|
<a class="dropdown-item text-center px-0 rounded-3" role="button" @click="this.downloadPeer()">
|
||||||
|
<i class="me-auto bi bi-download"></i>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-center px-0 rounded-3" role="button"
|
||||||
|
@click="this.downloadQRCode()">
|
||||||
|
<i class="me-auto bi bi-qr-code"></i>
|
||||||
|
</a>
|
||||||
|
<a class="dropdown-item text-center px-0 rounded-3" role="button" @click="this.$emit('share')">
|
||||||
|
<i class="me-auto bi bi-share"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item d-flex" role="button"
|
<a class="dropdown-item d-flex" role="button"
|
||||||
@click="this.$emit('setting')"
|
@click="this.$emit('setting')"
|
||||||
@@ -109,20 +124,7 @@ export default {
|
|||||||
<i class="me-auto bi bi-app-indicator"></i> Schedule Jobs
|
<i class="me-auto bi bi-app-indicator"></i> Schedule Jobs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<template v-if="this.Peer.private_key">
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item d-flex" role="button" @click="this.downloadPeer()">
|
|
||||||
<i class="me-auto bi bi-download"></i> Download
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item d-flex" role="button"
|
|
||||||
@click="this.downloadQRCode()"
|
|
||||||
>
|
|
||||||
<i class="me-auto bi bi-qr-code"></i> QR Code
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><hr class="dropdown-divider"></li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<script>
|
||||||
|
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||||
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "peerShareLinkModal",
|
||||||
|
props: {
|
||||||
|
peer: Object
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
VueDatePicker
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
dataCopy: undefined,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(){
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
return {store}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.dataCopy = JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0);
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'peer.ShareLink': {
|
||||||
|
deep: true,
|
||||||
|
handler(newVal, oldVal){
|
||||||
|
if (oldVal.length !== newVal.length){
|
||||||
|
this.dataCopy = JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
startSharing(){
|
||||||
|
this.loading = true;
|
||||||
|
fetchPost("/api/sharePeer/create", {
|
||||||
|
Configuration: this.peer.configuration.Name,
|
||||||
|
Peer: this.peer.id,
|
||||||
|
ExpireDate: dayjs().add(7, 'd').format("YYYY-MM-DD HH:mm:ss")
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.peer.ShareLink = res.data;
|
||||||
|
this.dataCopy = res.data.at(0);
|
||||||
|
this.store.newMessage("Server", "Share link created successfully", "success")
|
||||||
|
}else{
|
||||||
|
this.store.newMessage("Server",
|
||||||
|
"Share link failed to create. Reason: " + res.message, "danger")
|
||||||
|
|
||||||
|
}
|
||||||
|
this.loading = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateLinkExpireDate(){
|
||||||
|
fetchPost("/api/sharePeer/update", this.dataCopy, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.dataCopy = res.data.at(0)
|
||||||
|
this.peer.ShareLink = res.data;
|
||||||
|
this.store.newMessage("Server", "Link expire date updated", "success")
|
||||||
|
}else{
|
||||||
|
this.store.newMessage("Server",
|
||||||
|
"Link expire date failed to update. Reason: " + res.message, "danger")
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stopSharing(){
|
||||||
|
this.loading = true;
|
||||||
|
this.dataCopy.ExpireDate = dayjs().format("YYYY-MM-DD HH:mm:ss")
|
||||||
|
this.updateLinkExpireDate()
|
||||||
|
},
|
||||||
|
parseTime(modelData){
|
||||||
|
if(modelData){
|
||||||
|
this.dataCopy.ExpireDate = dayjs(modelData).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
}else{
|
||||||
|
this.dataCopy.ExpireDate = undefined
|
||||||
|
}
|
||||||
|
this.updateLinkExpireDate()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getUrl(){
|
||||||
|
return window.location.origin
|
||||||
|
+ window.location.pathname
|
||||||
|
+ this.$router.resolve(
|
||||||
|
{path: "/share", query: {"ShareID": this.dataCopy.ShareID}}).href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
|
||||||
|
<div class="container d-flex h-100 w-100">
|
||||||
|
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 500px">
|
||||||
|
<div class="card rounded-3 shadow flex-grow-1">
|
||||||
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
|
||||||
|
<h4 class="mb-0">Share Peer</h4>
|
||||||
|
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-4 pb-4" v-if="this.peer.ShareLink">
|
||||||
|
<div v-if="!this.dataCopy">
|
||||||
|
<h6 class="mb-3 text-muted">
|
||||||
|
Currently the peer is not sharing
|
||||||
|
</h6>
|
||||||
|
<button
|
||||||
|
@click="this.startSharing()"
|
||||||
|
:disabled="this.loading"
|
||||||
|
class="w-100 btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm">
|
||||||
|
<span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}">
|
||||||
|
<i class="bi bi-send-fill me-2" ></i>
|
||||||
|
</span>
|
||||||
|
{{this.loading ? "Sharing...":"Start Sharing"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="d-flex gap-2 mb-4">
|
||||||
|
<i class="bi bi-link-45deg"></i>
|
||||||
|
<a :href="this.getUrl"
|
||||||
|
class="text-decoration-none" target="_blank">
|
||||||
|
{{ getUrl }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column gap-2 mb-3">
|
||||||
|
<small>
|
||||||
|
<i class="bi bi-calendar me-2"></i>
|
||||||
|
Expire Date
|
||||||
|
</small>
|
||||||
|
<VueDatePicker
|
||||||
|
:is24="true"
|
||||||
|
:min-date="new Date()"
|
||||||
|
:model-value="this.dataCopy.ExpireDate"
|
||||||
|
@update:model-value="this.parseTime" time-picker-inline
|
||||||
|
format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
preview-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
|
||||||
|
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="this.stopSharing()"
|
||||||
|
:disabled="this.loading"
|
||||||
|
class="w-100 btn bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle rounded-3 shadow-sm">
|
||||||
|
<span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}">
|
||||||
|
<i class="bi bi-send-slash-fill me-2" ></i>
|
||||||
|
</span>
|
||||||
|
{{this.loading ? "Stop Sharing...":"Stop Sharing"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -8,7 +8,6 @@ export default {
|
|||||||
components: {ConfigurationCard},
|
components: {ConfigurationCard},
|
||||||
async setup(){
|
async setup(){
|
||||||
const wireguardConfigurationsStore = WireguardConfigurationsStore();
|
const wireguardConfigurationsStore = WireguardConfigurationsStore();
|
||||||
|
|
||||||
return {wireguardConfigurationsStore}
|
return {wireguardConfigurationsStore}
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
@@ -19,6 +18,13 @@ export default {
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
await this.wireguardConfigurationsStore.getConfigurations();
|
await this.wireguardConfigurationsStore.getConfigurations();
|
||||||
this.configurationLoaded = true;
|
this.configurationLoaded = true;
|
||||||
|
|
||||||
|
this.wireguardConfigurationsStore.ConfigurationListInterval = setInterval(() => {
|
||||||
|
this.wireguardConfigurationsStore.getConfigurations()
|
||||||
|
}, 10000)
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
clearInterval(this.wireguardConfigurationsStore.ConfigurationListInterval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -28,7 +34,9 @@ export default {
|
|||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="d-flex mb-4 ">
|
<div class="d-flex mb-4 ">
|
||||||
<h3 class="text-body">WireGuard Configurations</h3>
|
<h3 class="text-body">
|
||||||
|
<i class="bi bi-body-text me-2"></i>
|
||||||
|
WireGuard Configurations</h3>
|
||||||
<RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-3">
|
<RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-3">
|
||||||
<i class="bi bi-plus-circle-fill me-2"></i>
|
<i class="bi bi-plus-circle-fill me-2"></i>
|
||||||
Configuration
|
Configuration
|
||||||
@@ -40,7 +48,7 @@ export default {
|
|||||||
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">
|
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">
|
||||||
You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".
|
You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".
|
||||||
</p>
|
</p>
|
||||||
<div class="d-flex gap-3 flex-column" v-else>
|
<div class="d-flex gap-3 flex-column mb-3" v-else>
|
||||||
<ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard>
|
<ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export default {
|
|||||||
Status: Boolean,
|
Status: Boolean,
|
||||||
PublicKey: String,
|
PublicKey: String,
|
||||||
PrivateKey: String
|
PrivateKey: String
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
@@ -52,9 +51,24 @@ export default {
|
|||||||
<i class="bi bi-chevron-right"></i>
|
<i class="bi bi-chevron-right"></i>
|
||||||
</h6>
|
</h6>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div class="card-footer d-flex align-items-center">
|
<div class="card-footer d-flex gap-2 flex-column">
|
||||||
|
<div class="d-flex gap-4">
|
||||||
|
<small >
|
||||||
|
<i class="bi bi-arrow-down-up me-2"></i>{{c.DataUsage.Total > 0 ? c.DataUsage.Total.toFixed(4) : 0}} GB
|
||||||
|
</small>
|
||||||
|
<small class="text-primary-emphasis">
|
||||||
|
<i class="bi bi-arrow-down me-2"></i>{{c.DataUsage.Receive > 0 ? c.DataUsage.Receive.toFixed(4) : 0}} GB
|
||||||
|
</small>
|
||||||
|
<small class="text-success-emphasis">
|
||||||
|
<i class="bi bi-arrow-up me-2"></i>{{c.DataUsage.Sent > 0 ? c.DataUsage.Sent.toFixed(4) : 0}} GB
|
||||||
|
</small>
|
||||||
|
<small class="ms-auto">
|
||||||
|
<span class="dot me-2" :class="{active: c.ConnectedPeers > 0}"></span>{{c.ConnectedPeers}} Peers
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
<small class="me-2 text-muted">
|
<small class="me-2 text-muted">
|
||||||
<strong>PUBLIC KEY</strong>
|
<strong>Public Key</strong>
|
||||||
</small>
|
</small>
|
||||||
<small class="mb-0 d-block d-lg-inline-block ">
|
<small class="mb-0 d-block d-lg-inline-block ">
|
||||||
<samp style="line-break: anywhere">{{c.PublicKey}}</samp>
|
<samp style="line-break: anywhere">{{c.PublicKey}}</samp>
|
||||||
@@ -76,6 +90,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -17,41 +17,52 @@ export default {
|
|||||||
<div class="col-md-3 col-lg-2 d-md-block p-3" style="height: calc(-50px + 100vh);">
|
<div class="col-md-3 col-lg-2 d-md-block p-3" style="height: calc(-50px + 100vh);">
|
||||||
<nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow overflow-y-scroll" >
|
<nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow overflow-y-scroll" >
|
||||||
<div class="sidebar-sticky pt-3">
|
<div class="sidebar-sticky pt-3">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column px-2">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink class="nav-link" to="/" exact-active-class="active">Home</RouterLink></li>
|
<RouterLink class="nav-link rounded-3"
|
||||||
|
to="/" exact-active-class="active">
|
||||||
|
<i class="bi bi-house me-2"></i>
|
||||||
|
Home</RouterLink></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink class="nav-link" to="/settings"
|
<RouterLink class="nav-link rounded-3" to="/settings"
|
||||||
exact-active-class="active">Settings</RouterLink></li>
|
exact-active-class="active">
|
||||||
|
<i class="bi bi-gear me-2"></i>
|
||||||
|
Settings</RouterLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr class="text-body">
|
||||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center">
|
||||||
<span>Configurations</span>
|
<i class="bi bi-body-text me-2"></i>
|
||||||
|
Configurations
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column px-2">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink :to="'/configuration/'+c.Name + '/peers'" class="nav-link nav-conf-link"
|
<RouterLink :to="'/configuration/'+c.Name + '/peers'" class="nav-link nav-conf-link rounded-3"
|
||||||
active-class="active"
|
active-class="active"
|
||||||
|
|
||||||
v-for="c in this.wireguardConfigurationsStore.Configurations">
|
v-for="c in this.wireguardConfigurationsStore.Configurations">
|
||||||
<samp>{{c.Name}}</samp>
|
{{c.Name}}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr class="text-body">
|
||||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center">
|
||||||
<span>Tools</span>
|
<i class="bi bi-tools me-2"></i>
|
||||||
|
Tools
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column px-2">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink to="/ping" class="nav-link" active-class="active">Ping</RouterLink></li>
|
<RouterLink to="/ping" class="nav-link rounded-3" active-class="active">Ping</RouterLink></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink to="/traceroute" class="nav-link" active-class="active">Traceroute</RouterLink>
|
<RouterLink to="/traceroute" class="nav-link rounded-3" active-class="active">Traceroute</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr class="text-body">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column px-2">
|
||||||
<li class="nav-item"><a class="nav-link text-danger" @click="this.dashboardConfigurationStore.signOut()" role="button" style="font-weight: bold">Sign Out</a></li>
|
<li class="nav-item"><a class="nav-link text-danger rounded-3"
|
||||||
|
@click="this.dashboardConfigurationStore.signOut()"
|
||||||
|
role="button" style="font-weight: bold">
|
||||||
|
<i class="bi bi-box-arrow-left me-2"></i>
|
||||||
|
Sign Out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item"><a href="https://github.com/donaldzou/WGDashboard/releases/tag/"><small class="nav-link text-muted"></small></a></li>
|
<li class="nav-item"><a href="https://github.com/donaldzou/WGDashboard/releases/tag/"><small class="nav-link text-muted"></small></a></li>
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<script>
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import {v4} from "uuid";
|
||||||
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "accountSettingsMFA",
|
||||||
|
setup(){
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
const uuid = `input_${v4()}`;
|
||||||
|
return {store, uuid};
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
status: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.status = this.store.Configuration.Account["enable_totp"]
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async resetMFA(){
|
||||||
|
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||||
|
section: "Account",
|
||||||
|
key: "totp_verified",
|
||||||
|
value: "false"
|
||||||
|
}, async (res) => {
|
||||||
|
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||||
|
section: "Account",
|
||||||
|
key: "enable_totp",
|
||||||
|
value: "false"
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.$router.push("/2FASetup")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<strong>Multi-Factor Authentication</strong>
|
||||||
|
<div class="form-check form-switch ms-3">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
v-model="this.status"
|
||||||
|
role="switch" id="allowAPIKeysSwitch">
|
||||||
|
</div>
|
||||||
|
<button class="btn bg-warning-subtle text-warning-emphasis border-1 border-warning-subtle ms-auto rounded-3 shadow-sm"
|
||||||
|
v-if="this.status" @click="this.resetMFA()">
|
||||||
|
<i class="bi bi-shield-lock-fill me-2"></i>
|
||||||
|
{{this.store.Configuration.Account["totp_verified"] ? "Reset" : "Setup" }} MFA
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
+21
-5
@@ -2,13 +2,15 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {fetchPost} from "@/utilities/fetch.js";
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import VueDatePicker from "@vuepic/vue-datepicker";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "newDashboardAPIKey",
|
name: "newDashboardAPIKey",
|
||||||
|
components: {VueDatePicker},
|
||||||
data(){
|
data(){
|
||||||
return{
|
return{
|
||||||
newKeyData:{
|
newKeyData:{
|
||||||
ExpiredAt: dayjs().add(1, 'd').format("YYYY-MM-DDTHH:mm:ss"),
|
ExpiredAt: dayjs().add(7, 'd').format("YYYY-MM-DD HH:mm:ss"),
|
||||||
neverExpire: false
|
neverExpire: false
|
||||||
},
|
},
|
||||||
submitting: false
|
submitting: false
|
||||||
@@ -39,6 +41,13 @@ export default {
|
|||||||
fixDate(date){
|
fixDate(date){
|
||||||
console.log(dayjs(date).format("YYYY-MM-DDTHH:mm:ss"))
|
console.log(dayjs(date).format("YYYY-MM-DDTHH:mm:ss"))
|
||||||
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss")
|
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss")
|
||||||
|
},
|
||||||
|
parseTime(modelData){
|
||||||
|
if(modelData){
|
||||||
|
this.newKeyData.ExpiredAt = dayjs(modelData).format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
}else{
|
||||||
|
this.newKeyData.ExpiredAt = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,16 +58,23 @@ export default {
|
|||||||
style="background-color: #00000060; backdrop-filter: blur(3px)">
|
style="background-color: #00000060; backdrop-filter: blur(3px)">
|
||||||
<div class="card m-auto rounded-3 mt-5">
|
<div class="card m-auto rounded-3 mt-5">
|
||||||
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
|
||||||
Create API Key
|
<h6 class="mb-0">Create API Key</h6>
|
||||||
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
|
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body d-flex gap-2 p-4 flex-column">
|
<div class="card-body d-flex gap-2 p-4 flex-column">
|
||||||
<small class="text-muted">When should this API Key expire?</small>
|
<small class="text-muted">When should this API Key expire?</small>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<input class="form-control" type="datetime-local"
|
<VueDatePicker
|
||||||
@change="this.newKeyData.ExpiredAt = this.fixDate(this.newKeyData.ExpiredAt)"
|
:is24="true"
|
||||||
|
:min-date="new Date()"
|
||||||
|
:model-value="this.newKeyData.ExpiredAt"
|
||||||
|
@update:model-value="this.parseTime" time-picker-inline
|
||||||
|
format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
preview-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
:clearable="false"
|
||||||
:disabled="this.newKeyData.neverExpire || this.submitting"
|
:disabled="this.newKeyData.neverExpire || this.submitting"
|
||||||
v-model="this.newKeyData.ExpiredAt">
|
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox"
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "totp",
|
name: "totp",
|
||||||
async setup(){
|
async setup(){
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
let l = ""
|
let l = ""
|
||||||
await fetchGet("/api/Welcome_GetTotpLink", {}, (res => {
|
await fetchGet("/api/Welcome_GetTotpLink", {}, (res => {
|
||||||
if (res.status) l = res.data;
|
if (res.status) l = res.data;
|
||||||
}));
|
}));
|
||||||
return {l}
|
return {l, store}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.l) {
|
if (this.l) {
|
||||||
@@ -58,7 +60,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mb-3">
|
<div class="container-fluid login-container-fluid d-flex main pt-5 overflow-scroll"
|
||||||
|
:data-bs-theme="this.store.Configuration.Server.dashboard_theme">
|
||||||
|
<div class="m-auto text-body" style="width: 500px">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<div>
|
||||||
|
<h1 class="dashboardLogo display-4">Multi-Factor Authentication</h1>
|
||||||
<p class="mb-2"><small class="text-muted">1. Please scan the following QR Code to generate TOTP</small></p>
|
<p class="mb-2"><small class="text-muted">1. Please scan the following QR Code to generate TOTP</small></p>
|
||||||
<canvas id="qrcode" class="rounded-3 mb-2"></canvas>
|
<canvas id="qrcode" class="rounded-3 mb-2"></canvas>
|
||||||
<div class="p-3 bg-body-secondary rounded-3 border mb-3">
|
<div class="p-3 bg-body-secondary rounded-3 border mb-3">
|
||||||
@@ -66,7 +73,7 @@ export default {
|
|||||||
</p><a :href="this.l"><code style="line-break: anywhere">{{this.l}}</code></a>
|
</p><a :href="this.l"><code style="line-break: anywhere">{{this.l}}</code></a>
|
||||||
</div>
|
</div>
|
||||||
<label for="totp" class="mb-2"><small class="text-muted">2. Enter the TOTP generated by your authenticator to verify</small></label>
|
<label for="totp" class="mb-2"><small class="text-muted">2. Enter the TOTP generated by your authenticator to verify</small></label>
|
||||||
<div class="form-group">
|
<div class="form-group mb-2">
|
||||||
<input class="form-control text-center totp"
|
<input class="form-control text-center totp"
|
||||||
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
|
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
|
||||||
v-model="this.totp"
|
v-model="this.totp"
|
||||||
@@ -79,7 +86,32 @@ export default {
|
|||||||
TOTP verified!
|
TOTP verified!
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="alert alert-warning rounded-3">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> If you ever lost your TOTP and can't login, please follow instruction on
|
||||||
|
<a href="https://github.com/donaldzou/WGDashboard" target="_blank">readme.md</a> to reset.
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex gap-3 mt-5 flex-column">
|
||||||
|
<RouterLink
|
||||||
|
to="/"
|
||||||
|
v-if="!this.verified"
|
||||||
|
class="btn bg-secondary-subtle text-secondary-emphasis
|
||||||
|
rounded-3
|
||||||
|
flex-grow-1 btn-lg border-1 border-secondary-subtle shadow d-flex">
|
||||||
|
I don't need MFA <i class="bi bi-chevron-right ms-auto"></i>
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
to="/"
|
||||||
|
v-else class="btn btn-dark btn-lg d-flex btn-brand shadow align-items-center flex-grow-1 rounded-3">
|
||||||
|
Complete <i class="bi bi-chevron-right ms-auto"></i>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'bootstrap/dist/css/bootstrap.css'
|
|||||||
import 'bootstrap/dist/js/bootstrap.js'
|
import 'bootstrap/dist/js/bootstrap.js'
|
||||||
import 'bootstrap-icons/font/bootstrap-icons.css'
|
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||||
import 'animate.css/animate.compat.css'
|
import 'animate.css/animate.compat.css'
|
||||||
|
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'
|
||||||
|
|||||||
@@ -4,20 +4,18 @@ import Index from "@/views/index.vue"
|
|||||||
import Signin from "@/views/signin.vue";
|
import Signin from "@/views/signin.vue";
|
||||||
import ConfigurationList from "@/components/configurationList.vue";
|
import ConfigurationList from "@/components/configurationList.vue";
|
||||||
import {fetchGet} from "@/utilities/fetch.js";
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
|
||||||
import Settings from "@/views/settings.vue";
|
import Settings from "@/views/settings.vue";
|
||||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
import Setup from "@/views/setup.vue";
|
import Setup from "@/views/setup.vue";
|
||||||
import NewConfiguration from "@/views/newConfiguration.vue";
|
import NewConfiguration from "@/views/newConfiguration.vue";
|
||||||
import Configuration from "@/views/configuration.vue";
|
import Configuration from "@/views/configuration.vue";
|
||||||
import PeerSettings from "@/components/configurationComponents/peerSettings.vue";
|
|
||||||
import PeerList from "@/components/configurationComponents/peerList.vue";
|
import PeerList from "@/components/configurationComponents/peerList.vue";
|
||||||
import PeerCreate from "@/components/configurationComponents/peerCreate.vue";
|
import PeerCreate from "@/components/configurationComponents/peerCreate.vue";
|
||||||
import RestrictedPeers from "@/components/configurationComponents/restrictedPeers.vue";
|
|
||||||
import Ping from "@/views/ping.vue";
|
import Ping from "@/views/ping.vue";
|
||||||
import Traceroute from "@/views/traceroute.vue";
|
import Traceroute from "@/views/traceroute.vue";
|
||||||
import PeerJobs from "@/components/configurationComponents/peerJobs.vue";
|
import Totp from "@/components/setupComponent/totp.vue";
|
||||||
|
import Share from "@/views/share.vue";
|
||||||
|
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
let result = false
|
let result = false
|
||||||
@@ -30,6 +28,7 @@ const checkAuth = async () => {
|
|||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "Index",
|
name: "Index",
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -90,7 +89,6 @@ const router = createRouter({
|
|||||||
path: 'create',
|
path: 'create',
|
||||||
component: PeerCreate
|
component: PeerCreate
|
||||||
},
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -105,8 +103,22 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: '/welcome', component: Setup,
|
path: '/welcome', component: Setup,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true
|
requiresAuth: true,
|
||||||
|
title: "Welcome to WGDashboard"
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/2FASetup', component: Totp,
|
||||||
|
meta: {
|
||||||
|
requiresAuth: true,
|
||||||
|
title: "Multi-Factor Authentication Setup"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/share', component: Share,
|
||||||
|
meta: {
|
||||||
|
title: "Share"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -136,6 +148,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}else{
|
}else{
|
||||||
dashboardConfigurationStore.Redirect = to;
|
dashboardConfigurationStore.Redirect = to;
|
||||||
next("/signin")
|
next("/signin")
|
||||||
|
dashboardConfigurationStore.newMessage("WGDashboard", "Session Ended", "warning")
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
Configurations: undefined,
|
Configurations: undefined,
|
||||||
searchString: "",
|
searchString: "",
|
||||||
|
ConfigurationListInterval: undefined,
|
||||||
PeerScheduleJobs: {
|
PeerScheduleJobs: {
|
||||||
dropdowns: {
|
dropdowns: {
|
||||||
Field: [
|
Field: [
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import router from "@/router/index.js";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
||||||
const urlSearchParams = new URLSearchParams(params);
|
const urlSearchParams = new URLSearchParams(params);
|
||||||
await fetch(`${url}?${urlSearchParams.toString()}`, {
|
await fetch(`${url}?${urlSearchParams.toString()}`, {
|
||||||
@@ -5,13 +7,21 @@ export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
|||||||
"content-type": "application/json"
|
"content-type": "application/json"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(x => x.json())
|
.then((x) => {
|
||||||
.then(x => callback ? callback(x) : undefined)
|
const store = DashboardConfigurationStore();
|
||||||
.catch(x => {
|
if (!x.ok){
|
||||||
// let router = useRouter()
|
if (x.status !== 200){
|
||||||
// if (x.status === 401){
|
if (x.status === 401){
|
||||||
// router.push('/signin')
|
router.push({path: '/signin'})
|
||||||
// }
|
store.newMessage("WGDashboard", "Session Ended", "warning")
|
||||||
|
}
|
||||||
|
throw new Error(x.statusText)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return x.json()
|
||||||
|
}
|
||||||
|
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||||
|
console.log(x)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,10 +32,20 @@ export const fetchPost = async (url, body, callback) => {
|
|||||||
},
|
},
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
})
|
}).then((x) => {
|
||||||
.then(x => x.json())
|
const store = DashboardConfigurationStore();
|
||||||
.then(x => callback ? callback(x) : undefined)
|
if (!x.ok){
|
||||||
// .catch(() => {
|
if (x.status !== 200){
|
||||||
// alert("Error occurred! Check console")
|
if (x.status === 401){
|
||||||
// });
|
router.push({path: '/signin'})
|
||||||
|
store.newMessage("WGDashboard", "Session Ended", "warning")
|
||||||
|
}
|
||||||
|
throw new Error(x.statusText)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return x.json()
|
||||||
|
}
|
||||||
|
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||||
|
console.log(x)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,13 @@ import DashboardTheme from "@/components/settingsComponent/dashboardTheme.vue";
|
|||||||
import DashboardSettingsInputIPAddressAndPort
|
import DashboardSettingsInputIPAddressAndPort
|
||||||
from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue";
|
from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue";
|
||||||
import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vue";
|
import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vue";
|
||||||
|
import AccountSettingsMFA from "@/components/settingsComponent/accountSettingsMFA.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "settings",
|
name: "settings",
|
||||||
methods: {ipV46RegexCheck},
|
methods: {ipV46RegexCheck},
|
||||||
components: {
|
components: {
|
||||||
|
AccountSettingsMFA,
|
||||||
DashboardAPIKeys,
|
DashboardAPIKeys,
|
||||||
DashboardSettingsInputIPAddressAndPort,
|
DashboardSettingsInputIPAddressAndPort,
|
||||||
DashboardTheme,
|
DashboardTheme,
|
||||||
@@ -49,7 +51,7 @@ export default {
|
|||||||
<PeersDefaultSettingsInput targetData="peer_mtu" title="MTU (Max Transmission Unit)"></PeersDefaultSettingsInput>
|
<PeersDefaultSettingsInput targetData="peer_mtu" title="MTU (Max Transmission Unit)"></PeersDefaultSettingsInput>
|
||||||
<PeersDefaultSettingsInput targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
|
<PeersDefaultSettingsInput targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
|
||||||
<PeersDefaultSettingsInput targetData="remote_endpoint" title="Peer Remote Endpoint"
|
<PeersDefaultSettingsInput targetData="remote_endpoint" title="Peer Remote Endpoint"
|
||||||
:warning="true" warningText="This will be change 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."
|
||||||
></PeersDefaultSettingsInput>
|
></PeersDefaultSettingsInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,14 +69,16 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
<div class="card mb-4 shadow rounded-3">
|
<div class="card mb-4 shadow rounded-3">
|
||||||
<p class="card-header">Account Settings</p>
|
<p class="card-header">Account Settings</p>
|
||||||
<div class="card-body">
|
<div class="card-body d-flex gap-4 flex-column">
|
||||||
<AccountSettingsInputUsername targetData="username"
|
<AccountSettingsInputUsername targetData="username"
|
||||||
title="Username"
|
title="Username"
|
||||||
></AccountSettingsInputUsername>
|
></AccountSettingsInputUsername>
|
||||||
<hr>
|
<hr class="m-0">
|
||||||
<AccountSettingsInputPassword
|
<AccountSettingsInputPassword
|
||||||
targetData="password">
|
targetData="password">
|
||||||
</AccountSettingsInputPassword>
|
</AccountSettingsInputPassword>
|
||||||
|
<hr class="m-0">
|
||||||
|
<AccountSettingsMFA></AccountSettingsMFA>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DashboardAPIKeys></DashboardAPIKeys>
|
<DashboardAPIKeys></DashboardAPIKeys>
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
import QRCode from 'qrcode'
|
|
||||||
import Totp from "@/components/setupComponent/totp.vue";
|
|
||||||
import {fetchPost} from "@/utilities/fetch.js";
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
export default {
|
export default {
|
||||||
name: "setup",
|
name: "setup",
|
||||||
components: {Totp},
|
components: {},
|
||||||
setup(){
|
setup(){
|
||||||
const store = DashboardConfigurationStore();
|
const store = DashboardConfigurationStore();
|
||||||
return {store}
|
return {store}
|
||||||
@@ -16,8 +14,7 @@ export default {
|
|||||||
username: "",
|
username: "",
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
repeatNewPassword: "",
|
repeatNewPassword: "",
|
||||||
enable_totp: false,
|
enable_totp: true
|
||||||
verified_totp: false
|
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
errorMessage: "",
|
errorMessage: "",
|
||||||
@@ -30,7 +27,6 @@ export default {
|
|||||||
&& this.setup.newPassword.length >= 8
|
&& this.setup.newPassword.length >= 8
|
||||||
&& this.setup.repeatNewPassword.length >= 8
|
&& this.setup.repeatNewPassword.length >= 8
|
||||||
&& this.setup.newPassword === this.setup.repeatNewPassword
|
&& this.setup.newPassword === this.setup.repeatNewPassword
|
||||||
&& ((this.setup.enable_totp && this.setup.verified_totp) || !this.setup.enable_totp)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -39,9 +35,7 @@ export default {
|
|||||||
fetchPost("/api/Welcome_Finish", this.setup, (res) => {
|
fetchPost("/api/Welcome_Finish", this.setup, (res) => {
|
||||||
if (res.status){
|
if (res.status){
|
||||||
this.done = true;
|
this.done = true;
|
||||||
setTimeout(() => {
|
this.$router.push('/2FASetup')
|
||||||
this.$router.push('/')
|
|
||||||
}, 500)
|
|
||||||
}else{
|
}else{
|
||||||
document.querySelectorAll("#createAccount input").forEach(x => x.classList.add("is-invalid"))
|
document.querySelectorAll("#createAccount input").forEach(x => x.classList.add("is-invalid"))
|
||||||
this.errorMessage = res.message;
|
this.errorMessage = res.message;
|
||||||
@@ -62,7 +56,7 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-fluid login-container-fluid d-flex main pt-5 overflow-scroll"
|
<div class="container-fluid login-container-fluid d-flex main pt-5 overflow-scroll"
|
||||||
:data-bs-theme="this.store.Configuration.Server.dashboard_theme">
|
:data-bs-theme="this.store.Configuration.Server.dashboard_theme">
|
||||||
<div class="mx-auto text-body" style="width: 500px">
|
<div class="m-auto text-body" style="width: 500px">
|
||||||
<span class="dashboardLogo display-4">Nice to meet you!</span>
|
<span class="dashboardLogo display-4">Nice to meet you!</span>
|
||||||
<p class="mb-5">Please fill in the following fields to finish setup 😊</p>
|
<p class="mb-5">Please fill in the following fields to finish setup 😊</p>
|
||||||
<div>
|
<div>
|
||||||
@@ -94,26 +88,23 @@ export default {
|
|||||||
class="form-control" id="confirmPassword" name="confirmPassword" placeholder="and you can remember it :)" required>
|
class="form-control" id="confirmPassword" name="confirmPassword" placeholder="and you can remember it :)" required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<!-- <div class="form-check form-switch">-->
|
||||||
<div class="form-check form-switch">
|
<!-- <input class="form-check-input" type="checkbox" role="switch" id="enable_totp" -->
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="enable_totp"
|
<!-- v-model="this.setup.enable_totp">-->
|
||||||
v-model="this.setup.enable_totp">
|
<!-- <label class="form-check-label" -->
|
||||||
<label class="form-check-label"
|
<!-- for="enable_totp">Enable 2 Factor Authentication? <strong>Strongly recommended</strong></label>-->
|
||||||
for="enable_totp">Enable 2 Factor Authentication? <strong>Strongly recommended</strong></label>
|
<!-- </div>-->
|
||||||
</div>
|
<!-- <Suspense>-->
|
||||||
<Suspense>
|
<!-- <Transition name="fade">-->
|
||||||
<Transition name="fade">
|
<!-- <Totp v-if="this.setup.enable_totp" @verified="this.setup.verified_totp = true"></Totp>-->
|
||||||
<Totp v-if="this.setup.enable_totp" @verified="this.setup.verified_totp = true"></Totp>
|
<!-- </Transition>-->
|
||||||
</Transition>
|
<!-- </Suspense>-->
|
||||||
</Suspense>
|
|
||||||
|
|
||||||
<button class="btn btn-dark btn-lg mb-5 d-flex btn-brand shadow align-items-center"
|
<button class="btn btn-dark btn-lg mb-5 d-flex btn-brand shadow align-items-center"
|
||||||
ref="signInBtn"
|
ref="signInBtn"
|
||||||
:disabled="!this.goodToSubmit || this.loading || this.done" @click="this.submit()">
|
:disabled="!this.goodToSubmit || this.loading || this.done" @click="this.submit()">
|
||||||
<span class="d-flex align-items-center w-100" v-if="!this.loading && !this.done">
|
<span class="d-flex align-items-center w-100" v-if="!this.loading && !this.done">
|
||||||
Finish<i class="bi bi-chevron-right ms-auto"></i></span>
|
Next<i class="bi bi-chevron-right ms-auto"></i></span>
|
||||||
<span class="d-flex align-items-center w-100" v-else-if="this.done">
|
|
||||||
Welcome to WGDashboard!</span>
|
|
||||||
<span class="d-flex align-items-center w-100" v-else>
|
<span class="d-flex align-items-center w-100" v-else>
|
||||||
Saving...<span class="spinner-border ms-auto spinner-border-sm" role="status">
|
Saving...<span class="spinner-border ms-auto spinner-border-sm" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<script>
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
import {ref} from "vue";
|
||||||
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "share",
|
||||||
|
async setup(){
|
||||||
|
const route = useRoute();
|
||||||
|
const loaded = ref(false)
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
const theme = ref("");
|
||||||
|
const peerConfiguration = ref("");
|
||||||
|
const blob = ref(new Blob())
|
||||||
|
await fetchGet("/api/getDashboardTheme", {}, (res) => {
|
||||||
|
theme.value = res.data
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = route.query.ShareID
|
||||||
|
if(id === undefined || id.length === 0){
|
||||||
|
peerConfiguration.value = undefined
|
||||||
|
loaded.value = true;
|
||||||
|
}else{
|
||||||
|
await fetchGet("/api/sharePeer/get", {
|
||||||
|
ShareID: id
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
peerConfiguration.value = res.data;
|
||||||
|
blob.value = new Blob([peerConfiguration.value.file], { type: "text/plain" });
|
||||||
|
}else{
|
||||||
|
peerConfiguration.value = undefined
|
||||||
|
}
|
||||||
|
loaded.value = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {store, theme, peerConfiguration, blob}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
QRCode.toCanvas(document.querySelector("#qrcode"), this.peerConfiguration.file , (error) => {
|
||||||
|
if (error) console.error(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
download(){
|
||||||
|
const blob = new Blob([this.peerConfiguration.file], { type: "text/plain" });
|
||||||
|
const jsonObjectUrl = URL.createObjectURL(blob);
|
||||||
|
const filename = `${this.peerConfiguration.fileName}.conf`;
|
||||||
|
const anchorEl = document.createElement("a");
|
||||||
|
anchorEl.href = jsonObjectUrl;
|
||||||
|
anchorEl.download = filename;
|
||||||
|
anchorEl.click();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed:{
|
||||||
|
getBlob(){
|
||||||
|
return URL.createObjectURL(this.blob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="container-fluid login-container-fluid d-flex main pt-5 overflow-scroll"
|
||||||
|
:data-bs-theme="this.theme">
|
||||||
|
<div class="m-auto text-body" style="width: 500px">
|
||||||
|
<div class="text-center position-relative" style=""
|
||||||
|
v-if="!this.peerConfiguration">
|
||||||
|
<div class="animate__animated animate__fadeInUp">
|
||||||
|
<h1 style="font-size: 20rem; filter: blur(1rem); animation-duration: 7s"
|
||||||
|
class="animate__animated animate__flash animate__infinite">
|
||||||
|
<i class="bi bi-file-binary"></i>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="position-absolute w-100 h-100 top-0 start-0 d-flex animate__animated animate__fadeInUp"
|
||||||
|
style="animation-delay: 0.1s;"
|
||||||
|
>
|
||||||
|
<h3 class="m-auto">Oh no... This link is either expired or invalid.</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="d-flex align-items-center flex-column gap-3">
|
||||||
|
<div class="h1 dashboardLogo text-center animate__animated animate__fadeInUp">
|
||||||
|
<h6>WGDashboard</h6>
|
||||||
|
Scan QR Code from the WireGuard App
|
||||||
|
</div>
|
||||||
|
<canvas id="qrcode" class="rounded-3 shadow animate__animated animate__fadeInUp mb-3" ref="qrcode"></canvas>
|
||||||
|
<p class="text-muted animate__animated animate__fadeInUp mb-1"
|
||||||
|
style="animation-delay: 0.2s;"
|
||||||
|
>or click the button below to download the <samp>.conf</samp> file</p>
|
||||||
|
<a
|
||||||
|
:download="this.peerConfiguration.fileName + '.conf'"
|
||||||
|
:href="getBlob"
|
||||||
|
class="btn btn-lg bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle animate__animated animate__fadeInUp shadow-sm"
|
||||||
|
style="animation-delay: 0.25s;"
|
||||||
|
|
||||||
|
>
|
||||||
|
<i class="bi bi-download"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.animate__fadeInUp{
|
||||||
|
animation-timing-function: cubic-bezier(0.42, 0, 0.22, 1.0)
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
<script>
|
<script>
|
||||||
import {fetchGet, fetchPost} from "../utilities/fetch.js";
|
import {fetchGet, fetchPost} from "../utilities/fetch.js";
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import Message from "@/components/messageCentreComponent/message.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "signin",
|
name: "signin",
|
||||||
|
components: {Message},
|
||||||
async setup(){
|
async setup(){
|
||||||
const store = DashboardConfigurationStore()
|
const store = DashboardConfigurationStore()
|
||||||
let theme = ""
|
let theme = ""
|
||||||
@@ -26,6 +28,11 @@ export default {
|
|||||||
loading: false
|
loading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
getMessages(){
|
||||||
|
return this.store.Messages.filter(x => x.show)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async auth(){
|
async auth(){
|
||||||
if (this.username && this.password && ((this.totpEnabled && this.totp) || !this.totpEnabled)){
|
if (this.username && this.password && ((this.totpEnabled && this.totp) || !this.totpEnabled)){
|
||||||
@@ -76,10 +83,12 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid login-container-fluid d-flex main flex-column" :data-bs-theme="this.theme">
|
<div class="container-fluid login-container-fluid d-flex main flex-column" :data-bs-theme="this.theme">
|
||||||
<div class="login-box m-auto" style="width: 500px;">
|
<div class="login-box m-auto" style="width: 600px;">
|
||||||
<h4 class="mb-0 text-body">Welcome to</h4>
|
|
||||||
<span class="dashboardLogo display-3">WGDashboard</span>
|
|
||||||
<div class="m-auto">
|
<div class="m-auto">
|
||||||
|
<div class="card px-4 py-5 rounded-4 shadow-lg">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="mb-0 text-body">Welcome to</h4>
|
||||||
|
<span class="dashboardLogo display-3"><strong>WGDashboard</strong></span>
|
||||||
<div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError">
|
<div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError">
|
||||||
{{this.loginErrorMessage}}
|
{{this.loginErrorMessage}}
|
||||||
</div>
|
</div>
|
||||||
@@ -107,7 +116,7 @@ export default {
|
|||||||
v-model="this.totp"
|
v-model="this.totp"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-lg btn-dark ms-auto mt-4 w-100 d-flex btn-brand shadow signInBtn" ref="signInBtn">
|
<button class="btn btn-lg btn-dark ms-auto mt-4 w-100 d-flex btn-brand signInBtn" ref="signInBtn">
|
||||||
<span v-if="!this.loading" class="d-flex w-100">
|
<span v-if="!this.loading" class="d-flex w-100">
|
||||||
Sign In<i class="ms-auto bi bi-chevron-right"></i>
|
Sign In<i class="ms-auto bi bi-chevron-right"></i>
|
||||||
</span>
|
</span>
|
||||||
@@ -121,10 +130,18 @@ export default {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<small class="text-muted pb-3 d-block w-100 text-center">
|
<small class="text-muted pb-3 d-block w-100 text-center">
|
||||||
WGDashboard v4.0 | Developed with ❤️ by
|
WGDashboard v4.0 | Developed with ❤️ by
|
||||||
<a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a>
|
<a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a>
|
||||||
</small>
|
</small>
|
||||||
|
<div class="messageCentre text-body position-absolute end-0 m-3">
|
||||||
|
<TransitionGroup name="message" tag="div" class="position-relative">
|
||||||
|
<Message v-for="m in getMessages.slice().reverse()"
|
||||||
|
:message="m" :key="m.id"></Message>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
/**{*/
|
||||||
|
/* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
.dp__input{
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -138,8 +146,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.nav-link:hover {
|
||||||
padding-left: 30px;
|
background-color: #e8e8e8;
|
||||||
background-color: #dfdfdf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link .feather {
|
.sidebar .nav-link .feather {
|
||||||
@@ -149,6 +156,7 @@
|
|||||||
|
|
||||||
.sidebar .nav-link.active, .bottomNavContainer .nav-link.active {
|
.sidebar .nav-link.active, .bottomNavContainer .nav-link.active {
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link:hover .feather,
|
.sidebar .nav-link:hover .feather,
|
||||||
@@ -1120,6 +1128,7 @@ pre.index-alert {
|
|||||||
background-color: #00000060;
|
background-color: #00000060;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
backdrop-filter: blur(1px);
|
backdrop-filter: blur(1px);
|
||||||
|
-webkit-backdrop-filter: blur(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboardModal{
|
.dashboardModal{
|
||||||
@@ -1153,4 +1162,6 @@ pre.index-alert {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.messageCentre{
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
+14
-5
@@ -165,6 +165,7 @@ _checkWireguard(){
|
|||||||
install_wgd(){
|
install_wgd(){
|
||||||
printf "[WGDashboard] Starting to install WGDashboard\n"
|
printf "[WGDashboard] Starting to install WGDashboard\n"
|
||||||
_checkWireguard
|
_checkWireguard
|
||||||
|
sudo chmod -R 755 /etc/wireguard/
|
||||||
|
|
||||||
if [ ! -d "log" ]
|
if [ ! -d "log" ]
|
||||||
then
|
then
|
||||||
@@ -183,7 +184,7 @@ install_wgd(){
|
|||||||
_installPythonPip
|
_installPythonPip
|
||||||
|
|
||||||
|
|
||||||
version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 7) else print("0");')
|
version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 10) else print("0");')
|
||||||
if [ $version_pass == "0" ]
|
if [ $version_pass == "0" ]
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] WGDashboard required Python 3.7 or above\n"
|
printf "[WGDashboard] WGDashboard required Python 3.7 or above\n"
|
||||||
@@ -236,10 +237,18 @@ gunicorn_start () {
|
|||||||
export PATH=$PATH:/usr/local/bin:$HOME/.local/bin
|
export PATH=$PATH:/usr/local/bin:$HOME/.local/bin
|
||||||
fi
|
fi
|
||||||
_check_and_set_venv
|
_check_and_set_venv
|
||||||
sudo "$venv_gunicorn" --access-logfile log/access_"$d".log \
|
sudo "$venv_gunicorn" --config ./gunicorn.conf.py
|
||||||
--log-level 'debug' --capture-output \
|
sleep 5
|
||||||
--error-logfile log/error_"$d".log 'dashboard:app'
|
checkPIDExist=0
|
||||||
printf "[WGDashboard] Log files is under ./log\n"
|
while [ $checkPIDExist -eq 0 ]
|
||||||
|
do
|
||||||
|
if test -f './gunicorn.pid'; then
|
||||||
|
checkPIDExist=1
|
||||||
|
printf "[WGDashboard] Checking if WGDashboard w/ Gunicorn started successfully\n"
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
printf "[WGDashboard] WGDashboard w/ Gunicorn started successfully\n"
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user