Compare commits

..

16 Commits

Author SHA1 Message Date
Donald Zou 3a34a0eb40 Adjusted some UI 2024-08-13 12:29:58 -04:00
Donald Zou e3f82e136a Adjusted some UI 2024-08-12 18:04:41 -04:00
Donald Zou 8a7df4ba9f Update wgd.sh 2024-08-12 01:19:55 -04:00
Donald Zou e86d1a4c7a Updated langugage 2024-08-12 00:34:54 -04:00
Donald Zou 5b9d0b60a1 Adjusted some UI 2024-08-11 19:20:52 -04:00
Donald Zou 7eff2f0c49 Fixed issue #250 2024-08-11 19:20:42 -04:00
Donald Zou 97236bb01d Fixed new configuration hang when error 2024-08-11 19:20:03 -04:00
Donald Zou 96ccb03eea Adjusted some code for electron version 2024-08-11 16:39:00 -04:00
Donald Zou 55f55820c5 Update wg-dashboard.service 2024-08-11 11:02:08 -04:00
Donald Zou 955839d513 I think cross server actually worked 2024-08-11 01:48:13 -04:00
Donald Zou a650e628e5 CORS SUCCESS!!! 2024-08-10 19:23:50 -04:00
Donald Zou 54142b73fb Ohhhhh kay testing CORS :) 2024-08-10 19:03:21 -04:00
Donald Zou 55e0d2695d Update .gitignore 2024-08-10 12:58:41 -04:00
Donald Zou 2f90ab15dc Let's try ElectronJS 2024-08-10 12:58:14 -04:00
Donald Zou fd3fc66bfc Thinking of adding Electron.js 2024-08-10 00:25:25 -04:00
Donald Zou a352a94d8a Update .gitignore 2024-08-10 00:24:07 -04:00
25 changed files with 2887 additions and 195 deletions
-1
View File
@@ -48,5 +48,4 @@ coverage
*.sw? *.sw?
*.tsbuildinfo *.tsbuildinfo
proxy.js
.vite/* .vite/*
-16
View File
@@ -1,16 +0,0 @@
#!/bin/bash
# if [ -z "$(ls -A /etc/wireguard)" ]; then
# mv /wg0.conf /etc/wireguard
# echo "Moved conf file to /etc/wireguard"
# else
# rm wg0.conf
# echo "Removed unneeded conf file"
# fi
# wg-quick up wg0
chmod u+x /opt/wgdashboard/wgd.sh
if [ ! -f "/opt/wgdashboard/wg-dashboard.ini" ]; then
/opt/wgdashboard/wgd.sh install
fi
/opt/wgdashboard/wgd.sh debug
+55 -27
View File
@@ -47,7 +47,11 @@ UPDATE = None
app = Flask("WGDashboard") app = Flask("WGDashboard")
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928 app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928
app.secret_key = secrets.token_urlsafe(32) app.secret_key = secrets.token_urlsafe(32)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}}) cors = CORS(app, resources={r"/api/*": {
"origins": "*",
"methods": "DELETE, POST, GET, OPTIONS",
"allow_headers": ["Content-Type", "wg-dashboard-apikey"]
}})
class ModelEncoder(JSONEncoder): class ModelEncoder(JSONEncoder):
def default(self, o: Any) -> Any: def default(self, o: Any) -> Any:
@@ -493,7 +497,7 @@ class WireguardConfiguration:
if self.Name not in existingTables: if self.Name not in existingTables:
sqldb.cursor().execute( sqldb.cursor().execute(
""" """
CREATE TABLE %s ( CREATE TABLE '%s'(
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
@@ -509,7 +513,7 @@ class WireguardConfiguration:
if f'{self.Name}_restrict_access' not in existingTables: if f'{self.Name}_restrict_access' not in existingTables:
sqldb.cursor().execute( sqldb.cursor().execute(
""" """
CREATE TABLE %s_restrict_access ( CREATE TABLE '%s_restrict_access' (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
@@ -524,7 +528,7 @@ class WireguardConfiguration:
if f'{self.Name}_transfer' not in existingTables: if f'{self.Name}_transfer' not in existingTables:
sqldb.cursor().execute( sqldb.cursor().execute(
""" """
CREATE TABLE %s_transfer ( CREATE TABLE '%s_transfer' (
id VARCHAR NOT NULL, total_receive FLOAT NULL, id VARCHAR NOT NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, total_sent FLOAT NULL, total_data FLOAT NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME
@@ -535,7 +539,7 @@ class WireguardConfiguration:
if f'{self.Name}_deleted' not in existingTables: if f'{self.Name}_deleted' not in existingTables:
sqldb.cursor().execute( sqldb.cursor().execute(
""" """
CREATE TABLE %s_deleted ( CREATE TABLE '%s_deleted' (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL, endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL, total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
@@ -559,7 +563,7 @@ class WireguardConfiguration:
def __getRestrictedPeers(self): def __getRestrictedPeers(self):
self.RestrictedPeers = [] self.RestrictedPeers = []
restricted = sqldb.cursor().execute("SELECT * FROM %s_restrict_access" % self.Name).fetchall() restricted = sqldb.cursor().execute("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall()
for i in restricted: for i in restricted:
self.RestrictedPeers.append(Peer(i, self)) self.RestrictedPeers.append(Peer(i, self))
@@ -584,7 +588,7 @@ class WireguardConfiguration:
p[pCounter][split[0]] = split[1] p[pCounter][split[0]] = split[1]
for i in p: for i in p:
if "PublicKey" in i.keys(): if "PublicKey" in i.keys():
checkIfExist = sqldb.cursor().execute("SELECT * FROM %s WHERE id = ?" % self.Name, checkIfExist = sqldb.cursor().execute("SELECT * FROM '%s' WHERE id = ?" % self.Name,
((i['PublicKey']),)).fetchone() ((i['PublicKey']),)).fetchone()
if checkIfExist is None: if checkIfExist is None:
newPeer = { newPeer = {
@@ -612,7 +616,7 @@ class WireguardConfiguration:
} }
sqldb.cursor().execute( sqldb.cursor().execute(
""" """
INSERT INTO %s INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent, VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent, :total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key); :cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
@@ -621,7 +625,7 @@ class WireguardConfiguration:
sqldb.commit() sqldb.commit()
self.Peers.append(Peer(newPeer, self)) self.Peers.append(Peer(newPeer, self))
else: else:
sqldb.cursor().execute("UPDATE %s SET allowed_ip = ? WHERE id = ?" % self.Name, sqldb.cursor().execute("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name,
(i.get("AllowedIPs", "N/A"), i['PublicKey'],)) (i.get("AllowedIPs", "N/A"), i['PublicKey'],))
sqldb.commit() sqldb.commit()
self.Peers.append(Peer(checkIfExist, self)) self.Peers.append(Peer(checkIfExist, self))
@@ -649,11 +653,11 @@ class WireguardConfiguration:
self.toggleConfiguration() self.toggleConfiguration()
for i in listOfPublicKeys: for i in listOfPublicKeys:
p = sqldb.cursor().execute("SELECT * FROM %s_restrict_access WHERE id = ?" % self.Name, (i,)).fetchone() p = sqldb.cursor().execute("SELECT * FROM '%s_restrict_access' WHERE id = ?" % self.Name, (i,)).fetchone()
if p is not None: if p is not None:
sqldb.cursor().execute("INSERT INTO %s SELECT * FROM %s_restrict_access WHERE id = ?" sqldb.cursor().execute("INSERT INTO '%s' SELECT * FROM %s_restrict_access WHERE id = ?"
% (self.Name, self.Name,), (p['id'],)) % (self.Name, self.Name,), (p['id'],))
sqldb.cursor().execute("DELETE FROM %s_restrict_access WHERE id = ?" sqldb.cursor().execute("DELETE FROM '%s_restrict_access' WHERE id = ?"
% self.Name, (p['id'],)) % self.Name, (p['id'],))
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}", subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
@@ -676,11 +680,11 @@ class WireguardConfiguration:
try: try:
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove", subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
sqldb.cursor().execute("INSERT INTO %s_restrict_access SELECT * FROM %s WHERE id = ?" % sqldb.cursor().execute("INSERT INTO '%s_restrict_access' SELECT * FROM %s WHERE id = ?" %
(self.Name, self.Name,), (pf.id,)) (self.Name, self.Name,), (pf.id,))
sqldb.cursor().execute("UPDATE %s_restrict_access SET status = 'stopped' WHERE id = ?" % sqldb.cursor().execute("UPDATE '%s_restrict_access' SET status = 'stopped' WHERE id = ?" %
(self.Name,), (pf.id,)) (self.Name,), (pf.id,))
sqldb.cursor().execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,)) sqldb.cursor().execute("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,))
numOfRestrictedPeers += 1 numOfRestrictedPeers += 1
except Exception as e: except Exception as e:
numOfFailedToRestrictPeers += 1 numOfFailedToRestrictPeers += 1
@@ -707,7 +711,7 @@ class WireguardConfiguration:
try: try:
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove", subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
sqldb.cursor().execute("DELETE FROM %s WHERE id = ?" % self.Name, (pf.id,)) sqldb.cursor().execute("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,))
numOfDeletedPeers += 1 numOfDeletedPeers += 1
except Exception as e: except Exception as e:
numOfFailedToDeletePeers += 1 numOfFailedToDeletePeers += 1
@@ -727,7 +731,7 @@ class WireguardConfiguration:
d = i.toJson() d = i.toJson()
sqldb.execute( sqldb.execute(
''' '''
UPDATE %s SET private_key = :private_key, UPDATE '%s' SET private_key = :private_key,
DNS = :DNS, endpoint_allowed_ip = :endpoint_allowed_ip, name = :name, DNS = :DNS, endpoint_allowed_ip = :endpoint_allowed_ip, name = :name,
total_receive = :total_receive, total_sent = :total_sent, total_data = :total_data, total_receive = :total_receive, total_sent = :total_sent, total_data = :total_data,
endpoint = :endpoint, status = :status, latest_handshake = :latest_handshake, endpoint = :endpoint, status = :status, latest_handshake = :latest_handshake,
@@ -764,10 +768,10 @@ class WireguardConfiguration:
else: else:
status = "stopped" status = "stopped"
if int(latestHandshake[count + 1]) > 0: if int(latestHandshake[count + 1]) > 0:
sqldb.execute("UPDATE %s SET latest_handshake = ?, status = ? WHERE id= ?" % self.Name sqldb.execute("UPDATE '%s' SET latest_handshake = ?, status = ? WHERE id= ?" % self.Name
, (str(minus).split(".", maxsplit=1)[0], status, latestHandshake[count],)) , (str(minus).split(".", maxsplit=1)[0], status, latestHandshake[count],))
else: else:
sqldb.execute("UPDATE %s SET latest_handshake = 'No Handshake', status = ? WHERE id= ?" % self.Name sqldb.execute("UPDATE '%s' SET latest_handshake = 'No Handshake', status = ? WHERE id= ?" % self.Name
, (status, latestHandshake[count],)) , (status, latestHandshake[count],))
sqldb.commit() sqldb.commit()
count += 2 count += 2
@@ -783,7 +787,7 @@ class WireguardConfiguration:
for i in range(len(data_usage)): for i in range(len(data_usage)):
if len(data_usage[i]) == 3: if len(data_usage[i]) == 3:
cur_i = sqldb.cursor().execute( cur_i = sqldb.cursor().execute(
"SELECT total_receive, total_sent, cumu_receive, cumu_sent, status FROM %s WHERE id= ? " "SELECT total_receive, total_sent, cumu_receive, cumu_sent, status FROM '%s' WHERE id= ? "
% self.Name, (data_usage[i][0],)).fetchone() % self.Name, (data_usage[i][0],)).fetchone()
if cur_i is not None: if cur_i is not None:
total_sent = cur_i['total_sent'] total_sent = cur_i['total_sent']
@@ -797,7 +801,7 @@ class WireguardConfiguration:
total_receive = cur_total_receive total_receive = cur_total_receive
else: else:
sqldb.cursor().execute( sqldb.cursor().execute(
"UPDATE %s SET cumu_receive = ?, cumu_sent = ?, cumu_data = ? WHERE id = ?" % "UPDATE '%s' SET cumu_receive = ?, cumu_sent = ?, cumu_data = ? WHERE id = ?" %
self.Name, (cumulative_receive, cumulative_sent, self.Name, (cumulative_receive, cumulative_sent,
cumulative_sent + cumulative_receive, cumulative_sent + cumulative_receive,
data_usage[i][0],)) data_usage[i][0],))
@@ -807,7 +811,7 @@ class WireguardConfiguration:
_, p = self.searchPeer(data_usage[i][0]) _, p = self.searchPeer(data_usage[i][0])
if p.total_receive != total_receive or p.total_sent != total_sent: if p.total_receive != total_receive or p.total_sent != total_sent:
sqldb.cursor().execute( sqldb.cursor().execute(
"UPDATE %s SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?" "UPDATE '%s' SET total_receive = ?, total_sent = ?, total_data = ? WHERE id = ?"
% self.Name, (total_receive, total_sent, % self.Name, (total_receive, total_sent,
total_receive + total_sent, data_usage[i][0],)) total_receive + total_sent, data_usage[i][0],))
except Exception as e: except Exception as e:
@@ -824,7 +828,7 @@ class WireguardConfiguration:
data_usage = data_usage.decode("UTF-8").split() data_usage = data_usage.decode("UTF-8").split()
count = 0 count = 0
for _ in range(int(len(data_usage) / 2)): for _ in range(int(len(data_usage) / 2)):
sqldb.execute("UPDATE %s SET endpoint = ? WHERE id = ?" % self.Name sqldb.execute("UPDATE '%s' SET endpoint = ? WHERE id = ?" % self.Name
, (data_usage[count + 1], data_usage[count],)) , (data_usage[count + 1], data_usage[count],))
sqldb.commit() sqldb.commit()
count += 2 count += 2
@@ -962,7 +966,7 @@ class Peer:
return ResponseObject(False, return ResponseObject(False,
"Update peer failed when saving the configuration.") "Update peer failed when saving the configuration.")
sqldb.cursor().execute( sqldb.cursor().execute(
'''UPDATE %s SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?, '''UPDATE '%s' SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?,
keepalive = ?, preshared_key = ? WHERE id = ?''' % self.configuration.Name, keepalive = ?, preshared_key = ? WHERE id = ?''' % self.configuration.Name,
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu, (name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
keepalive, preshared_key, self.id,) keepalive, preshared_key, self.id,)
@@ -1014,11 +1018,11 @@ PersistentKeepalive = {str(self.keepalive)}
def resetDataUsage(self, type): def resetDataUsage(self, type):
try: try:
if type == "total": if type == "total":
sqldb.cursor().execute("UPDATE %s SET total_data = 0, cumu_data = 0, total_receive = 0, cumu_receive = 0, total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, )) sqldb.cursor().execute("UPDATE '%s' SET total_data = 0, cumu_data = 0, total_receive = 0, cumu_receive = 0, total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
elif type == "receive": elif type == "receive":
sqldb.cursor().execute("UPDATE %s SET total_receive = 0, cumu_receive = 0 WHERE id = ?" % self.configuration.Name, (self.id, )) sqldb.cursor().execute("UPDATE '%s' SET total_receive = 0, cumu_receive = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
elif type == "sent": elif type == "sent":
sqldb.cursor().execute("UPDATE %s SET total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, )) sqldb.cursor().execute("UPDATE '%s' SET total_sent = 0, cumu_sent = 0 WHERE id = ?" % self.configuration.Name, (self.id, ))
else: else:
return False return False
except Exception as e: except Exception as e:
@@ -1092,6 +1096,7 @@ class DashboardConfig:
self.SetConfig(section, key, value, True) self.SetConfig(section, key, value, True)
self.__createAPIKeyTable() self.__createAPIKeyTable()
self.DashboardAPIKeys = self.__getAPIKeys() self.DashboardAPIKeys = self.__getAPIKeys()
self.APIAccessed = False
def __createAPIKeyTable(self): def __createAPIKeyTable(self):
existingTable = sqldb.cursor().execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall() existingTable = sqldb.cursor().execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall()
@@ -1339,12 +1344,17 @@ def _getWireguardConfigurationAvailableIP(configName: str) -> tuple[bool, list[s
return False, None return False, None
''' '''
API Routes API Routes
''' '''
@app.before_request @app.before_request
def auth_req(): def auth_req():
if request.method.lower() == 'options':
return ResponseObject(True)
DashboardConfig.APIAccessed = False
if "api" in request.path: if "api" in request.path:
if str(request.method) == "GET": if str(request.method) == "GET":
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args)) DashboardLogger.log(str(request.url), str(request.remote_addr), Message=str(request.args))
@@ -1361,6 +1371,7 @@ def auth_req():
apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1 apiKeyExist = len(list(filter(lambda x : x.Key == apiKey, DashboardConfig.DashboardAPIKeys))) == 1
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}") DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}")
if not apiKeyExist: if not apiKeyExist:
DashboardConfig.APIAccessed = False
response = Flask.make_response(app, { response = Flask.make_response(app, {
"status": False, "status": False,
"message": "API Key does not exist", "message": "API Key does not exist",
@@ -1369,7 +1380,9 @@ def auth_req():
response.content_type = "application/json" response.content_type = "application/json"
response.status_code = 401 response.status_code = 401
return response return response
DashboardConfig.APIAccessed = True
else: else:
DashboardConfig.APIAccessed = False
if ('/static/' not in request.path and "username" not in session and "/" != request.path if ('/static/' not in request.path and "username" not in session and "/" != request.path
and "validateAuthentication" not in request.path and "authenticate" not in request.path and "validateAuthentication" not in request.path and "authenticate" not in request.path
and "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path and "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path
@@ -1385,6 +1398,10 @@ def auth_req():
response.status_code = 401 response.status_code = 401
return response return response
@app.route('/api/handshake', methods=["GET", "OPTIONS"])
def API_ValidateAPIKey():
return ResponseObject(True)
@app.route('/api/validateAuthentication', methods=["GET"]) @app.route('/api/validateAuthentication', methods=["GET"])
def API_ValidateAuthentication(): def API_ValidateAuthentication():
@@ -1397,6 +1414,17 @@ def API_ValidateAuthentication():
@app.route('/api/authenticate', methods=['POST']) @app.route('/api/authenticate', methods=['POST'])
def API_AuthenticateLogin(): def API_AuthenticateLogin():
data = request.get_json() data = request.get_json()
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])
print(data['host'])
resp.set_cookie("authToken", authToken, domain=data['host'])
session.permanent = True
return resp
valid = bcrypt.checkpw(data['password'].encode("utf-8"), valid = bcrypt.checkpw(data['password'].encode("utf-8"),
DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8")) DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1] totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
File diff suppressed because one or more lines are too long
+28 -28
View File
File diff suppressed because one or more lines are too long
+2286 -2
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -1,11 +1,12 @@
{ {
"name": "app", "name": "app",
"version": "0.0.0", "version": "4.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"build electron": "vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
@@ -17,6 +18,7 @@
"bootstrap-icons": "^1.11.2", "bootstrap-icons": "^1.11.2",
"cidr-tools": "^7.0.4", "cidr-tools": "^7.0.4",
"dayjs": "^1.11.12", "dayjs": "^1.11.12",
"electron-builder": "^24.13.3",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"i": "^0.3.7", "i": "^0.3.7",
"is-cidr": "^5.0.3", "is-cidr": "^5.0.3",
+27 -3
View File
@@ -1,13 +1,37 @@
<script setup > <script setup>
import { RouterView } from 'vue-router' import { RouterView } from 'vue-router'
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {computed, watch} from "vue";
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
store.initCrossServerConfiguration();
if (window.IS_WGDASHBOARD_DESKTOP){
store.IsElectronApp = true;
store.CrossServerConfiguration.Enable = true;
}
watch(store.CrossServerConfiguration, () => {
store.syncCrossServerConfiguration()
}, {
deep: true
});
const getActiveCrossServer = computed(() => {
if (store.ActiveServerConfiguration){
return store.CrossServerConfiguration.ServerList[store.ActiveServerConfiguration]
}
return undefined
})
</script> </script>
<template> <template>
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark"> <nav class="navbar bg-dark sticky-top border-bottom border-secondary-subtle" data-bs-theme="dark">
<div class="container-fluid"> <div class="container-fluid d-flex text-body align-items-center">
<span class="navbar-brand mb-0 h1">WGDashboard</span> <span class="navbar-brand mb-0 h1">WGDashboard</span>
<small class="ms-auto text-muted" v-if="getActiveCrossServer !== undefined">
<i class="bi bi-server me-2"></i>{{getActiveCrossServer.host}}
</small>
<a role="button"><i class="bi bi-list"></i></a>
</div> </div>
</nav> </nav>
<Suspense> <Suspense>
@@ -87,6 +87,12 @@ export default {
}, },
computed: { computed: {
getUrl(){ getUrl(){
const crossServer = this.store.getActiveCrossServer();
if(crossServer){
return `${crossServer.host}/${this.$router.resolve(
{path: "/share", query: {"ShareID": this.dataCopy.ShareID}}).href}`
}
return window.location.origin return window.location.origin
+ window.location.pathname + window.location.pathname
+ this.$router.resolve( + this.$router.resolve(
+7 -2
View File
@@ -14,7 +14,7 @@ export default {
</script> </script>
<template> <template>
<div class="col-md-3 col-lg-2 d-md-block p-3" style="height: calc(-50px + 100vh);"> <div class="col-md-3 col-lg-2 d-md-block p-3 navbar-container bg-body" style="height: calc(-50px + 100vh);">
<nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow overflow-y-scroll" > <nav id="sidebarMenu" class=" bg-body-tertiary sidebar border h-100 rounded-3 shadow overflow-y-scroll" >
<div class="sidebar-sticky pt-3"> <div class="sidebar-sticky pt-3">
<ul class="nav flex-column px-2"> <ul class="nav flex-column px-2">
@@ -75,5 +75,10 @@ export default {
</template> </template>
<style scoped> <style scoped>
@media screen and (max-width: 768px) {
.navbar-container{
position: absolute;
z-index: 1000;
}
}
</style> </style>
@@ -47,7 +47,7 @@ export default {
<div class="form-check form-switch ms-3"> <div class="form-check form-switch ms-3">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
v-model="this.status" v-model="this.status"
role="switch" id="allowAPIKeysSwitch"> role="switch" id="allowMFAKeysSwitch">
</div> </div>
<button class="btn bg-warning-subtle text-warning-emphasis border-1 border-warning-subtle ms-auto rounded-3 shadow-sm" <button class="btn bg-warning-subtle text-warning-emphasis border-1 border-warning-subtle ms-auto rounded-3 shadow-sm"
v-if="this.status" @click="this.resetMFA()"> v-if="this.status" @click="this.resetMFA()">
@@ -65,7 +65,7 @@ export default {
<div class="card mb-4 shadow rounded-3"> <div class="card mb-4 shadow rounded-3">
<div class="card-header d-flex"> <div class="card-header d-flex">
API Keys API Keys
<div class="form-check form-switch ms-auto"> <div class="form-check form-switch ms-auto" v-if="!this.store.getActiveCrossServer()">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
v-model="this.value" v-model="this.value"
@change="this.toggleDashboardAPIKeys()" @change="this.toggleDashboardAPIKeys()"
@@ -78,6 +78,7 @@ export default {
<div class="card-body position-relative d-flex flex-column gap-2" v-if="this.value"> <div class="card-body position-relative d-flex flex-column gap-2" v-if="this.value">
<button class="ms-auto btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm" <button class="ms-auto btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm"
@click="this.newDashboardAPIKey = true" @click="this.newDashboardAPIKey = true"
v-if="!this.store.getActiveCrossServer()"
> >
<i class="bi bi-key me-2"></i> Create <i class="bi bi-key me-2"></i> Create
</button> </button>
@@ -35,26 +35,31 @@ export default {
<template> <template>
<div class="card rounded-3 shadow-sm"> <div class="card rounded-3 shadow-sm">
<div class="card-body d-flex gap-3 align-items-center" v-if="!this.confirmDelete"> <div class="card-body d-flex gap-3 align-items-center apiKey-card-body" v-if="!this.confirmDelete">
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<small class="text-muted">Key</small>{{this.apiKey.Key}} <small class="text-muted">Key</small>
<span style="word-break: break-all">{{this.apiKey.Key}}</span>
</div> </div>
<div class="d-flex align-items-center gap-2 ms-auto"> <div class="d-flex align-items-center gap-2 ms-auto">
<small class="text-muted">Expire At</small> <small class="text-muted">Expire At</small>
{{this.apiKey.ExpiredAt ? this.apiKey.ExpiredAt : 'Never'}} {{this.apiKey.ExpiredAt ? this.apiKey.ExpiredAt : 'Never'}}
</div> </div>
<a role="button" class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3" @click="this.confirmDelete = true"> <a role="button" class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3"
v-if="!this.store.getActiveCrossServer()"
@click="this.confirmDelete = true">
<i class="bi bi-trash-fill"></i> <i class="bi bi-trash-fill"></i>
</a> </a>
</div> </div>
<div v-else class="card-body d-flex gap-3 align-items-center justify-content-end"> <div v-else class="card-body d-flex gap-3 align-items-center justify-content-end"
v-if="!this.store.getActiveCrossServer()">
Are you sure to delete this API key? Are you sure to delete this API key?
<a role="button" class="btn btn-sm bg-success-subtle text-success-emphasis rounded-3" <a role="button" class="btn btn-sm bg-success-subtle text-success-emphasis rounded-3"
@click="this.deleteAPIKey()" @click="this.deleteAPIKey()"
> >
<i class="bi bi-check-lg"></i> <i class="bi bi-check-lg"></i>
</a> </a>
<a role="button" class="btn btn-sm bg-secondary-subtle text-secondary-emphasis rounded-3" @click="this.confirmDelete = false"> <a role="button" class="btn btn-sm bg-secondary-subtle text-secondary-emphasis rounded-3"
@click="this.confirmDelete = false">
<i class="bi bi-x-lg"></i> <i class="bi bi-x-lg"></i>
</a> </a>
</div> </div>
@@ -62,5 +67,23 @@ export default {
</template> </template>
<style scoped> <style scoped>
@media screen and (max-width: 992px) {
.apiKey-card-body{
flex-direction: column !important;
align-items: start !important;
div.ms-auto{
margin-left: 0 !important;
}
div{
width: 100%;
align-items: start !important;
}
small{
margin-right: auto;
}
}
}
</style> </style>
@@ -0,0 +1,164 @@
<script>
import dayjs from "dayjs";
export default {
name: "RemoteServer",
props: {
server: Object
},
data(){
return{
active: false,
startTime: undefined,
endTime: undefined,
errorMsg: "",
refreshing: false
}
},
methods: {
async handshake(){
this.active = false;
this.refreshing = true;
if (this.server.host && this.server.apiKey){
this.startTime = undefined;
this.endTime = undefined;
this.startTime = dayjs()
await fetch(`${this.server.host}/api/handshake`, {
headers: {
"content-type": "application/json",
"wg-dashboard-apikey": this.server.apiKey
},
method: "GET",
signal: AbortSignal.timeout(5000)
}).then(res => {
if (res.status === 200){
return res.json()
}
throw new Error(res.statusText)
}).then(() => {
this.endTime = dayjs()
this.active = true;
}).catch((res) => {
this.active = false;
this.errorMsg = res;
});
this.refreshing = false;
}
},
async connect(){
await fetch(`${this.server.host}/api/authenticate`, {
headers: {
"content-type": "application/json",
"wg-dashboard-apikey": this.server.apiKey
},
body: JSON.stringify({
host: window.location.hostname
}),
method: "POST",
signal: AbortSignal.timeout(5000),
}).then(res => res.json()).then(res => {
this.$emit("setActiveServer")
this.$router.push('/')
})
}
},
mounted() {
this.handshake()
},
computed: {
getHandshakeTime(){
if (this.startTime && this.endTime){
return `${dayjs().subtract(this.startTime).millisecond()}ms`
}else{
if (this.refreshing){
return `Pinging...`
}
return this.errorMsg ? this.errorMsg : "N/A"
}
}
}
}
</script>
<template>
<div class="card rounded-3">
<div class="card-body">
<div class="d-flex gap-3 w-100 remoteServerContainer">
<div class="d-flex gap-3 align-items-center flex-grow-1">
<i class="bi bi-server"></i>
<input class="form-control form-control-sm"
@blur="this.handshake()"
v-model="this.server.host"
type="url">
</div>
<div class="d-flex gap-3 align-items-center flex-grow-1">
<i class="bi bi-key-fill"></i>
<input class="form-control form-control-sm"
@blur="this.handshake()"
v-model="this.server.apiKey"
type="text">
</div>
<div class="d-flex gap-2 button-group">
<button
@click="this.$emit('delete')"
class="ms-auto btn btn-sm bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle">
<i class="bi bi-trash"></i>
</button>
<button
@click="this.connect()"
:class="{disabled: !this.active}"
class="ms-auto btn btn-sm bg-success-subtle text-success-emphasis border-1 border-success-subtle">
<i class="bi bi-arrow-right-circle"></i>
</button>
</div>
</div>
</div>
<div class="card-footer gap-2 d-flex align-items-center">
<span class="dot ms-0 me-2" :class="[this.active ? 'active':'inactive']"></span>
<small>{{this.getHandshakeTime}}</small>
<div class="spin ms-auto text-primary-emphasis" v-if="this.refreshing">
<i class="bi bi-arrow-clockwise"></i>
</div>
<a role="button"
v-else
@click="this.handshake()"
class="text-primary-emphasis text-decoration-none ms-auto disabled">
<i class="bi bi-arrow-clockwise me"></i>
</a>
</div>
</div>
</template>
<style scoped>
.dot.inactive{
background-color: #dc3545;
box-shadow: 0 0 0 0.2rem #dc354545;
}
.spin{
animation: spin 1s infinite cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
@keyframes spin {
0%{
transform: rotate(0deg);
}
100%{
transform: rotate(360deg);
}
}
@media screen and (max-width: 768px) {
.remoteServerContainer{
flex-direction: column;
}
.remoteServerContainer .button-group button{
width: 100%;
}
}
</style>
@@ -0,0 +1,40 @@
<script>
import RemoteServer from "@/components/signInComponents/RemoteServer.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "RemoteServerList",
setup(){
const store = DashboardConfigurationStore();
return {store}
},
components: {RemoteServer}
}
</script>
<template>
<div class="w-100 mt-3">
<div class="d-flex align-items-center mb-3">
<h5 class="mb-0">Server List</h5>
<button
@click="this.store.addCrossServerConfiguration()"
class="btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle shadow-sm ms-auto">
<i class="bi bi-plus-circle-fill me-2"></i>Server
</button>
</div>
<div class="w-100 d-flex gap-3 flex-column p-3 border border-1 border-secondary-subtle rounded-3"
style="height: 400px; overflow-y: scroll">
<RemoteServer v-for="(server, key) in this.store.CrossServerConfiguration.ServerList"
@setActiveServer="this.store.setActiveCrossServer(key)"
@delete="this.store.deleteCrossServerConfiguration(key)"
:key="key"
:server="server"></RemoteServer>
<h6 class="text-muted m-auto" v-if="Object.keys(this.store.CrossServerConfiguration.ServerList).length === 0">
Click<i class="bi bi-plus-circle-fill mx-1"></i>to add your server</h6>
</div>
</div>
</template>
<style scoped>
</style>
+15 -6
View File
@@ -138,19 +138,28 @@ router.beforeEach(async (to, from, next) => {
} }
if (to.meta.requiresAuth){ if (to.meta.requiresAuth){
if (cookie.getCookie("authToken") && await checkAuth()){ if (!dashboardConfigurationStore.getActiveCrossServer()){
if (cookie.getCookie("authToken") && await checkAuth()){
await dashboardConfigurationStore.getConfiguration()
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
await wireguardConfigurationsStore.getConfigurations();
}
dashboardConfigurationStore.Redirect = undefined;
next()
}else{
dashboardConfigurationStore.Redirect = to;
next("/signin")
dashboardConfigurationStore.newMessage("WGDashboard", "Session Ended", "warning")
}
}else{
await dashboardConfigurationStore.getConfiguration() await dashboardConfigurationStore.getConfiguration()
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){ if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
await wireguardConfigurationsStore.getConfigurations(); await wireguardConfigurationsStore.getConfigurations();
} }
dashboardConfigurationStore.Redirect = undefined;
next() next()
}else{
dashboardConfigurationStore.Redirect = to;
next("/signin")
dashboardConfigurationStore.newMessage("WGDashboard", "Session Ended", "warning")
} }
}else { }else {
next(); next();
} }
}); });
@@ -10,9 +10,53 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
Peers: { Peers: {
Selecting: false, Selecting: false,
RefreshInterval: undefined RefreshInterval: undefined
} },
CrossServerConfiguration:{
Enable: false,
ServerList: {}
},
ActiveServerConfiguration: undefined,
IsElectronApp: false
}), }),
actions: { actions: {
initCrossServerConfiguration(){
const currentConfiguration = localStorage.getItem('CrossServerConfiguration');
if (localStorage.getItem("ActiveCrossServerConfiguration") !== null){
this.ActiveServerConfiguration = localStorage.getItem("ActiveCrossServerConfiguration");
}
if (currentConfiguration === null){
localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
}else{
this.CrossServerConfiguration = JSON.parse(currentConfiguration)
}
},
syncCrossServerConfiguration(){
localStorage.setItem('CrossServerConfiguration', JSON.stringify(this.CrossServerConfiguration))
},
addCrossServerConfiguration(){
this.CrossServerConfiguration.ServerList[v4().toString()] = {host: "", apiKey: "", active: false}
},
deleteCrossServerConfiguration(key){
delete this.CrossServerConfiguration.ServerList[key];
},
getActiveCrossServer(){
const key = localStorage.getItem('ActiveCrossServerConfiguration');
if (key !== null){
return this.CrossServerConfiguration.ServerList[key]
}
return undefined
},
setActiveCrossServer(key){
this.ActiveServerConfiguration = key;
localStorage.setItem('ActiveCrossServerConfiguration', key)
},
removeActiveCrossServer(){
this.ActiveServerConfiguration = undefined;
localStorage.removeItem('ActiveCrossServerConfiguration')
},
async getConfiguration(){ async getConfiguration(){
await fetchGet("/api/getDashboardConfiguration", {}, (res) => { await fetchGet("/api/getDashboardConfiguration", {}, (res) => {
if (res.status) this.Configuration = res.data if (res.status) this.Configuration = res.data
@@ -27,6 +71,7 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
}, },
async signOut(){ async signOut(){
await fetchGet("/api/signout", {}, (res) => { await fetchGet("/api/signout", {}, (res) => {
this.removeActiveCrossServer();
this.$router.go('/signin') this.$router.go('/signin')
}); });
}, },
+30 -10
View File
@@ -1,18 +1,38 @@
import router from "@/router/index.js"; import router from "@/router/index.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
const getHeaders = () => {
let headers = {
"content-type": "application/json"
}
const store = DashboardConfigurationStore();
const apiKey = store.getActiveCrossServer();
if (apiKey){
headers['wg-dashboard-apikey'] = apiKey.apiKey
}
return headers
}
const getUrl = (url) => {
const store = DashboardConfigurationStore();
const apiKey = store.getActiveCrossServer();
if (apiKey){
return `${apiKey.host}${url}`
}
return url
}
export const fetchGet = async (url, params=undefined, callback=undefined) => { export const fetchGet = async (url, params=undefined, callback=undefined) => {
const urlSearchParams = new URLSearchParams(params); const urlSearchParams = new URLSearchParams(params);
await fetch(`${url}?${urlSearchParams.toString()}`, { await fetch(`${getUrl(url)}?${urlSearchParams.toString()}`, {
headers: { headers: getHeaders()
"content-type": "application/json"
}
}) })
.then((x) => { .then((x) => {
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
if (!x.ok){ if (!x.ok){
if (x.status !== 200){ if (x.status !== 200){
if (x.status === 401){ if (x.status === 401){
router.push({path: '/signin'})
store.newMessage("WGDashboard", "Session Ended", "warning") store.newMessage("WGDashboard", "Session Ended", "warning")
} }
throw new Error(x.statusText) throw new Error(x.statusText)
@@ -22,14 +42,13 @@ export const fetchGet = async (url, params=undefined, callback=undefined) => {
} }
}).then(x => callback ? callback(x) : undefined).catch(x => { }).then(x => callback ? callback(x) : undefined).catch(x => {
console.log(x) console.log(x)
router.push({path: '/signin'})
}) })
} }
export const fetchPost = async (url, body, callback) => { export const fetchPost = async (url, body, callback) => {
await fetch(`${url}`, { await fetch(`${getUrl(url)}`, {
headers: { headers: getHeaders(),
"content-type": "application/json"
},
method: "POST", method: "POST",
body: JSON.stringify(body) body: JSON.stringify(body)
}).then((x) => { }).then((x) => {
@@ -37,7 +56,7 @@ export const fetchPost = async (url, body, callback) => {
if (!x.ok){ if (!x.ok){
if (x.status !== 200){ if (x.status !== 200){
if (x.status === 401){ if (x.status === 401){
router.push({path: '/signin'})
store.newMessage("WGDashboard", "Session Ended", "warning") store.newMessage("WGDashboard", "Session Ended", "warning")
} }
throw new Error(x.statusText) throw new Error(x.statusText)
@@ -47,5 +66,6 @@ export const fetchPost = async (url, body, callback) => {
} }
}).then(x => callback ? callback(x) : undefined).catch(x => { }).then(x => callback ? callback(x) : undefined).catch(x => {
console.log(x) console.log(x)
router.push({path: '/signin'})
}) })
} }
@@ -56,7 +56,7 @@ export default {
this.errorMessage = res.message; this.errorMessage = res.message;
document.querySelector(`#${res.data}`).classList.remove("is-valid") document.querySelector(`#${res.data}`).classList.remove("is-valid")
document.querySelector(`#${res.data}`).classList.add("is-invalid") document.querySelector(`#${res.data}`).classList.add("is-invalid")
this.loading = false;
} }
}) })
} }
@@ -126,15 +126,14 @@ export default {
</script> </script>
<template> <template>
<div class="mt-4"> <div class="mt-5">
<div class="container mb-4"> <div class="container mb-4">
<div class="mb-4 d-flex align-items-center gap-4"> <div class="mb-4 d-flex align-items-center gap-4">
<RouterLink to="/"> <RouterLink to="/" class="text-decoration-none">
<h3 class="mb-0 text-body"> <h3 class="mb-0 text-body">
<i class="bi bi-chevron-left"></i> <i class="bi bi-chevron-left me-4"></i> New Configuration
</h3> </h3>
</RouterLink> </RouterLink>
<h3 class="text-body mb-0">New Configuration</h3>
</div> </div>
<form class="text-body d-flex flex-column gap-3" <form class="text-body d-flex flex-column gap-3"
+2 -2
View File
@@ -77,8 +77,8 @@ export default {
<AccountSettingsInputPassword <AccountSettingsInputPassword
targetData="password"> targetData="password">
</AccountSettingsInputPassword> </AccountSettingsInputPassword>
<hr class="m-0"> <hr class="m-0" v-if="!this.dashboardConfigurationStore.getActiveCrossServer()">
<AccountSettingsMFA></AccountSettingsMFA> <AccountSettingsMFA v-if="!this.dashboardConfigurationStore.getActiveCrossServer()"></AccountSettingsMFA>
</div> </div>
</div> </div>
<DashboardAPIKeys></DashboardAPIKeys> <DashboardAPIKeys></DashboardAPIKeys>
+68 -47
View File
@@ -2,20 +2,24 @@
import {fetchGet, fetchPost} from "../utilities/fetch.js"; import {fetchGet, fetchPost} from "../utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import Message from "@/components/messageCentreComponent/message.vue"; import Message from "@/components/messageCentreComponent/message.vue";
import RemoteServerList from "@/components/signInComponents/RemoteServerList.vue";
export default { export default {
name: "signin", name: "signin",
components: {Message}, components: {RemoteServerList, Message},
async setup(){ async setup(){
const store = DashboardConfigurationStore() const store = DashboardConfigurationStore()
let theme = "" let theme = "dark"
let totpEnabled = false; let totpEnabled = false;
await fetchGet("/api/getDashboardTheme", {}, (res) => { if (!store.IsElectronApp){
theme = res.data await fetchGet("/api/getDashboardTheme", {}, (res) => {
}); theme = res.data
await fetchGet("/api/isTotpEnabled", {}, (res) => { });
totpEnabled = res.data await fetchGet("/api/isTotpEnabled", {}, (res) => {
}); totpEnabled = res.data
});
}
store.removeActiveCrossServer();
return {store, theme, totpEnabled} return {store, theme, totpEnabled}
}, },
data(){ data(){
@@ -82,57 +86,66 @@ export default {
</script> </script>
<template> <template>
<div class="container-fluid login-container-fluid d-flex main flex-column" :data-bs-theme="this.theme"> <div class="container-fluid login-container-fluid d-flex main flex-column py-4 text-body"
<div class="login-box m-auto" style="width: 600px;"> style="overflow-y: scroll"
<div class="m-auto"> :data-bs-theme="this.theme">
<div class="card px-4 py-5 rounded-4 shadow-lg"> <div class="login-box m-auto" >
<div class="card-body"> <div class="m-auto" style="width: 700px;">
<h4 class="mb-0 text-body">Welcome to</h4> <h4 class="mb-0 text-body">Welcome to</h4>
<span class="dashboardLogo display-3"><strong>WGDashboard</strong></span> <span class="dashboardLogo display-3"><strong>WGDashboard</strong></span>
<div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError"> <div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError">
{{this.loginErrorMessage}} {{this.loginErrorMessage}}
</div> </div>
<form @submit="(e) => {e.preventDefault(); this.auth();}"> <form @submit="(e) => {e.preventDefault(); this.auth();}"
<div class="form-group text-body"> v-if="!this.store.CrossServerConfiguration.Enable">
<label for="username" class="text-left" style="font-size: 1rem"> <div class="form-group text-body">
<i class="bi bi-person-circle"></i></label> <label for="username" class="text-left" style="font-size: 1rem">
<input type="text" v-model="username" class="form-control" id="username" name="username" <i class="bi bi-person-circle"></i></label>
autocomplete="on" <input type="text" v-model="username" class="form-control" id="username" name="username"
placeholder="Username" required> autocomplete="on"
</div> placeholder="Username" required>
<div class="form-group text-body"> </div>
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label> <div class="form-group text-body">
<input type="password" <label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
v-model="password" class="form-control" id="password" name="password" <input type="password"
autocomplete="on" v-model="password" class="form-control" id="password" name="password"
placeholder="Password" required> autocomplete="on"
</div> placeholder="Password" required>
<div class="form-group text-body" v-if="totpEnabled"> </div>
<label for="totp" class="text-left" style="font-size: 1rem"><i class="bi bi-lock-fill"></i></label> <div class="form-group text-body" v-if="totpEnabled">
<input class="form-control totp" <label for="totp" class="text-left" style="font-size: 1rem"><i class="bi bi-lock-fill"></i></label>
required <input class="form-control totp"
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code" required
placeholder="OTP from your authenticator" id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
v-model="this.totp" placeholder="OTP from your authenticator"
> v-model="this.totp"
</div> >
<button class="btn btn-lg btn-dark ms-auto mt-4 w-100 d-flex btn-brand signInBtn" ref="signInBtn"> </div>
<button class="btn btn-lg btn-dark ms-auto mt-4 w-100 d-flex btn-brand signInBtn" ref="signInBtn">
<span v-if="!this.loading" class="d-flex w-100"> <span v-if="!this.loading" class="d-flex w-100">
Sign In<i class="ms-auto bi bi-chevron-right"></i> Sign In<i class="ms-auto bi bi-chevron-right"></i>
</span> </span>
<span v-else class="d-flex w-100 align-items-center"> <span v-else class="d-flex w-100 align-items-center">
Signing In... Signing In...
<span class="spinner-border ms-auto spinner-border-sm" role="status"> <span class="spinner-border ms-auto spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</span> </span>
</span> </span>
</button> </button>
</form> </form>
<RemoteServerList v-else></RemoteServerList>
<div class="d-flex mt-3" v-if="!this.store.IsElectronApp">
<div class="form-check form-switch ms-auto">
<input
v-model="this.store.CrossServerConfiguration.Enable"
class="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckChecked">
<label class="form-check-label" for="flexSwitchCheckChecked">Access Remote Server</label>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<small class="text-muted pb-3 d-block w-100 text-center"> <small class="text-muted pb-3 d-block w-100 text-center mt-3">
WGDashboard v4.0 | Developed with by WGDashboard v4.0 | Developed with by
<a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a> <a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a>
</small> </small>
@@ -146,5 +159,13 @@ export default {
</template> </template>
<style scoped> <style scoped>
@media screen and (max-width: 768px) {
.login-box{
width: 100% !important;
}
.login-box div{
width: auto !important;
}
}
</style> </style>
+51 -20
View File
@@ -5,27 +5,58 @@ import {proxy} from "./proxy.js";
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig(({mode}) => {
base: "/static/app/dist",
plugins: [
vue(), if (mode === 'electron'){
], return {
resolve: { emptyOutDir: false,
alias: { base: './',
'@': fileURLToPath(new URL('./src', import.meta.url)) plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
build: {
outDir: '../../../../WGDashboard-Desktop',
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
}
}
} }
}, }
server:{
proxy: { return {
'/api': proxy base: "/static/app/dist",
} plugins: [
}, vue(),
build: { ],
rollupOptions: { resolve: {
output: { alias: {
entryFileNames: `assets/[name].js`, '@': fileURLToPath(new URL('./src', import.meta.url))
chunkFileNames: `assets/[name].js`, }
assetFileNames: `assets/[name].[ext]` },
server:{
proxy: {
'/api': proxy
},
host: '0.0.0.0'
},
build: {
outDir: 'dist',
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
} }
} }
} }
+3 -3
View File
@@ -1,6 +1,6 @@
/**{*/ *{
/* font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";*/ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
/*}*/ }
.dp__input{ .dp__input{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol" !important;
+8 -4
View File
@@ -1,14 +1,18 @@
[Unit] [Unit]
After=syslog.target network-online.target After=syslog.target network-online.target
Wants=wg-quick.target
ConditionPathIsDirectory=/etc/wireguard ConditionPathIsDirectory=/etc/wireguard
[Service] [Service]
Environment="VIRTUAL_ENV={{VIRTUAL_ENV}}" Type=forking
WorkingDirectory={{APP_ROOT}} PIDFile=/opt/wgdashboard/src/gunicorn.pid
ExecStart={{VIRTUAL_ENV}}/bin/python3 {{APP_ROOT}}/dashboard.py WorkingDirectory=/opt/wgdashboard/src
ExecStart=/opt/wgdashboard/src/wgd.sh start
ExecStop=/opt/wgdashboard/src/wgd.sh stop
ExecReload=/opt/wgdashboard/src/wgd.sh restart
TimeoutSec=120
PrivateTmp=yes PrivateTmp=yes
Restart=always Restart=always
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
+9 -6
View File
@@ -180,16 +180,19 @@ install_wgd(){
else else
printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n" printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n"
fi fi
version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 10) else print("0");')
if [ $version_pass == "0" ]
then
printf "[WGDashboard] WGDashboard required Python 3.10 or above\n"
exit 1
fi
_installPythonVenv _installPythonVenv
_installPythonPip _installPythonPip
version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 10) else print("0");')
if [ $version_pass == "0" ]
then
printf "[WGDashboard] WGDashboard required Python 3.7 or above\n"
exit 1
fi
if [ ! -d "db" ] if [ ! -d "db" ]
then then