Compare commits

...

95 Commits

Author SHA1 Message Date
Donald Cheng Hong Zou
f76579fe5e v2.0-beta-6 Commit 2021-05-13 18:00:40 -04:00
Donald Zou
a05d6979ec Update README.md 2021-05-07 15:27:37 -04:00
Donald Zou
be40d17172 Update README.md 2021-05-07 15:25:23 -04:00
Donald Zou
9e11339979 Update README.md 2021-05-06 11:20:28 -04:00
Donald Zou
a56129afe0 Update README.md 2021-05-05 17:25:23 -04:00
Donald Zou
8933ea999c Update README.md 2021-05-05 15:52:13 -04:00
Donald Zou
ae3b8f6494 Merge pull request #19 from donaldzou/v2.0-beta-6
v2.0 merge to main
2021-05-05 15:29:41 -04:00
Donald Cheng Hong Zou
33487ee03f v2.0-beta-6 Commit 2021-05-05 15:16:32 -04:00
Donald Cheng Hong Zou
58ecae1162 v2.0-beta-6 Commit 2021-05-05 15:09:34 -04:00
Donald Cheng Hong Zou
8777da10e4 v2.0-beta-1 Commit 2021-05-05 14:59:00 -04:00
Donald Zou
4ea971c1b9 v2.0-beta-1 Commit 2021-05-05 14:52:28 -04:00
Donald Cheng Hong Zou
0fa4759c3a Update 2021-05-05 14:38:10 -04:00
Donald Cheng Hong Zou
fe7b9730d3 Update README.md 2021-05-04 21:41:13 -04:00
Donald Cheng Hong Zou
040f6a8393 Commit 2021-05-04 21:26:40 -04:00
Donald Cheng Hong Zou
b17575b197 Update requirements.txt 2021-05-04 15:55:52 -04:00
Donald Cheng Hong Zou
dee4f757e7 Upload README pictures 2021-05-04 15:42:04 -04:00
Donald Cheng Hong Zou
5ae08d02ab Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2021-05-04 02:10:32 -04:00
Donald Cheng Hong Zou
b83aec4965 v2.0-beta-2 Commit 2021-05-04 02:10:06 -04:00
Donald Zou
68c4837780 Merge pull request #18 from donaldzou/testing
Update .gitignore
2021-05-04 01:39:51 -04:00
Donald Cheng Hong Zou
46fe5e99fe Update .gitignore 2021-05-04 01:39:32 -04:00
Donald Zou
9ffb537cfb Delete wg-dashboard.ini 2021-05-04 01:39:14 -04:00
Donald Zou
cc7e6f852d Merge pull request #17 from donaldzou/testing
v2.0-beta1 Merge
2021-05-04 01:37:27 -04:00
Donald Cheng Hong Zou
f83e99c62c Merge branch 'testing' of https://github.com/donaldzou/Wireguard-Dashboard into testing 2021-05-04 01:36:32 -04:00
Donald Zou
bc8b8e7982 v2.0-beta-1 Commit 2021-05-04 01:32:34 -04:00
Donald Zou
3b761ca2a8 Update README.md 2021-05-04 01:20:20 -04:00
Donald Cheng Hong Zou
edc21f9830 Update README.md 2021-04-12 23:18:57 -04:00
Donald Cheng Hong Zou
d025ec4dff Push Logo 2021-04-12 23:16:12 -04:00
Donald Cheng Hong Zou
ab334a393f Logo 2021-04-12 23:15:53 -04:00
Donald Cheng Hong Zou
2a9044086f Push Logo 2021-04-12 23:14:53 -04:00
Donald Zou
c3038ec60c Merge pull request #14 from donaldzou/all-contributors/add-tonjo
docs: add tonjo as a contributor
2021-04-12 22:30:23 -04:00
allcontributors[bot]
0fa827ba00 docs: update .all-contributorsrc [skip ci] 2021-04-13 02:29:48 +00:00
allcontributors[bot]
26002fc372 docs: update README.md [skip ci] 2021-04-13 02:29:47 +00:00
Donald Zou
59acba8c08 Update README.md 2021-04-12 22:29:00 -04:00
Donald Zou
4c5ee45fdb Update README.md 2021-04-12 22:28:30 -04:00
Donald Zou
6062120d0d Merge pull request #13 from donaldzou/all-contributors/add-antonioag95
docs: add antonioag95 as a contributor
2021-04-12 22:27:54 -04:00
allcontributors[bot]
cd3ffb2126 docs: create .all-contributorsrc [skip ci] 2021-04-13 02:26:56 +00:00
allcontributors[bot]
a8c11cc445 docs: update README.md [skip ci] 2021-04-13 02:26:55 +00:00
Donald Zou
fcb89aac6b Update README.md 2021-04-12 11:05:19 -04:00
Donald Zou
3704d8f6e7 Merge pull request #11 from tonjo/patch-1
Avoid error when `AllowedIPs` is not defined for a peer.
2021-04-12 11:01:38 -04:00
Donald Zou
8c75d172c5 Update issue templates 2021-04-10 12:49:34 -04:00
Donald Zou
5687302081 Update issue templates 2021-04-10 12:47:18 -04:00
Donald Zou
9020b16a9a Update README.md 2021-04-10 00:49:32 -04:00
tonjo
d5462df4da Update dashboard.py 2021-04-09 11:15:25 +02:00
tonjo
9021ca31af Avoid error when AllowedIps is not defined 2021-04-09 11:08:41 +02:00
Donald Zou
83cb8b7f03 Update README.md 2021-04-09 00:42:42 -04:00
Donald Cheng Hong Zou
baf2658eb8 v20210409 2021-04-09 00:07:37 -04:00
Donald Zou
3f19b05d28 Update README.md 2021-04-07 17:25:21 -04:00
Donald Zou
5e6be1f830 Update README.md 2021-04-07 15:58:00 -04:00
Donald Zou
14d1a3038f Update README.md 2021-04-07 12:19:23 -04:00
Donald Zou
6a15e2e587 Update README.md 2021-04-07 12:15:45 -04:00
Donald Zou
0679805728 Update README.md 2021-04-07 12:01:35 -04:00
Donald Zou
62a03419a5 Update README.md 2021-04-07 11:52:24 -04:00
Donald Zou
81f6b0767e Update README.md 2021-04-07 11:52:03 -04:00
Donald Zou
c7ca5c749b Update README.md 2021-04-06 19:52:33 -04:00
Donald Zou
1daac09b7c Update README.md 2021-04-06 19:51:50 -04:00
Donald Zou
4d3dd68ac0 Merge pull request #5 from antonioag95/main
- Fixed a bug where a peer without last handshake date was showing wrong date in UI
- Hidden and temp `.conf` files will be excluded from interfaces list
2021-04-06 19:43:52 -04:00
Donald Zou
de9e4a6270 Update README.md 2021-04-06 16:51:37 -04:00
Donald Zou
bf8f347245 Update README.md 2021-04-06 15:03:49 -04:00
Donald Zou
a70d09ac7a Update README.md 2021-04-06 10:14:11 -04:00
Donald Zou
919b344da4 Update README.md 2021-04-05 22:41:55 -04:00
antonioag95
44f013a678 Update dashboard.py
- Fixed a bug where a peer without last handshake date was showing wrong date in webui
- Hidden and temp conf files will be excluded from interfaces list
2021-04-03 23:48:07 +02:00
Donald Zou
742191111b Merge pull request #4 from donaldzou/v20210403-alpha
v20210403 Bug Fixed
2021-04-03 14:31:11 -04:00
Donald Cheng Hong Zou
8dc0031475 Update dashboard.py 2021-04-03 14:30:02 -04:00
Donald Cheng Hong Zou
19fb90a300 Update dashboard.py 2021-04-03 14:10:07 -04:00
Donald Cheng Hong Zou
0c3960d8d9 Commit 2021-04-03 14:06:21 -04:00
Donald Cheng Hong Zou
7e770b9b5d v20210402.2 2021-04-02 22:22:41 -04:00
Donald Zou
b95f391f02 Update README.md 2021-04-02 21:15:50 -04:00
Donald Zou
4ed861ffce Update README.md 2021-04-02 21:15:28 -04:00
Donald Zou
2c0b3be900 Update README.md 2021-04-02 21:14:51 -04:00
Donald Cheng Hong Zou
145e163670 Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2021-04-02 21:14:06 -04:00
Donald Cheng Hong Zou
d5497843eb v20210403.2 2021-04-02 21:12:46 -04:00
Donald Zou
3799f6fe49 Update README.md 2021-04-02 20:49:41 -04:00
Donald Cheng Hong Zou
07678300da v20210402
Added more features
2021-04-02 20:48:00 -04:00
Donald Zou
881c8f30ef Update README.md 2021-03-24 02:07:01 -04:00
Donald Zou
5d02fbbdea Update README.md 2021-01-12 13:36:13 -05:00
donaldzou
4c969f5524 Update 2020-12-27 00:02:36 -05:00
donaldzou
f361e178f1 Added the function to remove peers 2020-12-26 23:42:41 -05:00
donaldzou
edc77be8ef Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2020-12-26 00:17:43 -05:00
donaldzou
55ba5801af Added connected peers 2020-12-26 00:17:42 -05:00
Donald Zou
6d9c35f7cc Update README.md 2020-10-28 10:12:39 -04:00
Donald Zou
320b82dc18 Update README.md 2020-10-23 17:51:11 -04:00
donaldzou
d22d695312 Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2020-10-23 01:51:03 -04:00
donaldzou
ba5eeeba07 Commit 2020-10-23 01:50:52 -04:00
Donald Zou
0b5b2b6243 Delete sftp.json 2020-10-23 01:50:04 -04:00
donaldzou
0efef135d6 Merge branch 'main' of https://github.com/donaldzou/Wireguard-Dashboard into main 2020-10-23 01:31:15 -04:00
donaldzou
ea52529e06 Able to add peer 2020-10-23 01:31:10 -04:00
Donald Zou
259bd325af Update README.md 2020-10-21 20:30:16 -04:00
Donald Zou
faf05bad6a Delete .DS_Store 2020-10-20 10:47:02 -04:00
Donald Zou
0576906907 Delete .DS_Store 2020-10-20 10:46:53 -04:00
donaldzou
8c88440c3e commit 2020-10-20 10:45:29 -04:00
donaldzou
2f327ed49a File arrangement 2020-10-20 10:44:41 -04:00
donaldzou
96af266c38 Update configuration.html 2020-10-19 11:48:08 -04:00
Donald Zou
397e448899 Update README.md 2020-10-18 20:20:02 -04:00
donaldzou
cabb6df477 Update configuration.html 2020-10-18 20:19:41 -04:00
donaldzou
032ea1d967 On/Off Switch 2020-10-18 12:23:38 -04:00
40 changed files with 2015 additions and 376 deletions

