2025-01-19 14:04:59 +08:00
|
|
|
import random, shutil, sqlite3, configparser, hashlib, ipaddress, json, os, secrets, subprocess
|
2025-05-14 09:24:29 +08:00
|
|
|
import time, re, uuid, bcrypt, psutil, pyotp, threading
|
2025-05-18 15:24:41 +08:00
|
|
|
import traceback
|
2025-01-13 16:47:15 +08:00
|
|
|
from uuid import uuid4
|
2024-12-26 00:06:37 +08:00
|
|
|
from zipfile import ZipFile
|
2021-12-28 22:53:51 +03:00
|
|
|
from datetime import datetime, timedelta
|
2025-05-18 15:24:41 +08:00
|
|
|
|
|
|
|
import sqlalchemy
|
2025-01-13 16:47:15 +08:00
|
|
|
from jinja2 import Template
|
2025-01-19 14:04:59 +08:00
|
|
|
from flask import Flask, request, render_template, session, send_file
|
2024-07-31 02:27:44 -04:00
|
|
|
from flask_cors import CORS
|
2021-12-26 02:26:39 +03:00
|
|
|
from icmplib import ping, traceroute
|
2024-06-18 03:16:42 +08:00
|
|
|
from flask.json.provider import DefaultJSONProvider
|
2025-02-16 17:42:32 +08:00
|
|
|
from itertools import islice
|
2025-05-14 09:24:29 +08:00
|
|
|
from modules.Utilities import (
|
|
|
|
RegexMatch, StringToBoolean,
|
2025-01-19 14:04:59 +08:00
|
|
|
ValidateIPAddressesWithRange, ValidateDNSAddress,
|
2024-11-25 22:11:51 +08:00
|
|
|
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
|
|
|
)
|
2025-02-13 23:04:27 -09:00
|
|
|
from packaging import version
|
2025-01-19 14:04:59 +08:00
|
|
|
from modules.Email import EmailSender
|
|
|
|
from modules.DashboardLogger import DashboardLogger
|
2025-01-22 15:46:04 +08:00
|
|
|
from modules.PeerJob import PeerJob
|
2025-02-08 15:45:09 +08:00
|
|
|
from modules.SystemStatus import SystemStatus
|
2025-05-08 19:03:26 +08:00
|
|
|
from modules.PeerShareLinks import PeerShareLinks
|
2025-05-13 21:36:15 +08:00
|
|
|
from modules.PeerJobs import PeerJobs
|
2025-05-14 09:24:29 +08:00
|
|
|
from modules.DashboardConfig import DashboardConfig
|
2025-05-18 15:24:41 +08:00
|
|
|
from modules.WireguardConfiguration import WireguardConfiguration
|
|
|
|
from modules.AmneziaWireguardConfiguration import AmneziaWireguardConfiguration
|
|
|
|
|
2025-05-13 21:36:15 +08:00
|
|
|
|
2025-02-08 15:45:09 +08:00
|
|
|
SystemStatus = SystemStatus()
|
2022-02-11 09:35:58 -05:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
CONFIGURATION_PATH = os.getenv('CONFIGURATION_PATH', '.')
|
2024-11-07 10:37:11 +08:00
|
|
|
app = Flask("WGDashboard", template_folder=os.path.abspath("./static/app/dist"))
|
2021-12-25 14:44:14 -05:00
|
|
|
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928
|
2024-06-18 03:16:42 +08:00
|
|
|
app.secret_key = secrets.token_urlsafe(32)
|
2024-08-14 01:17:47 -04:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
class CustomJsonEncoder(DefaultJSONProvider):
|
|
|
|
def __init__(self, app):
|
|
|
|
super().__init__(app)
|
|
|
|
|
|
|
|
def default(self, o):
|
2025-01-19 14:04:59 +08:00
|
|
|
if callable(getattr(o, "toJson", None)):
|
2024-06-18 03:16:42 +08:00
|
|
|
return o.toJson()
|
2025-03-12 00:44:36 +08:00
|
|
|
return super().default(self)
|
2024-06-18 03:16:42 +08:00
|
|
|
app.json = CustomJsonEncoder(app)
|
|
|
|
|
2024-11-25 22:11:51 +08:00
|
|
|
'''
|
|
|
|
Response Object
|
|
|
|
'''
|
2025-04-19 02:54:47 +08:00
|
|
|
def ResponseObject(status=True, message=None, data=None, status_code = 200) -> Flask.response_class:
|
2024-11-25 22:11:51 +08:00
|
|
|
response = Flask.make_response(app, {
|
|
|
|
"status": status,
|
|
|
|
"message": message,
|
|
|
|
"data": data
|
|
|
|
})
|
2025-04-19 02:54:47 +08:00
|
|
|
response.status_code = status_code
|
2024-11-25 22:11:51 +08:00
|
|
|
response.content_type = "application/json"
|
2025-01-19 14:04:59 +08:00
|
|
|
return response
|
2025-01-08 18:09:05 +08:00
|
|
|
|
2024-08-14 01:17:47 -04:00
|
|
|
DashboardConfig = DashboardConfig()
|
2025-01-08 18:09:05 +08:00
|
|
|
EmailSender = EmailSender(DashboardConfig)
|
2024-08-14 01:17:47 -04:00
|
|
|
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
|
|
|
cors = CORS(app, resources={rf"{APP_PREFIX}/api/*": {
|
|
|
|
"origins": "*",
|
|
|
|
"methods": "DELETE, POST, GET, OPTIONS",
|
|
|
|
"allow_headers": ["Content-Type", "wg-dashboard-apikey"]
|
|
|
|
}})
|
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
'''
|
|
|
|
API Routes
|
|
|
|
'''
|
2021-05-04 01:32:34 -04:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
@app.before_request
|
|
|
|
def auth_req():
|
2024-08-10 19:03:21 -04:00
|
|
|
if request.method.lower() == 'options':
|
2024-11-24 00:22:33 +08:00
|
|
|
return ResponseObject(True)
|
2024-08-11 01:48:13 -04:00
|
|
|
|
|
|
|
DashboardConfig.APIAccessed = False
|
2024-08-03 17:03:39 -04:00
|
|
|
if "api" in request.path:
|
|
|
|
if str(request.method) == "GET":
|
|
|
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args))
|
|
|
|
elif str(request.method) == "POST":
|
|
|
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Request Args: {str(request.args)} Body:{str(request.get_json())}")
|
|
|
|
|
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
authenticationRequired = DashboardConfig.GetConfig("Server", "auth_req")[1]
|
2024-08-02 17:27:28 -04:00
|
|
|
d = request.headers
|
2024-06-18 03:16:42 +08:00
|
|
|
if authenticationRequired:
|
2024-08-02 17:27:28 -04:00
|
|
|
apiKey = d.get('wg-dashboard-apikey')
|
2024-07-31 02:27:44 -04:00
|
|
|
apiKeyEnabled = DashboardConfig.GetConfig("Server", "dashboard_api_key")[1]
|
|
|
|
if apiKey is not None and len(apiKey) > 0 and apiKeyEnabled:
|
|
|
|
apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1
|
2024-08-03 17:03:39 -04:00
|
|
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}")
|
2024-07-31 02:27:44 -04:00
|
|
|
if not apiKeyExist:
|
2024-08-11 01:48:13 -04:00
|
|
|
DashboardConfig.APIAccessed = False
|
2024-07-31 02:27:44 -04:00
|
|
|
response = Flask.make_response(app, {
|
|
|
|
"status": False,
|
|
|
|
"message": "API Key does not exist",
|
|
|
|
"data": None
|
|
|
|
})
|
|
|
|
response.content_type = "application/json"
|
|
|
|
response.status_code = 401
|
|
|
|
return response
|
2024-08-11 01:48:13 -04:00
|
|
|
DashboardConfig.APIAccessed = True
|
2024-07-31 02:27:44 -04:00
|
|
|
else:
|
2024-08-11 01:48:13 -04:00
|
|
|
DashboardConfig.APIAccessed = False
|
2024-12-26 00:06:37 +08:00
|
|
|
whiteList = [
|
|
|
|
'/static/', 'validateAuthentication', 'authenticate', 'getDashboardConfiguration',
|
|
|
|
'getDashboardTheme', 'getDashboardVersion', 'sharePeer/get', 'isTotpEnabled', 'locale',
|
|
|
|
'/fileDownload'
|
|
|
|
]
|
|
|
|
|
|
|
|
if ("username" not in session
|
2024-08-14 01:17:47 -04:00
|
|
|
and (f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}/" != request.path
|
2024-12-26 00:06:37 +08:00
|
|
|
and f"{(APP_PREFIX if len(APP_PREFIX) > 0 else '')}" != request.path)
|
|
|
|
and len(list(filter(lambda x : x not in request.path, whiteList))) == len(whiteList)
|
2024-07-31 02:27:44 -04:00
|
|
|
):
|
|
|
|
response = Flask.make_response(app, {
|
|
|
|
"status": False,
|
|
|
|
"message": "Unauthorized access.",
|
|
|
|
"data": None
|
|
|
|
})
|
|
|
|
response.content_type = "application/json"
|
|
|
|
response.status_code = 401
|
|
|
|
return response
|
2021-05-04 21:26:40 -04:00
|
|
|
|
2024-08-14 01:17:47 -04:00
|
|
|
@app.route(f'{APP_PREFIX}/api/handshake', methods=["GET", "OPTIONS"])
|
2024-11-24 00:22:33 +08:00
|
|
|
def API_Handshake():
|
2024-08-10 19:03:21 -04:00
|
|
|
return ResponseObject(True)
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/validateAuthentication')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_ValidateAuthentication():
|
2024-11-24 00:22:33 +08:00
|
|
|
token = request.cookies.get("authToken")
|
|
|
|
if DashboardConfig.GetConfig("Server", "auth_req")[1]:
|
|
|
|
if token is None or token == "" or "username" not in session or session["username"] != token:
|
|
|
|
return ResponseObject(False, "Invalid authentication.")
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(True)
|
2021-08-14 17:13:16 -04:00
|
|
|
|
2024-11-24 00:22:33 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/requireAuthentication')
|
|
|
|
def API_RequireAuthentication():
|
|
|
|
return ResponseObject(data=DashboardConfig.GetConfig("Server", "auth_req")[1])
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/authenticate')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_AuthenticateLogin():
|
|
|
|
data = request.get_json()
|
2024-11-25 02:48:55 +08:00
|
|
|
if not DashboardConfig.GetConfig("Server", "auth_req")[1]:
|
|
|
|
return ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
|
|
|
|
2024-08-11 01:48:13 -04:00
|
|
|
if DashboardConfig.APIAccessed:
|
|
|
|
authToken = hashlib.sha256(f"{request.headers.get('wg-dashboard-apikey')}{datetime.now()}".encode()).hexdigest()
|
|
|
|
session['username'] = authToken
|
|
|
|
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
2024-08-15 16:55:34 -04:00
|
|
|
resp.set_cookie("authToken", authToken)
|
2024-08-11 01:48:13 -04:00
|
|
|
session.permanent = True
|
|
|
|
return resp
|
2024-06-18 03:16:42 +08:00
|
|
|
valid = bcrypt.checkpw(data['password'].encode("utf-8"),
|
|
|
|
DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
|
|
|
|
totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
|
|
|
|
totpValid = False
|
|
|
|
if totpEnabled:
|
|
|
|
totpValid = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now() == data['totp']
|
|
|
|
|
|
|
|
if (valid
|
|
|
|
and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]
|
|
|
|
and ((totpEnabled and totpValid) or not totpEnabled)
|
|
|
|
):
|
|
|
|
authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
|
|
|
|
session['username'] = authToken
|
|
|
|
resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
|
|
|
|
resp.set_cookie("authToken", authToken)
|
|
|
|
session.permanent = True
|
2024-08-03 17:03:39 -04:00
|
|
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {data['username']}")
|
2024-06-18 03:16:42 +08:00
|
|
|
return resp
|
2024-08-03 17:03:39 -04:00
|
|
|
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {data['username']}")
|
2024-06-18 03:16:42 +08:00
|
|
|
if totpEnabled:
|
|
|
|
return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
|
2021-12-29 12:17:44 -05:00
|
|
|
else:
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(False, "Sorry, your username or password is incorrect.")
|
2021-05-13 18:00:40 -04:00
|
|
|
|
2024-08-15 16:55:34 -04:00
|
|
|
@app.get(f'{APP_PREFIX}/api/signout')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_SignOut():
|
|
|
|
resp = ResponseObject(True, "")
|
|
|
|
resp.delete_cookie("authToken")
|
2024-11-25 01:46:27 +08:00
|
|
|
session.clear()
|
2024-06-18 03:16:42 +08:00
|
|
|
return resp
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-08-14 01:17:47 -04:00
|
|
|
@app.route(f'{APP_PREFIX}/api/getWireguardConfigurations', methods=["GET"])
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_getWireguardConfigurations():
|
2024-11-25 22:11:51 +08:00
|
|
|
InitWireguardConfigurationsList()
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()])
|
2022-01-02 14:44:27 -05:00
|
|
|
|
2024-08-14 01:17:47 -04:00
|
|
|
@app.route(f'{APP_PREFIX}/api/addWireguardConfiguration', methods=["POST"])
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_addWireguardConfiguration():
|
2022-01-06 15:17:43 -05:00
|
|
|
data = request.get_json()
|
2024-06-18 03:16:42 +08:00
|
|
|
requiredKeys = [
|
2024-12-04 17:50:16 +08:00
|
|
|
"ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol"
|
2024-06-18 03:16:42 +08:00
|
|
|
]
|
2024-10-25 23:34:07 +08:00
|
|
|
for i in requiredKeys:
|
|
|
|
if i not in data.keys():
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(False, "Please provide all required parameters.")
|
2024-12-04 17:50:16 +08:00
|
|
|
|
|
|
|
if data.get("Protocol") not in ProtocolsEnabled():
|
|
|
|
return ResponseObject(False, "Please provide a valid protocol: wg / awg.")
|
2024-06-18 03:16:42 +08:00
|
|
|
|
|
|
|
# Check duplicate names, ports, address
|
|
|
|
for i in WireguardConfigurations.values():
|
|
|
|
if i.Name == data['ConfigurationName']:
|
|
|
|
return ResponseObject(False,
|
|
|
|
f"Already have a configuration with the name \"{data['ConfigurationName']}\"",
|
|
|
|
"ConfigurationName")
|
|
|
|
|
|
|
|
if str(i.ListenPort) == str(data["ListenPort"]):
|
|
|
|
return ResponseObject(False,
|
|
|
|
f"Already have a configuration with the port \"{data['ListenPort']}\"",
|
|
|
|
"ListenPort")
|
|
|
|
|
|
|
|
if i.Address == data["Address"]:
|
|
|
|
return ResponseObject(False,
|
|
|
|
f"Already have a configuration with the address \"{data['Address']}\"",
|
|
|
|
"Address")
|
|
|
|
|
2024-10-25 23:34:07 +08:00
|
|
|
if "Backup" in data.keys():
|
2024-12-04 17:50:16 +08:00
|
|
|
path = {
|
|
|
|
"wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
|
|
|
|
"awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and
|
|
|
|
os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
|
|
|
|
protocol = "wg"
|
|
|
|
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and
|
|
|
|
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
|
|
|
|
protocol = "awg"
|
|
|
|
else:
|
|
|
|
return ResponseObject(False, "Backup does not exist")
|
2024-10-25 23:34:07 +08:00
|
|
|
|
|
|
|
shutil.copy(
|
2024-12-04 17:50:16 +08:00
|
|
|
os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]),
|
|
|
|
os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf')
|
2024-10-25 23:34:07 +08:00
|
|
|
)
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[data['ConfigurationName']] = (
|
|
|
|
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName'])) if protocol == 'wg' else (
|
|
|
|
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data, name=data['ConfigurationName']))
|
2024-10-25 23:34:07 +08:00
|
|
|
else:
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[data['ConfigurationName']] = (
|
|
|
|
WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data)) if data.get('Protocol') == 'wg' else (
|
|
|
|
AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data=data))
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject()
|
|
|
|
|
2025-04-23 19:24:50 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_toggleWireguardConfiguration():
|
|
|
|
configurationName = request.args.get('configurationName')
|
|
|
|
if configurationName is None or len(
|
|
|
|
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
|
2024-06-18 03:16:42 +08:00
|
|
|
toggleStatus, msg = WireguardConfigurations[configurationName].toggleConfiguration()
|
|
|
|
return ResponseObject(toggleStatus, msg, WireguardConfigurations[configurationName].Status)
|
|
|
|
|
2024-10-04 16:58:47 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/updateWireguardConfiguration')
|
|
|
|
def API_updateWireguardConfiguration():
|
|
|
|
data = request.get_json()
|
|
|
|
requiredKeys = ["Name"]
|
|
|
|
for i in requiredKeys:
|
|
|
|
if i not in data.keys():
|
|
|
|
return ResponseObject(False, "Please provide these following field: " + ", ".join(requiredKeys))
|
|
|
|
name = data.get("Name")
|
|
|
|
if name not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2024-10-04 16:58:47 +08:00
|
|
|
|
|
|
|
status, msg = WireguardConfigurations[name].updateConfigurationSettings(data)
|
|
|
|
|
|
|
|
return ResponseObject(status, message=msg, data=WireguardConfigurations[name])
|
2024-06-18 03:16:42 +08:00
|
|
|
|
2024-12-06 20:27:04 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRawFile')
|
|
|
|
def API_GetWireguardConfigurationRawFile():
|
|
|
|
configurationName = request.args.get('configurationName')
|
|
|
|
if configurationName is None or len(
|
|
|
|
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Please provide a valid configuration name", status_code=404)
|
2024-12-06 20:27:04 +08:00
|
|
|
|
|
|
|
return ResponseObject(data={
|
|
|
|
"path": WireguardConfigurations[configurationName].configPath,
|
|
|
|
"content": WireguardConfigurations[configurationName].getRawConfigurationFile()
|
|
|
|
})
|
|
|
|
|
|
|
|
@app.post(f'{APP_PREFIX}/api/updateWireguardConfigurationRawFile')
|
|
|
|
def API_UpdateWireguardConfigurationRawFile():
|
|
|
|
data = request.get_json()
|
|
|
|
configurationName = data.get('configurationName')
|
|
|
|
rawConfiguration = data.get('rawConfiguration')
|
|
|
|
if configurationName is None or len(
|
|
|
|
configurationName) == 0 or configurationName not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Please provide a valid configuration name")
|
|
|
|
if rawConfiguration is None or len(rawConfiguration) == 0:
|
|
|
|
return ResponseObject(False, "Please provide content")
|
|
|
|
|
|
|
|
status, err = WireguardConfigurations[configurationName].updateRawConfigurationFile(rawConfiguration)
|
|
|
|
|
|
|
|
return ResponseObject(status=status, message=err)
|
|
|
|
|
2024-10-25 00:19:27 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfiguration')
|
|
|
|
def API_deleteWireguardConfiguration():
|
|
|
|
data = request.get_json()
|
2025-03-28 00:13:38 +08:00
|
|
|
if "ConfigurationName" not in data.keys() or data.get("ConfigurationName") is None or data.get("ConfigurationName") not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Please provide the configuration name you want to delete", status_code=404)
|
2025-05-18 15:24:41 +08:00
|
|
|
rp = WireguardConfigurations.pop(data.get("ConfigurationName"))
|
|
|
|
|
|
|
|
status = rp.deleteConfiguration()
|
|
|
|
if not status:
|
|
|
|
WireguardConfigurations[data.get("ConfigurationName")] = rp
|
2024-10-25 00:19:27 +08:00
|
|
|
return ResponseObject(status)
|
|
|
|
|
2024-11-06 18:36:55 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/renameWireguardConfiguration')
|
|
|
|
def API_renameWireguardConfiguration():
|
|
|
|
data = request.get_json()
|
2025-03-28 00:13:38 +08:00
|
|
|
keys = ["ConfigurationName", "NewConfigurationName"]
|
2024-11-06 18:36:55 +08:00
|
|
|
for k in keys:
|
|
|
|
if (k not in data.keys() or data.get(k) is None or len(data.get(k)) == 0 or
|
2025-03-28 00:13:38 +08:00
|
|
|
(k == "ConfigurationName" and data.get(k) not in WireguardConfigurations.keys())):
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Please provide the configuration name you want to rename", status_code=404)
|
2025-05-18 15:24:41 +08:00
|
|
|
|
|
|
|
if data.get("NewConfigurationName") in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Configuration name already exist", status_code=400)
|
|
|
|
|
|
|
|
rc = WireguardConfigurations.pop(data.get("ConfigurationName"))
|
|
|
|
|
|
|
|
status, message = rc.renameConfiguration(data.get("NewConfigurationName"))
|
2024-11-06 18:36:55 +08:00
|
|
|
if status:
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[data.get("NewConfigurationName")] = (WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data.get("NewConfigurationName")) if rc.Protocol == 'wg' else AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, data.get("NewConfigurationName")))
|
|
|
|
else:
|
|
|
|
WireguardConfigurations[data.get("ConfigurationName")] = rc
|
2024-11-06 18:36:55 +08:00
|
|
|
return ResponseObject(status, message)
|
|
|
|
|
2024-12-30 20:30:09 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationRealtimeTraffic')
|
|
|
|
def API_getWireguardConfigurationRealtimeTraffic():
|
|
|
|
configurationName = request.args.get('configurationName')
|
|
|
|
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2024-12-30 20:30:09 +08:00
|
|
|
return ResponseObject(data=WireguardConfigurations[configurationName].getRealtimeTrafficUsage())
|
|
|
|
|
2024-10-15 00:30:20 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationBackup')
|
|
|
|
def API_getWireguardConfigurationBackup():
|
|
|
|
configurationName = request.args.get('configurationName')
|
|
|
|
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2024-10-15 00:30:20 +08:00
|
|
|
return ResponseObject(data=WireguardConfigurations[configurationName].getBackups())
|
|
|
|
|
2024-10-25 00:19:27 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getAllWireguardConfigurationBackup')
|
|
|
|
def API_getAllWireguardConfigurationBackup():
|
|
|
|
data = {
|
|
|
|
"ExistingConfigurations": {},
|
|
|
|
"NonExistingConfigurations": {}
|
|
|
|
}
|
|
|
|
existingConfiguration = WireguardConfigurations.keys()
|
|
|
|
for i in existingConfiguration:
|
|
|
|
b = WireguardConfigurations[i].getBackups(True)
|
|
|
|
if len(b) > 0:
|
|
|
|
data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True)
|
2024-12-04 17:50:16 +08:00
|
|
|
|
|
|
|
for protocol in ProtocolsEnabled():
|
|
|
|
directory = os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup')
|
|
|
|
files = [(file, os.path.getctime(os.path.join(directory, file)))
|
|
|
|
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
|
|
|
|
files.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
|
|
|
|
for f, ct in files:
|
|
|
|
if RegexMatch(r"^(.*)_(.*)\.(conf)$", f):
|
|
|
|
s = re.search(r"^(.*)_(.*)\.(conf)$", f)
|
|
|
|
name = s.group(1)
|
|
|
|
if name not in existingConfiguration:
|
|
|
|
if name not in data['NonExistingConfigurations'].keys():
|
|
|
|
data['NonExistingConfigurations'][name] = []
|
|
|
|
|
|
|
|
date = s.group(2)
|
|
|
|
d = {
|
|
|
|
"protocol": protocol,
|
|
|
|
"filename": f,
|
|
|
|
"backupDate": date,
|
|
|
|
"content": open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
|
|
|
|
}
|
|
|
|
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
|
|
|
|
d['database'] = True
|
|
|
|
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
|
|
|
|
data['NonExistingConfigurations'][name].append(d)
|
2024-10-25 00:19:27 +08:00
|
|
|
return ResponseObject(data=data)
|
|
|
|
|
2024-10-16 17:44:49 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
|
|
|
|
def API_createWireguardConfigurationBackup():
|
|
|
|
configurationName = request.args.get('configurationName')
|
|
|
|
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2024-12-06 20:27:04 +08:00
|
|
|
return ResponseObject(status=WireguardConfigurations[configurationName].backupConfigurationFile()[0],
|
2024-10-16 17:44:49 +08:00
|
|
|
data=WireguardConfigurations[configurationName].getBackups())
|
|
|
|
|
|
|
|
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfigurationBackup')
|
|
|
|
def API_deleteWireguardConfigurationBackup():
|
|
|
|
data = request.get_json()
|
2025-04-07 18:45:12 +08:00
|
|
|
if ("ConfigurationName" not in data.keys() or
|
|
|
|
"BackupFileName" not in data.keys() or
|
|
|
|
len(data['ConfigurationName']) == 0 or
|
|
|
|
len(data['BackupFileName']) == 0):
|
2024-10-16 17:44:49 +08:00
|
|
|
return ResponseObject(False,
|
2025-04-19 02:54:47 +08:00
|
|
|
"Please provide configurationName and backupFileName in body", status_code=400)
|
2025-04-07 18:45:12 +08:00
|
|
|
configurationName = data['ConfigurationName']
|
|
|
|
backupFileName = data['BackupFileName']
|
2024-10-16 17:44:49 +08:00
|
|
|
if configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2024-10-16 17:44:49 +08:00
|
|
|
|
2025-04-19 02:54:47 +08:00
|
|
|
status = WireguardConfigurations[configurationName].deleteBackup(backupFileName)
|
|
|
|
return ResponseObject(status=status, message=(None if status else 'Backup file does not exist'),
|
|
|
|
status_code = (200 if status else 404))
|
2024-10-16 17:44:49 +08:00
|
|
|
|
2024-12-26 00:06:37 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/downloadWireguardConfigurationBackup')
|
|
|
|
def API_downloadWireguardConfigurationBackup():
|
|
|
|
configurationName = request.args.get('configurationName')
|
|
|
|
backupFileName = request.args.get('backupFileName')
|
|
|
|
if configurationName is None or configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2024-12-26 00:06:37 +08:00
|
|
|
status, zip = WireguardConfigurations[configurationName].downloadBackup(backupFileName)
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(status, data=zip, status_code=(200 if status else 404))
|
2024-12-26 00:06:37 +08:00
|
|
|
|
2024-10-16 17:44:49 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/restoreWireguardConfigurationBackup')
|
|
|
|
def API_restoreWireguardConfigurationBackup():
|
|
|
|
data = request.get_json()
|
2025-04-07 18:45:12 +08:00
|
|
|
if ("ConfigurationName" not in data.keys() or
|
|
|
|
"BackupFileName" not in data.keys() or
|
|
|
|
len(data['ConfigurationName']) == 0 or
|
|
|
|
len(data['BackupFileName']) == 0):
|
2024-10-16 17:44:49 +08:00
|
|
|
return ResponseObject(False,
|
2025-04-19 02:54:47 +08:00
|
|
|
"Please provide ConfigurationName and BackupFileName in body", status_code=400)
|
2025-04-07 18:45:12 +08:00
|
|
|
configurationName = data['ConfigurationName']
|
|
|
|
backupFileName = data['BackupFileName']
|
2024-10-16 17:44:49 +08:00
|
|
|
if configurationName not in WireguardConfigurations.keys():
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
|
|
|
|
|
|
|
status = WireguardConfigurations[configurationName].restoreBackup(backupFileName)
|
|
|
|
return ResponseObject(status=status, message=(None if status else 'Restore backup failed'))
|
2024-10-16 17:44:49 +08:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getDashboardConfiguration')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_getDashboardConfiguration():
|
|
|
|
return ResponseObject(data=DashboardConfig.toJson())
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/updateDashboardConfigurationItem')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_updateDashboardConfigurationItem():
|
2020-12-26 23:42:41 -05:00
|
|
|
data = request.get_json()
|
2024-06-18 03:16:42 +08:00
|
|
|
if "section" not in data.keys() or "key" not in data.keys() or "value" not in data.keys():
|
|
|
|
return ResponseObject(False, "Invalid request.")
|
|
|
|
valid, msg = DashboardConfig.SetConfig(
|
|
|
|
data["section"], data["key"], data['value'])
|
|
|
|
if not valid:
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, msg, status_code=404)
|
2024-09-06 16:31:54 +08:00
|
|
|
if data['section'] == "Server":
|
|
|
|
if data['key'] == 'wg_conf_path':
|
2024-12-02 15:09:54 +08:00
|
|
|
WireguardConfigurations.clear()
|
2024-09-06 16:31:54 +08:00
|
|
|
WireguardConfigurations.clear()
|
2025-01-24 00:01:29 +08:00
|
|
|
InitWireguardConfigurationsList()
|
2024-11-02 14:26:47 +06:00
|
|
|
return ResponseObject(True, data=DashboardConfig.GetConfig(data["section"], data["key"])[1])
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getDashboardAPIKeys')
|
2024-07-30 18:45:05 -04:00
|
|
|
def API_getDashboardAPIKeys():
|
|
|
|
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
|
|
|
|
return ResponseObject(data=DashboardConfig.DashboardAPIKeys)
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "WGDashboard API Keys function is disabled")
|
2024-07-30 18:45:05 -04:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/newDashboardAPIKey')
|
2024-07-30 18:45:05 -04:00
|
|
|
def API_newDashboardAPIKey():
|
|
|
|
data = request.get_json()
|
|
|
|
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
|
|
|
|
try:
|
2025-04-19 02:54:47 +08:00
|
|
|
if data['NeverExpire']:
|
2024-07-30 18:45:05 -04:00
|
|
|
expiredAt = None
|
|
|
|
else:
|
2024-08-06 19:15:00 -04:00
|
|
|
expiredAt = datetime.strptime(data['ExpiredAt'], '%Y-%m-%d %H:%M:%S')
|
2024-07-30 18:45:05 -04:00
|
|
|
DashboardConfig.createAPIKeys(expiredAt)
|
|
|
|
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
|
|
|
|
except Exception as e:
|
|
|
|
return ResponseObject(False, str(e))
|
|
|
|
return ResponseObject(False, "Dashboard API Keys function is disbaled")
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/deleteDashboardAPIKey')
|
2024-07-30 18:45:05 -04:00
|
|
|
def API_deleteDashboardAPIKey():
|
|
|
|
data = request.get_json()
|
|
|
|
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
|
|
|
|
if len(data['Key']) > 0 and len(list(filter(lambda x : x.Key == data['Key'], DashboardConfig.DashboardAPIKeys))) > 0:
|
|
|
|
DashboardConfig.deleteAPIKey(data['Key'])
|
|
|
|
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
|
2025-04-19 02:54:47 +08:00
|
|
|
else:
|
|
|
|
return ResponseObject(False, "API Key does not exist", status_code=404)
|
2024-07-30 18:45:05 -04:00
|
|
|
return ResponseObject(False, "Dashboard API Keys function is disbaled")
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/updatePeerSettings/<configName>')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_updatePeerSettings(configName):
|
2021-04-02 20:48:00 -04:00
|
|
|
data = request.get_json()
|
|
|
|
id = data['id']
|
2024-06-18 03:16:42 +08:00
|
|
|
if len(id) > 0 and configName in WireguardConfigurations.keys():
|
|
|
|
name = data['name']
|
|
|
|
private_key = data['private_key']
|
|
|
|
dns_addresses = data['DNS']
|
|
|
|
allowed_ip = data['allowed_ip']
|
|
|
|
endpoint_allowed_ip = data['endpoint_allowed_ip']
|
|
|
|
preshared_key = data['preshared_key']
|
|
|
|
mtu = data['mtu']
|
|
|
|
keepalive = data['keepalive']
|
|
|
|
wireguardConfig = WireguardConfigurations[configName]
|
|
|
|
foundPeer, peer = wireguardConfig.searchPeer(id)
|
|
|
|
if foundPeer:
|
2024-12-03 02:34:45 +08:00
|
|
|
if wireguardConfig.Protocol == 'wg':
|
2025-05-18 15:24:41 +08:00
|
|
|
status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses,
|
2024-12-03 02:34:45 +08:00
|
|
|
allowed_ip, endpoint_allowed_ip, mtu, keepalive)
|
2025-05-18 15:24:41 +08:00
|
|
|
return ResponseObject(status, msg)
|
|
|
|
status, msg = peer.updatePeer(name, private_key, preshared_key, dns_addresses,
|
2025-05-01 22:40:53 +08:00
|
|
|
allowed_ip, endpoint_allowed_ip, mtu, keepalive, "off")
|
2025-05-18 15:24:41 +08:00
|
|
|
return ResponseObject(status, msg)
|
2024-12-03 02:34:45 +08:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(False, "Peer does not exist")
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/resetPeerData/<configName>')
|
2024-08-08 23:27:13 -04:00
|
|
|
def API_resetPeerData(configName):
|
|
|
|
data = request.get_json()
|
|
|
|
id = data['id']
|
|
|
|
type = data['type']
|
|
|
|
if len(id) == 0 or configName not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Configuration/Peer does not exist")
|
|
|
|
wgc = WireguardConfigurations.get(configName)
|
|
|
|
foundPeer, peer = wgc.searchPeer(id)
|
|
|
|
if not foundPeer:
|
|
|
|
return ResponseObject(False, "Configuration/Peer does not exist")
|
2025-03-07 00:52:51 +08:00
|
|
|
|
|
|
|
resetStatus = peer.resetDataUsage(type)
|
|
|
|
if resetStatus:
|
|
|
|
wgc.restrictPeers([id])
|
|
|
|
wgc.allowAccessPeers([id])
|
|
|
|
|
|
|
|
return ResponseObject(status=resetStatus)
|
2024-06-18 03:16:42 +08:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/deletePeers/<configName>')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_deletePeers(configName: str) -> ResponseObject:
|
|
|
|
data = request.get_json()
|
|
|
|
peers = data['peers']
|
|
|
|
if configName in WireguardConfigurations.keys():
|
|
|
|
if len(peers) == 0:
|
2025-05-02 11:52:39 +02:00
|
|
|
return ResponseObject(False, "Please specify one or more peers", status_code=400)
|
2024-06-18 03:16:42 +08:00
|
|
|
configuration = WireguardConfigurations.get(configName)
|
2025-05-18 15:24:41 +08:00
|
|
|
status, msg = configuration.deletePeers(peers)
|
|
|
|
return ResponseObject(status, msg)
|
2021-04-02 20:48:00 -04:00
|
|
|
|
2025-05-02 11:52:39 +02:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2021-12-26 02:26:39 +03:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/restrictPeers/<configName>')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_restrictPeers(configName: str) -> ResponseObject:
|
2021-04-02 20:48:00 -04:00
|
|
|
data = request.get_json()
|
2024-06-18 03:16:42 +08:00
|
|
|
peers = data['peers']
|
|
|
|
if configName in WireguardConfigurations.keys():
|
|
|
|
if len(peers) == 0:
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Please specify one or more peers")
|
2024-06-18 03:16:42 +08:00
|
|
|
configuration = WireguardConfigurations.get(configName)
|
2025-05-18 15:24:41 +08:00
|
|
|
status, msg = configuration.restrictPeers(peers)
|
|
|
|
return ResponseObject(status, msg)
|
2025-04-19 02:54:47 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist", status_code=404)
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/sharePeer/create')
|
2024-08-06 10:17:14 -04:00
|
|
|
def API_sharePeer_create():
|
|
|
|
data: dict[str, str] = request.get_json()
|
|
|
|
Configuration = data.get('Configuration')
|
|
|
|
Peer = data.get('Peer')
|
|
|
|
ExpireDate = data.get('ExpireDate')
|
|
|
|
if Configuration is None or Peer is None:
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Please specify configuration and peers")
|
2024-08-06 10:17:14 -04:00
|
|
|
activeLink = AllPeerShareLinks.getLink(Configuration, Peer)
|
|
|
|
if len(activeLink) > 0:
|
2024-11-29 14:42:13 +08:00
|
|
|
return ResponseObject(True,
|
|
|
|
"This peer is already sharing. Please view data for shared link.",
|
|
|
|
data=activeLink[0]
|
|
|
|
)
|
2025-05-08 17:27:49 +08:00
|
|
|
status, message = AllPeerShareLinks.addLink(Configuration, Peer, datetime.strptime(ExpireDate, "%Y-%m-%d %H:%M:%S"))
|
2024-08-06 10:17:14 -04:00
|
|
|
if not status:
|
|
|
|
return ResponseObject(status, message)
|
|
|
|
return ResponseObject(data=AllPeerShareLinks.getLinkByID(message))
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/sharePeer/update')
|
2024-08-06 10:17:14 -04:00
|
|
|
def API_sharePeer_update():
|
|
|
|
data: dict[str, str] = request.get_json()
|
|
|
|
ShareID: str = data.get("ShareID")
|
|
|
|
ExpireDate: str = data.get("ExpireDate")
|
|
|
|
|
|
|
|
if ShareID is None:
|
|
|
|
return ResponseObject(False, "Please specify ShareID")
|
|
|
|
|
|
|
|
if len(AllPeerShareLinks.getLinkByID(ShareID)) == 0:
|
|
|
|
return ResponseObject(False, "ShareID does not exist")
|
|
|
|
|
2025-05-08 17:27:49 +08:00
|
|
|
status, message = AllPeerShareLinks.updateLinkExpireDate(ShareID, datetime.strptime(ExpireDate, "%Y-%m-%d %H:%M:%S"))
|
2024-08-06 10:17:14 -04:00
|
|
|
if not status:
|
|
|
|
return ResponseObject(status, message)
|
|
|
|
return ResponseObject(data=AllPeerShareLinks.getLinkByID(ShareID))
|
2024-06-18 03:16:42 +08:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/sharePeer/get')
|
2024-08-07 00:37:05 -04:00
|
|
|
def API_sharePeer_get():
|
|
|
|
data = request.args
|
|
|
|
ShareID = data.get("ShareID")
|
|
|
|
if ShareID is None or len(ShareID) == 0:
|
|
|
|
return ResponseObject(False, "Please provide ShareID")
|
|
|
|
link = AllPeerShareLinks.getLinkByID(ShareID)
|
|
|
|
if len(link) == 0:
|
|
|
|
return ResponseObject(False, "This link is either expired to invalid")
|
|
|
|
l = link[0]
|
|
|
|
if l.Configuration not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "The peer you're looking for does not exist")
|
|
|
|
c = WireguardConfigurations.get(l.Configuration)
|
|
|
|
fp, p = c.searchPeer(l.Peer)
|
|
|
|
if not fp:
|
|
|
|
return ResponseObject(False, "The peer you're looking for does not exist")
|
|
|
|
|
|
|
|
return ResponseObject(data=p.downloadPeer())
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/allowAccessPeers/<configName>')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_allowAccessPeers(configName: str) -> ResponseObject:
|
2021-08-05 23:15:50 -04:00
|
|
|
data = request.get_json()
|
2024-06-18 03:16:42 +08:00
|
|
|
peers = data['peers']
|
|
|
|
if configName in WireguardConfigurations.keys():
|
|
|
|
if len(peers) == 0:
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Please specify one or more peers")
|
2024-06-18 03:16:42 +08:00
|
|
|
configuration = WireguardConfigurations.get(configName)
|
2025-05-18 15:24:41 +08:00
|
|
|
status, msg = configuration.allowAccessPeers(peers)
|
|
|
|
return ResponseObject(status, msg)
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
2021-08-14 17:13:16 -04:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/addPeers/<configName>')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_addPeers(configName):
|
|
|
|
if configName in WireguardConfigurations.keys():
|
2024-10-02 17:09:35 +08:00
|
|
|
try:
|
|
|
|
data: dict = request.get_json()
|
2024-08-19 16:50:00 -04:00
|
|
|
|
2024-10-02 17:09:35 +08:00
|
|
|
bulkAdd: bool = data.get("bulkAdd", False)
|
|
|
|
bulkAddAmount: int = data.get('bulkAddAmount', 0)
|
|
|
|
preshared_key_bulkAdd: bool = data.get('preshared_key_bulkAdd', False)
|
2025-02-08 16:40:20 +08:00
|
|
|
|
2024-10-02 17:09:35 +08:00
|
|
|
public_key: str = data.get('public_key', "")
|
2025-02-10 16:17:00 +08:00
|
|
|
allowed_ips: list[str] = data.get('allowed_ips', [])
|
2025-02-16 17:42:32 +08:00
|
|
|
allowed_ips_validation: bool = data.get('allowed_ips_validation', True)
|
2024-08-08 23:27:13 -04:00
|
|
|
|
2024-10-02 17:09:35 +08:00
|
|
|
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
|
|
|
|
dns_addresses: str = data.get('DNS', DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1])
|
|
|
|
mtu: int = data.get('mtu', int(DashboardConfig.GetConfig("Peers", "peer_MTU")[1]))
|
|
|
|
keep_alive: int = data.get('keepalive', int(DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]))
|
2025-02-08 16:40:20 +08:00
|
|
|
preshared_key: str = data.get('preshared_key', "")
|
2024-10-02 17:09:35 +08:00
|
|
|
|
|
|
|
if type(mtu) is not int or mtu < 0 or mtu > 1460:
|
|
|
|
mtu = int(DashboardConfig.GetConfig("Peers", "peer_MTU")[1])
|
|
|
|
if type(keep_alive) is not int or keep_alive < 0:
|
|
|
|
keep_alive = int(DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1])
|
|
|
|
config = WireguardConfigurations.get(configName)
|
|
|
|
if not config.getStatus():
|
|
|
|
config.toggleConfiguration()
|
2025-02-16 23:07:16 +08:00
|
|
|
ipStatus, availableIps = config.getAvailableIP(-1)
|
|
|
|
ipCountStatus, numberOfAvailableIPs = config.getNumberOfAvailableIP()
|
2025-02-16 17:42:32 +08:00
|
|
|
defaultIPSubnet = list(availableIps.keys())[0]
|
2024-10-02 17:09:35 +08:00
|
|
|
if bulkAdd:
|
|
|
|
if type(preshared_key_bulkAdd) is not bool:
|
|
|
|
preshared_key_bulkAdd = False
|
|
|
|
if type(bulkAddAmount) is not int or bulkAddAmount < 1:
|
|
|
|
return ResponseObject(False, "Please specify amount of peers you want to add")
|
2025-02-16 17:42:32 +08:00
|
|
|
if not ipStatus:
|
2024-10-02 17:09:35 +08:00
|
|
|
return ResponseObject(False, "No more available IP can assign")
|
2025-02-16 17:42:32 +08:00
|
|
|
if len(availableIps.keys()) == 0:
|
|
|
|
return ResponseObject(False, "This configuration does not have any IP address available")
|
2025-02-16 23:07:16 +08:00
|
|
|
if bulkAddAmount > sum(list(numberOfAvailableIPs.values())):
|
2024-10-02 17:09:35 +08:00
|
|
|
return ResponseObject(False,
|
2025-02-16 23:07:16 +08:00
|
|
|
f"The maximum number of peers can add is {sum(list(numberOfAvailableIPs.values()))}")
|
2024-10-02 17:09:35 +08:00
|
|
|
keyPairs = []
|
2025-02-16 23:07:16 +08:00
|
|
|
addedCount = 0
|
|
|
|
for subnet in availableIps.keys():
|
|
|
|
for ip in availableIps[subnet]:
|
|
|
|
newPrivateKey = GenerateWireguardPrivateKey()[1]
|
|
|
|
addedCount += 1
|
|
|
|
keyPairs.append({
|
|
|
|
"private_key": newPrivateKey,
|
|
|
|
"id": GenerateWireguardPublicKey(newPrivateKey)[1],
|
|
|
|
"preshared_key": (GenerateWireguardPrivateKey()[1] if preshared_key_bulkAdd else ""),
|
|
|
|
"allowed_ip": ip,
|
|
|
|
"name": f"BulkPeer_{(addedCount + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
|
|
|
"DNS": dns_addresses,
|
|
|
|
"endpoint_allowed_ip": endpoint_allowed_ip,
|
|
|
|
"mtu": mtu,
|
|
|
|
"keepalive": keep_alive,
|
2025-05-01 22:40:53 +08:00
|
|
|
"advanced_security": "off"
|
2025-02-16 23:07:16 +08:00
|
|
|
})
|
|
|
|
if addedCount == bulkAddAmount:
|
|
|
|
break
|
|
|
|
if addedCount == bulkAddAmount:
|
|
|
|
break
|
|
|
|
if len(keyPairs) == 0 or (bulkAdd and len(keyPairs) != bulkAddAmount):
|
2024-10-02 17:09:35 +08:00
|
|
|
return ResponseObject(False, "Generating key pairs by bulk failed")
|
2025-02-10 16:17:00 +08:00
|
|
|
status, result = config.addPeers(keyPairs)
|
|
|
|
return ResponseObject(status=status, message=result['message'], data=result['peers'])
|
2024-10-02 17:09:35 +08:00
|
|
|
|
|
|
|
else:
|
|
|
|
if config.searchPeer(public_key)[0] is True:
|
|
|
|
return ResponseObject(False, f"This peer already exist")
|
|
|
|
name = data.get("name", "")
|
|
|
|
private_key = data.get("private_key", "")
|
2025-04-19 02:54:47 +08:00
|
|
|
|
|
|
|
if len(public_key) == 0:
|
|
|
|
if len(private_key) == 0:
|
|
|
|
private_key = GenerateWireguardPrivateKey()[1]
|
|
|
|
public_key = GenerateWireguardPublicKey(private_key)[1]
|
|
|
|
else:
|
|
|
|
public_key = GenerateWireguardPublicKey(private_key)[1]
|
|
|
|
else:
|
|
|
|
if len(private_key) > 0:
|
|
|
|
genPub = GenerateWireguardPublicKey(private_key)[1]
|
|
|
|
# Check if provided pubkey match provided private key
|
|
|
|
if public_key != genPub:
|
|
|
|
return ResponseObject(False, "Provided Public Key does not match provided Private Key")
|
|
|
|
|
|
|
|
# if len(public_key) == 0 and len(private_key) == 0:
|
|
|
|
# private_key = GenerateWireguardPrivateKey()[1]
|
|
|
|
# public_key = GenerateWireguardPublicKey(private_key)[1]
|
|
|
|
# elif len(public_key) == 0 and len(private_key) > 0:
|
|
|
|
# public_key = GenerateWireguardPublicKey(private_key)[1]
|
2025-02-10 16:17:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
if len(allowed_ips) == 0:
|
2025-02-16 17:42:32 +08:00
|
|
|
if ipStatus:
|
2025-02-16 23:07:16 +08:00
|
|
|
for subnet in availableIps.keys():
|
|
|
|
for ip in availableIps[subnet]:
|
|
|
|
allowed_ips = [ip]
|
|
|
|
break
|
|
|
|
break
|
2025-02-10 16:17:00 +08:00
|
|
|
else:
|
|
|
|
return ResponseObject(False, "No more available IP can assign")
|
2025-02-08 16:40:20 +08:00
|
|
|
|
2025-02-16 17:42:32 +08:00
|
|
|
if allowed_ips_validation:
|
2025-02-08 16:40:20 +08:00
|
|
|
for i in allowed_ips:
|
2025-02-16 17:42:32 +08:00
|
|
|
found = False
|
2025-02-16 23:07:16 +08:00
|
|
|
for subnet in availableIps.keys():
|
|
|
|
network = ipaddress.ip_network(subnet, False)
|
|
|
|
ap = ipaddress.ip_network(i)
|
|
|
|
if network.version == ap.version and ap.subnet_of(network):
|
2025-02-16 17:42:32 +08:00
|
|
|
found = True
|
2025-02-16 23:07:16 +08:00
|
|
|
|
2025-02-16 17:42:32 +08:00
|
|
|
if not found:
|
2025-02-08 16:40:20 +08:00
|
|
|
return ResponseObject(False, f"This IP is not available: {i}")
|
|
|
|
|
2025-02-10 16:17:00 +08:00
|
|
|
status, result = config.addPeers([
|
2024-11-06 20:58:53 +08:00
|
|
|
{
|
|
|
|
"name": name,
|
|
|
|
"id": public_key,
|
|
|
|
"private_key": private_key,
|
|
|
|
"allowed_ip": ','.join(allowed_ips),
|
|
|
|
"preshared_key": preshared_key,
|
|
|
|
"endpoint_allowed_ip": endpoint_allowed_ip,
|
|
|
|
"DNS": dns_addresses,
|
|
|
|
"mtu": mtu,
|
2024-12-03 02:34:45 +08:00
|
|
|
"keepalive": keep_alive,
|
2025-05-01 22:40:53 +08:00
|
|
|
"advanced_security": "off"
|
2024-11-06 20:58:53 +08:00
|
|
|
}]
|
|
|
|
)
|
2025-02-10 16:17:00 +08:00
|
|
|
return ResponseObject(status=status, message=result['message'], data=result['peers'])
|
2024-10-02 17:09:35 +08:00
|
|
|
except Exception as e:
|
2025-02-16 17:42:32 +08:00
|
|
|
print(e, str(e.__traceback__))
|
2024-10-02 17:09:35 +08:00
|
|
|
return ResponseObject(False, "Add peers failed. Please see data for specific issue")
|
2024-06-18 03:16:42 +08:00
|
|
|
|
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f"{APP_PREFIX}/api/downloadPeer/<configName>")
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_downloadPeer(configName):
|
|
|
|
data = request.args
|
|
|
|
if configName not in WireguardConfigurations.keys():
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
2024-06-18 03:16:42 +08:00
|
|
|
configuration = WireguardConfigurations[configName]
|
|
|
|
peerFound, peer = configuration.searchPeer(data['id'])
|
|
|
|
if len(data['id']) == 0 or not peerFound:
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Peer does not exist")
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(data=peer.downloadPeer())
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f"{APP_PREFIX}/api/downloadAllPeers/<configName>")
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_downloadAllPeers(configName):
|
|
|
|
if configName not in WireguardConfigurations.keys():
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
2024-06-18 03:16:42 +08:00
|
|
|
configuration = WireguardConfigurations[configName]
|
|
|
|
peerData = []
|
|
|
|
untitledPeer = 0
|
|
|
|
for i in configuration.Peers:
|
|
|
|
file = i.downloadPeer()
|
2025-01-16 18:36:06 +08:00
|
|
|
if file["fileName"] == "UntitledPeer":
|
2024-06-18 03:16:42 +08:00
|
|
|
file["fileName"] = str(untitledPeer) + "_" + file["fileName"]
|
|
|
|
untitledPeer += 1
|
|
|
|
peerData.append(file)
|
|
|
|
return ResponseObject(data=peerData)
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f"{APP_PREFIX}/api/getAvailableIPs/<configName>")
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_getAvailableIPs(configName):
|
2024-11-25 22:11:51 +08:00
|
|
|
if configName not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
|
|
|
status, ips = WireguardConfigurations.get(configName).getAvailableIP()
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(status=status, data=ips)
|
|
|
|
|
2025-02-16 17:42:32 +08:00
|
|
|
@app.get(f"{APP_PREFIX}/api/getNumberOfAvailableIPs/<configName>")
|
|
|
|
def API_getNumberOfAvailableIPs(configName):
|
|
|
|
if configName not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
|
|
|
status, ips = WireguardConfigurations.get(configName).getNumberOfAvailableIP()
|
|
|
|
return ResponseObject(status=status, data=ips)
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationInfo')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_getConfigurationInfo():
|
|
|
|
configurationName = request.args.get("configurationName")
|
|
|
|
if not configurationName or configurationName not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Please provide configuration name")
|
|
|
|
return ResponseObject(data={
|
|
|
|
"configurationInfo": WireguardConfigurations[configurationName],
|
|
|
|
"configurationPeers": WireguardConfigurations[configurationName].getPeersList(),
|
|
|
|
"configurationRestrictedPeers": WireguardConfigurations[configurationName].getRestrictedPeersList()
|
|
|
|
})
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getDashboardTheme')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_getDashboardTheme():
|
|
|
|
return ResponseObject(data=DashboardConfig.GetConfig("Server", "dashboard_theme")[1])
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getDashboardVersion')
|
2024-08-19 16:50:00 -04:00
|
|
|
def API_getDashboardVersion():
|
|
|
|
return ResponseObject(data=DashboardConfig.GetConfig("Server", "version")[1])
|
|
|
|
|
2025-04-19 02:54:47 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/savePeerScheduleJob')
|
2024-06-25 23:02:13 +08:00
|
|
|
def API_savePeerScheduleJob():
|
|
|
|
data = request.json
|
2025-04-19 02:54:47 +08:00
|
|
|
if "Job" not in data.keys():
|
2024-06-25 23:02:13 +08:00
|
|
|
return ResponseObject(False, "Please specify job")
|
|
|
|
job: dict = data['Job']
|
|
|
|
if "Peer" not in job.keys() or "Configuration" not in job.keys():
|
|
|
|
return ResponseObject(False, "Please specify peer and configuration")
|
|
|
|
configuration = WireguardConfigurations.get(job['Configuration'])
|
2025-04-19 02:54:47 +08:00
|
|
|
if configuration is None:
|
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
2024-06-25 23:02:13 +08:00
|
|
|
f, fp = configuration.searchPeer(job['Peer'])
|
|
|
|
if not f:
|
2024-09-22 21:50:30 +08:00
|
|
|
return ResponseObject(False, "Peer does not exist")
|
2025-04-19 02:54:47 +08:00
|
|
|
|
|
|
|
|
2024-06-25 23:02:13 +08:00
|
|
|
s, p = AllPeerJobs.saveJob(PeerJob(
|
|
|
|
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
|
|
|
|
job['CreationDate'], job['ExpireDate'], job['Action']))
|
|
|
|
if s:
|
|
|
|
return ResponseObject(s, data=p)
|
|
|
|
return ResponseObject(s, message=p)
|
|
|
|
|
2025-04-19 02:54:47 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/deletePeerScheduleJob')
|
2024-07-01 00:58:02 +08:00
|
|
|
def API_deletePeerScheduleJob():
|
|
|
|
data = request.json
|
2025-04-19 02:54:47 +08:00
|
|
|
if "Job" not in data.keys():
|
2024-07-01 00:58:02 +08:00
|
|
|
return ResponseObject(False, "Please specify job")
|
|
|
|
job: dict = data['Job']
|
|
|
|
if "Peer" not in job.keys() or "Configuration" not in job.keys():
|
|
|
|
return ResponseObject(False, "Please specify peer and configuration")
|
|
|
|
configuration = WireguardConfigurations.get(job['Configuration'])
|
2025-04-19 02:54:47 +08:00
|
|
|
if configuration is None:
|
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
2025-05-13 21:36:15 +08:00
|
|
|
# f, fp = configuration.searchPeer(job['Peer'])
|
|
|
|
# if not f:
|
|
|
|
# return ResponseObject(False, "Peer does not exist")
|
2024-07-01 00:58:02 +08:00
|
|
|
|
|
|
|
s, p = AllPeerJobs.deleteJob(PeerJob(
|
|
|
|
job['JobID'], job['Configuration'], job['Peer'], job['Field'], job['Operator'], job['Value'],
|
|
|
|
job['CreationDate'], job['ExpireDate'], job['Action']))
|
|
|
|
if s:
|
2025-05-10 18:16:29 +08:00
|
|
|
return ResponseObject(s)
|
2024-07-01 00:58:02 +08:00
|
|
|
return ResponseObject(s, message=p)
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getPeerScheduleJobLogs/<configName>')
|
2024-07-29 18:40:07 -04:00
|
|
|
def API_getPeerScheduleJobLogs(configName):
|
|
|
|
if configName not in WireguardConfigurations.keys():
|
|
|
|
return ResponseObject(False, "Configuration does not exist")
|
|
|
|
data = request.args.get("requestAll")
|
|
|
|
requestAll = False
|
|
|
|
if data is not None and data == "true":
|
|
|
|
requestAll = True
|
2025-05-13 21:36:15 +08:00
|
|
|
return ResponseObject(data=AllPeerJobs.getPeerJobLogs(configName))
|
2024-07-29 18:40:07 -04:00
|
|
|
|
2024-12-26 00:06:37 +08:00
|
|
|
'''
|
|
|
|
File Download
|
|
|
|
'''
|
|
|
|
@app.get(f'{APP_PREFIX}/fileDownload')
|
|
|
|
def API_download():
|
|
|
|
file = request.args.get('file')
|
|
|
|
if file is None or len(file) == 0:
|
|
|
|
return ResponseObject(False, "Please specify a file")
|
|
|
|
if os.path.exists(os.path.join('download', file)):
|
|
|
|
return send_file(os.path.join('download', file), as_attachment=True)
|
|
|
|
else:
|
|
|
|
return ResponseObject(False, "File does not exist")
|
|
|
|
|
|
|
|
|
2024-06-25 23:02:13 +08:00
|
|
|
'''
|
|
|
|
Tools
|
|
|
|
'''
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/ping/getAllPeersIpAddress')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_ping_getAllPeersIpAddress():
|
|
|
|
ips = {}
|
|
|
|
for c in WireguardConfigurations.values():
|
|
|
|
cips = {}
|
|
|
|
for p in c.Peers:
|
|
|
|
allowed_ip = p.allowed_ip.replace(" ", "").split(",")
|
|
|
|
parsed = []
|
|
|
|
for x in allowed_ip:
|
2024-09-19 22:32:15 +08:00
|
|
|
try:
|
|
|
|
ip = ipaddress.ip_network(x, strict=False)
|
|
|
|
except ValueError as e:
|
|
|
|
print(f"{p.id} - {c.Name}")
|
2024-06-18 03:16:42 +08:00
|
|
|
if len(list(ip.hosts())) == 1:
|
|
|
|
parsed.append(str(ip.hosts()[0]))
|
|
|
|
endpoint = p.endpoint.replace(" ", "").replace("(none)", "")
|
|
|
|
if len(p.name) > 0:
|
|
|
|
cips[f"{p.name} - {p.id}"] = {
|
|
|
|
"allowed_ips": parsed,
|
|
|
|
"endpoint": endpoint
|
|
|
|
}
|
2021-08-14 17:13:16 -04:00
|
|
|
else:
|
2024-06-18 03:16:42 +08:00
|
|
|
cips[f"{p.id}"] = {
|
|
|
|
"allowed_ips": parsed,
|
|
|
|
"endpoint": endpoint
|
|
|
|
}
|
|
|
|
ips[c.Name] = cips
|
|
|
|
return ResponseObject(data=ips)
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-10-02 17:09:35 +08:00
|
|
|
import requests
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/ping/execute')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_ping_execute():
|
|
|
|
if "ipAddress" in request.args.keys() and "count" in request.args.keys():
|
|
|
|
ip = request.args['ipAddress']
|
|
|
|
count = request.args['count']
|
|
|
|
try:
|
|
|
|
if ip is not None and len(ip) > 0 and count is not None and count.isnumeric():
|
|
|
|
result = ping(ip, count=int(count), source=None)
|
2024-10-02 17:09:35 +08:00
|
|
|
data = {
|
2024-06-18 03:16:42 +08:00
|
|
|
"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,
|
2024-10-02 17:09:35 +08:00
|
|
|
"package_loss": result.packet_loss,
|
|
|
|
"geo": None
|
|
|
|
}
|
|
|
|
try:
|
|
|
|
r = requests.get(f"http://ip-api.com/json/{result.address}?field=city")
|
|
|
|
data['geo'] = r.json()
|
|
|
|
except Exception as e:
|
|
|
|
pass
|
|
|
|
return ResponseObject(data=data)
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(False, "Please specify an IP Address (v4/v6)")
|
|
|
|
except Exception as exp:
|
|
|
|
return ResponseObject(False, exp)
|
|
|
|
return ResponseObject(False, "Please provide ipAddress and count")
|
|
|
|
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/traceroute/execute')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_traceroute_execute():
|
|
|
|
if "ipAddress" in request.args.keys() and len(request.args.get("ipAddress")) > 0:
|
|
|
|
ipAddress = request.args.get('ipAddress')
|
|
|
|
try:
|
2024-10-02 17:09:35 +08:00
|
|
|
tracerouteResult = traceroute(ipAddress, timeout=1, max_hops=64)
|
2024-06-18 03:16:42 +08:00
|
|
|
result = []
|
|
|
|
for hop in tracerouteResult:
|
|
|
|
if len(result) > 1:
|
|
|
|
skipped = False
|
|
|
|
for i in range(result[-1]["hop"] + 1, hop.distance):
|
|
|
|
result.append(
|
|
|
|
{
|
|
|
|
"hop": i,
|
|
|
|
"ip": "*",
|
|
|
|
"avg_rtt": "*",
|
|
|
|
"min_rtt": "*",
|
|
|
|
"max_rtt": "*"
|
|
|
|
}
|
|
|
|
)
|
|
|
|
skip = True
|
|
|
|
if skipped: continue
|
|
|
|
result.append(
|
|
|
|
{
|
|
|
|
"hop": hop.distance,
|
|
|
|
"ip": hop.address,
|
|
|
|
"avg_rtt": hop.avg_rtt,
|
|
|
|
"min_rtt": hop.min_rtt,
|
|
|
|
"max_rtt": hop.max_rtt
|
|
|
|
})
|
2024-10-02 17:09:35 +08:00
|
|
|
try:
|
|
|
|
r = requests.post(f"http://ip-api.com/batch?fields=city,country,lat,lon,query",
|
|
|
|
data=json.dumps([x['ip'] for x in result]))
|
|
|
|
d = r.json()
|
|
|
|
for i in range(len(result)):
|
2025-02-12 20:07:37 +08:00
|
|
|
result[i]['geo'] = d[i]
|
2024-10-02 17:09:35 +08:00
|
|
|
except Exception as e:
|
2025-02-12 20:07:37 +08:00
|
|
|
return ResponseObject(data=result, message="Failed to request IP address geolocation")
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(data=result)
|
|
|
|
except Exception as exp:
|
|
|
|
return ResponseObject(False, exp)
|
|
|
|
else:
|
|
|
|
return ResponseObject(False, "Please provide ipAddress")
|
2021-09-03 17:32:51 -04:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/getDashboardUpdate')
|
2024-08-17 00:31:46 -04:00
|
|
|
def API_getDashboardUpdate():
|
2024-08-19 21:30:47 -04:00
|
|
|
import urllib.request as req
|
2024-08-17 00:31:46 -04:00
|
|
|
try:
|
|
|
|
r = req.urlopen("https://api.github.com/repos/donaldzou/WGDashboard/releases/latest", timeout=5).read()
|
|
|
|
data = dict(json.loads(r))
|
|
|
|
tagName = data.get('tag_name')
|
|
|
|
htmlUrl = data.get('html_url')
|
|
|
|
if tagName is not None and htmlUrl is not None:
|
2025-05-14 09:24:29 +08:00
|
|
|
if version.parse(tagName) > version.parse(DashboardConfig.DashboardVersion):
|
2024-09-19 22:32:15 +08:00
|
|
|
return ResponseObject(message=f"{tagName} is now available for update!", data=htmlUrl)
|
2024-08-17 00:31:46 -04:00
|
|
|
else:
|
|
|
|
return ResponseObject(message="You're on the latest version")
|
|
|
|
return ResponseObject(False)
|
2025-02-12 20:07:37 +08:00
|
|
|
except Exception as e:
|
2024-08-19 21:30:47 -04:00
|
|
|
return ResponseObject(False, f"Request to GitHub API failed.")
|
2021-09-03 17:32:51 -04:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
'''
|
|
|
|
Sign Up
|
|
|
|
'''
|
2022-03-24 02:10:52 -04:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/isTotpEnabled')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_isTotpEnabled():
|
2024-08-05 15:39:11 -04:00
|
|
|
return (
|
|
|
|
ResponseObject(data=DashboardConfig.GetConfig("Account", "enable_totp")[1] and DashboardConfig.GetConfig("Account", "totp_verified")[1]))
|
2022-03-21 22:33:19 -04:00
|
|
|
|
2022-03-24 02:10:52 -04:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/Welcome_GetTotpLink')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_Welcome_GetTotpLink():
|
2024-08-05 15:39:11 -04:00
|
|
|
if not DashboardConfig.GetConfig("Account", "totp_verified")[1]:
|
2025-05-03 16:13:30 +08:00
|
|
|
DashboardConfig.SetConfig("Account", "totp_key", pyotp.random_base32(), True)
|
2024-06-18 03:16:42 +08:00
|
|
|
return ResponseObject(
|
|
|
|
data=pyotp.totp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).provisioning_uri(
|
|
|
|
issuer_name="WGDashboard"))
|
|
|
|
return ResponseObject(False)
|
2022-03-24 02:10:52 -04:00
|
|
|
|
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/Welcome_VerifyTotpLink')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_Welcome_VerifyTotpLink():
|
2023-11-28 16:37:16 -05:00
|
|
|
data = request.get_json()
|
2024-08-05 15:39:11 -04:00
|
|
|
totp = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now()
|
|
|
|
if totp == data['totp']:
|
|
|
|
DashboardConfig.SetConfig("Account", "totp_verified", "true")
|
|
|
|
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
|
|
|
return ResponseObject(totp == data['totp'])
|
2023-11-28 16:37:16 -05:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
|
2024-06-18 03:16:42 +08:00
|
|
|
def API_Welcome_Finish():
|
2022-04-23 00:34:11 -04:00
|
|
|
data = request.get_json()
|
2024-06-18 03:16:42 +08:00
|
|
|
if DashboardConfig.GetConfig("Other", "welcome_session")[1]:
|
|
|
|
if data["username"] == "":
|
|
|
|
return ResponseObject(False, "Username cannot be blank.")
|
2022-04-23 00:34:11 -04:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
if data["newPassword"] == "" or len(data["newPassword"]) < 8:
|
|
|
|
return ResponseObject(False, "Password must be at least 8 characters")
|
2022-04-21 15:11:01 -04:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
updateUsername, updateUsernameErr = DashboardConfig.SetConfig("Account", "username", data["username"])
|
|
|
|
updatePassword, updatePasswordErr = DashboardConfig.SetConfig("Account", "password",
|
|
|
|
{
|
|
|
|
"newPassword": data["newPassword"],
|
2024-08-02 17:27:28 -04:00
|
|
|
"repeatNewPassword": data["repeatNewPassword"],
|
2024-06-18 03:16:42 +08:00
|
|
|
"currentPassword": "admin"
|
|
|
|
})
|
2024-08-05 15:39:11 -04:00
|
|
|
if not updateUsername or not updatePassword:
|
|
|
|
return ResponseObject(False, f"{updateUsernameErr},{updatePasswordErr}".strip(","))
|
2021-12-26 02:26:39 +03:00
|
|
|
|
2024-06-18 03:16:42 +08:00
|
|
|
DashboardConfig.SetConfig("Other", "welcome_session", False)
|
|
|
|
return ResponseObject()
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-09-23 00:17:30 +08:00
|
|
|
class Locale:
|
|
|
|
def __init__(self):
|
|
|
|
self.localePath = './static/locale/'
|
|
|
|
self.activeLanguages = {}
|
|
|
|
with open(os.path.join(f"{self.localePath}active_languages.json"), "r") as f:
|
2025-04-20 02:40:36 +08:00
|
|
|
self.activeLanguages = sorted(json.loads(''.join(f.readlines())), key=lambda x : x['lang_name'])
|
2024-09-23 00:17:30 +08:00
|
|
|
|
|
|
|
def getLanguage(self) -> dict | None:
|
|
|
|
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
|
|
|
|
if currentLanguage == "en":
|
|
|
|
return None
|
|
|
|
if os.path.exists(os.path.join(f"{self.localePath}{currentLanguage}.json")):
|
|
|
|
with open(os.path.join(f"{self.localePath}{currentLanguage}.json"), "r") as f:
|
|
|
|
return dict(json.loads(''.join(f.readlines())))
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def updateLanguage(self, lang_id):
|
|
|
|
if not os.path.exists(os.path.join(f"{self.localePath}{lang_id}.json")):
|
|
|
|
DashboardConfig.SetConfig("Server", "dashboard_language", "en")
|
|
|
|
else:
|
|
|
|
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
|
|
|
|
|
|
|
|
Locale = Locale()
|
|
|
|
|
|
|
|
@app.get(f'{APP_PREFIX}/api/locale')
|
|
|
|
def API_Locale_CurrentLang():
|
|
|
|
return ResponseObject(data=Locale.getLanguage())
|
|
|
|
|
|
|
|
@app.get(f'{APP_PREFIX}/api/locale/available')
|
|
|
|
def API_Locale_Available():
|
|
|
|
return ResponseObject(data=Locale.activeLanguages)
|
|
|
|
|
|
|
|
@app.post(f'{APP_PREFIX}/api/locale/update')
|
|
|
|
def API_Locale_Update():
|
|
|
|
data = request.get_json()
|
|
|
|
if 'lang_id' not in data.keys():
|
|
|
|
return ResponseObject(False, "Please specify a lang_id")
|
|
|
|
Locale.updateLanguage(data['lang_id'])
|
|
|
|
return ResponseObject(data=Locale.getLanguage())
|
2024-09-09 23:43:55 +08:00
|
|
|
|
2025-01-08 18:09:05 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/email/ready')
|
|
|
|
def API_Email_Ready():
|
|
|
|
return ResponseObject(EmailSender.ready())
|
|
|
|
|
|
|
|
@app.post(f'{APP_PREFIX}/api/email/send')
|
|
|
|
def API_Email_Send():
|
|
|
|
data = request.get_json()
|
2025-01-13 16:47:15 +08:00
|
|
|
if "Receiver" not in data.keys():
|
2025-01-08 18:09:05 +08:00
|
|
|
return ResponseObject(False, "Please at least specify receiver")
|
2025-01-13 16:47:15 +08:00
|
|
|
body = data.get('Body', '')
|
2025-01-16 00:44:22 +08:00
|
|
|
download = None
|
2025-04-19 02:54:47 +08:00
|
|
|
if ("ConfigurationName" in data.keys()
|
|
|
|
and "Peer" in data.keys()):
|
2025-01-13 16:47:15 +08:00
|
|
|
if data.get('ConfigurationName') in WireguardConfigurations.keys():
|
|
|
|
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
|
|
|
|
attachmentName = ""
|
|
|
|
if configuration is not None:
|
|
|
|
fp, p = configuration.searchPeer(data.get('Peer'))
|
|
|
|
if fp:
|
|
|
|
template = Template(body)
|
|
|
|
download = p.downloadPeer()
|
|
|
|
body = template.render(peer=p.toJson(), configurationFile=download)
|
|
|
|
if data.get('IncludeAttachment', False):
|
|
|
|
u = str(uuid4())
|
|
|
|
attachmentName = f'{u}.conf'
|
2025-04-19 02:54:47 +08:00
|
|
|
with open(os.path.join('./attachments', attachmentName,), 'w+') as f:
|
|
|
|
f.write(download['file'])
|
|
|
|
|
2025-01-13 16:47:15 +08:00
|
|
|
|
|
|
|
s, m = EmailSender.send(data.get('Receiver'), data.get('Subject', ''), body,
|
2025-04-19 02:54:47 +08:00
|
|
|
data.get('IncludeAttachment', False), (attachmentName if download else ''))
|
2025-01-08 18:09:05 +08:00
|
|
|
return ResponseObject(s, m)
|
|
|
|
|
2025-01-16 00:44:22 +08:00
|
|
|
@app.post(f'{APP_PREFIX}/api/email/previewBody')
|
|
|
|
def API_Email_PreviewBody():
|
|
|
|
data = request.get_json()
|
|
|
|
body = data.get('Body', '')
|
|
|
|
if len(body) == 0:
|
|
|
|
return ResponseObject(False, "Nothing to preview")
|
|
|
|
if ("ConfigurationName" not in data.keys()
|
|
|
|
or "Peer" not in data.keys() or data.get('ConfigurationName') not in WireguardConfigurations.keys()):
|
|
|
|
return ResponseObject(False, "Please specify configuration and peer")
|
|
|
|
|
|
|
|
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
|
|
|
|
fp, p = configuration.searchPeer(data.get('Peer'))
|
|
|
|
if not fp:
|
|
|
|
return ResponseObject(False, "Peer does not exist")
|
|
|
|
|
|
|
|
try:
|
|
|
|
template = Template(body)
|
|
|
|
download = p.downloadPeer()
|
|
|
|
body = template.render(peer=p.toJson(), configurationFile=download)
|
|
|
|
return ResponseObject(data=body)
|
|
|
|
except Exception as e:
|
|
|
|
return ResponseObject(False, message=str(e))
|
|
|
|
|
2024-11-27 18:26:13 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/systemStatus')
|
|
|
|
def API_SystemStatus():
|
2025-01-24 19:19:17 +08:00
|
|
|
return ResponseObject(data=SystemStatus)
|
2024-11-27 18:26:13 +08:00
|
|
|
|
2024-12-04 17:50:16 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/api/protocolsEnabled')
|
|
|
|
def API_ProtocolsEnabled():
|
|
|
|
return ResponseObject(data=ProtocolsEnabled())
|
2024-11-27 18:26:13 +08:00
|
|
|
|
2024-09-24 22:54:18 +08:00
|
|
|
@app.get(f'{APP_PREFIX}/')
|
2024-06-18 03:16:42 +08:00
|
|
|
def index():
|
2024-11-07 18:33:39 +08:00
|
|
|
return render_template('index.html')
|
2024-01-09 00:25:47 -05:00
|
|
|
|
2025-02-12 20:07:37 +08:00
|
|
|
def peerInformationBackgroundThread():
|
2024-09-06 16:31:54 +08:00
|
|
|
global WireguardConfigurations
|
|
|
|
print(f"[WGDashboard] Background Thread #1 Started", flush=True)
|
|
|
|
time.sleep(10)
|
|
|
|
while True:
|
|
|
|
with app.app_context():
|
2024-06-18 03:16:42 +08:00
|
|
|
for c in WireguardConfigurations.values():
|
|
|
|
if c.getStatus():
|
2025-05-24 18:25:52 +08:00
|
|
|
# try:
|
|
|
|
c.getPeersTransfer()
|
|
|
|
c.getPeersLatestHandshake()
|
|
|
|
c.getPeersEndpoint()
|
|
|
|
c.getPeersList()
|
|
|
|
c.getRestrictedPeersList()
|
|
|
|
# except Exception as e:
|
|
|
|
# print(f"[WGDashboard] Background Thread #1 Error: {str(e)}", flush=True)
|
2024-09-06 16:31:54 +08:00
|
|
|
time.sleep(10)
|
2022-01-02 16:35:39 +03:00
|
|
|
|
2024-07-01 00:58:02 +08:00
|
|
|
def peerJobScheduleBackgroundThread():
|
|
|
|
with app.app_context():
|
2024-08-04 20:17:29 -04:00
|
|
|
print(f"[WGDashboard] Background Thread #2 Started", flush=True)
|
2024-07-27 18:51:43 -04:00
|
|
|
time.sleep(10)
|
2024-07-01 00:58:02 +08:00
|
|
|
while True:
|
|
|
|
AllPeerJobs.runJob()
|
2024-08-04 01:31:31 -04:00
|
|
|
time.sleep(180)
|
2024-07-01 00:58:02 +08:00
|
|
|
|
2024-06-18 03:40:25 +08:00
|
|
|
def gunicornConfig():
|
|
|
|
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
|
|
|
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
|
|
|
return app_ip, app_port
|
|
|
|
|
2024-12-04 17:50:16 +08:00
|
|
|
def ProtocolsEnabled() -> list[str]:
|
2024-12-02 16:34:26 +08:00
|
|
|
from shutil import which
|
2024-12-04 17:50:16 +08:00
|
|
|
protocols = []
|
|
|
|
if which('awg') is not None and which('awg-quick') is not None:
|
|
|
|
protocols.append("awg")
|
|
|
|
if which('wg') is not None and which('wg-quick') is not None:
|
|
|
|
protocols.append("wg")
|
|
|
|
return protocols
|
2024-12-02 16:34:26 +08:00
|
|
|
|
2024-12-26 00:06:37 +08:00
|
|
|
def InitWireguardConfigurationsList(startup: bool = False):
|
|
|
|
if os.path.exists(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]):
|
|
|
|
confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1])
|
|
|
|
confs.sort()
|
|
|
|
for i in confs:
|
|
|
|
if RegexMatch("^(.{1,}).(conf)$", i):
|
|
|
|
i = i.replace('.conf', '')
|
|
|
|
try:
|
|
|
|
if i in WireguardConfigurations.keys():
|
|
|
|
if WireguardConfigurations[i].configurationFileChanged():
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[i] = WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i)
|
2024-12-26 00:06:37 +08:00
|
|
|
else:
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[i] = WireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i, startup=startup)
|
2024-12-26 00:06:37 +08:00
|
|
|
except WireguardConfiguration.InvalidConfigurationFileException as e:
|
|
|
|
print(f"{i} have an invalid configuration file.")
|
2024-11-25 22:11:51 +08:00
|
|
|
|
2024-12-04 17:50:16 +08:00
|
|
|
if "awg" in ProtocolsEnabled():
|
2024-12-02 16:34:26 +08:00
|
|
|
confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1])
|
|
|
|
confs.sort()
|
|
|
|
for i in confs:
|
|
|
|
if RegexMatch("^(.{1,}).(conf)$", i):
|
|
|
|
i = i.replace('.conf', '')
|
|
|
|
try:
|
|
|
|
if i in WireguardConfigurations.keys():
|
|
|
|
if WireguardConfigurations[i].configurationFileChanged():
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, i)
|
2024-12-02 16:34:26 +08:00
|
|
|
else:
|
2025-05-18 15:24:41 +08:00
|
|
|
WireguardConfigurations[i] = AmneziaWireguardConfiguration(DashboardConfig, AllPeerJobs, AllPeerShareLinks, i, startup=startup)
|
|
|
|
except WireguardConfiguration.InvalidConfigurationFileException as e:
|
2024-12-02 16:34:26 +08:00
|
|
|
print(f"{i} have an invalid configuration file.")
|
2024-12-02 15:09:54 +08:00
|
|
|
|
2025-05-13 21:36:15 +08:00
|
|
|
|
2024-06-19 17:09:58 +08:00
|
|
|
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
|
|
|
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
|
|
|
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
|
2024-08-03 13:25:57 -04:00
|
|
|
|
|
|
|
WireguardConfigurations: dict[str, WireguardConfiguration] = {}
|
2025-05-13 21:36:15 +08:00
|
|
|
|
|
|
|
AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig)
|
|
|
|
AllPeerJobs: PeerJobs = PeerJobs(DashboardConfig, WireguardConfigurations)
|
2025-05-14 09:24:29 +08:00
|
|
|
DashboardLogger: DashboardLogger = DashboardLogger(DashboardConfig)
|
2025-05-13 21:36:15 +08:00
|
|
|
|
2024-11-25 22:11:51 +08:00
|
|
|
InitWireguardConfigurationsList(startup=True)
|
2024-06-18 03:40:25 +08:00
|
|
|
|
2024-08-05 15:39:11 -04:00
|
|
|
def startThreads():
|
2025-02-12 20:07:37 +08:00
|
|
|
bgThread = threading.Thread(target=peerInformationBackgroundThread, daemon=True)
|
2024-08-05 15:39:11 -04:00
|
|
|
bgThread.start()
|
2025-02-12 20:07:37 +08:00
|
|
|
scheduleJobThread = threading.Thread(target=peerJobScheduleBackgroundThread, daemon=True)
|
2024-08-05 15:39:11 -04:00
|
|
|
scheduleJobThread.start()
|
2024-07-01 00:58:02 +08:00
|
|
|
|
2021-10-18 02:24:09 +03:00
|
|
|
if __name__ == "__main__":
|
2024-08-05 15:39:11 -04:00
|
|
|
startThreads()
|
2025-05-18 15:24:41 +08:00
|
|
|
app.run(host=app_ip, debug=False, port=app_port)
|