1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-21 23:01:39 +01:00

Merge branch 'v4.1-dev' into v4.0.3-fix

This commit is contained in:
Donald Zou 2024-09-14 16:23:08 +08:00 committed by GitHub
commit c01201b88e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 896 additions and 364 deletions

View File

@ -33,7 +33,8 @@ import threading
from flask.json.provider import DefaultJSONProvider from flask.json.provider import DefaultJSONProvider
DASHBOARD_VERSION = 'v4.0.3'
DASHBOARD_VERSION = 'v4.1'
CONFIGURATION_PATH = os.getenv('CONFIGURATION_PATH', '.') CONFIGURATION_PATH = os.getenv('CONFIGURATION_PATH', '.')
DB_PATH = os.path.join(CONFIGURATION_PATH, 'db') DB_PATH = os.path.join(CONFIGURATION_PATH, 'db')
if not os.path.isdir(DB_PATH): if not os.path.isdir(DB_PATH):
@ -398,9 +399,7 @@ class PeerShareLinks:
) )
""" """
) )
# sqldb.commit()
self.__getSharedLinks() self.__getSharedLinks()
# print(self.Links)
def __getSharedLinks(self): def __getSharedLinks(self):
self.Links.clear() self.Links.clear()
allLinks = sqlSelect("SELECT * FROM PeerShareLinks WHERE ExpireDate IS NULL OR ExpireDate > datetime('now', 'localtime')").fetchall() allLinks = sqlSelect("SELECT * FROM PeerShareLinks WHERE ExpireDate IS NULL OR ExpireDate > datetime('now', 'localtime')").fetchall()
@ -421,7 +420,6 @@ class PeerShareLinks:
if len(self.getLink(Configuration, Peer)) > 0: if len(self.getLink(Configuration, Peer)) > 0:
sqlUpdate("UPDATE PeerShareLinks SET ExpireDate = datetime('now', 'localtime') WHERE Configuration = ? AND Peer = ?", (Configuration, Peer, )) sqlUpdate("UPDATE PeerShareLinks SET ExpireDate = datetime('now', 'localtime') WHERE Configuration = ? AND Peer = ?", (Configuration, Peer, ))
sqlUpdate("INSERT INTO PeerShareLinks (ShareID, Configuration, Peer, ExpireDate) VALUES (?, ?, ?, ?)", (newShareID, Configuration, Peer, ExpireDate, )) sqlUpdate("INSERT INTO PeerShareLinks (ShareID, Configuration, Peer, ExpireDate) VALUES (?, ?, ?, ?)", (newShareID, Configuration, Peer, ExpireDate, ))
# sqldb.commit()
self.__getSharedLinks() self.__getSharedLinks()
except Exception as e: except Exception as e:
return False, str(e) return False, str(e)
@ -429,7 +427,6 @@ class PeerShareLinks:
def updateLinkExpireDate(self, ShareID, ExpireDate: datetime = None) -> tuple[bool, str]: def updateLinkExpireDate(self, ShareID, ExpireDate: datetime = None) -> tuple[bool, str]:
sqlUpdate("UPDATE PeerShareLinks SET ExpireDate = ? WHERE ShareID = ?;", (ExpireDate, ShareID, )) sqlUpdate("UPDATE PeerShareLinks SET ExpireDate = ? WHERE ShareID = ?;", (ExpireDate, ShareID, ))
# sqldb.commit()
self.__getSharedLinks() self.__getSharedLinks()
return True, "" return True, ""
@ -465,11 +462,11 @@ class WireguardConfiguration:
if name is not None: if name is not None:
self.Name = name self.Name = name
self.__parser.read_file(open(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'))) self.__parser.read_file(open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')))
sections = self.__parser.sections() sections = self.__parser.sections()
if "Interface" not in sections: if "Interface" not in sections:
raise self.InvalidConfigurationFileException( raise self.InvalidConfigurationFileException(
"[Interface] section not found in " + os.path.join(WG_CONF_PATH, f'{self.Name}.conf')) "[Interface] section not found in " + os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'))
interfaceConfig = dict(self.__parser.items("Interface", True)) interfaceConfig = dict(self.__parser.items("Interface", True))
for i in dir(self): for i in dir(self):
if str(i) in interfaceConfig.keys(): if str(i) in interfaceConfig.keys():
@ -506,7 +503,6 @@ class WireguardConfiguration:
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
f"{self.Name}.conf"), "w+") as configFile: f"{self.Name}.conf"), "w+") as configFile:
# print(self.__parser.sections())
self.__parser.write(configFile) self.__parser.write(configFile)
self.Peers: list[Peer] = [] self.Peers: list[Peer] = []
@ -532,7 +528,6 @@ class WireguardConfiguration:
) )
""" % self.Name """ % self.Name
) )
# sqldb.commit()
if f'{self.Name}_restrict_access' not in existingTables: if f'{self.Name}_restrict_access' not in existingTables:
sqlUpdate( sqlUpdate(
@ -548,7 +543,6 @@ class WireguardConfiguration:
) )
""" % self.Name """ % self.Name
) )
# sqldb.commit()
if f'{self.Name}_transfer' not in existingTables: if f'{self.Name}_transfer' not in existingTables:
sqlUpdate( sqlUpdate(
""" """
@ -559,7 +553,6 @@ class WireguardConfiguration:
) )
""" % self.Name """ % self.Name
) )
# sqldb.commit()
if f'{self.Name}_deleted' not in existingTables: if f'{self.Name}_deleted' not in existingTables:
sqlUpdate( sqlUpdate(
""" """
@ -574,9 +567,6 @@ class WireguardConfiguration:
) )
""" % self.Name """ % self.Name
) )
# sqldb.commit()
def __getPublicKey(self) -> str: def __getPublicKey(self) -> str:
return _generatePublicKey(self.PrivateKey)[1] return _generatePublicKey(self.PrivateKey)[1]
@ -592,7 +582,7 @@ class WireguardConfiguration:
self.RestrictedPeers.append(Peer(i, self)) self.RestrictedPeers.append(Peer(i, self))
def configurationFileChanged(self) : def configurationFileChanged(self) :
mt = os.path.getmtime(os.path.join(WG_CONF_PATH, f'{self.Name}.conf')) mt = os.path.getmtime(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'))
changed = self.__configFileModifiedTime is None or self.__configFileModifiedTime != mt changed = self.__configFileModifiedTime is None or self.__configFileModifiedTime != mt
self.__configFileModifiedTime = mt self.__configFileModifiedTime = mt
return changed return changed
@ -601,7 +591,7 @@ class WireguardConfiguration:
if self.configurationFileChanged(): if self.configurationFileChanged():
self.Peers = [] self.Peers = []
with open(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'), 'r') as configFile: with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'r') as configFile:
p = [] p = []
pCounter = -1 pCounter = -1
content = configFile.read().split('\n') content = configFile.read().split('\n')
@ -622,7 +612,6 @@ class WireguardConfiguration:
if regex_match("#Name# = (.*)", i): if regex_match("#Name# = (.*)", i):
split = re.split(r'\s*=\s*', i, 1) split = re.split(r'\s*=\s*', i, 1)
print(split)
if len(split) == 2: if len(split) == 2:
p[pCounter]["name"] = split[1] p[pCounter]["name"] = split[1]
@ -662,15 +651,14 @@ class WireguardConfiguration:
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key); :cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name """ % self.Name
, newPeer) , newPeer)
# sqldb.commit()
self.Peers.append(Peer(newPeer, self)) self.Peers.append(Peer(newPeer, self))
else: else:
sqlUpdate("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name, sqlUpdate("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()
self.Peers.append(Peer(checkIfExist, self)) self.Peers.append(Peer(checkIfExist, self))
except Exception as e: except Exception as e:
print(f"[WGDashboard] {self.Name} Error: {str(e)}") if __name__ == '__main__':
print(f"[WGDashboard] {self.Name} Error: {str(e)}")
else: else:
self.Peers.clear() self.Peers.clear()
checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall() checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall()
@ -731,7 +719,6 @@ class WireguardConfiguration:
sqlUpdate("UPDATE '%s_restrict_access' SET status = 'stopped' WHERE id = ?" % sqlUpdate("UPDATE '%s_restrict_access' SET status = 'stopped' WHERE id = ?" %
(self.Name,), (pf.id,)) (self.Name,), (pf.id,))
sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,)) sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,))
# sqldb.commit()
numOfRestrictedPeers += 1 numOfRestrictedPeers += 1
except Exception as e: except Exception as e:
numOfFailedToRestrictPeers += 1 numOfFailedToRestrictPeers += 1
@ -776,7 +763,7 @@ class WireguardConfiguration:
def __savePeers(self): def __savePeers(self):
for i in self.Peers: for i in self.Peers:
d = i.toJson() d = i.toJson()
sqldb.execute( sqlUpdate(
''' '''
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,
@ -787,7 +774,6 @@ class WireguardConfiguration:
remote_endpoint = :remote_endpoint, preshared_key = :preshared_key WHERE id = :id remote_endpoint = :remote_endpoint, preshared_key = :preshared_key WHERE id = :id
''' % self.Name, d ''' % self.Name, d
) )
sqldb.commit()
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]: def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:
try: try:
@ -853,7 +839,6 @@ class WireguardConfiguration:
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],))
sqldb.commit()
total_sent = 0 total_sent = 0
total_receive = 0 total_receive = 0
_, p = self.searchPeer(data_usage[i][0]) _, p = self.searchPeer(data_usage[i][0])
@ -862,7 +847,6 @@ class WireguardConfiguration:
"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],))
sqldb.commit()
except Exception as e: except Exception as e:
print(f"[WGDashboard] {self.Name} Error: {str(e)} {str(e.__traceback__)}") print(f"[WGDashboard] {self.Name} Error: {str(e)} {str(e.__traceback__)}")
@ -877,9 +861,8 @@ 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 sqlUpdate("UPDATE '%s' SET endpoint = ? WHERE id = ?" % self.Name
, (data_usage[count + 1], data_usage[count],)) , (data_usage[count + 1], data_usage[count],))
# sqldb.commit()
count += 2 count += 2
def toggleConfiguration(self) -> [bool, str]: def toggleConfiguration(self) -> [bool, str]:
@ -1020,7 +1003,6 @@ class Peer:
(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,)
) )
sqldb.commit()
return ResponseObject() return ResponseObject()
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return ResponseObject(False, exc.output.decode("UTF-8").strip()) return ResponseObject(False, exc.output.decode("UTF-8").strip())
@ -1123,7 +1105,8 @@ class DashboardConfig:
"dashboard_refresh_interval": "60000", "dashboard_refresh_interval": "60000",
"dashboard_sort": "status", "dashboard_sort": "status",
"dashboard_theme": "dark", "dashboard_theme": "dark",
"dashboard_api_key": "false" "dashboard_api_key": "false",
"dashboard_language": "en"
}, },
"Peers": { "Peers": {
"peer_global_DNS": "1.1.1.1", "peer_global_DNS": "1.1.1.1",
@ -1156,7 +1139,6 @@ class DashboardConfig:
existingTable = sqlSelect("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall() existingTable = sqlSelect("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall()
if len(existingTable) == 0: if len(existingTable) == 0:
sqlUpdate("CREATE TABLE DashboardAPIKeys (Key VARCHAR NOT NULL PRIMARY KEY, CreatedAt DATETIME NOT NULL DEFAULT (datetime('now', 'localtime')), ExpiredAt VARCHAR)") sqlUpdate("CREATE TABLE DashboardAPIKeys (Key VARCHAR NOT NULL PRIMARY KEY, CreatedAt DATETIME NOT NULL DEFAULT (datetime('now', 'localtime')), ExpiredAt VARCHAR)")
# sqldb.commit()
def __getAPIKeys(self) -> list[DashboardAPIKey]: def __getAPIKeys(self) -> list[DashboardAPIKey]:
keys = sqlSelect("SELECT * FROM DashboardAPIKeys WHERE ExpiredAt IS NULL OR ExpiredAt > datetime('now', 'localtime') ORDER BY CreatedAt DESC").fetchall() keys = sqlSelect("SELECT * FROM DashboardAPIKeys WHERE ExpiredAt IS NULL OR ExpiredAt > datetime('now', 'localtime') ORDER BY CreatedAt DESC").fetchall()
@ -1168,12 +1150,11 @@ class DashboardConfig:
def createAPIKeys(self, ExpiredAt = None): def createAPIKeys(self, ExpiredAt = None):
newKey = secrets.token_urlsafe(32) newKey = secrets.token_urlsafe(32)
sqlUpdate('INSERT INTO DashboardAPIKeys (Key, ExpiredAt) VALUES (?, ?)', (newKey, ExpiredAt,)) sqlUpdate('INSERT INTO DashboardAPIKeys (Key, ExpiredAt) VALUES (?, ?)', (newKey, ExpiredAt,))
# sqldb.commit()
self.DashboardAPIKeys = self.__getAPIKeys() self.DashboardAPIKeys = self.__getAPIKeys()
def deleteAPIKey(self, key): def deleteAPIKey(self, key):
sqlUpdate("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, )) sqlUpdate("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
# sqldb.commit()
self.DashboardAPIKeys = self.__getAPIKeys() self.DashboardAPIKeys = self.__getAPIKeys()
@ -1228,6 +1209,10 @@ class DashboardConfig:
else: else:
value = self.generatePassword(value).decode("utf-8") value = self.generatePassword(value).decode("utf-8")
if section == "Server" and key == "wg_conf_path":
if not os.path.exists(value):
return False, "Path does not exist"
if section not in self.__config: if section not in self.__config:
self.__config[section] = {} self.__config[section] = {}
@ -1250,7 +1235,7 @@ class DashboardConfig:
except Exception as e: except Exception as e:
return False return False
def GetConfig(self, section, key) -> [any, bool]: def GetConfig(self, section, key) -> [bool, any]:
if section not in self.__config: if section not in self.__config:
return False, None return False, None
@ -1296,8 +1281,7 @@ def _regexMatch(regex, text):
def _getConfigurationList(): def _getConfigurationList():
# configurations = {} for i in os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]):
for i in os.listdir(WG_CONF_PATH):
if _regexMatch("^(.{1,}).(conf)$", i): if _regexMatch("^(.{1,}).(conf)$", i):
i = i.replace('.conf', '') i = i.replace('.conf', '')
try: try:
@ -1382,7 +1366,10 @@ def _getWireguardConfigurationAvailableIP(configName: str, all: bool = False) ->
add = p.allowed_ip.split(',') add = p.allowed_ip.split(',')
for i in add: for i in add:
a, c = i.split('/') a, c = i.split('/')
existedAddress.append(ipaddress.ip_address(a.replace(" ", ""))) try:
existedAddress.append(ipaddress.ip_address(a.replace(" ", "")))
except ValueError as error:
print(f"[WGDashboard] Error: {configName} peer {p.id} have invalid ip")
for p in configuration.getRestrictedPeersList(): for p in configuration.getRestrictedPeersList():
if len(p.allowed_ip) > 0: if len(p.allowed_ip) > 0:
@ -1415,14 +1402,20 @@ cursor = sqldb.cursor()
def sqlSelect(statement: str, paramters: tuple = ()) -> sqlite3.Cursor: def sqlSelect(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
with sqldb: with sqldb:
cursor = sqldb.cursor() try:
return cursor.execute(statement, paramters) cursor = sqldb.cursor()
return cursor.execute(statement, paramters)
except sqlite3.OperationalError as error:
print("[WGDashboard] SQLite Error:" + str(error) + " | Statement: " + statement)
def sqlUpdate(statement: str, paramters: tuple = ()) -> sqlite3.Cursor: def sqlUpdate(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
with sqldb: with sqldb:
cursor = sqldb.cursor() cursor = sqldb.cursor()
cursor.execute(statement, paramters) try:
sqldb.commit() cursor.execute(statement, paramters)
sqldb.commit()
except sqlite3.OperationalError as error:
print("[WGDashboard] SQLite Error:" + str(error))
DashboardConfig = DashboardConfig() DashboardConfig = DashboardConfig()
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix") _, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
@ -1481,6 +1474,7 @@ def auth_req():
and "getDashboardVersion" not in request.path and "getDashboardVersion" not in request.path
and "sharePeer/get" not in request.path and "sharePeer/get" not in request.path
and "isTotpEnabled" not in request.path and "isTotpEnabled" not in request.path
and "locale" not in request.path
): ):
response = Flask.make_response(app, { response = Flask.make_response(app, {
"status": False, "status": False,
@ -1614,14 +1608,7 @@ def API_getDashboardConfiguration():
return ResponseObject(data=DashboardConfig.toJson()) return ResponseObject(data=DashboardConfig.toJson())
# @app.route(f'{APP_PREFIX}/api/updateDashboardConfiguration', methods=["POST"])
# def API_updateDashboardConfiguration():
# data = request.get_json()
# for section in data['DashboardConfiguration'].keys():
# for key in data['DashboardConfiguration'][section].keys():
# if not DashboardConfig.SetConfig(section, key, data['DashboardConfiguration'][section][key])[0]:
# return ResponseObject(False, "Section or value is invalid.")
# return ResponseObject()
@app.route(f'{APP_PREFIX}/api/updateDashboardConfigurationItem', methods=["POST"]) @app.route(f'{APP_PREFIX}/api/updateDashboardConfigurationItem', methods=["POST"])
@ -1629,13 +1616,16 @@ def API_updateDashboardConfigurationItem():
data = request.get_json() data = request.get_json()
if "section" not in data.keys() or "key" not in data.keys() or "value" not in data.keys(): if "section" not in data.keys() or "key" not in data.keys() or "value" not in data.keys():
return ResponseObject(False, "Invalid request.") return ResponseObject(False, "Invalid request.")
valid, msg = DashboardConfig.SetConfig( valid, msg = DashboardConfig.SetConfig(
data["section"], data["key"], data['value']) data["section"], data["key"], data['value'])
if not valid: if not valid:
return ResponseObject(False, msg) return ResponseObject(False, msg)
if data['section'] == "Server":
if data['key'] == 'wg_conf_path':
WireguardConfigurations.clear()
_getConfigurationList()
return ResponseObject() return ResponseObject()
@app.route(f'{APP_PREFIX}/api/getDashboardAPIKeys', methods=['GET']) @app.route(f'{APP_PREFIX}/api/getDashboardAPIKeys', methods=['GET'])
@ -1804,6 +1794,7 @@ def API_addPeers(configName):
mtu = data['mtu'] mtu = data['mtu']
keep_alive = data['keepalive'] keep_alive = data['keepalive']
preshared_key = data['preshared_key'] preshared_key = data['preshared_key']
preshared_key_bulkAdd: bool = data['preshared_key_bulkAdd']
if configName in WireguardConfigurations.keys(): if configName in WireguardConfigurations.keys():
config = WireguardConfigurations.get(configName) config = WireguardConfigurations.get(configName)
@ -1830,7 +1821,7 @@ def API_addPeers(configName):
keyPairs.append({ keyPairs.append({
"private_key": newPrivateKey, "private_key": newPrivateKey,
"id": _generatePublicKey(newPrivateKey)[1], "id": _generatePublicKey(newPrivateKey)[1],
"preshared_key": _generatePrivateKey()[1], "preshared_key": (_generatePrivateKey()[1] if preshared_key_bulkAdd else ""),
"allowed_ip": availableIps[1][i], "allowed_ip": availableIps[1][i],
"name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" "name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
}) })
@ -2132,15 +2123,30 @@ def API_Welcome_Finish():
"repeatNewPassword": data["repeatNewPassword"], "repeatNewPassword": data["repeatNewPassword"],
"currentPassword": "admin" "currentPassword": "admin"
}) })
# updateEnableTotp, updateEnableTotpErr = DashboardConfig.SetConfig("Account", "enable_totp", data["enable_totp"])
if not updateUsername or not updatePassword: if not updateUsername or not updatePassword:
return ResponseObject(False, f"{updateUsernameErr},{updatePasswordErr}".strip(",")) return ResponseObject(False, f"{updateUsernameErr},{updatePasswordErr}".strip(","))
DashboardConfig.SetConfig("Other", "welcome_session", False) DashboardConfig.SetConfig("Other", "welcome_session", False)
return ResponseObject() return ResponseObject()
@app.get(f'{APP_PREFIX}/api/locale')
def API_Local_CurrentLang():
# if param is None or len(param) == 0:
# with open(os.path.join("./static/locale/active_languages.json"), "r") as f:
# return ResponseObject(data=''.join(f.readlines()))
_, param = DashboardConfig.GetConfig("Server", "dashboard_language")
if param == "en":
return ResponseObject()
if os.path.exists(os.path.join(f"./static/locale/{param}.json")):
with open(os.path.join(f"./static/locale/{param}.json"), "r") as f:
return ResponseObject(data=''.join(f.readlines()))
@app.route(f'{APP_PREFIX}/', methods=['GET']) @app.route(f'{APP_PREFIX}/', methods=['GET'])
def index(): def index():
@ -2152,10 +2158,11 @@ def index():
def backGroundThread(): def backGroundThread():
with app.app_context(): global WireguardConfigurations
print(f"[WGDashboard] Background Thread #1 Started", flush=True) print(f"[WGDashboard] Background Thread #1 Started", flush=True)
time.sleep(10) time.sleep(10)
while True: while True:
with app.app_context():
for c in WireguardConfigurations.values(): for c in WireguardConfigurations.values():
if c.getStatus(): if c.getStatus():
try: try:
@ -2166,7 +2173,7 @@ def backGroundThread():
c.getRestrictedPeersList() c.getRestrictedPeersList()
except Exception as e: except Exception as e:
print(f"[WGDashboard] Background Thread #1 Error: {str(e)}", flush=True) print(f"[WGDashboard] Background Thread #1 Error: {str(e)}", flush=True)
time.sleep(10) time.sleep(10)
def peerJobScheduleBackgroundThread(): def peerJobScheduleBackgroundThread():

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,19 @@
{ {
"name": "app", "name": "app",
"version": "4.0.0", "version": "4.0.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "app", "name": "app",
"version": "4.0.0", "version": "4.0.2",
"dependencies": { "dependencies": {
"@vuepic/vue-datepicker": "^9.0.1", "@vuepic/vue-datepicker": "^9.0.1",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"@vueuse/shared": "^10.9.0", "@vueuse/shared": "^10.9.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.2", "bootstrap-icons": "^1.11.3",
"cidr-tools": "^7.0.4", "cidr-tools": "^7.0.4",
"dayjs": "^1.11.12", "dayjs": "^1.11.12",
"electron-builder": "^24.13.3", "electron-builder": "^24.13.3",
@ -1486,9 +1486,9 @@
} }
}, },
"node_modules/bootstrap-icons": { "node_modules/bootstrap-icons": {
"version": "1.11.2", "version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.2.tgz", "resolved": "https://registry.npmmirror.com/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-TgdiPv+IM9tgDb+dsxrnGIyocsk85d2M7T0qIgkvPedZeoZfyeG/j+yiAE4uHCEayKef2RP05ahQ0/e9Sv75Wg==", "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",

View File

@ -15,7 +15,7 @@
"@vueuse/shared": "^10.9.0", "@vueuse/shared": "^10.9.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"bootstrap": "^5.3.2", "bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.2", "bootstrap-icons": "^1.11.3",
"cidr-tools": "^7.0.4", "cidr-tools": "^7.0.4",
"dayjs": "^1.11.12", "dayjs": "^1.11.12",
"electron-builder": "^24.13.3", "electron-builder": "^24.13.3",

View File

@ -1,4 +1,4 @@
<script setup> <script setup async>
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"; import {computed, watch} from "vue";
@ -21,7 +21,6 @@ const getActiveCrossServer = computed(() => {
} }
return undefined return undefined
}) })
</script> </script>
<template> <template>

View File

@ -4,17 +4,38 @@ export default {
props: { props: {
data: Object, data: Object,
saving: Boolean saving: Boolean
},
data(){
return{
enable: false
}
},
watch:{
enable(){
if (this.enable){
this.data.preshared_key = window.wireguard.generateKeypair().presharedKey
}else {
this.data.preshared_key = ""
}
}
} }
} }
</script> </script>
<template> <template>
<div> <div>
<label for="peer_preshared_key_textbox" class="form-label"> <div class="d-flex align-items-start">
<small class="text-muted">Pre-Shared Key</small> <label for="peer_preshared_key_textbox" class="form-label">
</label> <small class="text-muted">Pre-Shared Key</small>
</label>
<div class="form-check form-switch ms-auto">
<input class="form-check-input" type="checkbox" role="switch"
v-model="this.enable"
id="peer_preshared_key_switch">
</div>
</div>
<input type="text" class="form-control form-control-sm rounded-3" <input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving || !this.enable"
v-model="this.data.preshared_key" v-model="this.data.preshared_key"
id="peer_preshared_key_textbox"> id="peer_preshared_key_textbox">
</div> </div>

View File

@ -3,9 +3,10 @@ import { ref } from 'vue'
import { onClickOutside } from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
import "animate.css" import "animate.css"
import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue"; import PeerSettingsDropdown from "@/components/configurationComponents/peerSettingsDropdown.vue";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peer", name: "peer",
components: {PeerSettingsDropdown}, components: {LocaleText, PeerSettingsDropdown},
props: { props: {
Peer: Object Peer: Object
}, },
@ -57,7 +58,7 @@ export default {
<div v-else class="border-0 card-header bg-transparent text-warning fw-bold" <div v-else class="border-0 card-header bg-transparent text-warning fw-bold"
style="font-size: 0.8rem"> style="font-size: 0.8rem">
<i class="bi-lock-fill me-2"></i> <i class="bi-lock-fill me-2"></i>
Access Restricted <LocaleText t="Access Restricted"></LocaleText>
</div> </div>
</div> </div>
<div class="card-body pt-1" style="font-size: 0.9rem"> <div class="card-body pt-1" style="font-size: 0.9rem">
@ -65,12 +66,16 @@ export default {
{{Peer.name ? Peer.name : 'Untitled Peer'}} {{Peer.name ? Peer.name : 'Untitled Peer'}}
</h6> </h6>
<div class="mb-2"> <div class="mb-2">
<small class="text-muted">Public Key</small> <small class="text-muted">
<LocaleText t="Public Key"></LocaleText>
</small>
<p class="mb-0"><samp>{{Peer.id}}</samp></p> <p class="mb-0"><samp>{{Peer.id}}</samp></p>
</div> </div>
<div class="d-flex align-items-end"> <div class="d-flex align-items-end">
<div> <div>
<small class="text-muted">Allowed IP</small> <small class="text-muted">
<LocaleText t="Allowed IPs"></LocaleText>
</small>
<p class="mb-0"><samp>{{Peer.allowed_ip}}</samp></p> <p class="mb-0"><samp>{{Peer.allowed_ip}}</samp></p>
</div> </div>
<div class="ms-auto px-2 rounded-3 subMenuBtn" <div class="ms-auto px-2 rounded-3 subMenuBtn"

View File

@ -33,7 +33,8 @@ export default {
endpoint_allowed_ip: this.dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip, endpoint_allowed_ip: this.dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip,
keepalive: parseInt(this.dashboardStore.Configuration.Peers.peer_keep_alive), keepalive: parseInt(this.dashboardStore.Configuration.Peers.peer_keep_alive),
mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu), mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu),
preshared_key: "" preshared_key: "",
preshared_key_bulkAdd: false
}, },
availableIp: undefined, availableIp: undefined,
availableIpSearchString: "", availableIpSearchString: "",
@ -119,16 +120,28 @@ export default {
<DnsInput :saving="saving" :data="data"></DnsInput> <DnsInput :saving="saving" :data="data"></DnsInput>
<hr class="mb-0 mt-2"> <hr class="mb-0 mt-2">
<div class="row"> <div class="row gy-3">
<div class="col-sm" v-if="!this.data.bulkAdd"> <div class="col-sm" v-if="!this.data.bulkAdd">
<PresharedKeyInput :saving="saving" :data="data" :bulk="this.data.bulkAdd"></PresharedKeyInput> <PresharedKeyInput :saving="saving" :data="data" :bulk="this.data.bulkAdd"></PresharedKeyInput>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<MtuInput :saving="saving" :data="data"></MtuInput> <MtuInput :saving="saving" :data="data"></MtuInput>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<PersistentKeepAliveInput :saving="saving" :data="data"></PersistentKeepAliveInput> <PersistentKeepAliveInput :saving="saving" :data="data"></PersistentKeepAliveInput>
</div> </div>
<div class="col-12" v-if="this.data.bulkAdd">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch"
v-model="this.data.preshared_key_bulkAdd"
id="bullAdd_PresharedKey_Switch" checked>
<label class="form-check-label" for="bullAdd_PresharedKey_Switch">
Pre-Share Key
{{this.data.preshared_key_bulkAdd ? "Enabled":"Disabled"}}
</label>
</div>
</div>
</div> </div>
<div class="d-flex mt-2"> <div class="d-flex mt-2">
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow" <button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"

View File

@ -3,6 +3,7 @@ import ScheduleDropdown from "@/components/configurationComponents/peerScheduleJ
import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue"; import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peerJobs", name: "peerJobs",
setup(){ setup(){
@ -13,6 +14,7 @@ export default {
selectedPeer: Object selectedPeer: Object
}, },
components:{ components:{
LocaleText,
SchedulePeerJob, SchedulePeerJob,
ScheduleDropdown, ScheduleDropdown,
}, },
@ -50,8 +52,8 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal"> <div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow" style="width: 700px"> <div class="card rounded-3 shadow" style="width: 700px">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Schedule Jobs <h4 class="mb-0 fw-normal">
<strong></strong> <LocaleText t="Schedule Jobs"></LocaleText>
</h4> </h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
@ -60,7 +62,8 @@ export default {
<button class="btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow" <button class="btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow"
@click="this.addJob()"> @click="this.addJob()">
<i class="bi bi-plus-lg me-2"></i> Job <i class="bi bi-plus-lg me-2"></i>
<LocaleText t="Job"></LocaleText>
</button> </button>
</div> </div>
<TransitionGroup name="schedulePeerJobTransition" tag="div" class="position-relative"> <TransitionGroup name="schedulePeerJobTransition" tag="div" class="position-relative">
@ -76,7 +79,9 @@ export default {
style="height: 153px" style="height: 153px"
v-if="this.selectedPeer.jobs.length === 0"> v-if="this.selectedPeer.jobs.length === 0">
<div class="card-body text-muted text-center d-flex"> <div class="card-body text-muted text-center d-flex">
<h6 class="m-auto">This peer does not have any job yet.</h6> <h6 class="m-auto">
<LocaleText t="This peer does not have any job yet."></LocaleText>
</h6>
</div> </div>
</div> </div>
</TransitionGroup> </TransitionGroup>

View File

@ -2,6 +2,7 @@
import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue"; import SchedulePeerJob from "@/components/configurationComponents/peerScheduleJobsComponents/schedulePeerJob.vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peerJobsAllModal", name: "peerJobsAllModal",
@ -9,7 +10,7 @@ export default {
const store = WireguardConfigurationsStore(); const store = WireguardConfigurationsStore();
return {store} return {store}
}, },
components: {SchedulePeerJob}, components: {LocaleText, SchedulePeerJob},
props: { props: {
configurationPeers: Array[Object] configurationPeers: Array[Object]
}, },
@ -32,7 +33,8 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal"> <div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow" style="width: 700px"> <div class="card rounded-3 shadow" style="width: 700px">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">All Active Jobs <h4 class="mb-0 fw-normal">
<LocaleText t="All Active Jobs"></LocaleText>
</h4> </h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
@ -71,7 +73,9 @@ export default {
style="height: 153px" style="height: 153px"
v-else> v-else>
<div class="card-body text-muted text-center d-flex"> <div class="card-body text-muted text-center d-flex">
<h6 class="m-auto">No active job at the moment.</h6> <span class="m-auto">
<LocaleText t="No active job at the moment."></LocaleText>
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,8 +1,10 @@
<script> <script>
import dayjs from "dayjs"; import dayjs from "dayjs";
import {fetchGet} from "@/utilities/fetch.js"; import {fetchGet} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peerJobsLogsModal", name: "peerJobsLogsModal",
components: {LocaleText},
props: { props: {
configurationInfo: Object configurationInfo: Object
}, },
@ -51,44 +53,56 @@ export default {
<div class="m-auto mt-0 modal-dialog-centered dashboardModal" style="width: 100%"> <div class="m-auto mt-0 modal-dialog-centered dashboardModal" style="width: 100%">
<div class="card rounded-3 shadow w-100" > <div class="card rounded-3 shadow w-100" >
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h4 class="mb-0">Jobs Logs</h4> <h4 class="mb-0">
<LocaleText t="Jobs Logs"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
<div class="card-body px-4 pb-4 pt-2"> <div class="card-body px-4 pb-4 pt-2">
<div v-if="!this.dataLoading"> <div v-if="!this.dataLoading">
<p>Updated at: {{this.logFetchTime}}</p> <p>
<LocaleText t="Updated at"></LocaleText>
: {{this.logFetchTime}}</p>
<div class="mb-2 d-flex gap-3"> <div class="mb-2 d-flex gap-3">
<button @click="this.fetchLog()" <button @click="this.fetchLog()"
class="btn btn-sm rounded-3 shadow-sm class="btn btn-sm rounded-3 shadow-sm
text-info-emphasis bg-info-subtle border-1 border-info-subtle me-1"> text-info-emphasis bg-info-subtle border-1 border-info-subtle me-1">
<i class="bi bi-arrow-clockwise me-2"></i> <i class="bi bi-arrow-clockwise me-2"></i>
Refresh <LocaleText t="Refresh"></LocaleText>
</button> </button>
<div class="d-flex gap-3 align-items-center"> <div class="d-flex gap-3 align-items-center">
<span class="text-muted">Filter</span> <span class="text-muted">
<LocaleText t="Filter"></LocaleText>
</span>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" v-model="this.showSuccessJob" <input class="form-check-input" type="checkbox" v-model="this.showSuccessJob"
id="jobLogsShowSuccessCheck"> id="jobLogsShowSuccessCheck">
<label class="form-check-label" for="jobLogsShowSuccessCheck"> <label class="form-check-label" for="jobLogsShowSuccessCheck">
<span class="badge text-success-emphasis bg-success-subtle">Success</span> <span class="badge text-success-emphasis bg-success-subtle">
<LocaleText t="Success"></LocaleText>
</span>
</label> </label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" v-model="this.showFailedJob" <input class="form-check-input" type="checkbox" v-model="this.showFailedJob"
id="jobLogsShowFailedCheck"> id="jobLogsShowFailedCheck">
<label class="form-check-label" for="jobLogsShowFailedCheck"> <label class="form-check-label" for="jobLogsShowFailedCheck">
<span class="badge text-danger-emphasis bg-danger-subtle">Failed</span> <span class="badge text-danger-emphasis bg-danger-subtle">
<LocaleText t="Failed"></LocaleText>
</span>
</label> </label>
</div> </div>
</div> </div>
<div class="d-flex gap-3 align-items-center ms-auto"> <div class="d-flex gap-3 align-items-center ms-auto">
<span class="text-muted">Display</span> <span class="text-muted">
<LocaleText t="Display"></LocaleText>
</span>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
v-model="showJobID" v-model="showJobID"
id="jobLogsShowJobIDCheck"> id="jobLogsShowJobIDCheck">
<label class="form-check-label" for="jobLogsShowJobIDCheck"> <label class="form-check-label" for="jobLogsShowJobIDCheck">
Job ID <LocaleText t="Job ID"></LocaleText>
</label> </label>
</div> </div>
<div class="form-check"> <div class="form-check">
@ -96,7 +110,7 @@ export default {
v-model="showLogID" v-model="showLogID"
id="jobLogsShowLogIDCheck"> id="jobLogsShowLogIDCheck">
<label class="form-check-label" for="jobLogsShowLogIDCheck"> <label class="form-check-label" for="jobLogsShowLogIDCheck">
Log ID <LocaleText t="Log ID"></LocaleText>
</label> </label>
</div> </div>
@ -106,11 +120,21 @@ export default {
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th scope="col">Date</th> <th scope="col">
<th scope="col" v-if="showLogID">Log ID</th> <LocaleText t="Date"></LocaleText>
<th scope="col" v-if="showJobID">Job ID</th> </th>
<th scope="col">Status</th> <th scope="col" v-if="showLogID">
<th scope="col">Message</th> <LocaleText t="Log ID"></LocaleText>
</th>
<th scope="col" v-if="showJobID">
<LocaleText t="Job ID"></LocaleText>
</th>
<th scope="col">
<LocaleText t="Status"></LocaleText>
</th>
<th scope="col">
<LocaleText t="Message"></LocaleText>
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -41,6 +41,7 @@ import PeerJobsAllModal from "@/components/configurationComponents/peerJobsAllMo
import PeerJobsLogsModal from "@/components/configurationComponents/peerJobsLogsModal.vue"; import PeerJobsLogsModal from "@/components/configurationComponents/peerJobsLogsModal.vue";
import {ref} from "vue"; import {ref} from "vue";
import PeerShareLinkModal from "@/components/configurationComponents/peerShareLinkModal.vue"; import PeerShareLinkModal from "@/components/configurationComponents/peerShareLinkModal.vue";
import LocaleText from "@/components/text/localeText.vue";
Chart.register( Chart.register(
ArcElement, ArcElement,
@ -71,6 +72,7 @@ Chart.register(
export default { export default {
name: "peerList", name: "peerList",
components: { components: {
LocaleText,
PeerShareLinkModal, PeerShareLinkModal,
PeerJobsLogsModal, PeerJobsLogsModal,
PeerJobsAllModal, PeerJobs, PeerCreate, PeerQRCode, PeerSettings, PeerSearch, Peer, Line, Bar}, PeerJobsAllModal, PeerJobs, PeerCreate, PeerQRCode, PeerSettings, PeerSearch, Peer, Line, Bar},
@ -370,7 +372,11 @@ export default {
}); });
const result = this.wireguardConfigurationStore.searchString ? const result = this.wireguardConfigurationStore.searchString ?
fuse.search(this.wireguardConfigurationStore.searchString).map(x => x.item) : this.configurationPeers; this.configurationPeers.filter(x => {
return x.name.includes(this.wireguardConfigurationStore.searchString) ||
x.id.includes(this.wireguardConfigurationStore.searchString) ||
x.allowed_ip.includes(this.wireguardConfigurationStore.searchString)
}) : this.configurationPeers;
if (this.dashboardConfigurationStore.Configuration.Server.dashboard_sort === "restricted"){ if (this.dashboardConfigurationStore.Configuration.Server.dashboard_sort === "restricted"){
return result.slice().sort((a, b) => { return result.slice().sort((a, b) => {
@ -406,7 +412,9 @@ export default {
<div v-if="!this.loading" class="container-md"> <div v-if="!this.loading" class="container-md">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<div> <div>
<small CLASS="text-muted">CONFIGURATION</small> <small CLASS="text-muted">
<LocaleText t="CONFIGURATION"></LocaleText>
</small>
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1> <h1 class="mb-0"><samp>{{this.configurationInfo.Name}}</samp></h1>
</div> </div>
@ -414,13 +422,15 @@ export default {
<div class="card rounded-3 bg-transparent shadow-sm ms-auto"> <div class="card rounded-3 bg-transparent shadow-sm ms-auto">
<div class="card-body py-2 d-flex align-items-center"> <div class="card-body py-2 d-flex align-items-center">
<div> <div>
<p class="mb-0 text-muted"><small>Status</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Status"></LocaleText>
</small></p>
<div class="form-check form-switch ms-auto"> <div class="form-check form-switch ms-auto">
<label class="form-check-label" style="cursor: pointer" :for="'switch' + this.configurationInfo.id"> <label class="form-check-label" style="cursor: pointer" :for="'switch' + this.configurationInfo.id">
{{this.configurationToggling ? 'Turning ':''}} <LocaleText t="On" v-if="this.configurationInfo.Status"></LocaleText>
{{this.configurationInfo.Status ? "On":"Off"}} <LocaleText t="Off" v-else></LocaleText>
<span v-if="this.configurationToggling" <span v-if="this.configurationToggling"
class="spinner-border spinner-border-sm" aria-hidden="true"></span> class="spinner-border spinner-border-sm ms-2" aria-hidden="true"></span>
</label> </label>
<input class="form-check-input" <input class="form-check-input"
style="cursor: pointer" style="cursor: pointer"
@ -440,7 +450,9 @@ export default {
<div class="col-6 col-lg-3"> <div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body py-2"> <div class="card-body py-2">
<p class="mb-0 text-muted"><small>Address</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Address"></LocaleText>
</small></p>
{{this.configurationInfo.Address}} {{this.configurationInfo.Address}}
</div> </div>
</div> </div>
@ -448,7 +460,9 @@ export default {
<div class="col-6 col-lg-3"> <div class="col-6 col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body py-2"> <div class="card-body py-2">
<p class="mb-0 text-muted"><small>Listen Port</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Listen Port"></LocaleText>
</small></p>
{{this.configurationInfo.ListenPort}} {{this.configurationInfo.ListenPort}}
</div> </div>
</div> </div>
@ -456,7 +470,9 @@ export default {
<div style="word-break: break-all" class="col-12 col-lg-6"> <div style="word-break: break-all" class="col-12 col-lg-6">
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body py-2"> <div class="card-body py-2">
<p class="mb-0 text-muted"><small>Public Key</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Public Key"></LocaleText>
</small></p>
<samp>{{this.configurationInfo.PublicKey}}</samp> <samp>{{this.configurationInfo.PublicKey}}</samp>
</div> </div>
</div> </div>
@ -467,7 +483,9 @@ export default {
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex"> <div class="card-body d-flex">
<div> <div>
<p class="mb-0 text-muted"><small>Connected Peers</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Connected Peers"></LocaleText>
</small></p>
<strong class="h4">{{configurationSummary.connectedPeers}}</strong> <strong class="h4">{{configurationSummary.connectedPeers}}</strong>
</div> </div>
<i class="bi bi-ethernet ms-auto h2 text-muted"></i> <i class="bi bi-ethernet ms-auto h2 text-muted"></i>
@ -478,7 +496,9 @@ export default {
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex"> <div class="card-body d-flex">
<div> <div>
<p class="mb-0 text-muted"><small>Total Usage</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Total Usage"></LocaleText>
</small></p>
<strong class="h4">{{configurationSummary.totalUsage}} GB</strong> <strong class="h4">{{configurationSummary.totalUsage}} GB</strong>
</div> </div>
<i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i> <i class="bi bi-arrow-down-up ms-auto h2 text-muted"></i>
@ -489,7 +509,9 @@ export default {
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex"> <div class="card-body d-flex">
<div> <div>
<p class="mb-0 text-muted"><small>Total Received</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Total Received"></LocaleText>
</small></p>
<strong class="h4 text-primary">{{configurationSummary.totalReceive}} GB</strong> <strong class="h4 text-primary">{{configurationSummary.totalReceive}} GB</strong>
</div> </div>
<i class="bi bi-arrow-down ms-auto h2 text-muted"></i> <i class="bi bi-arrow-down ms-auto h2 text-muted"></i>
@ -500,7 +522,9 @@ export default {
<div class="card rounded-3 bg-transparent shadow-sm"> <div class="card rounded-3 bg-transparent shadow-sm">
<div class="card-body d-flex"> <div class="card-body d-flex">
<div> <div>
<p class="mb-0 text-muted"><small>Total Sent</small></p> <p class="mb-0 text-muted"><small>
<LocaleText t="Total Sent"></LocaleText>
</small></p>
<strong class="h4 text-success">{{configurationSummary.totalSent}} GB</strong> <strong class="h4 text-success">{{configurationSummary.totalSent}} GB</strong>
</div> </div>
<i class="bi bi-arrow-up ms-auto h2 text-muted"></i> <i class="bi bi-arrow-up ms-auto h2 text-muted"></i>
@ -512,7 +536,9 @@ export default {
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px"> <div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0"> <div class="card-header bg-transparent border-0">
<small class="text-muted">Peers Total Data Usage</small></div> <small class="text-muted">
<LocaleText t="Peers Data Usage"></LocaleText>
</small></div>
<div class="card-body pt-1"> <div class="card-body pt-1">
<Bar <Bar
:data="individualDataUsage" :data="individualDataUsage"
@ -523,7 +549,9 @@ export default {
</div> </div>
<div class="col-sm col-lg-3"> <div class="col-sm col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px"> <div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Received Data Usage</small></div> <div class="card-header bg-transparent border-0"><small class="text-muted">
<LocaleText t="Real Time Received Data Usage"></LocaleText>
</small></div>
<div class="card-body pt-1"> <div class="card-body pt-1">
<Line <Line
:options="chartOptions" :options="chartOptions"
@ -535,7 +563,9 @@ export default {
</div> </div>
<div class="col-sm col-lg-3"> <div class="col-sm col-lg-3">
<div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px"> <div class="card rounded-3 bg-transparent shadow-sm" style="height: 270px">
<div class="card-header bg-transparent border-0"><small class="text-muted">Real Time Sent Data Usage</small></div> <div class="card-header bg-transparent border-0"><small class="text-muted">
<LocaleText t="Real Time Sent Data Usage"></LocaleText>
</small></div>
<div class="card-body pt-1"> <div class="card-body pt-1">
<Line <Line
:options="chartOptions" :options="chartOptions"

View File

@ -1,7 +1,9 @@
<script> <script>
import QRCode from "qrcode"; import QRCode from "qrcode";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peerQRCode", name: "peerQRCode",
components: {LocaleText},
props: { props: {
peerConfigData: String peerConfigData: String
}, },
@ -19,7 +21,9 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal justify-content-center"> <div class="m-auto modal-dialog-centered dashboardModal justify-content-center">
<div class="card rounded-3 shadow"> <div class="card rounded-3 shadow">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h4 class="mb-0">QR Code</h4> <h4 class="mb-0">
<LocaleText t="QR Code"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
<div class="card-body"> <div class="card-body">

View File

@ -5,10 +5,11 @@ import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import VueDatePicker from "@vuepic/vue-datepicker"; import VueDatePicker from "@vuepic/vue-datepicker";
import dayjs from "dayjs"; import dayjs from "dayjs";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "schedulePeerJob", name: "schedulePeerJob",
components: {VueDatePicker, ScheduleDropdown}, components: {LocaleText, VueDatePicker, ScheduleDropdown},
props: { props: {
dropdowns: Array[Object], dropdowns: Array[Object],
pjob: Object, pjob: Object,
@ -110,15 +111,19 @@ export default {
<div class="card shadow-sm rounded-3 mb-2" :class="{'border-warning-subtle': this.newJob}"> <div class="card shadow-sm rounded-3 mb-2" :class="{'border-warning-subtle': this.newJob}">
<div class="card-header bg-transparent text-muted border-0"> <div class="card-header bg-transparent text-muted border-0">
<small class="d-flex" v-if="!this.newJob"> <small class="d-flex" v-if="!this.newJob">
<strong class="me-auto">Job ID</strong> <strong class="me-auto">
<LocaleText t="Job ID"></LocaleText>
</strong>
<samp>{{this.job.JobID}}</samp> <samp>{{this.job.JobID}}</samp>
</small> </small>
<small v-else><span class="badge text-bg-warning">Unsaved Job</span></small> <small v-else><span class="badge text-bg-warning">
<LocaleText t="Unsaved Job"></LocaleText>
</span></small>
</div> </div>
<div class="card-body pt-1" style="font-family: var(--bs-font-monospace)"> <div class="card-body pt-1" style="font-family: var(--bs-font-monospace)">
<div class="d-flex gap-2 align-items-center mb-2"> <div class="d-flex gap-2 align-items-center mb-2">
<samp> <samp>
if <LocaleText t="if"></LocaleText>
</samp> </samp>
<ScheduleDropdown <ScheduleDropdown
:edit="edit" :edit="edit"
@ -127,7 +132,7 @@ export default {
@update="(value) => {this.job.Field = value}" @update="(value) => {this.job.Field = value}"
></ScheduleDropdown> ></ScheduleDropdown>
<samp> <samp>
is <LocaleText t="is"></LocaleText>
</samp> </samp>
<ScheduleDropdown <ScheduleDropdown
:edit="edit" :edit="edit"
@ -149,12 +154,6 @@ export default {
:dark="this.store.Configuration.Server.dashboard_theme === 'dark'" :dark="this.store.Configuration.Server.dashboard_theme === 'dark'"
/> />
<!-- <input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"-->
<!-- :disabled="!edit"-->
<!-- type="datetime-local"-->
<!-- v-if="this.job.Field === 'date'"-->
<!-- v-model="this.job.Value"-->
<!-- style="width: auto">-->
<input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1" <input class="form-control form-control-sm form-control-dark rounded-3 flex-grow-1"
:disabled="!edit" :disabled="!edit"
v-else v-else
@ -165,7 +164,7 @@ export default {
</samp> </samp>
</div> </div>
<div class="px-5 d-flex gap-2 align-items-center"> <div class="px-5 d-flex gap-2 align-items-center">
<samp>then</samp> <samp><LocaleText t="then"></LocaleText></samp>
<ScheduleDropdown <ScheduleDropdown
:edit="edit" :edit="edit"
:options="this.dropdowns.Action" :options="this.dropdowns.Action"
@ -178,18 +177,18 @@ export default {
<div class="ms-auto d-flex gap-3" v-if="!this.edit"> <div class="ms-auto d-flex gap-3" v-if="!this.edit">
<a role="button" <a role="button"
class="ms-auto text-decoration-none" class="ms-auto text-decoration-none"
@click="this.edit = true">[E] Edit</a> @click="this.edit = true">[E] <LocaleText t="Edit"></LocaleText></a>
<a role="button" <a role="button"
@click="this.delete()" @click="this.delete()"
class=" text-danger text-decoration-none">[D] Delete</a> class=" text-danger text-decoration-none">[D] <LocaleText t="Delete"></LocaleText></a>
</div> </div>
<div class="ms-auto d-flex gap-3" v-else> <div class="ms-auto d-flex gap-3" v-else>
<a role="button" <a role="button"
class="text-secondary text-decoration-none" class="text-secondary text-decoration-none"
@click="this.reset()">[C] Cancel</a> @click="this.reset()">[C] <LocaleText t="Cancel"></LocaleText></a>
<a role="button" <a role="button"
class="text-primary ms-auto text-decoration-none" class="text-primary ms-auto text-decoration-none"
@click="this.save()">[S] Save</a> @click="this.save()">[S] <LocaleText t="Save"></LocaleText></a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,10 +2,13 @@
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchGet, fetchPost} from "@/utilities/fetch.js"; import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import LocaleText from "@/components/text/localeText.vue";
import {GetLocale} from "@/utilities/locale.js";
export default { export default {
name: "peerSearch", name: "peerSearch",
components: {LocaleText},
setup(){ setup(){
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
const wireguardConfigurationStore = WireguardConfigurationsStore() const wireguardConfigurationStore = WireguardConfigurationsStore()
@ -18,16 +21,16 @@ export default {
data(){ data(){
return { return {
sort: { sort: {
status: "Status", status: GetLocale("Status"),
name: "Name", name: GetLocale("Name"),
allowed_ip: "Allowed IP", allowed_ip: GetLocale("Allowed IPs"),
restricted: "Restricted" restricted: GetLocale("Restricted")
}, },
interval: { interval: {
'5000': '5 Seconds', '5000': GetLocale('5 Seconds'),
'10000': '10 Seconds', '10000': GetLocale('10 Seconds'),
'30000': '30 Seconds', '30000': GetLocale('30 Seconds'),
'60000': '1 Minutes' '60000': GetLocale('1 Minutes')
}, },
searchString: "", searchString: "",
searchStringTimeout: undefined, searchStringTimeout: undefined,
@ -77,8 +80,10 @@ export default {
}) })
} }
}, },
mounted() { computed: {
searchBarPlaceholder(){
return GetLocale("Search Peers...")
}
} }
} }
</script> </script>
@ -89,15 +94,18 @@ export default {
<RouterLink <RouterLink
to="create" to="create"
class="text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm"> class="text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm">
<i class="bi bi-plus-lg me-2"></i>Peer <i class="bi bi-plus-lg me-2"></i>
<LocaleText t="Peer"></LocaleText>
</RouterLink> </RouterLink>
<button class="btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm" <button class="btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm"
@click="this.downloadAllPeer()"> @click="this.downloadAllPeer()">
<i class="bi bi-download me-2"></i> Download All <i class="bi bi-download me-2"></i>
<LocaleText t="Download All"></LocaleText>
</button> </button>
<div class="flex-grow-1 mt-3 mt-md-0"> <div class="mt-3 mt-md-0 flex-grow-1">
<input class="form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle shadow-sm w-100" <input class="form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle shadow-sm w-100"
placeholder="Search..." :placeholder="searchBarPlaceholder"
id="searchPeers" id="searchPeers"
@keyup="this.debounce()" @keyup="this.debounce()"
v-model="this.searchString"> v-model="this.searchString">
@ -105,9 +113,9 @@ export default {
<button <button
@click="this.showDisplaySettings = true" @click="this.showDisplaySettings = true"
class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm" class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
type="button" aria-expanded="false"> type="button" aria-expanded="false">
<i class="bi bi-filter-circle me-2"></i> <i class="bi bi-filter-circle me-2"></i>
Display <LocaleText t="Display"></LocaleText>
</button> </button>
<button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm" <button class="btn text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
@click="this.showMoreSettings = true" @click="this.showMoreSettings = true"
@ -122,13 +130,15 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal"> <div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow w-100"> <div class="card rounded-3 shadow w-100">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Display <h4 class="mb-0 fw-normal"><LocaleText t="Display"></LocaleText>
</h4> </h4>
<button type="button" class="btn-close ms-auto" @click="this.showDisplaySettings = false"></button> <button type="button" class="btn-close ms-auto" @click="this.showDisplaySettings = false"></button>
</div> </div>
<div class="card-body px-4 pb-4 d-flex gap-3 flex-column"> <div class="card-body px-4 pb-4 d-flex gap-3 flex-column">
<div> <div>
<p class="text-muted fw-bold mb-2"><small>Sort by</small></p> <p class="text-muted fw-bold mb-2"><small>
<LocaleText t="Sort by"></LocaleText>
</small></p>
<div class="list-group"> <div class="list-group">
<a v-for="(value, key) in this.sort" class="list-group-item list-group-item-action d-flex" role="button" @click="this.updateSort(key)"> <a v-for="(value, key) in this.sort" class="list-group-item list-group-item-action d-flex" role="button" @click="this.updateSort(key)">
<span class="me-auto">{{value}}</span> <span class="me-auto">{{value}}</span>
@ -138,7 +148,9 @@ export default {
</div> </div>
</div> </div>
<div> <div>
<p class="text-muted fw-bold mb-2"><small>Refresh interval</small></p> <p class="text-muted fw-bold mb-2"><small>
<LocaleText t="Refresh Interval"></LocaleText>
</small></p>
<div class="list-group"> <div class="list-group">
<a v-for="(value, key) in this.interval" <a v-for="(value, key) in this.interval"
class="list-group-item list-group-item-action d-flex" role="button" class="list-group-item list-group-item-action d-flex" role="button"
@ -163,21 +175,24 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal"> <div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow w-100"> <div class="card rounded-3 shadow w-100">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0 fw-normal">Configuration Settings <h4 class="mb-0 fw-normal">
<LocaleText t="Configuration Settings"></LocaleText>
</h4> </h4>
<button type="button" class="btn-close ms-auto" @click="this.showMoreSettings = false"></button> <button type="button" class="btn-close ms-auto" @click="this.showMoreSettings = false"></button>
</div> </div>
<div class="card-body px-4 pb-4 d-flex gap-3 flex-column"> <div class="card-body px-4 pb-4 d-flex gap-3 flex-column">
<div> <div>
<p class="text-muted fw-bold mb-2"><small>Peer Jobs</small></p> <p class="text-muted fw-bold mb-2"><small>
<LocaleText t="Peer Jobs"></LocaleText>
</small></p>
<div class="list-group"> <div class="list-group">
<a class="list-group-item list-group-item-action d-flex" role="button" <a class="list-group-item list-group-item-action d-flex" role="button"
@click="this.$emit('jobsAll')"> @click="this.$emit('jobsAll')">
Active Jobs <LocaleText t="Active Jobs"></LocaleText>
</a> </a>
<a class="list-group-item list-group-item-action d-flex" role="button" <a class="list-group-item list-group-item-action d-flex" role="button"
@click="this.$emit('jobLogs')"> @click="this.$emit('jobLogs')">
Logs <LocaleText t="Logs"></LocaleText>
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,9 +1,11 @@
<script> <script>
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peerSettings", name: "peerSettings",
components: {LocaleText},
props: { props: {
selectedPeer: Object selectedPeer: Object
}, },
@ -73,18 +75,24 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal"> <div class="m-auto modal-dialog-centered dashboardModal">
<div class="card rounded-3 shadow flex-grow-1"> <div class="card rounded-3 shadow flex-grow-1">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2">
<h4 class="mb-0">Peer Settings</h4> <h4 class="mb-0">
<LocaleText t="Peer Settings"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
<div class="card-body px-4 pb-4" v-if="this.data"> <div class="card-body px-4 pb-4" v-if="this.data">
<div class="d-flex flex-column gap-2 mb-4"> <div class="d-flex flex-column gap-2 mb-4">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<small class="text-muted">Public Key</small> <small class="text-muted">
<LocaleText t="Public Key"></LocaleText>
</small>
<small class="ms-auto"><samp>{{this.data.id}}</samp></small> <small class="ms-auto"><samp>{{this.data.id}}</samp></small>
</div> </div>
<div> <div>
<label for="peer_name_textbox" class="form-label"> <label for="peer_name_textbox" class="form-label">
<small class="text-muted">Name</small> <small class="text-muted">
<LocaleText t="Name"></LocaleText>
</small>
</label> </label>
<input type="text" class="form-control form-control-sm rounded-3" <input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
@ -94,7 +102,10 @@ export default {
<div> <div>
<div class="d-flex position-relative"> <div class="d-flex position-relative">
<label for="peer_private_key_textbox" class="form-label"> <label for="peer_private_key_textbox" class="form-label">
<small class="text-muted">Private Key <code>(Required for QR Code and Download)</code></small> <small class="text-muted"><LocaleText t="Private Key"></LocaleText>
<code>
<LocaleText t="(Required for QR Code and Download)"></LocaleText>
</code></small>
</label> </label>
<a role="button" class="ms-auto text-decoration-none toggleShowKey" <a role="button" class="ms-auto text-decoration-none toggleShowKey"
@click="this.showKey = !this.showKey" @click="this.showKey = !this.showKey"
@ -110,7 +121,11 @@ export default {
</div> </div>
<div> <div>
<label for="peer_allowed_ip_textbox" class="form-label"> <label for="peer_allowed_ip_textbox" class="form-label">
<small class="text-muted">Allowed IPs <code>(Required)</code></small> <small class="text-muted">
<LocaleText t="Allowed IPs"></LocaleText>
<code>
<LocaleText t="(Required)"></LocaleText>
</code></small>
</label> </label>
<input type="text" class="form-control form-control-sm rounded-3" <input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
@ -120,7 +135,11 @@ export default {
<div> <div>
<label for="peer_endpoint_allowed_ips" class="form-label"> <label for="peer_endpoint_allowed_ips" class="form-label">
<small class="text-muted">Endpoint Allowed IPs <code>(Required)</code></small> <small class="text-muted">
<LocaleText t="Endpoint Allowed IPs"></LocaleText>
<code>
<LocaleText t="(Required)"></LocaleText>
</code></small>
</label> </label>
<input type="text" class="form-control form-control-sm rounded-3" <input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
@ -129,7 +148,9 @@ export default {
</div> </div>
<div> <div>
<label for="peer_DNS_textbox" class="form-label"> <label for="peer_DNS_textbox" class="form-label">
<small class="text-muted">DNS</small> <small class="text-muted">
<LocaleText t="DNS"></LocaleText>
</small>
</label> </label>
<input type="text" class="form-control form-control-sm rounded-3" <input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
@ -141,7 +162,7 @@ export default {
<h2 class="accordion-header"> <h2 class="accordion-header">
<button class="accordion-button rounded-3 collapsed" type="button" <button class="accordion-button rounded-3 collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#peerSettingsAccordionOptional"> data-bs-toggle="collapse" data-bs-target="#peerSettingsAccordionOptional">
Optional Settings <LocaleText t="Optional Settings"></LocaleText>
</button> </button>
</h2> </h2>
<div id="peerSettingsAccordionOptional" class="accordion-collapse collapse" <div id="peerSettingsAccordionOptional" class="accordion-collapse collapse"
@ -149,7 +170,8 @@ export default {
<div class="accordion-body d-flex flex-column gap-2 mb-2"> <div class="accordion-body d-flex flex-column gap-2 mb-2">
<div> <div>
<label for="peer_preshared_key_textbox" class="form-label"> <label for="peer_preshared_key_textbox" class="form-label">
<small class="text-muted">Pre-Shared Key</small> <small class="text-muted">
<LocaleText t="Pre-Shared Key"></LocaleText></small>
</label> </label>
<input type="text" class="form-control form-control-sm rounded-3" <input type="text" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
@ -157,7 +179,9 @@ export default {
id="peer_preshared_key_textbox"> id="peer_preshared_key_textbox">
</div> </div>
<div> <div>
<label for="peer_mtu" class="form-label"><small class="text-muted">MTU</small></label> <label for="peer_mtu" class="form-label"><small class="text-muted">
<LocaleText t="MTU"></LocaleText>
</small></label>
<input type="number" class="form-control form-control-sm rounded-3" <input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
v-model="this.data.mtu" v-model="this.data.mtu"
@ -165,7 +189,9 @@ export default {
</div> </div>
<div> <div>
<label for="peer_keep_alive" class="form-label"> <label for="peer_keep_alive" class="form-label">
<small class="text-muted">Persistent Keepalive</small> <small class="text-muted">
<LocaleText t="Persistent Keepalive"></LocaleText>
</small>
</label> </label>
<input type="number" class="form-control form-control-sm rounded-3" <input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving" :disabled="this.saving"
@ -178,25 +204,27 @@ export default {
</div> </div>
<hr> <hr>
<div class="d-flex gap-2 align-items-center"> <div class="d-flex gap-2 align-items-center">
<strong>Reset Data Usage</strong> <strong>
<LocaleText t="Reset Data Usage"></LocaleText>
</strong>
<div class="d-flex gap-2 ms-auto"> <div class="d-flex gap-2 ms-auto">
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm" <button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
@click="this.resetPeerData('total')" @click="this.resetPeerData('total')"
> >
<i class="bi bi-arrow-down-up me-2"></i> <i class="bi bi-arrow-down-up me-2"></i>
Total <LocaleText t="Total"></LocaleText>
</button> </button>
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm" <button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
@click="this.resetPeerData('receive')" @click="this.resetPeerData('receive')"
> >
<i class="bi bi-arrow-down me-2"></i> <i class="bi bi-arrow-down me-2"></i>
Received <LocaleText t="Received"></LocaleText>
</button> </button>
<button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm" <button class="btn bg-primary-subtle text-primary-emphasis rounded-3 flex-grow-1 shadow-sm"
@click="this.resetPeerData('sent')" @click="this.resetPeerData('sent')"
> >
<i class="bi bi-arrow-up me-2"></i> <i class="bi bi-arrow-up me-2"></i>
Sent <LocaleText t="Sent"></LocaleText>
</button> </button>
</div> </div>
@ -206,14 +234,16 @@ export default {
<button class="btn btn-secondary rounded-3 shadow" <button class="btn btn-secondary rounded-3 shadow"
@click="this.reset()" @click="this.reset()"
:disabled="!this.dataChanged || this.saving"> :disabled="!this.dataChanged || this.saving">
Revert <i class="bi bi-arrow-clockwise ms-2"></i> <LocaleText t="Revert"></LocaleText>
<i class="bi bi-arrow-clockwise ms-2"></i>
</button> </button>
<button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow" <button class="ms-auto btn btn-dark btn-brand rounded-3 px-3 py-2 shadow"
:disabled="!this.dataChanged || this.saving" :disabled="!this.dataChanged || this.saving"
@click="this.savePeer()" @click="this.savePeer()"
> >
Save Peer<i class="bi bi-save-fill ms-2"></i></button> <LocaleText t="Save Peer"></LocaleText>
<i class="bi bi-save-fill ms-2"></i></button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,9 +1,11 @@
<script> <script>
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 LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "peerSettingsDropdown", name: "peerSettingsDropdown",
components: {LocaleText},
setup(){ setup(){
const dashboardStore = DashboardConfigurationStore() const dashboardStore = DashboardConfigurationStore()
return {dashboardStore} return {dashboardStore}
@ -88,9 +90,8 @@ export default {
<template v-if="!this.Peer.private_key"> <template v-if="!this.Peer.private_key">
<li> <li>
<small class="w-100 dropdown-item text-muted" <small class="w-100 dropdown-item text-muted"
style="white-space: break-spaces; font-size: 0.7rem" style="white-space: break-spaces; font-size: 0.7rem">
>Download & QR Code is not available due to no <code>private key</code> <LocaleText t="Download & QR Code is not available due to no private key set for this peer"></LocaleText>
set for this peer
</small> </small>
</li> </li>
@ -114,7 +115,7 @@ export default {
<a class="dropdown-item d-flex" role="button" <a class="dropdown-item d-flex" role="button"
@click="this.$emit('setting')" @click="this.$emit('setting')"
> >
<i class="me-auto bi bi-pen"></i> Edit <i class="me-auto bi bi-pen"></i> <LocaleText t="Peer Settings"></LocaleText>
</a> </a>
</li> </li>
<li> <li>

View File

@ -4,6 +4,7 @@ import {fetchPost} from "@/utilities/fetch.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import VueDatePicker from '@vuepic/vue-datepicker'; import VueDatePicker from '@vuepic/vue-datepicker';
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
@ -12,6 +13,7 @@ export default {
peer: Object peer: Object
}, },
components: { components: {
LocaleText,
VueDatePicker VueDatePicker
}, },
data(){ data(){
@ -49,11 +51,9 @@ export default {
if (res.status){ if (res.status){
this.peer.ShareLink = res.data; this.peer.ShareLink = res.data;
this.dataCopy = res.data.at(0); this.dataCopy = res.data.at(0);
this.store.newMessage("Server", "Share link created successfully", "success")
}else{ }else{
this.store.newMessage("Server", this.store.newMessage("Server",
"Share link failed to create. Reason: " + res.message, "danger") "Share link failed to create. Reason: " + res.message, "danger")
} }
this.loading = false; this.loading = false;
}) })
@ -108,13 +108,15 @@ export default {
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 500px"> <div class="m-auto modal-dialog-centered dashboardModal" style="width: 500px">
<div class="card rounded-3 shadow flex-grow-1"> <div class="card rounded-3 shadow flex-grow-1">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
<h4 class="mb-0">Share Peer</h4> <h4 class="mb-0">
<LocaleText t="Share Peer"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
<div class="card-body px-4 pb-4" v-if="this.peer.ShareLink"> <div class="card-body px-4 pb-4" v-if="this.peer.ShareLink">
<div v-if="!this.dataCopy"> <div v-if="!this.dataCopy">
<h6 class="mb-3 text-muted"> <h6 class="mb-3 text-muted">
Currently the peer is not sharing <LocaleText t="Currently the peer is not sharing"></LocaleText>
</h6> </h6>
<button <button
@click="this.startSharing()" @click="this.startSharing()"
@ -123,7 +125,8 @@ export default {
<span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}"> <span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}">
<i class="bi bi-send-fill me-2" ></i> <i class="bi bi-send-fill me-2" ></i>
</span> </span>
{{this.loading ? "Sharing...":"Start Sharing"}} <LocaleText t="Sharing..." v-if="this.loading"></LocaleText>
<LocaleText t="Start Sharing" v-else></LocaleText>
</button> </button>
</div> </div>
<div v-else> <div v-else>
@ -137,7 +140,7 @@ export default {
<div class="d-flex flex-column gap-2 mb-3"> <div class="d-flex flex-column gap-2 mb-3">
<small> <small>
<i class="bi bi-calendar me-2"></i> <i class="bi bi-calendar me-2"></i>
Expire Date <LocaleText t="Expire At"></LocaleText>
</small> </small>
<VueDatePicker <VueDatePicker
:is24="true" :is24="true"
@ -157,7 +160,8 @@ export default {
<span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}"> <span :class="{'animate__animated animate__flash animate__infinite animate__slower': this.loading}">
<i class="bi bi-send-slash-fill me-2" ></i> <i class="bi bi-send-slash-fill me-2" ></i>
</span> </span>
{{this.loading ? "Stop Sharing...":"Stop Sharing"}} <LocaleText t="Stop Sharing..." v-if="this.loading"></LocaleText>
<LocaleText t="Stop Sharing" v-else></LocaleText>
</button> </button>
</div> </div>
</div> </div>

View File

@ -2,10 +2,11 @@
import {wgdashboardStore} from "@/stores/wgdashboardStore.js"; import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import ConfigurationCard from "@/components/configurationListComponents/configurationCard.vue"; import ConfigurationCard from "@/components/configurationListComponents/configurationCard.vue";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "configurationList", name: "configurationList",
components: {ConfigurationCard}, components: {LocaleText, ConfigurationCard},
async setup(){ async setup(){
const wireguardConfigurationsStore = WireguardConfigurationsStore(); const wireguardConfigurationsStore = WireguardConfigurationsStore();
return {wireguardConfigurationsStore} return {wireguardConfigurationsStore}
@ -36,16 +37,18 @@ export default {
<div class="d-flex mb-4 configurationListTitle"> <div class="d-flex mb-4 configurationListTitle">
<h3 class="text-body d-flex"> <h3 class="text-body d-flex">
<i class="bi bi-body-text me-2"></i> <i class="bi bi-body-text me-2"></i>
<span>WireGuard Configurations</span></h3> <span>
<LocaleText t="WireGuard Configurations"></LocaleText>
</span></h3>
<RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-3"> <RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-3">
<i class="bi bi-plus-circle-fill me-2"></i> <i class="bi bi-plus-circle-fill me-2"></i>
Configuration <LocaleText t="Configuration"></LocaleText>
</RouterLink> </RouterLink>
</div> </div>
<Transition name="fade" mode="out-in"> <Transition name="fade" mode="out-in">
<div v-if="this.configurationLoaded"> <div v-if="this.configurationLoaded">
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0"> <p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">
You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard". <LocaleText t="You don't have any WireGuard configurations yet. Please check the configuration folder or change it in Settings. By default the folder is /etc/wireguard."></LocaleText>
</p> </p>
<div class="d-flex gap-3 flex-column mb-3" v-else> <div class="d-flex gap-3 flex-column mb-3" v-else>
<ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard> <ConfigurationCard v-for="c in this.wireguardConfigurationsStore.Configurations" :key="c.Name" :c="c"></ConfigurationCard>

View File

@ -3,9 +3,11 @@ import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchGet} from "@/utilities/fetch.js"; import {fetchGet} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "navbar", name: "navbar",
components: {LocaleText},
setup(){ setup(){
const wireguardConfigurationsStore = WireguardConfigurationsStore(); const wireguardConfigurationsStore = WireguardConfigurationsStore();
const dashboardConfigurationStore = DashboardConfigurationStore(); const dashboardConfigurationStore = DashboardConfigurationStore();
@ -47,17 +49,19 @@ export default {
<RouterLink class="nav-link rounded-3" <RouterLink class="nav-link rounded-3"
to="/" exact-active-class="active"> to="/" exact-active-class="active">
<i class="bi bi-house me-2"></i> <i class="bi bi-house me-2"></i>
Home</RouterLink></li> <LocaleText t="Home"></LocaleText>
</RouterLink></li>
<li class="nav-item"> <li class="nav-item">
<RouterLink class="nav-link rounded-3" to="/settings" <RouterLink class="nav-link rounded-3" to="/settings"
exact-active-class="active"> exact-active-class="active">
<i class="bi bi-gear me-2"></i> <i class="bi bi-gear me-2"></i>
Settings</RouterLink></li> <LocaleText t="Settings"></LocaleText>
</RouterLink></li>
</ul> </ul>
<hr class="text-body"> <hr class="text-body">
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center"> <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center">
<i class="bi bi-body-text me-2"></i> <i class="bi bi-body-text me-2"></i>
Configurations <LocaleText t="WireGuard Configurations"></LocaleText>
</h6> </h6>
<ul class="nav flex-column px-2"> <ul class="nav flex-column px-2">
<li class="nav-item"> <li class="nav-item">
@ -72,7 +76,7 @@ export default {
<hr class="text-body"> <hr class="text-body">
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center"> <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted text-center">
<i class="bi bi-tools me-2"></i> <i class="bi bi-tools me-2"></i>
Tools <LocaleText t="Tools"></LocaleText>
</h6> </h6>
<ul class="nav flex-column px-2"> <ul class="nav flex-column px-2">
<li class="nav-item"> <li class="nav-item">
@ -87,16 +91,18 @@ export default {
@click="this.dashboardConfigurationStore.signOut()" @click="this.dashboardConfigurationStore.signOut()"
role="button" style="font-weight: bold"> role="button" style="font-weight: bold">
<i class="bi bi-box-arrow-left me-2"></i> <i class="bi bi-box-arrow-left me-2"></i>
Sign Out</a> <LocaleText t="Sign Out"></LocaleText>
</a>
</li> </li>
<li class="nav-item" style="font-size: 0.8rem"> <li class="nav-item" style="font-size: 0.8rem">
<a :href="this.updateUrl" v-if="this.updateAvailable" class="text-decoration-none" target="_blank"> <a :href="this.updateUrl" v-if="this.updateAvailable" class="text-decoration-none" target="_blank">
<small class="nav-link text-muted rounded-3" > <small class="nav-link text-muted rounded-3" >
{{ this.updateMessage }} <LocaleText :t="this.updateMessage"></LocaleText>
</small> </small>
</a> </a>
<small class="nav-link text-muted" v-else> <small class="nav-link text-muted" v-else>
{{ this.updateMessage }} <LocaleText :t="this.updateMessage"></LocaleText>
({{ dashboardConfigurationStore.Configuration.Server.version}})
</small> </small>
</li> </li>
</ul> </ul>

View File

@ -2,9 +2,11 @@
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "accountSettingsInputPassword", name: "accountSettingsInputPassword",
components: {LocaleText},
props:{ props:{
targetData: String, targetData: String,
warning: false, warning: false,
@ -66,6 +68,11 @@ export default {
this.invalidFeedback = "Please fill in all required fields." this.invalidFeedback = "Please fill in all required fields."
} }
} }
},
computed: {
passwordValid(){
return Object.values(this.value).find(x => x.length === 0) === undefined && this.value.newPassword === this.value.repeatNewPassword
}
} }
} }
</script> </script>
@ -76,7 +83,9 @@ export default {
<div class="col-sm"> <div class="col-sm">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label :for="'currentPassword_' + this.uuid" class="text-muted mb-1"> <label :for="'currentPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>Current Password</small></strong> <strong><small>
<LocaleText t="Current Password"></LocaleText>
</small></strong>
</label> </label>
<input type="password" class="form-control mb-2" <input type="password" class="form-control mb-2"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}" :class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
@ -88,7 +97,9 @@ export default {
<div class="col-sm"> <div class="col-sm">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label :for="'newPassword_' + this.uuid" class="text-muted mb-1"> <label :for="'newPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>New Password</small></strong> <strong><small>
<LocaleText t="New Password"></LocaleText>
</small></strong>
</label> </label>
<input type="password" class="form-control mb-2" <input type="password" class="form-control mb-2"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}" :class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
@ -100,7 +111,9 @@ export default {
<div class="col-sm"> <div class="col-sm">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label :for="'repeatNewPassword_' + this.uuid" class="text-muted mb-1"> <label :for="'repeatNewPassword_' + this.uuid" class="text-muted mb-1">
<strong><small>Repeat New Password</small></strong> <strong><small>
<LocaleText t="Repeat New Password"></LocaleText>
</small></strong>
</label> </label>
<input type="password" class="form-control mb-2" <input type="password" class="form-control mb-2"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}" :class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
@ -109,8 +122,11 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" @click="this.useValidation()"> <button
<i class="bi bi-save2-fill me-2"></i>Update Password :disabled="!this.passwordValid"
class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" @click="this.useValidation()">
<i class="bi bi-save2-fill me-2"></i>
<LocaleText t="Update Password"></LocaleText>
</button> </button>
</div> </div>
</template> </template>

View File

@ -2,14 +2,14 @@
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "accountSettingsInputUsername", name: "accountSettingsInputUsername",
components: {LocaleText},
props:{ props:{
targetData: String, targetData: String,
title: String, title: String,
warning: false,
warningText: ""
}, },
setup(){ setup(){
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
@ -62,7 +62,9 @@ export default {
<template> <template>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label :for="this.uuid" class="text-muted mb-1"> <label :for="this.uuid" class="text-muted mb-1">
<strong><small>{{this.title}}</small></strong> <strong><small>
<LocaleText :t="this.title"></LocaleText>
</small></strong>
</label> </label>
<input type="text" class="form-control" <input type="text" class="form-control"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}" :class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
@ -73,11 +75,6 @@ export default {
:disabled="this.updating" :disabled="this.updating"
> >
<div class="invalid-feedback">{{this.invalidFeedback}}</div> <div class="invalid-feedback">{{this.invalidFeedback}}</div>
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1"
v-if="warning"
>
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><span v-html="warningText"></span></small>
</div>
</div> </div>
</template> </template>

View File

@ -2,9 +2,11 @@
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "accountSettingsMFA", name: "accountSettingsMFA",
components: {LocaleText},
setup(){ setup(){
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
const uuid = `input_${v4()}`; const uuid = `input_${v4()}`;
@ -43,7 +45,9 @@ export default {
<template> <template>
<div> <div>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<strong>Multi-Factor Authentication</strong> <strong>
<LocaleText t="Multi-Factor Authentication (MFA)"></LocaleText>
</strong>
<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"
@ -52,7 +56,9 @@ export default {
<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()">
<i class="bi bi-shield-lock-fill me-2"></i> <i class="bi bi-shield-lock-fill me-2"></i>
{{this.store.Configuration.Account["totp_verified"] ? "Reset" : "Setup" }} MFA <LocaleText t="Reset" v-if='this.store.Configuration.Account["totp_verified"]'></LocaleText>
<LocaleText t="Setup" v-else></LocaleText>
MFA
</button> </button>
</div> </div>
</div> </div>

View File

@ -4,10 +4,11 @@ import {v4} from "uuid";
import {fetchGet, fetchPost} from "@/utilities/fetch.js"; import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import NewDashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/newDashboardAPIKey.vue"; import NewDashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/newDashboardAPIKey.vue";
import DashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/dashboardAPIKey.vue"; import DashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/dashboardAPIKey.vue";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "dashboardAPIKeys", name: "dashboardAPIKeys",
components: {DashboardAPIKey, NewDashboardAPIKey}, components: {LocaleText, DashboardAPIKey, NewDashboardAPIKey},
setup(){ setup(){
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
return {store}; return {store};
@ -64,14 +65,17 @@ export default {
<template> <template>
<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 <LocaleText t="API Keys"></LocaleText>
<div class="form-check form-switch ms-auto" v-if="!this.store.getActiveCrossServer()"> <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()"
role="switch" id="allowAPIKeysSwitch"> role="switch" id="allowAPIKeysSwitch">
<label class="form-check-label" for="allowAPIKeysSwitch"> <label class="form-check-label" for="allowAPIKeysSwitch">
{{this.value ? 'Enabled':'Disabled'}} <LocaleText t="Enabled" v-if="this.value"></LocaleText>
<LocaleText t="Disabled" v-else></LocaleText>
</label> </label>
</div> </div>
</div> </div>
@ -80,12 +84,13 @@ export default {
@click="this.newDashboardAPIKey = true" @click="this.newDashboardAPIKey = true"
v-if="!this.store.getActiveCrossServer()" v-if="!this.store.getActiveCrossServer()"
> >
<i class="bi bi-key me-2"></i> Create <i class="bi bi-plus-circle-fill me-2"></i>
<LocaleText t="API Key"></LocaleText>
</button> </button>
<div class="card" style="height: 300px" v-if="this.apiKeys.length === 0"> <div class="card" style="height: 300px" v-if="this.apiKeys.length === 0">
<div class="card-body d-flex text-muted"> <div class="card-body d-flex text-muted">
<span class="m-auto"> <span class="m-auto">
No Dashboard API Key <LocaleText t="No WGDashboard API Key"></LocaleText>
</span> </span>
</div> </div>
</div> </div>

View File

@ -1,9 +1,11 @@
<script> <script>
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "dashboardAPIKey", name: "dashboardAPIKey",
components: {LocaleText},
props: { props: {
apiKey: Object apiKey: Object
}, },
@ -37,12 +39,18 @@ export default {
<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 apiKey-card-body" 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> <small class="text-muted">
<LocaleText t="Key"></LocaleText>
</small>
<span style="word-break: break-all">{{this.apiKey.Key}}</span> <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">
{{this.apiKey.ExpiredAt ? this.apiKey.ExpiredAt : 'Never'}} <LocaleText t="Expire At"></LocaleText>
</small>
<LocaleText t="Never Expire" v-if="!this.apiKey.ExpiredAt"></LocaleText>
<span>{{ this.apiKey.ExpiredAt }}</span>
</div> </div>
<a role="button" class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3" <a role="button" class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3"
v-if="!this.store.getActiveCrossServer()" v-if="!this.store.getActiveCrossServer()"
@ -52,7 +60,7 @@ export default {
</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()"> v-if="!this.store.getActiveCrossServer()">
Are you sure to delete this API key? <LocaleText t="Are you sure to delete this API key?"></LocaleText>
<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()"
> >

View File

@ -3,10 +3,11 @@ import dayjs from "dayjs";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import VueDatePicker from "@vuepic/vue-datepicker"; import VueDatePicker from "@vuepic/vue-datepicker";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "newDashboardAPIKey", name: "newDashboardAPIKey",
components: {VueDatePicker}, components: {LocaleText, VueDatePicker},
data(){ data(){
return{ return{
newKeyData:{ newKeyData:{
@ -58,11 +59,15 @@ export default {
style="background-color: #00000060; backdrop-filter: blur(3px)"> style="background-color: #00000060; backdrop-filter: blur(3px)">
<div class="card m-auto rounded-3 mt-5"> <div class="card m-auto rounded-3 mt-5">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"> <div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
<h6 class="mb-0">Create API Key</h6> <h6 class="mb-0">
<LocaleText t="Create API Key"></LocaleText>
</h6>
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button> <button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
</div> </div>
<div class="card-body d-flex gap-2 p-4 flex-column"> <div class="card-body d-flex gap-2 p-4 flex-column">
<small class="text-muted">When should this API Key expire?</small> <small class="text-muted">
<LocaleText t="When should this API Key expire?"></LocaleText>
</small>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<VueDatePicker <VueDatePicker
:is24="true" :is24="true"
@ -80,7 +85,8 @@ export default {
<input class="form-check-input" type="checkbox" <input class="form-check-input" type="checkbox"
v-model="this.newKeyData.neverExpire" id="neverExpire" :disabled="this.submitting"> v-model="this.newKeyData.neverExpire" id="neverExpire" :disabled="this.submitting">
<label class="form-check-label" for="neverExpire"> <label class="form-check-label" for="neverExpire">
Never Expire (<i class="bi bi-emoji-grimace-fill"></i> Don't think that's a good idea) <LocaleText t="Never Expire"></LocaleText> (<i class="bi bi-emoji-grimace-fill me-2"></i>
<LocaleText t="Don't think that's a good idea"></LocaleText>)
</label> </label>
</div> </div>
<button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" <button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm"
@ -88,7 +94,8 @@ export default {
@click="this.submitNewAPIKey()" @click="this.submitNewAPIKey()"
> >
<i class="bi bi-check-lg me-2" v-if="!this.submitting"></i> <i class="bi bi-check-lg me-2" v-if="!this.submitting"></i>
{{this.submitting ? 'Creating...':'Done'}} <LocaleText t="Creating..." v-if="this.submitting"></LocaleText>
<LocaleText t="Create" v-else></LocaleText>
</button> </button>
</div> </div>
</div> </div>

View File

@ -2,9 +2,12 @@
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "dashboardSettingsInputWireguardConfigurationPath", name: "dashboardSettingsInputWireguardConfigurationPath",
components: {LocaleText},
props:{ props:{
targetData: String, targetData: String,
title: String, title: String,
@ -13,8 +16,9 @@ export default {
}, },
setup(){ setup(){
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
const WireguardConfigurationStore = WireguardConfigurationsStore()
const uuid = `input_${v4()}`; const uuid = `input_${v4()}`;
return {store, uuid}; return {store, uuid, WireguardConfigurationStore};
}, },
data(){ data(){
return{ return{
@ -33,6 +37,7 @@ export default {
methods:{ methods:{
async useValidation(){ async useValidation(){
if(this.changed){ if(this.changed){
this.updating = true;
await fetchPost("/api/updateDashboardConfigurationItem", { await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server", section: "Server",
key: this.targetData, key: this.targetData,
@ -44,6 +49,8 @@ export default {
this.store.Configuration.Account[this.targetData] = this.value this.store.Configuration.Account[this.targetData] = this.value
clearTimeout(this.timeout) clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.isValid = false, 5000); this.timeout = setTimeout(() => this.isValid = false, 5000);
this.WireguardConfigurationStore.getConfigurations()
this.store.newMessage("Server", "WireGuard configuration path saved", "success")
}else{ }else{
this.isValid = false; this.isValid = false;
this.showInvalidFeedback = true; this.showInvalidFeedback = true;
@ -59,24 +66,39 @@ export default {
</script> </script>
<template> <template>
<div class="form-group mb-2"> <div class="form-group">
<label :for="this.uuid" class="text-muted mb-1"> <label :for="this.uuid" class="text-muted mb-1">
<strong><small>{{this.title}}</small></strong> <strong><small>
<LocaleText :t="this.title"></LocaleText>
</small></strong>
</label> </label>
<input type="text" class="form-control" <div class="d-flex gap-2 align-items-start">
:class="{'is-invalid': this.showInvalidFeedback, 'is-valid': this.isValid}" <div class="flex-grow-1">
:id="this.uuid" <input type="text" class="form-control rounded-3"
v-model="this.value" :class="{'is-invalid': this.showInvalidFeedback, 'is-valid': this.isValid}"
@keydown="this.changed = true" :id="this.uuid"
@blur="this.useValidation()" v-model="this.value"
:disabled="this.updating" @keydown="this.changed = true"
> :disabled="this.updating"
<div class="invalid-feedback">{{this.invalidFeedback}}</div> >
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1" <div class="invalid-feedback fw-bold">{{this.invalidFeedback}}</div>
</div>
<button
@click="this.useValidation()"
:disabled="!this.changed"
class="ms-auto btn rounded-3 border-success-subtle bg-success-subtle text-success-emphasis">
<i class="bi bi-save2-fill" v-if="!this.updating"></i>
<span class="spinner-border spinner-border-sm" v-else></span>
</button>
</div>
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1 mb-2"
v-if="warning" v-if="warning"
> >
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><span v-html="warningText"></span></small> <small><i class="bi bi-exclamation-triangle-fill me-2"></i>
<LocaleText :t="warningText"></LocaleText>
</small>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,9 +1,11 @@
<script> <script>
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "dashboardTheme", name: "dashboardTheme",
components: {LocaleText},
setup(){ setup(){
const dashboardConfigurationStore = DashboardConfigurationStore(); const dashboardConfigurationStore = DashboardConfigurationStore();
return {dashboardConfigurationStore} return {dashboardConfigurationStore}
@ -26,19 +28,21 @@ export default {
<template> <template>
<div class="card mb-4 shadow rounded-3"> <div class="card mb-4 shadow rounded-3">
<p class="card-header">Dashboard Theme</p> <p class="card-header">
<LocaleText t="Dashboard Theme"></LocaleText>
</p>
<div class="card-body d-flex gap-2"> <div class="card-body d-flex gap-2">
<button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1" <button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1"
@click="this.switchTheme('light')" @click="this.switchTheme('light')"
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'light'}"> :class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'light'}">
<i class="bi bi-sun-fill"></i> <i class="bi bi-sun-fill me-2"></i>
Light <LocaleText t="Light"></LocaleText>
</button> </button>
<button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1" <button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1"
@click="this.switchTheme('dark')" @click="this.switchTheme('dark')"
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'dark'}"> :class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'dark'}">
<i class="bi bi-moon-fill"></i> <i class="bi bi-moon-fill me-2"></i>
Dark <LocaleText t="Dark"></LocaleText>
</button> </button>
</div> </div>
</div> </div>

View File

@ -2,8 +2,10 @@
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {v4} from "uuid"; import {v4} from "uuid";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchPost} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
components: {LocaleText},
props:{ props:{
targetData: String, targetData: String,
title: String, title: String,
@ -61,7 +63,9 @@ export default {
<template> <template>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label :for="this.uuid" class="text-muted mb-1"> <label :for="this.uuid" class="text-muted mb-1">
<strong><small>{{this.title}}</small></strong> <strong><small>
<LocaleText :t="this.title"></LocaleText>
</small></strong>
</label> </label>
<input type="text" class="form-control" <input type="text" class="form-control"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}" :class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
@ -75,7 +79,9 @@ export default {
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1" <div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1"
v-if="warning" v-if="warning"
> >
<small><i class="bi bi-exclamation-triangle-fill me-2"></i><span v-html="warningText"></span></small> <small><i class="bi bi-exclamation-triangle-fill me-2"></i>
<LocaleText :t="warningText"></LocaleText>
</small>
</div> </div>
</div> </div>
</template> </template>

View File

@ -0,0 +1,30 @@
<script>
import {GetLocale} from "@/utilities/locale.js";
export default {
name: "signInInput",
methods: {GetLocale},
props: {
id: "",
data: "",
type: "",
placeholder: ""
},
computed: {
getLocaleText(){
return GetLocale(this.placeholder)
}
}
}
</script>
<template>
<input :type="type" v-model="this.data[this.id]" class="form-control"
:id="this.id" :name="this.id"
autocomplete="on"
:placeholder="this.getLocaleText" required>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,29 @@
<script>
import {GetLocale} from "@/utilities/locale.js";
export default {
name: "signInTOTP",
methods: {GetLocale},
props: {
data: "",
},
computed: {
getLocaleText(){
return GetLocale(this.placeholder)
}
}
}
</script>
<template>
<input class="form-control totp"
required
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
:placeholder="this.getLocaleText('OTP from your authenticator')"
v-model="this.data.totp"
>
</template>
<style scoped>
</style>

View File

@ -1,5 +1,6 @@
<script> <script>
import dayjs from "dayjs"; import dayjs from "dayjs";
import {GetLocale} from "@/utilities/locale.js";
export default { export default {
name: "RemoteServer", name: "RemoteServer",
@ -73,7 +74,7 @@ export default {
return `${dayjs().subtract(this.startTime).millisecond()}ms` return `${dayjs().subtract(this.startTime).millisecond()}ms`
}else{ }else{
if (this.refreshing){ if (this.refreshing){
return `Pinging...` return GetLocale(`Pinging...`)
} }
return this.errorMsg ? this.errorMsg : "N/A" return this.errorMsg ? this.errorMsg : "N/A"
} }

View File

@ -1,6 +1,7 @@
<script> <script>
import RemoteServer from "@/components/signInComponents/RemoteServer.vue"; import RemoteServer from "@/components/signInComponents/RemoteServer.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "RemoteServerList", name: "RemoteServerList",
@ -8,18 +9,21 @@ export default {
const store = DashboardConfigurationStore(); const store = DashboardConfigurationStore();
return {store} return {store}
}, },
components: {RemoteServer} components: {LocaleText, RemoteServer}
} }
</script> </script>
<template> <template>
<div class="w-100 mt-3"> <div class="w-100 mt-3">
<div class="d-flex align-items-center mb-3"> <div class="d-flex align-items-center mb-3">
<h5 class="mb-0">Server List</h5> <h5 class="mb-0">
<LocaleText t="Server List"></LocaleText>
</h5>
<button <button
@click="this.store.addCrossServerConfiguration()" @click="this.store.addCrossServerConfiguration()"
class="btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle shadow-sm ms-auto"> 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 <i class="bi bi-plus-circle-fill me-2"></i>
<LocaleText t="Server"></LocaleText>
</button> </button>
</div> </div>
<div class="w-100 d-flex gap-3 flex-column p-3 border border-1 border-secondary-subtle rounded-3" <div class="w-100 d-flex gap-3 flex-column p-3 border border-1 border-secondary-subtle rounded-3"
@ -30,7 +34,10 @@ export default {
:key="key" :key="key"
:server="server"></RemoteServer> :server="server"></RemoteServer>
<h6 class="text-muted m-auto" v-if="Object.keys(this.store.CrossServerConfiguration.ServerList).length === 0"> <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> <LocaleText t="Click"></LocaleText>
<i class="bi bi-plus-circle-fill mx-1"></i>
<LocaleText t="to add your server"></LocaleText>
</h6>
</div> </div>
</div> </div>
</template> </template>

View File

@ -0,0 +1,23 @@
<script>
import {GetLocale} from "@/utilities/locale.js";
export default {
name: "localeText",
props: {
t: ""
},
computed: {
getLocaleText(){
return GetLocale(this.t)
}
}
}
</script>
<template>
{{ this.getLocaleText }}
</template>
<style scoped>
</style>

View File

@ -10,17 +10,24 @@ import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchGet} from "@/utilities/fetch.js";
let Locale;
await fetch("/api/locale").then(res => res.json()).then(res => Locale = JSON.parse(res.data))
const app = createApp(App) const app = createApp(App)
app.use(router)
const pinia = createPinia();
app.use(router)
const pinia = createPinia();
pinia.use(({ store }) => { pinia.use(({ store }) => {
store.$router = markRaw(router) store.$router = markRaw(router)
}) })
app.use(pinia) app.use(pinia)
const store = DashboardConfigurationStore()
window.Locale = Locale;
app.mount('#app') app.mount('#app')

View File

@ -28,7 +28,6 @@ const checkAuth = async () => {
const router = createRouter({ const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
routes: [ routes: [
{ {
name: "Index", name: "Index",
path: '/', path: '/',

View File

@ -1,6 +1,7 @@
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import {fetchGet, fetchPost} from "@/utilities/fetch.js"; import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {v4} from "uuid"; import {v4} from "uuid";
import {GetLocale} from "@/utilities/locale.js";
export const DashboardConfigurationStore = defineStore('DashboardConfigurationStore', { export const DashboardConfigurationStore = defineStore('DashboardConfigurationStore', {
state: () => ({ state: () => ({
@ -17,7 +18,8 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
}, },
ActiveServerConfiguration: undefined, ActiveServerConfiguration: undefined,
IsElectronApp: false, IsElectronApp: false,
ShowNavBar: false ShowNavBar: false,
Locale: undefined
}), }),
actions: { actions: {
initCrossServerConfiguration(){ initCrossServerConfiguration(){
@ -57,19 +59,11 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
this.ActiveServerConfiguration = undefined; this.ActiveServerConfiguration = undefined;
localStorage.removeItem('ActiveCrossServerConfiguration') 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
}); });
}, },
// async updateConfiguration(){
// await fetchPost("/api/updateDashboardConfiguration", {
// DashboardConfiguration: this.Configuration
// }, (res) => {
// console.log(res)
// })
// },
async signOut(){ async signOut(){
await fetchGet("/api/signout", {}, (res) => { await fetchGet("/api/signout", {}, (res) => {
this.removeActiveCrossServer(); this.removeActiveCrossServer();
@ -79,11 +73,25 @@ export const DashboardConfigurationStore = defineStore('DashboardConfigurationSt
newMessage(from, content, type){ newMessage(from, content, type){
this.Messages.push({ this.Messages.push({
id: v4(), id: v4(),
from: from, from: GetLocale(from),
content: content, content: GetLocale(content),
type: type, type: type,
show: true show: true
}) })
},
applyLocale(key){
if (this.Locale === null)
return key
const reg = Object.keys(this.Locale)
const match = reg.filter(x => {
return key.match(new RegExp('^' + x + '$', 'g')) !== null
})
console.log(match)
if (match.length === 0 || match.length > 1){
return key
}
return this.Locale[match[0]]
} }
} }
}); });

View File

@ -1,6 +1,7 @@
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import {fetchGet} from "@/utilities/fetch.js"; import {fetchGet} from "@/utilities/fetch.js";
import isCidr from "is-cidr"; import isCidr from "is-cidr";
import {GetLocale} from "@/utilities/locale.js";
export const WireguardConfigurationsStore = defineStore('WireguardConfigurationsStore', { export const WireguardConfigurationsStore = defineStore('WireguardConfigurationsStore', {
state: () => ({ state: () => ({
@ -11,54 +12,54 @@ export const WireguardConfigurationsStore = defineStore('WireguardConfigurations
dropdowns: { dropdowns: {
Field: [ Field: [
{ {
display: "Total Received", display: GetLocale("Total Received"),
value: "total_receive", value: "total_receive",
unit: "GB", unit: "GB",
type: 'number' type: 'number'
}, },
{ {
display: "Total Sent", display: GetLocale("Total Sent"),
value: "total_sent", value: "total_sent",
unit: "GB", unit: "GB",
type: 'number' type: 'number'
}, },
{ {
display: "Total Data", display: GetLocale("Total Usage"),
value: "total_data", value: "total_data",
unit: "GB", unit: "GB",
type: 'number' type: 'number'
}, },
{ {
display: "Date", display: GetLocale("Date"),
value: "date", value: "date",
type: 'date' type: 'date'
} }
], ],
Operator: [ Operator: [
// {
// display: "equal",
// value: "eq"
// },
// {
// display: "not equal",
// value: "neq"
// },
{ {
display: "equal", display: GetLocale("larger than"),
value: "eq"
},
{
display: "not equal",
value: "neq"
},
{
display: "larger than",
value: "lgt" value: "lgt"
}, },
{ // {
display: "less than", // display: "less than",
value: "lst" // value: "lst"
}, // },
], ],
Action: [ Action: [
{ {
display: "Restrict Peer", display: GetLocale("Restrict Peer"),
value: "restrict" value: "restrict"
}, },
{ {
display: "Delete Peer", display: GetLocale("Delete Peer"),
value: "delete" value: "delete"
} }
] ]

View File

@ -0,0 +1,12 @@
export const GetLocale = (key) => {
if (window.Locale === null)
return key
const reg = Object.keys(window.Locale)
const match = reg.filter(x => {
return key.match(new RegExp('^' + x + '$', 'gi')) !== null
})
if (match.length === 0 || match.length > 1){
return key
}
return key.replace(new RegExp(match[0], 'gi'), window.Locale[match[0]])
}

View File

@ -12,11 +12,13 @@ import DashboardSettingsInputIPAddressAndPort
from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue"; from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue";
import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vue"; import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vue";
import AccountSettingsMFA from "@/components/settingsComponent/accountSettingsMFA.vue"; import AccountSettingsMFA from "@/components/settingsComponent/accountSettingsMFA.vue";
import LocaleText from "@/components/text/localeText.vue";
export default { export default {
name: "settings", name: "settings",
methods: {ipV46RegexCheck}, methods: {ipV46RegexCheck},
components: { components: {
LocaleText,
AccountSettingsMFA, AccountSettingsMFA,
DashboardAPIKeys, DashboardAPIKeys,
DashboardSettingsInputIPAddressAndPort, DashboardSettingsInputIPAddressAndPort,
@ -27,24 +29,20 @@ export default {
const dashboardConfigurationStore = DashboardConfigurationStore() const dashboardConfigurationStore = DashboardConfigurationStore()
return {dashboardConfigurationStore} return {dashboardConfigurationStore}
}, },
watch: {
// 'dashboardConfigurationStore.Configuration': {
// deep: true,
// handler(){
// this.dashboardConfigurationStore.updateConfiguration();
// }
// }
}
} }
</script> </script>
<template> <template>
<div class="mt-md-5 mt-3"> <div class="mt-md-5 mt-3">
<div class="container-md"> <div class="container-md">
<h3 class="mb-3 text-body">Settings</h3> <h3 class="mb-3 text-body">
<LocaleText t="Settings"></LocaleText>
</h3>
<DashboardTheme></DashboardTheme> <DashboardTheme></DashboardTheme>
<div class="card mb-4 shadow rounded-3"> <div class="card mb-4 shadow rounded-3">
<p class="card-header">Peers Default Settings</p> <p class="card-header">
<LocaleText t="Peers Default Settings"></LocaleText>
</p>
<div class="card-body"> <div class="card-body">
<PeersDefaultSettingsInput targetData="peer_global_dns" title="DNS"></PeersDefaultSettingsInput> <PeersDefaultSettingsInput targetData="peer_global_dns" title="DNS"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_endpoint_allowed_ip" title="Peer Endpoint Allowed IPs"></PeersDefaultSettingsInput> <PeersDefaultSettingsInput targetData="peer_endpoint_allowed_ip" title="Peer Endpoint Allowed IPs"></PeersDefaultSettingsInput>
@ -56,19 +54,23 @@ export default {
</div> </div>
</div> </div>
<div class="card mb-4 shadow rounded-3"> <div class="card mb-4 shadow rounded-3">
<p class="card-header">WireGuard Configurations Settings</p> <p class="card-header">
<LocaleText t="WireGuard Configurations Settings"></LocaleText>
</p>
<div class="card-body"> <div class="card-body">
<DashboardSettingsInputWireguardConfigurationPath <DashboardSettingsInputWireguardConfigurationPath
targetData="wg_conf_path" targetData="wg_conf_path"
title="Configurations Directory" title="Configurations Directory"
:warning="true" :warning="true"
warning-text="Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code>" warning-text="Remember to remove / at the end of your path. e.g /etc/wireguard"
> >
</DashboardSettingsInputWireguardConfigurationPath> </DashboardSettingsInputWireguardConfigurationPath>
</div> </div>
</div> </div>
<div class="card mb-4 shadow rounded-3"> <div class="card mb-4 shadow rounded-3">
<p class="card-header">Account Settings</p> <p class="card-header">
<LocaleText t="WGDashboard Account Settings"></LocaleText>
</p>
<div class="card-body d-flex gap-4 flex-column"> <div class="card-body d-flex gap-4 flex-column">
<AccountSettingsInputUsername targetData="username" <AccountSettingsInputUsername targetData="username"
title="Username" title="Username"

View File

@ -3,10 +3,14 @@ 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"; import RemoteServerList from "@/components/signInComponents/RemoteServerList.vue";
import {GetLocale} from "@/utilities/locale.js";
import LocaleText from "@/components/text/localeText.vue";
import SignInInput from "@/components/signIn/signInInput.vue";
import SignInTOTP from "@/components/signIn/signInTOTP.vue";
export default { export default {
name: "signin", name: "signin",
components: {RemoteServerList, Message}, components: {SignInTOTP, SignInInput, LocaleText, RemoteServerList, Message},
async setup(){ async setup(){
const store = DashboardConfigurationStore() const store = DashboardConfigurationStore()
let theme = "dark" let theme = "dark"
@ -22,6 +26,7 @@ export default {
}), }),
fetchGet("/api/getDashboardVersion", {}, (res) => { fetchGet("/api/getDashboardVersion", {}, (res) => {
version = res.data version = res.data
}) })
]); ]);
} }
@ -30,9 +35,11 @@ export default {
}, },
data(){ data(){
return { return {
username: "", data: {
password: "", username: "",
totp: "", password: "",
totp: "",
},
loginError: false, loginError: false,
loginErrorMessage: "", loginErrorMessage: "",
loading: false loading: false
@ -41,17 +48,17 @@ export default {
computed: { computed: {
getMessages(){ getMessages(){
return this.store.Messages.filter(x => x.show) return this.store.Messages.filter(x => x.show)
},
applyLocale(key){
return GetLocale(key)
} }
}, },
methods: { methods: {
GetLocale,
async auth(){ async auth(){
if (this.username && this.password && ((this.totpEnabled && this.totp) || !this.totpEnabled)){ if (this.data.username && this.data.password && ((this.totpEnabled && this.data.totp) || !this.totpEnabled)){
this.loading = true this.loading = true
await fetchPost("/api/authenticate", { await fetchPost("/api/authenticate", this.data, (response) => {
username: this.username,
password: this.password,
totp: this.totp
}, (response) => {
if (response.status){ if (response.status){
this.loginError = false; this.loginError = false;
this.$refs["signInBtn"].classList.add("signedIn") this.$refs["signInBtn"].classList.add("signedIn")
@ -97,45 +104,38 @@ export default {
:data-bs-theme="this.theme"> :data-bs-theme="this.theme">
<div class="login-box m-auto" > <div class="login-box m-auto" >
<div class="m-auto" style="width: 700px;"> <div class="m-auto" style="width: 700px;">
<h4 class="mb-0 text-body">Welcome to</h4> <h4 class="mb-0 text-body">
<LocaleText t="Welcome to"></LocaleText>
</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}} <LocaleText :t="this.loginErrorMessage"></LocaleText>
</div> </div>
<form @submit="(e) => {e.preventDefault(); this.auth();}" <form @submit="(e) => {e.preventDefault(); this.auth();}"
v-if="!this.store.CrossServerConfiguration.Enable"> 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>
<input type="text" v-model="username" class="form-control" id="username" name="username" <SignInInput id="username" :data="this.data"
autocomplete="on" type="text" placeholder="Username"></SignInInput>
placeholder="Username" required>
</div> </div>
<div class="form-group text-body"> <div class="form-group text-body">
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label> <label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
<input type="password" <SignInInput id="password" :data="this.data"
v-model="password" class="form-control" id="password" name="password" type="password" placeholder="Password"></SignInInput>
autocomplete="on"
placeholder="Password" required>
</div> </div>
<div class="form-group text-body" v-if="totpEnabled"> <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> <label for="totp" class="text-left" style="font-size: 1rem"><i class="bi bi-lock-fill"></i></label>
<input class="form-control totp" <SignInTOTP :data="this.data"></SignInTOTP>
required
id="totp" maxlength="6" type="text" inputmode="numeric" autocomplete="one-time-code"
placeholder="OTP from your authenticator"
v-model="this.totp"
>
</div> </div>
<button class="btn btn-lg btn-dark ms-auto mt-4 w-100 d-flex btn-brand signInBtn" ref="signInBtn"> <button class="btn btn-lg btn-dark ms-auto mt-4 w-100 d-flex btn-brand signInBtn" ref="signInBtn">
<span v-if="!this.loading" class="d-flex w-100"> <span v-if="!this.loading" class="d-flex w-100">
Sign In<i class="ms-auto bi bi-chevron-right"></i> <LocaleText t="Sign In"></LocaleText>
<i class="ms-auto bi bi-chevron-right"></i>
</span> </span>
<span v-else class="d-flex w-100 align-items-center"> <span v-else class="d-flex w-100 align-items-center">
Signing In... <LocaleText t="Signing In..."></LocaleText>
<span class="spinner-border ms-auto spinner-border-sm" role="status"> <span class="spinner-border ms-auto spinner-border-sm" role="status"></span>
<span class="visually-hidden">Loading...</span>
</span>
</span> </span>
</button> </button>
</form> </form>
@ -146,7 +146,9 @@ export default {
<input <input
v-model="this.store.CrossServerConfiguration.Enable" v-model="this.store.CrossServerConfiguration.Enable"
class="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckChecked"> class="form-check-input" type="checkbox" role="switch" id="flexSwitchCheckChecked">
<label class="form-check-label" for="flexSwitchCheckChecked">Access Remote Server</label> <label class="form-check-label" for="flexSwitchCheckChecked">
<LocaleText t="Access Remote Server"></LocaleText>
</label>
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,6 +21,7 @@ export default defineConfig(({mode}) => {
} }
}, },
build: { build: {
target: "es2022",
outDir: '../../../../WGDashboard-Desktop', outDir: '../../../../WGDashboard-Desktop',
rollupOptions: { rollupOptions: {
output: { output: {
@ -50,6 +51,7 @@ export default defineConfig(({mode}) => {
host: '0.0.0.0' host: '0.0.0.0'
}, },
build: { build: {
target: "es2022",
outDir: 'dist', outDir: 'dist',
rollupOptions: { rollupOptions: {
output: { output: {

View File

@ -0,0 +1,14 @@
[
{
"lang_id": "en",
"lang_name": "English"
},
{
"lang_id": "zh-CN",
"lang_name": "Chinese (Simplified)"
},
{
"lang_id": "zh-HK",
"lang_name": "Chinese (Traditional)"
}
]

View File

@ -0,0 +1,10 @@
{
"WireGuard Configurations": {
"zh-CN": "WireGuard 配置",
"zh-HK": "WireGuard 配置"
},
"Configuration": {
"zh-CN": "配置",
"zh-HK": "配置"
}
}

View File

@ -0,0 +1,134 @@
{
"Welcome to": "欢迎来到",
"Username": "用户名",
"Password": "密码",
"OTP from your authenticator": "您双因素身份验证的一次性验证码",
"Sign In": "登录",
"Signing In\\.\\.\\.": "正在登录...",
"Access Remote Server": "访问远程服务器",
"Server": "服务器",
"Click": "点击",
"Pinging...": "尝试连接中...",
"to add your server": "添加您的服务器",
"Server List": "服务器列表",
"Sorry, your username or password is incorrect.": "对不起,您的用户名或密码不正确",
"Home": "主页",
"Settings": "设定",
"Tools": "工具箱",
"Sign Out": "退出登录",
"Checking for update...": "正在检查是否有新版本...",
"You're on the latest version": "已经是最新版本",
"WireGuard Configurations": "WireGuard 配置",
"You don't have any WireGuard configurations yet. Please check the configuration folder or change it in Settings. By default the folder is /etc/wireguard.": "您还没有任何WireGuard配置。请检查您的配置文件夹或前往设置更改路径。默认文件夹是 /etc/wireguard",
"Configuration": "配置",
"Configurations": "配置",
"Peers Default Settings": "端点默认设置",
"Dashboard Theme": "面板主题",
"Light": "简约白",
"Dark": "简约黑",
"This will be changed globally, and will be apply to all peer's QR code and configuration file.": "更改这个设定会应用到所有端点的配置文件和配置二维码",
"WireGuard Configurations Settings": "WireGuard 配置设定",
"Configurations Directory": "配置文件路径",
"Remember to remove / at the end of your path. e.g /etc/wireguard": "请把路径最后的 /(左斜杠)移除,例如:/etc/wireguard",
"WGDashboard Account Settings": "WGDashboard 账户设定",
"Current Password": "当前密码",
"New Password": "新密码",
"Repeat New Password": "重复新密码",
"Update Password": "更新密码",
"Multi-Factor Authentication \\(MFA\\)": "多重身份验证MFA)",
"Reset": "重置",
"Setup": "设置",
"API Keys": "API 秘钥",
"API Key": "API 秘钥",
"Key": "秘钥",
"Enabled": "已启用",
"Disabled": "已停用",
"No WGDashboard API Key": "没有 WGDashboard API 秘钥",
"Expire At": "过期于",
"Are you sure to delete this API key\\?": "确定删除此 API 秘钥?",
"Create API Key": "创建 API 秘钥",
"When should this API Key expire\\?": "这个 API 秘钥什么时候过期呢?",
"Never Expire": "从不过期",
"Don't think that's a good idea": "我不觉得这是一个好主意",
"Creating\\.\\.\\.": "创建中...",
"Create": "创建",
"Status": "状态",
"On": "已启用",
"Off": "已停用",
"Address": "网络地址",
"Listen Port": "监听端口",
"Public Key": "公钥",
"Connected Peers": "已连接端点",
"Total Usage": "总数据用量",
"Total Received": "总接收数据用量",
"Total Sent": "总发送数据用量",
"Peers Data Usage": "端点的数据用量",
"Real Time Received Data Usage": "实时接收数据量",
"Real Time Sent Data Usage": "实时发送数据量",
"Peer": "端点",
"Peer Settings": "端点设定",
"Download All": "全部下载",
"Search Peers\\.\\.\\.": "搜索端点...",
"Display": "显示设置",
"Sort By": "排列方式",
"Refresh Interval": "刷新间隔",
"Name": "名字",
"Allowed IPs": "允许的 IP 地址",
"Restricted": "已限制端点",
"(.*) Seconds": "$1 秒",
"(.*) Minutes": "$1 分钟",
"Configuration Settings": "配置设定",
"Peer Jobs": "端点任务",
"Active Jobs": "未运行任务",
"All Active Jobs": "所有未运行任务",
"Logs": "日志",
"Private Key": "秘钥",
"\\(Required for QR Code and Download\\)": "(二维码以及下载功能需要填写秘钥)",
"\\(Required\\)": "(必填项)",
"Endpoint Allowed IPs": "终结点允许的 IP 地址",
"DNS": "域名系统DNS",
"Optional Settings": "可选设定",
"Pre-Shared Key": "共享秘钥",
"MTU": "最大传输单元",
"Persistent Keepalive": "持久保持活动",
"Reset Data Usage": "重置数据用量",
"Total": "总数据",
"Sent": "发送数据",
"Received": "接收数据",
"Revert": "撤销更改",
"Save Peer": "保存端点",
"QR Code": "二维码",
"Schedule Jobs": "计划任务",
"Job": "任务",
"Job ID": "任务 ID",
"Unsaved Job": "未保存任务",
"This peer does not have any job yet\\.": "此端点还没有任何任务",
"if": "如果",
"is": "是",
"then": "那就",
"larger than": "大于",
"Date": "日期",
"Restrict Peer": "限制端点",
"Delete Peer": "删除端点",
"Edit": "编辑",
"Delete": "删除",
"Cancel": "取消",
"Save": "保存",
"No active job at the moment\\.": "没有未运行的任务",
"Jobs Logs": "任务日志",
"Updated at": "更新于",
"Refresh": "刷新",
"Filter": "筛选",
"Success": "成功",
"Failed": "失败",
"Log ID": "任务 ID",
"Message": "消息",
"Share Peer": "分享端点",
"Currently the peer is not sharing": "此端点未被共享",
"Sharing\\.\\.\\.": "分享中...",
"Start Sharing": "开始分享",
"Stop Sharing\\.\\.\\.": "停止分享中...",
"Stop Sharing": "停止分享",
"Access Restricted": "已限制访问",
"Download \\& QR Code is not available due to no private key set for this peer": "下载以及二维码功能不可用,需要填写此端点的秘钥"
}