34
.all-contributorsrc Normal file
View File

@@ -0,0 +1,34 @@
{
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "antonioag95",
"name": "antonioag95",
"avatar_url": "https://avatars.githubusercontent.com/u/30556866?v=4",
"profile": "https://github.com/antonioag95",
"contributions": [
"test",
"code"
]
},
{
"login": "tonjo",
"name": "tonjo",
"avatar_url": "https://avatars.githubusercontent.com/u/4726289?v=4",
"profile": "https://github.com/tonjo",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "wireguard-dashboard",
"projectOwner": "donaldzou",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true
}

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,28 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe The Problem**
A clear and concise description of what the bug is.
**Expected Error / Traceback**
```
Please provide the error traceback here
```
**To Reproduce**
Please provide how you run the dashboard
**OS Information:**
- OS: [e.g. Ubuntu 18.02]
- Python Version: [e.g v3.7]
**Sample of your `.conf` file**
```
Please provide a sample of your configuration file that you are having problem with. You can replace your public key and private key to ABCD...
```

View File

@@ -0,0 +1,14 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.

11
.gitignore vendored
View File

@@ -1 +1,12 @@
.vscode/sftp.json
src/.vscode/sftp.json
.DS_Store
wg.db
*.json
.idea
src/test.py
tmp
__pycache__
src/wg-dashboard.ini
src/wg-dashboard.ini
src/static/pic.xd

215
README.md
View File

@@ -1,19 +1,202 @@
# Wireguard-Dashboard
## Intro
Monitoring Wireguard is not convinient, need to login into server and type wg show. That's why this platform is being created, to view all configurations in a more straight forward way.
## Installation
**Requirement:**
- Wireguard
<hr>
<p align=center>I'm looking for someone that have experiences on migrating this project to a Docker app, with a complete solution ;) If you know how please <a href="https://github.com/donaldzou/wireguard-dashboard/issues/20">comment in here</a>.</p>
<hr>
<p align="center">
<img src="https://raw.githubusercontent.com/donaldzou/wireguard-dashboard/main/img/Group%202.png" width="128">
</p>
<h1 align="center"> Wireguard Dashboard</h1>
<p align="center">
<img src="http://ForTheBadge.com/images/badges/made-with-python.svg">
</p>
<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>
</p>
<p align="center">Monitoring WireGuard is not convinient, need to login into server and type <code>wg show</code>. That's why this platform is being created, to view all configurations and manage them in a easier way.</p>
## 📣 What's New: Version 2.0.1
- Added **Ping** and **Traceroute** tool
- Adjusted the calculation of data usage on each peers
- Added refresh interval of the dashboard
<hr>
### ⚠️ **Update from v1.x.x**
1. Stop the dashboard if it is running.
2. You can use `git pull https://github.com/donaldzou/Wireguard-Dashboard.git v2.0.1` to get the new update inside `Wireguard-Dashboard` directory.
3. Proceed **Step 2 & 3** in the [Install](#-install) step down below.
## 💡 Features
- Add peers for each WireGuard configuration
- Manage peer
- Delete peers
- And many more coming up! Welcome to contribute to this project!
## 📝 Requirement
- Ubuntu or Debian based OS, other might work, but haven't test yet. Tested on the following OS:
- [x] Ubuntu 18.04.1 LTS
- [ ] If you have tested on other OS and it works perfectly please provide it to me!
- ‼️ Make sure you have **Wireguard** and **Wireguard-Tools (`wg-quick`)** installed.‼️ <a href="https://www.wireguard.com/install/">How to install?</a>
- Configuration files under **/etc/wireguard**
- Python 3.7
- Flask
**Install:**
1. `git clone https://github.com/donaldzou/Wireguard-Dashboard.git`
2. `cd Wireguard-Dashboard`
3. `python3 dashboard.py`
4. Access your server! e.g (http://your_server_ip:10086)
- **Note: For peers, `PublicKey` & `AllowedIPs` is required.**
- Python 3.7+ & Pip3
## 🛠 Install
**1. Download Wireguard Dashboard**
```shell
git clone -b v2.0.1 https://github.com/donaldzou/Wireguard-Dashboard.git
```
**2. Install Python Dependencies**
```shell
cd Wireguard-Dashboard/src
python3 -m pip install -r requirements.txt
```
**3. Install & run Wireguard Dashboard**
```shell
chmod u+x wgd.sh
./wgd.sh start
```
Access your server with port `10086` ! e.g (http://your_server_ip:10086), continue to read to on how to change port and ip that dashboard is running with.
## 🪜 Usage
**1. Start/Stop/Restart Wireguard Dashboard**
```shell
cd Wireguard-Dashboard/src
./wgd.sh start # Start the dashboard in background
./wgd.sh debug # Start the dashboard in foreground (debug mode)
./wgd.sh stop # Stop the dashboard
./wgd.sh restart # Restart the dasboard
./wgd.sh update # Update the dashboard
```
⚠️ **For first time user please also read the next section.**
## ✂️ Dashboard Configuration
Since version 2.0, Wireguard Dashboard 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 config:
### `[Account]`
`username` - Username (Default: `admin`)
`password` - Password, will be hash with SHA256 (Default: `admin`).
### `[Server]`
`wg_conf_path` - The path of all the Wireguard configurations (Default: `/etc/wireguard`)
`app_ip` - IP address the flask will run with (Default: `0.0.0.0`)
`app_port` - Port the flask will run with (Default: `10086`)
`auth_req` - Does the dashboard need authentication (Default: `true`)
- If `auth_req = false` , user will not be access the **Setting** tab due to security consideration. **User can only change the file directly in system**.
`version` - Dashboard Version
All these settings will be able to configure within the dashboard in **Settings** on the sidebar, without changing the actual file. **Except `version` and `auth_req` due to security consideration.**
## ❓ How to update the dashboard?
```shell
cd wireguard-dashboard
sudo sh wgd.sh update # Perform update
sudo sh wgd.sh start # Start dashboard
```
## 🔍 Screenshot
![Index Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/src/static/index.png)
<p align=center>Index Page</p>
![Signin Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/src/static/signin.png)
<p align=center>Signin Page</p>
![Configuration Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/src/static/configuration.png)
<p align=center>Configuration Page</p>
![Settings Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/src/static/settings.png)
<p align=center>Settings Page</p>
## 🛒 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/) `v1.1.2`
- [TinyDB](https://pypi.org/project/tinydb/) `v4.3.0`
- [ifcfg](https://pypi.org/project/ifcfg/) `v0.21`
- [icmplib](https://pypi.org/project/icmplib/) `v2.1.1`
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#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/wireguard-dashboard/commits?author=antonioag95" title="Tests">⚠️</a> <a href="https://github.com/donaldzou/wireguard-dashboard/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/wireguard-dashboard/commits?author=tonjo" 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!
## Example
![Index Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/static/index.png)
![Conf Image](https://github.com/donaldzou/Wireguard-Dashboard/raw/main/static/configuration.png)

View File

@@ -1,139 +0,0 @@
import os
from flask import Flask, request, render_template
from tinydb import TinyDB, Query
import subprocess
from datetime import datetime, date, time, timedelta
conf_location = "/etc/wireguard"
app = Flask("Wireguard Dashboard")
app.config['TEMPLATES_AUTO_RELOAD'] = True
css = ""
def get_conf_peers_data(config_name):
peer_data = {}
# Get key
try: peer_key = subprocess.check_output("wg show "+config_name+" peers", shell=True)
except Exception: return "stopped"
peer_key = peer_key.decode("UTF-8").split()
for i in peer_key: peer_data[i] = {}
#Get transfer
try: data_usage = subprocess.check_output("wg show "+config_name+" transfer", shell=True)
except Exception: return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
upload_total = 0
download_total = 0
total = 0
for i in range(int(len(data_usage)/3)):
peer_data[data_usage[count]]['total_recive'] = round(int(data_usage[count+1])/(1024**3), 4)
peer_data[data_usage[count]]['total_sent'] = round(int(data_usage[count+2])/(1024**3),4)
peer_data[data_usage[count]]['total_data'] = round((int(data_usage[count+2])+int(data_usage[count+1]))/(1024**3),4)
count += 3
#Get endpoint
try: data_usage = subprocess.check_output("wg show "+config_name+" endpoints", shell=True)
except Exception: return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage)/2)):
peer_data[data_usage[count]]['endpoint'] = data_usage[count+1]
count += 2
#Get latest handshakes
try: data_usage = subprocess.check_output("wg show "+config_name+" latest-handshakes", shell=True)
except Exception: return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
now = datetime.now()
b = timedelta(minutes=2)
for i in range(int(len(data_usage)/2)):
minus = now - datetime.fromtimestamp(int(data_usage[count+1]))
if minus < b:
peer_data[data_usage[count]]['status'] = "running"
else:
peer_data[data_usage[count]]['status'] = "stopped"
peer_data[data_usage[count]]['latest_handshake'] = minus
count += 2
#Get allowed ip
try: data_usage = subprocess.check_output("wg show "+config_name+" allowed-ips", shell=True)
except Exception: return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage)/2)):
peer_data[data_usage[count]]['allowed_ip'] = data_usage[count+1]
count += 2
return peer_data
def get_conf_pub_key(config_name):
try: pub_key = subprocess.check_output("wg show "+config_name+" public-key", shell=True)
except Exception: return "stopped"
return pub_key.decode("UTF-8")
def get_conf_listen_port(config_name):
try: pub_key = subprocess.check_output("wg show "+config_name+" listen-port", shell=True)
except Exception: return "stopped"
return pub_key.decode("UTF-8")
def get_conf_total_data(config_name):
try: data_usage = subprocess.check_output("wg show "+config_name+" transfer", shell=True)
except Exception: return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
upload_total = 0
download_total = 0
total = 0
for i in range(int(len(data_usage)/3)):
upload_total += int(data_usage[count+1])
download_total += int(data_usage[count+2])
count += 3
total = round(((((upload_total+download_total)/1024)/1024)/1024),3)
upload_total = round(((((upload_total)/1024)/1024)/1024),3)
download_total = round(((((download_total)/1024)/1024)/1024),3)
return [total, upload_total, download_total]
def get_conf_status(config_name):
try: status = subprocess.check_output("wg show "+config_name, shell=True)
except Exception: return "stopped"
else: return "running"
def get_conf_list():
conf = []
for i in os.listdir(conf_location):
if ".conf" in i:
i = i.replace('.conf','')
temp = {"conf":i, "status":get_conf_status(i), "public_key": get_conf_pub_key(i)}
conf.append(temp)
return conf
@app.route('/',methods=['GET'])
def index():
return render_template('index.html', conf=get_conf_list())
@app.route('/configuration/<config_name>', methods=['GET'])
def conf(config_name):
conf_data = {
"name": config_name,
"status": get_conf_status(config_name),
"total_data_usage": get_conf_total_data(config_name),
"public_key": get_conf_pub_key(config_name),
"listen_port": get_conf_listen_port(config_name),
"peer_data":get_conf_peers_data(config_name)
}
return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data)
app.run(host='0.0.0.0',debug=False, port=10086)

