Merge pull request #538 from donaldzou/v4.2-dev-amneziawg-support

Add AmneziaWG Support
This commit is contained in:
Donald Zou 2024-12-05 01:52:35 +08:00 committed by GitHub
commit 000978e4fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 882 additions and 250 deletions

View File

@ -445,7 +445,7 @@ class WireguardConfiguration:
def __str__(self): def __str__(self):
return self.message return self.message
def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False): def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False, wg: bool = True):
self.__parser: configparser.ConfigParser = configparser.ConfigParser(strict=False) self.__parser: configparser.ConfigParser = configparser.ConfigParser(strict=False)
@ -467,24 +467,25 @@ class WireguardConfiguration:
self.PostDown: str = "" self.PostDown: str = ""
self.SaveConfig: bool = True self.SaveConfig: bool = True
self.Name = name self.Name = name
self.__configPath = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf') self.Protocol = "wg" if wg else "awg"
self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf') if wg else os.path.join(DashboardConfig.GetConfig("Server", "awg_conf_path")[1], f'{self.Name}.conf')
if name is not None: if name is not None:
if data is not None and "Backup" in data.keys(): if data is not None and "Backup" in data.keys():
db = self.__importDatabase( db = self.__importDatabase(
os.path.join( os.path.join(
DashboardConfig.GetConfig("Server", "wg_conf_path")[1], self.__getProtocolPath(),
'WGDashboard_Backup', 'WGDashboard_Backup',
data["Backup"].replace(".conf", ".sql"))) data["Backup"].replace(".conf", ".sql")))
else: else:
self.__createDatabase() self.createDatabase()
self.__parseConfigurationFile() self.__parseConfigurationFile()
self.__initPeersList() self.__initPeersList()
else: else:
self.Name = data["ConfigurationName"] self.Name = data["ConfigurationName"]
self.__configPath = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf') self.configPath = os.path.join(self.__getProtocolPath(), f'{self.Name}.conf')
for i in dir(self): for i in dir(self):
if str(i) in data.keys(): if str(i) in data.keys():
@ -504,10 +505,23 @@ class WireguardConfiguration:
"SaveConfig": "true" "SaveConfig": "true"
} }
if self.Protocol == 'awg':
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jc"] = self.Jc
self.__parser["Interface"]["Jmin"] = self.Jmin
self.__parser["Interface"]["Jmax"] = self.Jmax
self.__parser["Interface"]["S1"] = self.S1
self.__parser["Interface"]["S2"] = self.S2
self.__parser["Interface"]["H1"] = self.H1
self.__parser["Interface"]["H2"] = self.H2
self.__parser["Interface"]["H3"] = self.H3
self.__parser["Interface"]["H4"] = self.H4
if "Backup" not in data.keys(): if "Backup" not in data.keys():
self.__createDatabase() self.createDatabase()
with open(self.__configPath, "w+") as configFile: with open(self.configPath, "w+") as configFile:
self.__parser.write(configFile) self.__parser.write(configFile)
print(f"[WGDashboard] Configuration file {self.configPath} created")
self.__initPeersList() self.__initPeersList()
print(f"[WGDashboard] Initialized Configuration: {name}") print(f"[WGDashboard] Initialized Configuration: {name}")
@ -515,13 +529,17 @@ class WireguardConfiguration:
self.toggleConfiguration() self.toggleConfiguration()
print(f"[WGDashboard] Autostart Configuration: {name}") print(f"[WGDashboard] Autostart Configuration: {name}")
def __getProtocolPath(self):
return DashboardConfig.GetConfig("Server", "wg_conf_path")[1] if self.Protocol == "wg" \
else DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
def __initPeersList(self): def __initPeersList(self):
self.Peers: list[Peer] = [] self.Peers: list[Peer] = []
self.getPeersList() self.getPeersList()
self.getRestrictedPeersList() self.getRestrictedPeersList()
def __parseConfigurationFile(self): def __parseConfigurationFile(self):
with open(self.__configPath, 'r') as f: with open(self.configPath, 'r') as f:
original = [l.rstrip("\n") for l in f.readlines()] original = [l.rstrip("\n") for l in f.readlines()]
try: try:
start = original.index("[Interface]") start = original.index("[Interface]")
@ -557,7 +575,7 @@ class WireguardConfiguration:
setattr(self, key, value) setattr(self, key, value)
except ValueError as e: except ValueError as e:
raise self.InvalidConfigurationFileException( raise self.InvalidConfigurationFileException(
"[Interface] section not found in " + self.__configPath) "[Interface] section not found in " + self.configPath)
if self.PrivateKey: if self.PrivateKey:
self.PublicKey = self.__getPublicKey() self.PublicKey = self.__getPublicKey()
self.Status = self.getStatus() self.Status = self.getStatus()
@ -569,7 +587,7 @@ class WireguardConfiguration:
existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall() existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
def __createDatabase(self, dbName = None): def createDatabase(self, dbName = None):
if dbName is None: if dbName is None:
dbName = self.Name dbName = self.Name
@ -640,7 +658,7 @@ class WireguardConfiguration:
def __importDatabase(self, sqlFilePath) -> bool: def __importDatabase(self, sqlFilePath) -> bool:
self.__dropDatabase() self.__dropDatabase()
self.__createDatabase() self.createDatabase()
if not os.path.exists(sqlFilePath): if not os.path.exists(sqlFilePath):
return False return False
with open(sqlFilePath, 'r') as f: with open(sqlFilePath, 'r') as f:
@ -661,22 +679,22 @@ class WireguardConfiguration:
s, d = DashboardConfig.GetConfig("WireGuardConfiguration", "autostart") s, d = DashboardConfig.GetConfig("WireGuardConfiguration", "autostart")
return self.Name in d return self.Name in d
def __getRestrictedPeers(self): def getRestrictedPeers(self):
self.RestrictedPeers = [] self.RestrictedPeers = []
restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall() restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall()
for i in restricted: for i in restricted:
self.RestrictedPeers.append(Peer(i, self)) self.RestrictedPeers.append(Peer(i, self))
def configurationFileChanged(self) : def configurationFileChanged(self) :
mt = os.path.getmtime(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')) mt = os.path.getmtime(self.configPath)
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
def __getPeers(self): def getPeers(self):
if self.configurationFileChanged(): if self.configurationFileChanged():
self.Peers = [] self.Peers = []
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'r') as configFile: with open(self.configPath, 'r') as configFile:
p = [] p = []
pCounter = -1 pCounter = -1
content = configFile.read().split('\n') content = configFile.read().split('\n')
@ -791,12 +809,12 @@ class WireguardConfiguration:
with open(uid, "w+") as f: with open(uid, "w+") as f:
f.write(p['preshared_key']) f.write(p['preshared_key'])
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist: if presharedKeyExist:
os.remove(uid) os.remove(uid)
subprocess.check_output( subprocess.check_output(
f"wg-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
self.getPeersList() self.getPeersList()
return True return True
except Exception as e: except Exception as e:
@ -828,7 +846,7 @@ class WireguardConfiguration:
with open(uid, "w+") as f: with open(uid, "w+") as f:
f.write(p['preshared_key']) f.write(p['preshared_key'])
subprocess.check_output(f"wg set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}", subprocess.check_output(f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist: os.remove(uid) if presharedKeyExist: os.remove(uid)
else: else:
@ -836,7 +854,7 @@ class WireguardConfiguration:
if not self.__wgSave(): if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard") return ResponseObject(False, "Failed to save configuration through WireGuard")
self.__getPeers() self.getPeers()
return ResponseObject(True, "Allow access successfully") return ResponseObject(True, "Allow access successfully")
def restrictPeers(self, listOfPublicKeys): def restrictPeers(self, listOfPublicKeys):
@ -848,7 +866,7 @@ class WireguardConfiguration:
found, pf = self.searchPeer(p) found, pf = self.searchPeer(p)
if found: if found:
try: try:
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove", subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
sqlUpdate("INSERT INTO '%s_restrict_access' SELECT * FROM %s WHERE id = ?" % sqlUpdate("INSERT INTO '%s_restrict_access' SELECT * FROM %s WHERE id = ?" %
(self.Name, self.Name,), (pf.id,)) (self.Name, self.Name,), (pf.id,))
@ -862,7 +880,7 @@ class WireguardConfiguration:
if not self.__wgSave(): if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard") return ResponseObject(False, "Failed to save configuration through WireGuard")
self.__getPeers() self.getPeers()
if numOfRestrictedPeers == len(listOfPublicKeys): if numOfRestrictedPeers == len(listOfPublicKeys):
return ResponseObject(True, f"Restricted {numOfRestrictedPeers} peer(s)") return ResponseObject(True, f"Restricted {numOfRestrictedPeers} peer(s)")
@ -879,7 +897,7 @@ class WireguardConfiguration:
found, pf = self.searchPeer(p) found, pf = self.searchPeer(p)
if found: if found:
try: try:
subprocess.check_output(f"wg set {self.Name} peer {pf.id} remove", subprocess.check_output(f"{self.Protocol} set {self.Name} peer {pf.id} remove",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,)) sqlUpdate("DELETE FROM '%s' WHERE id = ?" % self.Name, (pf.id,))
numOfDeletedPeers += 1 numOfDeletedPeers += 1
@ -889,7 +907,7 @@ class WireguardConfiguration:
if not self.__wgSave(): if not self.__wgSave():
return ResponseObject(False, "Failed to save configuration through WireGuard") return ResponseObject(False, "Failed to save configuration through WireGuard")
self.__getPeers() self.getPeers()
if numOfDeletedPeers == len(listOfPublicKeys): if numOfDeletedPeers == len(listOfPublicKeys):
return ResponseObject(True, f"Deleted {numOfDeletedPeers} peer(s)") return ResponseObject(True, f"Deleted {numOfDeletedPeers} peer(s)")
@ -898,7 +916,7 @@ class WireguardConfiguration:
def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]: def __wgSave(self) -> tuple[bool, str] | tuple[bool, None]:
try: try:
subprocess.check_output(f"wg-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT) subprocess.check_output(f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
return True, None return True, None
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
return False, str(e) return False, str(e)
@ -907,7 +925,7 @@ class WireguardConfiguration:
if not self.getStatus(): if not self.getStatus():
self.toggleConfiguration() self.toggleConfiguration()
try: try:
latestHandshake = subprocess.check_output(f"wg show {self.Name} latest-handshakes", latestHandshake = subprocess.check_output(f"{self.Protocol} show {self.Name} latest-handshakes",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return "stopped" return "stopped"
@ -933,7 +951,7 @@ class WireguardConfiguration:
if not self.getStatus(): if not self.getStatus():
self.toggleConfiguration() self.toggleConfiguration()
try: try:
data_usage = subprocess.check_output(f"wg show {self.Name} transfer", data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} transfer",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
data_usage = data_usage.decode("UTF-8").split("\n") data_usage = data_usage.decode("UTF-8").split("\n")
data_usage = [p.split("\t") for p in data_usage] data_usage = [p.split("\t") for p in data_usage]
@ -974,7 +992,7 @@ class WireguardConfiguration:
if not self.getStatus(): if not self.getStatus():
self.toggleConfiguration() self.toggleConfiguration()
try: try:
data_usage = subprocess.check_output(f"wg show {self.Name} endpoints", data_usage = subprocess.check_output(f"{self.Protocol} show {self.Name} endpoints",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
return "stopped" return "stopped"
@ -989,13 +1007,13 @@ class WireguardConfiguration:
self.getStatus() self.getStatus()
if self.Status: if self.Status:
try: try:
check = subprocess.check_output(f"wg-quick down {self.Name}", check = subprocess.check_output(f"{self.Protocol}-quick down {self.Name}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return False, str(exc.output.strip().decode("utf-8")) return False, str(exc.output.strip().decode("utf-8"))
else: else:
try: try:
check = subprocess.check_output(f"wg-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT) check = subprocess.check_output(f"{self.Protocol}-quick up {self.Name}", shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as exc: except subprocess.CalledProcessError as exc:
return False, str(exc.output.strip().decode("utf-8")) return False, str(exc.output.strip().decode("utf-8"))
self.__parseConfigurationFile() self.__parseConfigurationFile()
@ -1003,11 +1021,11 @@ class WireguardConfiguration:
return True, None return True, None
def getPeersList(self): def getPeersList(self):
self.__getPeers() self.getPeers()
return self.Peers return self.Peers
def getRestrictedPeersList(self) -> list: def getRestrictedPeersList(self) -> list:
self.__getRestrictedPeers() self.getRestrictedPeers()
return self.RestrictedPeers return self.RestrictedPeers
def toJson(self): def toJson(self):
@ -1030,25 +1048,26 @@ class WireguardConfiguration:
"Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers))) "Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers)))
}, },
"ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))), "ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))),
"TotalPeers": len(self.Peers) "TotalPeers": len(self.Peers),
"Protocol": self.Protocol
} }
def backupConfigurationFile(self): def backupConfigurationFile(self):
if not os.path.exists(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')): if not os.path.exists(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')):
os.mkdir(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')) os.mkdir(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup'))
time = datetime.now().strftime("%Y%m%d%H%M%S") time = datetime.now().strftime("%Y%m%d%H%M%S")
shutil.copy( shutil.copy(
self.__configPath, self.configPath,
os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f'{self.Name}_{time}.conf') os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.conf')
) )
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f'{self.Name}_{time}.sql'), 'w+') as f: with open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f'{self.Name}_{time}.sql'), 'w+') as f:
for l in self.__dumpDatabase(): for l in self.__dumpDatabase():
f.write(l + "\n") f.write(l + "\n")
def getBackups(self, databaseContent: bool = False) -> list[dict[str: str, str: str, str: str]]: def getBackups(self, databaseContent: bool = False) -> list[dict[str: str, str: str, str: str]]:
backups = [] backups = []
directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup') directory = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup')
files = [(file, os.path.getctime(os.path.join(directory, file))) files = [(file, os.path.getctime(os.path.join(directory, file)))
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))] for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
files.sort(key=lambda x: x[1], reverse=True) files.sort(key=lambda x: x[1], reverse=True)
@ -1060,12 +1079,12 @@ class WireguardConfiguration:
d = { d = {
"filename": f, "filename": f,
"backupDate": date, "backupDate": date,
"content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read() "content": open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f), 'r').read()
} }
if f.replace(".conf", ".sql") in list(os.listdir(directory)): if f.replace(".conf", ".sql") in list(os.listdir(directory)):
d['database'] = True d['database'] = True
if databaseContent: if databaseContent:
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() d['databaseContent'] = open(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
backups.append(d) backups.append(d)
return backups return backups
@ -1077,13 +1096,13 @@ class WireguardConfiguration:
self.backupConfigurationFile() self.backupConfigurationFile()
if self.Status: if self.Status:
self.toggleConfiguration() self.toggleConfiguration()
target = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName) target = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName)
targetSQL = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName.replace(".conf", ".sql")) targetSQL = os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName.replace(".conf", ".sql"))
if not os.path.exists(target): if not os.path.exists(target):
return False return False
targetContent = open(target, 'r').read() targetContent = open(target, 'r').read()
try: try:
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f: with open(self.configPath, 'w') as f:
f.write(targetContent) f.write(targetContent)
except Exception as e: except Exception as e:
return False return False
@ -1098,7 +1117,7 @@ class WireguardConfiguration:
if backupFileName not in backups: if backupFileName not in backups:
return False return False
try: try:
os.remove(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName)) os.remove(os.path.join(self.__getProtocolPath(), 'WGDashboard_Backup', backupFileName))
except Exception as e: except Exception as e:
return False return False
return True return True
@ -1108,9 +1127,11 @@ class WireguardConfiguration:
self.toggleConfiguration() self.toggleConfiguration()
original = [] original = []
dataChanged = False dataChanged = False
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'r') as f: with open(self.configPath, 'r') as f:
original = [l.rstrip("\n") for l in f.readlines()] original = [l.rstrip("\n") for l in f.readlines()]
allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort"] allowEdit = ["Address", "PreUp", "PostUp", "PreDown", "PostDown", "ListenPort"]
if self.Protocol == 'awg':
allowEdit += ["Jc", "Jmin", "Jmax", "S1", "S2", "H1", "H2", "H3", "H4"]
start = original.index("[Interface]") start = original.index("[Interface]")
try: try:
end = original.index("[Peer]") end = original.index("[Peer]")
@ -1124,12 +1145,12 @@ class WireguardConfiguration:
if split[0] not in allowEdit: if split[0] not in allowEdit:
new.append(original[line]) new.append(original[line])
for key in allowEdit: for key in allowEdit:
new.insert(1, f"{key} = {newData[key].strip()}") new.insert(1, f"{key} = {str(newData[key]).strip()}")
new.append("") new.append("")
for line in range(end, len(original)): for line in range(end, len(original)):
new.append(original[line]) new.append(original[line])
self.backupConfigurationFile() self.backupConfigurationFile()
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f: with open(self.configPath, 'w') as f:
f.write("\n".join(new)) f.write("\n".join(new))
status, msg = self.toggleConfiguration() status, msg = self.toggleConfiguration()
@ -1140,7 +1161,7 @@ class WireguardConfiguration:
def deleteConfiguration(self): def deleteConfiguration(self):
if self.getStatus(): if self.getStatus():
self.toggleConfiguration() self.toggleConfiguration()
os.remove(self.__configPath) os.remove(self.configPath)
self.__dropDatabase() self.__dropDatabase()
return True return True
@ -1150,15 +1171,15 @@ class WireguardConfiguration:
try: try:
if self.getStatus(): if self.getStatus():
self.toggleConfiguration() self.toggleConfiguration()
self.__createDatabase(newConfigurationName) self.createDatabase(newConfigurationName)
sqlUpdate(f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"') sqlUpdate(f'INSERT INTO "{newConfigurationName}" SELECT * FROM "{self.Name}"')
sqlUpdate(f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"') sqlUpdate(f'INSERT INTO "{newConfigurationName}_restrict_access" SELECT * FROM "{self.Name}_restrict_access"')
sqlUpdate(f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"') sqlUpdate(f'INSERT INTO "{newConfigurationName}_deleted" SELECT * FROM "{self.Name}_deleted"')
sqlUpdate(f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"') sqlUpdate(f'INSERT INTO "{newConfigurationName}_transfer" SELECT * FROM "{self.Name}_transfer"')
AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName) AllPeerJobs.updateJobConfigurationName(self.Name, newConfigurationName)
shutil.copy( shutil.copy(
self.__configPath, self.configPath,
os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{newConfigurationName}.conf') os.path.join(self.__getProtocolPath(), f'{newConfigurationName}.conf')
) )
self.deleteConfiguration() self.deleteConfiguration()
except Exception as e: except Exception as e:
@ -1201,6 +1222,256 @@ class WireguardConfiguration:
break break
return True, availableAddress return True, availableAddress
"""
AmneziaWG Configuration
"""
class AmneziaWireguardConfiguration(WireguardConfiguration):
def __init__(self, name: str = None, data: dict = None, backup: dict = None, startup: bool = False):
self.Jc = 0
self.Jmin = 0
self.Jmax = 0
self.S1 = 0
self.S2 = 0
self.H1 = 1
self.H2 = 2
self.H3 = 3
self.H4 = 4
super().__init__(name, data, backup, startup, wg=False)
def toJson(self):
self.Status = self.getStatus()
return {
"Status": self.Status,
"Name": self.Name,
"PrivateKey": self.PrivateKey,
"PublicKey": self.PublicKey,
"Address": self.Address,
"ListenPort": self.ListenPort,
"PreUp": self.PreUp,
"PreDown": self.PreDown,
"PostUp": self.PostUp,
"PostDown": self.PostDown,
"SaveConfig": self.SaveConfig,
"DataUsage": {
"Total": sum(list(map(lambda x: x.cumu_data + x.total_data, self.Peers))),
"Sent": sum(list(map(lambda x: x.cumu_sent + x.total_sent, self.Peers))),
"Receive": sum(list(map(lambda x: x.cumu_receive + x.total_receive, self.Peers)))
},
"ConnectedPeers": len(list(filter(lambda x: x.status == "running", self.Peers))),
"TotalPeers": len(self.Peers),
"Protocol": self.Protocol,
"Jc": self.Jc,
"Jmin": self.Jmin,
"Jmax": self.Jmax,
"S1": self.S1,
"S2": self.S2,
"H1": self.H1,
"H2": self.H2,
"H3": self.H3,
"H4": self.H4
}
def createDatabase(self, dbName = None):
if dbName is None:
dbName = self.Name
existingTables = sqlSelect("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
existingTables = [t['name'] for t in existingTables]
if dbName not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s'(
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
""" % dbName
)
if f'{dbName}_restrict_access' not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s_restrict_access' (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
""" % dbName
)
if f'{dbName}_transfer' not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s_transfer' (
id VARCHAR NOT NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, time DATETIME
)
""" % dbName
)
if f'{dbName}_deleted' not in existingTables:
sqlUpdate(
"""
CREATE TABLE '%s_deleted' (
id VARCHAR NOT NULL, private_key VARCHAR NULL, DNS VARCHAR NULL, advanced_security VARCHAR NULL,
endpoint_allowed_ip VARCHAR NULL, name VARCHAR NULL, total_receive FLOAT NULL,
total_sent FLOAT NULL, total_data FLOAT NULL, endpoint VARCHAR NULL,
status VARCHAR NULL, latest_handshake VARCHAR NULL, allowed_ip VARCHAR NULL,
cumu_receive FLOAT NULL, cumu_sent FLOAT NULL, cumu_data FLOAT NULL, mtu INT NULL,
keepalive INT NULL, remote_endpoint VARCHAR NULL, preshared_key VARCHAR NULL,
PRIMARY KEY (id)
)
""" % dbName
)
def getPeers(self):
if self.configurationFileChanged():
self.Peers = []
with open(self.configPath, 'r') as configFile:
p = []
pCounter = -1
content = configFile.read().split('\n')
try:
peerStarts = content.index("[Peer]")
content = content[peerStarts:]
for i in content:
if not RegexMatch("#(.*)", i) and not RegexMatch(";(.*)", i):
if i == "[Peer]":
pCounter += 1
p.append({})
p[pCounter]["name"] = ""
else:
if len(i) > 0:
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter][split[0]] = split[1]
if RegexMatch("#Name# = (.*)", i):
split = re.split(r'\s*=\s*', i, 1)
if len(split) == 2:
p[pCounter]["name"] = split[1]
for i in p:
if "PublicKey" in i.keys():
checkIfExist = sqlSelect("SELECT * FROM '%s' WHERE id = ?" % self.Name,
((i['PublicKey']),)).fetchone()
if checkIfExist is None:
newPeer = {
"id": i['PublicKey'],
"advanced_security": i.get('AdvancedSecurity', 'off'),
"private_key": "",
"DNS": DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1],
"endpoint_allowed_ip": DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[
1],
"name": i.get("name"),
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("AllowedIPs", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"traffic": [],
"mtu": DashboardConfig.GetConfig("Peers", "peer_mtu")[1],
"keepalive": DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1],
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["PresharedKey"] if "PresharedKey" in i.keys() else ""
}
sqlUpdate(
"""
INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :advanced_security, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name
, newPeer)
self.Peers.append(AmneziaWGPeer(newPeer, self))
else:
sqlUpdate("UPDATE '%s' SET allowed_ip = ? WHERE id = ?" % self.Name,
(i.get("AllowedIPs", "N/A"), i['PublicKey'],))
self.Peers.append(AmneziaWGPeer(checkIfExist, self))
except Exception as e:
if __name__ == '__main__':
print(f"[WGDashboard] {self.Name} Error: {str(e)}")
else:
self.Peers.clear()
checkIfExist = sqlSelect("SELECT * FROM '%s'" % self.Name).fetchall()
for i in checkIfExist:
self.Peers.append(AmneziaWGPeer(i, self))
def addPeers(self, peers: list):
try:
for i in peers:
newPeer = {
"id": i['id'],
"private_key": i['private_key'],
"DNS": i['DNS'],
"endpoint_allowed_ip": i['endpoint_allowed_ip'],
"name": i['name'],
"total_receive": 0,
"total_sent": 0,
"total_data": 0,
"endpoint": "N/A",
"status": "stopped",
"latest_handshake": "N/A",
"allowed_ip": i.get("allowed_ip", "N/A"),
"cumu_receive": 0,
"cumu_sent": 0,
"cumu_data": 0,
"traffic": [],
"mtu": i['mtu'],
"keepalive": i['keepalive'],
"remote_endpoint": DashboardConfig.GetConfig("Peers", "remote_endpoint")[1],
"preshared_key": i["preshared_key"],
"advanced_security": i['advanced_security']
}
sqlUpdate(
"""
INSERT INTO '%s'
VALUES (:id, :private_key, :DNS, :advanced_security, :endpoint_allowed_ip, :name, :total_receive, :total_sent,
:total_data, :endpoint, :status, :latest_handshake, :allowed_ip, :cumu_receive, :cumu_sent,
:cumu_data, :mtu, :keepalive, :remote_endpoint, :preshared_key);
""" % self.Name
, newPeer)
for p in peers:
presharedKeyExist = len(p['preshared_key']) > 0
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
if presharedKeyExist:
with open(uid, "w+") as f:
f.write(p['preshared_key'])
subprocess.check_output(
f"{self.Protocol} set {self.Name} peer {p['id']} allowed-ips {p['allowed_ip'].replace(' ', '')}{f' preshared-key {uid}' if presharedKeyExist else ''} advanced-security {p['advanced_security']}",
shell=True, stderr=subprocess.STDOUT)
if presharedKeyExist:
os.remove(uid)
subprocess.check_output(
f"{self.Protocol}-quick save {self.Name}", shell=True, stderr=subprocess.STDOUT)
self.getPeersList()
return True
except Exception as e:
print(str(e))
return False
def getRestrictedPeers(self):
self.RestrictedPeers = []
restricted = sqlSelect("SELECT * FROM '%s_restrict_access'" % self.Name).fetchall()
for i in restricted:
self.RestrictedPeers.append(AmneziaWGPeer(i, self))
""" """
Peer Peer
""" """
@ -1226,6 +1497,10 @@ class Peer:
self.keepalive = tableData["keepalive"] self.keepalive = tableData["keepalive"]
self.remote_endpoint = tableData["remote_endpoint"] self.remote_endpoint = tableData["remote_endpoint"]
self.preshared_key = tableData["preshared_key"] self.preshared_key = tableData["preshared_key"]
self.jobs: list[PeerJob] = [] self.jobs: list[PeerJob] = []
self.ShareLink: list[PeerShareLink] = [] self.ShareLink: list[PeerShareLink] = []
self.getJobs() self.getJobs()
@ -1275,7 +1550,7 @@ class Peer:
f.write(preshared_key) f.write(preshared_key)
newAllowedIPs = allowed_ip.replace(" ", "") newAllowedIPs = allowed_ip.replace(" ", "")
updateAllowedIp = subprocess.check_output( updateAllowedIp = subprocess.check_output(
f"wg set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs}{f' preshared-key {uid}' if pskExist else ''}", f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs}{f' preshared-key {uid}' if pskExist else ''}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
if pskExist: os.remove(uid) if pskExist: os.remove(uid)
@ -1283,7 +1558,7 @@ class Peer:
if len(updateAllowedIp.decode().strip("\n")) != 0: if len(updateAllowedIp.decode().strip("\n")) != 0:
return ResponseObject(False, return ResponseObject(False,
"Update peer failed when updating Allowed IPs") "Update peer failed when updating Allowed IPs")
saveConfig = subprocess.check_output(f"wg-quick save {self.configuration.Name}", saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
shell=True, stderr=subprocess.STDOUT) shell=True, stderr=subprocess.STDOUT)
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'): if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
return ResponseObject(False, return ResponseObject(False,
@ -1317,6 +1592,7 @@ MTU = {str(self.mtu)}
''' '''
if len(self.DNS) > 0: if len(self.DNS) > 0:
peerConfiguration += f"DNS = {self.DNS}\n" peerConfiguration += f"DNS = {self.DNS}\n"
peerConfiguration += f''' peerConfiguration += f'''
[Peer] [Peer]
PublicKey = {self.configuration.PublicKey} PublicKey = {self.configuration.PublicKey}
@ -1351,6 +1627,115 @@ PersistentKeepalive = {str(self.keepalive)}
return False return False
return True return True
class AmneziaWGPeer(Peer):
def __init__(self, tableData, configuration: AmneziaWireguardConfiguration):
self.advanced_security = tableData["advanced_security"]
super().__init__(tableData, configuration)
def downloadPeer(self) -> dict[str, str]:
filename = self.name
if len(filename) == 0:
filename = "UntitledPeer"
filename = "".join(filename.split(' '))
filename = f"{filename}_{self.configuration.Name}"
illegal_filename = [".", ",", "/", "?", "<", ">", "\\", ":", "*", '|' '\"', "com1", "com2", "com3",
"com4", "com5", "com6", "com7", "com8", "com9", "lpt1", "lpt2", "lpt3", "lpt4",
"lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "con", "nul", "prn"]
for i in illegal_filename:
filename = filename.replace(i, "")
peerConfiguration = f'''[Interface]
PrivateKey = {self.private_key}
Address = {self.allowed_ip}
MTU = {str(self.mtu)}
Jc = {self.configuration.Jc}
Jmin = {self.configuration.Jmin}
Jmax = {self.configuration.Jmax}
S1 = {self.configuration.S1}
S2 = {self.configuration.S2}
H1 = {self.configuration.H1}
H2 = {self.configuration.H2}
H3 = {self.configuration.H3}
H4 = {self.configuration.H4}
'''
if len(self.DNS) > 0:
peerConfiguration += f"DNS = {self.DNS}\n"
peerConfiguration += f'''
[Peer]
PublicKey = {self.configuration.PublicKey}
AllowedIPs = {self.endpoint_allowed_ip}
Endpoint = {DashboardConfig.GetConfig("Peers", "remote_endpoint")[1]}:{self.configuration.ListenPort}
PersistentKeepalive = {str(self.keepalive)}
'''
if len(self.preshared_key) > 0:
peerConfiguration += f"PresharedKey = {self.preshared_key}\n"
return {
"fileName": filename,
"file": peerConfiguration
}
def updatePeer(self, name: str, private_key: str,
preshared_key: str,
dns_addresses: str, allowed_ip: str, endpoint_allowed_ip: str, mtu: int,
keepalive: int, advanced_security: str) -> ResponseObject:
if not self.configuration.getStatus():
self.configuration.toggleConfiguration()
existingAllowedIps = [item for row in list(
map(lambda x: [q.strip() for q in x.split(',')],
map(lambda y: y.allowed_ip,
list(filter(lambda k: k.id != self.id, self.configuration.getPeersList()))))) for item in row]
if allowed_ip in existingAllowedIps:
return ResponseObject(False, "Allowed IP already taken by another peer")
if not ValidateIPAddressesWithRange(endpoint_allowed_ip):
return ResponseObject(False, f"Endpoint Allowed IPs format is incorrect")
if len(dns_addresses) > 0 and not ValidateDNSAddress(dns_addresses):
return ResponseObject(False, f"DNS format is incorrect")
if mtu < 0 or mtu > 1460:
return ResponseObject(False, "MTU format is not correct")
if keepalive < 0:
return ResponseObject(False, "Persistent Keepalive format is not correct")
if advanced_security != "on" and advanced_security != "off":
return ResponseObject(False, "Advanced Security can only be on or off")
if len(private_key) > 0:
pubKey = GenerateWireguardPublicKey(private_key)
if not pubKey[0] or pubKey[1] != self.id:
return ResponseObject(False, "Private key does not match with the public key")
try:
rd = random.Random()
uid = str(uuid.UUID(int=rd.getrandbits(128), version=4))
pskExist = len(preshared_key) > 0
if pskExist:
with open(uid, "w+") as f:
f.write(preshared_key)
newAllowedIPs = allowed_ip.replace(" ", "")
updateAllowedIp = subprocess.check_output(
f"{self.configuration.Protocol} set {self.configuration.Name} peer {self.id} allowed-ips {newAllowedIPs}{f' preshared-key {uid}' if pskExist else ''} advanced-security {advanced_security}",
shell=True, stderr=subprocess.STDOUT)
if pskExist: os.remove(uid)
if len(updateAllowedIp.decode().strip("\n")) != 0:
return ResponseObject(False,
"Update peer failed when updating Allowed IPs")
saveConfig = subprocess.check_output(f"{self.configuration.Protocol}-quick save {self.configuration.Name}",
shell=True, stderr=subprocess.STDOUT)
if f"wg showconf {self.configuration.Name}" not in saveConfig.decode().strip('\n'):
return ResponseObject(False,
"Update peer failed when saving the configuration")
sqlUpdate(
'''UPDATE '%s' SET name = ?, private_key = ?, DNS = ?, endpoint_allowed_ip = ?, mtu = ?,
keepalive = ?, preshared_key = ?, advanced_security = ? WHERE id = ?''' % self.configuration.Name,
(name, private_key, dns_addresses, endpoint_allowed_ip, mtu,
keepalive, preshared_key, advanced_security, self.id,)
)
return ResponseObject()
except subprocess.CalledProcessError as exc:
return ResponseObject(False, exc.output.decode("UTF-8").strip())
""" """
Dashboard API Key Dashboard API Key
""" """
@ -1384,6 +1769,7 @@ class DashboardConfig:
}, },
"Server": { "Server": {
"wg_conf_path": "/etc/wireguard", "wg_conf_path": "/etc/wireguard",
"awg_conf_path": "/etc/amnezia/amneziawg",
"app_prefix": "", "app_prefix": "",
"app_ip": "0.0.0.0", "app_ip": "0.0.0.0",
"app_port": "10086", "app_port": "10086",
@ -1708,18 +2094,22 @@ def API_SignOut():
@app.route(f'{APP_PREFIX}/api/getWireguardConfigurations', methods=["GET"]) @app.route(f'{APP_PREFIX}/api/getWireguardConfigurations', methods=["GET"])
def API_getWireguardConfigurations(): def API_getWireguardConfigurations():
InitWireguardConfigurationsList() InitWireguardConfigurationsList()
InitAmneziaWireguardConfigurationsList()
return ResponseObject(data=[wc for wc in WireguardConfigurations.values()]) return ResponseObject(data=[wc for wc in WireguardConfigurations.values()])
@app.route(f'{APP_PREFIX}/api/addWireguardConfiguration', methods=["POST"]) @app.route(f'{APP_PREFIX}/api/addWireguardConfiguration', methods=["POST"])
def API_addWireguardConfiguration(): def API_addWireguardConfiguration():
data = request.get_json() data = request.get_json()
requiredKeys = [ requiredKeys = [
"ConfigurationName", "Address", "ListenPort", "PrivateKey" "ConfigurationName", "Address", "ListenPort", "PrivateKey", "Protocol"
] ]
for i in requiredKeys: for i in requiredKeys:
if i not in data.keys(): if i not in data.keys():
return ResponseObject(False, "Please provide all required parameters.") return ResponseObject(False, "Please provide all required parameters.")
if data.get("Protocol") not in ProtocolsEnabled():
return ResponseObject(False, "Please provide a valid protocol: wg / awg.")
# Check duplicate names, ports, address # Check duplicate names, ports, address
for i in WireguardConfigurations.values(): for i in WireguardConfigurations.values():
if i.Name == data['ConfigurationName']: if i.Name == data['ConfigurationName']:
@ -1738,22 +2128,27 @@ def API_addWireguardConfiguration():
"Address") "Address")
if "Backup" in data.keys(): if "Backup" in data.keys():
if not os.path.exists(os.path.join( path = {
DashboardConfig.GetConfig("Server", "wg_conf_path")[1], "wg": DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
'WGDashboard_Backup', "awg": DashboardConfig.GetConfig("Server", "awg_conf_path")[1]
data["Backup"])) or not os.path.exists(os.path.join( }
DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
'WGDashboard_Backup', if (os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"])) and
data["Backup"].replace('.conf', '.sql'))): os.path.exists(os.path.join(path['wg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
return ResponseObject(False, "Backup file does not exist") protocol = "wg"
elif (os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"])) and
os.path.exists(os.path.join(path['awg'], 'WGDashboard_Backup', data["Backup"].replace('.conf', '.sql')))):
protocol = "awg"
else:
return ResponseObject(False, "Backup does not exist")
shutil.copy( shutil.copy(
os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', data["Backup"]), os.path.join(path[protocol], 'WGDashboard_Backup', data["Backup"]),
os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{data["ConfigurationName"]}.conf') os.path.join(path[protocol], f'{data["ConfigurationName"]}.conf')
) )
WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data, name=data['ConfigurationName']) WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data, name=data['ConfigurationName']) if protocol == 'wg' else AmneziaWireguardConfiguration(data=data, name=data['ConfigurationName'])
else: else:
WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data) WireguardConfigurations[data['ConfigurationName']] = WireguardConfiguration(data=data) if data.get('Protocol') == 'wg' else AmneziaWireguardConfiguration(data=data)
return ResponseObject() return ResponseObject()
@app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration/') @app.get(f'{APP_PREFIX}/api/toggleWireguardConfiguration/')
@ -1826,7 +2221,8 @@ def API_getAllWireguardConfigurationBackup():
if len(b) > 0: if len(b) > 0:
data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True) data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True)
directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup') for protocol in ProtocolsEnabled():
directory = os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup')
files = [(file, os.path.getctime(os.path.join(directory, file))) files = [(file, os.path.getctime(os.path.join(directory, file)))
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))] for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
files.sort(key=lambda x: x[1], reverse=True) files.sort(key=lambda x: x[1], reverse=True)
@ -1841,13 +2237,14 @@ def API_getAllWireguardConfigurationBackup():
date = s.group(2) date = s.group(2)
d = { d = {
"protocol": protocol,
"filename": f, "filename": f,
"backupDate": date, "backupDate": date,
"content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read() "content": open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
} }
if f.replace(".conf", ".sql") in list(os.listdir(directory)): if f.replace(".conf", ".sql") in list(os.listdir(directory)):
d['database'] = True d['database'] = True
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read() d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", f"{protocol}_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
data['NonExistingConfigurations'][name].append(d) data['NonExistingConfigurations'][name].append(d)
return ResponseObject(data=data) return ResponseObject(data=data)
@ -1907,8 +2304,10 @@ def API_updateDashboardConfigurationItem():
if data['section'] == "Server": if data['section'] == "Server":
if data['key'] == 'wg_conf_path': if data['key'] == 'wg_conf_path':
WireguardConfigurations.clear()
WireguardConfigurations.clear() WireguardConfigurations.clear()
InitWireguardConfigurationsList() InitWireguardConfigurationsList()
InitAmneziaWireguardConfigurationsList()
return ResponseObject(True, data=DashboardConfig.GetConfig(data["section"], data["key"])[1]) return ResponseObject(True, data=DashboardConfig.GetConfig(data["section"], data["key"])[1])
@ -1958,8 +2357,13 @@ def API_updatePeerSettings(configName):
wireguardConfig = WireguardConfigurations[configName] wireguardConfig = WireguardConfigurations[configName]
foundPeer, peer = wireguardConfig.searchPeer(id) foundPeer, peer = wireguardConfig.searchPeer(id)
if foundPeer: if foundPeer:
if wireguardConfig.Protocol == 'wg':
return peer.updatePeer(name, private_key, preshared_key, dns_addresses, return peer.updatePeer(name, private_key, preshared_key, dns_addresses,
allowed_ip, endpoint_allowed_ip, mtu, keepalive) allowed_ip, endpoint_allowed_ip, mtu, keepalive)
return peer.updatePeer(name, private_key, preshared_key, dns_addresses,
allowed_ip, endpoint_allowed_ip, mtu, keepalive, data.get('advanced_security', 'off'))
return ResponseObject(False, "Peer does not exist") return ResponseObject(False, "Peer does not exist")
@app.post(f'{APP_PREFIX}/api/resetPeerData/<configName>') @app.post(f'{APP_PREFIX}/api/resetPeerData/<configName>')
@ -2122,7 +2526,8 @@ def API_addPeers(configName):
"DNS": dns_addresses, "DNS": dns_addresses,
"endpoint_allowed_ip": endpoint_allowed_ip, "endpoint_allowed_ip": endpoint_allowed_ip,
"mtu": mtu, "mtu": mtu,
"keepalive": keep_alive "keepalive": keep_alive,
"advanced_security": data.get("advanced_security", "off")
}) })
if len(keyPairs) == 0: if len(keyPairs) == 0:
return ResponseObject(False, "Generating key pairs by bulk failed") return ResponseObject(False, "Generating key pairs by bulk failed")
@ -2149,7 +2554,8 @@ def API_addPeers(configName):
"endpoint_allowed_ip": endpoint_allowed_ip, "endpoint_allowed_ip": endpoint_allowed_ip,
"DNS": dns_addresses, "DNS": dns_addresses,
"mtu": mtu, "mtu": mtu,
"keepalive": keep_alive "keepalive": keep_alive,
"advanced_security": data.get("advanced_security", "off")
}] }]
) )
return ResponseObject(status) return ResponseObject(status)
@ -2563,6 +2969,9 @@ def API_SystemStatus():
# pass # pass
return ResponseObject(data=status) return ResponseObject(data=status)
@app.get(f'{APP_PREFIX}/api/protocolsEnabled')
def API_ProtocolsEnabled():
return ResponseObject(data=ProtocolsEnabled())
@app.get(f'{APP_PREFIX}/') @app.get(f'{APP_PREFIX}/')
def index(): def index():
@ -2599,6 +3008,16 @@ def gunicornConfig():
_, app_port = DashboardConfig.GetConfig("Server", "app_port") _, app_port = DashboardConfig.GetConfig("Server", "app_port")
return app_ip, app_port return app_ip, app_port
def ProtocolsEnabled() -> list[str]:
from shutil import which
protocols = []
if which('awg') is not None and which('awg-quick') is not None:
protocols.append("awg")
if which('wg') is not None and which('wg-quick') is not None:
protocols.append("wg")
return protocols
def InitWireguardConfigurationsList(startup: bool = False): def InitWireguardConfigurationsList(startup: bool = False):
confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1]) confs = os.listdir(DashboardConfig.GetConfig("Server", "wg_conf_path")[1])
confs.sort() confs.sort()
@ -2614,6 +3033,24 @@ def InitWireguardConfigurationsList(startup: bool = False):
except WireguardConfiguration.InvalidConfigurationFileException as e: except WireguardConfiguration.InvalidConfigurationFileException as e:
print(f"{i} have an invalid configuration file.") print(f"{i} have an invalid configuration file.")
if "awg" in ProtocolsEnabled():
confs = os.listdir(DashboardConfig.GetConfig("Server", "awg_conf_path")[1])
confs.sort()
for i in confs:
if RegexMatch("^(.{1,}).(conf)$", i):
i = i.replace('.conf', '')
try:
if i in WireguardConfigurations.keys():
if WireguardConfigurations[i].configurationFileChanged():
WireguardConfigurations[i] = AmneziaWireguardConfiguration(i)
else:
WireguardConfigurations[i] = AmneziaWireguardConfiguration(i, startup=startup)
except WireguardConfigurations.InvalidConfigurationFileException as e:
print(f"{i} have an invalid configuration file.")
def InitAmneziaWireguardConfigurationsList(startup: bool = False):
pass
AllPeerShareLinks: PeerShareLinks = PeerShareLinks() AllPeerShareLinks: PeerShareLinks = PeerShareLinks()
AllPeerJobs: PeerJobs = PeerJobs() AllPeerJobs: PeerJobs = PeerJobs()
JobLogger: PeerJobLogger = PeerJobLogger() JobLogger: PeerJobLogger = PeerJobLogger()
@ -2623,7 +3060,9 @@ _, app_port = DashboardConfig.GetConfig("Server", "app_port")
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path") _, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
WireguardConfigurations: dict[str, WireguardConfiguration] = {} WireguardConfigurations: dict[str, WireguardConfiguration] = {}
AmneziaWireguardConfigurations: dict[str, AmneziaWireguardConfiguration] = {}
InitWireguardConfigurationsList(startup=True) InitWireguardConfigurationsList(startup=True)
InitAmneziaWireguardConfigurationsList(startup=True)
def startThreads(): def startThreads():
bgThread = threading.Thread(target=backGroundThread) bgThread = threading.Thread(target=backGroundThread)

