mirror of
https://github.com/WGDashboard/WGDashboard.git
synced 2026-08-04 23:13:02 +00:00
Compare commits
16 Commits
v4.0.beta2
...
v4.0.beta3
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a34a0eb40 | |||
| e3f82e136a | |||
| 8a7df4ba9f | |||
| e86d1a4c7a | |||
| 5b9d0b60a1 | |||
| 7eff2f0c49 | |||
| 97236bb01d | |||
| 96ccb03eea | |||
| 55f55820c5 | |||
| 955839d513 | |||
| a650e628e5 | |||
| 54142b73fb | |||
| 55e0d2695d | |||
| 2f90ab15dc | |||
| fd3fc66bfc | |||
| a352a94d8a |
@@ -48,5 +48,4 @@ coverage
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
proxy.js
|
|
||||||
.vite/*
|
.vite/*
|
||||||
@@ -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
@@ -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]
|
||||||
|
|||||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+28
-28
File diff suppressed because one or more lines are too long
Generated
+2286
-2
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+28
-5
@@ -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>
|
||||||
@@ -138,6 +138,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (to.meta.requiresAuth){
|
if (to.meta.requiresAuth){
|
||||||
|
if (!dashboardConfigurationStore.getActiveCrossServer()){
|
||||||
if (cookie.getCookie("authToken") && await checkAuth()){
|
if (cookie.getCookie("authToken") && await checkAuth()){
|
||||||
await dashboardConfigurationStore.getConfiguration()
|
await dashboardConfigurationStore.getConfiguration()
|
||||||
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
|
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
|
||||||
@@ -150,7 +151,15 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
next("/signin")
|
next("/signin")
|
||||||
dashboardConfigurationStore.newMessage("WGDashboard", "Session Ended", "warning")
|
dashboardConfigurationStore.newMessage("WGDashboard", "Session Ended", "warning")
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
await dashboardConfigurationStore.getConfiguration()
|
||||||
|
if (!wireguardConfigurationsStore.Configurations && to.name !== "Configuration List"){
|
||||||
|
await wireguardConfigurationsStore.getConfigurations();
|
||||||
|
}
|
||||||
|
next()
|
||||||
|
}
|
||||||
}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')
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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";
|
||||||
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
|
||||||
const urlSearchParams = new URLSearchParams(params);
|
const getHeaders = () => {
|
||||||
await fetch(`${url}?${urlSearchParams.toString()}`, {
|
let headers = {
|
||||||
headers: {
|
|
||||||
"content-type": "application/json"
|
"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) => {
|
||||||
|
const urlSearchParams = new URLSearchParams(params);
|
||||||
|
await fetch(`${getUrl(url)}?${urlSearchParams.toString()}`, {
|
||||||
|
headers: getHeaders()
|
||||||
})
|
})
|
||||||
.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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
if (!store.IsElectronApp){
|
||||||
await fetchGet("/api/getDashboardTheme", {}, (res) => {
|
await fetchGet("/api/getDashboardTheme", {}, (res) => {
|
||||||
theme = res.data
|
theme = res.data
|
||||||
});
|
});
|
||||||
await fetchGet("/api/isTotpEnabled", {}, (res) => {
|
await fetchGet("/api/isTotpEnabled", {}, (res) => {
|
||||||
totpEnabled = res.data
|
totpEnabled = res.data
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
store.removeActiveCrossServer();
|
||||||
return {store, theme, totpEnabled}
|
return {store, theme, totpEnabled}
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
@@ -82,17 +86,18 @@ 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();}"
|
||||||
|
v-if="!this.store.CrossServerConfiguration.Enable">
|
||||||
<div class="form-group text-body">
|
<div class="form-group text-body">
|
||||||
<label for="username" class="text-left" style="font-size: 1rem">
|
<label for="username" class="text-left" style="font-size: 1rem">
|
||||||
<i class="bi bi-person-circle"></i></label>
|
<i class="bi bi-person-circle"></i></label>
|
||||||
@@ -128,11 +133,19 @@ export default {
|
|||||||
</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>
|
||||||
@@ -5,7 +5,35 @@ 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}) => {
|
||||||
|
|
||||||
|
|
||||||
|
if (mode === 'electron'){
|
||||||
|
return {
|
||||||
|
emptyOutDir: false,
|
||||||
|
base: './',
|
||||||
|
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]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
base: "/static/app/dist",
|
base: "/static/app/dist",
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
@@ -18,9 +46,11 @@ export default defineConfig({
|
|||||||
server:{
|
server:{
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': proxy
|
'/api': proxy
|
||||||
}
|
},
|
||||||
|
host: '0.0.0.0'
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
entryFileNames: `assets/[name].js`,
|
entryFileNames: `assets/[name].js`,
|
||||||
@@ -29,4 +59,5 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
+7
-4
@@ -180,17 +180,20 @@ install_wgd(){
|
|||||||
else
|
else
|
||||||
printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n"
|
printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n"
|
||||||
fi
|
fi
|
||||||
_installPythonVenv
|
|
||||||
_installPythonPip
|
|
||||||
|
|
||||||
|
|
||||||
version_pass=$(python3 -c 'import sys; print("1") if (sys.version_info.major == 3 and sys.version_info.minor >= 10) else print("0");')
|
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" ]
|
if [ $version_pass == "0" ]
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] WGDashboard required Python 3.7 or above\n"
|
printf "[WGDashboard] WGDashboard required Python 3.10 or above\n"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
_installPythonVenv
|
||||||
|
_installPythonPip
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if [ ! -d "db" ]
|
if [ ! -d "db" ]
|
||||||
then
|
then
|
||||||
printf "[WGDashboard] Creating ./db folder\n"
|
printf "[WGDashboard] Creating ./db folder\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user