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

Merge branch 'v4.1-dev' into v4.1-dev-italian

This commit is contained in:
Donald Zou 2024-10-03 20:13:38 +08:00 committed by GitHub
commit d3c8a350a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1115 additions and 206 deletions

View File

@ -1228,6 +1228,8 @@ class DashboardConfig:
self.__config[section][key] = "true"
else:
self.__config[section][key] = "false"
if type(value) in [int, float]:
self.__config[section][key] = str(value)
else:
self.__config[section][key] = value
return self.SaveConfig(), ""
@ -1781,73 +1783,95 @@ def API_allowAccessPeers(configName: str) -> ResponseObject:
@app.post(f'{APP_PREFIX}/api/addPeers/<configName>')
def API_addPeers(configName):
data = request.get_json()
bulkAdd = data['bulkAdd']
bulkAddAmount = data['bulkAddAmount']
public_key = data['public_key']
allowed_ips = data['allowed_ips']
endpoint_allowed_ip = data['endpoint_allowed_ip']
dns_addresses = data['DNS']
mtu = data['mtu']
keep_alive = data['keepalive']
preshared_key = data['preshared_key']
preshared_key_bulkAdd: bool = data['preshared_key_bulkAdd']
if configName in WireguardConfigurations.keys():
config = WireguardConfigurations.get(configName)
if (not bulkAdd and (len(public_key) == 0 or len(allowed_ips) == 0)) or len(endpoint_allowed_ip) == 0:
return ResponseObject(False, "Please fill in all required box")
if not config.getStatus():
config.toggleConfiguration()
try:
data: dict = request.get_json()
availableIps = _getWireguardConfigurationAvailableIP(configName)
if bulkAdd:
if bulkAddAmount < 1:
return ResponseObject(False, "Please specify amount of peers you want to add")
if not availableIps[0]:
return ResponseObject(False, "No more available IP can assign")
if bulkAddAmount > len(availableIps[1]):
return ResponseObject(False,
f"The maximum number of peers can add is {len(availableIps[1])}")
keyPairs = []
for i in range(bulkAddAmount):
newPrivateKey = _generatePrivateKey()[1]
keyPairs.append({
"private_key": newPrivateKey,
"id": _generatePublicKey(newPrivateKey)[1],
"preshared_key": (_generatePrivateKey()[1] if preshared_key_bulkAdd else ""),
"allowed_ip": availableIps[1][i],
"name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
})
if len(keyPairs) == 0:
return ResponseObject(False, "Generating key pairs by bulk failed")
config.addPeers(keyPairs)
bulkAdd: bool = data.get("bulkAdd", False)
bulkAddAmount: int = data.get('bulkAddAmount', 0)
preshared_key_bulkAdd: bool = data.get('preshared_key_bulkAdd', False)
public_key: str = data.get('public_key', "")
allowed_ips: list[str] = data.get('allowed_ips', "")
for kp in keyPairs:
found, peer = config.searchPeer(kp['id'])
endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
dns_addresses: str = data.get('DNS', DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1])
mtu: int = data.get('mtu', int(DashboardConfig.GetConfig("Peers", "peer_MTU")[1]))
keep_alive: int = data.get('keepalive', int(DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1]))
preshared_key: str = data.get('preshared_key', "")
if type(mtu) is not int or mtu < 0 or mtu > 1460:
mtu = int(DashboardConfig.GetConfig("Peers", "peer_MTU")[1])
if type(keep_alive) is not int or keep_alive < 0:
keep_alive = int(DashboardConfig.GetConfig("Peers", "peer_keep_alive")[1])
if len(dns_addresses) == 0:
dns_addresses = DashboardConfig.GetConfig("Peers", "peer_global_DNS")[1]
if len(endpoint_allowed_ip) == 0:
endpoint_allowed_ip = DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1]
config = WireguardConfigurations.get(configName)
if not bulkAdd and (len(public_key) == 0 or len(allowed_ips) == 0):
return ResponseObject(False, "Please provide at lease public_key and allowed_ips")
if not config.getStatus():
config.toggleConfiguration()
availableIps = _getWireguardConfigurationAvailableIP(configName)
if bulkAdd:
if type(preshared_key_bulkAdd) is not bool:
preshared_key_bulkAdd = False
if type(bulkAddAmount) is not int or bulkAddAmount < 1:
return ResponseObject(False, "Please specify amount of peers you want to add")
if not availableIps[0]:
return ResponseObject(False, "No more available IP can assign")
if bulkAddAmount > len(availableIps[1]):
return ResponseObject(False,
f"The maximum number of peers can add is {len(availableIps[1])}")
keyPairs = []
for i in range(bulkAddAmount):
newPrivateKey = _generatePrivateKey()[1]
keyPairs.append({
"private_key": newPrivateKey,
"id": _generatePublicKey(newPrivateKey)[1],
"preshared_key": (_generatePrivateKey()[1] if preshared_key_bulkAdd else ""),
"allowed_ip": availableIps[1][i],
"name": f"BulkPeer #{(i + 1)}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
})
if len(keyPairs) == 0:
return ResponseObject(False, "Generating key pairs by bulk failed")
config.addPeers(keyPairs)
for kp in keyPairs:
found, peer = config.searchPeer(kp['id'])
if found:
if not peer.updatePeer(kp['name'], kp['private_key'], kp['preshared_key'], dns_addresses,
kp['allowed_ip'], endpoint_allowed_ip, mtu, keep_alive):
return ResponseObject(False, "Failed to add peers in bulk")
return ResponseObject()
else:
if config.searchPeer(public_key)[0] is True:
return ResponseObject(False, f"This peer already exist")
name = data.get("name", "")
private_key = data.get("private_key", "")
for i in allowed_ips:
if i not in availableIps[1]:
return ResponseObject(False, f"This IP is not available: {i}")
config.addPeers([{"id": public_key, "allowed_ip": ','.join(allowed_ips)}])
found, peer = config.searchPeer(public_key)
if found:
if not peer.updatePeer(kp['name'], kp['private_key'], kp['preshared_key'], dns_addresses,
kp['allowed_ip'], endpoint_allowed_ip, mtu, keep_alive):
return ResponseObject(False, "Failed to add peers in bulk")
return ResponseObject()
else:
if config.searchPeer(public_key)[0] is True:
return ResponseObject(False, f"This peer already exist")
name = data['name']
private_key = data['private_key']
for i in allowed_ips:
if i not in availableIps[1]:
return ResponseObject(False, f"This IP is not available: {i}")
config.addPeers([{"id": public_key, "allowed_ip": ','.join(allowed_ips)}])
found, peer = config.searchPeer(public_key)
if found:
return peer.updatePeer(name, private_key, preshared_key, dns_addresses, ",".join(allowed_ips),
endpoint_allowed_ip, mtu, keep_alive)
return peer.updatePeer(name, private_key, preshared_key, dns_addresses, ",".join(allowed_ips),
endpoint_allowed_ip, mtu, keep_alive)
except Exception as e:
print(e)
return ResponseObject(False, "Add peers failed. Please see data for specific issue")
return ResponseObject(False, "Configuration does not exist")
@ -1992,6 +2016,7 @@ def API_ping_getAllPeersIpAddress():
ips[c.Name] = cips
return ResponseObject(data=ips)
import requests
@app.get(f'{APP_PREFIX}/api/ping/execute')
def API_ping_execute():
@ -2001,8 +2026,8 @@ def API_ping_execute():
try:
if ip is not None and len(ip) > 0 and count is not None and count.isnumeric():
result = ping(ip, count=int(count), source=None)
return ResponseObject(data={
data = {
"address": result.address,
"is_alive": result.is_alive,
"min_rtt": result.min_rtt,
@ -2010,9 +2035,17 @@ def API_ping_execute():
"max_rtt": result.max_rtt,
"package_sent": result.packets_sent,
"package_received": result.packets_received,
"package_loss": result.packet_loss
})
"package_loss": result.packet_loss,
"geo": None
}
try:
r = requests.get(f"http://ip-api.com/json/{result.address}?field=city")
data['geo'] = r.json()
except Exception as e:
pass
return ResponseObject(data=data)
return ResponseObject(False, "Please specify an IP Address (v4/v6)")
except Exception as exp:
return ResponseObject(False, exp)
@ -2024,7 +2057,7 @@ def API_traceroute_execute():
if "ipAddress" in request.args.keys() and len(request.args.get("ipAddress")) > 0:
ipAddress = request.args.get('ipAddress')
try:
tracerouteResult = traceroute(ipAddress)
tracerouteResult = traceroute(ipAddress, timeout=1, max_hops=64)
result = []
for hop in tracerouteResult:
if len(result) > 1:
@ -2049,6 +2082,15 @@ def API_traceroute_execute():
"min_rtt": hop.min_rtt,
"max_rtt": hop.max_rtt
})
try:
r = requests.post(f"http://ip-api.com/batch?fields=city,country,lat,lon,query",
data=json.dumps([x['ip'] for x in result]))
d = r.json()
for i in range(len(result)):
result[i]['geo'] = d[i]
except Exception as e:
print(e)
return ResponseObject(data=result)
except Exception as exp:
return ResponseObject(False, exp)
@ -2077,7 +2119,6 @@ def API_getDashboardUpdate():
Sign Up
'''
@app.get(f'{APP_PREFIX}/api/isTotpEnabled')
def API_isTotpEnabled():
return (

View File

@ -5,4 +5,5 @@ pyotp
Flask
flask-cors
icmplib
gunicorn
gunicorn
requests

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,12 +1,12 @@
{
"name": "app",
"version": "4.0.2",
"version": "4.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "app",
"version": "4.0.2",
"version": "4.1.0",
"dependencies": {
"@vuepic/vue-datepicker": "^9.0.1",
"@vueuse/core": "^10.9.0",
@ -21,6 +21,7 @@
"i": "^0.3.7",
"is-cidr": "^5.0.3",
"npm": "^10.5.0",
"ol": "^10.2.1",
"pinia": "^2.1.7",
"qrcode": "^1.5.3",
"qrcodejs": "^1.0.0",
@ -723,6 +724,11 @@
"node": ">=10"
}
},
"node_modules/@petamoriken/float16": {
"version": "3.8.7",
"resolved": "https://registry.npmmirror.com/@petamoriken/float16/-/float16-3.8.7.tgz",
"integrity": "sha512-/Ri4xDDpe12NT6Ex/DRgHzLlobiQXEW/hmG08w1wj/YU7hLemk97c+zHQFp0iZQ9r7YqgLEXZR2sls4HxBf9NA=="
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -964,6 +970,11 @@
"xmlbuilder": ">=11.0.1"
}
},
"node_modules/@types/rbush": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/@types/rbush/-/rbush-3.0.3.tgz",
"integrity": "sha512-lX55lR0iYCgapxD3IrgujpQA1zDxwZI5qMRelKvmKAsSMplFVr7wmMpG7/6+Op2tjrgEex8o3vjg8CRDrRNYxg=="
},
"node_modules/@types/verror": {
"version": "1.10.10",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz",
@ -1717,6 +1728,36 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/color-parse": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/color-parse/-/color-parse-2.0.2.tgz",
"integrity": "sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw==",
"dependencies": {
"color-name": "^2.0.0"
}
},
"node_modules/color-parse/node_modules/color-name": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-2.0.0.tgz",
"integrity": "sha512-SbtvAMWvASO5TE2QP07jHBMXKafgdZz8Vrsrn96fiL+O92/FN/PLARzUW5sKt013fjAprK2d2iCn2hk2Xb5oow==",
"engines": {
"node": ">=12.20"
}
},
"node_modules/color-rgba": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/color-rgba/-/color-rgba-3.0.0.tgz",
"integrity": "sha512-PPwZYkEY3M2THEHHV6Y95sGUie77S7X8v+h1r6LSAPF3/LL2xJ8duUXSrkic31Nzc4odPwHgUbiX/XuTYzQHQg==",
"dependencies": {
"color-parse": "^2.0.0",
"color-space": "^2.0.0"
}
},
"node_modules/color-space": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/color-space/-/color-space-2.0.1.tgz",
"integrity": "sha512-nKqUYlo0vZATVOFHY810BSYjmCARrG7e5R3UE3CQlyjJTvv5kSSmPG1kzm/oDyyqjehM+lW1RnEt9It9GNa5JA=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@ -2005,6 +2046,11 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/earcut": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/earcut/-/earcut-3.0.0.tgz",
"integrity": "sha512-41Fs7Q/PLq1SDbqjsgcY7GA42T0jvaCNGXgGtsNdvg+Yv8eIu06bxv4/PoREkZ9nMDNwnUSG9OFB9+yv8eKhDg=="
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2358,6 +2404,24 @@
"node": ">=10"
}
},
"node_modules/geotiff": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/geotiff/-/geotiff-2.1.3.tgz",
"integrity": "sha512-PT6uoF5a1+kbC3tHmZSUsLHBp2QJlHasxxxxPW47QIY1VBKpFB+FcDvX+MxER6UzgLQZ0xDzJ9s48B9JbOCTqA==",
"dependencies": {
"@petamoriken/float16": "^3.4.7",
"lerc": "^3.0.0",
"pako": "^2.0.4",
"parse-headers": "^2.0.2",
"quick-lru": "^6.1.1",
"web-worker": "^1.2.0",
"xml-utils": "^1.0.2",
"zstddec": "^0.1.0"
},
"engines": {
"node": ">=10.19"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -2731,6 +2795,11 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/lerc/-/lerc-3.0.0.tgz",
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww=="
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@ -5476,6 +5545,24 @@
"inBundle": true,
"license": "ISC"
},
"node_modules/ol": {
"version": "10.2.1",
"resolved": "https://registry.npmmirror.com/ol/-/ol-10.2.1.tgz",
"integrity": "sha512-2bB/y2vEnmzjqynP0NA7Cp8k86No3Psn63Dueicep3E3i09axWRVIG5IS/bylEAGfWQx0QXD/uljkyFoY60Wig==",
"dependencies": {
"@types/rbush": "3.0.3",
"color-rgba": "^3.0.0",
"color-space": "^2.0.1",
"earcut": "^3.0.0",
"geotiff": "^2.0.7",
"pbf": "4.0.1",
"rbush": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -5522,6 +5609,16 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
},
"node_modules/parse-headers": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/parse-headers/-/parse-headers-2.0.5.tgz",
"integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA=="
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -5566,6 +5663,17 @@
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
"node_modules/pbf": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/pbf/-/pbf-4.0.1.tgz",
"integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==",
"dependencies": {
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -5687,6 +5795,11 @@
"node": ">=10"
}
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmmirror.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw=="
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -5717,6 +5830,30 @@
"resolved": "https://registry.npmjs.org/qrcodejs/-/qrcodejs-1.0.0.tgz",
"integrity": "sha512-67rj3mMBhSBepaD57qENnltO+r8rSYlqM7HGThks/BiyDAkc86sLvkKqjkqPS5v13f7tvnt6dbEf3qt7zq+BCg=="
},
"node_modules/quick-lru": {
"version": "6.1.2",
"resolved": "https://registry.npmmirror.com/quick-lru/-/quick-lru-6.1.2.tgz",
"integrity": "sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/quickselect": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/quickselect/-/quickselect-3.0.0.tgz",
"integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g=="
},
"node_modules/rbush": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/rbush/-/rbush-4.0.1.tgz",
"integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==",
"dependencies": {
"quickselect": "^3.0.0"
}
},
"node_modules/read-config-file": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz",
@ -5769,6 +5906,14 @@
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
},
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"dependencies": {
"protocol-buffers-schema": "^3.3.1"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
@ -6264,6 +6409,11 @@
"vue": "^3.2.0"
}
},
"node_modules/web-worker": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/web-worker/-/web-worker-1.3.0.tgz",
"integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA=="
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -6318,6 +6468,11 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/xml-utils": {
"version": "1.10.1",
"resolved": "https://registry.npmmirror.com/xml-utils/-/xml-utils-1.10.1.tgz",
"integrity": "sha512-Dn6vJ1Z9v1tepSjvnCpwk5QqwIPcEFKdgnjqfYOABv1ngSofuAhtlugcUC3ehS1OHdgDWSG6C5mvj+Qm15udTQ=="
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
@ -6403,6 +6558,11 @@
"engines": {
"node": ">= 10"
}
},
"node_modules/zstddec": {
"version": "0.1.0",
"resolved": "https://registry.npmmirror.com/zstddec/-/zstddec-0.1.0.tgz",
"integrity": "sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg=="
}
}
}

View File

@ -23,6 +23,7 @@
"i": "^0.3.7",
"is-cidr": "^5.0.3",
"npm": "^10.5.0",
"ol": "^10.2.1",
"pinia": "^2.1.7",
"qrcode": "^1.5.3",
"qrcodejs": "^1.0.0",

View File

@ -19,6 +19,7 @@ export default {
<input type="number" class="form-control form-control-sm rounded-3"
:disabled="this.saving"
v-model="this.data.mtu"
min="0"
id="peer_mtu">
</div>
</template>

View File

@ -26,7 +26,7 @@ export default {
return{
data: {
bulkAdd: false,
bulkAddAmount: "",
bulkAddAmount: 0,
name: "",
allowed_ips: [],
private_key: "",

View File

@ -0,0 +1,145 @@
<script>
import "ol/ol.css"
import Map from 'ol/Map.js';
import OSM from 'ol/source/OSM.js';
import TileLayer from 'ol/layer/Tile.js';
import View from 'ol/View.js';
import {Feature} from "ol";
import {fromLonLat} from "ol/proj"
import {LineString, Point} from "ol/geom"
import {Circle, Fill, Stroke, Style, Text} from "ol/style.js";
import {Vector} from "ol/layer"
import {Vector as SourceVector} from "ol/source"
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
export default {
name: "osmap",
props: {
type: "",
d: Object || Array
},
data(){
return {
osmAvailable: true
}
},
setup(){
const store = DashboardConfigurationStore();
return {
store
}
},
methods: {
getLastLonLat(){
if (this.type === 'traceroute'){
const k = this.d.findLast(data => data.geo && data.geo.lat && data.geo.lon)
if (k){
return [k.geo.lon, k.geo.lat]
}
return [0,0]
}
return [this.d.geo.lon, this.d.geo.lat]
}
},
async mounted() {
const osm = await fetch("https://tile.openstreetmap.org/",
{ signal: AbortSignal.timeout(1500) })
.then(res => {
const map = new Map({
target: 'map',
layers: [
new TileLayer({
source: new OSM(),
}),
],
view: new View({
center: fromLonLat(this.getLastLonLat()),
zoom: this.type === 'traceroute' ? 3:10,
}),
});
const coordinates = [];
const vectorSource = new SourceVector();
if (this.type === 'traceroute'){
console.log(this.getLastLonLat())
this.d.forEach(data => {
if (data.geo && data.geo.lat && data.geo.lon) {
const coordinate = fromLonLat([data.geo.lon, data.geo.lat]);
coordinates.push(coordinate);
const l = this.getLastLonLat();
console.log(data.geo.lon, data.geo.lat)
console.log( data.geo.lon === l[0] && data.geo.lat === l[1])
const marker = new Feature({
geometry: new Point(coordinate),
last: data.geo.lon === l[0] && data.geo.lat === l[1]
});
vectorSource.addFeature(marker);
}
})
}
else{
const coordinate = fromLonLat([this.d.geo.lon, this.d.geo.lat])
coordinates.push(coordinate);
const marker = new Feature({
geometry: new Point(coordinate)
});
vectorSource.addFeature(marker);
}
const lineString = new LineString(coordinates);
const lineFeature = new Feature({
geometry: lineString
});
vectorSource.addFeature(lineFeature);
const vectorLayer = new Vector({
source: vectorSource,
style: function(feature) {
if (feature.getGeometry().getType() === 'Point') {
return new Style({
image: new Circle({
radius: 10,
fill: new Fill({ color: feature.get("last") ? '#dc3545':'#0d6efd' }),
stroke: new Stroke({ color: 'white', width: 5 }),
})
});
} else if (feature.getGeometry().getType() === 'LineString') {
return new Style({
stroke: new Stroke({
color: '#0d6efd',
width: 2
})
});
}
}
});
map.addLayer(vectorLayer);
if (this.store.Configuration.Server.dashboard_theme === 'dark'){
map.on('postcompose',function(e){
document.querySelector('#map').style.filter="grayscale(80%) invert(100%) ";
});
}
}).catch(e => {
this.osmAvailable = false
})
}
}
</script>
<template>
<div id="map" class="w-100 rounded-3" v-if="this.osmAvailable"></div>
</template>
<style>
.ol-layer canvas{
border-radius: var(--bs-border-radius-lg) !important;
}
#map{
height: 300px;
}
</style>

View File

@ -31,7 +31,8 @@ export default {
this.value = this.store.Configuration.Account[this.targetData];
},
methods:{
async useValidation(){
async useValidation(e){
if (this.changed){
this.updating = true
await fetchPost("/api/updateDashboardConfigurationItem", {

View File

@ -0,0 +1,116 @@
<script>
import LocaleText from "@/components/text/localeText.vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import {fetchPost} from "@/utilities/fetch.js";
export default {
name: "dashboardIPPortInput" ,
components: {LocaleText},
setup(){
const store = DashboardConfigurationStore();
return {store};
},
data(){
return{
ipAddress:"",
port: 0,
invalidFeedback: "",
showInvalidFeedback: false,
isValid: false,
timeout: undefined,
changed: false,
updating: false,
}
},
mounted() {
this.ipAddress = this.store.Configuration.Server.app_ip
this.port = this.store.Configuration.Server.app_port
},
methods: {
async useValidation(e, targetData, value){
if (this.changed){
this.updating = true
await fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server",
key: targetData,
value: value
}, (res) => {
if (res.status){
e.target.classList.add("is-valid")
this.showInvalidFeedback = false;
this.store.Configuration.Server[targetData] = value
clearTimeout(this.timeout)
this.timeout = setTimeout(() => {
e.target.classList.remove("is-valid")
}, 5000);
}else{
this.isValid = false;
this.showInvalidFeedback = true;
this.invalidFeedback = res.message
}
this.changed = false
this.updating = false;
})
}
}
}
}
</script>
<template>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">
<LocaleText t="Dashboard IP Address & Listen Port"></LocaleText>
</p>
<div class="card-body">
<div class="row gx-3">
<div class="col-sm">
<div class="form-group mb-2">
<label for="input_dashboard_ip" class="text-muted mb-1">
<strong><small>
<LocaleText t="IP Address / Hostname"></LocaleText>
</small></strong>
</label>
<input type="text" class="form-control"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
id="input_dashboard_ip"
v-model="this.ipAddress"
@keydown="this.changed = true"
@blur="useValidation($event, 'app_ip', this.ipAddress)"
:disabled="this.updating"
>
<div class="invalid-feedback">{{this.invalidFeedback}}</div>
</div>
</div>
<div class="col-sm">
<div class="form-group mb-2">
<label for="input_dashboard_ip" class="text-muted mb-1">
<strong><small>
<LocaleText t="Listen Port"></LocaleText>
</small></strong>
</label>
<input type="number" class="form-control"
:class="{'is-invalid': showInvalidFeedback, 'is-valid': isValid}"
id="input_dashboard_ip"
v-model="this.port"
@keydown="this.changed = true"
@blur="useValidation($event, 'app_port', this.port)"
:disabled="this.updating"
>
<div class="invalid-feedback">{{this.invalidFeedback}}</div>
</div>
</div>
</div>
<div class="px-2 py-1 text-warning-emphasis bg-warning-subtle border border-warning-subtle rounded-2 d-inline-block mt-1 mb-2">
<small><i class="bi bi-exclamation-triangle-fill me-2"></i>
<LocaleText t="Manual restart of WGDashboard is needed to apply changes on IP Address and Listen Port"></LocaleText>
</small>
</div>
</div>
</div>
</template>
<style scoped>
</style>

View File

@ -59,7 +59,7 @@ export default {
</span>
</button>
<ul class="dropdown-menu rounded-3 shadow" style="width: 500px">
<ul class="dropdown-menu rounded-3 shadow">
<li v-for="x in this.languages">
<a class="dropdown-item d-flex align-items-center" role="button"
@click="this.changeLanguage(x.lang_id)"
@ -82,5 +82,8 @@ export default {
</template>
<style scoped>
.dropdown-menu{
width: 100%;
}
</style>

View File

@ -1,9 +1,12 @@
<script>
import {fetchGet} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import LocaleText from "@/components/text/localeText.vue";
import OSMap from "@/components/map/osmap.vue";
export default {
name: "ping",
components: {OSMap, LocaleText},
data(){
return {
loading: false,
@ -43,7 +46,9 @@ export default {
}else{
this.store.newMessage("Server", res.message, "danger")
}
this.pinging = false
})
}
{} }
},
@ -67,9 +72,11 @@ export default {
<div class="col-sm-4 d-flex gap-2 flex-column">
<div>
<label class="mb-1 text-muted" for="configuration">
<small>Configuration</small></label>
<select class="form-select" v-model="this.selectedConfiguration">
<option disabled selected :value="undefined">Select a Configuration...</option>
<small>
<LocaleText t="Configuration"></LocaleText>
</small></label>
<select class="form-select" v-model="this.selectedConfiguration" :disabled="this.pinging">
<option disabled selected :value="undefined"></option>
<option :value="key" v-for="(val, key) in this.cips">
{{key}}
</option>
@ -77,9 +84,11 @@ export default {
</div>
<div>
<label class="mb-1 text-muted" for="peer">
<small>Peer</small></label>
<select id="peer" class="form-select" v-model="this.selectedPeer" :disabled="this.selectedConfiguration === undefined">
<option disabled selected :value="undefined">Select a Peer...</option>
<small>
<LocaleText t="Peer"></LocaleText>
</small></label>
<select id="peer" class="form-select" v-model="this.selectedPeer" :disabled="this.selectedConfiguration === undefined || this.pinging">
<option disabled selected :value="undefined"></option>
<option v-if="this.selectedConfiguration !== undefined" :value="key" v-for="(peer, key) in
this.cips[this.selectedConfiguration]">
{{key}}
@ -88,9 +97,11 @@ export default {
</div>
<div>
<label class="mb-1 text-muted" for="ip">
<small>IP Address</small></label>
<select id="ip" class="form-select" v-model="this.selectedIp" :disabled="this.selectedPeer === undefined">
<option disabled selected :value="undefined">Select a IP...</option>
<small>
<LocaleText t="IP Address"></LocaleText>
</small></label>
<select id="ip" class="form-select" v-model="this.selectedIp" :disabled="this.selectedPeer === undefined || this.pinging">
<option disabled selected :value="undefined"></option>
<option
v-if="this.selectedPeer !== undefined"
v-for="ip in this.cips[this.selectedConfiguration][this.selectedPeer].allowed_ips">
@ -98,34 +109,93 @@ export default {
</option>
</select>
</div>
<div class="d-flex align-items-center gap-2">
<div class="flex-grow-1 border-top"></div>
<small class="text-muted">
<LocaleText t="OR"></LocaleText>
</small>
<div class="flex-grow-1 border-top"></div>
</div>
<div>
<label class="mb-1 text-muted" for="ipAddress">
<small>
<LocaleText t="Enter IP Address / Hostname"></LocaleText>
</small></label>
<input class="form-control" type="text"
id="ipAddress"
:disabled="this.pinging"
v-model="this.selectedIp">
</div>
<div class="w-100 border-top my-2"></div>
<div>
<label class="mb-1 text-muted" for="count">
<small>Ping Count</small></label>
<input class="form-control" type="number"
v-model="this.count"
min="1" id="count" placeholder="How many times you want to ping?">
<small>
<LocaleText t="Count"></LocaleText>
</small></label>
<div class="d-flex gap-3 align-items-center">
<button @click="this.count--"
:disabled="this.count === 1"
class="btn btn-sm bg-secondary-subtle text-secondary-emphasis">
<i class="bi bi-dash-lg"></i>
</button>
<strong>{{this.count}}</strong>
<button role="button" @click="this.count++" class="btn btn-sm bg-secondary-subtle text-secondary-emphasis">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
<button class="btn btn-primary rounded-3 mt-3"
:disabled="!this.selectedIp"
<button class="btn btn-primary rounded-3 mt-3 position-relative"
:disabled="!this.selectedIp || this.pinging"
@click="this.execute()">
<i class="bi bi-person-walking me-2"></i>Go!
<Transition name="slide">
<span v-if="!this.pinging" class="d-block">
<i class="bi bi-person-walking me-2"></i>Ping!
</span>
<span v-else class="d-block">
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
<span class="visually-hidden" role="status">Loading...</span>
</span>
</Transition>
</button>
</div>
<div class="col-sm-8">
<TransitionGroup name="ping">
<div class="col-sm-8 position-relative">
<Transition name="ping">
<div v-if="!this.pingResult" key="pingPlaceholder">
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3"
style="height: 300px"
></div>
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3"
:class="{'animate__animated animate__flash animate__slower animate__infinite': this.pinging}"
:style="{'animation-delay': `${x*0.15}s`}"
v-for="x in 4" ></div>
</div>
<div v-else key="pingResult" class="d-flex flex-column gap-2 w-100">
<OSMap :d="this.pingResult" v-if="this.pingResult.geo && this.pingResult.geo.status === 'success'"></OSMap>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.15s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Address</small></p>
{{this.pingResult.address}}
<div class="card-body row">
<div class="col-sm">
<p class="mb-0 text-muted">
<small>
<LocaleText t="IP Address"></LocaleText>
</small>
</p>
{{this.pingResult.address}}
</div>
<div class="col-sm" v-if="this.pingResult.geo && this.pingResult.geo.status === 'success'">
<p class="mb-0 text-muted">
<small>
<LocaleText t="Geolocation"></LocaleText>
</small>
</p>
{{this.pingResult.geo.city}}, {{this.pingResult.geo.country}}
</div>
</div>
</div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.3s">
@ -140,7 +210,9 @@ export default {
</div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.45s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Average / Min / Max Round Trip Time</small></p>
<p class="mb-0 text-muted"><small>
<LocaleText t="Average / Min / Max Round Trip Time"></LocaleText>
</small></p>
<samp>{{this.pingResult.avg_rtt}}ms /
{{this.pingResult.min_rtt}}ms /
{{this.pingResult.max_rtt}}ms
@ -149,16 +221,17 @@ export default {
</div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.6s">
<div class="card-body">
<p class="mb-0 text-muted"><small>Sent / Received / Lost Package</small></p>
<p class="mb-0 text-muted"><small>
<LocaleText t="Sent / Received / Lost Package"></LocaleText>
</small></p>
<samp>{{this.pingResult.package_sent}} /
{{this.pingResult.package_received}} /
{{this.pingResult.package_loss}}
</samp>
</div>
</div>
</div>
</TransitionGroup>
</Transition>
</div>
@ -181,17 +254,12 @@ export default {
.ping-leave-active{
position: absolute;
width: 100%;
}
.ping-enter-from,
.ping-leave-to {
opacity: 0;
//transform: scale(0.9);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.ping-leave-active {
position: absolute;
//transform: scale(1.1);
filter: blur(3px);
}
</style>

View File

@ -13,11 +13,13 @@ import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vu
import AccountSettingsMFA from "@/components/settingsComponent/accountSettingsMFA.vue";
import LocaleText from "@/components/text/localeText.vue";
import DashboardLanguage from "@/components/settingsComponent/dashboardLanguage.vue";
import DashboardIPPortInput from "@/components/settingsComponent/dashboardIPPortInput.vue";
export default {
name: "settings",
methods: {ipV46RegexCheck},
components: {
DashboardIPPortInput,
DashboardLanguage,
LocaleText,
AccountSettingsMFA,
@ -39,18 +41,22 @@ export default {
<h3 class="mb-3 text-body">
<LocaleText t="Settings"></LocaleText>
</h3>
<DashboardTheme></DashboardTheme>
<DashboardLanguage></DashboardLanguage>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">
<LocaleText t="Peers Default Settings"></LocaleText>
</p>
<div class="card-body">
<PeersDefaultSettingsInput targetData="peer_global_dns" title="DNS"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_endpoint_allowed_ip" title="Endpoint Allowed IPs"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_mtu" title="MTU"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput targetData="remote_endpoint" title="Peer Remote Endpoint"
<PeersDefaultSettingsInput
targetData="peer_global_dns" title="DNS"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput
targetData="peer_endpoint_allowed_ip" title="Endpoint Allowed IPs"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput
targetData="peer_mtu" title="MTU"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput
targetData="peer_keep_alive" title="Persistent Keepalive"></PeersDefaultSettingsInput>
<PeersDefaultSettingsInput
targetData="remote_endpoint" title="Peer Remote Endpoint"
:warning="true" warningText="This will be changed globally, and will be apply to all peer's QR code and configuration file."
></PeersDefaultSettingsInput>
</div>
@ -69,6 +75,19 @@ export default {
</DashboardSettingsInputWireguardConfigurationPath>
</div>
</div>
<hr class="mb-4">
<div class="row gx-4">
<div class="col-sm">
<DashboardTheme></DashboardTheme>
</div>
<div class="col-sm">
<DashboardLanguage></DashboardLanguage>
</div>
</div>
<DashboardIPPortInput></DashboardIPPortInput>
<div class="card mb-4 shadow rounded-3">
<p class="card-header">
<LocaleText t="WGDashboard Account Settings"></LocaleText>

View File

@ -1,9 +1,12 @@
<script>
import {fetchGet} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import OSMap from "@/components/map/osmap.vue";
import LocaleText from "@/components/text/localeText.vue";
export default {
name: "traceroute",
components: {LocaleText, OSMap},
data(){
return {
tracing: false,
@ -41,55 +44,90 @@ export default {
<div class="mt-md-5 mt-3 text-body">
<div class="container-md">
<h3 class="mb-3 text-body">Traceroute</h3>
<div class="row">
<div class="col-sm-4 d-flex gap-2 flex-column">
<div>
<label class="mb-1 text-muted" for="ipAddress">
<small>IP Address</small></label>
<input
id="ipAddress"
class="form-control"
v-model="this.ipAddress"
type="text" placeholder="Enter an IP Address you want to trace :)">
</div>
<button class="btn btn-primary rounded-3 mt-3"
:disabled="!this.store.regexCheckIP(this.ipAddress) || this.tracing"
@click="this.execute()">
<i class="bi bi-bullseye me-2"></i> {{this.tracing ? "Tracing...":"Trace It!"}}
</button>
<div class="d-flex gap-2 flex-column mb-5">
<div>
<label class="mb-1 text-muted" for="ipAddress">
<small>
<LocaleText t="Enter IP Address / Hostname"></LocaleText>
</small></label>
<input
:disabled="this.tracing"
id="ipAddress"
class="form-control"
v-model="this.ipAddress"
@keyup.enter="this.execute()"
type="text">
</div>
<div class="col-sm-8 position-relative">
<TransitionGroup name="ping">
<div v-if="!this.tracerouteResult" key="pingPlaceholder">
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3"
:class="{'animate__animated animate__flash animate__slower animate__infinite': this.tracing}"
:style="{'animation-delay': `${x*0.05}s`}"
v-for="x in 10" ></div>
</div>
<div v-else key="table" class="w-100">
<table class="table table-borderless rounded-3 w-100">
<button class="btn btn-primary rounded-3 mt-3 position-relative"
:disabled="this.tracing || !this.ipAddress"
@click="this.execute()">
<Transition name="slide">
<span v-if="!this.tracing" class="d-block">
<i class="bi bi-person-walking me-2"></i>Trace!
</span>
<span v-else class="d-block">
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
<span class="visually-hidden" role="status">Loading...</span>
</span>
</Transition>
</button>
</div>
<div class="position-relative">
<Transition name="ping">
<div v-if="!this.tracerouteResult" key="pingPlaceholder">
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3"
style="height: 300px !important;"
></div>
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3"
:style="{'animation-delay': `${x*0.05}s`}"
:class="{'animate__animated animate__flash animate__slower animate__infinite': this.tracing}"
v-for="x in 5" ></div>
</div>
<div v-else>
<OSMap :d="this.tracerouteResult" type="traceroute"></OSMap>
<div key="table" class="w-100 mt-2">
<table class="table table-sm rounded-3 w-100">
<thead>
<tr>
<th scope="col">Hop</th>
<th scope="col">IP Address</th>
<th scope="col">Average / Min / Max Round Trip Time</th>
<th scope="col">Average RTT (ms)</th>
<th scope="col">Min RTT (ms)</th>
<th scope="col">Max RTT (ms)</th>
<th scope="col">Geolocation</th>
</tr>
</thead>
<tbody>
<tr v-for="(hop, key) in this.tracerouteResult"
class="animate__fadeInUp animate__animated"
:style="{'animation-delay': `${key * 0.05}s`}"
>
<td>{{hop.hop}}</td>
<td>{{hop.ip}}</td>
<td>{{hop.avg_rtt}} / {{hop.min_rtt}} / {{hop.max_rtt}}</td>
</tr>
<tr v-for="(hop, key) in this.tracerouteResult">
<td>
<small>{{hop.hop}}</small>
</td>
<td>
<small>{{hop.ip}}</small>
</td>
<td>
<small>{{hop.avg_rtt}}</small>
</td>
<td>
<small>{{hop.min_rtt}}</small>
</td>
<td>
<small>{{hop.max_rtt}}</small>
</td>
<td>
<span v-if="hop.geo.city && hop.geo.country">
<small>{{hop.geo.city}}, {{hop.geo.country}}</small>
</span>
</td>
</tr>
</tbody>
</table>
</div>
</TransitionGroup>
</div>
</div>
</Transition>
</div>
</div>
</div>
@ -108,12 +146,14 @@ export default {
.ping-leave-active{
position: absolute;
width: 100%;
}
.ping-enter-from,
.ping-leave-to {
opacity: 0;
//transform: scale(0.9);
//transform: scale(1.1);
filter: blur(3px);
}
/* ensure leaving items are taken out of layout flow so that moving
@ -123,14 +163,27 @@ export default {
}
table th, table td{
padding: 0.9rem;
}
table tbody{
border-top: 1em solid transparent;
padding: 0.5rem;
}
.table > :not(caption) > * > *{
background-color: transparent !important;
}
.ping-move, /* apply transition to moving elements */
.ping-enter-active,
.ping-leave-active {
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.ping-leave-active{
position: absolute;
width: 100%;
}
.ping-enter-from,
.ping-leave-to {
opacity: 0;
//transform: scale(1.1);
filter: blur(3px);
}
</style>

View File

@ -24,6 +24,12 @@
font-weight: bold;
}
[data-bs-theme="dark"]{
hr{
border-color: #efefef;
}
}
/*
* Sidebar
*/
@ -1166,3 +1172,24 @@ pre.index-alert {
.messageCentre{
z-index: 9999;
}
.slide-move, /* apply transition to moving elements */
.slide-enter-active,
.slide-leave-active {
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9);
}
.slide-leave-active{
position: absolute;
width: 100%;
}
.slide-enter-from{
opacity: 0;
transform: translateX(-50px) !important;
}
.slide-leave-to {
opacity: 0;
transform: translateX(50px) !important;
}

View File

@ -28,5 +28,10 @@
"lang_id": "it-IT",
"lang_name": "Italian",
"lang_name_localized": "Italiano"
},
{
"lang_id": "ru",
"lang_name": "Russian",
"lang_name_localized": "Русский"
}
]

View File

@ -232,5 +232,9 @@
"Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen\\.": "Der Konfigurationsname darf nur 15 Zeichen enthalten, bestehend aus Groß-/Kleinbuchstaben, Zahlen, Unterstrich, Gleichheitszeichen, Pluszeichen, Punkt und Bindestrich.",
"Invalid Port": "Ungültiger Port",
"Save Configuration": "Konfiguration speichern",
"IP Address/CIDR is invalid": "IP-Adresse/CIDR ist ungültig"
"IP Address/CIDR is invalid": "IP-Adresse/CIDR ist ungültig",
"IP Address": "IP-Adresse",
"Enter IP Address / Hostname": "IP-Adresse/Hostnamen eingeben",
"IP Address / Hostname": "IP-Adresse/Hostnamen",
"Count": "Zählen"
}

241
src/static/locale/ru.json Normal file
View File

@ -0,0 +1,241 @@
{
"Welcome to": "Добро пожаловать",
"Username": "Имя",
"Password": "Пароль",
"OTP from your authenticator": "OTP вашего аутентификатора",
"Sign In": "Войти",
"Signing In\\.\\.\\.": "Вход в систему...",
"Access Remote Server": "Доступ к удаленному серверу",
"Server": "Сервер",
"Click": "Клик",
"Pinging...": "Пинг...",
"to add your server": "чтобы добавить свой сервер",
"Server List": "Список серверов",
"Sorry, your username or password is incorrect.": "К сожалению, ваше имя пользователя или пароль неверны.",
"Home": "Главная",
"Settings": "Настройки",
"Tools": "Инструменты",
"Sign Out": "Выйти",
"Checking for update...": "Проверка наличия обновлений...",
"You're on the latest version": "Вы используете последнюю версию",
"WireGuard Configurations": "Конфигурация WireGuard",
"You don't have any WireGuard configurations yet. Please check the configuration folder or change it in Settings. By default the folder is /etc/wireguard.": "У вас еще нет настроек WireGuard. Пожалуйста, проверьте папку конфигурации или измените ее в настройках. По умолчанию используется папка /etc/wireguard.",
"Configuration": "Конфигурация",
"Configurations": "Конфигурации",
"Peers Default Settings": "Настройки Peers по умолчанию",
"Dashboard Theme": "Тема интерфейса",
"Light": "Светлая",
"Dark": "Темная",
"This will be changed globally, and will be apply to all peer's QR code and configuration file.": "Это будет изменено глобально и будет применяться ко всем QR-кодам и конфигурационным файлам Peer's.",
"WireGuard Configurations Settings": "Параметры конфигурации WireGuard",
"Configurations Directory": "Каталог конфигураций",
"Remember to remove / at the end of your path. e.g /etc/wireguard": "Не забудьте удалить символ в конце вашего пути - например /etc/wireguard",
"WGDashboard Account Settings": "Настройки учетной записи WGDashboard",
"Current Password": "Текущий пароль",
"New Password": "Новый пароль",
"Repeat New Password": "Повторите новый пароль",
"Update Password": "Обновить пароль",
"Multi-Factor Authentication \\(MFA\\)": "Многофакторная аутентификация (MFA)",
"Reset": "Сброс",
"Setup": "Установка",
"API Keys": "Ключи API",
"API Key": "Ключ API",
"Key": "Ключ",
"Enabled": "Включен",
"Disabled": "Выключен",
"No WGDashboard API Key": "Нет ключа API для WGDashboard",
"Expire At": "Истекает в",
"Are you sure to delete this API key\\?": "Вы уверены, что нужно удалить этот API-ключ?",
"Create API Key": "Создать ключ API",
"When should this API Key expire\\?": "Когда должен истечь срок действия этого API-ключа?",
"Never Expire": "Бессрочный",
"Don't think that's a good idea": "Не думаю, что это хорошая идея",
"Creating\\.\\.\\.": "Создание...",
"Create": "Создать",
"Status": "Статус",
"On": "Вкл",
"Off": "Выкл",
"Turning On\\.\\.\\.": "Включение...",
"Turning Off\\.\\.\\.": "Выключение...",
"Address": "Адрес",
"Listen Port": "Порт",
"Public Key": "Публичный ключ",
"Connected Peers": "Подключенные клиенты",
"Total Usage": "Общий трафик",
"Total Received": "Всего получено",
"Total Sent": "Всего отправлено",
"Peers Data Usage": "Использовано Peers",
"Real Time Received Data Usage": "Входящий трафик в реальном времени",
"Real Time Sent Data Usage": "Исходящий трафик в реальном времени",
"Peer": "Peer",
"Peers": "Peers",
"Peer Settings": "Настройки Peer",
"Download All": "Загрузить все",
"Search Peers\\.\\.\\.": "Поиск Peers...",
"Display": "Отобразить",
"Sort By": "Сортировать по",
"Refresh Interval": "Интервал обновления",
"Name": "Имя",
"Allowed IPs": "Разрешенные IP-адреса",
"Restricted": "Прекращенный",
"(.*) Seconds": "$1 сек.",
"(.*) Minutes": "$1 мин..",
"Configuration Settings": "Параметры конфигурации",
"Peer Jobs": "Задачи Peer",
"Active Jobs": "Активные задачи",
"All Active Jobs": "Все активные задачи",
"Logs": "Журнал",
"Private Key": "Приватный ключ",
"\\(Required for QR Code and Download\\)": "(Требуется для получения QR-кода и загрузки)",
"\\(Required\\)": "(Обязательный)",
"Endpoint Allowed IPs": "Разрешенные IP-адреса конечной точки",
"DNS": "Сервер DNS",
"Optional Settings": "Дополнительные настройки",
"Pre-Shared Key": "Предварительный общий ключ",
"MTU": "MTU",
"Persistent Keepalive": "Поддержание активности",
"Reset Data Usage": "Сброс статистики использования",
"Total": "Общий",
"Sent": "Отправлено",
"Received": "Получено",
"Revert": "Возвращено",
"Save Peer": "Сохранить Peer",
"QR Code": "QR код",
"Schedule Jobs": "План задач",
"Job": "Задача",
"Job ID": "ID задачи",
"Unsaved Job": "Несохраненная задача",
"This peer does not have any job yet\\.": "У этого Peer нет заданий",
"if": "Если",
"is": "является",
"then": "тогда",
"larger than": "больше чем",
"Date": "Дата",
"Restrict Peer": "Остановить Peer",
"Delete Peer": "Удалить Peer",
"Edit": "Правка",
"Delete": "Удалить",
"Deleting...": "Удаление...",
"Cancel": "Закрыть",
"Save": "Сохранить",
"No active job at the moment\\.": "На данный момент нет активных задач.",
"Jobs Logs": "Журнал задач",
"Updated at": "Обновлено в",
"Refresh": "Обновить",
"Filter": "Фильтр",
"Success": "Успешно",
"Failed": "Отказано",
"Log ID": "ID журнала",
"Message": "Сообщение",
"Share Peer": "Поделиться Peer",
"Currently the peer is not sharing": "В настоящее время Peer не предоставляет общий доступ",
"Sharing\\.\\.\\.": "Делимся...",
"Start Sharing": "Начать делиться",
"Stop Sharing\\.\\.\\.": "Прекращаем делиться...",
"Stop Sharing": "Прекратить делиться",
"Access Restricted": "Доступ прекращен",
"Restrict Access": "Прекратить доступ",
"Restricting\\.\\.\\.": "Прекращаем...",
"Allow Access": "Разрешить доступ",
"Allowing Access\\.\\.\\.": "Разрешаем доступ...",
"Download \\& QR Code is not available due to no private key set for this peer": "Загрузка и QR-код недоступны из-за отсутствия закрытого ключа, установленного для этого Peer",
"Add Peers": "Добавить Peers",
"Bulk Add": "Массовое добавление",
"By adding peers by bulk, each peer's name will be auto generated, and Allowed IP will be assign to the next available IP\\.": "При массовом добавлении, имя каждого Peer's будет сгенерировано автоматически, а разрешенный IP-адрес будет присвоен по порядку следования",
"How many peers you want to add\\?": "Сколько Peers вы хотите добавить?",
"You can add up to (.*) peers": "Вы можете добавить до $1 peers",
"Use your own Private and Public Key": "Используйте свой закрытый и открытый ключ",
"Enter IP Address/CIDR": "Введите IP-адрес/CIDR",
"IP Address/CIDR": "IP-адреса/CIDR",
"or": "или",
"Pick Available IP": "Выберите доступный IP-адрес",
"No available IP containing": "Нет доступного IP-адреса",
"Add": "Добавить",
"Adding\\.\\.\\.": "Добавление...",
"Failed to check available update": "Не удалось проверить обновление",
"Nice to meet you!": "Приятно познакомиться с вами!",
"Please fill in the following fields to finish setup": "Пожалуйста, заполните следующие поля, чтобы завершить настройку",
"Create an account": "Создать учетную запись",
"Enter an username you like": "Введите имя пользователя",
"Enter a password": "Введите пароль",
"\\(At least 8 characters and make sure is strong enough!\\)": "(Убедитесь что ваш пароль достаточно сложный, для этого наберите минимум 8 символов!)",
"Confirm password": "Подтвердите пароль",
"Next": "Далее",
"Saving\\.\\.\\.": "Сохранение...",
"1\\. Please scan the following QR Code to generate TOTP with your choice of authenticator": "1.Пожалуйста, отсканируйте следующий QR-код, чтобы сгенерировать TOTP с выбранным вами средством аутентификации",
"Or you can click the link below:": "Или вы можете перейти по ссылке ниже:",
"2\\. Enter the TOTP generated by your authenticator to verify": "2. Для подтверждения Введите TOTP, сгенерированный вашим аутентификатором",
"TOTP verified!": "TOTP подтвержден!",
"I don't need MFA": "Мне не нужен MFA",
"Complete": "Завершить",
"(v[0-9.]{1,}) is now available for update!": "$1 теперь доступна для обновления!",
"Current Version:": "Текущая версия: ",
"Oh no\\.\\.\\. This link is either expired or invalid\\.": "О нет... Срок действия этой ссылки истек или она недействительна.",
"Scan QR Code with the WireGuard App to add peer": "Чтобы добавить peer, отсканируйте QR-код с помощью приложения WireGuard",
"or click the button below to download the ": "или нажмите на кнопку ниже, чтобы скачать файл конфигурации ",
" file": " файл",
"FROM ": "ОТ ",
"(.*) is on": "$1 включен",
"(.*) is off": "$1 выключен",
"Allowed IPs is invalid": "Разрешенные IP-адреса недопустимы",
"Peer created successfully": "Peer успешно создан",
"Please fill in all required box": "Пожалуйста, заполните все обязательные поля",
"Please specify amount of peers you want to add": "Пожалуйста, укажите количество Peers, которых вы хотите добавить",
"No more available IP can assign": "Нет доступных IP-адресов",
"The maximum number of peers can add is (.*)": "Максимальное количество Peers, которые можно добавить, составляет $1",
"Generating key pairs by bulk failed": "Не удалось сгенерировать пары ключей массовым способом",
"Failed to add peers in bulk": "Не удалось массово добавить Peers",
"This peer already exist": "Этот Peer уже существует",
"This IP is not available: (.*)": "Этот IP-адрес недоступен: $1",
"Configuration does not exist": "Конфигурация не существует",
"Peer does not exist": "Peer не существует",
"Please provide a valid configuration name": "Пожалуйста, укажите действительное имя конфигурации",
"Peer saved": "Peer сохранен",
"Allowed IPs already taken by another peer": "Разрешенные IP-адреса, уже занятые Peer",
"Endpoint Allowed IPs format is incorrect": "Неверный формат IP-адреса для конечной точки",
"DNS format is incorrect": "Неправильный формат DNS",
"MTU format is not correct": "Неправильный формат MTU",
"Persistent Keepalive format is not correct": "Неправильный формат значения поддержания активности",
"Private key does not match with the public key": "Закрытый ключ не совпадает с открытым ключом",
"Update peer failed when updating Pre-Shared Key": "Не удалось обновить Peer при обновлении Pre-Shared ключа",
"Update peer failed when updating Allowed IPs": "Не удалось обновить Peer при обновлении разрешенных IP-адресов",
"Update peer failed when saving the configuration": "Ошибка обновления Peer при сохранении конфигурации",
"Peer data usage reset successfully": "Успешный сброс статистики Peer",
"Peer download started": "Старт загрузки Peer",
"Please specify one or more peers": "Пожалуйста, укажите одного или несколькихх Peers",
"Share link failed to create. Reason: (.*)": "Не удалось создать ссылку для общего доступа. Причина: $1",
"Link expire date updated": "Обновлена дата действия ссылки",
"Link expire date failed to update. Reason: (.*)": "Не удалось обновить дату действия ссылки. Причина: $1",
"Peer job saved": "Задача для Peer сохранена",
"Please specify job": "Пожалуйста, укажите задачу",
"Please specify peer and configuration": "Пожалуйста, укажите Peer и конфигурацию",
"Peer job deleted": "Задача для Peer удалена",
"API Keys function is successfully enabled": "Функция API-ключей успешно включена",
"API Keys function is successfully disabled": "Функция API-ключей успешно отключена",
"API Keys function is failed to enable": "Не удалось включить функцию API-ключей",
"API Keys function is failed to disable": "Не удалось отключить функцию API-ключей",
"WGDashboard API Keys function is disabled": "Функция API-ключей WGDashboard отключена",
"WireGuard configuration path saved": "Путь к конфигурации WireGuard сохранен",
"API Key deleted": "Ключ API удален",
"API Key created": "Ключ API создан",
"Sign in session ended, please sign in again": "Сеанс входа в систему завершен, пожалуйста, войдите в систему еще раз",
"Please specify an IP Address (v4/v6)": "Пожалуйста, укажите IP-адрес (v4/v6) ",
"Please provide ipAddress and count": "Пожалуйста, укажите ip-адрес и количество",
"Please provide ipAddress": "Пожалуйста, укажите IP-адрес",
"Dashboard Language": "Язык панели мониторинга",
"Dashboard language update failed": "Не удалось обновить язык панели мониторинга",
"Peer Remote Endpoint": "Адрес для подключения Peer",
"New Configuration": "Новая конфигурация",
"Configuration Name": "Имя конфигурации",
"Configuration name is invalid. Possible reasons:": "Неверное имя конфигурации. Возможные причины:",
"Configuration name already exist\\.": "Имя конфигурации уже существует. ",
"Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen\\.": "Название конфигурации может содержать только 15 строчных/прописных букв, цифры, подчеркивание, знак равенства, знак плюс, точку и дефис ",
"Invalid Port": "Неверный порт",
"Save Configuration": "Сохранить конфигурацию",
"IP Address/CIDR is invalid": "IP-адрес/CIDR недействителен",
"IP Address": "IP-адрес",
"Enter IP Address / Hostname": "Введите IP-адрес/имя хоста",
"IP Address / Hostname": "IP-адрес/имя хоста",
"Count": "Считать"
}

View File

@ -232,5 +232,10 @@
"Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen\\.": "Назва конфігурації може містити лише 15 літер нижнього/верхнього регістру, цифри, підкреслення, знак рівності, плюс, крапку та дефіс. ",
"Invalid Port": "Невірний порт",
"Save Configuration": "Зберегти конфігурацію",
"IP Address/CIDR is invalid": "IP-адреса/CIDR невірна"
"IP Address/CIDR is invalid": "IP-адреса/CIDR невірна",
"IP Address": "IP-адреса",
"Enter IP Address / Hostname": "Введіть IP-адресу / ім’я хоста",
"IP Address / Hostname": "IP-адресу / ім’я хоста",
"Count": "Граф"
}

View File

@ -232,5 +232,15 @@
"Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen\\.": "配置名称只能含有15个小/大写英文字母,数字,下划线,等于号,加号,小数点或短横线。",
"Invalid Port": "错误的端口",
"Save Configuration": "保存配置",
"IP Address/CIDR is invalid": "IP 地址/前缀长度格式错误"
"IP Address/CIDR is invalid": "IP 地址/前缀长度格式错误",
"IP Address": "IP 地址",
"Enter IP Address / Hostname": "输入 IP 地址 / 域名",
"IP Address / Hostname": "IP 地址 / 域名",
"Dashboard IP Address \\& Listen Port": "面板 IP地址 & 监听端口",
"Count": "数量",
"Geolocation": "地理位置",
"Is Alive": "在线",
"Average / Min / Max Round Trip Time": "平均 / 最低 / 最高来回通讯延迟",
"Sent / Received / Lost Package": "发送 / 接收 / 丢失数据包",
"Manual restart of WGDashboard is needed to apply changes on IP Address and Listen Port": "更改 IP 地址或监听端口后需要手动重启 WGDashboard 以使用最新的设置"
}

View File

@ -232,5 +232,15 @@
"Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen\\.": "配置名稱只能含有15個小/大寫英文字母,數字,下划線,等於號,加號,小數點或短橫線。",
"Invalid Port": "錯誤的端口",
"Save Configuration": "保存配置",
"IP Address/CIDR is invalid": "IP 地址/前綴長度格式錯誤"
"IP Address/CIDR is invalid": "IP 地址/前綴長度格式錯誤",
"IP Address": "IP 地址",
"Enter IP Address / Hostname": "输入 IP 地址 / 域名",
"IP Address / Hostname": "IP 地址 / 域名",
"Count": "數量",
"Geolocation": "地理位置",
"Is Alive": "在線",
"Average / Min / Max Round Trip Time": "平均 / 最低 / 最高來回通信延遲",
"Sent / Received / Lost Package": "發送 / 接收 / 遺失數據包",
"Manual restart of WGDashboard is needed to apply changes on IP Address and Listen Port": "更改 IP 位址或監聽埠後需要手動重新啟動 WGDashboard 以使用最新的設置"
}