From 1e88491ca1baefceb92cc16ec32cd49f668b36be Mon Sep 17 00:00:00 2001 From: Donald Zou Date: Sun, 11 Feb 2024 23:53:51 -0500 Subject: [PATCH] I am giving up on using ORM... Lets go back to the good old sql query days ;) --- src/dashboard_new.py | 518 ++++++++++++++---- .../app/src/components/configurationList.vue | 21 +- .../configurationCard.vue | 71 +++ .../messageCentreComponent/message.vue | 32 ++ src/static/app/src/router/index.js | 6 + .../src/stores/DashboardConfigurationStore.js | 14 +- src/static/app/src/utilities/fetch.js | 4 +- src/static/app/src/views/configuration.vue | 15 + src/static/app/src/views/index.vue | 19 +- src/static/app/src/views/newConfiguration.vue | 108 ++-- src/static/css/dashboard.css | 28 +- 11 files changed, 665 insertions(+), 171 deletions(-) create mode 100644 src/static/app/src/components/configurationListComponents/configurationCard.vue create mode 100644 src/static/app/src/components/messageCentreComponent/message.vue create mode 100644 src/static/app/src/views/configuration.vue diff --git a/src/dashboard_new.py b/src/dashboard_new.py index dd1d868..0ad3a1a 100644 --- a/src/dashboard_new.py +++ b/src/dashboard_new.py @@ -1,3 +1,4 @@ +import itertools import sqlite3 import configparser import hashlib @@ -19,9 +20,9 @@ from operator import itemgetter from typing import Dict, Any import bcrypt -import flask # PIP installed library import ifcfg +import psutil import pyotp from flask import Flask, request, render_template, redirect, url_for, session, jsonify, g from flask.json.provider import JSONProvider @@ -57,7 +58,7 @@ QRcode(app) ''' Classes ''' -Base = declarative_base() +# Base = declarative_base(class_registry=dict()) class CustomJsonEncoder(JSONProvider): @@ -73,25 +74,40 @@ class CustomJsonEncoder(JSONProvider): app.json = CustomJsonEncoder(app) +class Peer: + def __init__(self, tableData): + # for i in range(0, len(table)): + # tableData[table.description[i][0]] = table[i] + + self.id = tableData["id"] + self.private_key = tableData["private_key"] + self.DNS = tableData["DNS"] + self.endpoint_allowed_ip = tableData["endpoint_allowed_ip"] + self.name = tableData["name"] + self.total_receive = tableData["total_receive"] + self.total_sent = tableData["total_sent"] + self.total_data = tableData["total_data"] + self.endpoint = tableData["endpoint"] + self.status = tableData["status"] + self.latest_handshake = tableData["latest_handshake"] + self.allowed_ip = tableData["allowed_ip"] + self.cumu_receive = tableData["cumu_receive"] + self.cumu_sent = tableData["cumu_sent"] + self.cumu_data = tableData["cumu_data"] + self.mtu = tableData["mtu"] + self.keepalive = tableData["keepalive"] + self.remote_endpoint = tableData["remote_endpoint"] + self.preshared_key = tableData["preshared_key"] + + def toJson(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) + + def __repr__(self): + return self.toJson() + + class WireguardConfiguration: - __parser: configparser.ConfigParser = configparser.ConfigParser(strict=False) - __parser.optionxform = str - - Status: bool = False - Name: str = "" - PrivateKey: str = "" - PublicKey: str = "" - ListenPort: str = "" - Address: str = "" - DNS: str = "" - Table: str = "" - MTU: str = "" - PreUp: str = "" - PostUp: str = "" - PreDown: str = "" - PostDown: str = "" - SaveConfig: bool = False - class InvalidConfigurationFileException(Exception): def __init__(self, m): self.message = m @@ -99,10 +115,28 @@ class WireguardConfiguration: def __str__(self): return self.message - def __init__(self, name: str = None): + def __init__(self, name: str = None, data: dict = None): + self.__parser: configparser.ConfigParser = configparser.ConfigParser(strict=False) + self.__parser.optionxform = str + + self.Status: bool = False + self.Name: str = "" + self.PrivateKey: str = "" + self.PublicKey: str = "" + self.ListenPort: str = "" + self.Address: str = "" + self.DNS: str = "" + self.Table: str = "" + self.MTU: str = "" + self.PreUp: str = "" + self.PostUp: str = "" + self.PreDown: str = "" + self.PostDown: str = "" + self.SaveConfig: bool = True + if name is not None: self.Name = name - self.__parser.read(os.path.join(WG_CONF_PATH, f'{self.Name}.conf')) + self.__parser.read_file(open(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'))) sections = self.__parser.sections() if "Interface" not in sections: raise self.InvalidConfigurationFileException( @@ -118,33 +152,195 @@ class WireguardConfiguration: if self.PrivateKey: self.PublicKey = self.__getPublicKey() - self.Status = self.__getStatus() + self.Status = self.getStatus() - # Create tables in database - self.__createDatabase() + + else: + self.Name = data["ConfigurationName"] + for i in dir(self): + if str(i) in data.keys(): + if isinstance(getattr(self, i), bool): + setattr(self, i, _strToBool(data[i])) + else: + setattr(self, i, str(data[i])) + + # self.__createDatabase() + self.__parser["Interface"] = { + "PrivateKey": self.PrivateKey, + "Address": self.Address, + "ListenPort": self.ListenPort, + "PreUp": self.PreUp, + "PreDown": self.PreDown, + "PostUp": self.PostUp, + "PostDown": self.PostDown, + "SaveConfig": "true" + } + + with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], + f"{self.Name}.conf"), "w+") as configFile: + print(self.__parser.sections()) + self.__parser.write(configFile) + + self.Peers = [] + + # Create tables in database + self.__createDatabase() + self.__getPeers() def __createDatabase(self): - inspector = inspect(engine) - existingTable = inspector.get_table_names() - if self.Name not in existingTable: - _createPeerModel(self.Name).__table__.create(engine) - if self.Name + "_restrict_access" not in existingTable: - _createRestrcitedPeerModel(self.Name).__table__.create(engine) - if self.Name + "_transfer" not in existingTable: - _createPeerTransferModel(self.Name).__table__.create(engine) + existingTables = cursor.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + existingTables = itertools.chain(*existingTables) + if self.Name not in existingTables: + cursor.execute( + """ + CREATE TABLE %s ( + id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, + endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, + status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL, + keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL, + PRIMARY KEY (id) + ) + """ % self.Name + ) + sqldb.commit() + + if f'{self.Name}_restrict_access' not in existingTables: + cursor.execute( + """ + CREATE TABLE %s_restrict_access ( + id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, + endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, + status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL, + keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL, + PRIMARY KEY (id) + ) + """ % self.Name + ) + sqldb.commit() + + if f'{self.Name}_transfer' not in existingTables: + cursor.execute( + """ + CREATE TABLE %s_transfer ( + id VARCHAR NOT NULL, total_receive FLOAT NULL, + total_sent FLOAT NULL, total_data FLOAT NULL, + cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME + ) + """ % self.Name + ) + sqldb.commit() def __getPublicKey(self) -> str: return subprocess.check_output(['wg', 'pubkey'], input=self.PrivateKey.encode()).decode().strip('\n') - def __getStatus(self) -> bool: - return self.Name in dict(ifcfg.interfaces().items()).keys() + def getStatus(self) -> bool: + self.Status = self.Name in psutil.net_if_addrs().keys() + return self.Status + + def __getPeers(self): + with open(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'), 'r') as configFile: + p = [] + pCounter = -1 + content = configFile.read().split('\n') + try: + peerStarts = content.index("[Peer]") + content = content[peerStarts:] + for i in content: + if not regex_match("#(.*)", i) and not regex_match(";(.*)", i): + if i == "[Peer]": + pCounter += 1 + p.append({}) + else: + if len(i) > 0: + split = re.split(r'\s*=\s*', i, 1) + if len(split) == 2: + p[pCounter][split[0]] = split[1] + for i in p: + if "PublicKey" in i.keys(): + checkIfExist = cursor.execute("SELECT * FROM %s WHERE id = ?" % self.Name, + ((i['PublicKey']),)).fetchone() + if checkIfExist is None: + newPeer = { + "id": i['PublicKey'], + "private_key": "", + "DNS": DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1], + "endpoint_allowed_ip": DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[ + 1], + "name": "", + "total_receive": 0, + "total_sent": 0, + "total_data": 0, + "endpoint": "N/A", + "status": "stopped", + "latest_handshake": "N/A", + "allowed_ip": "N/A", + "cumu_receive": 0, + "cumu_sent": 0, + "cumu_data": 0, + "traffic": [], + "mtu": DashboardConfig.GetConfig("Peers", "peer_mtu")[1], + "keepalive": DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1], + "remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1], + "preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else "" + } + cursor.execute( + """ + INSERT INTO %s + VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent, + :total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent, + :cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key); + """ % self.Name + , newPeer) + sqldb.commit() + else: + self.Peers.append(Peer(checkIfExist)) + except ValueError: + pass + print(self.Peers) + + def toggleConfiguration(self) -> [bool, str]: + self.getStatus() + print("Status: ", self.getStatus()) + if self.Status: + try: + check = subprocess.check_output(f"wg-quick down {self.Name}", + shell=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + return False, str(exc.output.strip().decode("utf-8")) + else: + try: + check = subprocess.check_output(f"wg-quick up {self.Name}", + shell=True, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as exc: + return False, str(exc.output.strip().decode("utf-8")) + self.getStatus() + return True, None def toJSON(self): - self.Status = self.__getStatus() - return self.__dict__ + self.Status = self.getStatus() + return { + "Status": self.Status, + "Name": self.Name, + "PrivateKey": self.PrivateKey, + "PublicKey": self.PublicKey, + "Address": self.Address, + "ListenPort": self.ListenPort, + "PreUp": self.PreUp, + "PreDown": self.PreDown, + "PostUp": self.PostUp, + "PostDown": self.PostDown, + "SaveConfig": self.SaveConfig + } - def newConfiguration(self): - pass + +# Regex Match +def regex_match(regex, text): + pattern = re.compile(regex) + return pattern.search(text) is not None def iPv46RegexCheck(ip): @@ -157,7 +353,7 @@ class DashboardConfig: def __init__(self): self.__config = configparser.ConfigParser(strict=False) - self.__config.read(DASHBOARD_CONF) + self.__config.read_file(open(DASHBOARD_CONF)) self.hiddenAttribute = ["totp_key"] self.__default = { "Account": { @@ -309,7 +505,7 @@ def ResponseObject(status=True, message=None, data=None) -> Flask.response_class DashboardConfig = DashboardConfig() -WireguardConfigurations: [WireguardConfiguration] = [] +WireguardConfigurations: {str: WireguardConfiguration} = {} ''' Private Functions @@ -320,71 +516,71 @@ def _strToBool(value: str) -> bool: return value.lower() in ("yes", "true", "t", "1", 1) -def _createPeerModel(wgConfigName): - class Peer(Base): - __tablename__ = wgConfigName - id = mapped_column(VARCHAR, primary_key=True) - private_key = mapped_column(VARCHAR) - DNS = mapped_column(VARCHAR) - endpoint_allowed_ip = mapped_column(VARCHAR) - name = mapped_column(VARCHAR) - total_receive = mapped_column(FLOAT) - total_sent = mapped_column(FLOAT) - total_data = mapped_column(FLOAT) - endpoint = mapped_column(VARCHAR) - status = mapped_column(VARCHAR) - latest_handshake = mapped_column(VARCHAR) - allowed_ip = mapped_column(VARCHAR) - cumu_receive = mapped_column(FLOAT) - cumu_sent = mapped_column(FLOAT) - cumu_data = mapped_column(FLOAT) - mtu = mapped_column(INT) - keepalive = mapped_column(INT) - remote_endpoint = mapped_column(VARCHAR) - preshared_key = mapped_column(VARCHAR) - - return Peer - - -def _createRestrcitedPeerModel(wgConfigName): - class PeerRestricted(Base): - __tablename__ = wgConfigName + "_restrict_access" - id = mapped_column(VARCHAR, primary_key=True) - private_key = mapped_column(VARCHAR) - DNS = mapped_column(VARCHAR) - endpoint_allowed_ip = mapped_column(VARCHAR) - name = mapped_column(VARCHAR) - total_receive = mapped_column(FLOAT) - total_sent = mapped_column(FLOAT) - total_data = mapped_column(FLOAT) - endpoint = mapped_column(VARCHAR) - status = mapped_column(VARCHAR) - latest_handshake = mapped_column(VARCHAR) - allowed_ip = mapped_column(VARCHAR) - cumu_receive = mapped_column(FLOAT) - cumu_sent = mapped_column(FLOAT) - cumu_data = mapped_column(FLOAT) - mtu = mapped_column(INT) - keepalive = mapped_column(INT) - remote_endpoint = mapped_column(VARCHAR) - preshared_key = mapped_column(VARCHAR) - - return PeerRestricted - - -def _createPeerTransferModel(wgConfigName): - class PeerTransfer(Base): - __tablename__ = wgConfigName + "_transfer" - id = mapped_column(VARCHAR, primary_key=True) - total_receive = mapped_column(FLOAT) - total_sent = mapped_column(FLOAT) - total_data = mapped_column(FLOAT) - cumu_receive = mapped_column(FLOAT) - cumu_sent = mapped_column(FLOAT) - cumu_data = mapped_column(FLOAT) - time = mapped_column(DATETIME) - - return PeerTransfer +# def _createPeerModel(wgConfigName): +# return type(wgConfigName, (Base,), { +# "id": mapped_column(VARCHAR, primary_key=True), +# "private_key": mapped_column(VARCHAR), +# "DNS": mapped_column(VARCHAR), +# "endpoint_allowed_ip": mapped_column(VARCHAR), +# "name": mapped_column(VARCHAR), +# "total_receive": mapped_column(FLOAT), +# "total_sent": mapped_column(FLOAT), +# "total_data": mapped_column(FLOAT), +# "endpoint": mapped_column(VARCHAR), +# "status": mapped_column(VARCHAR), +# "latest_handshake": mapped_column(VARCHAR), +# "allowed_ip": mapped_column(VARCHAR), +# "cumu_receive": mapped_column(FLOAT), +# "cumu_sent": mapped_column(FLOAT), +# "cumu_data": mapped_column(FLOAT), +# "mtu": mapped_column(INT), +# "keepalive": mapped_column(INT), +# "remote_endpoint": mapped_column(VARCHAR), +# "preshared_key": mapped_column(VARCHAR), +# "__tablename__": wgConfigName, +# "__table_args__": {'extend_existing': True} +# }) +# +# +# def _createRestrictedPeerModel(wgConfigName): +# return type(wgConfigName + "_restrict_access", (Base,), { +# "id": mapped_column(VARCHAR, primary_key=True), +# "private_key": mapped_column(VARCHAR), +# "DNS": mapped_column(VARCHAR), +# "endpoint_allowed_ip": mapped_column(VARCHAR), +# "name": mapped_column(VARCHAR), +# "total_receive": mapped_column(FLOAT), +# "total_sent": mapped_column(FLOAT), +# "total_data": mapped_column(FLOAT), +# "endpoint": mapped_column(VARCHAR), +# "status": mapped_column(VARCHAR), +# "latest_handshake": mapped_column(VARCHAR), +# "allowed_ip": mapped_column(VARCHAR), +# "cumu_receive": mapped_column(FLOAT), +# "cumu_sent": mapped_column(FLOAT), +# "cumu_data": mapped_column(FLOAT), +# "mtu": mapped_column(INT), +# "keepalive": mapped_column(INT), +# "remote_endpoint": mapped_column(VARCHAR), +# "preshared_key": mapped_column(VARCHAR), +# "__tablename__": wgConfigName, +# "__table_args__": {'extend_existing': True} +# }) +# +# +# def _createPeerTransferModel(wgConfigName): +# return type(wgConfigName + "_transfer", (Base,), { +# "id": mapped_column(VARCHAR, primary_key=True), +# "total_receive": mapped_column(FLOAT), +# "total_sent": mapped_column(FLOAT), +# "total_data": mapped_column(FLOAT), +# "cumu_receive": mapped_column(FLOAT), +# "cumu_sent": mapped_column(FLOAT), +# "cumu_data": mapped_column(FLOAT), +# "time": mapped_column(DATETIME), +# "__tablename__": wgConfigName + "_transfer", +# "__table_args__": {'extend_existing': True}, +# }) def _regexMatch(regex, text): @@ -393,12 +589,12 @@ def _regexMatch(regex, text): def _getConfigurationList() -> [WireguardConfiguration]: - configurations = [] + configurations = {} for i in os.listdir(WG_CONF_PATH): if _regexMatch("^(.{1,}).(conf)$", i): i = i.replace('.conf', '') try: - configurations.append(WireguardConfiguration(i)) + configurations[i] = WireguardConfiguration(i) except WireguardConfiguration.InvalidConfigurationFileException as e: print(f"{i} have an invalid configuration file.") return configurations @@ -471,7 +667,7 @@ def API_SignOut(): @app.route('/api/getWireguardConfigurations', methods=["GET"]) def API_getWireguardConfigurations(): WireguardConfigurations = _getConfigurationList() - return ResponseObject(data=[wc.toJSON() for wc in WireguardConfigurations]) + return ResponseObject(data=[wc.toJSON() for wc in WireguardConfigurations.values()]) @app.route('/api/addWireguardConfiguration', methods=["POST"]) @@ -488,12 +684,46 @@ def API_addWireguardConfiguration(): "PreDown", "PostUp", "PostDown", - "UsePreSharedKey" ] requiredKeys = [ "ConfigurationName", "Address", "ListenPort", "PrivateKey" ] - + for i in keys: + if i not in data.keys() or (i in requiredKeys and len(str(data[i])) == 0): + return ResponseObject(False, "Please provide all required parameters.") + + # 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") + + WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data) + return ResponseObject() + + +@app.route('/api/toggleWireguardConfiguration/') +def API_toggleWireguardConfiguration(): + configurationName = request.args.get('configurationName') + + if configurationName is None or len( + configurationName) == 0 or configurationName not in WireguardConfigurations.keys(): + return ResponseObject(False, "Please provide a valid configuration name") + + toggleStatus, msg = WireguardConfigurations[configurationName].toggleConfiguration() + + return ResponseObject(toggleStatus, msg, WireguardConfigurations[configurationName].Status) @app.route('/api/getDashboardConfiguration', methods=["GET"]) @@ -590,11 +820,75 @@ def index(): return render_template('index_new.html') +def backGroundThread(): + print("Waiting 5 sec") + time.sleep(5) + while True: + for c in WireguardConfigurations.values(): + if c.getStatus(): + try: + data_usage = subprocess.check_output(f"wg show {c.Name} transfer", + shell=True, stderr=subprocess.STDOUT) + data_usage = data_usage.decode("UTF-8").split("\n") + data_usage = [p.split("\t") for p in data_usage] + for i in range(len(data_usage)): + if len(data_usage[i]) == 3: + cur_i = cursor.execute( + "SELECT total_receive, total_sent, cumu_receive, cumu_sent, status FROM %s WHERE id= ? " + % c.Name, (data_usage[i][0], )).fetchone() + if cur_i is not None: + total_sent = cur_i['total_sent'] + total_receive = cur_i['total_receive'] + cur_total_sent = round(int(data_usage[i][2]) / (1024 ** 3), 4) + cur_total_receive = round(int(data_usage[i][1]) / (1024 ** 3), 4) + cumulative_receive = cur_i['cumu_receive'] + total_receive + cumulative_sent = cur_i['cumu_sent'] + total_sent + if total_sent <= cur_total_sent and total_receive <= cur_total_receive: + total_sent = cur_total_sent + total_receive = cur_total_receive + else: + cursor.execute( + "UPDATE %s SET cumu_receive = ?, cumu_sent = ?, cumu_data = ? WHERE id = ?" % + c.Name, (round(cumulative_receive, 4), round(cumulative_sent, 4), + round(cumulative_sent + cumulative_receive, 4), + data_usage[i][0], )) + total_sent = 0 + total_receive = 0 + cursor.execute( + "UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?" + % c.Name, (round(total_receive, 4), round(total_sent, 4), + round(total_receive + total_sent, 4), data_usage[i][0], )) + now = datetime.now() + now_string = now.strftime("%d/%m/%Y %H:%M:%S") + cursor.execute(f''' + INSERT INTO %s_transfer + (id, total_receive, total_sent, total_data, + cumu_receive, cumu_sent, cumu_data, time) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ''' % c.Name, (data_usage[i][0], round(total_receive, 4), round(total_sent, 4), + round(total_receive + total_sent, 4), round(cumulative_receive, 4), + round(cumulative_sent, 4), + round(cumulative_sent + cumulative_receive, 4), now_string, )) + sqldb.commit() + print(data_usage) + pass + except Exception as e: + print(str(e)) + time.sleep(30) + + if __name__ == "__main__": engine = create_engine("sqlite:///" + os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db')) + sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False) + sqldb.row_factory = sqlite3.Row + cursor = sqldb.cursor() _, app_ip = DashboardConfig.GetConfig("Server", "app_ip") _, app_port = DashboardConfig.GetConfig("Server", "app_port") _, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path") WireguardConfigurations = _getConfigurationList() - app.run(host=app_ip, debug=True, port=app_port) + bgThread = threading.Thread(target=backGroundThread) + bgThread.daemon = True + bgThread.start() + + app.run(host=app_ip, debug=False, port=app_port) diff --git a/src/static/app/src/components/configurationList.vue b/src/static/app/src/components/configurationList.vue index 677e3f2..63d3f00 100644 --- a/src/static/app/src/components/configurationList.vue +++ b/src/static/app/src/components/configurationList.vue @@ -1,9 +1,11 @@ + + + + \ No newline at end of file diff --git a/src/static/app/src/components/messageCentreComponent/message.vue b/src/static/app/src/components/messageCentreComponent/message.vue new file mode 100644 index 0000000..58c49c6 --- /dev/null +++ b/src/static/app/src/components/messageCentreComponent/message.vue @@ -0,0 +1,32 @@ + + + + + \ No newline at end of file diff --git a/src/static/app/src/router/index.js b/src/static/app/src/router/index.js index 53e041d..ea9c547 100644 --- a/src/static/app/src/router/index.js +++ b/src/static/app/src/router/index.js @@ -10,6 +10,7 @@ import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStor import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import Setup from "@/views/setup.vue"; import NewConfiguration from "@/views/newConfiguration.vue"; +import Configuration from "@/views/configuration.vue"; const checkAuth = async () => { let result = false @@ -44,6 +45,11 @@ const router = createRouter({ name: "New Configuration", path: '/new_configuration', component: NewConfiguration + }, + { + name: "Configuration", + path: '/configuration/:id', + component: Configuration } ] }, diff --git a/src/static/app/src/stores/DashboardConfigurationStore.js b/src/static/app/src/stores/DashboardConfigurationStore.js index d8b3550..980d3ee 100644 --- a/src/static/app/src/stores/DashboardConfigurationStore.js +++ b/src/static/app/src/stores/DashboardConfigurationStore.js @@ -1,10 +1,11 @@ import {defineStore} from "pinia"; import {fetchGet, fetchPost} from "@/utilities/fetch.js"; -import {cookie} from "@/utilities/cookie.js"; +import {v4} from "uuid"; export const DashboardConfigurationStore = defineStore('DashboardConfigurationStore', { state: () => ({ - Configuration: undefined + Configuration: undefined, + Messages: [] }), actions: { async getConfiguration(){ @@ -23,6 +24,15 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt await fetchGet("/api/signout", {}, (res) => { this.$router.go('/signin') }); + }, + newMessage(from, content, type){ + this.Messages.push({ + id: v4(), + from: from, + content: content, + type: type, + show: true + }) } } }); \ No newline at end of file diff --git a/src/static/app/src/utilities/fetch.js b/src/static/app/src/utilities/fetch.js index 7d64ea9..a08d6de 100644 --- a/src/static/app/src/utilities/fetch.js +++ b/src/static/app/src/utilities/fetch.js @@ -7,9 +7,7 @@ export const fetchGet = async (url, params=undefined, callback=undefined) => { }) .then(x => x.json()) .then(x => callback ? callback(x) : undefined) - .catch(() => { - alert("Error occurred! Check console") - }); + } export const fetchPost = async (url, body, callback) => { diff --git a/src/static/app/src/views/configuration.vue b/src/static/app/src/views/configuration.vue new file mode 100644 index 0000000..d3b6eb3 --- /dev/null +++ b/src/static/app/src/views/configuration.vue @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/src/static/app/src/views/index.vue b/src/static/app/src/views/index.vue index f3259d5..b144da2 100644 --- a/src/static/app/src/views/index.vue +++ b/src/static/app/src/views/index.vue @@ -3,13 +3,19 @@ import Navbar from "@/components/navbar.vue"; import {wgdashboardStore} from "@/stores/wgdashboardStore.js"; import {WireguardConfigurations} from "@/models/WireguardConfigurations.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; +import Message from "@/components/messageCentreComponent/message.vue"; export default { name: "index", - components: {Navbar}, + components: {Message, Navbar}, async setup(){ const dashboardConfigurationStore = DashboardConfigurationStore() return {dashboardConfigurationStore} + }, + computed: { + getMessages(){ + return this.dashboardConfigurationStore.Messages.filter(x => x.show) + } } } @@ -26,11 +32,20 @@ export default { +
+ + + +
\ No newline at end of file diff --git a/src/static/app/src/views/newConfiguration.vue b/src/static/app/src/views/newConfiguration.vue index 1886cc1..a446d33 100644 --- a/src/static/app/src/views/newConfiguration.vue +++ b/src/static/app/src/views/newConfiguration.vue @@ -2,6 +2,7 @@ import {parse} from "cidr-tools"; import '@/utilities/wireguard.js' import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; +import {fetchPost} from "@/utilities/fetch.js"; export default { name: "newConfiguration", @@ -21,10 +22,13 @@ export default { PreUp: "", PreDown: "", PostUp: "", - PostDown: "", - UsePreSharedKey: false + PostDown: "" }, - numberOfAvailableIPs: "0" + numberOfAvailableIPs: "0", + error: false, + errorMessage: "", + success: false, + loading: false } }, created() { @@ -36,14 +40,32 @@ export default { this.newConfiguration.PrivateKey = wg.privateKey; this.newConfiguration.PublicKey = wg.publicKey; this.newConfiguration.PresharedKey = wg.presharedKey; - } + }, + async saveNewConfiguration(){ + if (this.goodToSubmit){ + this.loading = true; + await fetchPost("/api/addWireguardConfiguration", this.newConfiguration, async (res) => { + if (res.status){ + this.success = true + await this.store.getConfigurations() + setTimeout(() => { + this.$router.push('/') + }, 1000) + }else{ + this.error = true; + this.errorMessage = res.message; + document.querySelector(`#${res.data}`).classList.remove("is-valid") + document.querySelector(`#${res.data}`).classList.add("is-invalid") + + } + }) + } + } }, computed: { goodToSubmit(){ let requirements = ["ConfigurationName", "Address", "ListenPort", "PrivateKey"] let elements = [...document.querySelectorAll("input[required]")]; - - return requirements.find(x => { return this.newConfiguration[x].length === 0 }) === undefined && elements.find(x => { @@ -115,19 +137,26 @@ export default {

New Configuration

-
+
Configuration Name
- Configuration name is invalid. Possible reasons: -
    -
  • Configuration name already exist.
  • -
  • Configuration name can only contain 15 lower/uppercase alphabet, numbers, "_"(underscore), "="(equal), "+"(plus), "."(period/dot), "-"(dash/hyphen)
  • -
+
{{this.errorMessage}}
+
+ Configuration name is invalid. Possible reasons: +
    +
  • Configuration name already exist.
  • +
  • Configuration name can only contain 15 lower/uppercase alphabet, numbers, "_"(underscore), "="(equal), "+"(plus), "."(period/dot), "-"(dash/hyphen)
  • +
+
+
@@ -138,6 +167,7 @@ export default {
-
-
- - -
-
- - -
-
-
- - +
+ +
+
@@ -176,7 +195,14 @@ export default { min="1" max="65353" v-model="this.newConfiguration.ListenPort" + :disabled="this.loading" required> +
+
{{this.errorMessage}}
+
+ Invalid port +
+
@@ -188,9 +214,14 @@ export default {
- IP address & range is invalid. +
{{this.errorMessage}}
+
+ IP address & range is invalid. +
+
@@ -237,10 +268,21 @@ export default { -
diff --git a/src/static/css/dashboard.css b/src/static/css/dashboard.css index 56ab9ab..5821df9 100644 --- a/src/static/css/dashboard.css +++ b/src/static/css/dashboard.css @@ -603,7 +603,6 @@ main { .conf_card:hover { border-color: #007bff; - cursor: pointer; } .info_loading { @@ -1051,4 +1050,31 @@ pre.index-alert { .totp{ font-family: var(--bs-font-monospace); +} + +.message-move, /* apply transition to moving elements */ +.message-enter-active, +.message-leave-active { + transition: all 0.5s ease; +} + +.message-enter-from, +.message-leave-to { + filter: blur(2px); + opacity: 0; +} + +.message-enter-from{ + transform: translateY(-30px) scale(0.7); +} + +.message-leave-to{ + transform: translateY(30px); +} + + +/* ensure leaving items are taken out of layout flow so that moving + animations can be calculated correctly. */ +.message-leave-active { + position: absolute; } \ No newline at end of file