Compare commits
204 Commits
v4.0.beta3
...
v4.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b30470fc | |||
| 41d91e75fc | |||
| a97a91b844 | |||
| f1c577ab76 | |||
| 891990b2f1 | |||
| e9ab7029c9 | |||
| 6f681dba09 | |||
| b3edff947d | |||
| d35bd6e75b | |||
| f3a2f98864 | |||
| e21853286e | |||
| c012b8c4a5 | |||
| 48f6c28556 | |||
| 0c1502f801 | |||
| fec20ed381 | |||
| 252c147dcf | |||
| 453d474104 | |||
| 84cf4a9b66 | |||
| fb016bebde | |||
| 8f6a738481 | |||
| b07f958577 | |||
| 8da0fde52a | |||
| 39be16cb63 | |||
| 59d0c0def4 | |||
| 79c03db9a0 | |||
| 0c77823020 | |||
| deed7e0022 | |||
| 99db8c7335 | |||
| 9fe2aa9ed5 | |||
| 4c80dc256b | |||
| cafe9e9c11 | |||
| 27ff4e63b6 | |||
| 8020714e07 | |||
| dbd825ba4b | |||
| fee6cf29eb | |||
| 56287d8e7a | |||
| 45504eaf95 | |||
| b8a9b1150a | |||
| 8bd0e43f58 | |||
| 2c83e9e83c | |||
| 53c9ca10a7 | |||
| 75fbdac42e | |||
| 09d54546ca | |||
| b62fece3d0 | |||
| 284a2b7f64 | |||
| 9c873ccbbd | |||
| 5f72f90031 | |||
| 93cf3c69b8 | |||
| 88f856cbc7 | |||
| 2d5796d161 | |||
| acf4f3fbf0 | |||
| 8378030c70 | |||
| dc7140d486 | |||
| 3c50e4768a | |||
| 63dc9834be | |||
| f042e42633 | |||
| 39b80a2e7e | |||
| fb6e948358 | |||
| 181b0845bf | |||
| b2a82dcfe5 | |||
| ed1c05dec9 | |||
| cd73aef0c9 | |||
| ed522ec024 | |||
| 16998d1e16 | |||
| 11421d2d32 | |||
| 1af708aaea | |||
| fbb82fa759 | |||
| a9f977c3b4 | |||
| 5669e22548 | |||
| da4bb9b83d | |||
| c2cbaf0937 | |||
| d81dce536c | |||
| 8e1e2c5b85 | |||
| 959d5e6822 | |||
| 036a0b4cb3 | |||
| c038f14374 | |||
| 343cf91643 | |||
| 4fe70a4c46 | |||
| 59f7200512 | |||
| 0335e709c2 | |||
| a7bb5ac21b | |||
| 99655cab33 | |||
| 348e574154 | |||
| f35dee8643 | |||
| 168412c2e7 | |||
| b9832542fb | |||
| 316545b253 | |||
| 348a57a0d8 | |||
| a3f5654816 | |||
| 8207afe806 | |||
| e15b8f8092 | |||
| e6c99028d6 | |||
| 2e691d7c26 | |||
| 6c87812780 | |||
| 798a3632cf | |||
| aa1d0aabd2 | |||
| f348e691fa | |||
| 0dfda83e45 | |||
| 56044fa3f7 | |||
| 4c69fd4f60 | |||
| 92e3f7a6a3 | |||
| 76d0618d6e | |||
| 80c3a99eb1 | |||
| 91e3a3237b | |||
| 8c8c5a5826 | |||
| b9370955d6 | |||
| 2761e728f7 | |||
| f418bf4f63 | |||
| 3d046e4369 | |||
| b6eb6f2d70 | |||
| 45ce3e26c1 | |||
| ae91cedf76 | |||
| b2a6b484b5 | |||
| 95c4fa56cc | |||
| e8c56afa8e | |||
| ddc3b1f6c2 | |||
| d33d886574 | |||
| 0487980d2f | |||
| e68257d6c9 | |||
| ea0bbab680 | |||
| 5d4a8136c5 | |||
| c98d851cd2 | |||
| 42fa89db7a | |||
| 1a70acc6f2 | |||
| ee0a287112 | |||
| cf16a66c63 | |||
| 4544b17c94 | |||
| 8119c9da51 | |||
| baf4d75c01 | |||
| ac9eff0fcc | |||
| dc57bd92ff | |||
| 3c56a2c868 | |||
| 2d2ab10a24 | |||
| ac906c9994 | |||
| 480406d579 | |||
| 47efb644b7 | |||
| fd0e519e41 | |||
| 0c8bb990d0 | |||
| bd7139827b | |||
| 4f648aff52 | |||
| 3feb45dc01 | |||
| 0489dc7c33 | |||
| a30843cff9 | |||
| 9c6aa12f48 | |||
| cd2aa2902a | |||
| 1740ab0bbe | |||
| 9d2b5593a2 | |||
| 1a9bdc5e6d | |||
| 1f565bca10 | |||
| da089197a3 | |||
| 2df8f41d6c | |||
| 4e4f0d4c97 | |||
| eaad22c0a1 | |||
| ceb8cdd337 | |||
| 570c754eec | |||
| aedcfd1d24 | |||
| 44e738acf5 | |||
| 701b45c286 | |||
| 72fe687d82 | |||
| 632cd66b57 | |||
| 20530c000e | |||
| 8824786fb4 | |||
| bdeb4a4efe | |||
| cf455fc19b | |||
| 1bcddadb7a | |||
| 6dc28f11e0 | |||
| 8ad601fcc0 | |||
| f400844a3d | |||
| 560096878f | |||
| 0938f5ab71 | |||
| 06193d27c0 | |||
| 60e64a3646 | |||
| d60f89976e | |||
| b6e62b08e4 | |||
| effe5b32fd | |||
| 9e38137e76 | |||
| 210f5eabc9 | |||
| 5f5c91a8ff | |||
| 05c7121c8a | |||
| 551a8e9588 | |||
| 8fe5507ff8 | |||
| 4aed647865 | |||
| 74ab7aaa3d | |||
| dcf2055851 | |||
| 734a4b5e00 | |||
| da70fac0b6 | |||
| 3d6ecba4f5 | |||
| 86220694ce | |||
| 77f31177c8 | |||
| 33ff91aea8 | |||
| a4151800f1 | |||
| 932f24c966 | |||
| 2c9cade70a | |||
| d7d7a84bd5 | |||
| 990463fbea | |||
| 0d9a8ba6f7 | |||
| edbaf3ac82 | |||
| cd84674ae0 | |||
| f60ea43f29 | |||
| 76f547a726 | |||
| dffe5e0819 | |||
| fb9ef0c547 | |||
| d52cd2b17c | |||
| cefa80f317 |
@@ -1,3 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [donaldzou]
|
github: [donaldzou]
|
||||||
|
patreon: DonaldDonnyZou
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ main ]
|
||||||
|
schedule:
|
||||||
|
- cron: '30 5 * * 4'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'javascript', 'python' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||||
|
# Learn more about CodeQL language support at https://git.io/codeql-language-support
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
name: Docker Image Build and Analysis
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *" # Schedule the workflow to run daily at midnight (UTC time). Adjust the time if needed.
|
||||||
|
workflow_dispatch: # Manual run trigger
|
||||||
|
inputs:
|
||||||
|
trigger-build:
|
||||||
|
description: 'Trigger a manual build and push'
|
||||||
|
default: 'true'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-analyze:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
id: build-image
|
||||||
|
run: |
|
||||||
|
echo "Building Docker image..."
|
||||||
|
docker build -t my-app-image:latest .
|
||||||
|
echo "Docker image built successfully."
|
||||||
|
|
||||||
|
- name: Install Docker Scout
|
||||||
|
run: |
|
||||||
|
echo "Installing Docker Scout..."
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/docker/scout-cli/main/install.sh | sh -s --
|
||||||
|
echo "Docker Scout installed successfully."
|
||||||
|
|
||||||
|
- name: Analyze Docker image with Docker Scout
|
||||||
|
id: analyze-image
|
||||||
|
run: |
|
||||||
|
echo "Analyzing Docker image with Docker Scout..."
|
||||||
|
docker scout cves my-app-image:latest > scout-results.txt
|
||||||
|
cat scout-results.txt # Print the report to the workflow logs for easy viewing
|
||||||
|
echo "Docker Scout analysis completed."
|
||||||
|
|
||||||
|
- name: Post Comment on Issue or PR
|
||||||
|
run: |
|
||||||
|
COMMENT="**Docker Image Build and Analysis Report**\n\nThe Docker image was built and analyzed successfully.\n\n**Build Summary:**\n- Image Tag: my-app-image:latest\n\n**Analysis Report:**\n\`\`\`\n$(cat scout-results.txt)\n\`\`\`"
|
||||||
|
|
||||||
|
# Post comment using GitHub API
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||||
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
|
-d "{\"body\": \"$COMMENT\"}" \
|
||||||
|
"https://api.github.com/repos/NOXCIS/WGDashboard/issues/1/comments" # Replace '1' with the issue or PR number
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
FROM ubuntu:20.04
|
# Pull from small Debian stable image.
|
||||||
|
FROM alpine:latest AS builder
|
||||||
|
|
||||||
ARG WG_ADDRESS=$WG_ADDRESS
|
LABEL maintainer="dselen@nerthus.nl"
|
||||||
|
|
||||||
RUN apt-get update && \
|
WORKDIR /opt/wireguarddashboard/src
|
||||||
apt-get install -y --no-install-recommends iproute2 wireguard-tools iptables nano net-tools python3 python3-pip python3-venv procps openresolv inotify-tools && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
RUN mkdir -p /etc/wireguard/
|
RUN apk update && \
|
||||||
RUN mkdir -p /opt/wgdashboard
|
apk add --no-cache sudo gcc musl-dev rust cargo linux-headers
|
||||||
RUN mkdir -p /opt/wgdashboard_tmp
|
|
||||||
# configure wireguard
|
|
||||||
RUN wg genkey | tee /opt/wgdashboard_tmp/privatekey | wg pubkey | tee /opt/wgdashboard_tmp/publickey
|
|
||||||
|
|
||||||
RUN cd / && echo "[Interface]" > wg0.conf && echo "SaveConfig = true" >> wg0.conf && echo -n "PrivateKey = " >> wg0.conf && cat /opt/wgdashboard_tmp/privatekey >> wg0.conf \
|
COPY ./docker/alpine/builder.sh /opt/wireguarddashboard/src/
|
||||||
&& echo "ListenPort = 51820" >> wg0.conf && echo "Address = ${WG_ADDRESS}" >> wg0.conf && chmod 700 wg0.conf
|
COPY ./docker/alpine/requirements.txt /opt/wireguarddashboard/src/
|
||||||
|
RUN chmod u+x /opt/wireguarddashboard/src/builder.sh
|
||||||
|
RUN /opt/wireguarddashboard/src/builder.sh
|
||||||
|
|
||||||
COPY ./src /opt/wgdashboard_tmp
|
|
||||||
RUN pip3 install -r /opt/wgdashboard_tmp/requirements.txt --no-cache-dir
|
|
||||||
RUN rm -rf /opt/wgdashboard_tmp
|
|
||||||
COPY ./entrypoint.sh /entrypoint.sh
|
|
||||||
RUN chmod u+x /entrypoint.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
FROM alpine:latest
|
||||||
WORKDIR /opt/wgdashboard
|
WORKDIR /opt/wireguarddashboard/src
|
||||||
|
|
||||||
EXPOSE 10086
|
COPY ./src /opt/wireguarddashboard/src/
|
||||||
EXPOSE 51820/udp
|
COPY --from=builder /opt/wireguarddashboard/src/venv /opt/wireguarddashboard/src/venv
|
||||||
|
COPY --from=builder /opt/wireguarddashboard/src/log /opt/wireguarddashboard/src/log/
|
||||||
|
|
||||||
|
RUN apk update && \
|
||||||
|
apk add --no-cache wireguard-tools sudo && \
|
||||||
|
apk add --no-cache iptables ip6tables && \
|
||||||
|
chmod u+x /opt/wireguarddashboard/src/entrypoint.sh
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD curl -f http://localhost:10086/signin || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["/opt/wireguarddashboard/src/entrypoint.sh"]
|
||||||
@@ -1,65 +1,108 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="WGDashboard" src="img/logo.png" width="128">
|
<img alt="WGDashboard" src="./src/static/img/logo.png" width="128">
|
||||||
</p>
|
</p>
|
||||||
<h1 align="center">WGDashboard</h1>
|
<h1 align="center">WGDashboard</h1>
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="http://ForTheBadge.com/images/badges/made-with-python.svg">
|
<img src="https://forthebadge.com/images/badges/made-with-python.svg">
|
||||||
|
<img src="https://forthebadge.com/images/badges/made-with-javascript.svg">
|
||||||
|
<img src="https://forthebadge.com/images/badges/license-mit.svg">
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://forthebadge.com/images/badges/built-with-love.svg">
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/donaldzou/wireguard-dashboard/releases/latest"><img src="https://img.shields.io/github/v/release/donaldzou/wireguard-dashboard"></a>
|
<a href="https://github.com/donaldzou/wireguard-dashboard/releases/latest"><img src="https://img.shields.io/github/v/release/donaldzou/wireguard-dashboard"></a>
|
||||||
<a href="https://wakatime.com/badge/user/45f53c7c-9da9-4cb0-85d6-17bd38cc748b/project/5334ae20-e9a6-4c55-9fea-52d4eb9dfba6"><img src="https://wakatime.com/badge/user/45f53c7c-9da9-4cb0-85d6-17bd38cc748b/project/5334ae20-e9a6-4c55-9fea-52d4eb9dfba6.svg" alt="wakatime"></a>
|
<a href="https://wakatime.com/badge/github/donaldzou/WGDashboard"><img src="https://wakatime.com/badge/github/donaldzou/WGDashboard.svg" alt="wakatime"></a>
|
||||||
<a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fdonaldzou%2FWGDashboard&count_bg=%2379C83D&title_bg=%23555555&icon=github.svg&icon_color=%23E7E7E7&title=Visitor&edge_flat=false"/></a>
|
<a href="https://hits.seeyoufarm.com"><img src="https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fdonaldzou%2FWGDashboard&count_bg=%2379C83D&title_bg=%23555555&icon=github.svg&icon_color=%23E7E7E7&title=Visitor&edge_flat=false"/></a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">Monitoring WireGuard is not convenient, need to login into server and type <code>wg show</code>. That's why this project is being created, to view all configurations and manage them in a easy way.</p>
|
<p align="center">Monitoring WireGuard is not convenient, need to remote access to server and type <code>wg show</code>. That's why this project is being created, to view all configurations and manage them in a easy way.</p>
|
||||||
<p align="center"><small><i>This project is not affiliate to the official WireGuard Project</i></small></p>
|
<p align="center">With all these awesome features, while keeping it <b>simple</b>, <b>easy to install and use</b></p>
|
||||||
|
|
||||||
|
<p align="center"><b><i>This project is not affiliate to the official WireGuard Project</i></b></p>
|
||||||
|
|
||||||
## 📣 What's New: v4.0
|
## 📣 What's New: v4.0
|
||||||
|
|
||||||
> I can't thank enough for all of you who wait for this release, and for those who are new to this project, welcome :) Also, huge thanks who sponsored me GitHub :heart:
|
> [!TIP]
|
||||||
|
> [📹 Demo video on YouTube](https://www.youtube.com/watch?v=0mwzd5Gr2eU)
|
||||||
|
|
||||||
- 🎉 **New Features**
|
### 🎉 New Features
|
||||||
- **Updated dashboard design**: Re-designed some of the section with more modern style and layout, the UI is faster and more responsive, it also uses less memory. But overall is still the same dashboard you're familiarized.
|
|
||||||
- **Peer Job Scheduler**: Now you can schedule jobs for each peer to either **restrict** or **delete** the peer if the peer's total / upload / download data usage exceeded a limit, or you can set a specific datetime to restrict or delete the peer.
|
- **Updated dashboard design**: Re-designed some of the section with more modern style and layout, the UI is faster and more responsive, it also uses less memory. But overall is still the same dashboard you're familiarized.
|
||||||
- **API Key for WGDashboard's REST API**: You can now request all the api endpoint used in the dashboard. For more details please review the API Documentation below.
|
- **Docker Solution**: We now have 2 docker solutions! Thanks to @DaanSelen & @shuricksumy for providing them. For more information, please see the [Docker](#-docker-solutions) section below.
|
||||||
- **Logging**: Dashboard will now log all activity on the dashboard and API requests.
|
- **Peer Job Scheduler**: Now you can schedule jobs for each peer to either **restrict** or **delete** the peer if the peer's total / upload / download data usage exceeded a limit, or you can set a specific datetime to restrict or delete the peer.
|
||||||
- **Time-Based One-Time Password (TOTP)**: You can enable this function to add one more layer of security, and generate the TOTP with your choice of authenticator.
|
- **Share Peer's QR Code with Public Link**: You can share a peer's QR code and `.conf` file without the need to loging in.
|
||||||
- **Designs**
|
- **WGDashboard's REST API**: You can now request all the api endpoint used in the dashboard. For more details please review the [API Documentation](./docs/api-documents.md).
|
||||||
|
- **Logging**: Dashboard will now log all activity on the dashboard and API requests.
|
||||||
|
- **Time-Based One-Time Password (TOTP)**: You can enable this function to add one more layer of security, and generate the TOTP with your choice of authenticator.
|
||||||
|
- **Designs**
|
||||||
- **Real-time Graphs**: You can view real-time data changes with graphs in each configuration.
|
- **Real-time Graphs**: You can view real-time data changes with graphs in each configuration.
|
||||||
- **Night mode**: You know what that means, it avoids bugs ;)
|
- **Night mode**: You know what that means, it avoids bugs ;)
|
||||||
- **Enforce Python Virtual Environment**: I noticed newer Python version (3.12) does not allow to install packages globally, and plus I think is a good idea to use venv.
|
- **Enforce Python Virtual Environment**: I noticed newer Python version (3.12) does not allow to install packages globally, and plus I think is a good idea to use venv.
|
||||||
|
|
||||||
- **🧐 Other Changes**
|
### 🧐 Other Changes
|
||||||
- **Deprecated jQuery from the project, and migrated and rewrote the whole front-end with Vue.js. This allow the dashboard is future proofed, and potential cross server access with a desktop app.**
|
- **Deprecated jQuery from the project, and migrated and rewrote the whole front-end with Vue.js. This allows the dashboard is future proofed, and potential cross server access with a desktop app.**
|
||||||
- Rewrote the backend into a REST API structure
|
- Rewrote the backend into a REST API structure
|
||||||
- Improved SQL query efficient
|
- Improved SQL query efficient
|
||||||
- Removed all templates, except for `index.html` where it will load the Vue.js app.
|
- Removed all templates, except for `index.html` where it will load the Vue.js app.
|
||||||
|
- Parsing names in `.conf`
|
||||||
|
- Minimized the need to read `.conf`, only when any `.conf` is modified
|
||||||
|
|
||||||
|
### 🥘 New Experimental Features
|
||||||
|
- **Cross-Server Access**: Now you can access other servers that installed `v4` of WGDashboard through API key.
|
||||||
|
- **Desktop App**: Thanks to **Cross-Server Access**, you can now download an ElectronJS based desktop app of WGDashboard, and use that to access WGDashboard on different servers.
|
||||||
|
- > For more information, please scroll down to [🥘 Experimental Functions](#-experimental-functions)
|
||||||
|
|
||||||
|
> I can't thank enough for all of you who wait for this release, and for those who are new to this project, welcome :)
|
||||||
|
> Also, huge thanks to who contributed to this major release:
|
||||||
|
> @bolgovrussia, @eduardorosabales, @Profik, @airgapper, @tokon2000, @bkeenke, @kontorskiy777, @bugsse, @Johnnykson, @DaanSelen, @shuricksumy and many others!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**For users who is using `v2.x.x` please be sure to read [this](#please-note-for-user-who-is-using-v231-or-below) before updating WGDashboard**
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
## Table of Content
|
## 📋 Table of Content
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
- [💡 Features](#-features)
|
* [📣 What's New: v4.0](#-whats-new-v40)
|
||||||
- [📝 Requirement](#-requirement)
|
* [🎉 New Features](#-new-features)
|
||||||
- [🛠 Install](#-install)
|
* [🧐 Other Changes](#-other-changes)
|
||||||
- [🪜 Usage](#-usage)
|
* [🥘 New Experimental Features](#-new-experimental-features)
|
||||||
- [Start/Stop/Restart WGDashboard](#startstoprestart-wgdashboard)
|
* [📋 Table of Content](#-table-of-content)
|
||||||
- [Autostart WGDashboard on boot](#autostart-wgdashboard-on-boot--v22)
|
* [💡 Features](#-features)
|
||||||
- [✂️ Dashboard Configuration](#%EF%B8%8F-dashboard-configuration)
|
* [📝 Requirements](#-requirements)
|
||||||
- [Dashboard Configuration file](#dashboard-configuration-file)
|
* [Supported Operating Systems](#supported-operating-systems)
|
||||||
- [Generating QR code and peer configuration file (.conf)](#generating-qr-code-and-peer-configuration-file-conf)
|
* [Existing WireGuard Configurations](#existing-wireguard-configurations)
|
||||||
- [❓ How to update the dashboard?](#-how-to-update-the-dashboard)
|
* [🛠 Install](#-install)
|
||||||
- [🥘 Experimental Functions](#-experimental-functions)
|
* [Install Commands](#install-commands)
|
||||||
- [🔍 Screenshot](#-screenshot)
|
* [Ubuntu 20.04 LTS](#ubuntu-2004-lts)
|
||||||
- [⏰ Changelog](#--changelog)
|
* [Ubuntu 22.04 LTS & Ubuntu 24.02 LTS](#ubuntu-2204-lts--ubuntu-2402-lts)
|
||||||
- [🛒 Dependencies](#-dependencies)
|
* [Debian 12.6](#debian-126)
|
||||||
- [✨ Contributors](#-contributors)
|
* [Debian 11.10](#debian-1110)
|
||||||
|
* [Red Hat Enterprise Linux 9.4 & CentOS 9-Stream](#red-hat-enterprise-linux-94--centos-9-stream)
|
||||||
|
* [Fedora 40 & Fedora 39 & Fedora 38](#fedora-40--fedora-39--fedora-38)
|
||||||
|
* [Alpine Linux 3.20.2](#alpine-linux-3202)
|
||||||
|
* [Manual Installation](#manual-installation)
|
||||||
|
* [🪜 Usage](#-usage)
|
||||||
|
* [Start/Stop/Restart WGDashboard](#startstoprestart-wgdashboard)
|
||||||
|
* [Autostart WGDashboard on boot (>= v2.2)](#autostart-wgdashboard-on-boot--v22)
|
||||||
|
* [✂️ Dashboard Configuration](#-dashboard-configuration)
|
||||||
|
* [Dashboard Configuration file](#dashboard-configuration-file)
|
||||||
|
* [Generating QR code and peer configuration file (.conf)](#generating-qr-code-and-peer-configuration-file-conf)
|
||||||
|
* [❓ How to update the dashboard?](#-how-to-update-the-dashboard)
|
||||||
|
* [**Please note for users who are using `v3 - v3.0.6` want to update to `v4.0`**](#please-note-for-users-who-are-using-v3---v306-want-to-update-to-v40)
|
||||||
|
* [**Please note for users who are using `v2.3.1` or below**](#please-note-for-users-who-are-using-v231-or-below)
|
||||||
|
* [🐬 Docker Solutions](#-docker-solutions)
|
||||||
|
* [Solution 1 from @DaanSelen](#solution-1-from-daanselen)
|
||||||
|
* [Solution 2 from @shuricksumy](#solution-2-from-shuricksumy)
|
||||||
|
* [📖 WGDashboard REST API Documentation & How to use API Key](#-wgdashboard-rest-api-documentation--how-to-use-api-key)
|
||||||
|
* [🥘 Experimental Features](#-experimental-features)
|
||||||
|
* [Cross-Server Access](#cross-server-access)
|
||||||
|
* [Desktop App](#desktop-app)
|
||||||
|
* [🔍 Screenshot](#-screenshot)
|
||||||
|
* [🕰️ Changelogs](#-changelogs)
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
## 💡 Features
|
## 💡 Features
|
||||||
|
|
||||||
@@ -70,49 +113,178 @@
|
|||||||
- Edit peer information
|
- Edit peer information
|
||||||
- Delete peers with ease
|
- Delete peers with ease
|
||||||
- Restrict peers
|
- Restrict peers
|
||||||
- Generate QR Code and `.conf` file for peers
|
- Generate QR Code and `.conf` file for peers, share it through a public link
|
||||||
- Schedule jobs to delete / restrict peer when conditions are met
|
- Schedule jobs to delete / restrict peer when conditions are met
|
||||||
- View real time peer status
|
- View real time peer status
|
||||||
- Testing tool: Ping and Traceroute to your peer
|
- Testing tool: Ping and Traceroute to your peer
|
||||||
|
|
||||||
|
|
||||||
## 📝 Requirement
|
## 📝 Requirements
|
||||||
|
|
||||||
- Recommend the following OS, tested by our beloved users:
|
1. Supported operating systems. Please view the list below.
|
||||||
- [x] Ubuntu 18.04.1 LTS, 20.04.1 LTS, 22.04.4 LTS [@Me]
|
2. WireGuard & WireGuard-Tools (`wg-quick`)
|
||||||
- [x] Debian GNU/Linux 10 (buster) [❤️ @[robchez](https://github.com/robchez)]
|
3. Python 3.10 / 3.11 / 3.12
|
||||||
- [x] AlmaLinux 8.4 (Electric Cheetah) [❤️ @[barry-smithjr](https://github.com/)]
|
4. `git`, `net-tools`, `sudo` (_This should only apply to RHEL 9 & 8, interestingly it doesn't have it preinstalled)_
|
||||||
- [x] CentOS 7 [❤️ @[PrzemekSkw](https://github.com/PrzemekSkw)]
|
|
||||||
|
|
||||||
> **If you have tested on other OS and it works perfectly please provide it to me in [#31](https://github.com/donaldzou/wireguard-dashboard/issues/31). Thank you!**
|
### Supported Operating Systems
|
||||||
|
> [!NOTE]
|
||||||
|
> All operating systems below are tested by myself. All are ARM64 ran in UTM Virtual Machine.
|
||||||
|
|
||||||
- **WireGuard** and **WireGuard-Tools (`wg-quick`)** are installed.
|
| Ubuntu | Debian | Red Hat Enterprise Linux | CentOS | Fedora | Alpine Linux |
|
||||||
|
|-----------|--------|--------------------------|----------|--------|------------------------|
|
||||||
|
| 20.04 LTS | 12.6 | 9.4 | 9-Stream | 40 | 3.20.2 (Under Testing) |
|
||||||
|
| 22.04 LTS | 11.10 | | | 39 | |
|
||||||
|
| 24.02 LTS | | | | 38 | |
|
||||||
|
|
||||||
> Don't know how? Check this <a href="https://www.wireguard.com/install/">official documentation</a>
|
> [!TIP]
|
||||||
|
> If you installed WGDashboard on other systems without any issues, please let me know. Thank you!
|
||||||
|
|
||||||
- Configuration files under **`/etc/wireguard`**, but please note the following sample
|
### Existing WireGuard Configurations
|
||||||
|
|
||||||
```ini
|
> [!NOTE]
|
||||||
[Interface]
|
> This only applies to existing WireGuard Configuration under `/etc/wireguard`
|
||||||
...
|
|
||||||
SaveConfig = true
|
|
||||||
# Need to include this line to allow WireGuard Tool to save your configuration,
|
|
||||||
# or if you just want it to monitor your WireGuard Interface and don't need to
|
|
||||||
# make any changes with the dashboard, you can set it to false.
|
|
||||||
|
|
||||||
[Peer]
|
```ini
|
||||||
PublicKey = abcd1234
|
[Interface]
|
||||||
AllowedIPs = 1.2.3.4/32
|
...
|
||||||
# Must have for each peer
|
SaveConfig = true
|
||||||
```
|
# Need to include this line to allow WireGuard Tool to save your configuration,
|
||||||
|
# or if you just want it to monitor your WireGuard Interface and don't need to
|
||||||
|
# make any changes with the dashboard, you can set it to false.
|
||||||
|
|
||||||
- **Python 3.10** for v4.0+, **Python 3.7 - 3.9** for v2.0 - v3.0.6.2
|
[Peer]
|
||||||
|
#Name# = Donald's iPhone
|
||||||
|
PublicKey = abcd1234
|
||||||
|
AllowedIPs = 1.2.3.4/32
|
||||||
|
```
|
||||||
|
> [!TIP]
|
||||||
|
> With `v4`, WGDashboard will look for entry with `#Name# = abc...` in each peer and use that for the name.
|
||||||
|
|
||||||
## 🛠 Install
|
## 🛠 Install
|
||||||
|
|
||||||
|
### Install Commands
|
||||||
|
|
||||||
|
These commands are tested by myself in each OS. It contains commands to install WireGuard, Git, Net Tools, and even Python on some OS.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Please make sure you understand these commands before you run them.
|
||||||
|
|
||||||
|
#### Ubuntu 20.04 LTS
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo add-apt-repository ppa:deadsnakes/ppa -y && \
|
||||||
|
sudo apt-get update -y && \
|
||||||
|
sudo apt-get install python3.10 python3.10-distutils wireguard-tools net-tools --no-install-recommends -y && \
|
||||||
|
git clone https://github.com/donaldzou/WGDashboard.git && \
|
||||||
|
cd WGDashboard/src && \
|
||||||
|
chmod +x ./wgd.sh && \
|
||||||
|
./wgd.sh install && \
|
||||||
|
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
|
||||||
|
sudo sysctl -p
|
||||||
|
```
|
||||||
|
#### Ubuntu 22.04 LTS & Ubuntu 24.02 LTS
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo apt-get update -y && \
|
||||||
|
sudo apt install wireguard-tools net-tools --no-install-recommends -y && \
|
||||||
|
git clone https://github.com/donaldzou/WGDashboard.git && \
|
||||||
|
cd ./WGDashboard/src && \
|
||||||
|
chmod +x ./wgd.sh && \
|
||||||
|
./wgd.sh install && \
|
||||||
|
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
|
||||||
|
sudo sysctl -p /etc/sysctl.conf
|
||||||
|
```
|
||||||
|
#### Debian 12.6
|
||||||
|
|
||||||
|
```shell
|
||||||
|
apt-get install sudo git iptables -y && \
|
||||||
|
sudo apt-get update && \
|
||||||
|
sudo apt install wireguard-tools net-tools && \
|
||||||
|
git clone https://github.com/donaldzou/WGDashboard.git && \
|
||||||
|
cd ./WGDashboard/src && \
|
||||||
|
chmod +x ./wgd.sh && \
|
||||||
|
./wgd.sh install && \
|
||||||
|
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
|
||||||
|
sudo sysctl -p /etc/sysctl.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Debian 11.10
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> This commands will download Python 3.10's source code and build from it, since Debian 11.10 doesn't comes with Python 3.10
|
||||||
|
|
||||||
|
```shell
|
||||||
|
apt-get install sudo -y && \
|
||||||
|
sudo apt-get update && \
|
||||||
|
sudo apt install -y git iptables build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libreadline-dev libffi-dev libsqlite3-dev wget libbz2-dev wireguard-tools net-tools && \
|
||||||
|
wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0.tgz && \
|
||||||
|
tar -xvf Python-3.10.0.tgz && \
|
||||||
|
cd Python-3.10.0 && \
|
||||||
|
sudo ./configure --enable-optimizations && \
|
||||||
|
sudo make && \
|
||||||
|
sudo make altinstall && \
|
||||||
|
cd .. && \
|
||||||
|
git clone https://github.com/donaldzou/WGDashboard.git && \
|
||||||
|
cd ./WGDashboard/src && \
|
||||||
|
chmod +x ./wgd.sh && \
|
||||||
|
./wgd.sh install && \
|
||||||
|
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
|
||||||
|
sudo sysctl -p /etc/sysctl.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Red Hat Enterprise Linux 9.4 & CentOS 9-Stream
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo yum install wireguard-tools net-tools git python3.11 -y && \
|
||||||
|
git clone https://github.com/donaldzou/WGDashboard.git && \
|
||||||
|
cd ./WGDashboard/src && \
|
||||||
|
chmod +x ./wgd.sh && \
|
||||||
|
./wgd.sh install && \
|
||||||
|
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
|
||||||
|
sudo sysctl -p /etc/sysctl.conf && \
|
||||||
|
firewall-cmd --add-port=10086/tcp --permanent && \
|
||||||
|
firewall-cmd --add-port=51820/udp --permanent && \
|
||||||
|
firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fedora 40 & Fedora 39 & Fedora 38
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo yum install wireguard-tools net-tools git -y && \
|
||||||
|
git clone https://github.com/donaldzou/WGDashboard.git && \
|
||||||
|
cd ./WGDashboard/src && \
|
||||||
|
chmod +x ./wgd.sh && \
|
||||||
|
./wgd.sh install && \
|
||||||
|
sudo echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf && \
|
||||||
|
sudo sysctl -p /etc/sysctl.conf && \
|
||||||
|
firewall-cmd --add-port=10086/tcp --permanent && \
|
||||||
|
firewall-cmd --add-port=51820/udp --permanent && \
|
||||||
|
firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Alpine Linux 3.20.2
|
||||||
|
|
||||||
|
```shell
|
||||||
|
setup-interfaces -a ; \
|
||||||
|
rc-service networking --quiet start ; \
|
||||||
|
printf "https://mirrors.aliyun.com/alpine/latest-stable/main\nhttps://mirrors.aliyun.com/alpine/latest-stable/community" > /etc/apk/repositories ; \
|
||||||
|
apk update ; \
|
||||||
|
apk add wireguard-tools python3 python3-dev git iptables net-tools gcc musl-dev linux-headers sudo ; \
|
||||||
|
git clone -b v4.0-alpine-linux https://github.com/donaldzou/WGDashboard.git ; \
|
||||||
|
cd ./WGDashboard/src ; \
|
||||||
|
chmod +x ./wgd.sh ; \
|
||||||
|
./wgd.sh install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To ensure a smooth installation process, please make sure Python 3.10/3.11/3.12, `git`, `wireguard-tools` and `net-tools` are installed :)
|
||||||
|
|
||||||
1. Download WGDashboard
|
1. Download WGDashboard
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
git clone -b v4.0 https://github.com/donaldzou/WGDashboard.git wgdashboard
|
git clone https://github.com/donaldzou/WGDashboard.git wgdashboard
|
||||||
|
|
||||||
2. Open the WGDashboard folder
|
2. Open the WGDashboard folder
|
||||||
|
|
||||||
@@ -123,7 +295,7 @@
|
|||||||
3. Install WGDashboard
|
3. Install WGDashboard
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo chmod u+x wgd.sh
|
sudo chmod u+x wgd.sh && \
|
||||||
sudo ./wgd.sh install
|
sudo ./wgd.sh install
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -139,14 +311,12 @@
|
|||||||
sudo ./wgd.sh start
|
sudo ./wgd.sh start
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**:
|
|
||||||
|
|
||||||
> For [`pivpn`](https://github.com/pivpn/pivpn) user, please use `sudo ./wgd.sh start` to run if your current account does not have the permission to run `wg show` and `wg-quick`.
|
|
||||||
|
|
||||||
6. Access dashboard
|
6. Access dashboard
|
||||||
|
|
||||||
Access your server with port `10086` (e.g. http://your_server_ip:10086), using username `admin` and password `admin`. See below how to change port and ip that the dashboard is running with.
|
Access your server with port `10086` (e.g. http://your_server_ip:10086), using username `admin` and password `admin`. See below how to change port and ip that the dashboard is running with.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 🪜 Usage
|
## 🪜 Usage
|
||||||
|
|
||||||
#### Start/Stop/Restart WGDashboard
|
#### Start/Stop/Restart WGDashboard
|
||||||
@@ -193,40 +363,33 @@ In the `src` folder, it contained a file called `wg-dashboard.service`, we can u
|
|||||||
|
|
||||||
```ini
|
```ini
|
||||||
[Unit]
|
[Unit]
|
||||||
After=network.service
|
After=syslog.target network-online.target
|
||||||
|
Wants=wg-quick.target
|
||||||
|
ConditionPathIsDirectory=/etc/wireguard
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
WorkingDirectory=<your dashboard directory full path here>
|
Type=forking
|
||||||
ExecStart=/usr/bin/python3 <your dashboard directory full path here>/dashboard.py
|
PIDFile=<absolute_path_of_wgdashboard_src>/gunicorn.pid
|
||||||
|
WorkingDirectory=<absolute_path_of_wgdashboard_src>
|
||||||
|
ExecStart=<absolute_path_of_wgdashboard_src>/wgd.sh start
|
||||||
|
ExecStop=<absolute_path_of_wgdashboard_src>/wgd.sh stop
|
||||||
|
ExecReload=<absolute_path_of_wgdashboard_src>/wgd.sh restart
|
||||||
|
TimeoutSec=120
|
||||||
|
PrivateTmp=yes
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=default.target
|
WantedBy=multi-user.target
|
||||||
```
|
```
|
||||||
|
|
||||||
Now, we need to replace both `<your dashboard directory full path here>` to the one you just copied from step 2. After doing this, the file will become something like this, your file might be different:
|
Now, we need to replace all `<absolute_path_of_wgdashboard_src>` to the one you just copied from step 2. After doing this, the file will become something like this, your file might be different:
|
||||||
|
|
||||||
```ini
|
|
||||||
[Unit]
|
|
||||||
After=netword.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
WorkingDirectory=/root/wgdashboard/src
|
|
||||||
ExecStart=/usr/bin/python3 /root/wgdashboard/src/dashboard.py
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
```
|
|
||||||
|
|
||||||
**Be aware that after the value of `WorkingDirectory`, it does not have a `/` (slash).** And then save the file after you edited it
|
**Be aware that after the value of `WorkingDirectory`, it does not have a `/` (slash).** And then save the file after you edited it
|
||||||
|
|
||||||
4. Copy the service file to systemd folder
|
4. Copy the service file to systemd folder
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ cp wg-dashboard.service /etc/systemd/system/wg-dashboard.service
|
$ sudo cp wg-dashboard.service /etc/systemd/system/wg-dashboard.service
|
||||||
```
|
```
|
||||||
|
|
||||||
To make sure you copy the file successfully, you can use this command `cat /etc/systemd/system/wg-dashboard.service` to see if it will output the file you just edited.
|
To make sure you copy the file successfully, you can use this command `cat /etc/systemd/system/wg-dashboard.service` to see if it will output the file you just edited.
|
||||||
@@ -245,28 +408,31 @@ In the `src` folder, it contained a file called `wg-dashboard.service`, we can u
|
|||||||
```bash
|
```bash
|
||||||
$ sudo systemctl status wg-dashboard.service
|
$ sudo systemctl status wg-dashboard.service
|
||||||
```
|
```
|
||||||
|
|
||||||
And you should see something like this
|
And you should see something like this
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
● wg-dashboard.service
|
● wg-dashboard.service
|
||||||
Loaded: loaded (/etc/systemd/system/wg-dashboard.service; enabled; vendor preset: enabled)
|
Loaded: loaded (/etc/systemd/system/wg-dashboard.service; enabled; vendor preset: enabled)
|
||||||
Active: active (running) since Tue 2021-08-03 22:31:26 UTC; 4s ago
|
Active: active (running) since Wed 2024-08-14 22:21:47 EDT; 55s ago
|
||||||
Main PID: 6602 (python3)
|
Process: 494968 ExecStart=/home/donaldzou/Wireguard-Dashboard/src/wgd.sh start (code=exited, status=0/SUCCESS)
|
||||||
Tasks: 1 (limit: 453)
|
Main PID: 495005 (gunicorn)
|
||||||
Memory: 26.1M
|
Tasks: 5 (limit: 4523)
|
||||||
|
Memory: 36.8M
|
||||||
|
CPU: 789ms
|
||||||
CGroup: /system.slice/wg-dashboard.service
|
CGroup: /system.slice/wg-dashboard.service
|
||||||
└─6602 /usr/bin/python3 /root/wgdashboard/src/dashboard.py
|
├─495005 /home/donaldzou/Wireguard-Dashboard/src/venv/bin/python3 ./venv/bin/gunicorn --config ./gunicorn.conf.py
|
||||||
|
└─495007 /home/donaldzou/Wireguard-Dashboard/src/venv/bin/python3 ./venv/bin/gunicorn --config ./gunicorn.conf.py
|
||||||
|
|
||||||
Aug 03 22:31:26 ubuntu-wg systemd[1]: Started wg-dashboard.service.
|
Aug 14 22:21:40 wg sudo[494978]: root : PWD=/home/donaldzou/Wireguard-Dashboard/src ; USER=root ; COMMAND=./venv/bin/gunicorn --config ./gunicorn.conf.py
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: * Serving Flask app1 "WGDashboard" (lazy loading)
|
Aug 14 22:21:40 wg sudo[494978]: pam_unix(sudo:session): session opened for user root(uid=0) by (uid=0)
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: * Environment: production
|
Aug 14 22:21:40 wg wgd.sh[494979]: [WGDashboard] WGDashboard w/ Gunicorn will be running on 0.0.0.0:10086
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: WARNING: This is a development server. Do not use it in a production deployment.
|
Aug 14 22:21:40 wg wgd.sh[494979]: [WGDashboard] Access log file is at ./log/access_2024_08_14_22_21_40.log
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: Use a production WSGI server instead.
|
Aug 14 22:21:40 wg wgd.sh[494979]: [WGDashboard] Error log file is at ./log/error_2024_08_14_22_21_40.log
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: * Debug mode: off
|
Aug 14 22:21:40 wg sudo[494978]: pam_unix(sudo:session): session closed for user root
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: * Running on all addresses.
|
Aug 14 22:21:45 wg wgd.sh[494968]: [WGDashboard] Checking if WGDashboard w/ Gunicorn started successfully
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: WARNING: This is a development server. Do not use it in a production deployment.
|
Aug 14 22:21:47 wg wgd.sh[494968]: [WGDashboard] WGDashboard w/ Gunicorn started successfully
|
||||||
Aug 03 22:31:27 ubuntu-wg python3[6602]: * Running on http://0.0.0.0:10086/ (Press CTRL+C to quit)
|
Aug 14 22:21:47 wg wgd.sh[494968]: ------------------------------------------------------------
|
||||||
|
Aug 14 22:21:47 wg systemd[1]: Started wg-dashboard.service.
|
||||||
```
|
```
|
||||||
|
|
||||||
If you see `Active:` followed by `active (running) since...` then it means it run correctly.
|
If you see `Active:` followed by `active (running) since...` then it means it run correctly.
|
||||||
@@ -288,7 +454,7 @@ In the `src` folder, it contained a file called `wg-dashboard.service`, we can u
|
|||||||
Since version 2.0, WGDashboard will be using a configuration file called `wg-dashboard.ini`, (It will generate automatically after first time running the dashboard). More options will include in future versions, and for now it included the following configurations:
|
Since version 2.0, WGDashboard will be using a configuration file called `wg-dashboard.ini`, (It will generate automatically after first time running the dashboard). More options will include in future versions, and for now it included the following configurations:
|
||||||
|
|
||||||
| | Description | Default | Edit Available |
|
| | Description | Default | Edit Available |
|
||||||
| ---------------------------- | ------------------------------------------------------------ | ---------------------------------------------------- | -------------- |
|
|------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|----------------|
|
||||||
| **`[Account]`** | *Configuration on account* | | |
|
| **`[Account]`** | *Configuration on account* | | |
|
||||||
| `username` | Dashboard login username | `admin` | Yes |
|
| `username` | Dashboard login username | `admin` | Yes |
|
||||||
| `password` | Password, will be hash with SHA256 | `admin` hashed in SHA256 | Yes |
|
| `password` | Password, will be hash with SHA256 | `admin` hashed in SHA256 | Yes |
|
||||||
@@ -298,9 +464,10 @@ Since version 2.0, WGDashboard will be using a configuration file called `wg-das
|
|||||||
| `app_ip` | IP address the dashboard will run with | `0.0.0.0` | Yes |
|
| `app_ip` | IP address the dashboard will run with | `0.0.0.0` | Yes |
|
||||||
| `app_port` | Port the the dashboard will run with | `10086` | Yes |
|
| `app_port` | Port the the dashboard will run with | `10086` | Yes |
|
||||||
| `auth_req` | Does the dashboard need authentication to access, if `auth_req = false` , user will not be access the **Setting** tab due to security consideration. **User can only edit the file directly in system**. | `true` | **No** |
|
| `auth_req` | Does the dashboard need authentication to access, if `auth_req = false` , user will not be access the **Setting** tab due to security consideration. **User can only edit the file directly in system**. | `true` | **No** |
|
||||||
| `version` | Dashboard Version | `v3.0.5` | **No** |
|
| `version` | Dashboard Version | `v4.0` | **No** |
|
||||||
| `dashboard_refresh_interval` | How frequent the dashboard will refresh on the configuration page | `60000ms` | Yes |
|
| `dashboard_refresh_interval` | How frequent the dashboard will refresh on the configuration page | `60000ms` | Yes |
|
||||||
| `dashboard_sort` | How configuration is sorting | `status` | Yes |
|
| `dashboard_sort` | How configuration is sorting | `status` | Yes |
|
||||||
|
| `dashboard_theme` | Dashboard Theme | `dark` | Yes |
|
||||||
| | | | |
|
| | | | |
|
||||||
| **`[Peers]`** | *Default Settings on a new peer* | | |
|
| **`[Peers]`** | *Default Settings on a new peer* | | |
|
||||||
| `peer_global_dns` | DNS Server | `1.1.1.1` | Yes |
|
| `peer_global_dns` | DNS Server | `1.1.1.1` | Yes |
|
||||||
@@ -339,29 +506,12 @@ Endpoint = 0.0.0.0:51820
|
|||||||
|
|
||||||
## ❓ How to update the dashboard?
|
## ❓ How to update the dashboard?
|
||||||
|
|
||||||
#### **Please note for user who is using `v2.3.1` or below**
|
#### **Please note for users who are using `v3 - v3.0.6` want to update to `v4.0`**
|
||||||
|
- Although theoretically updating through `wgd.sh` should work, but I still suggest you to update the dashboard manually.
|
||||||
|
|
||||||
- For user who is using `v2.3.1` or below, please notice that all data that stored in the current database will **not** transfer to the new database. This is hard decision to move from TinyDB to SQLite. But SQLite does provide a thread-safe access and TinyDB doesn't. I couldn't find a safe way to transfer the data, so you need to do them manually... Sorry about that :pensive: . But I guess this would be a great start for future development :sunglasses:.
|
#### **Please note for users who are using `v2.3.1` or below**
|
||||||
|
|
||||||
<hr>
|
- For user who is using `v2.3.1` or below, please notice that all data that stored in the current database will **not** transfer to the new database. This is hard decision to move from TinyDB to SQLite. But SQLite does provide a thread-safe access and TinyDB doesn't. I couldn't find a safe way to transfer the data, so you need to do them manually... Sorry about that :pensive:。 But I guess this would be a great start for future development :sunglasses:.
|
||||||
|
|
||||||
#### Update Method 1 (For `v3.0` or above)
|
|
||||||
|
|
||||||
1. Change your directory to `wgdashboard/src`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd wgdashboard/src
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Update the dashboard with the following
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./wgd.sh update
|
|
||||||
```
|
|
||||||
|
|
||||||
> If this doesn't work, please use the method below. Sorry about that :(
|
|
||||||
|
|
||||||
#### Update Method 2
|
|
||||||
|
|
||||||
|
|
||||||
1. Change your directory to `wgdashboard`
|
1. Change your directory to `wgdashboard`
|
||||||
@@ -372,191 +522,65 @@ Endpoint = 0.0.0.0:51820
|
|||||||
|
|
||||||
2. Update the dashboard
|
2. Update the dashboard
|
||||||
```shell
|
```shell
|
||||||
git pull https://github.com/donaldzou/WGDashboard.git v3.0.5 --force
|
git pull https://github.com/donaldzou/WGDashboard.git --force
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Install
|
3. Install
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
./wgd.sh install
|
sudo ./wgd.sh install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Starting with `v3.0`, you can simply do `sudo ./wgd.sh update` !! (I hope)
|
||||||
|
|
||||||
|
## 🐬 Docker Solutions
|
||||||
|
|
||||||
Starting with `v3.0`, you can simply do `./wgd.sh update` !! (I hope, lol)
|
Current, we have 2 beloved contributors provided solutions for hosting WGDashboard with Docker
|
||||||
|
|
||||||
## 🥘 Experimental Functions
|
### Solution 1 from @DaanSelen
|
||||||
|
|
||||||
#### Progressive Web App (PWA) for WGDashboard
|
Please visit [Docker-explain.md](./docker/Docker-explain.md)
|
||||||
|
|
||||||
- With `v3.0`, I've added a `manifest.json` into the dashboard, so user could add their dashboard as a PWA to their browser or mobile device.
|
### Solution 2 from @shuricksumy
|
||||||
|
|
||||||
<img src="img/PWA.gif"/>
|
Please visit [shuricksumy/docker-wgdashboard](https://github.com/shuricksumy/docker-wgdashboard)
|
||||||
|
|
||||||
|
> For questions or issues related to Docker, please visit [#272](https://github.com/donaldzou/WGDashboard/issues/272)
|
||||||
|
|
||||||
|
## 📖 WGDashboard REST API Documentation & How to use API Key
|
||||||
|
|
||||||
|
Please visit the [API Documentation](./docs/api-documents.md)
|
||||||
|
|
||||||
|
## 🥘 Experimental Features
|
||||||
|
|
||||||
|
### Cross-Server Access
|
||||||
|
|
||||||
|
Starting with `v4.0`, you can access WGDashboards on other server through one WGDashboard with API Keys
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Desktop App
|
||||||
|
|
||||||
|
Since the major changes for `v4.0` is to move the whole front-end code to Vue.js. And with this change, we can take the
|
||||||
|
advantage of combining ElectronJS and Vue.js to create a Desktop version of WGDashboard. Currently, we provide an Universal macOS app and a Windows app.
|
||||||
|
|
||||||
|
To download the app, please visit the [latest release](https://github.com/donaldzou/WGDashboard/releases).
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 🔍 Screenshot
|
## 🔍 Screenshot
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||

|
## 🕰️ Changelogs
|
||||||
|
|
||||||

|
Please visit the [Changelogs.md](./docs/changelogs.md)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## ⏰ Changelog
|
|
||||||
|
|
||||||
#### v3.0.0 - v3.0.6.2 - Jan 18, 2022
|
|
||||||
|
|
||||||
- 🎉 **New Features**
|
|
||||||
- **Moved from TinyDB to SQLite**: SQLite provide a better performance and loading speed when getting peers! Also avoided crashing the database due to **race condition**.
|
|
||||||
- **Added Gunicorn WSGI Server**: This could provide more stable on handling HTTP request, and more flexibility in the future (such as HTTPS support). **BIG THANKS to @pgalonza :heart:**
|
|
||||||
- **Add Peers by Bulk:** User can add peers by bulk, just simply set the amount and click add.
|
|
||||||
- **Delete Peers by Bulk**: User can delete peers by bulk, without deleting peers one by one.
|
|
||||||
- **Download Peers in Zip**: User can download all *downloadable* peers in a zip.
|
|
||||||
- **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button.
|
|
||||||
- **Redirect Back to Previous Page:** The dashboard will now redirect you back to your previous page if the current session got timed out and you need to sign in again.
|
|
||||||
- **Added Some [🥘 Experimental Functions](#-experimental-functions)**
|
|
||||||
|
|
||||||
- 🪚 **Bug Fixed**
|
|
||||||
- [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom]
|
|
||||||
- [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ikidd]
|
|
||||||
- [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @pgalonza]
|
|
||||||
- [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian]
|
|
||||||
- **And many other bugs provided by our beloved users** :heart:
|
|
||||||
- **🧐 Other Changes**
|
|
||||||
- **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)!
|
|
||||||
- **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up).
|
|
||||||
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
|
|
||||||
- **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command.
|
|
||||||
- **Minified JS and CSS files**: Although only a small changes on the file size, but I think is still a good practice to save a bit of bandwidth ;)
|
|
||||||
|
|
||||||
*And many other small changes for performance and bug fixes! :laughing:*
|
|
||||||
|
|
||||||
#### v2.3.1 - Sep 8, 2021
|
|
||||||
|
|
||||||
- Updated dashboard's name to **WGDashboard**!!
|
|
||||||
|
|
||||||
#### v2.3 - Sep 8, 2021
|
|
||||||
|
|
||||||
- 🎉 **New Features**
|
|
||||||
- **Update directly from `wgd.sh`:** Now you can update WGDashboard directly from the bash script.
|
|
||||||
- **Displaying Peers:** You can switch the display mode between list and table in the configuration page.
|
|
||||||
- 🪚 **Bug Fixed**
|
|
||||||
- [Peer DNS Validation Fails #67](issues/67): Added DNS format check. [❤️ @realfian]
|
|
||||||
- [configparser.NoSectionError: No section: 'Interface' #66](issues/66): Changed permission requirement for `etc/wireguard` from `744` to `755`. [❤️ @ramalmaty]
|
|
||||||
- [Feature request: Interface not loading when information missing #73](issues/73): Fixed when Configuration Address and Listen Port is missing will crash the dashboard. [❤️ @js32]
|
|
||||||
- [Remote Peer, MTU and PersistentKeepalives added #70](pull/70): Added MTU, remote peer and Persistent Keepalive. [❤️ @realfian]
|
|
||||||
- [Fixes DNS check to support search domain #65](pull/65): Added allow input domain into DNS. [❤️@davejlong]
|
|
||||||
- **🧐 Other Changes**
|
|
||||||
- Moved Add Peer Button into the right bottom corner.
|
|
||||||
|
|
||||||
#### v2.2.1 - Aug 16, 2021
|
|
||||||
|
|
||||||
Bug Fixed:
|
|
||||||
- Added support for full subnet on Allowed IP
|
|
||||||
- Peer setting Save button
|
|
||||||
|
|
||||||
#### v2.2 - Aug 14, 2021
|
|
||||||
|
|
||||||
- 🎉 **New Features**
|
|
||||||
- **Add new peers**: Now you can add peers directly on dashboard, it will generate a pair of private key and public key. You can also set its DNS, endpoint allowed IPs. Both can set a default value in the setting page. [❤️ in [#44](https://github.com/donaldzou/wireguard-dashboard/issues/44)]
|
|
||||||
- **QR Code:** You can add the private key in peer setting of your existed peer to create a QR code. Or just create a new one, dashboard will now be able to auto generate a private key and public key ;) Don't worry, all keys will be generated on your machine, and **will delete all key files after they got generated**. [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
|
|
||||||
- **Peer configuration file download:** Same as QR code, you now can download the peer configuration file, so you don't need to manually input all the details on the peer machine! [❤️ in [#40](https://github.com/donaldzou/wireguard-dashboard/issues/40)]
|
|
||||||
- **Search peers**: You can now search peers by their name.
|
|
||||||
- **Autostart on boot:** Added a tutorial on how to start the dashboard to on boot! Please read the [tutorial below](#autostart-wireguard-dashboard-on-boot). [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
|
|
||||||
- **Click to copy**: You can now click and copy all peer's public key and configuration's public key.
|
|
||||||
- ....
|
|
||||||
- 🪚 **Bug Fixed**
|
|
||||||
- When there are comments in the wireguard config file, will cause the dashboard to crash.
|
|
||||||
- Used regex to search for config files.
|
|
||||||
- **🧐 Other Changes**
|
|
||||||
- Moved all external CSS and JavaScript file to local hosting (Except Bootstrap Icon, due to large amount of SVG files).
|
|
||||||
- Updated Python dependencies
|
|
||||||
- Flask: `v1.1.2 => v2.0.1`
|
|
||||||
- Jinja: `v2.10.1 => v3.0.1`
|
|
||||||
- icmplib: `v2.1.1 => v3.0.1`
|
|
||||||
- Updated CSS/JS dependencies
|
|
||||||
- Bootstrap: `v4.5.3 => v4.6.0`
|
|
||||||
- UI adjustment
|
|
||||||
- Adjusted how peers will display in larger screens, used to be 1 row per peer, now is 3 peers in 1 row.
|
|
||||||
|
|
||||||
#### v2.1 - Jul 2, 2021
|
|
||||||
|
|
||||||
- Added **Ping** and **Traceroute** tools!
|
|
||||||
- Adjusted the calculation of data usage on each peers
|
|
||||||
- Added refresh interval of the dashboard
|
|
||||||
- Bug fixed when no configuration on fresh install ([#23](https://github.com/donaldzou/wireguard-dashboard/issues/23))
|
|
||||||
- Fixed crash when too many peers ([#22](https://github.com/donaldzou/wireguard-dashboard/issues/22))
|
|
||||||
|
|
||||||
#### v2.0 - May 5, 2021
|
|
||||||
|
|
||||||
- Added login function to dashboard
|
|
||||||
- ***I'm not using the most ideal way to store the username and password, feel free to provide a better way to do this if you any good idea!***
|
|
||||||
- Added a config file to the dashboard
|
|
||||||
- Dashboard config can be change within the **Setting** tab on the side bar
|
|
||||||
- Adjusted UI
|
|
||||||
- And much more!
|
|
||||||
|
|
||||||
#### v1.1.2 - Apr 3, 2021
|
|
||||||
|
|
||||||
- Resolved issue [#3](https://github.com/donaldzou/wireguard-dashboard/issues/3).
|
|
||||||
|
|
||||||
#### v1.1.1 - Apr 2, 2021
|
|
||||||
|
|
||||||
- Able to add a friendly name to each peer. Thanks [#2](https://github.com/donaldzou/wireguard-dashboard/issues/2) !
|
|
||||||
|
|
||||||
#### v1.0 - Dec 27, 2020
|
|
||||||
|
|
||||||
- Added the function to remove peers
|
|
||||||
|
|
||||||
## 🛒 Dependencies
|
|
||||||
|
|
||||||
- CSS/JS
|
|
||||||
- [Bootstrap](https://getbootstrap.com/docs/4.6/getting-started/introduction/) `v4.6.0`
|
|
||||||
- [Bootstrap Icon](https://icons.getbootstrap.com) `v1.4.0`
|
|
||||||
- [jQuery](https://jquery.com) `v3.5.1`
|
|
||||||
- Python
|
|
||||||
- [Flask](https://pypi.org/project/Flask/) `v2.0.1`
|
|
||||||
- [ifcfg](https://pypi.org/project/ifcfg/) `v0.21`
|
|
||||||
- [icmplib](https://pypi.org/project/icmplib/) `v2.1.1`
|
|
||||||
- [flask-qrcode](https://pypi.org/project/Flask-QRcode/) `v3.0.0`
|
|
||||||
|
|
||||||
## ✨ Contributors
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
||||||
[](#contributors-)
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center"><a href="https://github.com/antonioag95"><img src="https://avatars.githubusercontent.com/u/30556866?v=4?s=100" width="100px;" alt=""/><br /><sub><b>antonioag95</b></sub></a><br /><a href="https://github.com/donaldzou/WGDashboard/commits?author=antonioag95" title="Tests">⚠️</a> <a href="https://github.com/donaldzou/WGDashboard/commits?author=antonioag95" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/tonjo"><img src="https://avatars.githubusercontent.com/u/4726289?v=4?s=100" width="100px;" alt=""/><br /><sub><b>tonjo</b></sub></a><br /><a href="https://github.com/donaldzou/WGDashboard/commits?author=tonjo" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/reafian"><img src="https://avatars.githubusercontent.com/u/11992416?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Richard Newton</b></sub></a><br /><a href="https://github.com/donaldzou/WGDashboard/commits?author=reafian" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="http://www.davejlong.com"><img src="https://avatars.githubusercontent.com/u/175317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Long</b></sub></a><br /><a href="https://github.com/donaldzou/WGDashboard/commits?author=davejlong" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="http://www.std-soft.com"><img src="https://avatars.githubusercontent.com/u/5978293?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Markus Neubauer</b></sub></a><br /><a href="https://github.com/donaldzou/WGDashboard/commits?author=marneu" title="Code">💻</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 5.1.x | :white_check_mark: |
|
||||||
|
| 5.0.x | :x: |
|
||||||
|
| 4.0.x | :white_check_mark: |
|
||||||
|
| < 4.0 | :x: |
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
wireguard-dashboard:
|
||||||
|
build: ./
|
||||||
|
container_name: wiregate
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- wg_net=10.0.0.1/24
|
||||||
|
- wg_port=51820
|
||||||
|
volumes:
|
||||||
|
- wgd_configs:/etc/wireguard
|
||||||
|
- wgd_app:/opt/wireguarddashboard/src
|
||||||
|
ports:
|
||||||
|
- 10086:10086/tcp
|
||||||
|
- 51820:51820/udp
|
||||||
|
sysctls:
|
||||||
|
- net.ipv4.ip_forward=1
|
||||||
|
- net.ipv4.conf.all.src_valid_mark=1
|
||||||
|
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
wgd_configs:
|
||||||
|
wgd_app:
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
version: "3.5"
|
|
||||||
|
|
||||||
services:
|
|
||||||
wgdashboard:
|
|
||||||
image: donaldzou/wgdashboard
|
|
||||||
build:
|
|
||||||
context: ./
|
|
||||||
dockerfile: ./Dockerfile
|
|
||||||
cap_add:
|
|
||||||
- NET_ADMIN
|
|
||||||
- SYS_MODULE
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /lib/modules:/lib/modules
|
|
||||||
- ./src:/opt/wgdashboard
|
|
||||||
- ./config:/etc/wireguard
|
|
||||||
ports:
|
|
||||||
- 10086:10086
|
|
||||||
- 51820:51820/udp
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
# 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,43 @@
|
|||||||
|
venv_python="./venv/bin/python3"
|
||||||
|
venv_gunicorn="./venv/bin/gunicorn"
|
||||||
|
pythonExecutable="python3"
|
||||||
|
|
||||||
|
|
||||||
|
_check_and_set_venv(){
|
||||||
|
VIRTUAL_ENV="./venv"
|
||||||
|
if [ ! -d $VIRTUAL_ENV ]; then
|
||||||
|
printf "[WGDashboard] Creating Python Virtual Environment under ./venv\n"
|
||||||
|
{ $pythonExecutable -m venv $VIRTUAL_ENV; } >> ./log/install.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! $venv_python --version > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] %s Python Virtual Environment under ./venv failed to create. Halting now.\n" "$heavy_crossmark"
|
||||||
|
kill $TOP_PID
|
||||||
|
fi
|
||||||
|
|
||||||
|
source ${VIRTUAL_ENV}/bin/activate
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
build_core () {
|
||||||
|
if [ ! -d "log" ]
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] Creating ./log folder\n"
|
||||||
|
mkdir "log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
apk add --no-cache python3 net-tools python3-dev py3-virtualenv
|
||||||
|
_check_and_set_venv
|
||||||
|
printf "[WGDashboard] Upgrading Python Package Manage (PIP)\n"
|
||||||
|
{ date; python3 -m pip install --upgrade pip; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
printf "[WGDashboard] Building Bcrypt & Psutil\n"
|
||||||
|
{ date; python3 -m pip install -r requirements.txt ; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
printf "[WGDashboard] Build Successfull!\n"
|
||||||
|
printf "[WGDashboard] Clean Up Pip!\n"
|
||||||
|
{ date; rm -rf /opt/wireguarddashboard/src/venv/lib/python3.12/site-packages/pip* ; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
build_core
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
bcrypt
|
||||||
|
psutil
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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:
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,8 +1,612 @@
|
|||||||
# API Documents for WGDashboard
|
# 📖 API Document for WGDashboard
|
||||||
|
|
||||||
**Version: v4.0**
|
**Version: v4.0**
|
||||||
|
|
||||||
**Created by: Donald Zou**
|
**Created by: Donald Zou**
|
||||||
|
|
||||||
|
<!-- TOC -->
|
||||||
|
* [📖 API Document for WGDashboard](#-api-document-for-wgdashboard)
|
||||||
|
* [🔑 How to use API Key?](#-how-to-use-api-key)
|
||||||
|
* [Create API Key](#create-api-key)
|
||||||
|
* [Use API Key](#use-api-key)
|
||||||
|
* [API Endpoints](#api-endpoints)
|
||||||
|
* [Handshake to Server](#handshake-to-server)
|
||||||
|
* [Request](#request)
|
||||||
|
* [Response](#response)
|
||||||
|
* [Validate Authentication](#validate-authentication)
|
||||||
|
* [Request](#request-1)
|
||||||
|
* [Response](#response-1)
|
||||||
|
* [Authenticate](#authenticate)
|
||||||
|
* [Request](#request-2)
|
||||||
|
* [Body Parameters](#body-parameters)
|
||||||
|
* [Response](#response-2)
|
||||||
|
* [Sign Out](#sign-out)
|
||||||
|
* [Request](#request-3)
|
||||||
|
* [Response](#response-3)
|
||||||
|
* [Get WireGuard Configurations](#get-wireguard-configurations)
|
||||||
|
* [Request](#request-4)
|
||||||
|
* [Response](#response-4)
|
||||||
|
* [Add WireGuard Configuration](#add-wireguard-configuration)
|
||||||
|
* [Request](#request-5)
|
||||||
|
* [Body Parameters](#body-parameters-1)
|
||||||
|
* [Response](#response-5)
|
||||||
|
* [Toggle WireGuard Configuration](#toggle-wireguard-configuration)
|
||||||
|
* [Request](#request-6)
|
||||||
|
* [Query String Parameter](#query-string-parameter)
|
||||||
|
* [Response](#response-6)
|
||||||
|
* [Get WGDashboard Configuration](#get-wgdashboard-configuration)
|
||||||
|
* [Request](#request-7)
|
||||||
|
* [Response](#response-7)
|
||||||
|
* [Update WGDashboard Configuration Item](#update-wgdashboard-configuration-item)
|
||||||
|
* [Request](#request-8)
|
||||||
|
* [Body Parameters](#body-parameters-2)
|
||||||
|
* [Response](#response-8)
|
||||||
|
* [Get WGDashboard API Keys](#get-wgdashboard-api-keys)
|
||||||
|
* [Request](#request-9)
|
||||||
|
* [Response](#response-9)
|
||||||
|
* [Add WGDashboard API Key](#add-wgdashboard-api-key)
|
||||||
|
* [Request](#request-10)
|
||||||
|
* [Body Parameters](#body-parameters-3)
|
||||||
|
* [Response](#response-10)
|
||||||
|
* [Endpoint](#endpoint)
|
||||||
|
* [Request](#request-11)
|
||||||
|
* [Response](#response-11)
|
||||||
|
<!-- TOC -->
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
## 🔑 How to use API Key?
|
||||||
|
|
||||||
|
### Create API Key
|
||||||
|
|
||||||
|
1. To request an API Key, simply login to your WGDashboard, go to **Settings**, scroll to the very bottom. Click the **switch** on the right to enable API Key.
|
||||||
|
2. Click the blur **Create** button, set an **expiry date** you want or **never expire**, then click **Done**.
|
||||||
|
|
||||||
|
### Use API Key
|
||||||
|
|
||||||
|
- Simply add `wg-dashboard-apikey` with the value of your API key into the HTTP Header.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
fetch('http://server:10086/api/handshake', {
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'wg-dashboard-apikey': 'insert your api key here'
|
||||||
|
},
|
||||||
|
method: "GET"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Handshake to Server
|
||||||
|
|
||||||
|
This endpoint is designed for a simple handshake when using API key to connect. If `status` is `true` that means
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/handshake`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`401 - UNAUTHORIZED`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Unauthorized access.",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
> Notice: this `401` response will return at all endpoint if your API Key or session is invalid.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Validate Authentication
|
||||||
|
|
||||||
|
This endpoint if needed for non-cross-server access. This will check if the cookie on the client side is still valid on the server side.
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/validateAuthentication`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
Session is still valid
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Session is invalid
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Invalid authentication.",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Authenticate
|
||||||
|
|
||||||
|
This endpoint is dedicated for non-cross-server access. It is used to authenticate user's username, password and TOTP
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`POST /api/authenticate`
|
||||||
|
|
||||||
|
##### Body Parameters
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"username": "admin",
|
||||||
|
"password": "admin",
|
||||||
|
"totp": "123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type |
|
||||||
|
|------------|--------|
|
||||||
|
| `username` | string |
|
||||||
|
| `password` | string |
|
||||||
|
| `totp` | string |
|
||||||
|
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
If username, password and TOTP matched
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If username, password or TOTP is not match
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Sorry, your username, password or OTP is incorrect.",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Sign Out
|
||||||
|
|
||||||
|
To remove the current session on server side
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/signout`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Get WireGuard Configurations
|
||||||
|
|
||||||
|
To get all WireGuard configurations in `/etc/wireguard`
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/getWireguardConfigurations`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"Address": "10.200.200.1/24",
|
||||||
|
"ConnectedPeers": 0,
|
||||||
|
"DataUsage": {
|
||||||
|
"Receive": 0.1582,
|
||||||
|
"Sent": 2.1012999999999997,
|
||||||
|
"Total": 2.2595
|
||||||
|
},
|
||||||
|
"ListenPort": "51820",
|
||||||
|
"Name": "wg0",
|
||||||
|
"PostDown": "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||||
|
"PostUp": "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||||
|
"PreDown": "",
|
||||||
|
"PreUp": "",
|
||||||
|
"PrivateKey": "8DsSMli3okgUx5frKbFQ0fMW5ZMyqyxOdOW7+g21L18=",
|
||||||
|
"PublicKey": "GQlGi8QJ93hWY7L2xlJyh+7S6+ekER9xP11T92T0O0Q=",
|
||||||
|
"SaveConfig": true,
|
||||||
|
"Status": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Add WireGuard Configuration
|
||||||
|
|
||||||
|
Add a new WireGuard Configuration
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`POST /api/addWireguardConfiguration`
|
||||||
|
|
||||||
|
##### Body Parameters
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ConfigurationName": "wg0",
|
||||||
|
"Address": "10.0.0.1/24",
|
||||||
|
"ListenPort": 51820,
|
||||||
|
"PrivateKey": "eJuuamCgakVs2xUZGHh/g7C6Oy89JGh7eE2jjEGbbFc=",
|
||||||
|
"PublicKey": "3Ruirgw9qNRwNpBepkiVjjSe82tY+lDZr6WaFC4wO2g=",
|
||||||
|
"PresharedKey": "GMMLKWdJlgsKVoR26BJPsNbDXyfILL+x1Nd6Ecmn4lg=",
|
||||||
|
"PreUp": "",
|
||||||
|
"PreDown": "iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||||
|
"PostUp": "iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o enp0s1 -j MASQUERADE;",
|
||||||
|
"PostDown": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type |
|
||||||
|
|---------------------|--------|
|
||||||
|
| `ConfigurationName` | string |
|
||||||
|
| `Address` | string |
|
||||||
|
| `ListenPort` | int |
|
||||||
|
| `PrivateKey` | string |
|
||||||
|
| `PublicKey` | string |
|
||||||
|
| `PresharedKey` | string |
|
||||||
|
| `PreUp` | string |
|
||||||
|
| `PreDown` | string |
|
||||||
|
| `PostUp` | string |
|
||||||
|
| `PostDown` | string |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
If everything is good
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the new configuration's `ConfigurationName` is already existed
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Already have a configuration with the name \"wg0\"",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the new configuration's `ListenPort` is used by another configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Already have a configuration with the port \"51820\"",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the new configuration's `Address` is used by another configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Already have a configuration with the address \"10.0.0.1/24\"",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Toggle WireGuard Configuration
|
||||||
|
|
||||||
|
To turn on/off of a WireGuard Configuration
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/toggleWireguardConfiguration/?configurationName=`
|
||||||
|
|
||||||
|
##### Query String Parameter
|
||||||
|
|
||||||
|
| Parameter | Type |
|
||||||
|
|---------------------|--------|
|
||||||
|
| `configurationName` | string |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
If toggle is successful, server will return the current status in `status`: `true` or `false` indicating if the configuration is up or not.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": true,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If the `configurationName` provided does not exist
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Please provide a valid configuration name",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Get WGDashboard Configuration
|
||||||
|
|
||||||
|
Get the WGDashboard Configuration, such as `dashboard_theme`...
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/getDashboardConfiguration`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"Account": {
|
||||||
|
"enable_totp": false,
|
||||||
|
"password": "some hashed value :(",
|
||||||
|
"totp_verified": false,
|
||||||
|
"username": "admin"
|
||||||
|
},
|
||||||
|
"Database": {
|
||||||
|
"type": "sqlite"
|
||||||
|
},
|
||||||
|
"Other": {
|
||||||
|
"welcome_session": false
|
||||||
|
},
|
||||||
|
"Peers": {
|
||||||
|
"peer_display_mode": "grid",
|
||||||
|
"peer_endpoint_allowed_ip": "0.0.0.0/0",
|
||||||
|
"peer_global_dns": "1.1.1.1",
|
||||||
|
"peer_keep_alive": "21",
|
||||||
|
"peer_mtu": "1420",
|
||||||
|
"remote_endpoint": "192.168.2.38"
|
||||||
|
},
|
||||||
|
"Server": {
|
||||||
|
"app_ip": "0.0.0.0",
|
||||||
|
"app_port": "10086",
|
||||||
|
"app_prefix": "",
|
||||||
|
"auth_req": true,
|
||||||
|
"dashboard_api_key": true,
|
||||||
|
"dashboard_refresh_interval": "5000",
|
||||||
|
"dashboard_sort": "status",
|
||||||
|
"dashboard_theme": "dark",
|
||||||
|
"version": "v4.0",
|
||||||
|
"wg_conf_path": "/etc/wireguard"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Update WGDashboard Configuration Item
|
||||||
|
|
||||||
|
Update the WGDashboard Configuration one at a time
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`POST /api/updateDashboardConfigurationItem`
|
||||||
|
|
||||||
|
##### Body Parameters
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"section": "Server",
|
||||||
|
"key": "dashboard_theme",
|
||||||
|
"value": "dark"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
| Parameter | Type | |
|
||||||
|
|-----------|--------|----------------------------------------------------------|
|
||||||
|
| `section` | string | Each section in the `wg-dashboard.ini` |
|
||||||
|
| `key` | string | Each key/value pair under each in the `wg-dashboard.ini` |
|
||||||
|
| `value` | string | Value for this key/value pair |
|
||||||
|
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
If update is success
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": true,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If update failed
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": true,
|
||||||
|
"message": "Message related to the error will appear here",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Get WGDashboard API Keys
|
||||||
|
|
||||||
|
Get a list of active API key in WGDashboard
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET /api/getDashboardAPIKeys`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
If API Key function is enabled and there are active API keys
|
||||||
|
|
||||||
|
> If `ExpiredAt` is `null`, that means this API key will never expire
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"CreatedAt": "2024-08-15 00:42:31",
|
||||||
|
"ExpiredAt": null,
|
||||||
|
"Key": "AXt1x3TZMukmA-eSnAyESy08I14n20boppSsknHOB-Y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CreatedAt": "2024-08-14 22:50:44",
|
||||||
|
"ExpiredAt": "2024-08-21 22:50:43",
|
||||||
|
"Key": "ry0Suo0BrypSMzbq0C_TjkEcgrFHHj6UBZGmC2-KI2o"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If API key function is disabled
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Dashboard API Keys function is disabled",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Add WGDashboard API Key
|
||||||
|
|
||||||
|
Add a new API Key in WGDashboard
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`POST /api/newDashboardAPIKey`
|
||||||
|
|
||||||
|
##### Body Parameters
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"neverExpire": false,
|
||||||
|
"ExpiredAt": "2024-12-31 16:00:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
| Parameter | Type | |
|
||||||
|
|---------------|--------|-----------------------------------------------------------------------------------|
|
||||||
|
| `neverExpire` | bool | If this is `false`, please specify a date in `ExpiredAt` |
|
||||||
|
| `ExpiredAt` | string | If `neverExpire` is `true`, this can be omitted. Format is `YYYY-MM-DD hh:mm:ss`. |
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
If success, it will return the latest list of API Keys
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"CreatedAt": "2024-08-15 00:42:31",
|
||||||
|
"ExpiredAt": null,
|
||||||
|
"Key": "AXt1x3TZMukmA-eSnAyESy08I14n20boppSsknHOB-Y"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"CreatedAt": "2024-08-14 22:50:44",
|
||||||
|
"ExpiredAt": "2024-12-31 16:50:43",
|
||||||
|
"Key": "ry0Suo0BrypSMzbq0C_TjkEcgrFHHj6UBZGmC2-KI2o"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If API key function is disabled
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": null,
|
||||||
|
"message": "Dashboard API Keys function is disabled",
|
||||||
|
"status": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
### Endpoint
|
||||||
|
|
||||||
|
Description
|
||||||
|
|
||||||
|
#### Request
|
||||||
|
|
||||||
|
`GET`
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
`200 - OK`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": true,
|
||||||
|
"message": null,
|
||||||
|
"status": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# ⏰ Changelogs of WGDashboard
|
||||||
|
|
||||||
|
#### v3.0.0 - v3.0.6.2 - Jan 18, 2022
|
||||||
|
|
||||||
|
- 🎉 **New Features**
|
||||||
|
- **Moved from TinyDB to SQLite**: SQLite provide a better performance and loading speed when getting peers! Also avoided crashing the database due to **race condition**.
|
||||||
|
- **Added Gunicorn WSGI Server**: This could provide more stable on handling HTTP request, and more flexibility in the future (such as HTTPS support). **BIG THANKS to @pgalonza :heart:**
|
||||||
|
- **Add Peers by Bulk:** User can add peers by bulk, just simply set the amount and click add.
|
||||||
|
- **Delete Peers by Bulk**: User can delete peers by bulk, without deleting peers one by one.
|
||||||
|
- **Download Peers in Zip**: User can download all *downloadable* peers in a zip.
|
||||||
|
- **Added Pre-shared Key to peers:** Now each peer can add with a pre-shared key to enhance security. Previously added peers can add the pre-shared key through the peer setting button.
|
||||||
|
- **Redirect Back to Previous Page:** The dashboard will now redirect you back to your previous page if the current session got timed out and you need to sign in again.
|
||||||
|
- **Added Some [🥘 Experimental Functions](#-experimental-functions)**
|
||||||
|
|
||||||
|
- 🪚 **Bug Fixed**
|
||||||
|
- [IP Sorting range issues #99](https://github.com/donaldzou/WGDashboard/issues/99) [❤️ @barryboom]
|
||||||
|
- [INvalid character written to tunnel json file #108](https://github.com/donaldzou/WGDashboard/issues/108) [❤️ @ikidd]
|
||||||
|
- [Add IPv6 #91](https://github.com/donaldzou/WGDashboard/pull/91) [❤️ @pgalonza]
|
||||||
|
- [Added MTU and PersistentKeepalive to QR code and download files #112](https://github.com/donaldzou/WGDashboard/pull/112) [:heart: @reafian]
|
||||||
|
- **And many other bugs provided by our beloved users** :heart:
|
||||||
|
- **🧐 Other Changes**
|
||||||
|
- **Key generating moved to front-end**: No longer need to use the server's WireGuard to generate keys, thanks to the `wireguard.js` from the [official repository](https://git.zx2c4.com/wireguard-tools/tree/contrib/keygen-html/wireguard.js)!
|
||||||
|
- **Peer transfer calculation**: each peer will now show all transfer amount (previously was only showing transfer amount from the last configuration start-up).
|
||||||
|
- **UI adjustment on running peers**: peers will have a new style indicating that it is running.
|
||||||
|
- **`wgd.sh` finally can update itself**: So now user could update the whole dashboard from `wgd.sh`, with the `update` command.
|
||||||
|
- **Minified JS and CSS files**: Although only a small changes on the file size, but I think is still a good practice to save a bit of bandwidth ;)
|
||||||
|
|
||||||
|
*And many other small changes for performance and bug fixes! :laughing:*
|
||||||
|
|
||||||
|
#### v2.3.1 - Sep 8, 2021
|
||||||
|
|
||||||
|
- Updated dashboard's name to **WGDashboard**!!
|
||||||
|
|
||||||
|
#### v2.3 - Sep 8, 2021
|
||||||
|
|
||||||
|
- 🎉 **New Features**
|
||||||
|
- **Update directly from `wgd.sh`:** Now you can update WGDashboard directly from the bash script.
|
||||||
|
- **Displaying Peers:** You can switch the display mode between list and table in the configuration page.
|
||||||
|
- 🪚 **Bug Fixed**
|
||||||
|
- [Peer DNS Validation Fails #67](issues/67): Added DNS format check. [❤️ @realfian]
|
||||||
|
- [configparser.NoSectionError: No section: 'Interface' #66](issues/66): Changed permission requirement for `etc/wireguard` from `744` to `755`. [❤️ @ramalmaty]
|
||||||
|
- [Feature request: Interface not loading when information missing #73](issues/73): Fixed when Configuration Address and Listen Port is missing will crash the dashboard. [❤️ @js32]
|
||||||
|
- [Remote Peer, MTU and PersistentKeepalives added #70](pull/70): Added MTU, remote peer and Persistent Keepalive. [❤️ @realfian]
|
||||||
|
- [Fixes DNS check to support search domain #65](pull/65): Added allow input domain into DNS. [❤️@davejlong]
|
||||||
|
- **🧐 Other Changes**
|
||||||
|
- Moved Add Peer Button into the right bottom corner.
|
||||||
|
|
||||||
|
#### v2.2.1 - Aug 16, 2021
|
||||||
|
|
||||||
|
Bug Fixed:
|
||||||
|
- Added support for full subnet on Allowed IP
|
||||||
|
- Peer setting Save button
|
||||||
|
|
||||||
|
#### v2.2 - Aug 14, 2021
|
||||||
|
|
||||||
|
- 🎉 **New Features**
|
||||||
|
- **Add new peers**: Now you can add peers directly on dashboard, it will generate a pair of private key and public key. You can also set its DNS, endpoint allowed IPs. Both can set a default value in the setting page. [❤️ in [#44](https://github.com/donaldzou/wireguard-dashboard/issues/44)]
|
||||||
|
- **QR Code:** You can add the private key in peer setting of your existed peer to create a QR code. Or just create a new one, dashboard will now be able to auto generate a private key and public key ;) Don't worry, all keys will be generated on your machine, and **will delete all key files after they got generated**. [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
|
||||||
|
- **Peer configuration file download:** Same as QR code, you now can download the peer configuration file, so you don't need to manually input all the details on the peer machine! [❤️ in [#40](https://github.com/donaldzou/wireguard-dashboard/issues/40)]
|
||||||
|
- **Search peers**: You can now search peers by their name.
|
||||||
|
- **Autostart on boot:** Added a tutorial on how to start the dashboard to on boot! Please read the [tutorial below](#autostart-wireguard-dashboard-on-boot). [❤️ in [#29](https://github.com/donaldzou/wireguard-dashboard/issues/29)]
|
||||||
|
- **Click to copy**: You can now click and copy all peer's public key and configuration's public key.
|
||||||
|
- ....
|
||||||
|
- 🪚 **Bug Fixed**
|
||||||
|
- When there are comments in the wireguard config file, will cause the dashboard to crash.
|
||||||
|
- Used regex to search for config files.
|
||||||
|
- **🧐 Other Changes**
|
||||||
|
- Moved all external CSS and JavaScript file to local hosting (Except Bootstrap Icon, due to large amount of SVG files).
|
||||||
|
- Updated Python dependencies
|
||||||
|
- Flask: `v1.1.2 => v2.0.1`
|
||||||
|
- Jinja: `v2.10.1 => v3.0.1`
|
||||||
|
- icmplib: `v2.1.1 => v3.0.1`
|
||||||
|
- Updated CSS/JS dependencies
|
||||||
|
- Bootstrap: `v4.5.3 => v4.6.0`
|
||||||
|
- UI adjustment
|
||||||
|
- Adjusted how peers will display in larger screens, used to be 1 row per peer, now is 3 peers in 1 row.
|
||||||
|
|
||||||
|
#### v2.1 - Jul 2, 2021
|
||||||
|
|
||||||
|
- Added **Ping** and **Traceroute** tools!
|
||||||
|
- Adjusted the calculation of data usage on each peers
|
||||||
|
- Added refresh interval of the dashboard
|
||||||
|
- Bug fixed when no configuration on fresh install ([#23](https://github.com/donaldzou/wireguard-dashboard/issues/23))
|
||||||
|
- Fixed crash when too many peers ([#22](https://github.com/donaldzou/wireguard-dashboard/issues/22))
|
||||||
|
|
||||||
|
#### v2.0 - May 5, 2021
|
||||||
|
|
||||||
|
- Added login function to dashboard
|
||||||
|
- ***I'm not using the most ideal way to store the username and password, feel free to provide a better way to do this if you any good idea!***
|
||||||
|
- Added a config file to the dashboard
|
||||||
|
- Dashboard config can be change within the **Setting** tab on the side bar
|
||||||
|
- Adjusted UI
|
||||||
|
- And much more!
|
||||||
|
|
||||||
|
#### v1.1.2 - Apr 3, 2021
|
||||||
|
|
||||||
|
- Resolved issue [#3](https://github.com/donaldzou/wireguard-dashboard/issues/3).
|
||||||
|
|
||||||
|
#### v1.1.1 - Apr 2, 2021
|
||||||
|
|
||||||
|
- Able to add a friendly name to each peer. Thanks [#2](https://github.com/donaldzou/wireguard-dashboard/issues/2) !
|
||||||
|
|
||||||
|
#### v1.0 - Dec 27, 2020
|
||||||
|
|
||||||
|
- Added the function to remove peers
|
||||||
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 5.5 MiB |
|
Before Width: | Height: | Size: 229 KiB |
|
Before Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 180 KiB |
@@ -0,0 +1,34 @@
|
|||||||
|
#!/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
|
||||||
|
}
|
||||||
|
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/src/log" -mindepth 1 -maxdepth 1 -type f | read -r; then
|
||||||
|
latestErrLog=$(find /opt/wireguarddashboard/src/log -name "error_*.log" | head -n 1)
|
||||||
|
latestAccLog=$(find /opt/wireguarddashboard/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
|
||||||
|
}
|
||||||
|
|
||||||
|
{ date; clean_up; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
|
||||||
|
chmod u+x /opt/wireguarddashboard/src/wgd.sh
|
||||||
|
/opt/wireguarddashboard/src/wgd.sh install
|
||||||
|
/opt/wireguarddashboard/src/wgd.sh docker_start
|
||||||
|
ensure_blocking
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
WIREGUARD_INTERFACE=ADMINS
|
||||||
|
WIREGUARD_LAN=10.0.0.1/24
|
||||||
|
MASQUERADE_INTERFACE=eth0
|
||||||
|
|
||||||
|
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
|
||||||
|
|
||||||
|
iptables -t nat -D POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
|
||||||
|
|
||||||
|
# Remove and delete the WIREGUARD_wg0 chain
|
||||||
|
iptables -D FORWARD -j $CHAIN_NAME
|
||||||
|
iptables -F $CHAIN_NAME
|
||||||
|
iptables -X $CHAIN_NAME
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
WIREGUARD_INTERFACE=ADMINS
|
||||||
|
WIREGUARD_LAN=10.0.0.1/24
|
||||||
|
MASQUERADE_INTERFACE=eth0
|
||||||
|
|
||||||
|
iptables -t nat -I POSTROUTING -o $MASQUERADE_INTERFACE -j MASQUERADE -s $WIREGUARD_LAN
|
||||||
|
|
||||||
|
# Add a WIREGUARD_wg0 chain to the FORWARD chain
|
||||||
|
CHAIN_NAME="WIREGUARD_$WIREGUARD_INTERFACE"
|
||||||
|
iptables -N $CHAIN_NAME
|
||||||
|
iptables -A FORWARD -j $CHAIN_NAME
|
||||||
|
|
||||||
|
# Accept related or established traffic
|
||||||
|
iptables -A $CHAIN_NAME -o $WIREGUARD_INTERFACE -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||||
|
|
||||||
|
# Accept traffic from any Wireguard IP address connected to the Wireguard server
|
||||||
|
iptables -A $CHAIN_NAME -s $WIREGUARD_LAN -i $WIREGUARD_INTERFACE -j ACCEPT
|
||||||
|
|
||||||
|
# Allow traffic to the local loopback interface
|
||||||
|
iptables -A $CHAIN_NAME -o lo -j ACCEPT
|
||||||
|
|
||||||
|
# Drop everything else coming through the Wireguard interface
|
||||||
|
iptables -A $CHAIN_NAME -i $WIREGUARD_INTERFACE -j DROP
|
||||||
|
|
||||||
|
# Return to FORWARD chain
|
||||||
|
iptables -A $CHAIN_NAME -j RETURN
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"version": "4.0.0",
|
"version": "4.0.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"build electron": "vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder",
|
"build electron": "vite build && vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder --mac --win",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const proxy = "http://wg.local:10086/"
|
||||||
@@ -25,13 +25,16 @@ const getActiveCrossServer = computed(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="navbar bg-dark sticky-top border-bottom border-secondary-subtle" data-bs-theme="dark">
|
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark">
|
||||||
<div class="container-fluid d-flex text-body align-items-center">
|
<div class="container-fluid d-flex text-body align-items-center">
|
||||||
<span class="navbar-brand mb-0 h1">WGDashboard</span>
|
<span class="navbar-brand mb-0 h1">WGDashboard</span>
|
||||||
<small class="ms-auto text-muted" v-if="getActiveCrossServer !== undefined">
|
<small class="ms-auto text-muted" v-if="getActiveCrossServer !== undefined">
|
||||||
<i class="bi bi-server me-2"></i>{{getActiveCrossServer.host}}
|
<i class="bi bi-server me-2"></i>{{getActiveCrossServer.host}}
|
||||||
</small>
|
</small>
|
||||||
<a role="button"><i class="bi bi-list"></i></a>
|
<a role="button" class="navbarBtn text-body"
|
||||||
|
@click="store.ShowNavBar = !store.ShowNavBar"
|
||||||
|
style="line-height: 0; font-size: 2rem">
|
||||||
|
<i class="bi bi-list"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
@@ -58,4 +61,12 @@ const getActiveCrossServer = computed(() => {
|
|||||||
transform: translateY(-20px);
|
transform: translateY(-20px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.navbarBtn{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -36,8 +36,11 @@ export default {
|
|||||||
addAllowedIp(ip){
|
addAllowedIp(ip){
|
||||||
if(this.store.checkCIDR(ip)){
|
if(this.store.checkCIDR(ip)){
|
||||||
this.data.allowed_ips.push(ip);
|
this.data.allowed_ips.push(ip);
|
||||||
|
this.customAvailableIp = ''
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
this.allowedIpFormatError = true;
|
||||||
|
this.dashboardStore.newMessage('WGDashboard', 'Allowed IP is invalid', 'danger')
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -80,10 +83,7 @@ export default {
|
|||||||
:disabled="bulk">
|
:disabled="bulk">
|
||||||
<button class="btn btn-outline-success btn-sm rounded-end-3"
|
<button class="btn btn-outline-success btn-sm rounded-end-3"
|
||||||
:disabled="bulk || !this.customAvailableIp"
|
:disabled="bulk || !this.customAvailableIp"
|
||||||
@click="this.addAllowedIp(this.customAvailableIp)
|
@click="this.addAllowedIp(this.customAvailableIp)"
|
||||||
? this.customAvailableIp = '' :
|
|
||||||
this.allowedIpFormatError = true;
|
|
||||||
this.dashboardStore.newMessage('WGDashboard', 'Allowed IP is invalid', 'danger')"
|
|
||||||
type="button" id="button-addon2">
|
type="button" id="button-addon2">
|
||||||
<i class="bi bi-plus-lg"></i>
|
<i class="bi bi-plus-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -30,12 +30,24 @@ export default {
|
|||||||
this.data.private_key = this.keypair.privateKey;
|
this.data.private_key = this.keypair.privateKey;
|
||||||
this.data.public_key = this.keypair.publicKey;
|
this.data.public_key = this.keypair.publicKey;
|
||||||
},
|
},
|
||||||
|
testKey(key){
|
||||||
|
const reg = /^[A-Za-z0-9+/]{43}=?=?$/;
|
||||||
|
return reg.test(key)
|
||||||
|
},
|
||||||
checkMatching(){
|
checkMatching(){
|
||||||
try{
|
try{
|
||||||
|
if(this.keypair.privateKey){
|
||||||
|
if(this.testKey(this.keypair.privateKey)){
|
||||||
|
this.keypair.publicKey = window.wireguard.generatePublicKey(this.keypair.privateKey)
|
||||||
if (window.wireguard.generatePublicKey(this.keypair.privateKey)
|
if (window.wireguard.generatePublicKey(this.keypair.privateKey)
|
||||||
!== this.keypair.publicKey){
|
!== this.keypair.publicKey){
|
||||||
this.error = true;
|
this.error = true;
|
||||||
this.dashboardStore.newMessage("WGDashboard", "Private Key and Public Key does not match.", "danger");
|
this.dashboardStore.newMessage("WGDashboard", "Private Key and Public Key does not match.", "danger");
|
||||||
|
}else{
|
||||||
|
this.data.private_key = this.keypair.privateKey
|
||||||
|
this.data.public_key = this.keypair.publicKey
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}catch (e){
|
}catch (e){
|
||||||
this.error = true;
|
this.error = true;
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ export default {
|
|||||||
</h3>
|
</h3>
|
||||||
<h3 class="text-body mb-0">Add Peers</h3>
|
<h3 class="text-body mb-0">Add Peers</h3>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column gap-2">
|
<div class="d-flex flex-column gap-2">
|
||||||
<BulkAdd :saving="saving" :data="this.data" :availableIp="this.availableIp"></BulkAdd>
|
<BulkAdd :saving="saving" :data="this.data" :availableIp="this.availableIp"></BulkAdd>
|
||||||
|
|||||||
@@ -200,7 +200,8 @@ export default {
|
|||||||
})
|
})
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
if (this.configurationPeers.length > 0){
|
if (this.configurationPeers.length > 0){
|
||||||
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent).reduce((x,y) => x + y).toFixed(4);
|
const sent = this.configurationPeers.map(x => x.total_sent + x.cumu_sent)
|
||||||
|
.reduce((x,y) => x + y).toFixed(4);
|
||||||
const receive = this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((x,y) => x + y).toFixed(4);
|
const receive = this.configurationPeers.map(x => x.total_receive + x.cumu_receive).reduce((x,y) => x + y).toFixed(4);
|
||||||
if (
|
if (
|
||||||
this.historyDataSentDifference[this.historyDataSentDifference.length - 1] !== sent
|
this.historyDataSentDifference[this.historyDataSentDifference.length - 1] !== sent
|
||||||
@@ -259,13 +260,13 @@ export default {
|
|||||||
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
connectedPeers: this.configurationPeers.filter(x => x.status === "running").length,
|
||||||
totalUsage: this.configurationPeers.length > 0 ?
|
totalUsage: this.configurationPeers.length > 0 ?
|
||||||
this.configurationPeers.filter(x => !x.restricted)
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b).toFixed(4) : 0,
|
.map(x => x.total_data + x.cumu_data).reduce((a, b) => a + b, 0).toFixed(4) : 0,
|
||||||
totalReceive: this.configurationPeers.length > 0 ?
|
totalReceive: this.configurationPeers.length > 0 ?
|
||||||
this.configurationPeers.filter(x => !x.restricted)
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b).toFixed(4) : 0,
|
.map(x => x.total_receive + x.cumu_receive).reduce((a, b) => a + b, 0).toFixed(4) : 0,
|
||||||
totalSent: this.configurationPeers.length > 0 ?
|
totalSent: this.configurationPeers.length > 0 ?
|
||||||
this.configurationPeers.filter(x => !x.restricted)
|
this.configurationPeers.filter(x => !x.restricted)
|
||||||
.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b).toFixed(4) : 0
|
.map(x => x.total_sent + x.cumu_sent).reduce((a, b) => a + b, 0).toFixed(4) : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return k
|
return k
|
||||||
@@ -402,7 +403,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!this.loading">
|
<div v-if="!this.loading" class="container-md">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<small CLASS="text-muted">CONFIGURATION</small>
|
<small CLASS="text-muted">CONFIGURATION</small>
|
||||||
@@ -545,10 +546,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-3">
|
||||||
<!-- <div class="d-flex align-items-center gap-3 mb-2">-->
|
|
||||||
<!-- <h3>Peers</h3>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
<PeerSearch
|
<PeerSearch
|
||||||
@jobsAll="this.peerScheduleJobsAll.modalOpen = true"
|
@jobsAll="this.peerScheduleJobsAll.modalOpen = true"
|
||||||
@jobLogs="this.peerScheduleJobsLogs.modalOpen = true"
|
@jobLogs="this.peerScheduleJobsLogs.modalOpen = true"
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ export default {
|
|||||||
'60000': '1 Minutes'
|
'60000': '1 Minutes'
|
||||||
},
|
},
|
||||||
searchString: "",
|
searchString: "",
|
||||||
searchStringTimeout: undefined
|
searchStringTimeout: undefined,
|
||||||
|
showDisplaySettings: false,
|
||||||
|
showMoreSettings: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -83,7 +85,7 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="d-flex gap-2 z-3">
|
<div class="d-flex gap-2 z-3 peerSearchContainer">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="create"
|
to="create"
|
||||||
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">
|
||||||
@@ -93,66 +95,100 @@ export default {
|
|||||||
@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="flex-grow-1">
|
<div class="flex-grow-1 mt-3 mt-md-0">
|
||||||
<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 w-100"
|
||||||
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 dropup">
|
<button
|
||||||
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
@click="this.showDisplaySettings = true"
|
||||||
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
||||||
|
type="button" aria-expanded="false">
|
||||||
<i class="bi bi-filter-circle me-2"></i>
|
<i class="bi bi-filter-circle me-2"></i>
|
||||||
Display
|
Display
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu mt-2 shadow rounded-3 animate__animated animation__fadeInDropdown dropdown-menu-end">
|
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
||||||
<li>
|
@click="this.showMoreSettings = true"
|
||||||
<small class="dropdown-header">Sort by</small>
|
type="button" aria-expanded="false">
|
||||||
</li>
|
<i class="bi bi-three-dots"></i>
|
||||||
<li v-for="(value, key) in this.sort">
|
</button>
|
||||||
<a class="dropdown-item d-flex align-items-center" role="button" @click="this.updateSort(key)">
|
<Transition name="zoom">
|
||||||
<small class="me-auto">{{value}}</small>
|
<div
|
||||||
|
v-if="this.showDisplaySettings"
|
||||||
|
class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll displayModal">
|
||||||
|
<div class="container-md d-flex h-100 w-100">
|
||||||
|
<div class="m-auto modal-dialog-centered dashboardModal">
|
||||||
|
<div class="card rounded-3 shadow w-100">
|
||||||
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
|
||||||
|
<h4 class="mb-0 fw-normal">Display
|
||||||
|
</h4>
|
||||||
|
<button type="button" class="btn-close ms-auto" @click="this.showDisplaySettings = false"></button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-4 pb-4 d-flex gap-3 flex-column">
|
||||||
|
<div>
|
||||||
|
<p class="text-muted fw-bold mb-2"><small>Sort by</small></p>
|
||||||
|
<div class="list-group">
|
||||||
|
<a v-for="(value, key) in this.sort" class="list-group-item list-group-item-action d-flex" role="button" @click="this.updateSort(key)">
|
||||||
|
<span class="me-auto">{{value}}</span>
|
||||||
<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>
|
</div>
|
||||||
<li><hr class="dropdown-divider"></li>
|
</div>
|
||||||
<li>
|
<div>
|
||||||
<small class="dropdown-header">Refresh Interval</small>
|
<p class="text-muted fw-bold mb-2"><small>Refresh interval</small></p>
|
||||||
</li>
|
<div class="list-group">
|
||||||
<li v-for="(value, key) in this.interval">
|
<a v-for="(value, key) in this.interval"
|
||||||
<a class="dropdown-item d-flex" role="button" @click="updateRefreshInterval(key)">
|
class="list-group-item list-group-item-action d-flex" role="button"
|
||||||
<small class="me-auto">{{value}}</small>
|
@click="this.updateRefreshInterval(key)">
|
||||||
|
<span class="me-auto">{{value}}</span>
|
||||||
<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>
|
</a>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown dropup">
|
</div>
|
||||||
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
|
</div>
|
||||||
type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
</div>
|
||||||
<i class="bi bi-three-dots"></i>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
<ul class="dropdown-menu shadow mt-2 rounded-3 animate__animated animation__fadeInDropdown">
|
</div>
|
||||||
<li>
|
</Transition>
|
||||||
<h6 class="dropdown-header">Peer Jobs</h6>
|
<Transition name="zoom">
|
||||||
</li>
|
<div
|
||||||
<li>
|
v-if="this.showMoreSettings"
|
||||||
<a role="button" class="dropdown-item" @click="this.$emit('jobsAll')">
|
class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll displayModal">
|
||||||
|
<div class="container-md d-flex h-100 w-100">
|
||||||
|
<div class="m-auto modal-dialog-centered dashboardModal">
|
||||||
|
<div class="card rounded-3 shadow w-100">
|
||||||
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
|
||||||
|
<h4 class="mb-0 fw-normal">Configuration Settings
|
||||||
|
</h4>
|
||||||
|
<button type="button" class="btn-close ms-auto" @click="this.showMoreSettings = false"></button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-4 pb-4 d-flex gap-3 flex-column">
|
||||||
|
<div>
|
||||||
|
<p class="text-muted fw-bold mb-2"><small>Peer Jobs</small></p>
|
||||||
|
<div class="list-group">
|
||||||
|
<a class="list-group-item list-group-item-action d-flex" role="button"
|
||||||
|
@click="this.$emit('jobsAll')">
|
||||||
Active Jobs
|
Active Jobs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a class="list-group-item list-group-item-action d-flex" role="button"
|
||||||
<li>
|
@click="this.$emit('jobLogs')">
|
||||||
<a role="button" class="dropdown-item" @click="this.$emit('jobLogs')">
|
|
||||||
Logs
|
Logs
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -174,6 +210,19 @@ export default {
|
|||||||
filter: blur(0px);
|
filter: blur(0px);
|
||||||
transform: translateY(-40px);
|
transform: translateY(-40px);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.displayModal .dashboardModal{
|
||||||
|
width: 400px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.peerSearchContainer{
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peerSettingContainer .dashboardModal{
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -31,16 +31,15 @@ export default {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="mt-5">
|
<div class="mt-md-5 mt-3">
|
||||||
<div class="container">
|
<div class="container-md">
|
||||||
<div class="d-flex mb-4 ">
|
<div class="d-flex mb-4 configurationListTitle">
|
||||||
<h3 class="text-body">
|
<h3 class="text-body d-flex">
|
||||||
<i class="bi bi-body-text me-2"></i>
|
<i class="bi bi-body-text me-2"></i>
|
||||||
WireGuard Configurations</h3>
|
<span>WireGuard Configurations</span></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
|
||||||
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<Transition name="fade" mode="out-in">
|
<Transition name="fade" mode="out-in">
|
||||||
@@ -60,5 +59,18 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.configurationListTitle{
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
h3 span{
|
||||||
|
margin-left: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -52,23 +52,23 @@ export default {
|
|||||||
</h6>
|
</h6>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div class="card-footer d-flex gap-2 flex-column">
|
<div class="card-footer d-flex gap-2 flex-column">
|
||||||
<div class="d-flex gap-4">
|
<div class="row">
|
||||||
<small >
|
<small class="col-6 col-md-3">
|
||||||
<i class="bi bi-arrow-down-up me-2"></i>{{c.DataUsage.Total > 0 ? c.DataUsage.Total.toFixed(4) : 0}} GB
|
<i class="bi bi-arrow-down-up me-2"></i>{{c.DataUsage.Total > 0 ? c.DataUsage.Total.toFixed(4) : 0}} GB
|
||||||
</small>
|
</small>
|
||||||
<small class="text-primary-emphasis">
|
<small class="text-primary-emphasis col-6 col-md-3">
|
||||||
<i class="bi bi-arrow-down me-2"></i>{{c.DataUsage.Receive > 0 ? c.DataUsage.Receive.toFixed(4) : 0}} GB
|
<i class="bi bi-arrow-down me-2"></i>{{c.DataUsage.Receive > 0 ? c.DataUsage.Receive.toFixed(4) : 0}} GB
|
||||||
</small>
|
</small>
|
||||||
<small class="text-success-emphasis">
|
<small class="text-success-emphasis col-6 col-md-3">
|
||||||
<i class="bi bi-arrow-up me-2"></i>{{c.DataUsage.Sent > 0 ? c.DataUsage.Sent.toFixed(4) : 0}} GB
|
<i class="bi bi-arrow-up me-2"></i>{{c.DataUsage.Sent > 0 ? c.DataUsage.Sent.toFixed(4) : 0}} GB
|
||||||
</small>
|
</small>
|
||||||
<small class="ms-auto">
|
<small class="text-md-end col-6 col-md-3">
|
||||||
<span class="dot me-2" :class="{active: c.ConnectedPeers > 0}"></span>{{c.ConnectedPeers}} Peers
|
<span class="dot me-2" :class="{active: c.ConnectedPeers > 0}"></span>{{c.ConnectedPeers}} Peers
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<small class="me-2 text-muted">
|
<small class="text-muted">
|
||||||
<strong>Public Key</strong>
|
<strong style="word-break: keep-all">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>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
||||||
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 {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "navbar",
|
name: "navbar",
|
||||||
@@ -9,12 +10,36 @@ export default {
|
|||||||
const wireguardConfigurationsStore = WireguardConfigurationsStore();
|
const wireguardConfigurationsStore = WireguardConfigurationsStore();
|
||||||
const dashboardConfigurationStore = DashboardConfigurationStore();
|
const dashboardConfigurationStore = DashboardConfigurationStore();
|
||||||
return {wireguardConfigurationsStore, dashboardConfigurationStore}
|
return {wireguardConfigurationsStore, dashboardConfigurationStore}
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
updateAvailable: false,
|
||||||
|
updateMessage: "Checking for update...",
|
||||||
|
updateUrl: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
fetchGet("/api/getDashboardUpdate", {}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
if (res.data){
|
||||||
|
this.updateAvailable = true
|
||||||
|
this.updateUrl = res.data
|
||||||
|
}
|
||||||
|
this.updateMessage = res.message
|
||||||
|
}else{
|
||||||
|
this.updateMessage = "Failed to check available update"
|
||||||
|
console.log(`Failed to get update: ${res.message}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="col-md-3 col-lg-2 d-md-block p-3 navbar-container bg-body" style="height: calc(-50px + 100vh);">
|
<div class="col-md-3 col-lg-2 d-md-block p-3 navbar-container"
|
||||||
|
:class="{active: this.dashboardConfigurationStore.ShowNavBar}"
|
||||||
|
:data-bs-theme="dashboardConfigurationStore.Configuration.Server.dashboard_theme"
|
||||||
|
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 px-2">
|
<ul class="nav flex-column px-2">
|
||||||
@@ -38,8 +63,8 @@ export default {
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink :to="'/configuration/'+c.Name + '/peers'" class="nav-link nav-conf-link rounded-3"
|
<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">
|
||||||
|
<span class="dot me-2" :class="{active: c.Status}"></span>
|
||||||
{{c.Name}}
|
{{c.Name}}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
@@ -62,11 +87,20 @@ export default {
|
|||||||
@click="this.dashboardConfigurationStore.signOut()"
|
@click="this.dashboardConfigurationStore.signOut()"
|
||||||
role="button" style="font-weight: bold">
|
role="button" style="font-weight: bold">
|
||||||
<i class="bi bi-box-arrow-left me-2"></i>
|
<i class="bi bi-box-arrow-left me-2"></i>
|
||||||
Sign Out</a></li>
|
Sign Out</a>
|
||||||
</ul>
|
</li>
|
||||||
<ul class="nav flex-column">
|
<li class="nav-item" style="font-size: 0.8rem">
|
||||||
<li class="nav-item"><a href="https://github.com/donaldzou/WGDashboard/releases/tag/"><small class="nav-link text-muted"></small></a></li>
|
<a :href="this.updateUrl" v-if="this.updateAvailable" class="text-decoration-none" target="_blank">
|
||||||
|
<small class="nav-link text-muted rounded-3" >
|
||||||
|
{{ this.updateMessage }}
|
||||||
|
</small>
|
||||||
|
</a>
|
||||||
|
<small class="nav-link text-muted" v-else>
|
||||||
|
{{ this.updateMessage }}
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,6 +113,28 @@ export default {
|
|||||||
.navbar-container{
|
.navbar-container{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
animation-duration: 0.4s;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
display: none;
|
||||||
|
animation-timing-function: cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||||
|
}
|
||||||
|
.navbar-container.active{
|
||||||
|
animation-direction: normal;
|
||||||
|
display: block !important;
|
||||||
|
animation-name: zoomInFade
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes zoomInFade {
|
||||||
|
0%{
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(60px);
|
||||||
|
filter: blur(3px);
|
||||||
|
}
|
||||||
|
100%{
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0px);
|
||||||
|
filter: blur(0px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -18,8 +18,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async handshake(){
|
async handshake(){
|
||||||
this.active = false;
|
this.active = false;
|
||||||
this.refreshing = true;
|
|
||||||
if (this.server.host && this.server.apiKey){
|
if (this.server.host && this.server.apiKey){
|
||||||
|
|
||||||
|
this.refreshing = true;
|
||||||
this.startTime = undefined;
|
this.startTime = undefined;
|
||||||
this.endTime = undefined;
|
this.endTime = undefined;
|
||||||
this.startTime = dayjs()
|
this.startTime = dayjs()
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}else{
|
}else{
|
||||||
document.title = "WGDashboard"
|
document.title = "WGDashboard"
|
||||||
}
|
}
|
||||||
|
dashboardConfigurationStore.ShowNavBar = false;
|
||||||
|
|
||||||
if (to.meta.requiresAuth){
|
if (to.meta.requiresAuth){
|
||||||
if (!dashboardConfigurationStore.getActiveCrossServer()){
|
if (!dashboardConfigurationStore.getActiveCrossServer()){
|
||||||
@@ -159,7 +160,6 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
}else {
|
}else {
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
|
|||||||
ServerList: {}
|
ServerList: {}
|
||||||
},
|
},
|
||||||
ActiveServerConfiguration: undefined,
|
ActiveServerConfiguration: undefined,
|
||||||
IsElectronApp: false
|
IsElectronApp: false,
|
||||||
|
ShowNavBar: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
initCrossServerConfiguration(){
|
initCrossServerConfiguration(){
|
||||||
@@ -25,7 +26,7 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
|
|||||||
this.ActiveServerConfiguration = localStorage.getItem("ActiveCrossServerConfiguration");
|
this.ActiveServerConfiguration = localStorage.getItem("ActiveCrossServerConfiguration");
|
||||||
}
|
}
|
||||||
if (currentConfiguration === null){
|
if (currentConfiguration === null){
|
||||||
localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
|
window.localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
|
||||||
}else{
|
}else{
|
||||||
this.CrossServerConfiguration = JSON.parse(currentConfiguration)
|
this.CrossServerConfiguration = JSON.parse(currentConfiguration)
|
||||||
}
|
}
|
||||||
@@ -33,7 +34,7 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
|
|||||||
|
|
||||||
},
|
},
|
||||||
syncCrossServerConfiguration(){
|
syncCrossServerConfiguration(){
|
||||||
localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
|
window.localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
|
||||||
},
|
},
|
||||||
addCrossServerConfiguration(){
|
addCrossServerConfiguration(){
|
||||||
this.CrossServerConfiguration.ServerList[v4().toString()] = {host: "", apiKey: "", active: false}
|
this.CrossServerConfiguration.ServerList[v4().toString()] = {host: "", apiKey: "", active: false}
|
||||||
@@ -62,13 +63,13 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
|
|||||||
if (res.status) this.Configuration = res.data
|
if (res.status) this.Configuration = res.data
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async updateConfiguration(){
|
// async updateConfiguration(){
|
||||||
await fetchPost("/api/updateDashboardConfiguration", {
|
// await fetchPost("/api/updateDashboardConfiguration", {
|
||||||
DashboardConfiguration: this.Configuration
|
// DashboardConfiguration: this.Configuration
|
||||||
}, (res) => {
|
// }, (res) => {
|
||||||
console.log(res)
|
// console.log(res)
|
||||||
})
|
// })
|
||||||
},
|
// },
|
||||||
async signOut(){
|
async signOut(){
|
||||||
await fetchGet("/api/signout", {}, (res) => {
|
await fetchGet("/api/signout", {}, (res) => {
|
||||||
this.removeActiveCrossServer();
|
this.removeActiveCrossServer();
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ const getUrl = (url) => {
|
|||||||
if (apiKey){
|
if (apiKey){
|
||||||
return `${apiKey.host}${url}`
|
return `${apiKey.host}${url}`
|
||||||
}
|
}
|
||||||
return url
|
|
||||||
|
// console.log("URL fetching: ", import.meta.env.MODE === 'development' ? url
|
||||||
|
// : `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`)
|
||||||
|
return import.meta.env.MODE === 'development' ? url
|
||||||
|
: `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-5 text-body">
|
<div class="mt-md-5 mt-3 text-body">
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<Transition name="fade2" mode="out-in">
|
<Transition name="fade2" mode="out-in">
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-5 text-body">
|
<div class="mt-md-5 mt-3 text-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h3 class="mb-3 text-body">Ping</h3>
|
<h3 class="mb-3 text-body">Ping</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-5">
|
<div class="mt-md-5 mt-3">
|
||||||
<div class="container">
|
<div class="container-md">
|
||||||
<h3 class="mb-3 text-body">Settings</h3>
|
<h3 class="mb-3 text-body">Settings</h3>
|
||||||
<DashboardTheme></DashboardTheme>
|
<DashboardTheme></DashboardTheme>
|
||||||
<div class="card mb-4 shadow rounded-3">
|
<div class="card mb-4 shadow rounded-3">
|
||||||
|
|||||||
@@ -11,16 +11,22 @@ export default {
|
|||||||
const store = DashboardConfigurationStore()
|
const store = DashboardConfigurationStore()
|
||||||
let theme = "dark"
|
let theme = "dark"
|
||||||
let totpEnabled = false;
|
let totpEnabled = false;
|
||||||
|
let version = undefined;
|
||||||
if (!store.IsElectronApp){
|
if (!store.IsElectronApp){
|
||||||
await fetchGet("/api/getDashboardTheme", {}, (res) => {
|
await Promise.all([
|
||||||
|
fetchGet("/api/getDashboardTheme", {}, (res) => {
|
||||||
theme = res.data
|
theme = res.data
|
||||||
});
|
}),
|
||||||
await fetchGet("/api/isTotpEnabled", {}, (res) => {
|
fetchGet("/api/isTotpEnabled", {}, (res) => {
|
||||||
totpEnabled = res.data
|
totpEnabled = res.data
|
||||||
});
|
}),
|
||||||
|
fetchGet("/api/getDashboardVersion", {}, (res) => {
|
||||||
|
version = res.data
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
store.removeActiveCrossServer();
|
store.removeActiveCrossServer();
|
||||||
return {store, theme, totpEnabled}
|
return {store, theme, totpEnabled, version}
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
@@ -146,7 +152,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted pb-3 d-block w-100 text-center mt-3">
|
<small class="text-muted pb-3 d-block w-100 text-center mt-3">
|
||||||
WGDashboard v4.0 | Developed with ❤️ by
|
WGDashboard {{ this.version }} | 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">
|
<div class="messageCentre text-body position-absolute end-0 m-3">
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-5 text-body">
|
<div class="mt-md-5 mt-3 text-body">
|
||||||
<div class="container">
|
<div class="container-md">
|
||||||
<h3 class="mb-3 text-body">Traceroute</h3>
|
<h3 class="mb-3 text-body">Traceroute</h3>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-4 d-flex gap-2 flex-column">
|
<div class="col-sm-4 d-flex gap-2 flex-column">
|
||||||
|
|||||||
@@ -121,7 +121,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-bs-theme="dark"].main,
|
[data-bs-theme="dark"].main,
|
||||||
#app:has(.main[data-bs-theme="dark"]){
|
#app:has(.main[data-bs-theme="dark"]),
|
||||||
|
[data-bs-theme="dark"].navbar-container{
|
||||||
background-color: #1b1e21;
|
background-color: #1b1e21;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,929 +0,0 @@
|
|||||||
let $body = $("body");
|
|
||||||
let available_ips = [];
|
|
||||||
let $add_peer = document.getElementById("save_peer");
|
|
||||||
|
|
||||||
$("#configuration_delete").on("click", function(){
|
|
||||||
configurations.configurationDeleteModal().toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
function ajaxPostJSON(url, data, doneFunc){
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
method: "POST",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
headers: {"Content-Type": "application/json"}
|
|
||||||
}).done(function (res) {
|
|
||||||
doneFunc(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function ajaxGetJSON(url, doneFunc){
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
headers: {"Content-Type": "application/json"}
|
|
||||||
}).done(function (res) {
|
|
||||||
doneFunc(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#sure_delete_configuration").on("click", function () {
|
|
||||||
configurations.removeConfigurationInterval();
|
|
||||||
let ele = $(this)
|
|
||||||
ele.attr("disabled", "disabled");
|
|
||||||
function done(res){
|
|
||||||
if (res.status){
|
|
||||||
$('#configuration_delete_modal button[data-dismiss="modal"]').remove();
|
|
||||||
ele.text("Delete Successful! Redirecting in 5 seconds.");
|
|
||||||
setTimeout(function(){
|
|
||||||
window.location.replace('/');
|
|
||||||
}, 5000)
|
|
||||||
}else{
|
|
||||||
$("#remove_configuration_alert").removeClass("d-none").text(res.reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ajaxPostJSON("/api/deleteConfiguration", {"name": configurations.getConfigurationName()}, done);
|
|
||||||
});
|
|
||||||
|
|
||||||
function loadPeerDataUsageChartDone(res){
|
|
||||||
if (res.status === true){
|
|
||||||
let t = new Date();
|
|
||||||
let string = `${t.getDate()}/${t.getMonth()}/${t.getFullYear()} ${t.getHours() > 10 ? t.getHours():`0${t.getHours()}`}:${t.getMinutes() > 10 ? t.getMinutes():`0${t.getMinutes()}`}:${t.getSeconds() > 10 ? t.getSeconds():`0${t.getSeconds()}`}`;
|
|
||||||
$(".peerDataUsageUpdateTime").html(`Updated on: ${string}`);
|
|
||||||
|
|
||||||
configurations.peerDataUsageChartObj().data.labels = [];
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[0].data = [];
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[1].data = [];
|
|
||||||
console.log(res);
|
|
||||||
let data = res.data;
|
|
||||||
if (data.length > 0){
|
|
||||||
configurations.peerDataUsageChartObj().data.labels.push(data[data.length - 1].time);
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[0].data.push(0);
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[1].data.push(0);
|
|
||||||
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[0].lastData = data[data.length - 1].total_sent
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[1].lastData = data[data.length - 1].total_receive
|
|
||||||
|
|
||||||
|
|
||||||
for(let i = data.length - 2; i >= 0; i--){
|
|
||||||
let sent = data[i].total_sent - configurations.peerDataUsageChartObj().data.datasets[0].lastData;
|
|
||||||
let receive = data[i].total_receive - configurations.peerDataUsageChartObj().data.datasets[1].lastData;
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[0].data.push(sent);
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[1].data.push(receive);
|
|
||||||
configurations.peerDataUsageChartObj().data.labels.push(data[i].time);
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[0].lastData = data[i].total_sent;
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[1].lastData = data[i].total_receive;
|
|
||||||
}
|
|
||||||
configurations.peerDataUsageChartObj().update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let peerDataUsageInterval;
|
|
||||||
|
|
||||||
$body.on("click", ".btn-data-usage-peer", function(){
|
|
||||||
configurations.peerDataUsageChartObj().data.peerID = $(this).data("peer-id");
|
|
||||||
configurations.peerDataUsageModal().toggle();
|
|
||||||
peerDataUsageInterval = setInterval(function(){
|
|
||||||
ajaxPostJSON("/api/getPeerDataUsage", {"config": configurations.getConfigurationName(), "peerID": configurations.peerDataUsageChartObj().data.peerID, "interval": window.localStorage.getItem("peerTimePeriod")}, loadPeerDataUsageChartDone);
|
|
||||||
}, 30000);
|
|
||||||
ajaxPostJSON("/api/getPeerDataUsage", {"config": configurations.getConfigurationName(), "peerID": configurations.peerDataUsageChartObj().data.peerID, "interval": window.localStorage.getItem("peerTimePeriod")}, loadPeerDataUsageChartDone);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#peerDataUsage').on('shown.bs.modal', function() {
|
|
||||||
configurations.peerDataUsageChartObj().resize();
|
|
||||||
}).on('hidden.bs.modal', function() {
|
|
||||||
clearInterval(peerDataUsageInterval);
|
|
||||||
configurations.peerDataUsageChartObj().data.peerID = "";
|
|
||||||
configurations.peerDataUsageChartObj().data.labels = [];
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[0].data = [];
|
|
||||||
configurations.peerDataUsageChartObj().data.datasets[1].data = [];
|
|
||||||
configurations.peerDataUsageChartObj().update();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".switchTimePeriod").on("click", function(){
|
|
||||||
let peerTimePeriod = window.localStorage.peerTimePeriod;
|
|
||||||
$(".switchTimePeriod").removeClass("active");
|
|
||||||
$(this).addClass("active");
|
|
||||||
if ($(this).data('time') !== peerTimePeriod){
|
|
||||||
ajaxPostJSON("/api/getPeerDataUsage", {"config": configurations.getConfigurationName(), "peerID": configurations.peerDataUsageChartObj().data.peerID, "interval": $(this).data('time')}, loadPeerDataUsageChartDone);
|
|
||||||
window.localStorage.peerTimePeriod = $(this).data('time');
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edit Configuration
|
|
||||||
*/
|
|
||||||
|
|
||||||
$editConfiguration = $("#edit_configuration");
|
|
||||||
$editConfiguration.on("click", function(){
|
|
||||||
configurations.getConfigurationDetails();
|
|
||||||
configurations.configurationEditModal().toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
$saveConfiguration = $("#editConfigurationBtn");
|
|
||||||
$saveConfiguration.on("click", function(){
|
|
||||||
$(this).html("Saving...")
|
|
||||||
$(this).siblings().hide();
|
|
||||||
$(this).attr("disabled", "disabled");
|
|
||||||
|
|
||||||
let data = {
|
|
||||||
"configurationName": configurations.getConfigurationName(),
|
|
||||||
"ListenPort": $("#editConfigurationListenPort").val(),
|
|
||||||
"PostUp": $("#editConfigurationPostUp").val(),
|
|
||||||
"PostDown": $("#editConfigurationPostDown").val(),
|
|
||||||
"PreDown": $("#editConfigurationPreDown").val(),
|
|
||||||
"PreUp": $("#editConfigurationPreUp").val(),
|
|
||||||
}
|
|
||||||
function done(res){
|
|
||||||
console.log(res);
|
|
||||||
$saveConfiguration.removeAttr("disabled");
|
|
||||||
if (res.status){
|
|
||||||
configurations.configurationEditModal().toggle();
|
|
||||||
configurations.loadPeers("");
|
|
||||||
showToast("Configuration saved");
|
|
||||||
}else{
|
|
||||||
showToast(res.reason);
|
|
||||||
}
|
|
||||||
$saveConfiguration.html("Save");
|
|
||||||
$saveConfiguration.siblings().show();
|
|
||||||
}
|
|
||||||
ajaxPostJSON("/api/saveConfiguration", data, done);
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ==========
|
|
||||||
* Add peers
|
|
||||||
* ==========
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle add peers modal when add button clicked
|
|
||||||
*/
|
|
||||||
document.querySelector(".add_btn").addEventListener("click", () => {
|
|
||||||
configurations.addModal().toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When configuration switch got click
|
|
||||||
*/
|
|
||||||
$(".toggle--switch").on("change", function(){
|
|
||||||
console.log('lol')
|
|
||||||
$(this).addClass("waiting").attr("disabled", "disabled");
|
|
||||||
let id = configurations.getConfigurationName();
|
|
||||||
let status = $(this).prop("checked");
|
|
||||||
let ele = $(this);
|
|
||||||
$.ajax({
|
|
||||||
url: `/switch/${id}`
|
|
||||||
}).done(function(res){
|
|
||||||
if (res.status){
|
|
||||||
if (status){
|
|
||||||
showToast(`${id} is running.`)
|
|
||||||
}else{
|
|
||||||
showToast(`${id} is stopped.`)
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if (status){
|
|
||||||
ele.prop("checked", false)
|
|
||||||
}else{
|
|
||||||
ele.prop("checked", true)
|
|
||||||
}
|
|
||||||
showToast(res.reason, true);
|
|
||||||
$(".index-alert").removeClass("d-none").text(`Configuration toggle failed. Please check the following error message:\n${res.message}`);
|
|
||||||
}
|
|
||||||
ele.removeClass("waiting");
|
|
||||||
ele.removeAttr("disabled");
|
|
||||||
configurations.loadPeers($('#search_peer_textbox').val())
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate Public key when private got change
|
|
||||||
*/
|
|
||||||
document.querySelector("#private_key").addEventListener("change", (event) => {
|
|
||||||
let publicKey = document.querySelector("#public_key");
|
|
||||||
if (event.target.value.length === 44) {
|
|
||||||
publicKey.value = window.wireguard.generatePublicKey(event.target.value);
|
|
||||||
publicKey.setAttribute("disabled", "disabled");
|
|
||||||
} else {
|
|
||||||
publicKey.attributes.removeNamedItem("disabled");
|
|
||||||
publicKey.value = "";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when add modal is show and hide
|
|
||||||
*/
|
|
||||||
$('#add_modal').on('show.bs.modal', function() {
|
|
||||||
configurations.generateKeyPair();
|
|
||||||
configurations.getAvailableIps();
|
|
||||||
}).on('hide.bs.modal', function() {
|
|
||||||
$("#allowed_ips_indicator").html('');
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when user clicked the regenerate button
|
|
||||||
*/
|
|
||||||
$("#re_generate_key").on("click", function() {
|
|
||||||
$("#public_key").attr("disabled", "disabled");
|
|
||||||
$("#re_generate_key i").addClass("rotating");
|
|
||||||
configurations.generateKeyPair();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when user is editing in allowed ips textbox
|
|
||||||
*/
|
|
||||||
$("#allowed_ips").on("keyup", function() {
|
|
||||||
let s = configurations.cleanIp($(this).val());
|
|
||||||
s = s.split(",");
|
|
||||||
if (available_ips.includes(s[s.length - 1])) {
|
|
||||||
$("#allowed_ips_indicator").removeClass().addClass("text-success")
|
|
||||||
.html('<i class="bi bi-check-circle-fill"></i>');
|
|
||||||
} else {
|
|
||||||
$("#allowed_ips_indicator").removeClass().addClass("text-warning")
|
|
||||||
.html('<i class="bi bi-exclamation-circle-fill"></i>');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change peer name when user typing in peer name textbox
|
|
||||||
*/
|
|
||||||
$("#peer_name_textbox").on("keyup", function() {
|
|
||||||
$(".peer_name").html($(this).val());
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When Add Peer button got clicked
|
|
||||||
*/
|
|
||||||
$add_peer.addEventListener("click", function() {
|
|
||||||
let $bulk_add = $("#bulk_add");
|
|
||||||
if ($bulk_add.prop("checked")) {
|
|
||||||
if (!$("#new_add_amount").hasClass("is-invalid")) {
|
|
||||||
configurations.addPeersByBulk();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let $public_key = $("#public_key");
|
|
||||||
let $private_key = $("#private_key");
|
|
||||||
let $allowed_ips = $("#allowed_ips");
|
|
||||||
$allowed_ips.val(configurations.cleanIp($allowed_ips.val()));
|
|
||||||
let $new_add_DNS = $("#new_add_DNS");
|
|
||||||
$new_add_DNS.val(configurations.cleanIp($new_add_DNS.val()));
|
|
||||||
let $new_add_endpoint_allowed_ip = $("#new_add_endpoint_allowed_ip");
|
|
||||||
$new_add_endpoint_allowed_ip.val(configurations.cleanIp($new_add_endpoint_allowed_ip.val()));
|
|
||||||
let $new_add_name = $("#new_add_name");
|
|
||||||
let $new_add_MTU = $("#new_add_MTU");
|
|
||||||
let $new_add_keep_alive = $("#new_add_keep_alive");
|
|
||||||
let $enable_preshare_key = $("#enable_preshare_key");
|
|
||||||
$add_peer.setAttribute("disabled", "disabled");
|
|
||||||
$add_peer.innerHTML = "Adding...";
|
|
||||||
if ($allowed_ips.val() !== "" && $public_key.val() !== "" && $new_add_DNS.val() !== "" && $new_add_endpoint_allowed_ip.val() !== "") {
|
|
||||||
let conf = configurations.getConfigurationName();
|
|
||||||
let data_list = [$private_key, $allowed_ips, $new_add_name, $new_add_DNS, $new_add_endpoint_allowed_ip, $new_add_MTU, $new_add_keep_alive];
|
|
||||||
data_list.forEach((ele) => ele.attr("disabled", "disabled"));
|
|
||||||
$.ajax({
|
|
||||||
method: "POST",
|
|
||||||
url: "/add_peer/" + conf,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
data: JSON.stringify({
|
|
||||||
"private_key": $private_key.val(),
|
|
||||||
"public_key": $public_key.val(),
|
|
||||||
"allowed_ips": $allowed_ips.val(),
|
|
||||||
"name": $new_add_name.val(),
|
|
||||||
"DNS": $new_add_DNS.val(),
|
|
||||||
"endpoint_allowed_ip": $new_add_endpoint_allowed_ip.val(),
|
|
||||||
"MTU": $new_add_MTU.val(),
|
|
||||||
"keep_alive": $new_add_keep_alive.val(),
|
|
||||||
"enable_preshared_key": $enable_preshare_key.prop("checked"),
|
|
||||||
"preshared_key": $enable_preshare_key.val()
|
|
||||||
}),
|
|
||||||
success: function(response) {
|
|
||||||
if (response !== "true") {
|
|
||||||
$("#add_peer_alert").html(response).removeClass("d-none");
|
|
||||||
data_list.forEach((ele) => ele.removeAttr("disabled"));
|
|
||||||
$add_peer.removeAttribute("disabled");
|
|
||||||
$add_peer.innerHTML = "Save";
|
|
||||||
} else {
|
|
||||||
configurations.loadPeers("");
|
|
||||||
data_list.forEach((ele) => ele.removeAttr("disabled"));
|
|
||||||
$("#add_peer_form").trigger("reset");
|
|
||||||
$add_peer.removeAttribute("disabled");
|
|
||||||
$add_peer.innerHTML = "Save";
|
|
||||||
showToast("Add peer successful!");
|
|
||||||
configurations.addModal().toggle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$("#add_peer_alert").html("Please fill in all required box.").removeClass("d-none");
|
|
||||||
$add_peer.removeAttribute("disabled");
|
|
||||||
$add_peer.innerHTML = "Add";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when user is typing the amount of peers they want to add, and will check if the amount is less than 1 or
|
|
||||||
* is larger than the amount of available ips
|
|
||||||
*/
|
|
||||||
$("#new_add_amount").on("keyup", function() {
|
|
||||||
let $bulk_amount_validation = $("#bulk_amount_validation");
|
|
||||||
// $(this).removeClass("is-valid").addClass("is-invalid");
|
|
||||||
if ($(this).val().length > 0) {
|
|
||||||
if (isNaN($(this).val())) {
|
|
||||||
$(this).removeClass("is-valid").addClass("is-invalid");
|
|
||||||
$bulk_amount_validation.html("Please enter a valid integer");
|
|
||||||
} else if ($(this).val() > available_ips.length) {
|
|
||||||
$(this).removeClass("is-valid").addClass("is-invalid");
|
|
||||||
$bulk_amount_validation.html(`Cannot create more than ${available_ips.length} peers.`);
|
|
||||||
} else if ($(this).val() < 1) {
|
|
||||||
$(this).removeClass("is-valid").addClass("is-invalid");
|
|
||||||
$bulk_amount_validation.html("Please enter at least 1 or more.");
|
|
||||||
} else {
|
|
||||||
$(this).removeClass("is-invalid").addClass("is-valid");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$(this).removeClass("is-invalid").removeClass("is-valid");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when user toggled add peers by bulk
|
|
||||||
*/
|
|
||||||
$("#bulk_add").on("change", function() {
|
|
||||||
let hide = $(".non-bulk");
|
|
||||||
let amount = $("#new_add_amount");
|
|
||||||
if ($(this).prop("checked") === true) {
|
|
||||||
for (let i = 0; i < hide.length; i++) {
|
|
||||||
$(hide[i]).attr("disabled", "disabled");
|
|
||||||
}
|
|
||||||
amount.removeAttr("disabled");
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < hide.length; i++) {
|
|
||||||
if ($(hide[i]).attr('id') !== "public_key") {
|
|
||||||
$(hide[i]).removeAttr("disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
amount.attr("disabled", "disabled");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* =======================
|
|
||||||
* Available IP Related
|
|
||||||
* =======================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when available ip modal show and hide
|
|
||||||
*/
|
|
||||||
$("#available_ip_modal").on("show.bs.modal", () => {
|
|
||||||
document.querySelector('#add_modal').classList.add("ip_modal_open");
|
|
||||||
}).on("hidden.bs.modal", () => {
|
|
||||||
document.querySelector('#add_modal').classList.remove("ip_modal_open");
|
|
||||||
let ips = [];
|
|
||||||
let $selected_ip_list = document.querySelector("#selected_ip_list");
|
|
||||||
for (let i = 0; i < $selected_ip_list.childElementCount; i++) {
|
|
||||||
ips.push($selected_ip_list.children[i].dataset.ip);
|
|
||||||
}
|
|
||||||
ips.forEach((ele) => configurations.triggerIp(ele));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When IP Badge got click
|
|
||||||
*/
|
|
||||||
$body.on("click", ".available-ip-badge", function() {
|
|
||||||
$(".available-ip-item[data-ip='" + $(this).data("ip") + "']").removeClass("active");
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When available ip item got click
|
|
||||||
*/
|
|
||||||
$body.on("click", ".available-ip-item", function() {
|
|
||||||
configurations.triggerIp($(this).data("ip"));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When search IP button got clicked
|
|
||||||
*/
|
|
||||||
$("#search_available_ip").on("click", function() {
|
|
||||||
configurations.ipModal().toggle();
|
|
||||||
let $allowed_ips = document.querySelector("#allowed_ips");
|
|
||||||
if ($allowed_ips.value.length > 0) {
|
|
||||||
let s = $allowed_ips.value.split(",");
|
|
||||||
for (let i = 0; i < s.length; i++) {
|
|
||||||
s[i] = s[i].trim();
|
|
||||||
configurations.triggerIp(s[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).tooltip();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When confirm IP is clicked
|
|
||||||
*/
|
|
||||||
$("#confirm_ip").on("click", () => {
|
|
||||||
configurations.ipModal().toggle();
|
|
||||||
let ips = [];
|
|
||||||
let $selected_ip_list = $("#selected_ip_list");
|
|
||||||
$selected_ip_list.children().each(function() {
|
|
||||||
ips.push($(this).data("ip"));
|
|
||||||
});
|
|
||||||
$("#allowed_ips").val(ips.join(", "));
|
|
||||||
ips.forEach((ele) => configurations.triggerIp(ele));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* =======
|
|
||||||
* QR Code
|
|
||||||
* =======
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the QR-code button got clicked on each peer
|
|
||||||
*/
|
|
||||||
$body.on("click", ".btn-qrcode-peer", function() {
|
|
||||||
let src = $(this).data('imgsrc');
|
|
||||||
$.ajax({
|
|
||||||
"url": src,
|
|
||||||
"method": "GET"
|
|
||||||
}).done(function(res) {
|
|
||||||
$("#qrcode_img").attr('src', res);
|
|
||||||
configurations.qrcodeModal().toggle();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ===========
|
|
||||||
* Delete Peer
|
|
||||||
* ===========
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the delete button got clicked on each peer
|
|
||||||
*/
|
|
||||||
$body.on("click", ".btn-delete-peer", function() {
|
|
||||||
let peer_id = $(this).data('peer-id')
|
|
||||||
$("#delete_peer").data("peer-id", peer_id);
|
|
||||||
configurations.deleteModal().toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
$body.on("click", ".btn-lock-peer", function() {
|
|
||||||
configurations.toggleAccess($(this).data('peer-id'), configurations.getConfigurationName());
|
|
||||||
if ($(this).hasClass("lock")) {
|
|
||||||
console.log($(this).data("peer-name"))
|
|
||||||
showToast(`Enabled ${$(this).children().data("peer-name")}`)
|
|
||||||
$(this).removeClass("lock")
|
|
||||||
$(this).children().tooltip('hide').attr('data-original-title', 'Peer enabled. Click to disable peer.').tooltip('show');
|
|
||||||
} else {
|
|
||||||
// Currently unlocked
|
|
||||||
showToast(`Disabled ${$(this).children().data("peer-name")}`)
|
|
||||||
$(this).addClass("lock");
|
|
||||||
$(this).children().tooltip('hide').attr('data-original-title', 'Peer disabled. Click to enable peer.').tooltip('show');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the confirm delete button clicked
|
|
||||||
*/
|
|
||||||
$("#delete_peer").on("click", function() {
|
|
||||||
$(this).attr("disabled", "disabled");
|
|
||||||
$(this).html("Deleting...");
|
|
||||||
let config = configurations.getConfigurationName();
|
|
||||||
let peer_ids = [$(this).data("peer-id")];
|
|
||||||
configurations.deletePeers(config, peer_ids);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* =============
|
|
||||||
* Peer Settings
|
|
||||||
* =============
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when setting button got clicked for each peer
|
|
||||||
*/
|
|
||||||
$body.on("click", ".btn-setting-peer", function() {
|
|
||||||
// configurations.startProgressBar();
|
|
||||||
let peer_id = $(this).data("peer-id");
|
|
||||||
$("#save_peer_setting").attr("peer_id", peer_id);
|
|
||||||
$.ajax({
|
|
||||||
method: "POST",
|
|
||||||
url: "/get_peer_data/" + configurations.getConfigurationName(),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
data: JSON.stringify({ "id": peer_id }),
|
|
||||||
success: function(response) {
|
|
||||||
let peer_name = ((response.name === "") ? "Untitled" : response.name);
|
|
||||||
$("#setting_modal .peer_name").html(peer_name);
|
|
||||||
$("#setting_modal #peer_name_textbox").val(response.name);
|
|
||||||
$("#setting_modal #peer_private_key_textbox").val(response.private_key);
|
|
||||||
$("#setting_modal #peer_DNS_textbox").val(response.DNS);
|
|
||||||
$("#setting_modal #peer_allowed_ip_textbox").val(response.allowed_ip);
|
|
||||||
$("#setting_modal #peer_endpoint_allowed_ips").val(response.endpoint_allowed_ip);
|
|
||||||
$("#setting_modal #peer_mtu").val(response.mtu);
|
|
||||||
$("#setting_modal #peer_keep_alive").val(response.keep_alive);
|
|
||||||
$("#setting_modal #peer_preshared_key_textbox").val(response.preshared_key);
|
|
||||||
configurations.settingModal().toggle();
|
|
||||||
configurations.endProgressBar();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when setting modal is closing
|
|
||||||
*/
|
|
||||||
$('#setting_modal').on('hidden.bs.modal', function() {
|
|
||||||
$("#setting_peer_alert").addClass("d-none");
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when private key text box in setting modal got changed
|
|
||||||
*/
|
|
||||||
$("#peer_private_key_textbox").on("change", function() {
|
|
||||||
let $save_peer_setting = $("#save_peer_setting");
|
|
||||||
if ($(this).val().length > 0) {
|
|
||||||
$.ajax({
|
|
||||||
"url": "/check_key_match/" + configurations.getConfigurationName(),
|
|
||||||
"method": "POST",
|
|
||||||
"headers": { "Content-Type": "application/json" },
|
|
||||||
"data": JSON.stringify({
|
|
||||||
"private_key": $("#peer_private_key_textbox").val(),
|
|
||||||
"public_key": $save_peer_setting.attr("peer_id")
|
|
||||||
})
|
|
||||||
}).done(function(res) {
|
|
||||||
if (res.status === "failed") {
|
|
||||||
$("#setting_peer_alert").html(res.status).removeClass("d-none");
|
|
||||||
} else {
|
|
||||||
$("#setting_peer_alert").addClass("d-none");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When save peer setting button got clicked
|
|
||||||
*/
|
|
||||||
$("#save_peer_setting").on("click", function() {
|
|
||||||
$(this).attr("disabled", "disabled");
|
|
||||||
$(this).html("Saving...");
|
|
||||||
let $peer_DNS_textbox = $("#peer_DNS_textbox");
|
|
||||||
let $peer_allowed_ip_textbox = $("#peer_allowed_ip_textbox");
|
|
||||||
let $peer_endpoint_allowed_ips = $("#peer_endpoint_allowed_ips");
|
|
||||||
let $peer_name_textbox = $("#peer_name_textbox");
|
|
||||||
let $peer_private_key_textbox = $("#peer_private_key_textbox");
|
|
||||||
let $peer_preshared_key_textbox = $("#peer_preshared_key_textbox");
|
|
||||||
let $peer_mtu = $("#peer_mtu");
|
|
||||||
let $peer_keep_alive = $("#peer_keep_alive");
|
|
||||||
|
|
||||||
if ($peer_DNS_textbox.val() !== "" &&
|
|
||||||
$peer_allowed_ip_textbox.val() !== "" && $peer_endpoint_allowed_ips.val() !== "") {
|
|
||||||
let peer_id = $(this).attr("peer_id");
|
|
||||||
let conf_id = $(this).attr("conf_id");
|
|
||||||
let data_list = [$peer_name_textbox, $peer_DNS_textbox, $peer_private_key_textbox, $peer_preshared_key_textbox, $peer_allowed_ip_textbox, $peer_endpoint_allowed_ips, $peer_mtu, $peer_keep_alive];
|
|
||||||
data_list.forEach((ele) => ele.attr("disabled", "disabled"));
|
|
||||||
$.ajax({
|
|
||||||
method: "POST",
|
|
||||||
url: "/save_peer_setting/" + conf_id,
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
data: JSON.stringify({
|
|
||||||
id: peer_id,
|
|
||||||
name: $peer_name_textbox.val(),
|
|
||||||
DNS: $peer_DNS_textbox.val(),
|
|
||||||
private_key: $peer_private_key_textbox.val(),
|
|
||||||
allowed_ip: $peer_allowed_ip_textbox.val(),
|
|
||||||
endpoint_allowed_ip: $peer_endpoint_allowed_ips.val(),
|
|
||||||
MTU: $peer_mtu.val(),
|
|
||||||
keep_alive: $peer_keep_alive.val(),
|
|
||||||
preshared_key: $peer_preshared_key_textbox.val()
|
|
||||||
}),
|
|
||||||
success: function(response) {
|
|
||||||
if (response.status === "failed") {
|
|
||||||
$("#setting_peer_alert").html(response.msg).removeClass("d-none");
|
|
||||||
} else {
|
|
||||||
configurations.settingModal().toggle();
|
|
||||||
configurations.loadPeers($('#search_peer_textbox').val());
|
|
||||||
$('#alertToast').toast('show');
|
|
||||||
$('#alertToast .toast-body').html("Peer Saved!");
|
|
||||||
}
|
|
||||||
$("#save_peer_setting").removeAttr("disabled").html("Save");
|
|
||||||
data_list.forEach((ele) => ele.removeAttr("disabled"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$("#setting_peer_alert").html("Please fill in all required box.").removeClass("d-none");
|
|
||||||
$("#save_peer_setting").removeAttr("disabled").html("Save");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle show or hide for the private key textbox in the setting modal
|
|
||||||
*/
|
|
||||||
$(".peer_private_key_textbox_switch").on("click", function() {
|
|
||||||
let $peer_private_key_textbox = $("#peer_private_key_textbox");
|
|
||||||
let mode = (($peer_private_key_textbox.attr('type') === 'password') ? "text" : "password");
|
|
||||||
let icon = (($peer_private_key_textbox.attr('type') === 'password') ? "bi bi-eye-slash-fill" : "bi bi-eye-fill");
|
|
||||||
$peer_private_key_textbox.attr('type', mode);
|
|
||||||
$(".peer_private_key_textbox_switch i").removeClass().addClass(icon);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ===========
|
|
||||||
* Search Peer
|
|
||||||
* ===========
|
|
||||||
*/
|
|
||||||
|
|
||||||
let typingTimer; // Timeout object
|
|
||||||
let doneTypingInterval = 200; // Timeout interval
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when the user keyup and keydown on the search textbox
|
|
||||||
*/
|
|
||||||
$('#search_peer_textbox').on('keyup', function() {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
typingTimer = setTimeout(() => {
|
|
||||||
configurations.loadPeers($(this).val());
|
|
||||||
}, doneTypingInterval);
|
|
||||||
}).on('keydown', function() {
|
|
||||||
clearTimeout(typingTimer);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manage Peers
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when sort peers changed
|
|
||||||
*/
|
|
||||||
$body.on("change", "#sort_by_dropdown", function() {
|
|
||||||
$.ajax({
|
|
||||||
method: "POST",
|
|
||||||
data: JSON.stringify({ 'sort': $("#sort_by_dropdown option:selected").val() }),
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
url: "/update_dashboard_sort",
|
|
||||||
success: function() {
|
|
||||||
configurations.loadPeers($('#search_peer_textbox').val());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle copy public key
|
|
||||||
*/
|
|
||||||
$body.on("mouseenter", ".key", function() {
|
|
||||||
let label = $(this).parent().siblings().children()[1];
|
|
||||||
label.style.opacity = "100";
|
|
||||||
}).on("mouseout", ".key", function() {
|
|
||||||
let label = $(this).parent().siblings().children()[1];
|
|
||||||
label.style.opacity = "0";
|
|
||||||
setTimeout(function() {
|
|
||||||
label.innerHTML = "CLICK TO COPY";
|
|
||||||
}, 200);
|
|
||||||
}).on("click", ".key", function() {
|
|
||||||
let label = $(this).parent().siblings().children()[1];
|
|
||||||
configurations.copyToClipboard($(this));
|
|
||||||
label.innerHTML = "COPIED!";
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when interval button got clicked
|
|
||||||
*/
|
|
||||||
$body.on("click", ".update_interval", function() {
|
|
||||||
$(".interval-btn-group button").removeClass("active");
|
|
||||||
let _new = $(this);
|
|
||||||
_new.addClass("active");
|
|
||||||
let interval = $(this).data("refresh-interval");
|
|
||||||
if ([5000, 10000, 30000, 60000].includes(interval)) {
|
|
||||||
configurations.updateRefreshInterval(interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// $.ajax({
|
|
||||||
// method:"POST",
|
|
||||||
// data: "interval="+$(this).data("refresh-interval"),
|
|
||||||
// url: "/update_dashboard_refresh_interval",
|
|
||||||
// success: function (res){
|
|
||||||
// configurations.updateRefreshInterval(res, interval);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when refresh button got clicked
|
|
||||||
*/
|
|
||||||
$body.on("click", ".refresh", function() {
|
|
||||||
configurations.loadPeers($('#search_peer_textbox').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle when display mode button got clicked
|
|
||||||
*/
|
|
||||||
$body.on("click", ".display_mode", function() {
|
|
||||||
$(".display-btn-group button").removeClass("active");
|
|
||||||
$(this).addClass("active");
|
|
||||||
window.localStorage.setItem("displayMode", $(this).data("display-mode"));
|
|
||||||
configurations.updateDisplayMode();
|
|
||||||
if ($(this).data("display-mode") === "list") {
|
|
||||||
Array($(".peer_list").children()).forEach(function(child) {
|
|
||||||
$(child).removeClass().addClass("col-12");
|
|
||||||
});
|
|
||||||
showToast("Displaying as List");
|
|
||||||
} else {
|
|
||||||
Array($(".peer_list").children()).forEach(function(child) {
|
|
||||||
$(child).removeClass().addClass("col-sm-6 col-lg-4");
|
|
||||||
});
|
|
||||||
showToast("Displaying as Grids");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* =================
|
|
||||||
* Configuration Menu
|
|
||||||
* =================
|
|
||||||
*/
|
|
||||||
let $setting_btn_menu = $(".setting_btn_menu");
|
|
||||||
$setting_btn_menu.css("top", ($setting_btn_menu.height() + 54) * (-1));
|
|
||||||
let $setting_btn = $(".setting_btn");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the menu button got clicked
|
|
||||||
*/
|
|
||||||
$setting_btn.on("click", function() {
|
|
||||||
if ($setting_btn_menu.hasClass("show")) {
|
|
||||||
$setting_btn_menu.removeClass("showing");
|
|
||||||
setTimeout(function() {
|
|
||||||
$setting_btn_menu.removeClass("show");
|
|
||||||
}, 201);
|
|
||||||
} else {
|
|
||||||
$setting_btn_menu.addClass("show");
|
|
||||||
setTimeout(function() {
|
|
||||||
$setting_btn_menu.addClass("showing");
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whenever the user clicked, if it is outside the menu and the menu is opened, hide the menu
|
|
||||||
*/
|
|
||||||
$("html").on("click", function(r) {
|
|
||||||
if (document.querySelector(".setting_btn") !== r.target) {
|
|
||||||
if (!document.querySelector(".setting_btn").contains(r.target)) {
|
|
||||||
if (!document.querySelector(".setting_btn_menu").contains(r.target)) {
|
|
||||||
$setting_btn_menu.removeClass("showing");
|
|
||||||
setTimeout(function() {
|
|
||||||
$setting_btn_menu.removeClass("show");
|
|
||||||
}, 310);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ====================
|
|
||||||
* Delete Peers by Bulk
|
|
||||||
* ====================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When delete peers by bulk clicked
|
|
||||||
*/
|
|
||||||
$("#delete_peers_by_bulk_btn").on("click", () => {
|
|
||||||
let $delete_bulk_modal_list = $("#delete_bulk_modal .list-group");
|
|
||||||
$delete_bulk_modal_list.html('');
|
|
||||||
peers.forEach((peer) => {
|
|
||||||
let name;
|
|
||||||
if (peer.name === "") { name = "Untitled Peer"; } else { name = peer.name; }
|
|
||||||
$delete_bulk_modal_list.append('<a class="list-group-item list-group-item-action delete-bulk-peer-item" style="cursor: pointer" data-id="' +
|
|
||||||
peer.id + '" data-name="' + name + '">' + name + '<br><code>' + peer.id + '</code></a>');
|
|
||||||
});
|
|
||||||
configurations.deleteBulkModal().toggle();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the item or tag of delete peers by bulk got clicked
|
|
||||||
*/
|
|
||||||
$body.on("click", ".delete-bulk-peer-item", function() {
|
|
||||||
configurations.toggleDeleteByBulkIP($(this));
|
|
||||||
}).on("click", ".delete-peer-bulk-badge", function() {
|
|
||||||
configurations.toggleDeleteByBulkIP($(".delete-bulk-peer-item[data-id='" + $(this).data("id") + "']"));
|
|
||||||
});
|
|
||||||
|
|
||||||
let $selected_peer_list = document.getElementById("selected_peer_list");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The change observer to observe when user choose 1 or more peers to delete
|
|
||||||
* @type {MutationObserver}
|
|
||||||
*/
|
|
||||||
let changeObserver = new MutationObserver(function() {
|
|
||||||
if ($selected_peer_list.hasChildNodes()) {
|
|
||||||
$("#confirm_delete_bulk_peers").removeAttr("disabled");
|
|
||||||
} else {
|
|
||||||
$("#confirm_delete_bulk_peers").attr("disabled", "disabled");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
changeObserver.observe($selected_peer_list, {
|
|
||||||
attributes: true,
|
|
||||||
childList: true,
|
|
||||||
characterData: true
|
|
||||||
});
|
|
||||||
|
|
||||||
let confirm_delete_bulk_peers_interval;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the user clicked the delete button in the delete peers by bulk
|
|
||||||
*/
|
|
||||||
$("#confirm_delete_bulk_peers").on("click", function() {
|
|
||||||
let btn = $(this);
|
|
||||||
if (confirm_delete_bulk_peers_interval !== undefined) {
|
|
||||||
clearInterval(confirm_delete_bulk_peers_interval);
|
|
||||||
confirm_delete_bulk_peers_interval = undefined;
|
|
||||||
btn.html("Delete");
|
|
||||||
} else {
|
|
||||||
let timer = 5;
|
|
||||||
btn.html(`Deleting in ${timer} secs... Click to cancel`);
|
|
||||||
confirm_delete_bulk_peers_interval = setInterval(function() {
|
|
||||||
timer -= 1;
|
|
||||||
btn.html(`Deleting in ${timer} secs... Click to cancel`);
|
|
||||||
if (timer === 0) {
|
|
||||||
btn.html(`Deleting...`);
|
|
||||||
btn.attr("disabled", "disabled");
|
|
||||||
let ips = [];
|
|
||||||
$selected_peer_list.childNodes.forEach((ele) => ips.push(ele.dataset.id));
|
|
||||||
configurations.deletePeers(configurations.getConfigurationName(), ips);
|
|
||||||
clearInterval(confirm_delete_bulk_peers_interval);
|
|
||||||
confirm_delete_bulk_peers_interval = undefined;
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select all peers to delete
|
|
||||||
*/
|
|
||||||
$("#select_all_delete_bulk_peers").on("click", function() {
|
|
||||||
$(".delete-bulk-peer-item").each(function() {
|
|
||||||
if (!$(this).hasClass("active")) {
|
|
||||||
configurations.toggleDeleteByBulkIP($(this));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When delete peers by bulk window is hidden
|
|
||||||
*/
|
|
||||||
$(configurations.deleteBulkModal()._element).on("hidden.bs.modal", function() {
|
|
||||||
$(".delete-bulk-peer-item").each(function() {
|
|
||||||
if ($(this).hasClass("active")) {
|
|
||||||
configurations.toggleDeleteByBulkIP($(this));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ==============
|
|
||||||
* Download Peers
|
|
||||||
* ==============
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the download peers button got clicked
|
|
||||||
*/
|
|
||||||
$body.on("click", ".btn-download-peer", function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
let link = $(this).attr("href");
|
|
||||||
$.ajax({
|
|
||||||
"url": link,
|
|
||||||
"method": "GET",
|
|
||||||
success: function(res) {
|
|
||||||
configurations.downloadOneConfig(res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the download all peers got clicked
|
|
||||||
*/
|
|
||||||
$("#download_all_peers").on("click", function() {
|
|
||||||
$.ajax({
|
|
||||||
"url": `/download_all/${configurations.getConfigurationName()}`,
|
|
||||||
"method": "GET",
|
|
||||||
success: function(res) {
|
|
||||||
if (res.peers.length > 0) {
|
|
||||||
window.wireguard.generateZipFiles(res);
|
|
||||||
showToast("Peers' zip file download successful!");
|
|
||||||
} else {
|
|
||||||
showToast("Oops! There are no peer can be download.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
let emptyInputFeedback = "Can't leave empty";
|
|
||||||
$('[data-toggle="tooltip"]').tooltip()
|
|
||||||
let $add_configuration = $("#add_configuration");
|
|
||||||
|
|
||||||
let addConfigurationModal = $("#addConfigurationModal");
|
|
||||||
$(".bottomNavHome").addClass("active");
|
|
||||||
|
|
||||||
|
|
||||||
addConfigurationModal.modal({
|
|
||||||
keyboard: false,
|
|
||||||
backdrop: 'static',
|
|
||||||
show: false
|
|
||||||
});
|
|
||||||
|
|
||||||
addConfigurationModal.on("hidden.bs.modal", function(){
|
|
||||||
$("#add_configuration_form").trigger("reset");
|
|
||||||
$("#add_configuration_form input").removeClass("is-valid").removeClass("is-invalid");
|
|
||||||
$(".addConfigurationAvailableIPs").text("N/A");
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".toggle--switch").on("change", function(){
|
|
||||||
$(this).addClass("waiting").attr("disabled", "disabled");
|
|
||||||
let id = $(this).data("conf-id");
|
|
||||||
let status = $(this).prop("checked");
|
|
||||||
let ele = $(this);
|
|
||||||
let label = $(this).siblings("label");
|
|
||||||
$.ajax({
|
|
||||||
url: `/switch/${id}`
|
|
||||||
}).done(function(res){
|
|
||||||
let dot = $(`div[data-conf-id="${id}"] .dot`);
|
|
||||||
if (res.status){
|
|
||||||
if (status){
|
|
||||||
dot.removeClass("dot-stopped").addClass("dot-running");
|
|
||||||
dot.siblings().text("Running");
|
|
||||||
showToast(`${id} is running.`);
|
|
||||||
}else{
|
|
||||||
dot.removeClass("dot-running").addClass("dot-stopped");
|
|
||||||
showToast(`${id} is stopped.`);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
ele.parents().children(".card-message").html(`<pre class="index-alert">Configuration toggle failed. Please check the following error message:<br><code>${res.message}</code></pre>`)
|
|
||||||
showToast(`${id} toggled failed.`, true);
|
|
||||||
if (status){
|
|
||||||
ele.prop("checked", false)
|
|
||||||
}else{
|
|
||||||
ele.prop("checked", true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ele.removeClass("waiting").removeAttr("disabled");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".sb-home-url").addClass("active");
|
|
||||||
|
|
||||||
$(".card-body").on("click", function(handle){
|
|
||||||
if ($(handle.target).attr("class") !== "toggleLabel" && $(handle.target).attr("class") !== "toggle--switch") {
|
|
||||||
let c = $(".card");
|
|
||||||
for (let i of c){
|
|
||||||
if (i != $(this).parent()[0]){
|
|
||||||
$(i).css("transition", "ease-in-out 0.3s").css("opacity", "0.5")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.open($(this).find("a").attr("href"), "_self");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function genKeyPair(){
|
|
||||||
let keyPair = window.wireguard.generateKeypair();
|
|
||||||
$("#addConfigurationPrivateKey").val(keyPair.privateKey).data("checked", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#reGeneratePrivateKey").on("click", function() {
|
|
||||||
genKeyPair();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#toggleAddConfiguration").on("click", function(){
|
|
||||||
addConfigurationModal.modal('toggle');
|
|
||||||
genKeyPair()
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#addConfigurationPrivateKey").on("change", function() {
|
|
||||||
$privateKey = $(this);
|
|
||||||
$privateKeyFeedback = $("#addConfigurationPrivateKeyFeedback");
|
|
||||||
if ($privateKey.val().length != 44){
|
|
||||||
invalidInput($privateKey, $privateKeyFeedback, "Invalid length");
|
|
||||||
}else{
|
|
||||||
validInput($privateKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function ajaxPostJSON(url, data, doneFunc){
|
|
||||||
$.ajax({
|
|
||||||
url: url,
|
|
||||||
method: "POST",
|
|
||||||
data: JSON.stringify(data),
|
|
||||||
headers: {"Content-Type": "application/json"}
|
|
||||||
}).done(function (res) {
|
|
||||||
doneFunc(res);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function validInput(input){
|
|
||||||
input.removeClass("is-invalid").addClass("is-valid").removeAttr("disabled").data("checked", true);
|
|
||||||
}
|
|
||||||
function invalidInput(input, feedback, text){
|
|
||||||
input.removeClass("is-valid").addClass("is-invalid").removeAttr("disabled").data("checked", false);
|
|
||||||
feedback.addClass("invalid-feedback").text(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPort($this){
|
|
||||||
let port = $this;
|
|
||||||
port.attr("disabled", "disabled");
|
|
||||||
let portFeedback = $("#addConfigurationListenPortFeedback");
|
|
||||||
if (port.val().length == 0){
|
|
||||||
invalidInput(port, portFeedback, emptyInputFeedback)
|
|
||||||
}else{
|
|
||||||
function done(res){
|
|
||||||
if(res.status){
|
|
||||||
validInput(port);
|
|
||||||
}else{
|
|
||||||
invalidInput(port, portFeedback, res.reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ajaxPostJSON('/api/addConfigurationPortCheck', {"port": port.val()}, done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$("#addConfigurationListenPort").on("change", function(){
|
|
||||||
checkPort($(this));
|
|
||||||
})
|
|
||||||
|
|
||||||
function checkAddress($this){
|
|
||||||
let address = $this;
|
|
||||||
address.attr("disabled", "disabled");
|
|
||||||
let availableIPs = $(".addConfigurationAvailableIPs");
|
|
||||||
let addressFeedback = $("#addConfigurationAddressFeedback");
|
|
||||||
if (address.val().length == 0){
|
|
||||||
invalidInput(address, addressFeedback, emptyInputFeedback);
|
|
||||||
availableIPs.html(`N/A`);
|
|
||||||
}else{
|
|
||||||
function done(res){
|
|
||||||
if (res.status){
|
|
||||||
availableIPs.html(`<strong>${res.data}</strong>`);
|
|
||||||
validInput(address);
|
|
||||||
}else{
|
|
||||||
invalidInput(address, addressFeedback, res.reason);
|
|
||||||
availableIPs.html(`N/A`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ajaxPostJSON("/api/addConfigurationAddressCheck", {"address": address.val()}, done)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$("#addConfigurationAddress").on("change", function(){
|
|
||||||
checkAddress($(this));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function checkName($this){
|
|
||||||
let name = $this;
|
|
||||||
let nameFeedback = $("#addConfigurationNameFeedback");
|
|
||||||
name.val(name.val().replace(/\s/g,'')).attr("disabled", "disabled");
|
|
||||||
if (name.val().length === 0){
|
|
||||||
invalidInput(name, nameFeedback, emptyInputFeedback)
|
|
||||||
}else{
|
|
||||||
function done(res){
|
|
||||||
if (res.status){
|
|
||||||
validInput(name);
|
|
||||||
}else{
|
|
||||||
invalidInput(name, nameFeedback, res.reason);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ajaxPostJSON("/api/addConfigurationNameCheck", {"name": name.val()}, done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$("#addConfigurationName").on("change", function(){
|
|
||||||
checkName($(this));
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$("#addConfigurationBtn").on("click", function(){
|
|
||||||
let btn = $(this);
|
|
||||||
let input = $("#add_configuration_form input");
|
|
||||||
let filled = true;
|
|
||||||
for (let i = 0; i < input.length; i++){
|
|
||||||
let $i = $(input[i]);
|
|
||||||
if ($i.attr("required") != undefined){
|
|
||||||
if ($i.val().length == 0 && $i.attr("name") !== "addConfigurationPrivateKey"){
|
|
||||||
invalidInput($i, $i.siblings(".input-feedback"), emptyInputFeedback);
|
|
||||||
filled = false;
|
|
||||||
}
|
|
||||||
if ($i.val().length != 44 && $i.attr("name") == "addConfigurationPrivateKey"){
|
|
||||||
invalidInput($i, $i.siblings(".input-feedback"), "Invalid length");
|
|
||||||
filled = false;
|
|
||||||
}
|
|
||||||
if (!$i.data("checked")){
|
|
||||||
filled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (filled){
|
|
||||||
$("#addConfigurationModal .modal-footer .btn").hide();
|
|
||||||
$(".addConfigurationStatus").removeClass("d-none");
|
|
||||||
let data = {};
|
|
||||||
let q = [];
|
|
||||||
for (let i = 0; i < input.length; i++){
|
|
||||||
let $i = $(input[i]);
|
|
||||||
data[$i.attr("name")] = $i.val();
|
|
||||||
q.push($i.attr("name"));
|
|
||||||
}
|
|
||||||
let done = (res) => {
|
|
||||||
let name = res.data;
|
|
||||||
$(".addConfigurationAddStatus").removeClass("text-primary").addClass("text-success").html(`<i class="bi bi-check-circle-fill"></i> ${name} added successfully.`);
|
|
||||||
if (res.status){
|
|
||||||
setTimeout(() => {
|
|
||||||
$(".addConfigurationToggleStatus").removeClass("waiting").html(`<div class="spinner-border spinner-border-sm" role="status"></div> Toggle Configuration`)
|
|
||||||
$.ajax({
|
|
||||||
url: `/switch/${name}`
|
|
||||||
}).done(function(res){
|
|
||||||
if (res.status){
|
|
||||||
$(".addConfigurationToggleStatus").removeClass("text-primary").addClass("text-success").html(`<i class="bi bi-check-circle-fill"></i> Toggle Successfully. Refresh in 5 seconds.`);
|
|
||||||
setTimeout(() => {
|
|
||||||
$(".addConfigurationToggleStatus").text("Refeshing...")
|
|
||||||
location.reload();
|
|
||||||
}, 5000);
|
|
||||||
}else{
|
|
||||||
$(".addConfigurationToggleStatus").removeClass("text-primary").addClass("text-danger").html(`<i class="bi bi-x-circle-fill"></i> ${name} toggle failed.`)
|
|
||||||
$("#addCconfigurationAlertMessage").removeClass("d-none").html(`${name} toggle failed. Please check the following error message:<br>${res.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
}else{
|
|
||||||
$(".addConfigurationStatus").removeClass("text-primary").addClass("text-danger").html(`<i class="bi bi-x-circle-fill"></i> ${name} adding failed.`)
|
|
||||||
$("#addCconfigurationAlert").removeClass("d-none").children(".alert-body").text(res.reason);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ajaxPostJSON("/api/addConfiguration", data, done);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
let wrapper = $(".bottomNavWrapper");
|
|
||||||
$(".bottomNavConfigs").on("click", function(){
|
|
||||||
let subNav = $(this).children(".subNav");
|
|
||||||
subNav.removeClass("animate__fadeOutDown").addClass("active animate__fadeInUp");
|
|
||||||
wrapper.fadeIn();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".bottomNavHome").on("click", function(){
|
|
||||||
window.location.replace('/')
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".bottomNavSettings").on("click", function(){
|
|
||||||
window.location.replace('/settings')
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function hideBottomSubNav(){
|
|
||||||
$(".bottomNavButton .subNav").removeClass("animate__fadeInUp").addClass("animate__fadeOutDown");
|
|
||||||
wrapper.fadeOut();
|
|
||||||
setTimeout(function(){
|
|
||||||
$(".bottomNavButton .subNav").removeClass("active");
|
|
||||||
},350)
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.on("click", function(){
|
|
||||||
hideBottomSubNav();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$(".bottomNavMore").on("click", function(){
|
|
||||||
let subNav = $(this).children(".subNav");
|
|
||||||
subNav.removeClass("animate__fadeOutDown").addClass("active animate__fadeInUp");
|
|
||||||
wrapper.fadeIn();
|
|
||||||
});
|
|
||||||
|
|
||||||
// $(".bottomNavButton .nav-conf-link").on("click", function(){
|
|
||||||
// hideBottomSubNav();
|
|
||||||
// })
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
$(".sb-settings-url").addClass("active")
|
|
||||||
$(".confirm_modal").click(function () {
|
|
||||||
$(".app_new_ip").html($("#app_ip")[0].value)
|
|
||||||
$(".app_new_port").html($("#app_port")[0].value)
|
|
||||||
})
|
|
||||||
|
|
||||||
$(".confirm_restart").click(function () {
|
|
||||||
$(".cancel_restart").remove()
|
|
||||||
countdown = 7;
|
|
||||||
$.post('/update_app_ip_port', $('.update_app_ip_port').serialize())
|
|
||||||
url = $("#app_ip")[0].value + ":" + $("#app_port")[0].value;
|
|
||||||
$(".confirm_restart").attr("disabled", "disabled")
|
|
||||||
setInterval(function () {
|
|
||||||
if (countdown === 0) {
|
|
||||||
window.location.replace("http://" + url);
|
|
||||||
}
|
|
||||||
$(".confirm_restart").html("Redirecting you in " + countdown + " seconds.")
|
|
||||||
countdown--;
|
|
||||||
}, 1000)
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".change_path").click(function () {
|
|
||||||
$(this).attr("disabled", "disabled");
|
|
||||||
countdown = 5;
|
|
||||||
setInterval(function () {
|
|
||||||
if (countdown === 0) {
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
$(".change_path").html("Redirecting you in " + countdown + " seconds.")
|
|
||||||
countdown--;
|
|
||||||
}, 1000)
|
|
||||||
$.post('/update_wg_conf_path', $('.update_wg_conf_path').serialize())
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".bottomNavSettings").addClass("active");
|
|
||||||
|
|
||||||
|
|
||||||
$(".theme-switch-btn").on("click", function(){
|
|
||||||
if (!$(this).hasClass("active")){
|
|
||||||
let theme = $(this).data("theme");
|
|
||||||
$(".theme-switch-btn").removeClass("active");
|
|
||||||
$(this).addClass("active");
|
|
||||||
$.ajax({
|
|
||||||
method: "POST",
|
|
||||||
url: "/api/settings/setTheme",
|
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
data: JSON.stringify({"theme": theme})
|
|
||||||
}).done(function(res){
|
|
||||||
if (res.status == true){
|
|
||||||
if (theme == "light"){
|
|
||||||
$("#darkThemeCSS").remove();
|
|
||||||
showToast("Switched to light theme");
|
|
||||||
}else{
|
|
||||||
$("head").append('<link rel="stylesheet" type="text/css" href="/static/css/theme/dark.min.css" id="darkThemeCSS">');
|
|
||||||
showToast("Switched to dark theme");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
$(".sb-settings-url").addClass("active");$(".confirm_modal").click(function(){$(".app_new_ip").html($("#app_ip")[0].value);$(".app_new_port").html($("#app_port")[0].value)});$(".confirm_restart").click(function(){$(".cancel_restart").remove();countdown=7;$.post("/update_app_ip_port",$(".update_app_ip_port").serialize());url=$("#app_ip")[0].value+":"+$("#app_port")[0].value;$(".confirm_restart").attr("disabled","disabled");setInterval(function(){if(countdown===0){window.location.replace("http://"+url)}$(".confirm_restart").html("Redirecting you in "+countdown+" seconds.");countdown--},1e3)});$(".change_path").click(function(){$(this).attr("disabled","disabled");countdown=5;setInterval(function(){if(countdown===0){location.reload()}$(".change_path").html("Redirecting you in "+countdown+" seconds.");countdown--},1e3);$.post("/update_wg_conf_path",$(".update_wg_conf_path").serialize())});$(".bottomNavSettings").addClass("active");$(".theme-switch-btn").on("click",function(){if(!$(this).hasClass("active")){let theme=$(this).data("theme");$(".theme-switch-btn").removeClass("active");$(this).addClass("active");$.ajax({method:"POST",url:"/api/settings/setTheme",headers:{"Content-Type":"application/json"},data:JSON.stringify({theme:theme})}).done(function(res){if(res.status==true){if(theme=="light"){$("#darkThemeCSS").remove();showToast("Switched to light theme")}else{$("head").append('<link rel="stylesheet" type="text/css" href="/static/css/theme/dark.min.css" id="darkThemeCSS">');showToast("Switched to dark theme")}}})}});
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* tools.js - Copyright(C) 2021 Donald Zou [https://github.com/donaldzou]
|
|
||||||
*/
|
|
||||||
|
|
||||||
$(".ip_dropdown").on("change",function (){
|
|
||||||
$(".modal.show .btn").removeAttr("disabled");
|
|
||||||
});
|
|
||||||
|
|
||||||
$(".conf_dropdown").on("change", function (){
|
|
||||||
$(".modal.show .ip_dropdown").html('<option value="none" selected="selected" disabled>Loading...');
|
|
||||||
$.ajax({
|
|
||||||
url: "/get_ping_ip",
|
|
||||||
method: "POST",
|
|
||||||
data: "config=" + $(this).children("option:selected").val(),
|
|
||||||
success: function (res){
|
|
||||||
$(".modal.show .ip_dropdown").html("");
|
|
||||||
$(".modal.show .ip_dropdown").append('<option value="none" selected="selected" disabled>Choose an IP');
|
|
||||||
$(".modal.show .ip_dropdown").append(res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// Ping Tools
|
|
||||||
$(".send_ping").on("click", function (){
|
|
||||||
$(this).attr("disabled","disabled");
|
|
||||||
$(this).html("Pinging...");
|
|
||||||
$("#ping_modal .form-control").attr("disabled","disabled");
|
|
||||||
$.ajax({
|
|
||||||
method:"POST",
|
|
||||||
data: "ip="+ $(':selected', $("#ping_modal .ip_dropdown")).val() +
|
|
||||||
"&count=" + $("#ping_modal .ping_count").val(),
|
|
||||||
url: "/ping_ip",
|
|
||||||
success: function (res){
|
|
||||||
$(".ping_result tbody").html("");
|
|
||||||
let html = '<tr><th scope="row">Address</th><td>'+res.address+'</td></tr>' +
|
|
||||||
'<tr><th scope="row">Is Alive</th><td>'+res.is_alive+'</td></tr>' +
|
|
||||||
'<tr><th scope="row">Min RTT</th><td>'+res.min_rtt+'ms</td></tr>' +
|
|
||||||
'<tr><th scope="row">Average RTT </th><td>'+res.avg_rtt+'ms</td></tr>' +
|
|
||||||
'<tr><th scope="row">Max RTT</th><td>'+res.max_rtt+'ms</td></tr>' +
|
|
||||||
'<tr><th scope="row">Package Sent</th><td>'+res.package_sent+'</td></tr>' +
|
|
||||||
'<tr><th scope="row">Package Received</th><td>'+res.package_received+'</td></tr>' +
|
|
||||||
'<tr><th scope="row">Package Loss</th><td>'+res.package_loss+'</td></tr>';
|
|
||||||
$(".ping_result tbody").html(html);
|
|
||||||
$(".send_ping").removeAttr("disabled");
|
|
||||||
$(".send_ping").html("Ping");
|
|
||||||
$("#ping_modal .form-control").removeAttr("disabled");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Traceroute Tools
|
|
||||||
$(".send_traceroute").on("click", function (){
|
|
||||||
$(this).attr("disabled","disabled");
|
|
||||||
$(this).html("Tracing...");
|
|
||||||
$("#traceroute_modal .form-control").attr("disabled","disabled");
|
|
||||||
$.ajax({
|
|
||||||
url: "/traceroute_ip",
|
|
||||||
method: "POST",
|
|
||||||
data: "ip=" + $(':selected', $("#traceroute_modal .ip_dropdown")).val(),
|
|
||||||
success: function (res){
|
|
||||||
$(".traceroute_result tbody").html("");
|
|
||||||
res.forEach((ele) =>
|
|
||||||
$(".traceroute_result tbody").append('<tr><th scope="row">'+ele.hop+'</th><td>'+ele.ip+'</td><td>'+ele.avg_rtt+'</td><td>'+ele.min_rtt+'</td><td>'+ele.max_rtt+'</td></tr>'));
|
|
||||||
$(".send_traceroute").removeAttr("disabled").html("Traceroute");
|
|
||||||
$("#traceroute_modal .form-control").removeAttr("disabled");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let numberToast = 0;
|
|
||||||
function showToast(msg, isDanger = false) {
|
|
||||||
$(".toastContainer").append(
|
|
||||||
`<div id="${numberToast}-toast" class="toast hide animate__animated animate__fadeInUp" role="alert" data-delay="5000">
|
|
||||||
<div class="toast-header">
|
|
||||||
<strong class="mr-auto">WGDashboard</strong>
|
|
||||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body ${isDanger ? 'text-danger':''}">${msg}</div>
|
|
||||||
<div class="toast-progressbar ${isDanger ? 'bg-danger':''}"></div>
|
|
||||||
</div>` )
|
|
||||||
$(`#${numberToast}-toast`).toast('show');
|
|
||||||
$(`#${numberToast}-toast .toast-body`).html(msg);
|
|
||||||
$(`#${numberToast}-toast .toast-progressbar`).css("transition", `width ${$(`#${numberToast}-toast .toast-progressbar`).parent().data('delay')}ms cubic-bezier(0, 0, 0, 0)`);
|
|
||||||
$(`#${numberToast}-toast .toast-progressbar`).css("width", "0px");
|
|
||||||
let i = numberToast;
|
|
||||||
setTimeout(function(){
|
|
||||||
$(`#${i}-toast`).removeClass("animate__fadeInUp").addClass("animate__fadeOutRight")
|
|
||||||
}, 4500)
|
|
||||||
numberToast++;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
$(".ip_dropdown").on("change",function(){$(".modal.show .btn").removeAttr("disabled")});$(".conf_dropdown").on("change",function(){$(".modal.show .ip_dropdown").html('<option value="none" selected="selected" disabled>Loading...');$.ajax({url:"/get_ping_ip",method:"POST",data:"config="+$(this).children("option:selected").val(),success:function(res){$(".modal.show .ip_dropdown").html("");$(".modal.show .ip_dropdown").append('<option value="none" selected="selected" disabled>Choose an IP');$(".modal.show .ip_dropdown").append(res)}})});$(".send_ping").on("click",function(){$(this).attr("disabled","disabled");$(this).html("Pinging...");$("#ping_modal .form-control").attr("disabled","disabled");$.ajax({method:"POST",data:"ip="+$(":selected",$("#ping_modal .ip_dropdown")).val()+"&count="+$("#ping_modal .ping_count").val(),url:"/ping_ip",success:function(res){$(".ping_result tbody").html("");let html='<tr><th scope="row">Address</th><td>'+res.address+"</td></tr>"+'<tr><th scope="row">Is Alive</th><td>'+res.is_alive+"</td></tr>"+'<tr><th scope="row">Min RTT</th><td>'+res.min_rtt+"ms</td></tr>"+'<tr><th scope="row">Average RTT </th><td>'+res.avg_rtt+"ms</td></tr>"+'<tr><th scope="row">Max RTT</th><td>'+res.max_rtt+"ms</td></tr>"+'<tr><th scope="row">Package Sent</th><td>'+res.package_sent+"</td></tr>"+'<tr><th scope="row">Package Received</th><td>'+res.package_received+"</td></tr>"+'<tr><th scope="row">Package Loss</th><td>'+res.package_loss+"</td></tr>";$(".ping_result tbody").html(html);$(".send_ping").removeAttr("disabled");$(".send_ping").html("Ping");$("#ping_modal .form-control").removeAttr("disabled")}})});$(".send_traceroute").on("click",function(){$(this).attr("disabled","disabled");$(this).html("Tracing...");$("#traceroute_modal .form-control").attr("disabled","disabled");$.ajax({url:"/traceroute_ip",method:"POST",data:"ip="+$(":selected",$("#traceroute_modal .ip_dropdown")).val(),success:function(res){$(".traceroute_result tbody").html("");res.forEach(ele=>$(".traceroute_result tbody").append('<tr><th scope="row">'+ele.hop+"</th><td>"+ele.ip+"</td><td>"+ele.avg_rtt+"</td><td>"+ele.min_rtt+"</td><td>"+ele.max_rtt+"</td></tr>"));$(".send_traceroute").removeAttr("disabled").html("Traceroute");$("#traceroute_modal .form-control").removeAttr("disabled")}})});let numberToast=0;function showToast(msg,isDanger=false){$(".toastContainer").append(`<div id="${numberToast}-toast" class="toast hide animate__animated animate__fadeInUp" role="alert" data-delay="5000">
|
|
||||||
<div class="toast-header">
|
|
||||||
<strong class="mr-auto">WGDashboard</strong>
|
|
||||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body ${isDanger?"text-danger":""}">${msg}</div>
|
|
||||||
<div class="toast-progressbar ${isDanger?"bg-danger":""}"></div>
|
|
||||||
</div>`);$(`#${numberToast}-toast`).toast("show");$(`#${numberToast}-toast .toast-body`).html(msg);$(`#${numberToast}-toast .toast-progressbar`).css("transition",`width ${$(`#${numberToast}-toast .toast-progressbar`).parent().data("delay")}ms cubic-bezier(0, 0, 0, 0)`);$(`#${numberToast}-toast .toast-progressbar`).css("width","0px");let i=numberToast;setTimeout(function(){$(`#${i}-toast`).removeClass("animate__fadeInUp").addClass("animate__fadeOutRight")},4500);numberToast++}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
/*! SPDX-License-Identifier: GPL-2.0
|
|
||||||
*
|
|
||||||
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
function gf(init) {
|
|
||||||
var r = new Float64Array(16);
|
|
||||||
if (init) {
|
|
||||||
for (var i = 0; i < init.length; ++i)
|
|
||||||
r[i] = init[i];
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pack(o, n) {
|
|
||||||
var b, m = gf(), t = gf();
|
|
||||||
for (var i = 0; i < 16; ++i)
|
|
||||||
t[i] = n[i];
|
|
||||||
carry(t);
|
|
||||||
carry(t);
|
|
||||||
carry(t);
|
|
||||||
for (var j = 0; j < 2; ++j) {
|
|
||||||
m[0] = t[0] - 0xffed;
|
|
||||||
for (var i = 1; i < 15; ++i) {
|
|
||||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
|
||||||
m[i - 1] &= 0xffff;
|
|
||||||
}
|
|
||||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
|
||||||
b = (m[15] >> 16) & 1;
|
|
||||||
m[14] &= 0xffff;
|
|
||||||
cswap(t, m, 1 - b);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < 16; ++i) {
|
|
||||||
o[2 * i] = t[i] & 0xff;
|
|
||||||
o[2 * i + 1] = t[i] >> 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function carry(o) {
|
|
||||||
var c;
|
|
||||||
for (var i = 0; i < 16; ++i) {
|
|
||||||
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
|
|
||||||
o[i] &= 0xffff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cswap(p, q, b) {
|
|
||||||
var t, c = ~(b - 1);
|
|
||||||
for (var i = 0; i < 16; ++i) {
|
|
||||||
t = c & (p[i] ^ q[i]);
|
|
||||||
p[i] ^= t;
|
|
||||||
q[i] ^= t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function add(o, a, b) {
|
|
||||||
for (var i = 0; i < 16; ++i)
|
|
||||||
o[i] = (a[i] + b[i]) | 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function subtract(o, a, b) {
|
|
||||||
for (var i = 0; i < 16; ++i)
|
|
||||||
o[i] = (a[i] - b[i]) | 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function multmod(o, a, b) {
|
|
||||||
var t = new Float64Array(31);
|
|
||||||
for (var i = 0; i < 16; ++i) {
|
|
||||||
for (var j = 0; j < 16; ++j)
|
|
||||||
t[i + j] += a[i] * b[j];
|
|
||||||
}
|
|
||||||
for (var i = 0; i < 15; ++i)
|
|
||||||
t[i] += 38 * t[i + 16];
|
|
||||||
for (var i = 0; i < 16; ++i)
|
|
||||||
o[i] = t[i];
|
|
||||||
carry(o);
|
|
||||||
carry(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
function invert(o, i) {
|
|
||||||
var c = gf();
|
|
||||||
for (var a = 0; a < 16; ++a)
|
|
||||||
c[a] = i[a];
|
|
||||||
for (var a = 253; a >= 0; --a) {
|
|
||||||
multmod(c, c, c);
|
|
||||||
if (a !== 2 && a !== 4)
|
|
||||||
multmod(c, c, i);
|
|
||||||
}
|
|
||||||
for (var a = 0; a < 16; ++a)
|
|
||||||
o[a] = c[a];
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(z) {
|
|
||||||
z[31] = (z[31] & 127) | 64;
|
|
||||||
z[0] &= 248;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePublicKey(privateKey) {
|
|
||||||
var r, z = new Uint8Array(32);
|
|
||||||
var a = gf([1]),
|
|
||||||
b = gf([9]),
|
|
||||||
c = gf(),
|
|
||||||
d = gf([1]),
|
|
||||||
e = gf(),
|
|
||||||
f = gf(),
|
|
||||||
_121665 = gf([0xdb41, 1]),
|
|
||||||
_9 = gf([9]);
|
|
||||||
for (var i = 0; i < 32; ++i)
|
|
||||||
z[i] = privateKey[i];
|
|
||||||
clamp(z);
|
|
||||||
for (var i = 254; i >= 0; --i) {
|
|
||||||
r = (z[i >>> 3] >>> (i & 7)) & 1;
|
|
||||||
cswap(a, b, r);
|
|
||||||
cswap(c, d, r);
|
|
||||||
add(e, a, c);
|
|
||||||
subtract(a, a, c);
|
|
||||||
add(c, b, d);
|
|
||||||
subtract(b, b, d);
|
|
||||||
multmod(d, e, e);
|
|
||||||
multmod(f, a, a);
|
|
||||||
multmod(a, c, a);
|
|
||||||
multmod(c, b, e);
|
|
||||||
add(e, a, c);
|
|
||||||
subtract(a, a, c);
|
|
||||||
multmod(b, a, a);
|
|
||||||
subtract(c, d, f);
|
|
||||||
multmod(a, c, _121665);
|
|
||||||
add(a, a, d);
|
|
||||||
multmod(c, c, a);
|
|
||||||
multmod(a, d, f);
|
|
||||||
multmod(d, b, _9);
|
|
||||||
multmod(b, e, e);
|
|
||||||
cswap(a, b, r);
|
|
||||||
cswap(c, d, r);
|
|
||||||
}
|
|
||||||
invert(c, c);
|
|
||||||
multmod(a, a, c);
|
|
||||||
pack(z, a);
|
|
||||||
return z;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePresharedKey() {
|
|
||||||
var privateKey = new Uint8Array(32);
|
|
||||||
window.crypto.getRandomValues(privateKey);
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePrivateKey() {
|
|
||||||
var privateKey = generatePresharedKey();
|
|
||||||
clamp(privateKey);
|
|
||||||
return privateKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeBase64(dest, src) {
|
|
||||||
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
|
|
||||||
for (var i = 0; i < 4; ++i)
|
|
||||||
dest[i] = input[i] + 65 +
|
|
||||||
(((25 - input[i]) >> 8) & 6) -
|
|
||||||
(((51 - input[i]) >> 8) & 75) -
|
|
||||||
(((61 - input[i]) >> 8) & 15) +
|
|
||||||
(((62 - input[i]) >> 8) & 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyToBase64(key) {
|
|
||||||
var i, base64 = new Uint8Array(44);
|
|
||||||
for (i = 0; i < 32 / 3; ++i)
|
|
||||||
encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
|
|
||||||
encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
|
|
||||||
base64[43] = 61;
|
|
||||||
return String.fromCharCode.apply(null, base64);
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64ToKey(base64) {
|
|
||||||
let binary_string = window.atob(base64);
|
|
||||||
let len = binary_string.length;
|
|
||||||
let bytes = new Uint8Array(len);
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
bytes[i] = binary_string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
let uint8 = new Uint8Array(bytes.buffer);
|
|
||||||
return uint8;
|
|
||||||
}
|
|
||||||
|
|
||||||
function putU32(b, n)
|
|
||||||
{
|
|
||||||
b.push(n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
function putU16(b, n)
|
|
||||||
{
|
|
||||||
b.push(n & 0xff, (n >>> 8) & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
function putBytes(b, a)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < a.length; ++i)
|
|
||||||
b.push(a[i] & 0xff);
|
|
||||||
}
|
|
||||||
|
|
||||||
function encodeString(s)
|
|
||||||
{
|
|
||||||
var utf8 = unescape(encodeURIComponent(s));
|
|
||||||
var b = new Uint8Array(utf8.length);
|
|
||||||
for (var i = 0; i < utf8.length; ++i)
|
|
||||||
b[i] = utf8.charCodeAt(i);
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
function crc32(b)
|
|
||||||
{
|
|
||||||
if (!crc32.table) {
|
|
||||||
crc32.table = [];
|
|
||||||
for (var c = 0, n = 0; n < 256; c = ++n) {
|
|
||||||
for (var k = 0; k < 8; ++k)
|
|
||||||
c = ((c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1));
|
|
||||||
crc32.table[n] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var crc = -1;
|
|
||||||
for (var i = 0; i < b.length; ++i)
|
|
||||||
crc = (crc >>> 8) ^ crc32.table[(crc ^ b[i]) & 0xff];
|
|
||||||
return (crc ^ (-1)) >>> 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createZipFile(files)
|
|
||||||
{
|
|
||||||
var b = [];
|
|
||||||
var cd = [];
|
|
||||||
var offset = 0;
|
|
||||||
|
|
||||||
for (var i = 0; i < files.length; ++i) {
|
|
||||||
var name = encodeString(files[i].filename);
|
|
||||||
var contents = encodeString(files[i].content);
|
|
||||||
var crc = crc32(contents);
|
|
||||||
|
|
||||||
putU32(b, 0x04034b50); /* signature */
|
|
||||||
putU16(b, 20); /* version needed */
|
|
||||||
putU16(b, 0); /* flags */
|
|
||||||
putU16(b, 0); /* compression method */
|
|
||||||
putU16(b, 0); /* mtime */
|
|
||||||
putU16(b, 0); /* mdate */
|
|
||||||
putU32(b, crc); /* crc32 */
|
|
||||||
putU32(b, contents.length); /* compressed size */
|
|
||||||
putU32(b, contents.length); /* uncompressed size */
|
|
||||||
putU16(b, name.length); /* file name length */
|
|
||||||
putU16(b, 0); /* extra field length */
|
|
||||||
putBytes(b, name);
|
|
||||||
putBytes(b, contents);
|
|
||||||
|
|
||||||
putU32(cd, 0x02014b50); /* signature */
|
|
||||||
putU16(cd, 0); /* version made */
|
|
||||||
putU16(cd, 20); /* version needed */
|
|
||||||
putU16(cd, 0); /* flags */
|
|
||||||
putU16(cd, 0); /* compression method */
|
|
||||||
putU16(cd, 0); /* mtime */
|
|
||||||
putU16(cd, 0); /* mdate */
|
|
||||||
putU32(cd, crc); /* crc32 */
|
|
||||||
putU32(cd, contents.length); /* compressed size */
|
|
||||||
putU32(cd, contents.length); /* uncompressed size */
|
|
||||||
putU16(cd, name.length); /* file name length */
|
|
||||||
putU16(cd, 0); /* extra field length */
|
|
||||||
putU16(cd, 0); /* file comment length */
|
|
||||||
putU16(cd, 0); /* disk number start */
|
|
||||||
putU16(cd, 0); /* internal file attributes */
|
|
||||||
putU32(cd, 32); /* external file attributes - 'archive' bit set (32) */
|
|
||||||
putU32(cd, offset); /* relative offset of local header */
|
|
||||||
putBytes(cd, name); /* file name */
|
|
||||||
|
|
||||||
offset += 30 + contents.length + name.length
|
|
||||||
}
|
|
||||||
putBytes(b, cd); /* central directory */
|
|
||||||
putU32(b, 0x06054b50); /* end of central directory signature */
|
|
||||||
putU16(b, 0); /* number of this disk */
|
|
||||||
putU16(b, 0); /* number of disk with central directory start */
|
|
||||||
putU16(b, files.length); /* number of entries on disk */
|
|
||||||
putU16(b, files.length); /* number of entries */
|
|
||||||
putU32(b, cd.length); /* length of central directory */
|
|
||||||
putU32(b, offset); /* offset to start of central directory */
|
|
||||||
putU16(b, 0); /* zip comment size */
|
|
||||||
return Uint8Array.from(b);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.wireguard = {
|
|
||||||
generateKeypair: function() {
|
|
||||||
var privateKey = generatePrivateKey();
|
|
||||||
var publicKey = generatePublicKey(privateKey);
|
|
||||||
var presharedKey = generatePresharedKey();
|
|
||||||
return {
|
|
||||||
publicKey: keyToBase64(publicKey),
|
|
||||||
privateKey: keyToBase64(privateKey),
|
|
||||||
presharedKey: keyToBase64(presharedKey)
|
|
||||||
};
|
|
||||||
},
|
|
||||||
generatePublicKey: function (privateKey){
|
|
||||||
privateKey = base64ToKey(privateKey);
|
|
||||||
return keyToBase64(generatePublicKey(privateKey));
|
|
||||||
},
|
|
||||||
|
|
||||||
generateZipFiles: function(res){
|
|
||||||
var files = res.peers;
|
|
||||||
var zipFile = createZipFile(files);
|
|
||||||
var blob = new Blob([zipFile], { type: "application/zip" });
|
|
||||||
var a = document.createElement("a");
|
|
||||||
a.download = res.filename;
|
|
||||||
a.href = URL.createObjectURL(blob);
|
|
||||||
a.style.display = "none";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
<link rel="apple-touch-icon" sizes="192x192" href="{{ url_for('static',filename='img/192x192ios.png') }}">
|
<link rel="apple-touch-icon" sizes="192x192" href="{{ url_for('static',filename='img/192x192ios.png') }}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link rel="icon" href="{{ url_for('static',filename='img/logo.png') }}"/>
|
<link rel="icon" href="{{ url_for('static',filename='img/logo.png') }}"/>
|
||||||
<link rel="stylesheet" href="../static/app/dist/assets/index.css">
|
<link rel="stylesheet" href="{{ url_for('static',filename='app/dist/assets/index.css') }}">
|
||||||
<script src="../static/app/dist/assets/index.js" type="module"></script>
|
<script src="{{ url_for('static',filename='app/dist/assets/index.js') }}" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app" class="w-100 vh-100"></div>
|
<div id="app" class="w-100 vh-100"></div>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ ConditionPathIsDirectory=/etc/wireguard
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=forking
|
Type=forking
|
||||||
PIDFile=/opt/wgdashboard/src/gunicorn.pid
|
PIDFile=<absolute_path_of_wgdashboard_src>/gunicorn.pid
|
||||||
WorkingDirectory=/opt/wgdashboard/src
|
WorkingDirectory=<absolute_path_of_wgdashboard_src>
|
||||||
ExecStart=/opt/wgdashboard/src/wgd.sh start
|
ExecStart=<absolute_path_of_wgdashboard_src>/wgd.sh start
|
||||||
ExecStop=/opt/wgdashboard/src/wgd.sh stop
|
ExecStop=<absolute_path_of_wgdashboard_src>/wgd.sh stop
|
||||||
ExecReload=/opt/wgdashboard/src/wgd.sh restart
|
ExecReload=<absolute_path_of_wgdashboard_src>/wgd.sh restart
|
||||||
TimeoutSec=120
|
TimeoutSec=120
|
||||||
PrivateTmp=yes
|
PrivateTmp=yes
|
||||||
Restart=always
|
Restart=always
|
||||||
|
|||||||
@@ -2,10 +2,17 @@
|
|||||||
|
|
||||||
# wgd.sh - Copyright(C) 2024 Donald Zou [https://github.com/donaldzou]
|
# wgd.sh - Copyright(C) 2024 Donald Zou [https://github.com/donaldzou]
|
||||||
# Under Apache-2.0 License
|
# Under Apache-2.0 License
|
||||||
|
#trap "kill $TOP_PID"
|
||||||
|
export TOP_PID=$$
|
||||||
|
|
||||||
app_name="dashboard.py"
|
app_name="dashboard.py"
|
||||||
app_official_name="WGDashboard"
|
app_official_name="WGDashboard"
|
||||||
venv_python="./venv/bin/python3"
|
venv_python="./venv/bin/python3"
|
||||||
venv_gunicorn="./venv/bin/gunicorn"
|
venv_gunicorn="./venv/bin/gunicorn"
|
||||||
|
pythonExecutable="python3"
|
||||||
|
|
||||||
|
heavy_checkmark=$(printf "\xE2\x9C\x94")
|
||||||
|
heavy_crossmark=$(printf "\xE2\x9C\x97")
|
||||||
|
|
||||||
PID_FILE=./gunicorn.pid
|
PID_FILE=./gunicorn.pid
|
||||||
environment=$(if [[ $ENVIRONMENT ]]; then echo $ENVIRONMENT; else echo 'develop'; fi)
|
environment=$(if [[ $ENVIRONMENT ]]; then echo $ENVIRONMENT; else echo 'develop'; fi)
|
||||||
@@ -40,9 +47,15 @@ _check_and_set_venv(){
|
|||||||
VIRTUAL_ENV="./venv"
|
VIRTUAL_ENV="./venv"
|
||||||
if [ ! -d $VIRTUAL_ENV ]; then
|
if [ ! -d $VIRTUAL_ENV ]; then
|
||||||
printf "[WGDashboard] Creating Python Virtual Environment under ./venv\n"
|
printf "[WGDashboard] Creating Python Virtual Environment under ./venv\n"
|
||||||
{ python3 -m venv $VIRTUAL_ENV; } >> ./log/install.txt
|
{ $pythonExecutable -m venv $VIRTUAL_ENV; } >> ./log/install.txt
|
||||||
fi
|
fi
|
||||||
printf "[WGDashboard] Activate Python Virtual Environment under ./venv\n"
|
|
||||||
|
if ! $venv_python --version > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] %s Python Virtual Environment under ./venv failed to create. Halting now.\n" "$heavy_crossmark"
|
||||||
|
kill $TOP_PID
|
||||||
|
fi
|
||||||
|
|
||||||
. ${VIRTUAL_ENV}/bin/activate
|
. ${VIRTUAL_ENV}/bin/activate
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,113 +65,157 @@ _determineOS(){
|
|||||||
OS=$ID
|
OS=$ID
|
||||||
elif [ -f /etc/redhat-release ]; then
|
elif [ -f /etc/redhat-release ]; then
|
||||||
OS="redhat"
|
OS="redhat"
|
||||||
# elif [ -f /etc/arch-release ]; then
|
|
||||||
# OS="arch"
|
|
||||||
else
|
else
|
||||||
printf "[WGDashboard] Sorry, your OS is not supported. Currently the install script only support Debian-based, Red Hat-based OS."
|
printf "[WGDashboard] %s Sorry, your OS is not supported. Currently the install script only support Debian-based, Red Hat-based OS." "$heavy_crossmark"
|
||||||
printf "%s\n" "$helpMsg"
|
printf "%s\n" "$helpMsg"
|
||||||
exit 1
|
kill $TOP_PID
|
||||||
fi
|
fi
|
||||||
|
printf "[WGDashboard] OS: %s\n" "$OS"
|
||||||
}
|
}
|
||||||
|
|
||||||
_installPython(){
|
_installPython(){
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
ubuntu|debian)
|
ubuntu|debian)
|
||||||
{ sudo apt update ; sudo apt-get install -y python3; printf "\n\n"; } &>> ./log/install.txt
|
{ sudo apt update ; sudo apt-get install -y python3 net-tools; printf "\n\n"; } &>> ./log/install.txt
|
||||||
;;
|
;;
|
||||||
centos|fedora|redhat)
|
centos|fedora|redhat|rehl)
|
||||||
if command -v dnf &> /dev/null; then
|
if command -v dnf &> /dev/null; then
|
||||||
{ sudo dnf install -y python3; printf "\n\n"; } >> ./log/install.txt
|
{ sudo dnf install -y python3 net-tools; printf "\n\n"; } >> ./log/install.txt
|
||||||
else
|
else
|
||||||
{ sudo yum install -y python3; printf "\n\n"; } >> ./log/install.txt
|
{ sudo yum install -y python3 net-tools ; printf "\n\n"; } >> ./log/install.txt
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
# arch)
|
alpine)
|
||||||
# { sudo pacman -Sy python python-pip; printf "\n\n"; } >> ./log/install.txt
|
{ apk update; apk add python3 net-tools; printf "\n\n"; } &>> ./log/install.txt
|
||||||
# ;;
|
;;
|
||||||
# *)
|
|
||||||
# printf "[WGDashboard] Sorry, your OS is not supported. Currently the install script only support Debian-based, Red Hat-based and Arch-based OS."
|
|
||||||
# printf "%s" "$helpMsg"
|
|
||||||
# exit 1
|
|
||||||
# ;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if ! python3 --version > /dev/null 2>&1
|
if ! python3 --version > /dev/null 2>&1
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] Python is still not installed, halting script now.\n"
|
printf "[WGDashboard] %s Python is still not installed, halting script now.\n" "$heavy_crossmark"
|
||||||
printf "%s\n" "$helpMsg"
|
printf "%s\n" "$helpMsg"
|
||||||
exit 1
|
kill $TOP_PID
|
||||||
else
|
else
|
||||||
printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n"
|
printf "[WGDashboard] %s Python is installed\n" "$heavy_checkmark"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_installPythonVenv(){
|
_installPythonVenv(){
|
||||||
|
if [ "$pythonExecutable" = "python3" ]; then
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
ubuntu|debian)
|
ubuntu|debian)
|
||||||
{ sudo apt update ; sudo apt-get install -y python3-venv; printf "\n\n"; } &>> ./log/install.txt
|
{ sudo apt update ; sudo apt-get install -y python3-venv; printf "\n\n"; } &>> ./log/install.txt
|
||||||
;;
|
;;
|
||||||
centos|fedora|redhat)
|
centos|fedora|redhat|rhel)
|
||||||
if command -v dnf &> /dev/null; then
|
if command -v dnf &> /dev/null; then
|
||||||
{ sudo dnf install -y python3-virtualenv; printf "\n\n"; } >> ./log/install.txt
|
{ sudo dnf install -y python3-virtualenv; printf "\n\n"; } >> ./log/install.txt
|
||||||
else
|
else
|
||||||
{ sudo yum install -y python3-virtualenv; printf "\n\n"; } >> ./log/install.txt
|
{ sudo yum install -y python3-virtualenv; printf "\n\n"; } >> ./log/install.txt
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
alpine)
|
||||||
|
{ apk add python3 py3-virtualenv; printf "\n\n"; } &>> ./log/install.txt
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
printf "[WGDashboard] Sorry, your OS is not supported. Currently the install script only support Debian-based, Red Hat-based OS."
|
printf "[WGDashboard] %s Sorry, your OS is not supported. Currently the install script only support Debian-based, Red Hat-based OS.\n" "$heavy_crossmark"
|
||||||
printf "%s\n" "$helpMsg"
|
printf "%s\n" "$helpMsg"
|
||||||
exit 1
|
kill $TOP_PID
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
else
|
||||||
|
case "$OS" in
|
||||||
|
ubuntu|debian)
|
||||||
|
{ sudo apt-get update; sudo apt-get install ${pythonExecutable}-venv; } &>> ./log/install.txt
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
if ! python3 -m venv -h > /dev/null 2>&1
|
if ! $pythonExecutable -m venv -h > /dev/null 2>&1
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] Python Virtual Environment is still not installed, halting script now.\n"
|
printf "[WGDashboard] %s Python Virtual Environment is still not installed, halting script now.\n" "$heavy_crossmark"
|
||||||
printf "%s\n" "$helpMsg"
|
printf "%s\n" "$helpMsg"
|
||||||
else
|
else
|
||||||
printf "[WGDashboard] \xE2\x9C\x94 Python Virtual Environment is installed\n"
|
printf "[WGDashboard] %s Python Virtual Environment is installed\n" "$heavy_checkmark"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_installPythonPip(){
|
_installPythonPip(){
|
||||||
|
|
||||||
|
if ! $pythonExecutable -m pip -h > /dev/null 2>&1
|
||||||
|
then
|
||||||
case "$OS" in
|
case "$OS" in
|
||||||
ubuntu|debian)
|
ubuntu|debian)
|
||||||
|
if [ "$pythonExecutable" = "python3" ]; then
|
||||||
{ sudo apt update ; sudo apt-get install -y python3-pip; printf "\n\n"; } &>> ./log/install.txt
|
{ sudo apt update ; sudo apt-get install -y python3-pip; printf "\n\n"; } &>> ./log/install.txt
|
||||||
;;
|
|
||||||
centos|fedora|redhat)
|
|
||||||
if command -v dnf &> /dev/null; then
|
|
||||||
{ sudo dnf install -y python3-pip; printf "\n\n"; } >> ./log/install.txt
|
|
||||||
else
|
else
|
||||||
{ sudo yum install -y python3-pip printf "\n\n"; } >> ./log/install.txt
|
{ sudo apt update ; sudo apt-get install -y ${pythonExecutable}-distutil python3-pip; printf "\n\n"; } &>> ./log/install.txt
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
centos|fedora|redhat|rhel)
|
||||||
|
if [ "$pythonExecutable" = "python3" ]; then
|
||||||
|
{ sudo dnf install -y python3-pip; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
else
|
||||||
|
{ sudo dnf install -y ${pythonExecutable}-pip; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
alpine)
|
||||||
|
{ apk add py3-pip; printf "\n\n"; } &>> ./log/install.txt
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
printf "[WGDashboard] Sorry, your OS is not support auto install. Currently the install script only support Debian-based, Red Hat-based OS."
|
printf "[WGDashboard] %s Sorry, your OS is not supported. Currently the install script only support Debian-based, Red Hat-based OS.\n" "$heavy_crossmark"
|
||||||
printf "%s\n" "$helpMsg"
|
printf "%s\n" "$helpMsg"
|
||||||
exit 1
|
kill $TOP_PID
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
if ! python3 -m pip -h > /dev/null 2>&1
|
if ! $pythonExecutable -m pip -h > /dev/null 2>&1
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] Python Package Manager (PIP) is still not installed, halting script now.\n"
|
printf "[WGDashboard] %s Python Package Manager (PIP) is still not installed, halting script now.\n" "$heavy_crossmark"
|
||||||
printf "%s\n" "$helpMsg"
|
printf "%s\n" "$helpMsg"
|
||||||
|
kill $TOP_PID
|
||||||
else
|
else
|
||||||
printf "[WGDashboard] \xE2\x9C\x94 Python Package Manager (PIP) is installed\n"
|
printf "[WGDashboard] %s Python Package Manager (PIP) is installed\n" "$heavy_checkmark"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkWireguard(){
|
_checkWireguard(){
|
||||||
if ! wg -h > /dev/null 2>&1
|
if ! wg -h > /dev/null 2>&1
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] WireGuard is not installed. Please follow instruction on https://www.wireguard.com/install/ to install. \n"
|
printf "[WGDashboard] %s WireGuard is not installed. Please follow instruction on https://www.wireguard.com/install/ to install. \n" "$heavy_crossmark"
|
||||||
exit 1
|
kill $TOP_PID
|
||||||
fi
|
fi
|
||||||
if ! wg-quick -h > /dev/null 2>&1
|
if ! wg-quick -h > /dev/null 2>&1
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] WireGuard is not installed. Please follow instruction on https://www.wireguard.com/install/ to install. \n"
|
printf "[WGDashboard] %s WireGuard is not installed. Please follow instruction on https://www.wireguard.com/install/ to install. \n" "$heavy_crossmark"
|
||||||
exit 1
|
kill $TOP_PID
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
_checkPythonVersion(){
|
||||||
|
version_pass=$($pythonExecutable -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 10) else print("0");')
|
||||||
|
version=$($pythonExecutable --version)
|
||||||
|
if [ $version_pass == "1" ]
|
||||||
|
then
|
||||||
|
return;
|
||||||
|
elif python3.10 --version > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] %s Found Python 3.10. Will be using [python3.10] to install WGDashboard.\n" "$heavy_checkmark"
|
||||||
|
pythonExecutable="python3.10"
|
||||||
|
elif python3.11 --version > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] %s Found Python 3.11. Will be using [python3.11] to install WGDashboard.\n" "$heavy_checkmark"
|
||||||
|
pythonExecutable="python3.11"
|
||||||
|
elif python3.12 --version > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] %s Found Python 3.12. Will be using [python3.12] to install WGDashboard.\n" "$heavy_checkmark"
|
||||||
|
pythonExecutable="python3.12"
|
||||||
|
else
|
||||||
|
printf "[WGDashboard] %s Could not find a compatible version of Python. Current Python is %s.\n" "$heavy_crossmark" "$version"
|
||||||
|
printf "[WGDashboard] WGDashboard required Python 3.10, 3.11 or 3.12. Halting install now.\n"
|
||||||
|
kill $TOP_PID
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,22 +235,13 @@ install_wgd(){
|
|||||||
printf "[WGDashboard] Python is not installed, trying to install now\n"
|
printf "[WGDashboard] Python is not installed, trying to install now\n"
|
||||||
_installPython
|
_installPython
|
||||||
else
|
else
|
||||||
printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n"
|
printf "[WGDashboard] %s Python is installed\n" "$heavy_checkmark"
|
||||||
fi
|
|
||||||
|
|
||||||
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" ]
|
|
||||||
then
|
|
||||||
printf "[WGDashboard] WGDashboard required Python 3.10 or above\n"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
_checkPythonVersion
|
||||||
_installPythonVenv
|
_installPythonVenv
|
||||||
_installPythonPip
|
_installPythonPip
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if [ ! -d "db" ]
|
if [ ! -d "db" ]
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] Creating ./db folder\n"
|
printf "[WGDashboard] Creating ./db folder\n"
|
||||||
@@ -201,7 +249,8 @@ install_wgd(){
|
|||||||
fi
|
fi
|
||||||
_check_and_set_venv
|
_check_and_set_venv
|
||||||
printf "[WGDashboard] Upgrading Python Package Manage (PIP)\n"
|
printf "[WGDashboard] Upgrading Python Package Manage (PIP)\n"
|
||||||
{ date; python3 -m pip install pip; printf "\n\n"; } >> ./log/install.txt
|
{ date; python3 -m ensurepip --upgrade; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
{ date; python3 -m pip install --upgrade pip; printf "\n\n"; } >> ./log/install.txt
|
||||||
printf "[WGDashboard] Installing latest Python dependencies\n"
|
printf "[WGDashboard] Installing latest Python dependencies\n"
|
||||||
{ date; python3 -m pip install -r requirements.txt ; printf "\n\n"; } >> ./log/install.txt
|
{ date; python3 -m pip install -r requirements.txt ; printf "\n\n"; } >> ./log/install.txt
|
||||||
printf "[WGDashboard] WGDashboard installed successfully!\n"
|
printf "[WGDashboard] WGDashboard installed successfully!\n"
|
||||||
@@ -272,6 +321,54 @@ stop_wgd() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startwgd_docker() {
|
||||||
|
_checkWireguard
|
||||||
|
printf "[WGDashboard][Docker] WireGuard configuration started\n"
|
||||||
|
{ date; start_core ; printf "\n\n"; } >> ./log/install.txt
|
||||||
|
gunicorn_start
|
||||||
|
}
|
||||||
|
|
||||||
|
start_core() {
|
||||||
|
local iptable_dir="/opt/wireguarddashboard/src/iptable-rules"
|
||||||
|
# Check if wg0.conf exists in /etc/wireguard
|
||||||
|
if [[ ! -f /etc/wireguard/wg0.conf ]]; then
|
||||||
|
echo "[WGDashboard][Docker] wg0.conf not found. Running generate configuration."
|
||||||
|
newconf_wgd
|
||||||
|
else
|
||||||
|
echo "[WGDashboard][Docker] wg0.conf already exists. Skipping WireGuard configuration generation."
|
||||||
|
fi
|
||||||
|
# Re-assign config_files to ensure it includes any newly created configurations
|
||||||
|
local config_files=$(find /etc/wireguard -type f -name "*.conf")
|
||||||
|
|
||||||
|
# Set file permissions
|
||||||
|
find /etc/wireguard -type f -name "*.conf" -exec chmod 600 {} \;
|
||||||
|
find "$iptable_dir" -type f -name "*.sh" -exec chmod +x {} \;
|
||||||
|
|
||||||
|
# Start WireGuard for each config file
|
||||||
|
for file in $config_files; do
|
||||||
|
config_name=$(basename "$file" ".conf")
|
||||||
|
wg-quick up "$config_name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
newconf_wgd() {
|
||||||
|
local wg_port_listen=$wg_port
|
||||||
|
local wg_addr_range=$wg_net
|
||||||
|
private_key=$(wg genkey)
|
||||||
|
public_key=$(echo "$private_key" | wg pubkey)
|
||||||
|
cat <<EOF >"/etc/wireguard/wg0.conf"
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = $private_key
|
||||||
|
Address = $wg_addr_range
|
||||||
|
ListenPort = $wg_port_listen
|
||||||
|
SaveConfig = true
|
||||||
|
PostUp = /opt/wireguarddashboard/src/iptable-rules/postup.sh
|
||||||
|
PreDown = /opt/wireguarddashboard/src/iptable-rules/postdown.sh
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
start_wgd_debug() {
|
start_wgd_debug() {
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
_checkWireguard
|
_checkWireguard
|
||||||
@@ -281,7 +378,20 @@ start_wgd_debug() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
update_wgd() {
|
update_wgd() {
|
||||||
new_ver=$(python3 -c "import json; import urllib.request; data = urllib.request.urlopen('https://api.github.com/repos/donaldzou/WGDashboard/releases/latest').read(); output = json.loads(data);print(output['tag_name'])")
|
_determineOS
|
||||||
|
if ! python3 --version > /dev/null 2>&1
|
||||||
|
then
|
||||||
|
printf "[WGDashboard] Python is not installed, trying to install now\n"
|
||||||
|
_installPython
|
||||||
|
else
|
||||||
|
printf "[WGDashboard] %s Python is installed\n" "$heavy_checkmark"
|
||||||
|
fi
|
||||||
|
|
||||||
|
_checkPythonVersion
|
||||||
|
_installPythonVenv
|
||||||
|
_installPythonPip
|
||||||
|
|
||||||
|
new_ver=$($venv_python -c "import json; import urllib.request; data = urllib.request.urlopen('https://api.github.com/repos/donaldzou/WGDashboard/releases/latest').read(); output = json.loads(data);print(output['tag_name'])")
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
printf "[WGDashboard] Are you sure you want to update to the %s? (Y/N): " "$new_ver"
|
printf "[WGDashboard] Are you sure you want to update to the %s? (Y/N): " "$new_ver"
|
||||||
read up
|
read up
|
||||||
@@ -300,12 +410,11 @@ update_wgd() {
|
|||||||
rm wgd.sh.old
|
rm wgd.sh.old
|
||||||
else
|
else
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
printf "| Update Canceled. |\n"
|
printf "[WGDashboard] Update Canceled.\n"
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if [ "$#" != 1 ];
|
if [ "$#" != 1 ];
|
||||||
then
|
then
|
||||||
help
|
help
|
||||||
@@ -318,6 +427,10 @@ if [ "$#" != 1 ];
|
|||||||
else
|
else
|
||||||
start_wgd
|
start_wgd
|
||||||
fi
|
fi
|
||||||
|
elif [ "$1" = "docker_start" ]; then
|
||||||
|
printf "%s\n" "$dashes"
|
||||||
|
startwgd_docker
|
||||||
|
printf "%s\n" "$dashes"
|
||||||
elif [ "$1" = "stop" ]; then
|
elif [ "$1" = "stop" ]; then
|
||||||
if check_wgd_status; then
|
if check_wgd_status; then
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
@@ -339,7 +452,7 @@ if [ "$#" != 1 ];
|
|||||||
if check_wgd_status; then
|
if check_wgd_status; then
|
||||||
printf "%s\n" "$dashes"
|
printf "%s\n" "$dashes"
|
||||||
stop_wgd
|
stop_wgd
|
||||||
printf "| WGDashboard is stopped. |\n"
|
printf "[WGDashboard] WGDashboard is stopped.\n"
|
||||||
sleep 4
|
sleep 4
|
||||||
start_wgd
|
start_wgd
|
||||||
else
|
else
|
||||||
@@ -347,7 +460,7 @@ if [ "$#" != 1 ];
|
|||||||
fi
|
fi
|
||||||
elif [ "$1" = "debug" ]; then
|
elif [ "$1" = "debug" ]; then
|
||||||
if check_wgd_status; then
|
if check_wgd_status; then
|
||||||
printf "| WGDashboard is already running. |\n"
|
printf "[WGDashboard] WGDashboard is already running.\n"
|
||||||
else
|
else
|
||||||
start_wgd_debug
|
start_wgd_debug
|
||||||
fi
|
fi
|
||||||
|
|||||||