232 lines
8.0 KiB
Python
232 lines
8.0 KiB
Python
from flask import render_template, request, session
|
|
import sqlite3
|
|
from datetime import datetime, date, timedelta
|
|
import geoip2.database
|
|
from urllib.parse import urlparse, unquote
|
|
|
|
from auth import require_secret
|
|
|
|
file_access_temp = []
|
|
|
|
def lookup_location(ip, reader):
|
|
try:
|
|
response = reader.city(ip)
|
|
country = response.country.name if response.country.name else "Unknown"
|
|
city = response.city.name if response.city.name else "Unknown"
|
|
return country, city
|
|
except Exception:
|
|
return "Unknown", "Unknown"
|
|
|
|
def get_device_type(user_agent):
|
|
"classify device type based on user agent string"
|
|
if 'Android' in user_agent:
|
|
return 'Android'
|
|
elif 'iPhone' in user_agent or 'iPad' in user_agent:
|
|
return 'iOS'
|
|
elif 'Windows' in user_agent:
|
|
return 'Windows'
|
|
elif 'Macintosh' in user_agent or 'Mac OS' in user_agent:
|
|
return 'MacOS'
|
|
elif 'Linux' in user_agent:
|
|
return 'Linux'
|
|
else:
|
|
return 'Other'
|
|
|
|
def shorten_referrer(url):
|
|
segments = [seg for seg in url.split('/') if seg]
|
|
segment = segments[-1]
|
|
# Decode all percent-encoded characters (like %20, %2F, etc.)
|
|
segment_decoded = unquote(segment)
|
|
return segment_decoded
|
|
|
|
def log_file_access(full_path, ip_address, user_agent, referrer):
|
|
"""
|
|
Log file access details to a SQLite database.
|
|
Records the timestamp, full file path, client IP, user agent, and referrer.
|
|
"""
|
|
global file_access_temp
|
|
# Connect to the database (this will create the file if it doesn't exist)
|
|
conn = sqlite3.connect('access_log.db')
|
|
cursor = conn.cursor()
|
|
# Create the table if it doesn't exist
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS file_access_log (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
timestamp TEXT,
|
|
full_path TEXT,
|
|
ip_address TEXT,
|
|
user_agent TEXT,
|
|
referrer TEXT
|
|
)
|
|
''')
|
|
# Gather information from the request
|
|
timestamp = datetime.now().isoformat()
|
|
|
|
# Insert the access record into the database
|
|
cursor.execute('''
|
|
INSERT INTO file_access_log (timestamp, full_path, ip_address, user_agent, referrer)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
''', (timestamp, full_path, ip_address, user_agent, referrer))
|
|
conn.commit()
|
|
conn.close()
|
|
file_access_temp.insert(0, [timestamp, full_path, ip_address, user_agent, referrer])
|
|
return return_file_access()
|
|
|
|
def return_file_access():
|
|
global file_access_temp
|
|
if len(file_access_temp) > 0:
|
|
# Compute the cutoff time (10 minutes ago from now)
|
|
cutoff_time = datetime.now() - timedelta(minutes=10)
|
|
# Update the list in-place to keep only entries newer than 10 minutes
|
|
file_access_temp[:] = [
|
|
entry for entry in file_access_temp
|
|
if datetime.fromisoformat(entry[0]) >= cutoff_time
|
|
]
|
|
return file_access_temp
|
|
else:
|
|
return []
|
|
|
|
@require_secret
|
|
def connections():
|
|
return render_template('connections.html')
|
|
|
|
@require_secret
|
|
def dashboard():
|
|
timeframe = request.args.get('timeframe', 'today')
|
|
now = datetime.now()
|
|
|
|
if timeframe == 'today':
|
|
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
elif timeframe == '7days':
|
|
start = now - timedelta(days=7)
|
|
elif timeframe == '30days':
|
|
start = now - timedelta(days=30)
|
|
elif timeframe == '365days':
|
|
start = now - timedelta(days=365)
|
|
else:
|
|
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
conn = sqlite3.connect('access_log.db')
|
|
cursor = conn.cursor()
|
|
|
|
# Raw file access counts for the table (top files)
|
|
cursor.execute('''
|
|
SELECT full_path, COUNT(*) as access_count
|
|
FROM file_access_log
|
|
WHERE timestamp >= ?
|
|
GROUP BY full_path
|
|
ORDER BY access_count DESC
|
|
LIMIT 20
|
|
''', (start.isoformat(),))
|
|
rows = cursor.fetchall()
|
|
|
|
# Daily access trend for a line chart
|
|
cursor.execute('''
|
|
SELECT date(timestamp) as date, COUNT(*) as count
|
|
FROM file_access_log
|
|
WHERE timestamp >= ?
|
|
GROUP BY date
|
|
ORDER BY date
|
|
''', (start.isoformat(),))
|
|
daily_access_data = [dict(date=row[0], count=row[1]) for row in cursor.fetchall()]
|
|
|
|
# Top files for bar chart
|
|
cursor.execute('''
|
|
SELECT full_path, COUNT(*) as access_count
|
|
FROM file_access_log
|
|
WHERE timestamp >= ?
|
|
GROUP BY full_path
|
|
ORDER BY access_count DESC
|
|
LIMIT 10
|
|
''', (start.isoformat(),))
|
|
top_files_data = [dict(full_path=row[0], access_count=row[1]) for row in cursor.fetchall()]
|
|
|
|
# User agent distribution (aggregate by device type)
|
|
cursor.execute('''
|
|
SELECT user_agent, COUNT(*) as count
|
|
FROM file_access_log
|
|
WHERE timestamp >= ?
|
|
GROUP BY user_agent
|
|
ORDER BY count DESC
|
|
''', (start.isoformat(),))
|
|
raw_user_agents = [dict(user_agent=row[0], count=row[1]) for row in cursor.fetchall()]
|
|
device_counts = {}
|
|
for entry in raw_user_agents:
|
|
device = get_device_type(entry['user_agent'])
|
|
device_counts[device] = device_counts.get(device, 0) + entry['count']
|
|
# Rename to user_agent_data for compatibility with the frontend
|
|
user_agent_data = [dict(device=device, count=count) for device, count in device_counts.items()]
|
|
|
|
# Referrer distribution (shorten links)
|
|
cursor.execute('''
|
|
SELECT referrer, COUNT(*) as count
|
|
FROM file_access_log
|
|
WHERE timestamp >= ?
|
|
GROUP BY referrer
|
|
ORDER BY count DESC
|
|
LIMIT 10
|
|
''', (start.isoformat(),))
|
|
referrer_data = []
|
|
for row in cursor.fetchall():
|
|
raw_ref = row[0]
|
|
shortened = shorten_referrer(raw_ref) if raw_ref else "Direct/None"
|
|
referrer_data.append(dict(referrer=shortened, count=row[1]))
|
|
|
|
# Aggregate IP addresses with counts
|
|
cursor.execute('''
|
|
SELECT ip_address, COUNT(*) as count
|
|
FROM file_access_log
|
|
WHERE timestamp >= ?
|
|
GROUP BY ip_address
|
|
ORDER BY count DESC
|
|
LIMIT 1000
|
|
''', (start.isoformat(),))
|
|
ip_rows = cursor.fetchall()
|
|
|
|
# Initialize GeoIP2 reader once for efficiency
|
|
reader = geoip2.database.Reader('GeoLite2-City.mmdb')
|
|
location_data = {}
|
|
for ip, count in ip_rows:
|
|
country, city = lookup_location(ip, reader)
|
|
key = (country, city)
|
|
if key in location_data:
|
|
location_data[key] += count
|
|
else:
|
|
location_data[key] = count
|
|
reader.close()
|
|
|
|
# Convert the dictionary to a list of dictionaries
|
|
location_data = [
|
|
dict(country=key[0], city=key[1], count=value)
|
|
for key, value in location_data.items()
|
|
]
|
|
|
|
# Sort by count in descending order and take the top 20
|
|
location_data.sort(key=lambda x: x['count'], reverse=True)
|
|
location_data = location_data[:20]
|
|
|
|
# Summary stats using separate SQL queries
|
|
cursor.execute('SELECT COUNT(*) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),))
|
|
total_accesses = cursor.fetchone()[0]
|
|
|
|
# Use a separate query to count unique files (distinct full_path values)
|
|
cursor.execute('SELECT COUNT(DISTINCT full_path) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),))
|
|
unique_files = cursor.fetchone()[0]
|
|
|
|
# Use a separate query to count unique IP addresses
|
|
cursor.execute('SELECT COUNT(DISTINCT ip_address) FROM file_access_log WHERE timestamp >= ?', (start.isoformat(),))
|
|
unique_ips = cursor.fetchone()[0]
|
|
|
|
conn.close()
|
|
|
|
return render_template("dashboard.html",
|
|
timeframe=timeframe,
|
|
rows=rows,
|
|
daily_access_data=daily_access_data,
|
|
top_files_data=top_files_data,
|
|
user_agent_data=user_agent_data,
|
|
referrer_data=referrer_data,
|
|
location_data=location_data,
|
|
total_accesses=total_accesses,
|
|
unique_files=unique_files,
|
|
unique_ips=unique_ips) |