mirror of
https://github.com/WGDashboard/WGDashboard.git
synced 2026-08-03 22:42:59 +00:00
Compare commits
16 Commits
| 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?
|
||||
|
||||
*.tsbuildinfo
|
||||
proxy.js
|
||||
.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.config['SEND_FILE_MAX_AGE_DEFAULT'] = 5206928
|
||||
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):
|
||||
def default(self, o: Any) -> Any:
|
||||
@@ -493,7 +497,7 @@ class WireguardConfiguration:
|
||||
if self.Name not in existingTables:
|
||||
sqldb.cursor().execute(
|
||||
"""
|
||||
CREATE TABLE %s (
|
||||
CREATE TABLE '%s'(
|
||||
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
||||
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
|
||||
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
|
||||
@@ -509,7 +513,7 @@ class WireguardConfiguration:
|
||||
if f'{self.Name}_restrict_access' not in existingTables:
|
||||
sqldb.cursor().execute(
|
||||
"""
|
||||
CREATE TABLE %s_restrict_access (
|
||||
CREATE TABLE '%s_restrict_access' (
|
||||
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
||||
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
|
||||
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
|
||||
@@ -524,7 +528,7 @@ class WireguardConfiguration:
|
||||
if f'{self.Name}_transfer' not in existingTables:
|
||||
sqldb.cursor().execute(
|
||||
"""
|
||||
CREATE TABLE %s_transfer (
|
||||
CREATE TABLE '%s_transfer' (
|
||||
id VARCHAR NOT NULL, total_receive FLOAT NULL,
|
||||
total_sent FLOAT NULL, total_data FLOAT NULL,
|
||||
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME
|
||||
@@ -535,7 +539,7 @@ class WireguardConfiguration:
|
||||
if f'{self.Name}_deleted' not in existingTables:
|
||||
sqldb.cursor().execute(
|
||||
"""
|
||||
CREATE TABLE %s_deleted (
|
||||
CREATE TABLE '%s_deleted' (
|
||||
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL,
|
||||
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
|
||||
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
|
||||
@@ -559,7 +563,7 @@ class WireguardConfiguration:
|
||||
|
||||
def __getRestrictedPeers(self):
|
||||
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:
|
||||
self.RestrictedPeers.append(Peer(i, self))
|
||||
|
||||
@@ -584,7 +588,7 @@ class WireguardConfiguration:
|
||||
p[pCounter][split[0]] = split[1]
|
||||
for i in p:
|
||||
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()
|
||||
if checkIfExist is None:
|
||||
newPeer = {
|
||||
@@ -612,7 +616,7 @@ class WireguardConfiguration:
|
||||
}
|
||||
sqldb.cursor().execute(
|
||||
"""
|
||||
INSERT INTO %s
|
||||
INSERT INTO '%s'
|
||||
VALUES (:id, :private_key, :DNS, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
|
||||
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
|
||||
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
|
||||
@@ -621,7 +625,7 @@ class WireguardConfiguration:
|
||||
sqldb.commit()
|
||||
self.Peers.append(Peer(newPeer, self))
|
||||
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'],))
|
||||
sqldb.commit()
|
||||
self.Peers.append(Peer(checkIfExist, self))
|
||||
@@ -649,11 +653,11 @@ class WireguardConfiguration:
|
||||
self.toggleConfiguration()
|
||||
|
||||
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:
|
||||
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'],))
|
||||
sqldb.cursor().execute("DELETE FROM %s_restrict_access WHERE id = ?"
|
||||
sqldb.cursor().execute("DELETE FROM '%s_restrict_access' WHERE id = ?"
|
||||
% self.Name, (p['id'],))
|
||||
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip']}",
|
||||
shell=True, stderr=subprocess.STDOUT)
|
||||
@@ -676,11 +680,11 @@ class WireguardConfiguration:
|
||||
try:
|
||||
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
||||
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,))
|
||||
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,))
|
||||
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
|
||||
except Exception as e:
|
||||
numOfFailedToRestrictPeers += 1
|
||||
@@ -707,7 +711,7 @@ class WireguardConfiguration:
|
||||
try:
|
||||
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove",
|
||||
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
|
||||
except Exception as e:
|
||||
numOfFailedToDeletePeers += 1
|
||||
@@ -727,7 +731,7 @@ class WireguardConfiguration:
|
||||
d = i.toJson()
|
||||
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,
|
||||
total_receive = :total_receive, total_sent = :total_sent, total_data = :total_data,
|
||||
endpoint = :endpoint, status = :status, latest_handshake = :latest_handshake,
|
||||
@@ -764,10 +768,10 @@ class WireguardConfiguration:
|
||||
else:
|
||||
status = "stopped"
|
||||
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],))
|
||||
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],))
|
||||
sqldb.commit()
|
||||
count += 2
|
||||
@@ -783,7 +787,7 @@ class WireguardConfiguration:
|
||||
for i in range(len(data_usage)):
|
||||
if len(data_usage[i]) == 3:
|
||||
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()
|
||||
if cur_i is not None:
|
||||
total_sent = cur_i['total_sent']
|
||||
@@ -797,7 +801,7 @@ class WireguardConfiguration:
|
||||
total_receive = cur_total_receive
|
||||
else:
|
||||
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,
|
||||
cumulative_sent + cumulative_receive,
|
||||
data_usage[i][0],))
|
||||
@@ -807,7 +811,7 @@ class WireguardConfiguration:
|
||||
_, p = self.searchPeer(data_usage[i][0])
|
||||
if p.total_receive != total_receive or p.total_sent != total_sent:
|
||||
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,
|
||||
total_receive + total_sent, data_usage[i][0],))
|
||||
except Exception as e:
|
||||
@@ -824,7 +828,7 @@ class WireguardConfiguration:
|
||||
data_usage = data_usage.decode("UTF-8").split()
|
||||
count = 0
|
||||
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],))
|
||||
sqldb.commit()
|
||||
count += 2
|
||||
@@ -962,7 +966,7 @@ class Peer:
|
||||
return ResponseObject(False,
|
||||
"Update peer failed when saving the configuration.")
|
||||
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,
|
||||
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
|
||||
keepalive, preshared_key, self.id,)
|
||||
@@ -1014,11 +1018,11 @@ PersistentKeepalive = {str(self.keepalive)}
|
||||
def resetDataUsage(self, type):
|
||||
try:
|
||||
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":
|
||||
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":
|
||||
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:
|
||||
return False
|
||||
except Exception as e:
|
||||
@@ -1092,6 +1096,7 @@ class DashboardConfig:
|
||||
self.SetConfig(section, key, value, True)
|
||||
self.__createAPIKeyTable()
|
||||
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||
self.APIAccessed = False
|
||||
|
||||
def __createAPIKeyTable(self):
|
||||
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
|
||||
|
||||
|
||||
|
||||
'''
|
||||
API Routes
|
||||
'''
|
||||
|
||||
@app.before_request
|
||||
def auth_req():
|
||||
if request.method.lower() == 'options':
|
||||
return ResponseObject(True)
|
||||
|
||||
DashboardConfig.APIAccessed = False
|
||||
if "api" in request.path:
|
||||
if str(request.method) == "GET":
|
||||
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
|
||||
DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"API Key Access: {('true' if apiKeyExist else 'false')} - Key: {apiKey}")
|
||||
if not apiKeyExist:
|
||||
DashboardConfig.APIAccessed = False
|
||||
response = Flask.make_response(app, {
|
||||
"status": False,
|
||||
"message": "API Key does not exist",
|
||||
@@ -1369,7 +1380,9 @@ def auth_req():
|
||||
response.content_type = "application/json"
|
||||
response.status_code = 401
|
||||
return response
|
||||
DashboardConfig.APIAccessed = True
|
||||
else:
|
||||
DashboardConfig.APIAccessed = False
|
||||
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 "getDashboardConfiguration" not in request.path and "getDashboardTheme" not in request.path
|
||||
@@ -1385,6 +1398,10 @@ def auth_req():
|
||||
response.status_code = 401
|
||||
return response
|
||||
|
||||
@app.route('/api/handshake', methods=["GET", "OPTIONS"])
|
||||
def API_ValidateAPIKey():
|
||||
return ResponseObject(True)
|
||||
|
||||
|
||||
@app.route('/api/validateAuthentication', methods=["GET"])
|
||||
def API_ValidateAuthentication():
|
||||
@@ -1397,6 +1414,17 @@ def API_ValidateAuthentication():
|
||||
@app.route('/api/authenticate', methods=['POST'])
|
||||
def API_AuthenticateLogin():
|
||||
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"),
|
||||
DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
|
||||
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",
|
||||
"version": "0.0.0",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build electron": "vite build --mode electron && cd ../../../../WGDashboard-Desktop && electron-builder",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -17,6 +18,7 @@
|
||||
"bootstrap-icons": "^1.11.2",
|
||||
"cidr-tools": "^7.0.4",
|
||||
"dayjs": "^1.11.12",
|
||||
"electron-builder": "^24.13.3",
|
||||
"fuse.js": "^7.0.0",
|
||||
"i": "^0.3.7",
|
||||
"is-cidr": "^5.0.3",
|
||||
|
||||
@@ -1,13 +1,37 @@
|
||||
<script setup >
|
||||
<script setup>
|
||||
import { RouterView } from 'vue-router'
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import {computed, watch} from "vue";
|
||||
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>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-dark sticky-top" data-bs-theme="dark">
|
||||
<div class="container-fluid">
|
||||
<nav class="navbar bg-dark sticky-top border-bottom border-secondary-subtle" data-bs-theme="dark">
|
||||
<div class="container-fluid d-flex text-body align-items-center">
|
||||
<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>
|
||||
</nav>
|
||||
<Suspense>
|
||||
|
||||
@@ -87,6 +87,12 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
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
|
||||
+ window.location.pathname
|
||||
+ this.$router.resolve(
|
||||
|
||||
@@ -14,7 +14,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<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" >
|
||||
<div class="sidebar-sticky pt-3">
|
||||
<ul class="nav flex-column px-2">
|
||||
@@ -75,5 +75,10 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.navbar-container{
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
<div class="form-check form-switch ms-3">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
v-model="this.status"
|
||||
role="switch" id="allowAPIKeysSwitch">
|
||||
role="switch" id="allowMFAKeysSwitch">
|
||||
</div>
|
||||
<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()">
|
||||
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
<div class="card mb-4 shadow rounded-3">
|
||||
<div class="card-header d-flex">
|
||||
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"
|
||||
v-model="this.value"
|
||||
@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">
|
||||
<button class="ms-auto btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm"
|
||||
@click="this.newDashboardAPIKey = true"
|
||||
v-if="!this.store.getActiveCrossServer()"
|
||||
>
|
||||
<i class="bi bi-key me-2"></i> Create
|
||||
</button>
|
||||
|
||||
+29
-6
@@ -35,26 +35,31 @@ export default {
|
||||
|
||||
<template>
|
||||
<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">
|
||||
<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 class="d-flex align-items-center gap-2 ms-auto">
|
||||
<small class="text-muted">Expire At</small>
|
||||
{{this.apiKey.ExpiredAt ? this.apiKey.ExpiredAt : 'Never'}}
|
||||
</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>
|
||||
</a>
|
||||
</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?
|
||||
<a role="button" class="btn btn-sm bg-success-subtle text-success-emphasis rounded-3"
|
||||
@click="this.deleteAPIKey()"
|
||||
>
|
||||
<i class="bi bi-check-lg"></i>
|
||||
</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>
|
||||
</a>
|
||||
</div>
|
||||
@@ -62,5 +67,23 @@ export default {
|
||||
</template>
|
||||
|
||||
<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>
|
||||
@@ -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,19 +138,28 @@ router.beforeEach(async (to, from, next) => {
|
||||
}
|
||||
|
||||
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()
|
||||
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 {
|
||||
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,9 +10,53 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
|
||||
Peers: {
|
||||
Selecting: false,
|
||||
RefreshInterval: undefined
|
||||
}
|
||||
},
|
||||
CrossServerConfiguration:{
|
||||
Enable: false,
|
||||
ServerList: {}
|
||||
},
|
||||
ActiveServerConfiguration: undefined,
|
||||
IsElectronApp: false
|
||||
}),
|
||||
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(){
|
||||
await fetchGet("/api/getDashboardConfiguration", {}, (res) => {
|
||||
if (res.status) this.Configuration = res.data
|
||||
@@ -27,6 +71,7 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
|
||||
},
|
||||
async signOut(){
|
||||
await fetchGet("/api/signout", {}, (res) => {
|
||||
this.removeActiveCrossServer();
|
||||
this.$router.go('/signin')
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
import router from "@/router/index.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) => {
|
||||
const urlSearchParams = new URLSearchParams(params);
|
||||
await fetch(`${url}?${urlSearchParams.toString()}`, {
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
await fetch(`${getUrl(url)}?${urlSearchParams.toString()}`, {
|
||||
headers: getHeaders()
|
||||
})
|
||||
.then((x) => {
|
||||
const store = DashboardConfigurationStore();
|
||||
if (!x.ok){
|
||||
if (x.status !== 200){
|
||||
if (x.status === 401){
|
||||
router.push({path: '/signin'})
|
||||
|
||||
store.newMessage("WGDashboard", "Session Ended", "warning")
|
||||
}
|
||||
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 => {
|
||||
console.log(x)
|
||||
router.push({path: '/signin'})
|
||||
})
|
||||
}
|
||||
|
||||
export const fetchPost = async (url, body, callback) => {
|
||||
await fetch(`${url}`, {
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
await fetch(`${getUrl(url)}`, {
|
||||
headers: getHeaders(),
|
||||
method: "POST",
|
||||
body: JSON.stringify(body)
|
||||
}).then((x) => {
|
||||
@@ -37,7 +56,7 @@ export const fetchPost = async (url, body, callback) => {
|
||||
if (!x.ok){
|
||||
if (x.status !== 200){
|
||||
if (x.status === 401){
|
||||
router.push({path: '/signin'})
|
||||
|
||||
store.newMessage("WGDashboard", "Session Ended", "warning")
|
||||
}
|
||||
throw new Error(x.statusText)
|
||||
@@ -47,5 +66,6 @@ export const fetchPost = async (url, body, callback) => {
|
||||
}
|
||||
}).then(x => callback ? callback(x) : undefined).catch(x => {
|
||||
console.log(x)
|
||||
router.push({path: '/signin'})
|
||||
})
|
||||
}
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
this.errorMessage = res.message;
|
||||
document.querySelector(`#${res.data}`).classList.remove("is-valid")
|
||||
document.querySelector(`#${res.data}`).classList.add("is-invalid")
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -126,15 +126,14 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<div class="mt-5">
|
||||
<div class="container mb-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">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
<i class="bi bi-chevron-left me-4"></i> New Configuration
|
||||
</h3>
|
||||
</RouterLink>
|
||||
<h3 class="text-body mb-0">New Configuration</h3>
|
||||
</div>
|
||||
|
||||
<form class="text-body d-flex flex-column gap-3"
|
||||
|
||||
@@ -77,8 +77,8 @@ export default {
|
||||
<AccountSettingsInputPassword
|
||||
targetData="password">
|
||||
</AccountSettingsInputPassword>
|
||||
<hr class="m-0">
|
||||
<AccountSettingsMFA></AccountSettingsMFA>
|
||||
<hr class="m-0" v-if="!this.dashboardConfigurationStore.getActiveCrossServer()">
|
||||
<AccountSettingsMFA v-if="!this.dashboardConfigurationStore.getActiveCrossServer()"></AccountSettingsMFA>
|
||||
</div>
|
||||
</div>
|
||||
<DashboardAPIKeys></DashboardAPIKeys>
|
||||
|
||||
@@ -2,20 +2,24 @@
|
||||
import {fetchGet, fetchPost} from "../utilities/fetch.js";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import Message from "@/components/messageCentreComponent/message.vue";
|
||||
import RemoteServerList from "@/components/signInComponents/RemoteServerList.vue";
|
||||
|
||||
export default {
|
||||
name: "signin",
|
||||
components: {Message},
|
||||
components: {RemoteServerList, Message},
|
||||
async setup(){
|
||||
const store = DashboardConfigurationStore()
|
||||
let theme = ""
|
||||
let theme = "dark"
|
||||
let totpEnabled = false;
|
||||
await fetchGet("/api/getDashboardTheme", {}, (res) => {
|
||||
theme = res.data
|
||||
});
|
||||
await fetchGet("/api/isTotpEnabled", {}, (res) => {
|
||||
totpEnabled = res.data
|
||||
});
|
||||
if (!store.IsElectronApp){
|
||||
await fetchGet("/api/getDashboardTheme", {}, (res) => {
|
||||
theme = res.data
|
||||
});
|
||||
await fetchGet("/api/isTotpEnabled", {}, (res) => {
|
||||
totpEnabled = res.data
|
||||
});
|
||||
}
|
||||
store.removeActiveCrossServer();
|
||||
return {store, theme, totpEnabled}
|
||||
},
|
||||
data(){
|
||||
@@ -82,57 +86,66 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-fluid login-container-fluid d-flex main flex-column" :data-bs-theme="this.theme">
|
||||
<div class="login-box m-auto" style="width: 600px;">
|
||||
<div class="m-auto">
|
||||
<div class="card px-4 py-5 rounded-4 shadow-lg">
|
||||
<div class="card-body">
|
||||
<h4 class="mb-0 text-body">Welcome to</h4>
|
||||
<span class="dashboardLogo display-3"><strong>WGDashboard</strong></span>
|
||||
<div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError">
|
||||
{{this.loginErrorMessage}}
|
||||
</div>
|
||||
<form @submit="(e) => {e.preventDefault(); this.auth();}">
|
||||
<div class="form-group text-body">
|
||||
<label for="username" class="text-left" style="font-size: 1rem">
|
||||
<i class="bi bi-person-circle"></i></label>
|
||||
<input type="text" v-model="username" class="form-control" id="username" name="username"
|
||||
autocomplete="on"
|
||||
placeholder="Username" required>
|
||||
</div>
|
||||
<div class="form-group text-body">
|
||||
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
|
||||
<input type="password"
|
||||
v-model="password" class="form-control" id="password" name="password"
|
||||
autocomplete="on"
|
||||
placeholder="Password" required>
|
||||
</div>
|
||||
<div class="form-group text-body" v-if="totpEnabled">
|
||||
<label for="totp" class="text-left" style="font-size: 1rem"><i class="bi bi-lock-fill"></i></label>
|
||||
<input class="form-control totp"
|
||||
required
|
||||
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
|
||||
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 class="container-fluid login-container-fluid d-flex main flex-column py-4 text-body"
|
||||
style="overflow-y: scroll"
|
||||
:data-bs-theme="this.theme">
|
||||
<div class="login-box m-auto" >
|
||||
<div class="m-auto" style="width: 700px;">
|
||||
<h4 class="mb-0 text-body">Welcome to</h4>
|
||||
<span class="dashboardLogo display-3"><strong>WGDashboard</strong></span>
|
||||
<div class="alert alert-danger mt-2 mb-0" role="alert" v-if="loginError">
|
||||
{{this.loginErrorMessage}}
|
||||
</div>
|
||||
<form @submit="(e) => {e.preventDefault(); this.auth();}"
|
||||
v-if="!this.store.CrossServerConfiguration.Enable">
|
||||
<div class="form-group text-body">
|
||||
<label for="username" class="text-left" style="font-size: 1rem">
|
||||
<i class="bi bi-person-circle"></i></label>
|
||||
<input type="text" v-model="username" class="form-control" id="username" name="username"
|
||||
autocomplete="on"
|
||||
placeholder="Username" required>
|
||||
</div>
|
||||
<div class="form-group text-body">
|
||||
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
|
||||
<input type="password"
|
||||
v-model="password" class="form-control" id="password" name="password"
|
||||
autocomplete="on"
|
||||
placeholder="Password" required>
|
||||
</div>
|
||||
<div class="form-group text-body" v-if="totpEnabled">
|
||||
<label for="totp" class="text-left" style="font-size: 1rem"><i class="bi bi-lock-fill"></i></label>
|
||||
<input class="form-control totp"
|
||||
required
|
||||
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
|
||||
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">
|
||||
<span v-if="!this.loading" class="d-flex w-100">
|
||||
Sign In<i class="ms-auto bi bi-chevron-right"></i>
|
||||
</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...
|
||||
<span class="spinner-border ms-auto spinner-border-sm" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</button>
|
||||
</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>
|
||||
<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
|
||||
<a href="https://github.com/donaldzou" target="_blank"><strong>Donald Zou</strong></a>
|
||||
</small>
|
||||
@@ -146,5 +159,13 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@media screen and (max-width: 768px) {
|
||||
.login-box{
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.login-box div{
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,27 +5,58 @@ import {proxy} from "./proxy.js";
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: "/static/app/dist",
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
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]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
server:{
|
||||
proxy: {
|
||||
'/api': proxy
|
||||
}
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
|
||||
return {
|
||||
base: "/static/app/dist",
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
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]`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
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]
|
||||
After=syslog.target network-online.target
|
||||
Wants=wg-quick.target
|
||||
ConditionPathIsDirectory=/etc/wireguard
|
||||
|
||||
[Service]
|
||||
Environment="VIRTUAL_ENV={{VIRTUAL_ENV}}"
|
||||
WorkingDirectory={{APP_ROOT}}
|
||||
ExecStart={{VIRTUAL_ENV}}/bin/python3 {{APP_ROOT}}/dashboard.py
|
||||
Type=forking
|
||||
PIDFile=/opt/wgdashboard/src/gunicorn.pid
|
||||
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
|
||||
Restart=always
|
||||
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+9
-6
@@ -180,16 +180,19 @@ install_wgd(){
|
||||
else
|
||||
printf "[WGDashboard] \xE2\x9C\x94 Python is installed\n"
|
||||
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
|
||||
_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" ]
|
||||
then
|
||||
|
||||
Reference in New Issue
Block a user