View File

@ -1 +1 @@
import{_ as r,c as i,d as o,w as e,j as l,a as t,T as _,i as a,l as d,S as u}from"./index-giM6Gyhs.js";const m={name:"configuration"},p={class:"mt-md-5 mt-3 text-body"};function f(x,h,k,w,$,v){const n=l("RouterView");return t(),i("div",p,[o(n,null,{default:e(({Component:s,route:c})=>[o(_,{name:"fade2",mode:"out-in"},{default:e(()=>[(t(),a(u,null,{default:e(()=>[(t(),a(d(s),{key:c.path}))]),_:2},1024))]),_:2},1024)]),_:1})])}const B=r(m,[["render",f]]);export{B as default}; import{_ as r,c as i,d as o,w as e,j as l,a as t,T as _,i as a,l as d,S as u}from"./index-CRrH8rsP.js";const m={name:"configuration"},p={class:"mt-md-5 mt-3 text-body"};function f(x,h,k,w,$,v){const n=l("RouterView");return t(),i("div",p,[o(n,null,{default:e(({Component:s,route:c})=>[o(_,{name:"fade2",mode:"out-in"},{default:e(()=>[(t(),a(u,null,{default:e(()=>[(t(),a(d(s),{key:c.path}))]),_:2},1024))]),_:2},1024)]),_:1})])}const B=r(m,[["render",f]]);export{B as default};

