Working front end
This commit is contained in:
@ -748,7 +748,14 @@
|
|||||||
|
|
||||||
<!-- position -->
|
<!-- position -->
|
||||||
<div>
|
<div>
|
||||||
<div class="bg-gray-200 p-2 font-semibold">Position</div>
|
<div @click.stop class="flex bg-gray-200 p-2 font-semibold">
|
||||||
|
<div class="my-auto">Position</div>
|
||||||
|
<div class="ml-auto">
|
||||||
|
<button @click="handleNodePositionHistory(selectedNode.node_id)" type="button" class="rounded bg-white px-2 py-1 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
|
||||||
|
History
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
<ul role="list" class="flex-1 divide-y divide-gray-200">
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
@ -1589,6 +1596,56 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- node position history modal -->
|
||||||
|
<div class="relative z-sidebar" role="dialog" aria-modal="true">
|
||||||
|
|
||||||
|
<!-- sidebar -->
|
||||||
|
<transition
|
||||||
|
enter-active-class="transition duration-300 ease-in-out transform"
|
||||||
|
enter-from-class="translate-y-full"
|
||||||
|
enter-to-class="translate-y-0"
|
||||||
|
leave-active-class="transition duration-300 ease-in-out transform"
|
||||||
|
leave-from-class="translate-y-0"
|
||||||
|
leave-to-class="translate-y-full">
|
||||||
|
<div v-show="selectedNodeToShowPositionHistory != null" class="fixed left-0 right-0 bottom-0">
|
||||||
|
<div v-if="selectedNodeToShowPositionHistory != null" class="mx-auto w-screen max-w-md p-4">
|
||||||
|
<div class="flex h-full flex-col bg-white shadow-xl rounded-xl border">
|
||||||
|
<div class="p-2">
|
||||||
|
<div class="flex items-start justify-between">
|
||||||
|
<div>
|
||||||
|
<h2 class="font-bold">{{ selectedNodeToShowPositionHistory.short_name }} Position history</h2>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label for="datetime-from" class="text-sm pr-1">From:</label>
|
||||||
|
<input type="datetime-local" name="datetime-from" id="datetime-from" v-model="this.positionHistoryFromTimeDate" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-1">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label for="datetime-to" class="text-sm pr-1">To:</label>
|
||||||
|
<input type="datetime-local" name="datetime-to" id="datetime-to" v-model="this.positionHistoryToTimeDate" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button @click="loadNodePositionHistory(this.selectedNodeToShowPositionHistory.node_id)" class="bg-blue-500 text-white rounded-lg p-2 mt-2">Fetch</button>
|
||||||
|
</div>
|
||||||
|
<div class="my-auto ml-3 flex h-7 items-center">
|
||||||
|
<a href="javascript:void(0)" class="rounded-full" @click="dismissShowingNodePositionHistory">
|
||||||
|
<div class="bg-gray-100 hover:bg-gray-200 p-2 rounded-full">
|
||||||
|
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M18 6l-12 12"></path>
|
||||||
|
<path d="M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -1652,7 +1709,7 @@
|
|||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
|
||||||
// overlays enabled by default
|
// overlays enabled by default
|
||||||
return ["Legend"];
|
return ["Legend", "Position History"];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1755,6 +1812,13 @@
|
|||||||
selectedNodeMqttMetrics: [],
|
selectedNodeMqttMetrics: [],
|
||||||
selectedNodeTraceroutes: [],
|
selectedNodeTraceroutes: [],
|
||||||
|
|
||||||
|
positionHistoryFromTimeDate: new Date(new Date().getTime() - 3600 * 1000).toISOString().slice(0, 16),
|
||||||
|
positionHistoryToTimeDate: new Date().toISOString().slice(0, 16),
|
||||||
|
selectedNodePositionHistory: [],
|
||||||
|
selectedNodeToShowPositionHistory: null,
|
||||||
|
selectedNodePositionHistoryMarkers: [],
|
||||||
|
selectedNodePositionHistoryPolyLines: [],
|
||||||
|
|
||||||
selectedTraceRoute: null,
|
selectedTraceRoute: null,
|
||||||
|
|
||||||
selectedNodeToShowNeighbours: null,
|
selectedNodeToShowNeighbours: null,
|
||||||
@ -1783,6 +1847,7 @@
|
|||||||
this.loadNodePowerMetrics(node.node_id);
|
this.loadNodePowerMetrics(node.node_id);
|
||||||
this.loadNodeMqttMetrics(node.node_id);
|
this.loadNodeMqttMetrics(node.node_id);
|
||||||
this.loadNodeTraceroutes(node.node_id);
|
this.loadNodeTraceroutes(node.node_id);
|
||||||
|
this.loadNodePositionHistory(node.node_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// handle node callback from outside of vue
|
// handle node callback from outside of vue
|
||||||
@ -1877,6 +1942,22 @@
|
|||||||
// do nothing
|
// do nothing
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
loadNodePositionHistory: function(nodeId) {
|
||||||
|
this.selectedNodePositionHistory = [];
|
||||||
|
window.axios.get(`/api/v1/nodes/${nodeId}/position-history?time_from=${new Date(this.positionHistoryFromTimeDate).getTime()}&time_to=${new Date(this.positionHistoryToTimeDate).getTime()}`).then((response) => {
|
||||||
|
this.selectedNodePositionHistory = response.data.position_history;
|
||||||
|
|
||||||
|
console.log(this.selectedNodePositionHistory);
|
||||||
|
|
||||||
|
if (this.selectedNodeToShowPositionHistory != null) {
|
||||||
|
clearAllPositionHistory();
|
||||||
|
onPositionHistoryUpdated(response.data.position_history);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(() => {
|
||||||
|
// do nothing
|
||||||
|
});
|
||||||
|
},
|
||||||
renderDeviceMetricCharts: function() {
|
renderDeviceMetricCharts: function() {
|
||||||
this.updateBatteryLevelChart();
|
this.updateBatteryLevelChart();
|
||||||
this.updateVoltageChart();
|
this.updateVoltageChart();
|
||||||
@ -2489,6 +2570,11 @@
|
|||||||
getRegionFrequencyRange: function(regionName) {
|
getRegionFrequencyRange: function(regionName) {
|
||||||
return window.getRegionFrequencyRange(regionName);
|
return window.getRegionFrequencyRange(regionName);
|
||||||
},
|
},
|
||||||
|
handleNodePositionHistory: function(nodeId) {
|
||||||
|
this.selectedNodeToShowPositionHistory = this.selectedNode
|
||||||
|
this.selectedNode = null
|
||||||
|
onPositionHistoryUpdated(this.selectedNodePositionHistory)
|
||||||
|
},
|
||||||
getShareLinkForNode: function(nodeId) {
|
getShareLinkForNode: function(nodeId) {
|
||||||
return window.location.origin + `/?node_id=${nodeId}`;
|
return window.location.origin + `/?node_id=${nodeId}`;
|
||||||
},
|
},
|
||||||
@ -2512,6 +2598,13 @@
|
|||||||
window._onHideNodeNeighboursClick();
|
window._onHideNodeNeighboursClick();
|
||||||
this.selectedNodeToShowNeighbours = null;
|
this.selectedNodeToShowNeighbours = null;
|
||||||
},
|
},
|
||||||
|
dismissShowingNodePositionHistory: function() {
|
||||||
|
this.selectedNodePositionHistory = [],
|
||||||
|
this.selectedNodeToShowPositionHistory = null
|
||||||
|
this.selectedNodePositionHistoryMarkers = [],
|
||||||
|
this.selectedNodePositionHistoryPolyLines = [],
|
||||||
|
cleanUpPositionHistory()
|
||||||
|
},
|
||||||
formatUptimeSeconds: function(secondsToFormat) {
|
formatUptimeSeconds: function(secondsToFormat) {
|
||||||
secondsToFormat = Number(secondsToFormat);
|
secondsToFormat = Number(secondsToFormat);
|
||||||
var days = Math.floor(secondsToFormat / (3600 * 24));
|
var days = Math.floor(secondsToFormat / (3600 * 24));
|
||||||
@ -2615,6 +2708,7 @@
|
|||||||
var nodeMarkers = {};
|
var nodeMarkers = {};
|
||||||
var selectedNodeOutlineCircle = null;
|
var selectedNodeOutlineCircle = null;
|
||||||
var waypoints = [];
|
var waypoints = [];
|
||||||
|
var positionHistories = [];
|
||||||
|
|
||||||
// set map bounds to be a little more than full size to prevent panning off screen
|
// set map bounds to be a little more than full size to prevent panning off screen
|
||||||
var bounds = [
|
var bounds = [
|
||||||
@ -2680,6 +2774,7 @@
|
|||||||
disableClusteringAtZoom: 10, // zoom level where node clustering is disabled
|
disableClusteringAtZoom: 10, // zoom level where node clustering is disabled
|
||||||
});
|
});
|
||||||
var waypointsLayerGroup = new L.LayerGroup();
|
var waypointsLayerGroup = new L.LayerGroup();
|
||||||
|
var nodePositionHistoryLayerGroup = new L.LayerGroup();
|
||||||
|
|
||||||
// create icons
|
// create icons
|
||||||
var iconMqttConnected = L.divIcon({
|
var iconMqttConnected = L.divIcon({
|
||||||
@ -2739,6 +2834,7 @@
|
|||||||
"Legend": legendLayerGroup,
|
"Legend": legendLayerGroup,
|
||||||
"Neighbours": neighboursLayerGroup,
|
"Neighbours": neighboursLayerGroup,
|
||||||
"Waypoints": waypointsLayerGroup,
|
"Waypoints": waypointsLayerGroup,
|
||||||
|
"Position History": nodePositionHistoryLayerGroup,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
// make the "Nodes" group exclusive (use radio inputs instead of checkbox)
|
// make the "Nodes" group exclusive (use radio inputs instead of checkbox)
|
||||||
@ -2759,6 +2855,9 @@
|
|||||||
if(enabledOverlayLayers.includes("Waypoints")){
|
if(enabledOverlayLayers.includes("Waypoints")){
|
||||||
waypointsLayerGroup.addTo(map);
|
waypointsLayerGroup.addTo(map);
|
||||||
}
|
}
|
||||||
|
if(enabledOverlayLayers.includes("Position History")){
|
||||||
|
nodePositionHistoryLayerGroup.addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
// update config when map overlay is added
|
// update config when map overlay is added
|
||||||
map.on('overlayadd', function(event) {
|
map.on('overlayadd', function(event) {
|
||||||
@ -2917,6 +3016,10 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearAllPositionHistory() {
|
||||||
|
nodePositionHistoryLayerGroup.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
function clearNodeOutline() {
|
function clearNodeOutline() {
|
||||||
if(selectedNodeOutlineCircle){
|
if(selectedNodeOutlineCircle){
|
||||||
selectedNodeOutlineCircle.removeFrom(map);
|
selectedNodeOutlineCircle.removeFrom(map);
|
||||||
@ -3504,6 +3607,86 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPositionHistoryUpdated(updatedPositionHistories) {
|
||||||
|
let positionHistoryMarkers = [];
|
||||||
|
let positionHistoryLines = [];
|
||||||
|
let positionHistoryLinesCords = [];
|
||||||
|
|
||||||
|
// add nodes
|
||||||
|
for(var positionHistory of updatedPositionHistories) {
|
||||||
|
|
||||||
|
// skip nodes without position
|
||||||
|
if(!positionHistory.latitude || !positionHistory.longitude){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fix lat long
|
||||||
|
positionHistory.latitude = positionHistory.latitude / 10000000;
|
||||||
|
positionHistory.longitude = positionHistory.longitude / 10000000;
|
||||||
|
|
||||||
|
|
||||||
|
var hasLocation = isValidLatLng(positionHistory.latitude, positionHistory.longitude);
|
||||||
|
|
||||||
|
if(hasLocation){
|
||||||
|
|
||||||
|
// wrap longitude for shortest path, everything to left of australia should be shown on the right
|
||||||
|
var longitude = parseFloat(positionHistory.longitude);
|
||||||
|
if(longitude <= 100){
|
||||||
|
longitude += 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
positionHistoryLinesCords.push([positionHistory.latitude, longitude])
|
||||||
|
|
||||||
|
// determine emoji to show as marker icon
|
||||||
|
const emoji = 128205;
|
||||||
|
const emojiText = String.fromCodePoint(emoji)
|
||||||
|
|
||||||
|
let tooltip = ""
|
||||||
|
|
||||||
|
tooltip += `Position: ${positionHistory.latitude}, ${positionHistory.longitude}</br>`
|
||||||
|
tooltip += `Heard by: </br>`
|
||||||
|
tooltip += `Timestamp: ${new Date(positionHistory.time).toLocaleString()}</br>`
|
||||||
|
|
||||||
|
// create position history marker
|
||||||
|
const marker = L.marker([positionHistory.latitude, longitude],{
|
||||||
|
icon: L.divIcon({
|
||||||
|
className: 'waypoint-label',
|
||||||
|
iconSize: [26, 26], // increase from 12px to 26px
|
||||||
|
html: emojiText,
|
||||||
|
}),
|
||||||
|
}).bindPopup(tooltip).on('click', function(event) {
|
||||||
|
// close tooltip on click to prevent tooltip and popup showing at same time
|
||||||
|
event.target.closeTooltip();
|
||||||
|
});
|
||||||
|
|
||||||
|
// add marker to position history layer group
|
||||||
|
marker.addTo(nodePositionHistoryLayerGroup);
|
||||||
|
|
||||||
|
positionHistoryMarkers.push(marker)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const line = L.polyline(positionHistoryLinesCords).addTo(nodePositionHistoryLayerGroup);
|
||||||
|
|
||||||
|
positionHistoryLines.push(line)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanUpPositionHistory() {
|
||||||
|
|
||||||
|
// close tooltips and popups
|
||||||
|
closeAllPopups();
|
||||||
|
closeAllTooltips();
|
||||||
|
|
||||||
|
// setup node neighbours layer
|
||||||
|
nodePositionHistoryLayerGroup.clearLayers();
|
||||||
|
nodePositionHistoryLayerGroup.removeFrom(map);
|
||||||
|
nodePositionHistoryLayerGroup.addTo(map);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function setLoading(loading){
|
function setLoading(loading){
|
||||||
var reloadButton = document.getElementById("reload-button");
|
var reloadButton = document.getElementById("reload-button");
|
||||||
if(loading){
|
if(loading){
|
||||||
|
Reference in New Issue
Block a user