mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-10-03 15:56:17 +00:00
Added feature #525
This commit is contained in:
@@ -891,6 +891,31 @@ def API_getConfigurationInfo():
|
|||||||
"configurationRestrictedPeers": WireguardConfigurations[configurationName].getRestrictedPeersList()
|
"configurationRestrictedPeers": WireguardConfigurations[configurationName].getRestrictedPeersList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.get(f'{APP_PREFIX}/api/getPeerHistoricalEndpoints')
|
||||||
|
def API_GetPeerHistoricalEndpoints():
|
||||||
|
configurationName = request.args.get("configurationName")
|
||||||
|
id = request.args.get('id')
|
||||||
|
if not configurationName or not id:
|
||||||
|
return ResponseObject(False, "Please provide configurationName and id")
|
||||||
|
fp, p = WireguardConfigurations.get(configurationName).searchPeer(id)
|
||||||
|
if fp:
|
||||||
|
result = p.getEndpoints()
|
||||||
|
geo = {}
|
||||||
|
try:
|
||||||
|
r = requests.post(f"http://ip-api.com/batch?fields=city,country,lat,lon,query",
|
||||||
|
data=json.dumps([x['endpoint'] for x in result]))
|
||||||
|
d = r.json()
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return ResponseObject(data=result, message="Failed to request IP address geolocation. " + str(e))
|
||||||
|
|
||||||
|
return ResponseObject(data={
|
||||||
|
"endpoints": p.getEndpoints(),
|
||||||
|
"geolocation": d
|
||||||
|
})
|
||||||
|
return ResponseObject(False, "Peer does not exist")
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/getPeerSessions')
|
@app.get(f'{APP_PREFIX}/api/getPeerSessions')
|
||||||
def API_GetPeerSessions():
|
def API_GetPeerSessions():
|
||||||
configurationName = request.args.get("configurationName")
|
configurationName = request.args.get("configurationName")
|
||||||
|
@@ -160,6 +160,15 @@ class AmneziaWireguardConfiguration(WireguardConfiguration):
|
|||||||
extend_existing=True
|
extend_existing=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.peersHistoryEndpointTable = sqlalchemy.Table(
|
||||||
|
f'{dbName}_history_endpoint', self.metadata,
|
||||||
|
sqlalchemy.Column('id', sqlalchemy.String(255), nullable=False),
|
||||||
|
sqlalchemy.Column('endpoint', sqlalchemy.String(255), nullable=False),
|
||||||
|
sqlalchemy.Column('time',
|
||||||
|
(sqlalchemy.DATETIME if self.DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else sqlalchemy.TIMESTAMP)),
|
||||||
|
extend_existing=True
|
||||||
|
)
|
||||||
|
|
||||||
self.metadata.create_all(self.engine)
|
self.metadata.create_all(self.engine)
|
||||||
|
|
||||||
def getPeers(self):
|
def getPeers(self):
|
||||||
|
@@ -236,6 +236,20 @@ class Peer:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def getEndpoints(self):
|
||||||
|
result = []
|
||||||
|
with self.configuration.engine.connect() as conn:
|
||||||
|
result = conn.execute(
|
||||||
|
db.select(
|
||||||
|
self.configuration.peersHistoryEndpointTable.c.endpoint
|
||||||
|
).group_by(
|
||||||
|
self.configuration.peersHistoryEndpointTable.c.endpoint
|
||||||
|
).where(
|
||||||
|
self.configuration.peersHistoryEndpointTable.c.id == self.id
|
||||||
|
)
|
||||||
|
).mappings().fetchall()
|
||||||
|
return list(result)
|
||||||
|
|
||||||
def getTraffics(self, interval: int = 30, startDate: datetime.datetime = None, endDate: datetime.datetime = None):
|
def getTraffics(self, interval: int = 30, startDate: datetime.datetime = None, endDate: datetime.datetime = None):
|
||||||
if startDate is None and endDate is None:
|
if startDate is None and endDate is None:
|
||||||
endDate = datetime.datetime.now()
|
endDate = datetime.datetime.now()
|
||||||
|
@@ -452,11 +452,9 @@ class WireguardConfiguration:
|
|||||||
self.peersTable.columns.id == i['PublicKey']
|
self.peersTable.columns.id == i['PublicKey']
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
tmpList.append(Peer(tempPeer, self))
|
tmpList.append(Peer(tempPeer, self))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[WGDashboard] {self.Name} getPeers() Error: {str(e)}")
|
print(f"[WGDashboard] {self.Name} getPeers() Error: {str(e)}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
with self.engine.connect() as conn:
|
with self.engine.connect() as conn:
|
||||||
existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall()
|
existingPeers = conn.execute(self.peersTable.select()).mappings().fetchall()
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
import {computed, ref, watch} from "vue";
|
import {ref} from "vue";
|
||||||
import dayjs from "dayjs";
|
|
||||||
import {GetLocale} from "@/utilities/locale.js"
|
|
||||||
import {
|
import {
|
||||||
Chart,
|
Chart,
|
||||||
LineElement,
|
LineElement,
|
||||||
@@ -32,6 +31,7 @@ Chart.register(
|
|||||||
);
|
);
|
||||||
import PeerSessions from "@/components/peerDetailsModalComponents/peerSessions.vue";
|
import PeerSessions from "@/components/peerDetailsModalComponents/peerSessions.vue";
|
||||||
import PeerTraffics from "@/components/peerDetailsModalComponents/peerTraffics.vue";
|
import PeerTraffics from "@/components/peerDetailsModalComponents/peerTraffics.vue";
|
||||||
|
import PeerEndpoints from "@/components/peerDetailsModalComponents/peerEndpoints.vue";
|
||||||
const props = defineProps(['selectedPeer'])
|
const props = defineProps(['selectedPeer'])
|
||||||
const selectedDate = ref(undefined)
|
const selectedDate = ref(undefined)
|
||||||
defineEmits(['close'])
|
defineEmits(['close'])
|
||||||
@@ -160,7 +160,12 @@ defineEmits(['close'])
|
|||||||
@selectDate="args => selectedDate = args"
|
@selectDate="args => selectedDate = args"
|
||||||
:selectedPeer="selectedPeer"></PeerSessions>
|
:selectedPeer="selectedPeer"></PeerSessions>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<PeerEndpoints
|
||||||
|
:selectedPeer="selectedPeer"
|
||||||
|
>
|
||||||
|
</PeerEndpoints>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@@ -0,0 +1,164 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
|
import { fetchGet } from "@/utilities/fetch.js"
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
const props = defineProps(['selectedPeer'])
|
||||||
|
const loaded = ref(false)
|
||||||
|
const endpoints = ref(undefined)
|
||||||
|
const mapAvailable = ref(undefined)
|
||||||
|
|
||||||
|
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 {linear} from 'ol/easing';
|
||||||
|
import {Circle, Fill, Stroke, Style, Text} from "ol/style.js";
|
||||||
|
import {Vector} from "ol/layer"
|
||||||
|
import {Vector as SourceVector} from "ol/source"
|
||||||
|
|
||||||
|
const map = ref(undefined)
|
||||||
|
|
||||||
|
const loadEndpoints = async () => {
|
||||||
|
await fetchGet("/api/getPeerHistoricalEndpoints", {
|
||||||
|
id: props.selectedPeer.id,
|
||||||
|
configurationName: props.selectedPeer.configuration.Name
|
||||||
|
}, async (res) => {
|
||||||
|
if (res.status){
|
||||||
|
endpoints.value = res.data
|
||||||
|
}
|
||||||
|
loaded.value = true
|
||||||
|
if (endpoints.value.geolocation){
|
||||||
|
try{
|
||||||
|
await fetch("https://tile.openstreetmap.org/",
|
||||||
|
{ signal: AbortSignal.timeout(1500) })
|
||||||
|
mapAvailable.value = true;
|
||||||
|
map.value = new Map({
|
||||||
|
target: 'map',
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new OSM(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
center: fromLonLat([17.64, 16.35]),
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (endpoints.value.geolocation){
|
||||||
|
const vectorSource = new SourceVector();
|
||||||
|
let geo = endpoints.value.geolocation.filter(x => x.lat && x.lon)
|
||||||
|
geo.forEach(data => {
|
||||||
|
vectorSource.addFeature(new Feature({
|
||||||
|
geometry: new Point(fromLonLat([data.lon, data.lat])),
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
vectorSource.addFeature(new Feature({
|
||||||
|
|
||||||
|
}))
|
||||||
|
map.value.addLayer(new Vector({
|
||||||
|
source: vectorSource,
|
||||||
|
style: () => {
|
||||||
|
return new Style({
|
||||||
|
image: new Circle({
|
||||||
|
radius: 10,
|
||||||
|
fill: new Fill({ color: '#0d6efd' }),
|
||||||
|
stroke: new Stroke({ color: 'white', width: 5 }),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
mapAvailable.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => loadEndpoints())
|
||||||
|
const getGeolocation = (endpoint) => {
|
||||||
|
if (endpoints.value.geolocation){
|
||||||
|
let geo = endpoints.value.geolocation.find(x => x.query === endpoint)
|
||||||
|
if (geo){
|
||||||
|
let c = [geo.city, geo.country]
|
||||||
|
if (c.filter(x => x !== undefined).length === 0) c.push('Private Address')
|
||||||
|
return c.filter(x => x !== undefined).join(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const setMapCenter = (endpoint) => {
|
||||||
|
if (endpoints.value.geolocation){
|
||||||
|
let geo = endpoints.value.geolocation.find(x => x.query === endpoint)
|
||||||
|
if (geo && geo.lon && geo.lat){
|
||||||
|
map.value.getView().animate({zoom: 4}, {center: fromLonLat([
|
||||||
|
geo.lon, geo.lat
|
||||||
|
])}, {easing: linear})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card rounded-3 bg-transparent">
|
||||||
|
<div class="card-header text-muted">
|
||||||
|
<LocaleText t="Peer Historical Endpoints"></LocaleText>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="bg-body-tertiary p-3 d-flex rounded-3">
|
||||||
|
<div class="m-auto" v-if="!loaded">
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span><LocaleText t="Loading..."></LocaleText>
|
||||||
|
</div>
|
||||||
|
<div v-else class="w-100 d-flex flex-column gap-3">
|
||||||
|
<div class="bg-body d-flex w-100 rounded-3" style="height: 500px" id="map">
|
||||||
|
<div class="m-auto" v-if="!mapAvailable">
|
||||||
|
<div v-if="mapAvailable === undefined">
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
<LocaleText t="Loading Map..."></LocaleText>
|
||||||
|
</div>
|
||||||
|
<div v-if="mapAvailable === false" class="text-muted">
|
||||||
|
<LocaleText t="Map is not available"></LocaleText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Endpoint</th>
|
||||||
|
<th v-if="endpoints.geolocation">Geolocation</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="endpoint in endpoints.endpoints"
|
||||||
|
@click="setMapCenter(endpoint.endpoint)"
|
||||||
|
style="cursor: pointer">
|
||||||
|
<td>
|
||||||
|
{{ endpoint.endpoint }}
|
||||||
|
</td>
|
||||||
|
<td v-if="endpoints.geolocation">
|
||||||
|
{{ getGeolocation(endpoint.endpoint) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@@ -78,8 +78,11 @@ const emits = defineEmits(['selectDate'])
|
|||||||
<button class="btn btn-sm rounded-3" v-if="offsetMonth !== 0" @click="offsetMonth = 0; $emit('selectDate', day)">
|
<button class="btn btn-sm rounded-3" v-if="offsetMonth !== 0" @click="offsetMonth = 0; $emit('selectDate', day)">
|
||||||
<LocaleText t="Today"></LocaleText>
|
<LocaleText t="Today"></LocaleText>
|
||||||
</button>
|
</button>
|
||||||
<h5 class="mx-auto mb-0">
|
<h5 class="mx-auto mb-0 text-center">
|
||||||
{{ today.format('YYYY-MM')}}
|
<small class="text-muted" style="font-size: 0.9rem">
|
||||||
|
<LocaleText t="Peer Historical Sessions"></LocaleText>
|
||||||
|
</small><br>
|
||||||
|
{{ today.format('YYYY / MM')}}
|
||||||
</h5>
|
</h5>
|
||||||
<button class="btn btn-sm rounded-3" v-if="offsetMonth !== 0" @click="offsetMonth = 0; $emit('selectDate', day)">
|
<button class="btn btn-sm rounded-3" v-if="offsetMonth !== 0" @click="offsetMonth = 0; $emit('selectDate', day)">
|
||||||
<LocaleText t="Today"></LocaleText>
|
<LocaleText t="Today"></LocaleText>
|
||||||
|
@@ -20,7 +20,6 @@ const getTraffic = async () => {
|
|||||||
endDate: date.value.format("YYYY-MM-DD")
|
endDate: date.value.format("YYYY-MM-DD")
|
||||||
}, (res) => {
|
}, (res) => {
|
||||||
traffics.value = res.data
|
traffics.value = res.data
|
||||||
console.log(traffics.value)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const interval = ref(undefined)
|
const interval = ref(undefined)
|
||||||
|
Reference in New Issue
Block a user