BIN
img/Group 2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
img/Group 2@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
img/Group 3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
img/Group 3@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
img/Wg-dashboard-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

651
src/dashboard.py Normal file
View File

@@ -0,0 +1,651 @@
# Python Built-in Library
import os
from flask import Flask, request, render_template, redirect, url_for, session, abort, jsonify
from icmplib import ping, multiping, traceroute, resolve, Host, Hop
import subprocess
from datetime import datetime, date, time, timedelta
from operator import itemgetter
import secrets
import hashlib
import json, urllib.request
import configparser
import re
# PIP installed library
import ifcfg
from tinydb import TinyDB, Query
# Dashboard Version
dashboard_version = 'v2.0.1'
# Dashboard Config Name
dashboard_conf = 'wg-dashboard.ini'
# Upgrade Required
update = ""
# Flask App Configuration
app = Flask("Wireguard Dashboard")
app.secret_key = secrets.token_urlsafe(16)
app.config['TEMPLATES_AUTO_RELOAD'] = True
def get_conf_peer_key(config_name):
try:
peer_key = subprocess.check_output("wg show " + config_name + " peers", shell=True)
peer_key = peer_key.decode("UTF-8").split()
return peer_key
except Exception:
return config_name+" is not running."
def get_conf_running_peer_number(config_name):
running = 0
# Get latest handshakes
try:
data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
now = datetime.now()
b = timedelta(minutes=2)
for i in range(int(len(data_usage) / 2)):
minus = now - datetime.fromtimestamp(int(data_usage[count + 1]))
if minus < b:
running += 1
count += 2
return running
def is_match(regex, text):
pattern = re.compile(regex)
return pattern.search(text) is not None
def read_conf_file(config_name):
# Read Configuration File Start
conf_location = wg_conf_path + "/" + config_name + ".conf"
f = open(conf_location, 'r')
file = f.read().split("\n")
conf_peer_data = {
"Interface": {},
"Peers": []
}
peers_start = 0
for i in range(len(file)):
if not is_match("^#(.*)",file[i]):
if file[i] == "[Peer]":
peers_start = i
break
else:
if len(file[i]) > 0:
if file[i] != "[Interface]":
tmp = re.split(r'\s*=\s*', file[i], 1)
if len(tmp) == 2:
conf_peer_data['Interface'][tmp[0]] = tmp[1]
conf_peers = file[peers_start:]
peer = -1
for i in conf_peers:
if not is_match("^#(.*)", i):
if i == "[Peer]":
peer += 1
conf_peer_data["Peers"].append({})
else:
if len(i) > 0:
tmp = re.split('\s*=\s*', i,1)
if len(tmp) == 2:
conf_peer_data["Peers"][peer][tmp[0]] = tmp[1]
# Read Configuration File End
return conf_peer_data
def get_conf_peers_data(config_name):
db = TinyDB('db/' + config_name + '.json')
peers = Query()
conf_peer_data = read_conf_file(config_name)
for i in conf_peer_data['Peers']:
if not db.search(peers.id == i['PublicKey']):
db.insert({
"id": i['PublicKey'],
"name": "",
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": 0,
"status": 0,
"latest_handshake": 0,
"allowed_ip": 0,
"traffic": []
})
# Get latest handshakes
try:
data_usage = subprocess.check_output("wg show " + config_name + " latest-handshakes", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
now = datetime.now()
b = timedelta(minutes=2)
for i in range(int(len(data_usage) / 2)):
minus = now - datetime.fromtimestamp(int(data_usage[count + 1]))
if minus < b:
status = "running"
else:
status = "stopped"
if int(data_usage[count + 1]) > 0:
db.update({"latest_handshake": str(minus).split(".")[0], "status": status},
peers.id == data_usage[count])
else:
db.update({"latest_handshake": "(None)", "status": status}, peers.id == data_usage[count])
count += 2
# Get transfer
try:
data_usage = subprocess.check_output("wg show " + config_name + " transfer", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage) / 3)):
cur_i = db.search(peers.id == data_usage[count])
total_sent = cur_i[0]['total_sent']
total_receive = cur_i[0]['total_receive']
traffic = cur_i[0]['traffic']
cur_total_sent = round(int(data_usage[count + 2]) / (1024 ** 3), 4)
cur_total_receive = round(int(data_usage[count + 1]) / (1024 ** 3), 4)
if cur_i[0]["status"] == "running":
if total_sent <= cur_total_sent and total_receive <= cur_total_receive:
total_sent = cur_total_sent
total_receive = cur_total_receive
else:
now = datetime.now()
ctime = now.strftime("%d/%m/%Y %H:%M:%S")
traffic.append({"time": ctime, "total_receive": round(total_receive, 4),"total_sent": round(total_sent, 4),
"total_data": round(total_receive + total_sent, 4)})
total_sent = 0
total_receive = 0
db.update({"traffic": traffic}, peers.id == data_usage[count])
db.update({"total_receive": round(total_receive, 4),
"total_sent": round(total_sent, 4),
"total_data": round(total_receive + total_sent, 4)}, peers.id == data_usage[count])
count += 3
# Get endpoint
try:
data_usage = subprocess.check_output("wg show " + config_name + " endpoints", shell=True)
except Exception:
return "stopped"
data_usage = data_usage.decode("UTF-8").split()
count = 0
for i in range(int(len(data_usage) / 2)):
db.update({"endpoint": data_usage[count + 1]}, peers.id == data_usage[count])
count += 2
# Get allowed ip
for i in conf_peer_data["Peers"]:
db.update({"allowed_ip": i.get('AllowedIPs', '(None)')}, peers.id == i["PublicKey"])
db.close()
def get_peers(config_name):
get_conf_peers_data(config_name)
db = TinyDB('db/' + config_name + '.json')
result = db.all()
result = sorted(result, key=lambda d: d['status'])
db.close()
return result
def get_conf_pub_key(config_name):
conf = configparser.ConfigParser(strict=False)
conf.read(wg_conf_path + "/" + config_name + ".conf")
pri = conf.get("Interface", "PrivateKey")
pub = subprocess.check_output("echo '" + pri + "' | wg pubkey", shell=True)
conf.clear()
return pub.decode().strip("\n")
def get_conf_listen_port(config_name):
conf = configparser.ConfigParser(strict=False)
conf.read(wg_conf_path + "/" + config_name + ".conf")
port = conf.get("Interface", "ListenPort")
conf.clear()
return port
def get_conf_total_data(config_name):
db = TinyDB('db/' + config_name + '.json')
upload_total = 0
download_total = 0
for i in db.all():
upload_total += i['total_sent']
download_total += i['total_receive']
for k in i['traffic']:
upload_total += k['total_sent']
download_total += k['total_receive']
total = round(upload_total + download_total, 4)
upload_total = round(upload_total, 4)
download_total = round(download_total, 4)
db.close()
return [total, upload_total, download_total]
def get_conf_status(config_name):
ifconfig = dict(ifcfg.interfaces().items())
if config_name in ifconfig.keys():
return "running"
else:
return "stopped"
def get_conf_list():
conf = []
for i in os.listdir(wg_conf_path):
if not i.startswith('.'):
if ".conf" in i:
i = i.replace('.conf', '')
temp = {"conf": i, "status": get_conf_status(i), "public_key": get_conf_pub_key(i)}
# get_conf_peers_data(i)
if temp['status'] == "running":
temp['checked'] = 'checked'
else:
temp['checked'] = ""
conf.append(temp)
conf = sorted(conf, key=itemgetter('conf'))
return conf
@app.before_request
def auth_req():
conf = configparser.ConfigParser(strict=False)
conf.read(dashboard_conf)
req = conf.get("Server", "auth_req")
session['update'] = update
session['dashboard_version'] = dashboard_version
if req == "true":
if '/static/' not in request.path and \
request.endpoint != "signin" and \
request.endpoint != "signout" and \
request.endpoint != "auth" and \
"username" not in session:
print("not loggedin")
session['message'] = "You need to sign in first!"
return redirect(url_for("signin"))
else:
if request.endpoint in ['signin', 'signout', 'auth', 'settings', 'update_acct', 'update_pwd',
'update_app_ip_port', 'update_wg_conf_path']:
return redirect(url_for("index"))
@app.route('/signin', methods=['GET'])
def signin():
message = ""
if "message" in session:
message = session['message']
session.pop("message")
return render_template('signin.html', message=message)
@app.route('/signout', methods=['GET'])
def signout():
if "username" in session:
session.pop("username")
message = "Sign out successfully!"
return render_template('signin.html', message=message)
@app.route('/settings', methods=['GET'])
def settings():
message = ""
status = ""
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
if "message" in session and "message_status" in session:
message = session['message']
status = session['message_status']
session.pop("message")
session.pop("message_status")
required_auth = config.get("Server", "auth_req")
return render_template('settings.html', conf=get_conf_list(), message=message, status=status,
app_ip=config.get("Server", "app_ip"), app_port=config.get("Server", "app_port"),
required_auth=required_auth, wg_conf_path=config.get("Server", "wg_conf_path"))
@app.route('/auth', methods=['POST'])
def auth():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
password = hashlib.sha256(request.form['password'].encode())
if password.hexdigest() == config["Account"]["password"] and request.form['username'] == config["Account"][
"username"]:
session['username'] = request.form['username']
config.clear()
return redirect(url_for("index"))
else:
session['message'] = "Username or Password is correct."
config.clear()
return redirect(url_for("signin"))
@app.route('/update_acct', methods=['POST'])
def update_acct():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Account", "username", request.form['username'])
try:
config.write(open(dashboard_conf, "w"))
session['message'] = "Username update successfully!"
session['message_status'] = "success"
session['username'] = request.form['username']
config.clear()
return redirect(url_for("settings"))
except Exception:
session['message'] = "Username update failed."
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
@app.route('/update_pwd', methods=['POST'])
def update_pwd():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
if hashlib.sha256(request.form['currentpass'].encode()).hexdigest() == config.get("Account", "password"):
if hashlib.sha256(request.form['newpass'].encode()).hexdigest() == hashlib.sha256(
request.form['repnewpass'].encode()).hexdigest():
config.set("Account", "password", hashlib.sha256(request.form['repnewpass'].encode()).hexdigest())
try:
config.write(open(dashboard_conf, "w"))
session['message'] = "Password update successfully!"
session['message_status'] = "success"
config.clear()
return redirect(url_for("settings"))
except Exception:
session['message'] = "Password update failed"
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
else:
session['message'] = "Your New Password does not match."
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
else:
session['message'] = "Your Password does not match."
session['message_status'] = "danger"
config.clear()
return redirect(url_for("settings"))
@app.route('/update_app_ip_port', methods=['POST'])
def update_app_ip_port():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Server", "app_ip", request.form['app_ip'])
config.set("Server", "app_port", request.form['app_port'])
config.write(open(dashboard_conf, "w"))
config.clear()
os.system('bash wgd.sh restart')
@app.route('/update_wg_conf_path', methods=['POST'])
def update_wg_conf_path():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Server", "wg_conf_path", request.form['wg_conf_path'])
config.write(open(dashboard_conf, "w"))
session['message'] = "WireGuard Configuration Path Update Successfully!"
session['message_status'] = "success"
config.clear()
os.system('bash wgd.sh restart')
@app.route('/update_dashboard_refresh_interval', methods=['POST'])
def update_dashboard_refresh_interval():
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
config.set("Server", "dashboard_refresh_interval", str(request.form['interval']))
config.write(open(dashboard_conf, "w"))
config.clear()
return "true"
@app.route('/get_ping_ip', methods=['POST'])
def get_ping_ip():
config = request.form['config']
db = TinyDB('db/' + config + '.json')
html = ""
for i in db.all():
html += '<optgroup label="'+i['name']+' - '+i['id']+'">'
allowed_ip = str(i['allowed_ip']).split(",")
for k in allowed_ip:
k = k.split("/")
if len(k) == 2:
html += "<option value="+k[0]+">"+k[0]+"</option>"
endpoint = str(i['endpoint']).split(":")
if len(endpoint) == 2:
html += "<option value=" + endpoint[0] + ">" + endpoint[0] + "</option>"
html += "</optgroup>"
return html
@app.route('/ping_ip', methods=['POST'])
def ping_ip():
try:
result = ping(''+request.form['ip']+'', count=int(request.form['count']),privileged=True, source=None)
returnjson = {
"address": result.address,
"is_alive": result.is_alive,
"min_rtt": result.min_rtt,
"avg_rtt": result.avg_rtt,
"max_rtt": result.max_rtt,
"package_sent": result.packets_sent,
"package_received": result.packets_received,
"package_loss": result.packet_loss
}
return jsonify(returnjson)
except Exception:
return "Error"
@app.route('/traceroute_ip', methods=['POST'])
def traceroute_ip():
try:
result = traceroute(''+request.form['ip']+'', first_hop=1, max_hops=30, count=1, fast=True)
returnjson = []
last_distance = 0
for hop in result:
if last_distance + 1 != hop.distance:
returnjson.append({"hop":"*", "ip":"*", "avg_rtt":"", "min_rtt":"", "max_rtt":""})
returnjson.append({"hop": hop.distance, "ip": hop.address, "avg_rtt": hop.avg_rtt, "min_rtt": hop.min_rtt, "max_rtt": hop.max_rtt})
last_distance = hop.distance
return jsonify(returnjson)
except Exception:
return "Error"
@app.route('/', methods=['GET'])
def index():
return render_template('index.html', conf=get_conf_list())
@app.route('/configuration/<config_name>', methods=['GET'])
def conf(config_name):
conf_data = {
"name": config_name,
"status": get_conf_status(config_name),
"checked": ""
}
if conf_data['status'] == "stopped":
conf_data['checked'] = "nope"
else:
conf_data['checked'] = "checked"
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
return render_template('configuration.html', conf=get_conf_list(), conf_data=conf_data, dashboard_refresh_interval=int(config.get("Server","dashboard_refresh_interval")))
@app.route('/get_config/<config_name>', methods=['GET'])
def get_conf(config_name):
conf_data = {
"peer_data": get_peers(config_name),
"name": config_name,
"status": get_conf_status(config_name),
"total_data_usage": get_conf_total_data(config_name),
"public_key": get_conf_pub_key(config_name),
"listen_port": get_conf_listen_port(config_name),
"running_peer": get_conf_running_peer_number(config_name),
}
if conf_data['status'] == "stopped":
# return redirect('/')
conf_data['checked'] = "nope"
else:
conf_data['checked'] = "checked"
return render_template('get_conf.html', conf=get_conf_list(), conf_data=conf_data)
@app.route('/switch/<config_name>', methods=['GET'])
def switch(config_name):
if "username" not in session:
print("not loggedin")
return redirect(url_for("signin"))
status = get_conf_status(config_name)
if status == "running":
try:
status = subprocess.check_output("wg-quick down " + config_name, shell=True)
except Exception:
return redirect('/')
elif status == "stopped":
try:
status = subprocess.check_output("wg-quick up " + config_name, shell=True)
except Exception:
return redirect('/')
return redirect(request.referrer)
@app.route('/add_peer/<config_name>', methods=['POST'])
def add_peer(config_name):
data = request.get_json()
public_key = data['public_key']
allowed_ips = data['allowed_ips']
keys = get_conf_peer_key(config_name)
if public_key is not list:
return config_name+" is not running."
if public_key in keys:
return "Key already exist."
else:
status = ""
try:
status = subprocess.check_output(
"wg set " + config_name + " peer " + public_key + " allowed-ips " + allowed_ips, shell=True,
stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
get_conf_peers_data(config_name)
db = TinyDB("db/" + config_name + ".json")
peers = Query()
db.update({"name": data['name']}, peers.id == public_key)
db.close()
return "true"
except subprocess.CalledProcessError as exc:
return exc.output.strip()
@app.route('/remove_peer/<config_name>', methods=['POST'])
def remove_peer(config_name):
if get_conf_status(config_name) == "stopped":
return "Your need to turn on " + config_name + " first."
db = TinyDB("db/" + config_name + ".json")
peers = Query()
data = request.get_json()
delete_key = data['peer_id']
keys = get_conf_peer_key(config_name)
if keys is not list:
return config_name+" is not running."
if delete_key not in keys:
db.close()
return "This key does not exist"
else:
try:
status = subprocess.check_output("wg set " + config_name + " peer " + delete_key + " remove", shell=True,
stderr=subprocess.STDOUT)
status = subprocess.check_output("wg-quick save " + config_name, shell=True, stderr=subprocess.STDOUT)
db.remove(peers.id == delete_key)
db.close()
return "true"
except subprocess.CalledProcessError as exc:
return exc.output.strip()
@app.route('/save_peer_name/<config_name>', methods=['POST'])
def save_peer_name(config_name):
data = request.get_json()
id = data['id']
name = data['name']
db = TinyDB("db/" + config_name + ".json")
peers = Query()
db.update({"name": name}, peers.id == id)
db.close()
return id + " " + name
@app.route('/get_peer_name/<config_name>', methods=['POST'])
def get_peer_name(config_name):
data = request.get_json()
id = data['id']
db = TinyDB("db/" + config_name + ".json")
peers = Query()
result = db.search(peers.id == id)
db.close()
return result[0]['name']
def init_dashboard():
# Set Default INI File
if not os.path.isfile("wg-dashboard.ini"):
conf_file = open("wg-dashboard.ini", "w+")
config = configparser.ConfigParser(strict=False)
config.read(dashboard_conf)
if "Account" not in config:
config['Account'] = {}
if "username" not in config['Account']:
config['Account']['username'] = 'admin'
if "password" not in config['Account']:
config['Account']['password'] = '8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918'
if "Server" not in config:
config['Server'] = {}
if 'wg_conf_path' not in config['Server']:
config['Server']['wg_conf_path'] = '/etc/wireguard'
if 'app_ip' not in config['Server']:
config['Server']['app_ip'] = '0.0.0.0'
if 'app_port' not in config['Server']:
config['Server']['app_port'] = '10086'
if 'auth_req' not in config['Server']:
config['Server']['auth_req'] = 'true'
if 'version' not in config['Server'] or config['Server']['version'] != dashboard_version:
config['Server']['version'] = dashboard_version
if 'dashboard_refresh_interval' not in config['Server']:
config['Server']['dashboard_refresh_interval'] = '15000'
config.write(open(dashboard_conf, "w"))
config.clear()
def check_update():
conf = configparser.ConfigParser(strict=False)
conf.read(dashboard_conf)
data = urllib.request.urlopen("https://api.github.com/repos/donaldzou/wireguard-dashboard/releases").read()
output = json.loads(data)
if conf.get("Server", "version") == output[0]["tag_name"]:
return "false"
else:
return "true"
if __name__ == "__main__":
init_dashboard()
update = check_update()
config = configparser.ConfigParser(strict=False)
config.read('wg-dashboard.ini')
app_ip = config.get("Server", "app_ip")
app_port = config.get("Server", "app_port")
wg_conf_path = config.get("Server", "wg_conf_path")
config.clear()
app.run(host=app_ip, debug=False, port=app_port)

1
src/db/hi.txt Normal file
View File

@@ -0,0 +1 @@
You can delete this later ;)

