Update logs page: pagination

This commit is contained in:
2025-08-16 15:07:55 -04:00
parent 4b5d21bc73
commit 7114ed24c9
3 changed files with 1412 additions and 1145 deletions
+60 -12
View File
@@ -5,8 +5,7 @@ from functools import wraps
from datetime import datetime, date, time, timedelta from datetime import datetime, date, time, timedelta
from sqlalchemy import text from sqlalchemy import text
from user_agents import parse from user_agents import parse
from math import radians, cos, sin, asin, sqrt import io, os, base64, re, uuid, requests, json, qrcode, math
import io, os, base64, re, uuid, requests, json, qrcode
from dotenv import load_dotenv from dotenv import load_dotenv
# Import the logging handler # Import the logging handler
from logger_handler import AppLogger, log_user_activity, log_database_operations from logger_handler import AppLogger, log_user_activity, log_database_operations
@@ -1988,15 +1987,25 @@ def admin_logs():
@app.route('/api/logs/recent') @app.route('/api/logs/recent')
@admin_required @admin_required
def api_recent_logs(): def api_recent_logs():
"""API endpoint to get recent log entries with full details""" """API endpoint to get recent log entries with full details and pagination support"""
try: try:
days = request.args.get('days', 1, type=int) days = request.args.get('days', 1, type=int)
limit = request.args.get('limit', 50, type=int) limit = request.args.get('limit', 50, type=int)
page = request.args.get('page', 1, type=int)
category = request.args.get('category', '')
severity = request.args.get('severity', '')
search = request.args.get('search', '')
print(f"📊 API request - Days: {days}, Limit: {limit}, Page: {page}")
print(f"📊 Filters - Category: {category}, Severity: {severity}, Search: {search}")
cutoff_date = datetime.now() - timedelta(days=days) cutoff_date = datetime.now() - timedelta(days=days)
# Enhanced SQL to get all available fields # Calculate offset for pagination
logs_sql = """ offset = (page - 1) * limit
# Build the base SQL query with filters
base_sql = """
SELECT SELECT
event_id, event_id,
event_type, event_type,
@@ -2010,14 +2019,46 @@ def api_recent_logs():
ip_address ip_address
FROM log_events FROM log_events
WHERE created_timestamp >= :cutoff_date WHERE created_timestamp >= :cutoff_date
ORDER BY created_timestamp DESC
LIMIT :limit
""" """
result = db.session.execute(text(logs_sql), { count_sql = """
'cutoff_date': cutoff_date, SELECT COUNT(*) as total_count
'limit': limit FROM log_events
}).fetchall() WHERE created_timestamp >= :cutoff_date
"""
params = {'cutoff_date': cutoff_date}
# Add category filter
if category:
base_sql += " AND event_category = :category"
count_sql += " AND event_category = :category"
params['category'] = category
# Add severity filter
if severity:
base_sql += " AND severity_level = :severity"
count_sql += " AND severity_level = :severity"
params['severity'] = severity
# Add search filter
if search:
search_condition = " AND (event_type LIKE :search OR event_description LIKE :search OR username LIKE :search)"
base_sql += search_condition
count_sql += search_condition
params['search'] = f'%{search}%'
# Get total count first
count_result = db.session.execute(text(count_sql), params).fetchone()
total_count = count_result.total_count if count_result else 0
# Add ordering, limit and offset to main query
base_sql += " ORDER BY created_timestamp DESC LIMIT :limit OFFSET :offset"
params['limit'] = limit
params['offset'] = offset
# Execute main query
result = db.session.execute(text(base_sql), params).fetchall()
logs = [] logs = []
for row in result: for row in result:
@@ -2042,10 +2083,17 @@ def api_recent_logs():
'ip_address': row.ip_address or '-' 'ip_address': row.ip_address or '-'
}) })
print(f"📊 Returning {len(logs)} logs out of {total_count} total")
return jsonify({ return jsonify({
'success': True, 'success': True,
'logs': logs, 'logs': logs,
'total': len(logs) 'total': total_count,
'page': page,
'limit': limit,
'total_pages': math.ceil(total_count / limit) if total_count > 0 else 0,
'has_next': offset + limit < total_count,
'has_prev': page > 1
}) })
except Exception as e: except Exception as e:
+64 -11
View File
@@ -82,7 +82,11 @@
justify-content: center; justify-content: center;
font-size: var(--font-size-xl); font-size: var(--font-size-xl);
color: var(--white); color: var(--white);
background: linear-gradient(135deg, var(--primary-color), var(--primary-hover)); background: linear-gradient(
135deg,
var(--primary-color),
var(--primary-hover)
);
} }
.stat-icon.security { .stat-icon.security {
@@ -229,7 +233,7 @@
} }
.timestamp { .timestamp {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
color: var(--gray-600); color: var(--gray-600);
white-space: nowrap; white-space: nowrap;
@@ -314,7 +318,7 @@
} }
.ip-address { .ip-address {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
font-size: var(--font-size-xs); font-size: var(--font-size-xs);
color: var(--gray-500); color: var(--gray-500);
min-width: 120px; min-width: 120px;
@@ -336,8 +340,12 @@
} }
@keyframes spin { @keyframes spin {
from { transform: rotate(0deg); } from {
to { transform: rotate(360deg); } transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
} }
.empty-state i { .empty-state i {
@@ -397,6 +405,39 @@
background: var(--gray-100); background: var(--gray-100);
} }
/* Page number buttons */
.page-number {
min-width: 35px !important;
margin: 0 2px;
padding: var(--spacing-1) var(--spacing-2) !important;
border: 1px solid var(--gray-300);
background: var(--white);
color: var(--gray-700);
}
.page-number:hover:not(.active) {
background: var(--gray-50);
border-color: var(--primary-color);
}
.page-number.active {
background: var(--primary-color) !important;
color: var(--white) !important;
border-color: var(--primary-color) !important;
}
.pagination-ellipsis {
padding: var(--spacing-1) var(--spacing-2);
color: var(--gray-500);
font-size: var(--font-size-sm);
}
.pagination-numbers {
display: flex;
align-items: center;
gap: var(--spacing-1);
}
/* Modal Styles */ /* Modal Styles */
.modal { .modal {
display: none; display: none;
@@ -693,10 +734,18 @@
animation: fadeInUp 0.6s ease-out; animation: fadeInUp 0.6s ease-out;
} }
.stat-card:nth-child(1) { animation-delay: 0.1s; } .stat-card:nth-child(1) {
.stat-card:nth-child(2) { animation-delay: 0.2s; } animation-delay: 0.1s;
.stat-card:nth-child(3) { animation-delay: 0.3s; } }
.stat-card:nth-child(4) { animation-delay: 0.4s; } .stat-card:nth-child(2) {
animation-delay: 0.2s;
}
.stat-card:nth-child(3) {
animation-delay: 0.3s;
}
.stat-card:nth-child(4) {
animation-delay: 0.4s;
}
@keyframes fadeInUp { @keyframes fadeInUp {
from { from {
@@ -714,8 +763,12 @@
} }
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; } from {
to { opacity: 1; } opacity: 0;
}
to {
opacity: 1;
}
} }
/* Focus and accessibility improvements */ /* Focus and accessibility improvements */
+1287 -1121
View File
File diff suppressed because it is too large Load Diff