Merge branch 'main' of https://git.hackinpovo.it/HackInPovo/geolog
setup
This commit is contained in:
commit
c3e35c01f5
2 changed files with 0 additions and 202 deletions
|
@ -1,88 +0,0 @@
|
|||
# backend.py
|
||||
from fastapi import FastAPI, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from typing import Optional
|
||||
import pandas as pd
|
||||
import geoip2.database
|
||||
from datetime import datetime
|
||||
|
||||
# ----------------------------
|
||||
# 1. Load Access Logs
|
||||
# ----------------------------
|
||||
logs = []
|
||||
with open("access.log") as f:
|
||||
for line in f:
|
||||
# Example log format: "2025-08-28T12:34:56Z 192.0.2.1 GET /api/service1"
|
||||
parts = line.strip().split(" ", 3)
|
||||
if len(parts) != 4:
|
||||
continue # skip malformed lines
|
||||
timestamp, ip, method, path = parts
|
||||
logs.append({
|
||||
"timestamp": timestamp,
|
||||
"ip": ip,
|
||||
"method": method,
|
||||
"path": path
|
||||
})
|
||||
df = pd.DataFrame(logs)
|
||||
df["timestamp"] = pd.to_datetime(df["timestamp"])
|
||||
# ----------------------------
|
||||
# 2. GeoIP Lookup
|
||||
# ----------------------------
|
||||
reader = geoip2.database.Reader("GeoLite2-City.mmdb")
|
||||
|
||||
def ip_to_geo(ip):
|
||||
try:
|
||||
response = reader.city(ip)
|
||||
return response.location.latitude, response.location.longitude
|
||||
except Exception as e:
|
||||
print(f"GeoIP lookup failed for {ip}: {e}")
|
||||
return None, None
|
||||
df["lat"], df["lon"] = zip(*df["ip"].apply(ip_to_geo))
|
||||
print(df)
|
||||
df = df.dropna(subset=["lat", "lon"])
|
||||
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# 3. FastAPI Setup
|
||||
# ----------------------------
|
||||
app = FastAPI(title="Reverse Proxy Connections Map API")
|
||||
|
||||
# Allow frontend to query API from any origin
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"]
|
||||
)
|
||||
|
||||
# ----------------------------
|
||||
# 4. API Endpoint
|
||||
# ----------------------------
|
||||
@app.get("/connections")
|
||||
def get_connections(
|
||||
service: Optional[str] = Query(None, description="Filter by service path"),
|
||||
start: Optional[str] = Query(None, description="Start datetime in ISO format"),
|
||||
end: Optional[str] = Query(None, description="End datetime in ISO format")
|
||||
):
|
||||
data = df.copy()
|
||||
|
||||
if service:
|
||||
data = data[data["path"].str.contains(service)]
|
||||
|
||||
if start:
|
||||
data = data[data["timestamp"] >= pd.to_datetime(start)]
|
||||
if end:
|
||||
data = data[data["timestamp"] <= pd.to_datetime(end)]
|
||||
return data[["timestamp", "path", "lat", "lon"]].to_dict(orient="records")
|
||||
|
||||
# ----------------------------
|
||||
# 5. Healthcheck Endpoint
|
||||
# ----------------------------
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok", "total_connections": len(df)}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run("backend:app", host="0.0.0.0", port=8000, reload=True)
|
114
index.html.bacca
114
index.html.bacca
|
@ -1,114 +0,0 @@
|
|||
<!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>
|
Loading…
Add table
Add a link
Reference in a new issue