34
src/db/wg0.conf Normal file
View File

@@ -0,0 +1,34 @@
[Interface]
Address = 10.200.200.1/24
SaveConfig = true
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 51820
PrivateKey = 8DsSMli3okgUx5frKbFQ0fMW5ZMyqyxOdOW7+g21L18=
[Peer]
#Name = 10
PublicKey = Hw2sISygeuT4eQyubDdP0CA2RJHYcpHhBOnIn6riEA0=
AllowedIPs = 10.200.200.9/32, 10.200.200.2/32
Endpoint = 142.114.220.189:58403
[Peer]
PublicKey = 0pCjuGUyXIxTmCZCoFeVtMOgZFVkU9WPGilJxLpYVAI=
AllowedIPs = 10.200.200.3/32
Endpoint = 76.67.102.20:55349
[Peer]
PublicKey = sJrgj14BfToasB5EhQfUjw2xTj3FoU/lSca9bDx+2Ww=
AllowedIPs = 10.200.200.4/32
Endpoint = 175.0.140.3:59637
[Peer]
PublicKey = IxM4gsOWugRtQ0WmFnaAYUCVquTsSqxHiE7oqisKsRQ=
AllowedIPs = 10.200.200.5/32
Endpoint = 180.152.230.148:61618
[Peer]
PublicKey = TSsTB1NPTOHfqNkcNZbJQiz+XcJ32AudypDwa5ItmBM=
AllowedIPs = 10.200.200.6/32
Endpoint = 118.250.38.191:51820

