1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 07:10:09 +01:00

Added OpenStreetMap for Ping and Traceroute

This commit is contained in:
Donald Zou 2024-10-02 17:09:35 +08:00
parent 7fe4889b6e
commit ff0147bebb
9 changed files with 527 additions and 134 deletions

View File

@ -1781,73 +1781,95 @@ def API_allowAccessPeers(configName: str) -> ResponseObject:
@app.post(f'{APP_PREFIX}/api/addPeers/<configName>') @app.post(f'{APP_PREFIX}/api/addPeers/<configName>')
def API_addPeers(configName): def API_addPeers(configName):
data: dict = 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(): if configName in WireguardConfigurations.keys():
config = WireguardConfigurations.get(configName) try:
if (not bulkAdd and (len(public_key) == 0 or len(allowed_ips) == 0)) or len(endpoint_allowed_ip) == 0: data: dict = request.get_json()
return ResponseObject(False, "Please fill in all required box")
if not config.getStatus():
config.toggleConfiguration()
availableIps = _getWireguardConfigurationAvailableIP(configName) bulkAdd: bool = data.get("bulkAdd", False)
bulkAddAmount: int = data.get('bulkAddAmount', 0)
if bulkAdd: preshared_key_bulkAdd: bool = data.get('preshared_key_bulkAdd', False)
if bulkAddAmount < 1:
return ResponseObject(False, "Please specify amount of peers you want to add")
if not availableIps[0]: public_key: str = data.get('public_key', "")
return ResponseObject(False, "No more available IP can assign") allowed_ips: list[str] = data.get('allowed_ips', "")
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: endpoint_allowed_ip: str = data.get('endpoint_allowed_ip', DashboardConfig.GetConfig("Peers", "peer_endpoint_allowed_ip")[1])
found, peer = config.searchPeer(kp['id']) 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 found:
if not peer.updatePeer(kp['name'], kp['private_key'], kp['preshared_key'], dns_addresses, return peer.updatePeer(name, private_key, preshared_key, dns_addresses, ",".join(allowed_ips),
kp['allowed_ip'], endpoint_allowed_ip, mtu, keep_alive): endpoint_allowed_ip, mtu, keep_alive)
return ResponseObject(False, "Failed to add peers in bulk") except Exception as e:
return ResponseObject() print(e)
return ResponseObject(False, "Add peers failed. Please see data for specific issue")
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 ResponseObject(False, "Configuration does not exist") return ResponseObject(False, "Configuration does not exist")
@ -1992,6 +2014,7 @@ def API_ping_getAllPeersIpAddress():
ips[c.Name] = cips ips[c.Name] = cips
return ResponseObject(data=ips) return ResponseObject(data=ips)
import requests
@app.get(f'{APP_PREFIX}/api/ping/execute') @app.get(f'{APP_PREFIX}/api/ping/execute')
def API_ping_execute(): def API_ping_execute():
@ -2001,8 +2024,8 @@ def API_ping_execute():
try: try:
if ip is not None and len(ip) > 0 and count is not None and count.isnumeric(): 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) result = ping(ip, count=int(count), source=None)
return ResponseObject(data={ data = {
"address": result.address, "address": result.address,
"is_alive": result.is_alive, "is_alive": result.is_alive,
"min_rtt": result.min_rtt, "min_rtt": result.min_rtt,
@ -2010,9 +2033,17 @@ def API_ping_execute():
"max_rtt": result.max_rtt, "max_rtt": result.max_rtt,
"package_sent": result.packets_sent, "package_sent": result.packets_sent,
"package_received": result.packets_received, "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)") return ResponseObject(False, "Please specify an IP Address (v4/v6)")
except Exception as exp: except Exception as exp:
return ResponseObject(False, exp) return ResponseObject(False, exp)
@ -2024,7 +2055,7 @@ def API_traceroute_execute():
if "ipAddress" in request.args.keys() and len(request.args.get("ipAddress")) > 0: if "ipAddress" in request.args.keys() and len(request.args.get("ipAddress")) > 0:
ipAddress = request.args.get('ipAddress') ipAddress = request.args.get('ipAddress')
try: try:
tracerouteResult = traceroute(ipAddress) tracerouteResult = traceroute(ipAddress, timeout=1, max_hops=64)
result = [] result = []
for hop in tracerouteResult: for hop in tracerouteResult:
if len(result) > 1: if len(result) > 1:
@ -2049,6 +2080,15 @@ def API_traceroute_execute():
"min_rtt": hop.min_rtt, "min_rtt": hop.min_rtt,
"max_rtt": hop.max_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) return ResponseObject(data=result)
except Exception as exp: except Exception as exp:
return ResponseObject(False, exp) return ResponseObject(False, exp)
@ -2077,7 +2117,6 @@ def API_getDashboardUpdate():
Sign Up Sign Up
''' '''
@app.get(f'{APP_PREFIX}/api/isTotpEnabled') @app.get(f'{APP_PREFIX}/api/isTotpEnabled')
def API_isTotpEnabled(): def API_isTotpEnabled():
return ( return (

View File

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

View File

@ -1,12 +1,12 @@
{ {
"name": "app", "name": "app",
"version": "4.0.2", "version": "4.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "app", "name": "app",
"version": "4.0.2", "version": "4.1.0",
"dependencies": { "dependencies": {
"@vuepic/vue-datepicker": "^9.0.1", "@vuepic/vue-datepicker": "^9.0.1",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
@ -21,6 +21,7 @@
"i": "^0.3.7", "i": "^0.3.7",
"is-cidr": "^5.0.3", "is-cidr": "^5.0.3",
"npm": "^10.5.0", "npm": "^10.5.0",
"ol": "^10.2.1",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qrcodejs": "^1.0.0", "qrcodejs": "^1.0.0",
@ -723,6 +724,11 @@
"node": ">=10" "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": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -964,6 +970,11 @@
"xmlbuilder": ">=11.0.1" "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": { "node_modules/@types/verror": {
"version": "1.10.10", "version": "1.10.10",
"resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", "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", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" "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": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "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", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" "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": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2358,6 +2404,24 @@
"node": ">=10" "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": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -2731,6 +2795,11 @@
"safe-buffer": "~5.1.0" "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": { "node_modules/locate-path": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@ -5476,6 +5545,24 @@
"inBundle": true, "inBundle": true,
"license": "ISC" "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": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "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", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" "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": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" "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": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -5687,6 +5795,11 @@
"node": ">=10" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "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", "resolved": "https://registry.npmjs.org/qrcodejs/-/qrcodejs-1.0.0.tgz",
"integrity": "sha512-67rj3mMBhSBepaD57qENnltO+r8rSYlqM7HGThks/BiyDAkc86sLvkKqjkqPS5v13f7tvnt6dbEf3qt7zq+BCg==" "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": { "node_modules/read-config-file": {
"version": "6.3.2", "version": "6.3.2",
"resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", "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", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" "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": { "node_modules/retry": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
@ -6264,6 +6409,11 @@
"vue": "^3.2.0" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "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", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "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": { "node_modules/xmlbuilder": {
"version": "15.1.1", "version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
@ -6403,6 +6558,11 @@
"engines": { "engines": {
"node": ">= 10" "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", "i": "^0.3.7",
"is-cidr": "^5.0.3", "is-cidr": "^5.0.3",
"npm": "^10.5.0", "npm": "^10.5.0",
"ol": "^10.2.1",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qrcodejs": "^1.0.0", "qrcodejs": "^1.0.0",

View File

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

View File

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

View File

@ -0,0 +1,113 @@
<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"
export default {
name: "map",
props: {
type: "",
d: Object || Array
},
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]
}
},
mounted() {
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)
});
// Add marker to the vector source
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', // Line color
width: 2 // Line width
})
});
}
}
});
map.addLayer(vectorLayer);
}
}
</script>
<template>
<div id="map" class="w-100 rounded-3" style="height: 300px"></div>
</template>
<style>
.ol-layer canvas{
border-radius: var(--bs-border-radius-lg) !important;
}
</style>

View File

@ -1,9 +1,12 @@
<script> <script>
import {fetchGet} from "@/utilities/fetch.js"; import {fetchGet} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js"; import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import LocaleText from "@/components/text/localeText.vue";
import Map from "@/components/map/map.vue";
export default { export default {
name: "ping", name: "ping",
components: {Map, LocaleText},
data(){ data(){
return { return {
loading: false, loading: false,
@ -67,9 +70,11 @@ export default {
<div class="col-sm-4 d-flex gap-2 flex-column"> <div class="col-sm-4 d-flex gap-2 flex-column">
<div> <div>
<label class="mb-1 text-muted" for="configuration"> <label class="mb-1 text-muted" for="configuration">
<small>Configuration</small></label> <small>
<LocaleText t="Configuration"></LocaleText>
</small></label>
<select class="form-select" v-model="this.selectedConfiguration"> <select class="form-select" v-model="this.selectedConfiguration">
<option disabled selected :value="undefined">Select a Configuration...</option> <option disabled selected :value="undefined"></option>
<option :value="key" v-for="(val, key) in this.cips"> <option :value="key" v-for="(val, key) in this.cips">
{{key}} {{key}}
</option> </option>
@ -77,9 +82,11 @@ export default {
</div> </div>
<div> <div>
<label class="mb-1 text-muted" for="peer"> <label class="mb-1 text-muted" for="peer">
<small>Peer</small></label> <small>
<LocaleText t="Peer"></LocaleText>
</small></label>
<select id="peer" class="form-select" v-model="this.selectedPeer" :disabled="this.selectedConfiguration === undefined"> <select id="peer" class="form-select" v-model="this.selectedPeer" :disabled="this.selectedConfiguration === undefined">
<option disabled selected :value="undefined">Select a Peer...</option> <option disabled selected :value="undefined"></option>
<option v-if="this.selectedConfiguration !== undefined" :value="key" v-for="(peer, key) in <option v-if="this.selectedConfiguration !== undefined" :value="key" v-for="(peer, key) in
this.cips[this.selectedConfiguration]"> this.cips[this.selectedConfiguration]">
{{key}} {{key}}
@ -88,9 +95,11 @@ export default {
</div> </div>
<div> <div>
<label class="mb-1 text-muted" for="ip"> <label class="mb-1 text-muted" for="ip">
<small>IP Address</small></label> <small>
<LocaleText t="IP Address"></LocaleText>
</small></label>
<select id="ip" class="form-select" v-model="this.selectedIp" :disabled="this.selectedPeer === undefined"> <select id="ip" class="form-select" v-model="this.selectedIp" :disabled="this.selectedPeer === undefined">
<option disabled selected :value="undefined">Select a IP...</option> <option disabled selected :value="undefined"></option>
<option <option
v-if="this.selectedPeer !== undefined" v-if="this.selectedPeer !== undefined"
v-for="ip in this.cips[this.selectedConfiguration][this.selectedPeer].allowed_ips"> v-for="ip in this.cips[this.selectedConfiguration][this.selectedPeer].allowed_ips">
@ -98,17 +107,50 @@ export default {
</option> </option>
</select> </select>
</div> </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"
v-model="this.selectedIp">
</div>
<div class="w-100 border-top my-2"></div>
<div> <div>
<label class="mb-1 text-muted" for="count"> <label class="mb-1 text-muted" for="count">
<small>Ping Count</small></label> <small>
<input class="form-control" type="number" <LocaleText t="Count"></LocaleText>
v-model="this.count" </small></label>
min="1" id="count" placeholder="How many times you want to ping?">
<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>
<!-- <input class="form-control" type="number" -->
<!-- v-model="this.count"-->
<!-- min="1" id="count" placeholder="How many times you want to ping?">-->
</div> </div>
<button class="btn btn-primary rounded-3 mt-3" <button class="btn btn-primary rounded-3 mt-3"
:disabled="!this.selectedIp" :disabled="!this.selectedIp"
@click="this.execute()"> @click="this.execute()">
<i class="bi bi-person-walking me-2"></i>Go! <i class="bi bi-person-walking me-2"></i>Ping!
</button> </button>
</div> </div>
@ -123,9 +165,23 @@ export default {
<div v-else key="pingResult" class="d-flex flex-column gap-2 w-100"> <div v-else key="pingResult" class="d-flex flex-column gap-2 w-100">
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.15s"> <div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.15s">
<div class="card-body"> <div class="card-body row">
<p class="mb-0 text-muted"><small>Address</small></p> <div class="col-sm">
{{this.pingResult.address}} <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> </div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.3s"> <div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.3s">
@ -140,7 +196,9 @@ export default {
</div> </div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.45s"> <div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.45s">
<div class="card-body"> <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 / <samp>{{this.pingResult.avg_rtt}}ms /
{{this.pingResult.min_rtt}}ms / {{this.pingResult.min_rtt}}ms /
{{this.pingResult.max_rtt}}ms {{this.pingResult.max_rtt}}ms
@ -149,14 +207,16 @@ export default {
</div> </div>
<div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.6s"> <div class="card rounded-3 bg-transparent shadow-sm animate__animated animate__fadeIn" style="animation-delay: 0.6s">
<div class="card-body"> <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}} / <samp>{{this.pingResult.package_sent}} /
{{this.pingResult.package_received}} / {{this.pingResult.package_received}} /
{{this.pingResult.package_loss}} {{this.pingResult.package_loss}}
</samp> </samp>
</div> </div>
</div> </div>
<Map :d="this.pingResult" v-if="this.pingResult.geo && this.pingResult.geo.status === 'success'"></Map>
</div> </div>
</TransitionGroup> </TransitionGroup>

View File

@ -1,9 +1,11 @@
<script> <script>
import {fetchGet} from "@/utilities/fetch.js"; import {fetchGet} from "@/utilities/fetch.js";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js"; import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import Map from "@/components/map/map.vue";
export default { export default {
name: "traceroute", name: "traceroute",
components: {Map},
data(){ data(){
return { return {
tracing: false, tracing: false,
@ -41,55 +43,75 @@ export default {
<div class="mt-md-5 mt-3 text-body"> <div class="mt-md-5 mt-3 text-body">
<div class="container-md"> <div class="container-md">
<h3 class="mb-3 text-body">Traceroute</h3> <h3 class="mb-3 text-body">Traceroute</h3>
<div class="row"> <div class="d-flex gap-2 flex-column mb-5">
<div class="col-sm-4 d-flex gap-2 flex-column"> <div>
<div> <label class="mb-1 text-muted" for="ipAddress">
<label class="mb-1 text-muted" for="ipAddress"> <small>IP Address</small></label>
<small>IP Address</small></label> <input
<input id="ipAddress"
id="ipAddress" class="form-control"
class="form-control" v-model="this.ipAddress"
v-model="this.ipAddress" @keyup.enter="this.execute()"
type="text" placeholder="Enter an IP Address you want to trace :)"> 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> </div>
<div class="col-sm-8 position-relative"> <button class="btn btn-primary rounded-3 mt-3"
<TransitionGroup name="ping"> :disabled="this.tracing"
<div v-if="!this.tracerouteResult" key="pingPlaceholder"> @click="this.execute()">
<div class="pingPlaceholder bg-body-secondary rounded-3 mb-3" <i class="bi bi-bullseye me-2"></i> {{this.tracing ? "Tracing...":"Trace It!"}}
:class="{'animate__animated animate__flash animate__slower animate__infinite': this.tracing}" </button>
:style="{'animation-delay': `${x*0.05}s`}" </div>
v-for="x in 10" ></div> <div class="position-relative">
</div> <TransitionGroup name="ping">
<div v-else key="table" class="w-100"> <div v-if="!this.tracerouteResult" key="pingPlaceholder">
<table class="table table-borderless rounded-3 w-100"> <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 5" ></div>
</div>
<div v-else>
<Map :d="this.tracerouteResult" type="traceroute"></Map>
<div key="table" class="w-100 mt-2">
<table class="table table-sm rounded-3 w-100">
<thead> <thead>
<tr> <tr>
<th scope="col">Hop</th> <th scope="col">Hop</th>
<th scope="col">IP Address</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> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(hop, key) in this.tracerouteResult" <tr v-for="(hop, key) in this.tracerouteResult">
class="animate__fadeInUp animate__animated" <td>
:style="{'animation-delay': `${key * 0.05}s`}" <small>{{hop.hop}}</small>
> </td>
<td>{{hop.hop}}</td> <td>
<td>{{hop.ip}}</td> <small>{{hop.ip}}</small>
<td>{{hop.avg_rtt}} / {{hop.min_rtt}} / {{hop.max_rtt}}</td> </td>
</tr> <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> </tbody>
</table> </table>
</div> </div>
</TransitionGroup> </div>
</div> </TransitionGroup>
</div> </div>
</div> </div>
</div> </div>
@ -123,11 +145,7 @@ export default {
} }
table th, table td{ table th, table td{
padding: 0.9rem; padding: 0.5rem;
}
table tbody{
border-top: 1em solid transparent;
} }
.table > :not(caption) > * > *{ .table > :not(caption) > * > *{