View File

@ -0,0 +1 @@
.fade-enter-active[data-v-a12ef598]{transition-delay:var(--69638f1c)!important}.progress-bar[data-v-eb521609]{width:0;transition:all 1s cubic-bezier(.42,0,.22,1)}.configurationListTitle{.btn[data-v-9a9d4e41]{border-radius:50%!important}}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.fade-enter-active[data-v-a85a04a5]{transition-delay:var(--1d5189b2)!important}.progress-bar[data-v-eb521609]{width:0;transition:all 1s cubic-bezier(.42,0,.22,1)}.configurationListTitle{.btn[data-v-9a9d4e41]{border-radius:50%!important}}

File diff suppressed because one or more lines are too long

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 +1 @@
import{a1 as w,r as c,I as x,D as _,o as B,a as l,c as b,b as t,d as o,n as D,m as N,z as I,C as v,i as k,y as M,g as T}from"./index-giM6Gyhs.js";import{L as s}from"./localeText-DcZBPGqi.js";const $={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},R={class:"container d-flex h-100 w-100"},S={class:"m-auto modal-dialog-centered dashboardModal",style:{width:"700px"}},V={class:"card rounded-3 shadow flex-grow-1 bg-danger-subtle border-danger-subtle",id:"deleteConfigurationContainer"},z={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},A={class:"mb-0"},L={class:"card-body px-4 text-muted"},P={class:"mb-0"},W={key:0},E={key:1},G={key:2,class:"d-flex align-items-center gap-2"},O=["placeholder"],U=["disabled"],J={__name:"deleteConfiguration",emits:["backup","close"],setup(j,{emit:h}){const a=w().params.id,g=c(""),y=x(),p=_(),i=c(!1),C=()=>{clearInterval(p.Peers.RefreshInterval),i.value=!0,M("/api/deleteWireguardConfiguration",{Name:a},d=>{d.status?(y.push("/"),p.newMessage("Server","Configuration deleted","success")):i.value=!1})},r=c(!0),u=c([]),f=()=>{r.value=!0,T("/api/getWireguardConfigurationBackup",{configurationName:a},d=>{u.value=d.data,r.value=!1})};B(()=>{f()});const m=h;return(d,e)=>(l(),b("div",$,[t("div",R,[t("div",S,[t("div",V,[t("div",z,[t("h5",A,[o(s,{t:"Are you sure to delete this configuration?"})]),t("button",{type:"button",class:"btn-close ms-auto",onClick:e[0]||(e[0]=n=>m("close"))})]),t("div",L,[t("p",P,[o(s,{t:"Once you deleted this configuration:"})]),t("ul",null,[t("li",null,[o(s,{t:"All connected peers will get disconnected"})]),t("li",null,[o(s,{t:"Both configuration file (.conf) and database table related to this configuration will get deleted"})])]),t("div",{class:D(["alert",[r.value?"alert-secondary":u.value.length>0?"alert-success":"alert-danger"]])},[r.value?(l(),b("div",W,[e[5]||(e[5]=t("i",{class:"bi bi-search me-2"},null,-1)),o(s,{t:"Checking backups..."})])):u.value.length>0?(l(),b("div",E,[e[6]||(e[6]=t("i",{class:"bi bi-check-circle-fill me-2"},null,-1)),o(s,{t:"This configuration have "+u.value.length+" backups"},null,8,["t"])])):(l(),b("div",G,[e[9]||(e[9]=t("i",{class:"bi bi-x-circle-fill me-2"},null,-1)),o(s,{t:"This configuration have no backup"}),t("a",{role:"button",onClick:e[1]||(e[1]=n=>m("backup")),class:"ms-auto btn btn-sm btn-primary rounded-3"},[e[7]||(e[7]=t("i",{class:"bi bi-clock-history me-2"},null,-1)),o(s,{t:"Backup"})]),t("a",{role:"button",onClick:e[2]||(e[2]=n=>f()),class:"btn btn-sm btn-primary rounded-3"},e[8]||(e[8]=[t("i",{class:"bi bi-arrow-clockwise"},null,-1)]))]))],2),e[11]||(e[11]=t("hr",null,null,-1)),t("p",null,[o(s,{t:"If you're sure, please type in the configuration name below and click Delete"})]),N(t("input",{class:"form-control rounded-3 mb-3",placeholder:v(a),"onUpdate:modelValue":e[3]||(e[3]=n=>g.value=n),type:"text"},null,8,O),[[I,g.value]]),t("button",{class:"btn btn-danger w-100",onClick:e[4]||(e[4]=n=>C()),disabled:g.value!==v(a)||i.value},[e[10]||(e[10]=t("i",{class:"bi bi-trash-fill me-2 rounded-3"},null,-1)),i.value?(l(),k(s,{key:1,t:"Deleting..."})):(l(),k(s,{key:0,t:"Delete"}))],8,U)])])])])]))}};export{J as default}; import{a2 as w,r as c,I as x,D as _,o as B,a as l,c as b,b as t,d as o,n as D,m as N,z as I,C as v,i as k,y as M,g as T}from"./index-CRrH8rsP.js";import{L as s}from"./localeText-BwuzYH16.js";const $={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},R={class:"container d-flex h-100 w-100"},S={class:"m-auto modal-dialog-centered dashboardModal",style:{width:"700px"}},V={class:"card rounded-3 shadow flex-grow-1 bg-danger-subtle border-danger-subtle",id:"deleteConfigurationContainer"},z={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},A={class:"mb-0"},L={class:"card-body px-4 text-muted"},P={class:"mb-0"},W={key:0},E={key:1},G={key:2,class:"d-flex align-items-center gap-2"},O=["placeholder"],U=["disabled"],J={__name:"deleteConfiguration",emits:["backup","close"],setup(j,{emit:h}){const a=w().params.id,g=c(""),y=x(),p=_(),i=c(!1),C=()=>{clearInterval(p.Peers.RefreshInterval),i.value=!0,M("/api/deleteWireguardConfiguration",{Name:a},d=>{d.status?(y.push("/"),p.newMessage("Server","Configuration deleted","success")):i.value=!1})},r=c(!0),u=c([]),f=()=>{r.value=!0,T("/api/getWireguardConfigurationBackup",{configurationName:a},d=>{u.value=d.data,r.value=!1})};B(()=>{f()});const m=h;return(d,e)=>(l(),b("div",$,[t("div",R,[t("div",S,[t("div",V,[t("div",z,[t("h5",A,[o(s,{t:"Are you sure to delete this configuration?"})]),t("button",{type:"button",class:"btn-close ms-auto",onClick:e[0]||(e[0]=n=>m("close"))})]),t("div",L,[t("p",P,[o(s,{t:"Once you deleted this configuration:"})]),t("ul",null,[t("li",null,[o(s,{t:"All connected peers will get disconnected"})]),t("li",null,[o(s,{t:"Both configuration file (.conf) and database table related to this configuration will get deleted"})])]),t("div",{class:D(["alert",[r.value?"alert-secondary":u.value.length>0?"alert-success":"alert-danger"]])},[r.value?(l(),b("div",W,[e[5]||(e[5]=t("i",{class:"bi bi-search me-2"},null,-1)),o(s,{t:"Checking backups..."})])):u.value.length>0?(l(),b("div",E,[e[6]||(e[6]=t("i",{class:"bi bi-check-circle-fill me-2"},null,-1)),o(s,{t:"This configuration have "+u.value.length+" backups"},null,8,["t"])])):(l(),b("div",G,[e[9]||(e[9]=t("i",{class:"bi bi-x-circle-fill me-2"},null,-1)),o(s,{t:"This configuration have no backup"}),t("a",{role:"button",onClick:e[1]||(e[1]=n=>m("backup")),class:"ms-auto btn btn-sm btn-primary rounded-3"},[e[7]||(e[7]=t("i",{class:"bi bi-clock-history me-2"},null,-1)),o(s,{t:"Backup"})]),t("a",{role:"button",onClick:e[2]||(e[2]=n=>f()),class:"btn btn-sm btn-primary rounded-3"},e[8]||(e[8]=[t("i",{class:"bi bi-arrow-clockwise"},null,-1)]))]))],2),e[11]||(e[11]=t("hr",null,null,-1)),t("p",null,[o(s,{t:"If you're sure, please type in the configuration name below and click Delete"})]),N(t("input",{class:"form-control rounded-3 mb-3",placeholder:v(a),"onUpdate:modelValue":e[3]||(e[3]=n=>g.value=n),type:"text"},null,8,O),[[I,g.value]]),t("button",{class:"btn btn-danger w-100",onClick:e[4]||(e[4]=n=>C()),disabled:g.value!==v(a)||i.value},[e[10]||(e[10]=t("i",{class:"bi bi-trash-fill me-2 rounded-3"},null,-1)),i.value?(l(),k(s,{key:1,t:"Deleting..."})):(l(),k(s,{key:0,t:"Delete"}))],8,U)])])])])]))}};export{J as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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 +1 @@
import{_ as t,G as e,t as o}from"./index-giM6Gyhs.js";const s={name:"localeText",props:{t:""},computed:{getLocaleText(){return e(this.t)}}};function a(c,r,n,p,_,i){return o(this.getLocaleText)}const x=t(s,[["render",a]]);export{x as L}; import{_ as t,G as e,t as o}from"./index-CRrH8rsP.js";const s={name:"localeText",props:{t:""},computed:{getLocaleText(){return e(this.t)}}};function a(c,r,n,p,_,i){return o(this.getLocaleText)}const x=t(s,[["render",a]]);export{x as L};

View File

@ -1 +1 @@
import{L as c}from"./localeText-DcZBPGqi.js";import{d as n}from"./dayjs.min-CeJXmnto.js";import{_ as d,a as r,c as m,b as s,d as i,f as t,t as e,n as l,j as _}from"./index-giM6Gyhs.js";const p={name:"message",methods:{dayjs:n},components:{LocaleText:c},props:{message:Object},mounted(){setTimeout(()=>{this.message.show=!1},5e3)}},g=["id"],h={class:"card-body"},f={class:"d-flex"},x={class:"fw-bold d-block",style:{"text-transform":"uppercase"}},u={class:"ms-auto"};function b(y,v,w,T,j,a){const o=_("LocaleText");return r(),m("div",{class:l(["card shadow rounded-3 position-relative message ms-auto",{"text-bg-danger":this.message.type==="danger","text-bg-success":this.message.type==="success","text-bg-warning":this.message.type==="warning"}]),id:this.message.id},[s("div",h,[s("div",f,[s("small",x,[i(o,{t:"FROM "}),t(" "+e(this.message.from),1)]),s("small",u,e(a.dayjs().format("hh:mm A")),1)]),t(" "+e(this.message.content),1)])],10,g)}const M=d(p,[["render",b],["__scopeId","data-v-f50b8f0c"]]);export{M}; import{L as c}from"./localeText-BwuzYH16.js";import{d as n}from"./dayjs.min-DtBetGsd.js";import{_ as d,a as r,c as m,b as s,d as i,f as t,t as e,n as l,j as _}from"./index-CRrH8rsP.js";const p={name:"message",methods:{dayjs:n},components:{LocaleText:c},props:{message:Object},mounted(){setTimeout(()=>{this.message.show=!1},5e3)}},g=["id"],h={class:"card-body"},f={class:"d-flex"},x={class:"fw-bold d-block",style:{"text-transform":"uppercase"}},u={class:"ms-auto"};function b(y,v,w,T,j,a){const o=_("LocaleText");return r(),m("div",{class:l(["card shadow rounded-3 position-relative message ms-auto",{"text-bg-danger":this.message.type==="danger","text-bg-success":this.message.type==="success","text-bg-warning":this.message.type==="warning"}]),id:this.message.id},[s("div",h,[s("div",f,[s("small",x,[i(o,{t:"FROM "}),t(" "+e(this.message.from),1)]),s("small",u,e(a.dayjs().format("hh:mm A")),1)]),t(" "+e(this.message.content),1)])],10,g)}const M=d(p,[["render",b],["__scopeId","data-v-f50b8f0c"]]);export{M};

View File

@ -0,0 +1 @@
.protocolBtnGroup a[data-v-d074fb65]{transition:all .2s ease-in-out}

File diff suppressed because one or more lines are too long

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 +1 @@
import{_ as f,D as m,r as _,a as s,c as a,b as e,d as l,w as g,T as h}from"./index-giM6Gyhs.js";import{L as v}from"./localeText-DcZBPGqi.js";const y={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},x={class:"container d-flex h-100 w-100"},w={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},C={class:"card rounded-3 shadow w-100"},k={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},F={class:"mb-0"},T={class:"card-body p-4"},D={class:"d-flex"},S=["disabled"],B={key:0,class:"d-block"},M={key:1,class:"d-block",id:"check"},G=["value"],L={__name:"peerConfigurationFile",props:{configurationFile:String},emits:["close"],setup(i,{emit:r}){const c=r,d=i,n=m(),o=_(!1),u=async()=>{navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(d.configurationFile).then(()=>{o.value=!0,setTimeout(()=>{o.value=!1},3e3)}).catch(()=>{n.newMessage("WGDashboard","Failed to copy","danger")}):(document.querySelector("#peerConfigurationFile").select(),document.execCommand("copy")?(o.value=!0,setTimeout(()=>{o.value=!1},3e3)):n.newMessage("WGDashboard","Failed to copy","danger"))};return(p,t)=>(s(),a("div",y,[e("div",x,[e("div",w,[e("div",C,[e("div",k,[e("h4",F,[l(v,{t:"Peer Configuration File"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=b=>c("close"))})]),e("div",T,[e("div",D,[e("button",{onClick:t[1]||(t[1]=b=>u()),disabled:o.value,class:"ms-auto btn bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 position-relative"},[l(h,{name:"slide-up",mode:"out-in"},{default:g(()=>[o.value?(s(),a("span",M,t[3]||(t[3]=[e("i",{class:"bi bi-check-circle-fill"},null,-1)]))):(s(),a("span",B,t[2]||(t[2]=[e("i",{class:"bi bi-clipboard-fill"},null,-1)])))]),_:1})],8,S)]),e("textarea",{style:{height:"300px"},class:"form-control w-100 rounded-3 mt-2",id:"peerConfigurationFile",value:i.configurationFile},null,8,G)])])])])]))}},W=f(L,[["__scopeId","data-v-fcd3ae95"]]);export{W as default}; import{_ as f,D as m,r as _,a as s,c as a,b as e,d as l,w as g,T as h}from"./index-CRrH8rsP.js";import{L as v}from"./localeText-BwuzYH16.js";const y={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},x={class:"container d-flex h-100 w-100"},w={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},C={class:"card rounded-3 shadow w-100"},k={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},F={class:"mb-0"},T={class:"card-body p-4"},D={class:"d-flex"},S=["disabled"],B={key:0,class:"d-block"},M={key:1,class:"d-block",id:"check"},G=["value"],L={__name:"peerConfigurationFile",props:{configurationFile:String},emits:["close"],setup(i,{emit:r}){const c=r,d=i,n=m(),o=_(!1),u=async()=>{navigator.clipboard&&navigator.clipboard.writeText?navigator.clipboard.writeText(d.configurationFile).then(()=>{o.value=!0,setTimeout(()=>{o.value=!1},3e3)}).catch(()=>{n.newMessage("WGDashboard","Failed to copy","danger")}):(document.querySelector("#peerConfigurationFile").select(),document.execCommand("copy")?(o.value=!0,setTimeout(()=>{o.value=!1},3e3)):n.newMessage("WGDashboard","Failed to copy","danger"))};return(p,t)=>(s(),a("div",y,[e("div",x,[e("div",w,[e("div",C,[e("div",k,[e("h4",F,[l(v,{t:"Peer Configuration File"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=b=>c("close"))})]),e("div",T,[e("div",D,[e("button",{onClick:t[1]||(t[1]=b=>u()),disabled:o.value,class:"ms-auto btn bg-primary-subtle border-primary-subtle text-primary-emphasis rounded-3 position-relative"},[l(h,{name:"slide-up",mode:"out-in"},{default:g(()=>[o.value?(s(),a("span",M,t[3]||(t[3]=[e("i",{class:"bi bi-check-circle-fill"},null,-1)]))):(s(),a("span",B,t[2]||(t[2]=[e("i",{class:"bi bi-clipboard-fill"},null,-1)])))]),_:1})],8,S)]),e("textarea",{style:{height:"300px"},class:"form-control w-100 rounded-3 mt-2",id:"peerConfigurationFile",value:i.configurationFile},null,8,G)])])])])]))}},W=f(L,[["__scopeId","data-v-fcd3ae95"]]);export{W as default};

View File

@ -1 +1 @@
.list-move[data-v-6d5fc831],.list-enter-active[data-v-6d5fc831],.list-leave-active[data-v-6d5fc831]{transition:all .3s ease}.list-enter-from[data-v-6d5fc831],.list-leave-to[data-v-6d5fc831]{opacity:0;transform:translateY(10px)}.list-leave-active[data-v-6d5fc831]{position:absolute}.peerSettingContainer[data-v-ddffd6ec]{background-color:#00000060;z-index:9998}div[data-v-ddffd6ec]{transition:.2s ease-in-out}.inactiveField[data-v-ddffd6ec]{opacity:.4}.card[data-v-ddffd6ec]{max-height:100%} .list-move[data-v-6d5fc831],.list-enter-active[data-v-6d5fc831],.list-leave-active[data-v-6d5fc831]{transition:all .3s ease}.list-enter-from[data-v-6d5fc831],.list-leave-to[data-v-6d5fc831]{opacity:0;transform:translateY(10px)}.list-leave-active[data-v-6d5fc831]{position:absolute}.peerSettingContainer[data-v-49b35948]{background-color:#00000060;z-index:9998}div[data-v-49b35948]{transition:.2s ease-in-out}.inactiveField[data-v-49b35948]{opacity:.4}.card[data-v-49b35948]{max-height:100%}

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 +1 @@
import{S as p,a as b}from"./schedulePeerJob-PIgU3NIA.js";import{_ as h,W as u,x as m,j as i,a as o,c as a,b as e,d as r,w as _,F as v,h as f,i as J,e as x,k as g}from"./index-giM6Gyhs.js";import{L as w}from"./localeText-DcZBPGqi.js";import"./vue-datepicker-Bf7Wgq2X.js";import"./dayjs.min-CeJXmnto.js";const P={name:"peerJobs",setup(){return{store:u()}},props:{selectedPeer:Object},components:{LocaleText:w,SchedulePeerJob:p,ScheduleDropdown:b},data(){return{}},methods:{deleteJob(d){this.selectedPeer.jobs=this.selectedPeer.jobs.filter(t=>t.JobID!==d.JobID)},addJob(){this.selectedPeer.jobs.unshift(JSON.parse(JSON.stringify({JobID:m().toString(),Configuration:this.selectedPeer.configuration.Name,Peer:this.selectedPeer.id,Field:this.store.PeerScheduleJobs.dropdowns.Field[0].value,Operator:this.store.PeerScheduleJobs.dropdowns.Operator[0].value,Value:"",CreationDate:"",ExpireDate:"",Action:this.store.PeerScheduleJobs.dropdowns.Action[0].value})))}}},S={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},y={class:"container d-flex h-100 w-100"},$={class:"m-auto modal-dialog-centered dashboardModal"},C={class:"card rounded-3 shadow",style:{width:"700px"}},D={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},k={class:"card-body px-4 pb-4 pt-2 position-relative"},N={class:"d-flex align-items-center mb-3"},T={class:"card shadow-sm",key:"none",style:{height:"153px"}},I={class:"card-body text-muted text-center d-flex"},L={class:"m-auto"};function O(d,t,B,F,V,A){const n=i("LocaleText"),l=i("SchedulePeerJob");return o(),a("div",S,[e("div",y,[e("div",$,[e("div",C,[e("div",D,[e("h4",j,[r(n,{t:"Schedule Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=s=>this.$emit("close"))})]),e("div",k,[e("div",N,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow",onClick:t[1]||(t[1]=s=>this.addJob())},[t[3]||(t[3]=e("i",{class:"bi bi-plus-lg me-2"},null,-1)),r(n,{t:"Job"})])]),r(g,{name:"schedulePeerJobTransition",tag:"div",class:"position-relative"},{default:_(()=>[(o(!0),a(v,null,f(this.selectedPeer.jobs,(s,E)=>(o(),J(l,{onRefresh:t[2]||(t[2]=c=>this.$emit("refresh")),onDelete:c=>this.deleteJob(s),dropdowns:this.store.PeerScheduleJobs.dropdowns,key:s.JobID,pjob:s},null,8,["onDelete","dropdowns","pjob"]))),128)),this.selectedPeer.jobs.length===0?(o(),a("div",T,[e("div",I,[e("h6",L,[r(n,{t:"This peer does not have any job yet."})])])])):x("",!0)]),_:1})])])])])])}const z=h(P,[["render",O],["__scopeId","data-v-5bbdd42b"]]);export{z as default}; import{S as p,a as b}from"./schedulePeerJob-rxjbCqkE.js";import{_ as h,W as u,x as m,j as i,a as o,c as a,b as e,d as r,w as _,F as v,h as f,i as J,e as x,k as g}from"./index-CRrH8rsP.js";import{L as w}from"./localeText-BwuzYH16.js";import"./vue-datepicker-QWeTVbA6.js";import"./dayjs.min-DtBetGsd.js";const P={name:"peerJobs",setup(){return{store:u()}},props:{selectedPeer:Object},components:{LocaleText:w,SchedulePeerJob:p,ScheduleDropdown:b},data(){return{}},methods:{deleteJob(d){this.selectedPeer.jobs=this.selectedPeer.jobs.filter(t=>t.JobID!==d.JobID)},addJob(){this.selectedPeer.jobs.unshift(JSON.parse(JSON.stringify({JobID:m().toString(),Configuration:this.selectedPeer.configuration.Name,Peer:this.selectedPeer.id,Field:this.store.PeerScheduleJobs.dropdowns.Field[0].value,Operator:this.store.PeerScheduleJobs.dropdowns.Operator[0].value,Value:"",CreationDate:"",ExpireDate:"",Action:this.store.PeerScheduleJobs.dropdowns.Action[0].value})))}}},S={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},y={class:"container d-flex h-100 w-100"},$={class:"m-auto modal-dialog-centered dashboardModal"},C={class:"card rounded-3 shadow",style:{width:"700px"}},D={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},k={class:"card-body px-4 pb-4 pt-2 position-relative"},N={class:"d-flex align-items-center mb-3"},T={class:"card shadow-sm",key:"none",style:{height:"153px"}},I={class:"card-body text-muted text-center d-flex"},L={class:"m-auto"};function O(d,t,B,F,V,A){const n=i("LocaleText"),l=i("SchedulePeerJob");return o(),a("div",S,[e("div",y,[e("div",$,[e("div",C,[e("div",D,[e("h4",j,[r(n,{t:"Schedule Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=s=>this.$emit("close"))})]),e("div",k,[e("div",N,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow",onClick:t[1]||(t[1]=s=>this.addJob())},[t[3]||(t[3]=e("i",{class:"bi bi-plus-lg me-2"},null,-1)),r(n,{t:"Job"})])]),r(g,{name:"schedulePeerJobTransition",tag:"div",class:"position-relative"},{default:_(()=>[(o(!0),a(v,null,f(this.selectedPeer.jobs,(s,E)=>(o(),J(l,{onRefresh:t[2]||(t[2]=c=>this.$emit("refresh")),onDelete:c=>this.deleteJob(s),dropdowns:this.store.PeerScheduleJobs.dropdowns,key:s.JobID,pjob:s},null,8,["onDelete","dropdowns","pjob"]))),128)),this.selectedPeer.jobs.length===0?(o(),a("div",T,[e("div",I,[e("h6",L,[r(n,{t:"This peer does not have any job yet."})])])])):x("",!0)]),_:1})])])])])])}const z=h(P,[["render",O],["__scopeId","data-v-5bbdd42b"]]);export{z as default};

View File

@ -1 +1 @@
import{S as _}from"./schedulePeerJob-PIgU3NIA.js";import{_ as g,W as v,x as f,j as c,a as o,c as r,b as e,d,F as p,h as b,t as m,e as y,i as x}from"./index-giM6Gyhs.js";import{L as J}from"./localeText-DcZBPGqi.js";import"./vue-datepicker-Bf7Wgq2X.js";import"./dayjs.min-CeJXmnto.js";const w={name:"peerJobsAllModal",setup(){return{store:v()}},components:{LocaleText:J,SchedulePeerJob:_},props:{configurationPeers:Array[Object]},methods:{getuuid(){return f()}},computed:{getAllJobs(){return this.configurationPeers.filter(a=>a.jobs.length>0)}}},$={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},k={class:"container d-flex h-100 w-100"},A={class:"m-auto modal-dialog-centered dashboardModal"},L={class:"card rounded-3 shadow",style:{width:"900px"}},S={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},C={class:"card-body px-4 pb-4 pt-2"},P={key:0,class:"accordion",id:"peerJobsLogsModalAccordion"},M={class:"accordion-header"},B=["data-bs-target"],N={key:0},D={class:"text-muted"},T=["id"],V={class:"accordion-body"},F={key:1,class:"card shadow-sm",style:{height:"153px"}},O={class:"card-body text-muted text-center d-flex"},W={class:"m-auto"};function E(a,t,I,R,q,z){const n=c("LocaleText"),u=c("SchedulePeerJob");return o(),r("div",$,[e("div",k,[e("div",A,[e("div",L,[e("div",S,[e("h4",j,[d(n,{t:"All Active Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=s=>this.$emit("close"))})]),e("div",C,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow mb-2",onClick:t[1]||(t[1]=s=>this.$emit("allLogs"))},[t[4]||(t[4]=e("i",{class:"bi bi-clock me-2"},null,-1)),d(n,{t:"Logs"})]),this.getAllJobs.length>0?(o(),r("div",P,[(o(!0),r(p,null,b(this.getAllJobs,(s,l)=>(o(),r("div",{class:"accordion-item",key:s.id},[e("h2",M,[e("button",{class:"accordion-button collapsed",type:"button","data-bs-toggle":"collapse","data-bs-target":"#collapse_"+l},[e("small",null,[e("strong",null,[s.name?(o(),r("span",N,m(s.name)+" • ",1)):y("",!0),e("samp",D,m(s.id),1)])])],8,B)]),e("div",{id:"collapse_"+l,class:"accordion-collapse collapse","data-bs-parent":"#peerJobsLogsModalAccordion"},[e("div",V,[(o(!0),r(p,null,b(s.jobs,i=>(o(),x(u,{onDelete:t[2]||(t[2]=h=>this.$emit("refresh")),onRefresh:t[3]||(t[3]=h=>this.$emit("refresh")),dropdowns:this.store.PeerScheduleJobs.dropdowns,viewOnly:!0,key:i.JobID,pjob:i},null,8,["dropdowns","pjob"]))),128))])],8,T)]))),128))])):(o(),r("div",F,[e("div",O,[e("span",W,[d(n,{t:"No active job at the moment."})])])]))])])])])])}const X=g(w,[["render",E]]);export{X as default}; import{S as _}from"./schedulePeerJob-rxjbCqkE.js";import{_ as g,W as v,x as f,j as c,a as o,c as r,b as e,d,F as p,h as b,t as m,e as y,i as x}from"./index-CRrH8rsP.js";import{L as J}from"./localeText-BwuzYH16.js";import"./vue-datepicker-QWeTVbA6.js";import"./dayjs.min-DtBetGsd.js";const w={name:"peerJobsAllModal",setup(){return{store:v()}},components:{LocaleText:J,SchedulePeerJob:_},props:{configurationPeers:Array[Object]},methods:{getuuid(){return f()}},computed:{getAllJobs(){return this.configurationPeers.filter(a=>a.jobs.length>0)}}},$={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},k={class:"container d-flex h-100 w-100"},A={class:"m-auto modal-dialog-centered dashboardModal"},L={class:"card rounded-3 shadow",style:{width:"900px"}},S={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-2"},j={class:"mb-0 fw-normal"},C={class:"card-body px-4 pb-4 pt-2"},P={key:0,class:"accordion",id:"peerJobsLogsModalAccordion"},M={class:"accordion-header"},B=["data-bs-target"],N={key:0},D={class:"text-muted"},T=["id"],V={class:"accordion-body"},F={key:1,class:"card shadow-sm",style:{height:"153px"}},O={class:"card-body text-muted text-center d-flex"},W={class:"m-auto"};function E(a,t,I,R,q,z){const n=c("LocaleText"),u=c("SchedulePeerJob");return o(),r("div",$,[e("div",k,[e("div",A,[e("div",L,[e("div",S,[e("h4",j,[d(n,{t:"All Active Jobs"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=s=>this.$emit("close"))})]),e("div",C,[e("button",{class:"btn bg-primary-subtle border-1 border-primary-subtle text-primary-emphasis rounded-3 shadow mb-2",onClick:t[1]||(t[1]=s=>this.$emit("allLogs"))},[t[4]||(t[4]=e("i",{class:"bi bi-clock me-2"},null,-1)),d(n,{t:"Logs"})]),this.getAllJobs.length>0?(o(),r("div",P,[(o(!0),r(p,null,b(this.getAllJobs,(s,l)=>(o(),r("div",{class:"accordion-item",key:s.id},[e("h2",M,[e("button",{class:"accordion-button collapsed",type:"button","data-bs-toggle":"collapse","data-bs-target":"#collapse_"+l},[e("small",null,[e("strong",null,[s.name?(o(),r("span",N,m(s.name)+" • ",1)):y("",!0),e("samp",D,m(s.id),1)])])],8,B)]),e("div",{id:"collapse_"+l,class:"accordion-collapse collapse","data-bs-parent":"#peerJobsLogsModalAccordion"},[e("div",V,[(o(!0),r(p,null,b(s.jobs,i=>(o(),x(u,{onDelete:t[2]||(t[2]=h=>this.$emit("refresh")),onRefresh:t[3]||(t[3]=h=>this.$emit("refresh")),dropdowns:this.store.PeerScheduleJobs.dropdowns,viewOnly:!0,key:i.JobID,pjob:i},null,8,["dropdowns","pjob"]))),128))])],8,T)]))),128))])):(o(),r("div",F,[e("div",O,[e("span",W,[d(n,{t:"No active job at the moment."})])])]))])])])])])}const X=g(w,[["render",E]]);export{X as default};

File diff suppressed because one or more lines are too long

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 +1 @@
import{b as a}from"./browser-CjSdxGTc.js";import{L as n}from"./localeText-DcZBPGqi.js";import{_ as c,j as r,a as d,c as i,b as e,d as l}from"./index-giM6Gyhs.js";const p={name:"peerQRCode",components:{LocaleText:n},props:{peerConfigData:String},mounted(){a.toCanvas(document.querySelector("#qrcode"),this.peerConfigData,o=>{o&&console.error(o)})}},_={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},m={class:"container d-flex h-100 w-100"},h={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},u={class:"card rounded-3 shadow"},f={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},b={class:"mb-0"},v={class:"card-body"},C={id:"qrcode",class:"rounded-3 shadow",ref:"qrcode"};function g(o,t,x,$,w,q){const s=r("LocaleText");return d(),i("div",_,[e("div",m,[e("div",h,[e("div",u,[e("div",f,[e("h4",b,[l(s,{t:"QR Code"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=y=>this.$emit("close"))})]),e("div",v,[e("canvas",C,null,512)])])])])])}const Q=c(p,[["render",g]]);export{Q as default}; import{b as a}from"./browser-CjSdxGTc.js";import{L as n}from"./localeText-BwuzYH16.js";import{_ as c,j as r,a as d,c as i,b as e,d as l}from"./index-CRrH8rsP.js";const p={name:"peerQRCode",components:{LocaleText:n},props:{peerConfigData:String},mounted(){a.toCanvas(document.querySelector("#qrcode"),this.peerConfigData,o=>{o&&console.error(o)})}},_={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0"},m={class:"container d-flex h-100 w-100"},h={class:"m-auto modal-dialog-centered dashboardModal justify-content-center"},u={class:"card rounded-3 shadow"},f={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0"},b={class:"mb-0"},v={class:"card-body"},C={id:"qrcode",class:"rounded-3 shadow",ref:"qrcode"};function g(o,t,x,$,w,q){const s=r("LocaleText");return d(),i("div",_,[e("div",m,[e("div",h,[e("div",u,[e("div",f,[e("h4",b,[l(s,{t:"QR Code"})]),e("button",{type:"button",class:"btn-close ms-auto",onClick:t[0]||(t[0]=y=>this.$emit("close"))})]),e("div",v,[e("canvas",C,null,512)])])])])])}const Q=c(p,[["render",g]]);export{Q as default};

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.toggleShowKey[data-v-a63ae8cb]{position:absolute;top:35px;right:12px}

View File

@ -0,0 +1 @@
.toggleShowKey[data-v-7bd9609a]{position:absolute;top:35px;right:12px}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{_ as g,D as f,y as h,j as p,a as s,c as r,b as t,d as o,n as m,i as n,t as _,e as b}from"./index-giM6Gyhs.js";import{d}from"./dayjs.min-CeJXmnto.js";import{V as y}from"./vue-datepicker-Bf7Wgq2X.js";import{L as S}from"./localeText-DcZBPGqi.js";const k={name:"peerShareLinkModal",props:{peer:Object},components:{LocaleText:S,VueDatePicker:y},data(){return{dataCopy:void 0,loading:!1}},setup(){return{store:f()}},mounted(){this.dataCopy=JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0)},watch:{"peer.ShareLink":{deep:!0,handler(e,a){a.length!==e.length&&(this.dataCopy=JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0))}}},methods:{startSharing(){this.loading=!0,h("/api/sharePeer/create",{Configuration:this.peer.configuration.Name,Peer:this.peer.id,ExpireDate:d().add(7,"d").format("YYYY-MM-DD HH:mm:ss")},e=>{e.status?(this.peer.ShareLink=e.data,this.dataCopy=e.data.at(0)):this.store.newMessage("Server","Share link failed to create. Reason: "+e.message,"danger"),this.loading=!1})},updateLinkExpireDate(){h("/api/sharePeer/update",this.dataCopy,e=>{e.status?(this.dataCopy=e.data.at(0),this.peer.ShareLink=e.data,this.store.newMessage("Server","Link expire date updated","success")):this.store.newMessage("Server","Link expire date failed to update. Reason: "+e.message,"danger"),this.loading=!1})},stopSharing(){this.loading=!0,this.dataCopy.ExpireDate=d().format("YYYY-MM-DD HH:mm:ss"),this.updateLinkExpireDate()},parseTime(e){e?this.dataCopy.ExpireDate=d(e).format("YYYY-MM-DD HH:mm:ss"):this.dataCopy.ExpireDate=void 0,this.updateLinkExpireDate()}},computed:{getUrl(){const e=this.store.getActiveCrossServer();return e?`${e.host}/${this.$router.resolve({path:"/share",query:{ShareID:this.dataCopy.ShareID}}).href}`:window.location.origin+window.location.pathname+this.$router.resolve({path:"/share",query:{ShareID:this.dataCopy.ShareID}}).href}}},x={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},v={class:"container d-flex h-100 w-100"},C={class:"m-auto modal-dialog-centered dashboardModal",style:{width:"500px"}},D={class:"card rounded-3 shadow flex-grow-1"},w={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4"},L={class:"mb-0"},M={key:0,class:"card-body px-4 pb-4"},Y={key:0},$={class:"mb-3 text-muted"},E=["disabled"],H={key:1},V={class:"d-flex gap-2 mb-4"},N=["href"],P={class:"d-flex flex-column gap-2 mb-3"},O=["disabled"];function T(e,a,U,B,I,c){const i=p("LocaleText"),u=p("VueDatePicker");return s(),r("div",x,[t("div",v,[t("div",C,[t("div",D,[t("div",w,[t("h4",L,[o(i,{t:"Share Peer"})]),t("button",{type:"button",class:"btn-close ms-auto",onClick:a[0]||(a[0]=l=>this.$emit("close"))})]),this.peer.ShareLink?(s(),r("div",M,[this.dataCopy?(s(),r("div",H,[t("div",V,[a[4]||(a[4]=t("i",{class:"bi bi-link-45deg"},null,-1)),t("a",{href:this.getUrl,class:"text-decoration-none",target:"_blank"},_(c.getUrl),9,N)]),t("div",P,[t("small",null,[a[5]||(a[5]=t("i",{class:"bi bi-calendar me-2"},null,-1)),o(i,{t:"Expire At"})]),o(u,{is24:!0,"min-date":new Date,"model-value":this.dataCopy.ExpireDate,"onUpdate:modelValue":this.parseTime,"time-picker-inline":"",format:"yyyy-MM-dd HH:mm:ss","preview-format":"yyyy-MM-dd HH:mm:ss",dark:this.store.Configuration.Server.dashboard_theme==="dark"},null,8,["min-date","model-value","onUpdate:modelValue","dark"])]),t("button",{onClick:a[2]||(a[2]=l=>this.stopSharing()),disabled:this.loading,class:"w-100 btn bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle rounded-3 shadow-sm"},[t("span",{class:m({"animate__animated animate__flash animate__infinite animate__slower":this.loading})},a[6]||(a[6]=[t("i",{class:"bi bi-send-slash-fill me-2"},null,-1)]),2),this.loading?(s(),n(i,{key:0,t:"Stop Sharing..."})):(s(),n(i,{key:1,t:"Stop Sharing"}))],8,O)])):(s(),r("div",Y,[t("h6",$,[o(i,{t:"Currently the peer is not sharing"})]),t("button",{onClick:a[1]||(a[1]=l=>this.startSharing()),disabled:this.loading,class:"w-100 btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm"},[t("span",{class:m({"animate__animated animate__flash animate__infinite animate__slower":this.loading})},a[3]||(a[3]=[t("i",{class:"bi bi-send-fill me-2"},null,-1)]),2),this.loading?(s(),n(i,{key:0,t:"Sharing..."})):(s(),n(i,{key:1,t:"Start Sharing"}))],8,E)]))])):b("",!0)])])])])}const R=g(k,[["render",T]]);export{R as default}; import{_ as g,D as f,y as h,j as p,a as s,c as r,b as t,d as o,n as m,i as n,t as _,e as b}from"./index-CRrH8rsP.js";import{d}from"./dayjs.min-DtBetGsd.js";import{V as y}from"./vue-datepicker-QWeTVbA6.js";import{L as S}from"./localeText-BwuzYH16.js";const k={name:"peerShareLinkModal",props:{peer:Object},components:{LocaleText:S,VueDatePicker:y},data(){return{dataCopy:void 0,loading:!1}},setup(){return{store:f()}},mounted(){this.dataCopy=JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0)},watch:{"peer.ShareLink":{deep:!0,handler(e,a){a.length!==e.length&&(this.dataCopy=JSON.parse(JSON.stringify(this.peer.ShareLink)).at(0))}}},methods:{startSharing(){this.loading=!0,h("/api/sharePeer/create",{Configuration:this.peer.configuration.Name,Peer:this.peer.id,ExpireDate:d().add(7,"d").format("YYYY-MM-DD HH:mm:ss")},e=>{e.status?(this.peer.ShareLink=e.data,this.dataCopy=e.data.at(0)):this.store.newMessage("Server","Share link failed to create. Reason: "+e.message,"danger"),this.loading=!1})},updateLinkExpireDate(){h("/api/sharePeer/update",this.dataCopy,e=>{e.status?(this.dataCopy=e.data.at(0),this.peer.ShareLink=e.data,this.store.newMessage("Server","Link expire date updated","success")):this.store.newMessage("Server","Link expire date failed to update. Reason: "+e.message,"danger"),this.loading=!1})},stopSharing(){this.loading=!0,this.dataCopy.ExpireDate=d().format("YYYY-MM-DD HH:mm:ss"),this.updateLinkExpireDate()},parseTime(e){e?this.dataCopy.ExpireDate=d(e).format("YYYY-MM-DD HH:mm:ss"):this.dataCopy.ExpireDate=void 0,this.updateLinkExpireDate()}},computed:{getUrl(){const e=this.store.getActiveCrossServer();return e?`${e.host}/${this.$router.resolve({path:"/share",query:{ShareID:this.dataCopy.ShareID}}).href}`:window.location.origin+window.location.pathname+this.$router.resolve({path:"/share",query:{ShareID:this.dataCopy.ShareID}}).href}}},x={class:"peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll"},v={class:"container d-flex h-100 w-100"},C={class:"m-auto modal-dialog-centered dashboardModal",style:{width:"500px"}},D={class:"card rounded-3 shadow flex-grow-1"},w={class:"card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4"},L={class:"mb-0"},M={key:0,class:"card-body px-4 pb-4"},Y={key:0},$={class:"mb-3 text-muted"},E=["disabled"],H={key:1},V={class:"d-flex gap-2 mb-4"},N=["href"],P={class:"d-flex flex-column gap-2 mb-3"},O=["disabled"];function T(e,a,U,B,I,c){const i=p("LocaleText"),u=p("VueDatePicker");return s(),r("div",x,[t("div",v,[t("div",C,[t("div",D,[t("div",w,[t("h4",L,[o(i,{t:"Share Peer"})]),t("button",{type:"button",class:"btn-close ms-auto",onClick:a[0]||(a[0]=l=>this.$emit("close"))})]),this.peer.ShareLink?(s(),r("div",M,[this.dataCopy?(s(),r("div",H,[t("div",V,[a[4]||(a[4]=t("i",{class:"bi bi-link-45deg"},null,-1)),t("a",{href:this.getUrl,class:"text-decoration-none",target:"_blank"},_(c.getUrl),9,N)]),t("div",P,[t("small",null,[a[5]||(a[5]=t("i",{class:"bi bi-calendar me-2"},null,-1)),o(i,{t:"Expire At"})]),o(u,{is24:!0,"min-date":new Date,"model-value":this.dataCopy.ExpireDate,"onUpdate:modelValue":this.parseTime,"time-picker-inline":"",format:"yyyy-MM-dd HH:mm:ss","preview-format":"yyyy-MM-dd HH:mm:ss",dark:this.store.Configuration.Server.dashboard_theme==="dark"},null,8,["min-date","model-value","onUpdate:modelValue","dark"])]),t("button",{onClick:a[2]||(a[2]=l=>this.stopSharing()),disabled:this.loading,class:"w-100 btn bg-danger-subtle text-danger-emphasis border-1 border-danger-subtle rounded-3 shadow-sm"},[t("span",{class:m({"animate__animated animate__flash animate__infinite animate__slower":this.loading})},a[6]||(a[6]=[t("i",{class:"bi bi-send-slash-fill me-2"},null,-1)]),2),this.loading?(s(),n(i,{key:0,t:"Stop Sharing..."})):(s(),n(i,{key:1,t:"Stop Sharing"}))],8,O)])):(s(),r("div",Y,[t("h6",$,[o(i,{t:"Currently the peer is not sharing"})]),t("button",{onClick:a[1]||(a[1]=l=>this.startSharing()),disabled:this.loading,class:"w-100 btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm"},[t("span",{class:m({"animate__animated animate__flash animate__infinite animate__slower":this.loading})},a[3]||(a[3]=[t("i",{class:"bi bi-send-fill me-2"},null,-1)]),2),this.loading?(s(),n(i,{key:0,t:"Sharing..."})):(s(),n(i,{key:1,t:"Start Sharing"}))],8,E)]))])):b("",!0)])])])])}const R=g(k,[["render",T]]);export{R as default};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.dropdownIcon[data-v-ccf48ac7]{transition:all .2s ease-in-out}.dropdownIcon.active[data-v-ccf48ac7]{transform:rotate(180deg)}.steps{&[data-v-40ee451e]{transition:all .3s ease-in-out;opacity:.3}&.active[data-v-40ee451e]{opacity:1}}

View File

@ -1 +0,0 @@
.dropdownIcon[data-v-626f1988]{transition:all .2s ease-in-out}.dropdownIcon.active[data-v-626f1988]{transform:rotate(180deg)}.steps{&[data-v-f0245d51]{transition:all .3s ease-in-out;opacity:.3}&.active[data-v-f0245d51]{opacity:1}}

File diff suppressed because one or more lines are too long

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 +1 @@
import{_ as u,D as m,y as p,c as r,b as e,d as o,f as c,t as h,e as f,m as l,z as d,a as i,j as w}from"./index-giM6Gyhs.js";import{L as g}from"./localeText-DcZBPGqi.js";const b={name:"setup",components:{LocaleText:g},setup(){return{store:m()}},data(){return{setup:{username:"",newPassword:"",repeatNewPassword:"",enable_totp:!0},loading:!1,errorMessage:"",done:!1}},computed:{goodToSubmit(){return this.setup.username&&this.setup.newPassword.length>=8&&this.setup.repeatNewPassword.length>=8&&this.setup.newPassword===this.setup.repeatNewPassword}},methods:{submit(){this.loading=!0,p("/api/Welcome_Finish",this.setup,n=>{n.status?(this.done=!0,this.$router.push("/2FASetup")):(document.querySelectorAll("#createAccount input").forEach(s=>s.classList.add("is-invalid")),this.errorMessage=n.message,document.querySelector(".login-container-fluid").scrollTo({top:0,left:0,behavior:"smooth"})),this.loading=!1})}}},_=["data-bs-theme"],x={class:"m-auto text-body",style:{width:"500px"}},v={class:"dashboardLogo display-4"},y={class:"mb-5"},P={key:0,class:"alert alert-danger"},N={class:"d-flex flex-column gap-3"},k={id:"createAccount",class:"d-flex flex-column gap-2"},S={class:"form-group text-body"},T={for:"username",class:"mb-1 text-muted"},C={class:"form-group text-body"},L={for:"password",class:"mb-1 text-muted"},V={class:"form-group text-body"},$={for:"confirmPassword",class:"mb-1 text-muted"},q=["disabled"],A={key:0,class:"d-flex align-items-center w-100"},M={key:1,class:"d-flex align-items-center w-100"};function B(n,s,D,E,U,F){const t=w("LocaleText");return i(),r("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.store.Configuration.Server.dashboard_theme},[e("div",x,[e("span",v,[o(t,{t:"Nice to meet you!"})]),e("p",y,[o(t,{t:"Please fill in the following fields to finish setup"}),s[4]||(s[4]=c(" 😊"))]),e("div",null,[e("h3",null,[o(t,{t:"Create an account"})]),this.errorMessage?(i(),r("div",P,h(this.errorMessage),1)):f("",!0),e("div",N,[e("form",k,[e("div",S,[e("label",T,[e("small",null,[o(t,{t:"Enter an username you like"})])]),l(e("input",{type:"text",autocomplete:"username","onUpdate:modelValue":s[0]||(s[0]=a=>this.setup.username=a),class:"form-control",id:"username",name:"username",required:""},null,512),[[d,this.setup.username]])]),e("div",C,[e("label",L,[e("small",null,[o(t,{t:"Enter a password"}),e("code",null,[o(t,{t:"(At least 8 characters and make sure is strong enough!)"})])])]),l(e("input",{type:"password",autocomplete:"new-password","onUpdate:modelValue":s[1]||(s[1]=a=>this.setup.newPassword=a),class:"form-control",id:"password",name:"password",required:""},null,512),[[d,this.setup.newPassword]])]),e("div",V,[e("label",$,[e("small",null,[o(t,{t:"Confirm password"})])]),l(e("input",{type:"password",autocomplete:"confirm-new-password","onUpdate:modelValue":s[2]||(s[2]=a=>this.setup.repeatNewPassword=a),class:"form-control",id:"confirmPassword",name:"confirmPassword",required:""},null,512),[[d,this.setup.repeatNewPassword]])])]),e("button",{class:"btn btn-dark btn-lg mb-5 d-flex btn-brand shadow align-items-center",ref:"signInBtn",disabled:!this.goodToSubmit||this.loading||this.done,onClick:s[3]||(s[3]=a=>this.submit())},[!this.loading&&!this.done?(i(),r("span",A,[o(t,{t:"Next"}),s[5]||(s[5]=e("i",{class:"bi bi-chevron-right ms-auto"},null,-1))])):(i(),r("span",M,[o(t,{t:"Saving..."}),s[6]||(s[6]=e("span",{class:"spinner-border ms-auto spinner-border-sm",role:"status"},[e("span",{class:"visually-hidden"},"Loading...")],-1))]))],8,q)])])])],8,_)}const I=u(b,[["render",B]]);export{I as default}; import{_ as u,D as m,y as p,c as r,b as e,d as o,f as c,t as h,e as f,m as l,z as d,a as i,j as w}from"./index-CRrH8rsP.js";import{L as g}from"./localeText-BwuzYH16.js";const b={name:"setup",components:{LocaleText:g},setup(){return{store:m()}},data(){return{setup:{username:"",newPassword:"",repeatNewPassword:"",enable_totp:!0},loading:!1,errorMessage:"",done:!1}},computed:{goodToSubmit(){return this.setup.username&&this.setup.newPassword.length>=8&&this.setup.repeatNewPassword.length>=8&&this.setup.newPassword===this.setup.repeatNewPassword}},methods:{submit(){this.loading=!0,p("/api/Welcome_Finish",this.setup,n=>{n.status?(this.done=!0,this.$router.push("/2FASetup")):(document.querySelectorAll("#createAccount input").forEach(s=>s.classList.add("is-invalid")),this.errorMessage=n.message,document.querySelector(".login-container-fluid").scrollTo({top:0,left:0,behavior:"smooth"})),this.loading=!1})}}},_=["data-bs-theme"],x={class:"m-auto text-body",style:{width:"500px"}},v={class:"dashboardLogo display-4"},y={class:"mb-5"},P={key:0,class:"alert alert-danger"},N={class:"d-flex flex-column gap-3"},k={id:"createAccount",class:"d-flex flex-column gap-2"},S={class:"form-group text-body"},T={for:"username",class:"mb-1 text-muted"},C={class:"form-group text-body"},L={for:"password",class:"mb-1 text-muted"},V={class:"form-group text-body"},$={for:"confirmPassword",class:"mb-1 text-muted"},q=["disabled"],A={key:0,class:"d-flex align-items-center w-100"},M={key:1,class:"d-flex align-items-center w-100"};function B(n,s,D,E,U,F){const t=w("LocaleText");return i(),r("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.store.Configuration.Server.dashboard_theme},[e("div",x,[e("span",v,[o(t,{t:"Nice to meet you!"})]),e("p",y,[o(t,{t:"Please fill in the following fields to finish setup"}),s[4]||(s[4]=c(" 😊"))]),e("div",null,[e("h3",null,[o(t,{t:"Create an account"})]),this.errorMessage?(i(),r("div",P,h(this.errorMessage),1)):f("",!0),e("div",N,[e("form",k,[e("div",S,[e("label",T,[e("small",null,[o(t,{t:"Enter an username you like"})])]),l(e("input",{type:"text",autocomplete:"username","onUpdate:modelValue":s[0]||(s[0]=a=>this.setup.username=a),class:"form-control",id:"username",name:"username",required:""},null,512),[[d,this.setup.username]])]),e("div",C,[e("label",L,[e("small",null,[o(t,{t:"Enter a password"}),e("code",null,[o(t,{t:"(At least 8 characters and make sure is strong enough!)"})])])]),l(e("input",{type:"password",autocomplete:"new-password","onUpdate:modelValue":s[1]||(s[1]=a=>this.setup.newPassword=a),class:"form-control",id:"password",name:"password",required:""},null,512),[[d,this.setup.newPassword]])]),e("div",V,[e("label",$,[e("small",null,[o(t,{t:"Confirm password"})])]),l(e("input",{type:"password",autocomplete:"confirm-new-password","onUpdate:modelValue":s[2]||(s[2]=a=>this.setup.repeatNewPassword=a),class:"form-control",id:"confirmPassword",name:"confirmPassword",required:""},null,512),[[d,this.setup.repeatNewPassword]])])]),e("button",{class:"btn btn-dark btn-lg mb-5 d-flex btn-brand shadow align-items-center",ref:"signInBtn",disabled:!this.goodToSubmit||this.loading||this.done,onClick:s[3]||(s[3]=a=>this.submit())},[!this.loading&&!this.done?(i(),r("span",A,[o(t,{t:"Next"}),s[5]||(s[5]=e("i",{class:"bi bi-chevron-right ms-auto"},null,-1))])):(i(),r("span",M,[o(t,{t:"Saving..."}),s[6]||(s[6]=e("span",{class:"spinner-border ms-auto spinner-border-sm",role:"status"},[e("span",{class:"visually-hidden"},"Loading...")],-1))]))],8,q)])])])],8,_)}const I=u(b,[["render",B]]);export{I as default};

View File

@ -1 +1 @@
import{_,r,D as p,g as u,c as m,b as t,d as c,a1 as h,a as f,j as b}from"./index-giM6Gyhs.js";import{b as v}from"./browser-CjSdxGTc.js";import{L as y}from"./localeText-DcZBPGqi.js";const g={name:"share",components:{LocaleText:y},async setup(){const o=h(),e=r(!1),i=p(),n=r(""),s=r(void 0),l=r(new Blob);await u("/api/getDashboardTheme",{},d=>{n.value=d.data});const a=o.query.ShareID;return a===void 0||a.length===0?(s.value=void 0,e.value=!0):await u("/api/sharePeer/get",{ShareID:a},d=>{d.status?(s.value=d.data,l.value=new Blob([s.value.file],{type:"text/plain"})):s.value=void 0,e.value=!0}),{store:i,theme:n,peerConfiguration:s,blob:l}},mounted(){this.peerConfiguration&&v.toCanvas(document.querySelector("#qrcode"),this.peerConfiguration.file,o=>{o&&console.error(o)})},methods:{download(){const o=new Blob([this.peerConfiguration.file],{type:"text/plain"}),e=URL.createObjectURL(o),i=`${this.peerConfiguration.fileName}.conf`,n=document.createElement("a");n.href=e,n.download=i,n.click()}},computed:{getBlob(){return URL.createObjectURL(this.blob)}}},w=["data-bs-theme"],x={class:"m-auto text-body",style:{width:"500px"}},C={key:0,class:"text-center position-relative",style:{}},U={class:"position-absolute w-100 h-100 top-0 start-0 d-flex animate__animated animate__fadeInUp",style:{"animation-delay":"0.1s"}},I={class:"m-auto"},L={key:1,class:"d-flex align-items-center flex-column gap-3"},B={class:"h1 dashboardLogo text-center animate__animated animate__fadeInUp"},k={id:"qrcode",class:"rounded-3 shadow animate__animated animate__fadeInUp mb-3",ref:"qrcode"},D={class:"text-muted animate__animated animate__fadeInUp mb-1",style:{"animation-delay":"0.2s"}},R=["download","href"];function j(o,e,i,n,s,l){const a=b("LocaleText");return f(),m("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.theme},[t("div",x,[this.peerConfiguration?(f(),m("div",L,[t("div",B,[e[1]||(e[1]=t("h6",null,"WGDashboard",-1)),c(a,{t:"Scan QR Code with the WireGuard App to add peer"})]),t("canvas",k,null,512),t("p",D,[c(a,{t:"or click the button below to download the "}),e[2]||(e[2]=t("samp",null,".conf",-1)),c(a,{t:" file"})]),t("a",{download:this.peerConfiguration.fileName+".conf",href:l.getBlob,class:"btn btn-lg bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle animate__animated animate__fadeInUp shadow-sm",style:{"animation-delay":"0.25s"}},e[3]||(e[3]=[t("i",{class:"bi bi-download"},null,-1)]),8,R)])):(f(),m("div",C,[e[0]||(e[0]=t("div",{class:"animate__animated animate__fadeInUp"},[t("h1",{style:{"font-size":"20rem",filter:"blur(1rem)","animation-duration":"7s"},class:"animate__animated animate__flash animate__infinite"},[t("i",{class:"bi bi-file-binary"})])],-1)),t("div",U,[t("h3",I,[c(a,{t:"Oh no... This link is either expired or invalid."})])])]))])],8,w)}const N=_(g,[["render",j],["__scopeId","data-v-1b44aacd"]]);export{N as default}; import{_,r,D as p,g as u,c as m,b as t,d as c,a2 as h,a as f,j as b}from"./index-CRrH8rsP.js";import{b as v}from"./browser-CjSdxGTc.js";import{L as y}from"./localeText-BwuzYH16.js";const g={name:"share",components:{LocaleText:y},async setup(){const o=h(),e=r(!1),i=p(),n=r(""),s=r(void 0),l=r(new Blob);await u("/api/getDashboardTheme",{},d=>{n.value=d.data});const a=o.query.ShareID;return a===void 0||a.length===0?(s.value=void 0,e.value=!0):await u("/api/sharePeer/get",{ShareID:a},d=>{d.status?(s.value=d.data,l.value=new Blob([s.value.file],{type:"text/plain"})):s.value=void 0,e.value=!0}),{store:i,theme:n,peerConfiguration:s,blob:l}},mounted(){this.peerConfiguration&&v.toCanvas(document.querySelector("#qrcode"),this.peerConfiguration.file,o=>{o&&console.error(o)})},methods:{download(){const o=new Blob([this.peerConfiguration.file],{type:"text/plain"}),e=URL.createObjectURL(o),i=`${this.peerConfiguration.fileName}.conf`,n=document.createElement("a");n.href=e,n.download=i,n.click()}},computed:{getBlob(){return URL.createObjectURL(this.blob)}}},w=["data-bs-theme"],x={class:"m-auto text-body",style:{width:"500px"}},C={key:0,class:"text-center position-relative",style:{}},U={class:"position-absolute w-100 h-100 top-0 start-0 d-flex animate__animated animate__fadeInUp",style:{"animation-delay":"0.1s"}},I={class:"m-auto"},L={key:1,class:"d-flex align-items-center flex-column gap-3"},B={class:"h1 dashboardLogo text-center animate__animated animate__fadeInUp"},k={id:"qrcode",class:"rounded-3 shadow animate__animated animate__fadeInUp mb-3",ref:"qrcode"},D={class:"text-muted animate__animated animate__fadeInUp mb-1",style:{"animation-delay":"0.2s"}},R=["download","href"];function j(o,e,i,n,s,l){const a=b("LocaleText");return f(),m("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.theme},[t("div",x,[this.peerConfiguration?(f(),m("div",L,[t("div",B,[e[1]||(e[1]=t("h6",null,"WGDashboard",-1)),c(a,{t:"Scan QR Code with the WireGuard App to add peer"})]),t("canvas",k,null,512),t("p",D,[c(a,{t:"or click the button below to download the "}),e[2]||(e[2]=t("samp",null,".conf",-1)),c(a,{t:" file"})]),t("a",{download:this.peerConfiguration.fileName+".conf",href:l.getBlob,class:"btn btn-lg bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle animate__animated animate__fadeInUp shadow-sm",style:{"animation-delay":"0.25s"}},e[3]||(e[3]=[t("i",{class:"bi bi-download"},null,-1)]),8,R)])):(f(),m("div",C,[e[0]||(e[0]=t("div",{class:"animate__animated animate__fadeInUp"},[t("h1",{style:{"font-size":"20rem",filter:"blur(1rem)","animation-duration":"7s"},class:"animate__animated animate__flash animate__infinite"},[t("i",{class:"bi bi-file-binary"})])],-1)),t("div",U,[t("h3",I,[c(a,{t:"Oh no... This link is either expired or invalid."})])])]))])],8,w)}const N=_(g,[["render",j],["__scopeId","data-v-1b44aacd"]]);export{N as default};

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{_ as i,u as m,r as p,p as b,a as o,c as t,d as g,w as v,n as x,b as r,t as n,e as f,T as C,q as w}from"./index-giM6Gyhs.js";const y={class:"text-muted me-2"},_={class:"fw-bold"},k={__name:"cpuCore",props:{core_number:Number,percentage:Number,align:Boolean,square:Boolean},setup(e){m(c=>({e901480c:u.value}));const l=e,s=p(!1),u=b(()=>l.square?"40px":"25px");return(c,a)=>(o(),t("div",{class:"flex-grow-1 square rounded-3 border position-relative p-2",onMouseenter:a[0]||(a[0]=d=>s.value=!0),onMouseleave:a[1]||(a[1]=d=>s.value=!1),style:w({"background-color":`rgb(13 110 253 / ${e.percentage*10}%)`})},[g(C,{name:"zoomReversed"},{default:v(()=>[s.value?(o(),t("div",{key:0,style:{"white-space":"nowrap"},class:x(["floatingLabel z-3 border position-absolute d-block p-1 px-2 bg-body text-body rounded-3 border shadow d-flex",[e.align?"end-0":"start-0"]])},[r("small",y," Core #"+n(e.core_number+1),1),r("small",_,n(e.percentage)+"% ",1)],2)):f("",!0)]),_:1})],36))}},B=i(k,[["__scopeId","data-v-70102637"]]);export{B as C}; import{_ as i,u as m,r as p,p as b,a as o,c as t,d as g,w as v,n as x,b as r,t as n,e as f,T as C,q as w}from"./index-CRrH8rsP.js";const y={class:"text-muted me-2"},_={class:"fw-bold"},k={__name:"cpuCore",props:{core_number:Number,percentage:Number,align:Boolean,square:Boolean},setup(e){m(c=>({e901480c:u.value}));const l=e,s=p(!1),u=b(()=>l.square?"40px":"25px");return(c,a)=>(o(),t("div",{class:"flex-grow-1 square rounded-3 border position-relative p-2",onMouseenter:a[0]||(a[0]=d=>s.value=!0),onMouseleave:a[1]||(a[1]=d=>s.value=!1),style:w({"background-color":`rgb(13 110 253 / ${e.percentage*10}%)`})},[g(C,{name:"zoomReversed"},{default:v(()=>[s.value?(o(),t("div",{key:0,style:{"white-space":"nowrap"},class:x(["floatingLabel z-3 border position-absolute d-block p-1 px-2 bg-body text-body rounded-3 border shadow d-flex",[e.align?"end-0":"start-0"]])},[r("small",y," Core #"+n(e.core_number+1),1),r("small",_,n(e.percentage)+"% ",1)],2)):f("",!0)]),_:1})],36))}},B=i(k,[["__scopeId","data-v-70102637"]]);export{B as C};

View File

@ -1 +1 @@
import{_ as h,D as m,g as p,y as f,c as b,b as t,d as i,t as _,m as v,z as g,i as d,w as r,j as c,a as n}from"./index-giM6Gyhs.js";import{b as x}from"./browser-CjSdxGTc.js";import{L as y}from"./localeText-DcZBPGqi.js";const T={name:"totp",components:{LocaleText:y},async setup(){const s=m();let e="";return await p("/api/Welcome_GetTotpLink",{},a=>{a.status&&(e=a.data)}),{l:e,store:s}},mounted(){this.l&&x.toCanvas(document.getElementById("qrcode"),this.l,function(s){})},data(){return{totp:"",totpInvalidMessage:"",verified:!1}},methods:{validateTotp(){}},watch:{totp(s){const e=document.querySelector("#totp");e.classList.remove("is-invalid","is-valid"),s.length===6&&(console.log(s),/[0-9]{6}/.test(s)?f("/api/Welcome_VerifyTotpLink",{totp:s},a=>{a.status?(this.verified=!0,e.classList.add("is-valid"),this.$emit("verified")):(e.classList.add("is-invalid"),this.totpInvalidMessage="TOTP does not match.")}):(e.classList.add("is-invalid"),this.totpInvalidMessage="TOTP can only contain numbers"))}}},k=["data-bs-theme"],w={class:"m-auto text-body",style:{width:"500px"}},L={class:"d-flex flex-column"},M={class:"dashboardLogo display-4"},C={class:"mb-2"},P={class:"text-muted"},I={class:"p-3 bg-body-secondary rounded-3 border mb-3"},O={class:"text-muted mb-0"},B=["href"],$={style:{"line-break":"anywhere"}},D={for:"totp",class:"mb-2"},S={class:"text-muted"},q={class:"form-group mb-2"},A=["disabled"],E={class:"invalid-feedback"},F={class:"valid-feedback"},R={class:"d-flex gap-3 mt-5 flex-column"};function G(s,e,a,N,W,j){const o=c("LocaleText"),l=c("RouterLink");return n(),b("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.store.Configuration.Server.dashboard_theme},[t("div",w,[t("div",L,[t("div",null,[t("h1",M,[i(o,{t:"Multi-Factor Authentication (MFA)"})]),t("p",C,[t("small",P,[i(o,{t:"1. Please scan the following QR Code to generate TOTP with your choice of authenticator"})])]),e[1]||(e[1]=t("canvas",{id:"qrcode",class:"rounded-3 mb-2"},null,-1)),t("div",I,[t("p",O,[t("small",null,[i(o,{t:"Or you can click the link below:"})])]),t("a",{href:this.l},[t("code",$,_(this.l),1)],8,B)]),t("label",D,[t("small",S,[i(o,{t:"2. Enter the TOTP generated by your authenticator to verify"})])]),t("div",q,[v(t("input",{class:"form-control text-center totp",id:"totp",maxlength:"6",type:"text",inputmode:"numeric",autocomplete:"one-time-code","onUpdate:modelValue":e[0]||(e[0]=u=>this.totp=u),disabled:this.verified},null,8,A),[[g,this.totp]]),t("div",E,[i(o,{t:this.totpInvalidMessage},null,8,["t"])]),t("div",F,[i(o,{t:"TOTP verified!"})])])]),e[4]||(e[4]=t("hr",null,null,-1)),t("div",R,[this.verified?(n(),d(l,{key:1,to:"/",class:"btn btn-dark btn-lg d-flex btn-brand shadow align-items-center flex-grow-1 rounded-3"},{default:r(()=>[i(o,{t:"Complete"}),e[3]||(e[3]=t("i",{class:"bi bi-chevron-right ms-auto"},null,-1))]),_:1})):(n(),d(l,{key:0,to:"/",class:"btn bg-secondary-subtle text-secondary-emphasis rounded-3 flex-grow-1 btn-lg border-1 border-secondary-subtle shadow d-flex"},{default:r(()=>[i(o,{t:"I don't need MFA"}),e[2]||(e[2]=t("i",{class:"bi bi-chevron-right ms-auto"},null,-1))]),_:1}))])])])],8,k)}const V=h(T,[["render",G]]);export{V as default}; import{_ as h,D as m,g as p,y as f,c as b,b as t,d as i,t as _,m as v,z as g,i as d,w as r,j as c,a as n}from"./index-CRrH8rsP.js";import{b as x}from"./browser-CjSdxGTc.js";import{L as y}from"./localeText-BwuzYH16.js";const T={name:"totp",components:{LocaleText:y},async setup(){const s=m();let e="";return await p("/api/Welcome_GetTotpLink",{},a=>{a.status&&(e=a.data)}),{l:e,store:s}},mounted(){this.l&&x.toCanvas(document.getElementById("qrcode"),this.l,function(s){})},data(){return{totp:"",totpInvalidMessage:"",verified:!1}},methods:{validateTotp(){}},watch:{totp(s){const e=document.querySelector("#totp");e.classList.remove("is-invalid","is-valid"),s.length===6&&(console.log(s),/[0-9]{6}/.test(s)?f("/api/Welcome_VerifyTotpLink",{totp:s},a=>{a.status?(this.verified=!0,e.classList.add("is-valid"),this.$emit("verified")):(e.classList.add("is-invalid"),this.totpInvalidMessage="TOTP does not match.")}):(e.classList.add("is-invalid"),this.totpInvalidMessage="TOTP can only contain numbers"))}}},k=["data-bs-theme"],w={class:"m-auto text-body",style:{width:"500px"}},L={class:"d-flex flex-column"},M={class:"dashboardLogo display-4"},C={class:"mb-2"},P={class:"text-muted"},I={class:"p-3 bg-body-secondary rounded-3 border mb-3"},O={class:"text-muted mb-0"},B=["href"],$={style:{"line-break":"anywhere"}},D={for:"totp",class:"mb-2"},S={class:"text-muted"},q={class:"form-group mb-2"},A=["disabled"],E={class:"invalid-feedback"},F={class:"valid-feedback"},R={class:"d-flex gap-3 mt-5 flex-column"};function G(s,e,a,N,W,j){const o=c("LocaleText"),l=c("RouterLink");return n(),b("div",{class:"container-fluid login-container-fluid d-flex main pt-5 overflow-scroll","data-bs-theme":this.store.Configuration.Server.dashboard_theme},[t("div",w,[t("div",L,[t("div",null,[t("h1",M,[i(o,{t:"Multi-Factor Authentication (MFA)"})]),t("p",C,[t("small",P,[i(o,{t:"1. Please scan the following QR Code to generate TOTP with your choice of authenticator"})])]),e[1]||(e[1]=t("canvas",{id:"qrcode",class:"rounded-3 mb-2"},null,-1)),t("div",I,[t("p",O,[t("small",null,[i(o,{t:"Or you can click the link below:"})])]),t("a",{href:this.l},[t("code",$,_(this.l),1)],8,B)]),t("label",D,[t("small",S,[i(o,{t:"2. Enter the TOTP generated by your authenticator to verify"})])]),t("div",q,[v(t("input",{class:"form-control text-center totp",id:"totp",maxlength:"6",type:"text",inputmode:"numeric",autocomplete:"one-time-code","onUpdate:modelValue":e[0]||(e[0]=u=>this.totp=u),disabled:this.verified},null,8,A),[[g,this.totp]]),t("div",E,[i(o,{t:this.totpInvalidMessage},null,8,["t"])]),t("div",F,[i(o,{t:"TOTP verified!"})])])]),e[4]||(e[4]=t("hr",null,null,-1)),t("div",R,[this.verified?(n(),d(l,{key:1,to:"/",class:"btn btn-dark btn-lg d-flex btn-brand shadow align-items-center flex-grow-1 rounded-3"},{default:r(()=>[i(o,{t:"Complete"}),e[3]||(e[3]=t("i",{class:"bi bi-chevron-right ms-auto"},null,-1))]),_:1})):(n(),d(l,{key:0,to:"/",class:"btn bg-secondary-subtle text-secondary-emphasis rounded-3 flex-grow-1 btn-lg border-1 border-secondary-subtle shadow d-flex"},{default:r(()=>[i(o,{t:"I don't need MFA"}),e[2]||(e[2]=t("i",{class:"bi bi-chevron-right ms-auto"},null,-1))]),_:1}))])])])],8,k)}const V=h(T,[["render",G]]);export{V as default};

View File

@ -1 +1 @@
import{_ as h,W as b,g,c as o,b as t,d as n,m as y,z as f,B as v,w as r,T as c,a,f as x,F as u,h as m,n as T,q as k,t as i,e as A,j as _}from"./index-giM6Gyhs.js";import{O as w}from"./osmap-BslGH_D_.js";import{L as R}from"./localeText-DcZBPGqi.js";const M={name:"traceroute",components:{LocaleText:R,OSMap:w},data(){return{tracing:!1,ipAddress:void 0,tracerouteResult:void 0}},setup(){return{store:b()}},methods:{execute(){this.ipAddress&&(this.tracing=!0,this.tracerouteResult=void 0,g("/api/traceroute/execute",{ipAddress:this.ipAddress},d=>{d.status?this.tracerouteResult=d.data:this.store.newMessage("Server",d.message,"danger"),this.tracing=!1}))}}},S={class:"mt-md-5 mt-3 text-body"},$={class:"container-md"},C={class:"mb-3 text-body"},L={class:"d-flex gap-2 flex-column mb-5"},P={class:"mb-1 text-muted",for:"ipAddress"},V=["disabled"],B=["disabled"],N={key:0,class:"d-block"},O={key:1,class:"d-block"},z={class:"position-relative"},I={key:"pingPlaceholder"},D={key:1},E={key:"table",class:"w-100 mt-2"},F={class:"table table-sm rounded-3 w-100"},G={scope:"col"},H={scope:"col"},K={scope:"col"},W={scope:"col"},j={scope:"col"},q={scope:"col"},U={key:0};function J(d,s,Q,X,Y,Z){const l=_("LocaleText"),p=_("OSMap");return a(),o("div",S,[t("div",$,[t("h3",C,[n(l,{t:"Traceroute"})]),t("div",L,[t("div",null,[t("label",P,[t("small",null,[n(l,{t:"Enter IP Address / Hostname"})])]),y(t("input",{disabled:this.tracing,id:"ipAddress",class:"form-control","onUpdate:modelValue":s[0]||(s[0]=e=>this.ipAddress=e),onKeyup:s[1]||(s[1]=v(e=>this.execute(),["enter"])),type:"text"},null,40,V),[[f,this.ipAddress]])]),t("button",{class:"btn btn-primary rounded-3 mt-3 position-relative",disabled:this.tracing||!this.ipAddress,onClick:s[2]||(s[2]=e=>this.execute())},[n(c,{name:"slide"},{default:r(()=>[this.tracing?(a(),o("span",O,s[4]||(s[4]=[t("span",{class:"spinner-border spinner-border-sm","aria-hidden":"true"},null,-1),t("span",{class:"visually-hidden",role:"status"},"Loading...",-1)]))):(a(),o("span",N,s[3]||(s[3]=[t("i",{class:"bi bi-person-walking me-2"},null,-1),x("Trace! ")])))]),_:1})],8,B)]),t("div",z,[n(c,{name:"ping"},{default:r(()=>[this.tracerouteResult?(a(),o("div",D,[n(p,{d:this.tracerouteResult,type:"traceroute"},null,8,["d"]),t("div",E,[t("table",F,[t("thead",null,[t("tr",null,[t("th",G,[n(l,{t:"Hop"})]),t("th",H,[n(l,{t:"IP Address"})]),t("th",K,[n(l,{t:"Average RTT (ms)"})]),t("th",W,[n(l,{t:"Min RTT (ms)"})]),t("th",j,[n(l,{t:"Max RTT (ms)"})]),t("th",q,[n(l,{t:"Geolocation"})])])]),t("tbody",null,[(a(!0),o(u,null,m(this.tracerouteResult,(e,tt)=>(a(),o("tr",null,[t("td",null,[t("small",null,i(e.hop),1)]),t("td",null,[t("small",null,i(e.ip),1)]),t("td",null,[t("small",null,i(e.avg_rtt),1)]),t("td",null,[t("small",null,i(e.min_rtt),1)]),t("td",null,[t("small",null,i(e.max_rtt),1)]),t("td",null,[e.geo.city&&e.geo.country?(a(),o("span",U,[t("small",null,i(e.geo.city)+", "+i(e.geo.country),1)])):A("",!0)])]))),256))])])])])):(a(),o("div",I,[s[5]||(s[5]=t("div",{class:"pingPlaceholder bg-body-secondary rounded-3 mb-3",style:{height:"300px !important"}},null,-1)),(a(),o(u,null,m(5,e=>t("div",{class:T(["pingPlaceholder bg-body-secondary rounded-3 mb-3",{"animate__animated animate__flash animate__slower animate__infinite":this.tracing}]),style:k({"animation-delay":`${e*.05}s`})},null,6)),64))]))]),_:1})])])])}const ot=h(M,[["render",J],["__scopeId","data-v-549eb223"]]);export{ot as default}; import{_ as h,W as b,g,c as o,b as t,d as n,m as y,z as f,B as v,w as r,T as c,a,f as x,F as u,h as m,n as T,q as k,t as i,e as A,j as _}from"./index-CRrH8rsP.js";import{O as w}from"./osmap-w8OMy1AG.js";import{L as R}from"./localeText-BwuzYH16.js";const M={name:"traceroute",components:{LocaleText:R,OSMap:w},data(){return{tracing:!1,ipAddress:void 0,tracerouteResult:void 0}},setup(){return{store:b()}},methods:{execute(){this.ipAddress&&(this.tracing=!0,this.tracerouteResult=void 0,g("/api/traceroute/execute",{ipAddress:this.ipAddress},d=>{d.status?this.tracerouteResult=d.data:this.store.newMessage("Server",d.message,"danger"),this.tracing=!1}))}}},S={class:"mt-md-5 mt-3 text-body"},$={class:"container-md"},C={class:"mb-3 text-body"},L={class:"d-flex gap-2 flex-column mb-5"},P={class:"mb-1 text-muted",for:"ipAddress"},V=["disabled"],B=["disabled"],N={key:0,class:"d-block"},O={key:1,class:"d-block"},z={class:"position-relative"},I={key:"pingPlaceholder"},D={key:1},E={key:"table",class:"w-100 mt-2"},F={class:"table table-sm rounded-3 w-100"},G={scope:"col"},H={scope:"col"},K={scope:"col"},W={scope:"col"},j={scope:"col"},q={scope:"col"},U={key:0};function J(d,s,Q,X,Y,Z){const l=_("LocaleText"),p=_("OSMap");return a(),o("div",S,[t("div",$,[t("h3",C,[n(l,{t:"Traceroute"})]),t("div",L,[t("div",null,[t("label",P,[t("small",null,[n(l,{t:"Enter IP Address / Hostname"})])]),y(t("input",{disabled:this.tracing,id:"ipAddress",class:"form-control","onUpdate:modelValue":s[0]||(s[0]=e=>this.ipAddress=e),onKeyup:s[1]||(s[1]=v(e=>this.execute(),["enter"])),type:"text"},null,40,V),[[f,this.ipAddress]])]),t("button",{class:"btn btn-primary rounded-3 mt-3 position-relative",disabled:this.tracing||!this.ipAddress,onClick:s[2]||(s[2]=e=>this.execute())},[n(c,{name:"slide"},{default:r(()=>[this.tracing?(a(),o("span",O,s[4]||(s[4]=[t("span",{class:"spinner-border spinner-border-sm","aria-hidden":"true"},null,-1),t("span",{class:"visually-hidden",role:"status"},"Loading...",-1)]))):(a(),o("span",N,s[3]||(s[3]=[t("i",{class:"bi bi-person-walking me-2"},null,-1),x("Trace! ")])))]),_:1})],8,B)]),t("div",z,[n(c,{name:"ping"},{default:r(()=>[this.tracerouteResult?(a(),o("div",D,[n(p,{d:this.tracerouteResult,type:"traceroute"},null,8,["d"]),t("div",E,[t("table",F,[t("thead",null,[t("tr",null,[t("th",G,[n(l,{t:"Hop"})]),t("th",H,[n(l,{t:"IP Address"})]),t("th",K,[n(l,{t:"Average RTT (ms)"})]),t("th",W,[n(l,{t:"Min RTT (ms)"})]),t("th",j,[n(l,{t:"Max RTT (ms)"})]),t("th",q,[n(l,{t:"Geolocation"})])])]),t("tbody",null,[(a(!0),o(u,null,m(this.tracerouteResult,(e,tt)=>(a(),o("tr",null,[t("td",null,[t("small",null,i(e.hop),1)]),t("td",null,[t("small",null,i(e.ip),1)]),t("td",null,[t("small",null,i(e.avg_rtt),1)]),t("td",null,[t("small",null,i(e.min_rtt),1)]),t("td",null,[t("small",null,i(e.max_rtt),1)]),t("td",null,[e.geo.city&&e.geo.country?(a(),o("span",U,[t("small",null,i(e.geo.city)+", "+i(e.geo.country),1)])):A("",!0)])]))),256))])])])])):(a(),o("div",I,[s[5]||(s[5]=t("div",{class:"pingPlaceholder bg-body-secondary rounded-3 mb-3",style:{height:"300px !important"}},null,-1)),(a(),o(u,null,m(5,e=>t("div",{class:T(["pingPlaceholder bg-body-secondary rounded-3 mb-3",{"animate__animated animate__flash animate__slower animate__infinite":this.tracing}]),style:k({"animation-delay":`${e*.05}s`})},null,6)),64))]))]),_:1})])])])}const ot=h(M,[["render",J],["__scopeId","data-v-549eb223"]]);export{ot as default};

View File

@ -10,8 +10,8 @@
<link rel="icon" href="/static/app/dist/favicon.png"> <link rel="icon" href="/static/app/dist/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WGDashboard</title> <title>WGDashboard</title>
<script type="module" crossorigin src="/static/app/dist/assets/index-giM6Gyhs.js"></script> <script type="module" crossorigin src="/static/app/dist/assets/index-CRrH8rsP.js"></script>
<link rel="stylesheet" crossorigin href="/static/app/dist/assets/index-CMJDrSU9.css"> <link rel="stylesheet" crossorigin href="/static/app/dist/assets/index-BdzpqjNP.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -145,49 +145,61 @@ watch(data, () => {
id="configuration_listen_port"> id="configuration_listen_port">
</div> </div>
<div> <div v-for="key in ['PreUp', 'PreDown', 'PostUp', 'PostDown']">
<label for="configuration_preup" class="form-label"> <label :for="'configuration_' + key" class="form-label">
<small class="text-muted"> <small class="text-muted">
<LocaleText t="PreUp"></LocaleText> <LocaleText :t="key"></LocaleText>
</small> </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="saving" :disabled="saving"
v-model="data.PreUp" v-model="data[key]"
id="configuration_preup"> :id="'configuration_' + key">
</div> </div>
<div> <!-- <div>-->
<label for="configuration_predown" class="form-label"> <!-- <label for="configuration_predown" class="form-label">-->
<!-- <small class="text-muted">-->
<!-- <LocaleText t="PreDown"></LocaleText>-->
<!-- </small>-->
<!-- </label>-->
<!-- <input type="text" class="form-control form-control-sm rounded-3"-->
<!-- :disabled="saving"-->
<!-- v-model="data.PreDown"-->
<!-- id="configuration_predown">-->
<!-- </div>-->
<!-- <div>-->
<!-- <label for="configuration_postup" class="form-label">-->
<!-- <small class="text-muted">-->
<!-- <LocaleText t="PostUp"></LocaleText>-->
<!-- </small>-->
<!-- </label>-->
<!-- <input type="text" class="form-control form-control-sm rounded-3"-->
<!-- :disabled="saving"-->
<!-- v-model="data.PostUp"-->
<!-- id="configuration_postup">-->
<!-- </div>-->
<!-- <div>-->
<!-- <label for="configuration_postdown" class="form-label">-->
<!-- <small class="text-muted">-->
<!-- <LocaleText t="PostDown"></LocaleText>-->
<!-- </small>-->
<!-- </label>-->
<!-- <input type="text" class="form-control form-control-sm rounded-3"-->
<!-- :disabled="saving"-->
<!-- v-model="data.PostDown"-->
<!-- id="configuration_postdown">-->
<!-- </div>-->
<div v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4']"
v-if="configurationInfo.Protocol === 'awg'">
<label :for="'configuration_' + key" class="form-label">
<small class="text-muted"> <small class="text-muted">
<LocaleText t="PreDown"></LocaleText> <LocaleText :t="key"></LocaleText>
</small> </small>
</label> </label>
<input type="text" class="form-control form-control-sm rounded-3" <input type="number" class="form-control form-control-sm rounded-3"
:disabled="saving" :disabled="saving"
v-model="data.PreDown" v-model="data[key]"
id="configuration_predown"> :id="'configuration_' + key">
</div>
<div>
<label for="configuration_postup" class="form-label">
<small class="text-muted">
<LocaleText t="PostUp"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="data.PostUp"
id="configuration_postup">
</div>
<div>
<label for="configuration_postdown" class="form-label">
<small class="text-muted">
<LocaleText t="PostDown"></LocaleText>
</small>
</label>
<input type="text" class="form-control form-control-sm rounded-3"
:disabled="saving"
v-model="data.PostDown"
id="configuration_postdown">
</div> </div>
<div class="d-flex align-items-center gap-2 mt-4"> <div class="d-flex align-items-center gap-2 mt-4">
<button class="btn bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3 shadow ms-auto" <button class="btn bg-secondary-subtle border-secondary-subtle text-secondary-emphasis rounded-3 shadow ms-auto"

View File

@ -81,6 +81,14 @@ export default {
<samp>{{Peer.allowed_ip}}</samp> <samp>{{Peer.allowed_ip}}</samp>
</small> </small>
</div> </div>
<div v-if="Peer.advanced_security">
<small class="text-muted">
<LocaleText t="Advanced Security"></LocaleText>
</small>
<small class="d-block">
<samp>{{Peer.advanced_security}}</samp>
</small>
</div>
<div class="d-flex align-items-end"> <div class="d-flex align-items-end">
<div class="ms-auto px-2 rounded-3 subMenuBtn" <div class="ms-auto px-2 rounded-3 subMenuBtn"

View File

@ -36,7 +36,8 @@ export default {
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 preshared_key_bulkAdd: false,
advanced_security: "off",
}, },
availableIp: undefined, availableIp: undefined,
availableIpSearchString: "", availableIpSearchString: "",
@ -85,6 +86,9 @@ export default {
}); });
} }
return status; return status;
},
getProtocol(){
return this.store.Configurations.find(x => x.Name === this.$route.params.id).Protocol;
} }
}, },
watch: { watch: {
@ -152,6 +156,36 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<hr>
<div v-if="this.getProtocol === 'awg'">
<h5>
<LocaleText t="AmneziaWG Peer Setting"></LocaleText>
</h5>
<div >
<label class="form-label d-block"><small class="text-muted">
<LocaleText t="Advanced Security"></LocaleText>
</small></label>
<div class="btn-group" role="group">
<input type="radio" class="btn-check"
v-model="this.data.advanced_security"
value="on"
name="advanced_security_radio" id="advanced_security_on" autocomplete="off">
<label class="btn btn-outline-primary btn-sm" for="advanced_security_on">
<LocaleText t="On"></LocaleText>
</label>
<input type="radio"
v-model="this.data.advanced_security"
value="off"
class="btn-check" name="advanced_security_radio" id="advanced_security_off" autocomplete="off">
<label class="btn btn-outline-primary btn-sm" for="advanced_security_off">
<LocaleText t="Off"></LocaleText>
</label>
</div>
</div>
</div>
<hr>
<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"
:disabled="!this.allRequireFieldsFilled || this.saving" :disabled="!this.allRequireFieldsFilled || this.saving"

View File

@ -425,9 +425,16 @@ export default {
<div v-if="!this.loading" class="container-md"> <div v-if="!this.loading" class="container-md">
<div class="d-flex align-items-sm-center flex-column flex-sm-row gap-3"> <div class="d-flex align-items-sm-center flex-column flex-sm-row gap-3">
<div> <div>
<small CLASS="text-muted"> <div class="text-muted d-flex align-items-center gap-2">
<LocaleText t="CONFIGURATION"></LocaleText> <h5 class="mb-0">
</small> <span class="badge wireguardBg rounded-3 shadow" v-if="this.configurationInfo.Protocol === 'wg'">
WireGuard <LocaleText t="Configuration"></LocaleText>
</span>
<span class="badge amneziawgBg rounded-3 shadow" v-else>
AmneziaWG <LocaleText t="Configuration"></LocaleText>
</span>
</h5>
</div>
<div class="d-flex align-items-center gap-3"> <div class="d-flex align-items-center gap-3">
<h1 class="mb-0 display-4"><samp>{{this.configurationInfo.Name}}</samp></h1> <h1 class="mb-0 display-4"><samp>{{this.configurationInfo.Name}}</samp></h1>
</div> </div>
@ -496,6 +503,7 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<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 h-100"> <div class="card rounded-3 bg-transparent h-100">
<div class="card-body py-2 d-flex flex-column justify-content-center"> <div class="card-body py-2 d-flex flex-column justify-content-center">

View File

@ -61,7 +61,7 @@ export default {
}, },
mounted() { mounted() {
this.$el.querySelectorAll("input").forEach(x => { this.$el.querySelectorAll("input").forEach(x => {
x.addEventListener("keyup", () => { x.addEventListener("change", () => {
this.dataChanged = true; this.dataChanged = true;
}); });
}) })
@ -198,6 +198,30 @@ export default {
v-model="this.data.keepalive" v-model="this.data.keepalive"
id="peer_keep_alive"> id="peer_keep_alive">
</div> </div>
<div v-if="this.data.advanced_security">
<label for="peer_advance_security" class="form-label d-block">
<small class="text-muted">
<LocaleText t="Advanced Security"></LocaleText>
</small>
</label>
<div class="btn-group" role="group">
<input type="radio" class="btn-check"
v-model="this.data.advanced_security"
value="on"
name="advanced_security_radio" id="advanced_security_on" autocomplete="off">
<label class="btn btn-outline-primary btn-sm" for="advanced_security_on">
<LocaleText t="On"></LocaleText>
</label>
<input type="radio"
v-model="this.data.advanced_security"
value="off"
class="btn-check" name="advanced_security_radio" id="advanced_security_off" autocomplete="off">
<label class="btn btn-outline-primary btn-sm" for="advanced_security_off">
<LocaleText t="Off"></LocaleText>
</label>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -49,7 +49,17 @@ export default {
<div class="card conf_card rounded-3 shadow text-decoration-none"> <div class="card conf_card rounded-3 shadow text-decoration-none">
<RouterLink :to="'/configuration/' + c.Name + '/peers'" class="card-body d-flex align-items-center gap-3 flex-wrap text-decoration-none"> <RouterLink :to="'/configuration/' + c.Name + '/peers'" class="card-body d-flex align-items-center gap-3 flex-wrap text-decoration-none">
<h6 class="mb-0"><span class="dot" :class="{active: c.Status}"></span></h6> <h6 class="mb-0"><span class="dot" :class="{active: c.Status}"></span></h6>
<h6 class="card-title mb-0"><samp>{{c.Name}}</samp></h6> <h6 class="card-title mb-0 d-flex align-items-center gap-3">
<samp>{{c.Name}}</samp>
<small>
<span class="badge wireguardBg" v-if="c.Protocol === 'wg'">
WireGuard
</span>
<span class="badge amneziawgBg" v-else>
AmneziaWG
</span>
</small>
</h6>
<h6 class="mb-0 ms-auto"> <h6 class="mb-0 ms-auto">
<i class="bi bi-chevron-right"></i> <i class="bi bi-chevron-right"></i>
</h6> </h6>

View File

@ -0,0 +1,21 @@
<script setup>
import LocaleText from "@/components/text/localeText.vue";
const props = defineProps({
protocol: String,
mini: false
})
</script>
<template>
<span class="badge wireguardBg rounded-3 shadow" v-if="protocol === 'wg'">
WireGuard <LocaleText t="Configuration" v-if="!mini"></LocaleText>
</span>
<span class="badge amneziawgBg rounded-3 shadow" v-else-if="protocol === 'awg'">
AmneziaWG <LocaleText t="Configuration" v-if="!mini"></LocaleText>
</span>
</template>
<style scoped>
</style>

View File

@ -2,12 +2,14 @@
import {onMounted, ref} from "vue"; import {onMounted, ref} from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import LocaleText from "@/components/text/localeText.vue"; import LocaleText from "@/components/text/localeText.vue";
import ProtocolBadge from "@/components/protocolBadge.vue";
const props = defineProps({ const props = defineProps({
configurationName: String, configurationName: String,
backups: Array, backups: Array,
open: false, open: false,
selectedConfigurationBackup: Object selectedConfigurationBackup: Object,
protocol: Array
}) })
const emit = defineEmits(["select"]) const emit = defineEmits(["select"])
@ -28,18 +30,20 @@ onMounted(() => {
<template> <template>
<div class="card rounded-3 shadow-sm"> <div class="card rounded-3 shadow-sm">
<a role="button" class="card-body d-flex align-items-center text-decoration-none" @click="showBackups = !showBackups"> <a role="button" class="card-body d-flex align-items-center text-decoration-none d-flex gap-3" @click="showBackups = !showBackups">
<div class="d-flex gap-3 align-items-center"> <h6 class="mb-0 d-flex align-items-center gap-3">
<h6 class="mb-0">
<samp> <samp>
{{configurationName}} {{configurationName}}
</samp> </samp>
<ProtocolBadge
v-for="p in protocol"
:protocol="p"></ProtocolBadge>
</h6> </h6>
<small class="text-muted"> <small class="text-muted ms-auto d-block">
<LocaleText :t="backups.length + (backups.length > 1 ? ' Backups':' Backup')"></LocaleText> <LocaleText :t="backups.length + (backups.length > 1 ? ' Backups':' Backup')"></LocaleText>
</small> </small>
</div> <h5 class="mb-0 dropdownIcon text-muted" :class="{active: showBackups}">
<h5 class="ms-auto mb-0 dropdownIcon text-muted" :class="{active: showBackups}">
<i class="bi bi-chevron-down"></i> <i class="bi bi-chevron-down"></i>
</h5> </h5>
</a> </a>

View File

@ -6,6 +6,7 @@ import {parse} from "cidr-tools";
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 {useRouter} from "vue-router"; import {useRouter} from "vue-router";
import ProtocolBadge from "@/components/protocolBadge.vue";
const props = defineProps({ const props = defineProps({
@ -14,7 +15,8 @@ const props = defineProps({
const newConfiguration = reactive({ const newConfiguration = reactive({
ConfigurationName: props.selectedConfigurationBackup.filename.split("_")[0], ConfigurationName: props.selectedConfigurationBackup.filename.split("_")[0],
Backup: props.selectedConfigurationBackup.filename Backup: props.selectedConfigurationBackup.filename,
Protocol: props.selectedConfigurationBackup.protocol
}) })
const lineSplit = props.selectedConfigurationBackup.content.split("\n"); const lineSplit = props.selectedConfigurationBackup.content.split("\n");
@ -136,6 +138,16 @@ const submitRestore = async () => {
<LocaleText t="Configuration"></LocaleText> <LocaleText t="Configuration"></LocaleText>
</h4> </h4>
</div> </div>
<div>
<label class="text-muted mb-1">
<small>
<LocaleText t="Protocol"></LocaleText>
</small>
</label>
<h5 class="mb-0">
<ProtocolBadge :protocol="selectedConfigurationBackup.protocol" :mini="true"></ProtocolBadge>
</h5>
</div>
<div> <div>
<label class="text-muted mb-1" for="ConfigurationName"><small> <label class="text-muted mb-1" for="ConfigurationName"><small>
<LocaleText t="Configuration Name"></LocaleText> <LocaleText t="Configuration Name"></LocaleText>

View File

@ -1258,3 +1258,14 @@ pre.index-alert {
samp{ samp{
word-wrap: anywhere; word-wrap: anywhere;
} }
.amneziawgBg{
background: rgb(145,199,193);
background: linear-gradient(90deg, rgba(145,199,193,1) 0%, rgba(107,95,161,1) 50%, rgba(227,142,65,1) 100%);
}
.wireguardBg{
background: rgb(125,32,32);
background: linear-gradient(90deg, rgba(125,32,32,1) 0%, rgba(255,56,56,1) 100%);
}

View File

@ -180,12 +180,8 @@ router.beforeEach(async (to, from, next) => {
next() next()
} }
}else { }else {
if (to.path === "/signin" && await checkAuth()){
next("/")
}else{
next() next()
} }
}
}); });
router.afterEach(() => { router.afterEach(() => {

View File

@ -2,15 +2,22 @@
import {parse} from "cidr-tools"; import {parse} from "cidr-tools";
import '@/utilities/wireguard.js' import '@/utilities/wireguard.js'
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {fetchPost} from "@/utilities/fetch.js"; import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import LocaleText from "@/components/text/localeText.vue"; import LocaleText from "@/components/text/localeText.vue";
import {ref} from "vue";
export default { export default {
name: "newConfiguration", name: "newConfiguration",
components: {LocaleText}, components: {LocaleText},
setup(){ async setup(){
const store = WireguardConfigurationsStore() const store = WireguardConfigurationsStore()
return {store} const protocols = ref([])
await fetchGet("/api/protocolsEnabled", {}, (res) => {
protocols.value = res.data
})
return {store, protocols}
}, },
data(){ data(){
return { return {
@ -24,7 +31,17 @@ export default {
PreUp: "", PreUp: "",
PreDown: "", PreDown: "",
PostUp: "", PostUp: "",
PostDown: "" PostDown: "",
Protocol: "wg",
Jc: 5,
Jmin: 49,
Jmax: 998,
S1: 17,
S2: 110,
H1: 0,
H2: 0,
H3: 0,
H4: 0
}, },
numberOfAvailableIPs: "0", numberOfAvailableIPs: "0",
error: false, error: false,
@ -35,8 +52,19 @@ export default {
}, },
created() { created() {
this.wireguardGenerateKeypair(); this.wireguardGenerateKeypair();
let hValue = []
while ([...new Set(hValue)].length !== 4){
hValue = [this.rand(1, (2**31) - 1), this.rand(1, (2**31) - 1), this.rand(1, (2**31) - 1), this.rand(1, (2**31) - 1)]
}
this.newConfiguration.H1 = hValue[0]
this.newConfiguration.H2 = hValue[1]
this.newConfiguration.H3 = hValue[2]
this.newConfiguration.H4 = hValue[3]
}, },
methods: { methods: {
rand(min, max){
return Math.floor(Math.random() * (max - min) + min);
},
wireguardGenerateKeypair(){ wireguardGenerateKeypair(){
const wg = window.wireguard.generateKeypair(); const wg = window.wireguard.generateKeypair();
this.newConfiguration.PrivateKey = wg.privateKey; this.newConfiguration.PrivateKey = wg.privateKey;
@ -143,6 +171,34 @@ export default {
<form class="text-body d-flex flex-column gap-3" <form class="text-body d-flex flex-column gap-3"
@submit="(e) => {e.preventDefault(); this.saveNewConfiguration();}" @submit="(e) => {e.preventDefault(); this.saveNewConfiguration();}"
> >
<div class="card rounded-3 shadow">
<div class="card-header">
<LocaleText t="Protocol"></LocaleText>
</div>
<div class="card-body d-flex gap-2 protocolBtnGroup">
<a
@click="this.newConfiguration.Protocol = 'wg'"
:class="{'opacity-50': this.newConfiguration.Protocol !== 'wg'}"
class="btn btn-primary wireguardBg border-0 " style="flex-basis: 100%">
<i class="bi bi-check-circle-fill me-2" v-if="this.newConfiguration.Protocol === 'wg'"></i>
<i class="bi bi-circle me-2" v-else></i>
<strong>
WireGuard
</strong>
</a>
<a
@click="this.newConfiguration.Protocol = 'awg'"
:class="{'opacity-50': this.newConfiguration.Protocol !== 'awg'}"
class="btn btn-primary amneziawgBg border-0" style="flex-basis: 100%">
<i class="bi bi-check-circle-fill me-2" v-if="this.newConfiguration.Protocol === 'awg'"></i>
<i class="bi bi-circle me-2" v-else></i>
<strong>
AmneziaWG
</strong>
</a>
</div>
</div>
<div class="card rounded-3 shadow"> <div class="card rounded-3 shadow">
<div class="card-header"> <div class="card-header">
<LocaleText t="Configuration Name"></LocaleText> <LocaleText t="Configuration Name"></LocaleText>
@ -250,28 +306,21 @@ export default {
<div id="newConfigurationOptionalAccordionCollapse" <div id="newConfigurationOptionalAccordionCollapse"
class="accordion-collapse collapse" data-bs-parent="#newConfigurationOptionalAccordion"> class="accordion-collapse collapse" data-bs-parent="#newConfigurationOptionalAccordion">
<div class="accordion-body d-flex flex-column gap-3"> <div class="accordion-body d-flex flex-column gap-3">
<div class="card rounded-3"> <div class="card rounded-3" v-for="key in ['PreUp', 'PreDown', 'PostUp', 'PostDown']">
<div class="card-header">PreUp</div> <div class="card-header">{{ key }}</div>
<div class="card-body"> <div class="card-body">
<input type="text" class="form-control" id="preUp" v-model="this.newConfiguration.PreUp"> <input type="text"
class="form-control font-monospace" :id="key" v-model="this.newConfiguration[key]">
</div> </div>
</div> </div>
<div class="card rounded-3">
<div class="card-header">PreDown</div> <div class="card rounded-3"
v-if="this.newConfiguration.Protocol === 'awg'"
v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'H1', 'H2', 'H3', 'H4']">
<div class="card-header">{{ key }}</div>
<div class="card-body"> <div class="card-body">
<input type="text" class="form-control" id="preDown" v-model="this.newConfiguration.PreDown"> <input type="text"
</div> class="form-control font-monospace" :id="key" v-model="this.newConfiguration[key]">
</div>
<div class="card rounded-3">
<div class="card-header">PostUp</div>
<div class="card-body">
<input type="text" class="form-control" id="postUp" v-model="this.newConfiguration.PostUp">
</div>
</div>
<div class="card rounded-3">
<div class="card-header">PostDown</div>
<div class="card-body">
<input type="text" class="form-control" id="postDown" v-model="this.newConfiguration.PostDown">
</div> </div>
</div> </div>
</div> </div>
@ -303,5 +352,7 @@ export default {
</template> </template>
<style scoped> <style scoped>
.protocolBtnGroup a{
transition: all 0.2s ease-in-out;
}
</style> </style>

View File

@ -67,6 +67,7 @@ const selectedConfiguration = ref("")
@select="(b) => {selectedConfigurationBackup = b; selectedConfiguration = c; confirm = true}" @select="(b) => {selectedConfigurationBackup = b; selectedConfiguration = c; confirm = true}"
:selectedConfigurationBackup="selectedConfigurationBackup" :selectedConfigurationBackup="selectedConfigurationBackup"
:open="selectedConfiguration === c" :open="selectedConfiguration === c"
:protocol="[...new Set(backups.NonExistingConfigurations[c].map(x => x.protocol))]"
v-for="c in Object.keys(backups.NonExistingConfigurations)" v-for="c in Object.keys(backups.NonExistingConfigurations)"
:configuration-name="c" :backups="backups.NonExistingConfigurations[c]"></BackupGroup> :configuration-name="c" :backups="backups.NonExistingConfigurations[c]"></BackupGroup>
<div v-if="Object.keys(backups.NonExistingConfigurations).length === 0"> <div v-if="Object.keys(backups.NonExistingConfigurations).length === 0">