4
src/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
Flask==1.1.2
tinydb==4.3.0
ifcfg==0.21
icmplib==2.1.1

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -22,11 +22,11 @@ body {
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@media (max-width: 767.98px) {
.sidebar {
top: 5rem;
}
}
/*@media (max-width: 767.98px) {*/
/* .sidebar {*/
/* top: 5rem;*/
/* }*/
/*}*/
.sidebar-sticky {
position: relative;
@@ -47,6 +47,11 @@ body {
.sidebar .nav-link {
font-weight: 500;
color: #333;
transition: 0.2s cubic-bezier(0.82, -0.07, 0, 1.01);
}
.nav-link:hover {
padding-left: 30px;
}
.sidebar .nav-link .feather {
@@ -121,4 +126,63 @@ body {
.info h6{
line-break: anywhere;
}
.btn-control{
border: none !important;
padding: 0;
padding-right: 0.5rem;
}
.btn-control:hover{
background: white;
}
.btn-delete-peer:hover{
color: #dc3545;
}
.btn-setting-peer:hover{
color:#007bff
}
.login-container{
padding: 2rem;
}
@media (max-width: 992px){
.card-col{
margin-bottom: 1rem;
}
}
.switch{
font-size: 2rem;
}
.switch:hover{
text-decoration: none
}
.btn-group-label:hover{
color: #007bff;
border-color: #007bff;
background: white;
}
.peer_data_group{
text-align: right;
}
@media (max-width: 768px) {
.peer_data_group{
text-align: left;
}
}
.index-switch{
text-align: right;
}
main{
margin-bottom: 3rem;
}

BIN
src/static/index.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
src/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
src/static/settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
src/static/signin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

68
src/static/tools.js Normal file
View File

@@ -0,0 +1,68 @@
$(".ip_dropdown").change(function (){
$(".modal.show .btn").removeAttr("disabled")
});
$(".conf_dropdown").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").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("")
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").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("");
for (i in res){
$(".traceroute_result tbody").append('<tr><th scope="row">'+res[i]['hop']+'</th><td>'+res[i]['ip']+'</td><td>'+res[i]['avg_rtt']+'</td><td>'+res[i]['min_rtt']+'</td><td>'+res[i]['max_rtt']+'</td></tr>')
}
$(".send_traceroute").removeAttr("disabled");
$(".send_traceroute").html("Traceroute");
$("#traceroute_modal .form-control").removeAttr("disabled")
}
})
})

