add recent connection

This commit is contained in:
lelo 2025-03-22 11:52:19 +01:00
parent 48a4316d66
commit 0e3d5e1c1f
4 changed files with 169 additions and 14 deletions

76
app.py
View File

@ -7,7 +7,10 @@ import mimetypes
import sqlite3 import sqlite3
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
import diskcache import diskcache
import threading
import json import json
import time
from flask_socketio import SocketIO
import geoip2.database import geoip2.database
from functools import lru_cache from functools import lru_cache
from urllib.parse import urlparse, unquote from urllib.parse import urlparse, unquote
@ -26,6 +29,13 @@ if os.environ.get('FLASK_ENV') == 'production':
with open('folder_config.json') as file: with open('folder_config.json') as file:
app.config['folder_config'] = json.load(file) app.config['folder_config'] = json.load(file)
socketio = SocketIO(app)
# Global variables to track the number of connected clients and the background thread
clients_connected = 0
background_thread = None
thread_lock = threading.Lock()
def require_secret(f): def require_secret(f):
@wraps(f) @wraps(f)
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
@ -558,6 +568,68 @@ def crawl_and_cache(subpath):
# Return the list of cached files as a JSON response # Return the list of cached files as a JSON response
return json.dumps({"cached_files": cached_files}, indent=4), 200 return json.dumps({"cached_files": cached_files}, indent=4), 200
def query_recent_connections():
"""
Every 5 seconds, query the database for connections in the last 60 seconds,
sorted by timestamp (most recent first), and emit the data to clients.
This loop will exit when there are no connected clients.
"""
global clients_connected
while clients_connected > 0:
cutoff = datetime.now() - timedelta(seconds=60)
cutoff_iso = cutoff.isoformat()
# Query the SQLite database for recent connections
conn = sqlite3.connect('access_log.db')
cursor = conn.cursor()
cursor.execute('''
SELECT * FROM file_access_log
WHERE timestamp >= ?
ORDER BY timestamp DESC
''', (cutoff_iso,))
rows = cursor.fetchall()
conn.close()
# Convert rows to dictionaries for the client, including all columns.
connections = []
for row in rows:
# Row order: (id, timestamp, full_path, ip_address, user_agent, referrer)
connections.append({
'id': row[0],
'timestamp': row[1],
'full_path': row[2],
'ip_address': row[3],
'user_agent': row[4],
'referrer': row[5]
})
# Emit the result over Socket.IO (to the default namespace)
socketio.emit('recent_connections', connections)
time.sleep(5)
# When no clients are connected, exit the thread.
print("No clients connected; stopping query thread.")
@socketio.on('connect')
def handle_connect():
global clients_connected, background_thread
clients_connected += 1
print("Client connected. Total clients:", clients_connected)
with thread_lock:
# Start the background task if it's not already running.
if background_thread is None or not background_thread.is_alive():
background_thread = socketio.start_background_task(query_recent_connections)
print("Started background query task.")
@socketio.on('disconnect')
def handle_disconnect():
global clients_connected
clients_connected -= 1
print("Client disconnected. Total clients:", clients_connected)
@app.route('/network')
def network():
return render_template('network.html')
# Catch-all route to serve the single-page application template. # Catch-all route to serve the single-page application template.
@app.route('/', defaults={'path': ''}) @app.route('/', defaults={'path': ''})
@app.route('/<path:path>') @app.route('/<path:path>')
@ -565,5 +637,5 @@ def crawl_and_cache(subpath):
def index(path): def index(path):
return render_template("app.html") return render_template("app.html")
if __name__ == "__main__": if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0') socketio.run(app, debug=True, host='0.0.0.0')

View File

@ -1,4 +1,5 @@
flask flask
flask_socketio
pillow pillow
diskcache diskcache
geoip2 geoip2

View File

@ -1,22 +1,23 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Web Analytics Dashboard</title> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Using Bootstrap for responsive layout --> <title>Dashboard - Verbindungsanalyse</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style> <style>
body { margin: 20px; } body { margin: 20px; }
.card { margin-bottom: 20px; } .card { margin-bottom: 20px; }
canvas { max-width: 100%; } canvas { max-width: 100%; }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1 class="mb-4">Web Analytics Dashboard ({{ timeframe }})</h1> <h1 class="mb-4">Dashboard - Verbindungsanalyse({{ timeframe }})</h1>
<!-- Timeframe selection buttons --> <!-- Timeframe selection buttons -->
<div class="mb-3"> <div class="mb-3">
<a href="{{ url_for('index') }}" class="btn btn-primary">Home</a> <a href="{{ url_for('index') }}" class="btn btn-primary">Home</a>
<a href="{{ url_for('network') }}" class="btn btn-primary">Netzwerk</a>
<a href="{{ url_for('dashboard', timeframe='today') }}" class="btn btn-primary">Today</a> <a href="{{ url_for('dashboard', timeframe='today') }}" class="btn btn-primary">Today</a>
<a href="{{ url_for('dashboard', timeframe='7days') }}" class="btn btn-primary">Last 7 Days</a> <a href="{{ url_for('dashboard', timeframe='7days') }}" class="btn btn-primary">Last 7 Days</a>
<a href="{{ url_for('dashboard', timeframe='30days') }}" class="btn btn-primary">Last 30 Days</a> <a href="{{ url_for('dashboard', timeframe='30days') }}" class="btn btn-primary">Last 30 Days</a>

81
templates/network.html Normal file
View File

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recent Connections</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body { margin: 20px; }
canvas { max-width: 100%; }
</style>
</head>
<body>
<div class="container">
<h1 class="mb-4">kürzlich verbunden... (in der letzten Minute)</h1>
<div class="mb-3">
<a href="{{ url_for('index') }}" class="btn btn-primary">Home</a>
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Dashboard</a>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-info">
<tr>
<th>ID</th>
<th>Timestamp</th>
<th>Full Path</th>
<th>IP Address</th>
<th>User Agent</th>
<th>Referrer</th>
</tr>
</thead>
<tbody id="connectionsTableBody">
<!-- Rows will be dynamically inserted here -->
</tbody>
</table>
</div>
</div>
<!-- Socket.IO client library -->
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<!-- Using vanilla JavaScript for dynamic DOM updates -->
<script>
const socket = io();
socket.on('recent_connections', function(data) {
const tbody = document.getElementById('connectionsTableBody');
tbody.innerHTML = ''; // Clear previous content
data.forEach(record => {
const row = document.createElement('tr');
// Create cells for each column
const idCell = document.createElement('td');
idCell.textContent = record.id;
const timestampCell = document.createElement('td');
timestampCell.textContent = record.timestamp;
const fullPathCell = document.createElement('td');
fullPathCell.textContent = record.full_path;
const ipCell = document.createElement('td');
ipCell.textContent = record.ip_address;
const userAgentCell = document.createElement('td');
userAgentCell.textContent = record.user_agent;
const referrerCell = document.createElement('td');
referrerCell.textContent = record.referrer;
// Append cells to the row
row.appendChild(idCell);
row.appendChild(timestampCell);
row.appendChild(fullPathCell);
row.appendChild(ipCell);
row.appendChild(userAgentCell);
row.appendChild(referrerCell);
// Append the row to the table body
tbody.appendChild(row);
});
});
</script>
<!-- Bootstrap 5 JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>