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:
commit
d3c8a350a4
181
src/dashboard.py
181
src/dashboard.py
@ -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 (
|
||||
|
@ -5,4 +5,5 @@ pyotp
|
||||
Flask
|
||||
flask-cors
|
||||
icmplib
|
||||
gunicorn
|
||||
gunicorn
|
||||
requests
|
8
src/static/app/dist/assets/index.css
vendored
8
src/static/app/dist/assets/index.css
vendored
File diff suppressed because one or more lines are too long
74
src/static/app/dist/assets/index.js
vendored
74
src/static/app/dist/assets/index.js
vendored
File diff suppressed because one or more lines are too long
164
src/static/app/package-lock.json
generated
164
src/static/app/package-lock.json
generated
@ -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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -26,7 +26,7 @@ export default {
|
||||
return{
|
||||
data: {
|
||||
bulkAdd: false,
|
||||
bulkAddAmount: "",
|
||||
bulkAddAmount: 0,
|
||||
name: "",
|
||||
allowed_ips: [],
|
||||
private_key: "",
|
||||
|
145
src/static/app/src/components/map/osmap.vue
Normal file
145
src/static/app/src/components/map/osmap.vue
Normal 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>
|
@ -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", {
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
@ -28,5 +28,10 @@
|
||||
"lang_id": "it-IT",
|
||||
"lang_name": "Italian",
|
||||
"lang_name_localized": "Italiano"
|
||||
},
|
||||
{
|
||||
"lang_id": "ru",
|
||||
"lang_name": "Russian",
|
||||
"lang_name_localized": "Русский"
|
||||
}
|
||||
]
|
||||
|
@ -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
241
src/static/locale/ru.json
Normal 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": "Считать"
|
||||
|
||||
}
|
@ -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": "Граф"
|
||||
|
||||
}
|
@ -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 以使用最新的设置"
|
||||
}
|
@ -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 以使用最新的設置"
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user