View File

@@ -0,0 +1,256 @@
<html>
{% include "header.html" %}
<body>
{% include "navbar.html" %}
<div class="container-fluid">
{% include "sidebar.html" %}
<div id="config_body"></div>
</div>
<div class="modal fade" id="add_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Add a new peer</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="add_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form>
<div class="form-group">
<label for="public_key">Public Key</label>
<input type="text" class="form-control" id="public_key" aria-describedby="public_key">
</div>
<div class="form-group">
<label for="allowed_ips">Allowed IPs</label>
<input type="text" class="form-control" id="allowed_ips">
</div>
<div class="form-group">
<label for="allowed_ips">Name</label>
<input type="text" class="form-control" id="new_add_name">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save_peer" conf_id={{conf_data['name']}}>Save</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="delete_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Are you sure to delete this peer?</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="remove_peer_alert" class="alert alert-danger alert-dismissible fade show d-none" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<h6>This action is not reversible.</h6>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
<button type="button" class="btn btn-danger" id="delete_peer" conf_id={{conf_data['name']}} peer_id="">Yes</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="setting_modal" data-backdrop="static" data-keyboard="false" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-hidden="true" conf_id={{conf_data['name']}} peer_id="">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="peer_name"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="peer_name" class="form-label">Name</label>
<input type="text" class="form-control" id="peer_name_textbox" placeholder="">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="save_peer_name" conf_id={{conf_data['name']}} peer_id="">Save</button>
</div>
</div>
</div>
</div>
<div class="position-fixed top-0 right-0 p-3" style="z-index: 5; right: 0; top: 50px;">
<div id="alertToast" class="toast hide" role="alert" aria-live="assertive" aria-atomic="true" data-delay="5000">
<div class="toast-header">
<strong class="mr-auto">Wireguard Dashboard</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body">
</div>
</div>
</div>
{% include "tools.html" %}
</body>
{% include "footer.html" %}
<script>
$(".sb-{{conf_data['name']}}-url").addClass("active");
function load_data(){
$.ajax({
method: "GET",
url: "/get_config/"+"{{conf_data['name']}}",
headers:{
"Content-Type": "application/json"
},
async:false,
success: function (response){
$("#config_body").html(response);
$("[refresh-interval={{ dashboard_refresh_interval }}]").addClass("active")
}
})
}
$(document).ready(function(){
load_data();
setInterval(function(){
load_data();
}, {{dashboard_refresh_interval}})
});
$("body").on("click", ".update_interval", function(){
$.ajax({
method:"POST",
data: "interval="+$(this).attr("refresh-interval"),
url: "/update_dashboard_refresh_interval",
success: function (res){
location.reload()
}
})
});
$("body").on("click", ".refresh", function (){
load_data();
});
</script>
<script>
$("body").on("click", ".switch", function (){
$(this).siblings($(".spinner-border")).css("display", "inline-block");
$(this).remove()
location.replace("/switch/"+$(this).attr('id'));
})
$("#save_peer").click(function(){
if ($("#allowed_ips") != "" && $("#public_key") != ""){
var conf = $(this).attr('conf_id')
$.ajax({
method: "POST",
url: "/add_peer/"+conf,
headers:{
"Content-Type": "application/json"
},
data: JSON.stringify({"public_key":$("#public_key").val(), "allowed_ips": $("#allowed_ips").val(), "name":$("#new_add_name").val()}),
success: function (response){
if(response != "true"){
$("#add_peer_alert").html(response+$("#add_peer_alert").html());
$("#add_peer_alert").removeClass("d-none");
}
else{
location.reload();
}
}
})
}
})
$("body").on("click", ".btn-delete-peer", function(){
var peer_id = $(this).attr("id");
$("#delete_peer").attr("peer_id", peer_id);
})
$("#delete_peer").click(function(){
var peer_id = $(this).attr("peer_id");
var config = $(this).attr("conf_id");
$.ajax({
method: "POST",
url: "/remove_peer/"+config,
headers:{
"Content-Type": "application/json"
},
data: JSON.stringify({"action": "delete", "peer_id": peer_id}),
success: function (response){
if(response !== "true"){
$("#remove_peer_alert").html(response+$("#add_peer_alert").html());
$("#remove_peer_alert").removeClass("d-none");
}
else{
location.reload();
}
}
})
});
var myModal = new bootstrap.Modal(document.getElementById('setting_modal'), {
keyboard: false
})
$("body").on("click", ".btn-setting-peer", function(){
myModal.toggle();
var peer_id = $(this).attr("id");
$("#save_peer_name").attr("peer_id", peer_id);
$.ajax({
method: "POST",
url: "/get_peer_name/"+$("#setting_modal").attr("conf_id"),
headers:{
"Content-Type": "application/json"
},
data: JSON.stringify({"id": peer_id}),
success: function(response){
if (response == ""){
$("#setting_modal .peer_name").html("Untitled Peer");
$("#peer_name_textbox").val("")
}else{
$("#setting_modal .peer_name").html(response);
$("#peer_name_textbox").val(response)
}
}
})
});
$("#save_peer_name").click(function (){
var peer_id = $(this).attr("peer_id");
$.ajax({
method: "POST",
url: "/save_peer_name/"+"{{conf_data['name']}}",
headers:{
"Content-Type": "application/json"
},
data: JSON.stringify({id: peer_id, name: $("#peer_name_textbox").val()}),
success: function (response){
myModal.toggle();
load_data();
$('#alertToast').toast('show');
$('#alertToast .toast-body').html("Name Saved!");
}
})
})
</script>
</html>

View File

@@ -0,0 +1,9 @@
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script>
<script src="{{ url_for('static',filename='tools.js') }}"></script>

121
src/templates/get_conf.html Normal file
View File

@@ -0,0 +1,121 @@
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4 mb-4">
<div class="info mt-4">
<div class="row">
<div class="col">
<small class="text-muted"><strong>CONFIGURATION</strong></small>
<h1 class="mb-3">{{conf_data['name']}}</h1>
</div>
<div class="col">
<small class="text-muted"><strong>ACTION</strong></small><br>
{% if conf_data['checked'] == "checked" %}
<a href="#" id="{{conf_data['name']}}" {{conf_data['checked']}} class="switch text-primary"><i class="bi bi-toggle2-on"></i> ON</a>
{% else %}
<a href="#" id="{{conf_data['name']}}" {{conf_data['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i> OFF</a>
{% endif %}
<div class="spinner-border text-primary" role="status" style="display: none; margin-top: 10px">
<span class="sr-only">Loading...</span>
</div>
</div>
<div class="w-100"></div>
<div class="col">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['status']}}<span class="dot dot-{{conf_data['status']}}"></span></h6>
</div>
<div class="col">
<small class="text-muted"><strong>CONNECTED PEERS</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['running_peer']}}</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][0]}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL RECIEVED</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][1]}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL SENT</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][2]}} GB</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{conf_data['public_key']}}</samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>LISTEN PORT</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{conf_data['listen_port']}}</samp></h6>
</div>
</div>
<hr>
<div class="button-div mb-3" style="text-align: right;">
<div class="btn-group" role="group" aria-label="Basic example">
<button type="button" class="btn btn-outline-primary btn-sm btn-group-label refresh"><i class="bi bi-arrow-repeat"></i></button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="5000">5s</button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="10000">10s</button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="30000">30s</button>
<button type="button" class="btn btn-outline-primary btn-sm update_interval" refresh-interval="60000">1m</button>
</div>
<button type="button" class="btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#add_modal">
<i class="bi bi-plus-circle-fill"></i> Peer
</button>
</div>
</div>
{% for i in conf_data['peer_data']%}
<div class="card mb-3">
<div class="card-header">
<div class="row">
<div class="col-sm">
<div class="card-header-body ">
{% if not i['name']%}
{{ "Untitled Peer" }}
{% else %}
{{i['name']}}
{% endif %}
<span class="dot dot-{{i['status']}}"></span>
</div>
</div>
<div class="col-sm peer_data_group">
<p class="text-primary" style="text-transform: uppercase; display: inline-block; margin-bottom: 0; margin-right: 1rem"><i class="bi bi-arrow-down-right"></i> {{i['total_receive']}} GB</p>
<p class="text-success" style="text-transform: uppercase; display: inline-block; margin-bottom: 0"><i class="bi bi-arrow-up-right"></i> {{i['total_sent']}} GB</p>
</div>
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-sm">
<small class="text-muted"><strong>PEER</strong></small>
<h6><samp class="ml-auto">{{i['id']}}</samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>ALLOWED IP</strong></small>
<h6 style="text-transform: uppercase;">{{i['allowed_ip']}}</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>LATEST HANDSHAKE</strong></small>
<h6 style="text-transform: uppercase;">{{i['latest_handshake']}}</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>END POINT</strong></small>
<h6 style="text-transform: uppercase;">{{i['endpoint']}}</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<div class="button-group">
<hr>
<button type="button" class="btn btn-outline-primary btn-setting-peer btn-control" id="{{i['id']}}" data-toggle="modal"><i class="bi bi-gear-fill"></i></button>
<button type="button" class="btn btn-outline-danger btn-delete-peer btn-control" id="{{i['id']}}" data-toggle="modal" data-target="#delete_modal"><i class="bi bi-x-circle-fill"></i></button>
</div>
</div>
</div>
</div>
</div>
{%endfor%}
</main>

