114 lines
3.5 KiB
Text
114 lines
3.5 KiB
Text
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Connections Animation Map</title>
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css"/>
|
|
<style>
|
|
body { margin: 0; font-family: Arial, sans-serif; }
|
|
#controls {
|
|
position: absolute; top: 10px; left: 10px; z-index: 1000;
|
|
background: white; padding: 10px; border-radius: 8px;
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
|
}
|
|
#map { height: 100vh; width: 100vw; }
|
|
label { font-size: 14px; display: block; margin-top: 5px; }
|
|
.fade-marker {
|
|
transition: opacity 1s linear;
|
|
opacity: 1;
|
|
}
|
|
.fade-out {
|
|
opacity: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="controls">
|
|
<label>Service: <input type="text" id="service" placeholder="/api/service1"></label>
|
|
<label>Start: <input type="datetime-local" id="start"></label>
|
|
<label>End: <input type="datetime-local" id="end"></label>
|
|
<label>Duration (seconds): <input type="number" id="duration" value="10"></label>
|
|
<button onclick="startSimulation()">Start Simulation</button>
|
|
</div>
|
|
<div id="map"></div>
|
|
|
|
<script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
|
|
<script>
|
|
var map = L.map('map').setView([20, 0], 2);
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: "© OpenStreetMap contributors"
|
|
}).addTo(map);
|
|
|
|
var markersLayer = L.layerGroup().addTo(map);
|
|
|
|
async function fetchConnections(service, start, end) {
|
|
let url = `http://localhost:8000/connections?`;
|
|
if (service) url += `service=${encodeURIComponent(service)}&`;
|
|
if (start) url += `start=${new Date(start).toISOString()}&`;
|
|
if (end) url += `end=${new Date(end).toISOString()}&`;
|
|
|
|
let response = await fetch(url);
|
|
return await response.json();
|
|
}
|
|
|
|
function createFadingMarker(lat, lon, popupContent) {
|
|
var marker = L.circleMarker([lat, lon], {
|
|
radius: 6,
|
|
color: 'red',
|
|
fillOpacity: 0.8
|
|
}).bindPopup(popupContent);
|
|
|
|
marker.addTo(markersLayer);
|
|
|
|
// apply fade effect after a delay
|
|
setTimeout(() => {
|
|
let elem = marker._path; // SVG circle element
|
|
if (elem) {
|
|
elem.classList.add("fade-marker");
|
|
elem.classList.add("fade-out");
|
|
}
|
|
// remove after fade
|
|
setTimeout(() => markersLayer.removeLayer(marker), 1000);
|
|
}, 2000); // marker stays for 2s before fading
|
|
}
|
|
|
|
async function startSimulation() {
|
|
markersLayer.clearLayers();
|
|
|
|
let service = document.getElementById("service").value;
|
|
let start = document.getElementById("start").value;
|
|
let end = document.getElementById("end").value;
|
|
let duration = parseInt(document.getElementById("duration").value) || 10;
|
|
|
|
let data = await fetchConnections(service, start, end);
|
|
|
|
if (!data.length) {
|
|
alert("No data found in this range.");
|
|
return;
|
|
}
|
|
|
|
// Sort by timestamp
|
|
data.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
|
|
let totalTime = (new Date(end) - new Date(start)) / 1000; // total seconds
|
|
if (totalTime > 60 * 60 * 24 * 90) {
|
|
alert("Range too long (max 3 months).");
|
|
return;
|
|
}
|
|
|
|
let stepInterval = duration * 1000 / data.length; // ms between markers
|
|
|
|
data.forEach((conn, i) => {
|
|
setTimeout(() => {
|
|
createFadingMarker(
|
|
conn.lat, conn.lon,
|
|
`<b>IP:</b> ${conn.ip}<br>` +
|
|
`<b>Service:</b> ${conn.path}<br>` +
|
|
`<b>Time:</b> ${conn.timestamp}`
|
|
);
|
|
}, i * stepInterval);
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|