10
src/templates/header.html Normal file
View File

@@ -0,0 +1,10 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard</title>
<link rel="icon" href="{{ url_for('static',filename='logo.png') }}"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.4.1/font/bootstrap-icons.css">
</head>

54
src/templates/index.html Normal file
View File

@@ -0,0 +1,54 @@
<html>
{% include "header.html" %}
<body>
{% include "navbar.html" %}
<div class="container-fluid">
{% include "sidebar.html" %}
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
<h1 class="pb-4 mt-4">Home</h1>
{% for i in conf%}
<div class="card mt-3">
<div class="card-body">
<div class="row">
<div class="col card-col">
<small class="text-muted"><strong>CONFIGURATION</strong></small>
<a href="/configuration/{{i['conf']}}">
<h6 class="card-title" style="margin:0 !important;">{{i['conf']}}</h6>
</a>
</div>
<div class="col card-col">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase; margin:0 !important;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6>
</div>
<div class="col-md card-col">
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="text-transform: uppercase; margin:0 !important;"><samp>{{i['public_key']}}</samp></h6>
</div>
<div class="col-md index-switch">
{% if i['checked'] == "checked" %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-primary tt"><i class="bi bi-toggle2-on"></i></a>
{% else %}
<a href="#" id="{{i['conf']}}" {{i['checked']}} class="switch text-secondary"><i class="bi bi-toggle2-off"></i></a>
{% endif %}
<div class="spinner-border text-primary" role="status" style="display: none">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
</div>
{%endfor%}
</main>
</div>
{% include "tools.html" %}
</body>
{% include "footer.html" %}
<script>
$('.switch').click(function() {
$(this).siblings($(".spinner-border")).css("display", "inline-block")
$(this).remove()
location.replace("/switch/"+$(this).attr('id'))
});
$(".sb-home-url").addClass("active")
</script>
</html>

View File

@@ -0,0 +1,7 @@
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="/">Wireguard Dashboard</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>

133
src/templates/settings.html Normal file
View File

@@ -0,0 +1,133 @@
<html>
{% include "header.html" %}
<body>
{% include "navbar.html" %}
<div class="container-fluid">
{% include "sidebar.html" %}
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<div class="setting-container mt-4">
{% if message != ""%}
<div class="alert alert-{{ status }}" role="alert">
{{ message }}
</div>
{% endif %}
<h1 class="pb-4">Settings</h1>
{% if required_auth == "true" %}
<h3>Account</h3>
<form action="/update_acct" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control mb-4" id="username" name="username" value="{{ session['username'] }}">
<button type="submit" class="btn btn-success" >Update Account</button>
</div>
</form>
<hr>
<h3>WireGuard Configuration Path</h3>
<form action="/update_wg_conf_path" method="post" class="update_wg_conf_path">
<div class="form-group">
<label for="username">Path</label>
<input type="text" class="form-control mb-4" id="wg_conf_path" name="wg_conf_path" value="{{ wg_conf_path }}">
<p>Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code></p>
<button class="btn btn-danger change_path">Update Path & Restart Dashboard</button>
</div>
</form>
<hr>
<h3>Security</h3>
<form action="/update_pwd", method="post">
<div class="form-group">
<label for="currentpass">Current Password</label>
<input type="password" class="form-control mb-2" id="currentpass" name="currentpass">
<label for="newpass">New Password</label>
<input type="password" class="form-control mb-2" id="newpass" name="newpass">
<label for="repnewpass">Repeat New Password</label>
<input type="password" class="form-control mb-4" id="repnewpass" name="repnewpass">
<button type="submit" class="btn btn-danger">Update Password</button>
</div>
</form>
<hr>
{% endif %}
<h3>Dashboard Configuration</h3>
<form action="/update_app_ip_port" method="post" class="update_app_ip_port">
<div class="form-group">
<div class="row">
<div class="col-sm">
<label for="app_ip" >Dashboard IP</label>
<input type="text" class="form-control mb-2" id="app_ip" name="app_ip" value="{{ app_ip }}">
<p><small class="text-danger mb-4">0.0.0.0 means it can be access by anyone with your server IP Address.</small></p>
</div>
<div class="col-sm">
<label for="app_port">Dashboard Port</label>
<input type="text" class="form-control mb-4" id="app_port" name="app_port" value="{{ app_port }}">
</div>
</div>
<button type="button" class="btn btn-danger confirm_modal" data-toggle="modal" data-target="#confirmModal">Update Configuration & Restart</button>
</div>
</form>
</div>
</main>
<!-- Modal -->
<div class="modal fade" id="confirmModal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Confirm Dashboard Configuration</h5>
</div>
<div class="modal-body">
<small>Dashboard Original IP</small>
<p>{{ app_ip }}</p>
<small style="font-weight: bold" class="text-bold">Dashboard New IP</small>
<p class="app_new_ip text-bold text-danger" style="font-weight: bold"></p>
<small>Dashboard Original Port</small>
<p>{{ app_port }}</p>
<small style="font-weight: bold" class="text-bold">Dashboard New Port</small>
<p class="app_new_port text-bold text-danger" style="font-weight: bold"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary cancel_restart" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger confirm_restart">Confirm & Restart Dashboard</button>
</div>
</div>
</div>
</div>
</div>
</body>
{% include "footer.html" %}
<script>
$(".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())
});
</script>
</html>

View File

@@ -0,0 +1,47 @@
<div class="row">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link sb-home-url" href="/">Home</a></li>
{% if "username" in session %}
<li class="nav-item"><a class="nav-link sb-settings-url" href="/settings">Settings</a></li>
{% endif %}
{% if session['update'] == "true" %}
<li class="nav-item sb-update-li">
<a class="nav-link sb-update-url" data-toggle="modal" data-target="#update_modal" href="#">New Update Available!<span class="dot dot-running"></span></a>
</li>
{% endif %}
</ul>
<hr>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configurations</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link sb-{{i['conf']}}-url" href="/configuration/{{i['conf']}}">{{i['conf']}}</a></li>
{%endfor%}
</ul>
<hr>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Tools</span>
</h6>
<ul class="nav flex-column">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#ping_modal" href="#">Ping</a></li>
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#traceroute_modal" href="#">Traceroute</a></li>
</ul>
</ul>
<hr>
{% if "username" in session %}
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link text-danger" href="/signout" style="font-weight: bold">Sign Out</a></li>
</ul>
{% endif %}
<ul class="nav flex-column">
<li class="nav-item"><a href="https://github.com/donaldzou/wireguard-dashboard"><small class="nav-link text-muted">{{ session['dashboard_version'] }}</small></a></li>
</ul>
</div>
</nav>
</div>
</div>

39
src/templates/signin.html Normal file
View File

@@ -0,0 +1,39 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard | Login</title>
<link rel="icon" href="{{ url_for('static',filename='logo.png') }}"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}">
</head>
<body>
{% include "navbar.html" %}
<div class="container-fluid">
<main role="main" class="container login-container">
<div class="login-box" style="margin: auto !important;">
<h3 class="text-center">Sign In</h3>
<form style="margin-left: auto !important; margin-right: auto !important; max-width: 500px;" action="/auth" method="post">
{% if message != ""%}
<div class="alert alert-danger" role="alert">
{{ message }}
</div>
{% endif %}
<div class="form-group">
<label for="username" class="text-left">User Name</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password" class="text-left">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-dark" style="width: 100%;">Sign In</button>
</form>
</div>
</main>
</div>
</body>
{% include "footer.html" %}
</html>

121
src/templates/tools.html Normal file
View File

@@ -0,0 +1,121 @@
<div class="modal fade" id="ping_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Ping</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<small>Configuration</small>
<select class="form-control mt-2 conf_dropdown">
<option value="none" selected="selected" disabled>Select Configuration</option>
{% for i in conf%}
<option value="{{ i['conf'] }}">{{ i['conf'] }}</option>
{%endfor%}
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>IP</small>
<select class="form-control mt-2 ip_dropdown">
<option value="none" selected="selected" disabled>Choose an IP</option>
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>Ping Count</small>
<input type="number" class="form-control mt-2 ping_count" min=1 value=4>
</div>
</div>
</div>
<hr>
<div class="ping_result">
<table class="table">
<tbody></tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-primary send_ping" disabled>Ping</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="traceroute_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Traceroute</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<div class="mb-3">
<small>Configuration</small>
<select class="form-control mt-2 conf_dropdown">
<option value="none" selected="selected" disabled>Select Configuration</option>
{% for i in conf%}
<option value="{{ i['conf'] }}">{{ i['conf'] }}</option>
{%endfor%}
</select>
</div>
</div>
<div class="col-sm">
<div class="mb-3">
<small>IP</small>
<select class="form-control mt-2 ip_dropdown">
<option value="none" selected="selected" disabled>Choose an IP</option>
</select>
</div>
</div>
</div>
<button class="btn btn-primary send_traceroute" disabled>Traceroute</button>
<hr>
<div class="traceroute_result">
<table class="table table-sm">
<thead>
<tr>
<th scope="col">Hop</th>
<th scope="col">IP</th>
<th scope="col">Avg RTT</th>
<th scope="col">Min RTT</th>
<th scope="col">Max RTT</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="update_modal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">How to update dashboard</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pre><code>$ sudo sh wgd.sh stop</code><br><code>$ sudo sh wgd.sh update</code><br><code>$ sudo sh wgd.sh start</code></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>

105
src/wgd.sh Normal file
View File

@@ -0,0 +1,105 @@
#!/bin/bash
app_name="dashboard.py"
dashes='------------------------------------------------------------'
help () {
printf "<Wireguard Dashboard> by Donald Zou - https://github.com/donaldzou \n"
printf "Usage: sh wgd.sh <option>"
printf "\n \n"
printf "Available options: \n"
printf " start: To start Wireguard Dashboard.\n"
printf " stop: To stop Wireguard Dashboard.\n"
printf " debug: To start Wireguard Dashboard in debug mode (i.e run in foreground).\n"
printf " update: To update Wireguard Dashboard to the newest version from GitHub.\n"
printf "Thank you for using this dashboard! Your support is my motivation ;) \n"
printf "\n"
}
check_wgd_status(){
if ps aux | grep '[p]ython3 '$app_name > /dev/null;
then
return 0
else
return 1
fi
}
start_wgd () {
printf "%s" "$PLATFORM"
printf "Starting Wireguard Dashboard in the background. \n"
if [ ! -d "log" ]
then mkdir "log"
fi
d=$(date '+%Y%m%d%H%M%S')
python3 "$app_name" > log/"$d".txt 2>&1 &
printf "Log file: log/%s""$d"".txt\n"
}
stop_wgd() {
kill "$(ps aux | grep "[p]ython3 $app_name" | awk '{print $2}')"
}
start_wgd_debug() {
printf "Starting Wireguard Dashboard in the foreground. \n"
python3 "$app_name"
}
update_wgd() {
new_ver=$(python3 -c "import json; import urllib.request; data = urllib.request.urlopen('https://api.github.com/repos/donaldzou/wireguard-dashboard/releases').read(); output = json.loads(data);print(output[0]['tag_name'])")
printf "%s\n" "$dashes"
printf "Are you sure you want to update to the %s? (Y/N): " "$new_ver"
read up
if [ "$up" = "Y" ]; then
printf "| Shutting down Wireguard Dashboard... |\n"
printf "| Downloading %s from GitHub... |\n" "$new_ver"
git pull https://github.com/donaldzou/wireguard-dashboard.git $new_ver --force > /dev/null 2>&1
printf "| Installing all required python package |\n"
python3 -m pip install -r requirements.txt
printf "| Update Successfully! |\n"
printf "| Now you can start the dashboard with >> sh wgd.sh start |\n"
exit 1
else
printf "Cancel update. \n"
fi
}
if [ "$#" != 1 ];
then
help
else
if [ "$1" = "start" ]; then
if check_wgd_status; then
printf "Wireguard Dashboard is already running. \n"
else
start_wgd
fi
elif [ "$1" = "stop" ]; then
if check_wgd_status; then
stop_wgd
printf "Wireguard Dashboard is stopped. \n"
else
printf "Wireguard Dashboard is not running. \n"
fi
elif [ "$1" = "update" ]; then
update_wgd
elif [ "$1" = "restart" ]; then
if check_wgd_status; then
stop_wgd
sleep 2
printf "Wireguard Dashboard is stopped. \n"
start_wgd_debug
else
start_wgd_debug
fi
elif [ "$1" = "debug" ]; then
if check_wgd_status; then
printf "Wireguard Dashboard is already running. \n"
else
start_wgd_debug
fi
else
help
fi
fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

View File

@@ -1,133 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}">
</head>
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Wireguard Dashboard</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid">
<div class="row">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configurations</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link" href="/configuration/{{i['conf']}}">{{i['conf']}}</a></li>
{%endfor%}
</ul>
</div>
</nav>
</div>
</div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mt-4">
<div class="info mt-4">
<small class="text-muted"><strong>CONFIGURATION</strong></small>
<h1 class="mb-3">{{conf_data['name']}}</h1>
<div class="row">
<div class="col-sm">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['status']}}<span class="dot dot-{{conf_data['status']}}"></span></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][0]}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL RECIEVED</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][1]}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL SENT</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['total_data_usage'][2]}} GB</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{conf_data['public_key']}}</samp></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>LISTEN PORT</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{conf_data['listen_port']}}</samp></h6>
</div>
</div>
<hr>
</div>
{% for i in conf_data['peer_data']%}
<div class="card mb-3">
<div class="card-body">
<div class="row">
<div class="col-sm">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['status']}}<span class="dot dot-{{conf_data['peer_data'][i]['status']}}"></span></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>PEER</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{i}}</samp></h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>ALLOWED IP</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['allowed_ip']}}</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>LATEST HANDSHAKE</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['latest_handshake']}}</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>END POINT</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['endpoint']}}</h6>
</div>
<div class="w-100"></div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL DATA USAGE</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['total_data']}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL RECIEVED</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['total_recive']}} GB</h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>TOTAL SENT</strong></small>
<h6 style="text-transform: uppercase;">{{conf_data['peer_data'][i]['total_sent']}} GB</h6>
</div>
</div>
</div>
</div>
{%endfor%}
</main>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script>
<script>
setInterval(function(){
location.reload();
},10000)
</script>
</html>

View File

@@ -1,78 +0,0 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Wireguard Dashboard</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='dashboard.css') }}">
</head>
<body>
<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 mr-0 px-3" href="#">Wireguard Dashboard</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button" data-toggle="collapse"
data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</nav>
<div class="container-fluid">
<div class="row">
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="sidebar-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Configurations</span>
</h6>
<ul class="nav flex-column">
{% for i in conf%}
<li class="nav-item"><a class="nav-link" href="/configuration/{{i['conf']}}">{{i['conf']}}</a></li>
{%endfor%}
</ul>
</div>
</nav>
</div>
</div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
{% for i in conf%}
<div class="card mt-3">
<div class="card-body">
<a href="/configuration/{{i['conf']}}">
<h5 class="card-title">{{i['conf']}}</h5>
</a>
<div class="row">
<div class="col-sm">
<small class="text-muted"><strong>STATUS</strong></small>
<h6 style="text-transform: uppercase;">{{i['status']}}<span class="dot dot-{{i['status']}}"></span></h6>
</div>
<div class="col-sm">
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
<h6 style="text-transform: uppercase;"><samp>{{i['public_key']}}</samp></h6>
</div>
</div>
</div>
</div>
{%endfor%}
</main>
</div>
</body>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js"
integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s"
crossorigin="anonymous"></script>
<script>
// $.get("/get_conf", function(data, status){
// for (var i = 0; i < data['data'].length; i++){
// $(".nav").append('<li class="nav-item"><a class="nav-link" href="/conf/'+data['data'][i]['conf']+'">'+data['data'][i]['conf']+'</a></li>');
// $("main").append('<div class="card mt-3"><div class="card-body"><a href="/conf/'+data['data'][i]['conf']+'"><h5 class="card-title">'+data['data'][i]['conf']+'</h5></a><h6 class="card-subtitle mb-2 text-muted">Status: '+data['data'][i]['status']+'<span class="dot dot-'+data['data'][i]['status']+'"></span></h6></div></div>')
// }
// });
</script>
</html>

View File

@@ -1,5 +0,0 @@
import os
import subprocess
try: status = subprocess.check_output("wg show wg0", shell=True)
except Exception: print("false")
else:print(status.decode("UTF-8"))