Log page changes
This commit is contained in:
@@ -1979,57 +1979,64 @@ def admin_logs():
|
||||
@app.route('/api/logs/recent')
|
||||
@admin_required
|
||||
def api_recent_logs():
|
||||
"""Enhanced API endpoint to get recent log entries with filtering"""
|
||||
"""API endpoint to get recent log entries with full details"""
|
||||
try:
|
||||
# Get parameters
|
||||
days = request.args.get('days', 7, type=int)
|
||||
days = request.args.get('days', 1, type=int)
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
category_filter = request.args.get('category', None)
|
||||
severity_filter = request.args.get('severity', None)
|
||||
search_term = request.args.get('search', None)
|
||||
|
||||
# Verify log table exists
|
||||
if not logger_handler.verify_log_table_exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Log table not found or inaccessible',
|
||||
'logs': [],
|
||||
'total': 0
|
||||
}), 500
|
||||
cutoff_date = datetime.now() - timedelta(days=days)
|
||||
|
||||
# Get logs using the enhanced method
|
||||
logs = logger_handler.get_recent_logs(
|
||||
days=days,
|
||||
limit=limit,
|
||||
category_filter=category_filter,
|
||||
severity_filter=severity_filter,
|
||||
search_term=search_term
|
||||
)
|
||||
# Enhanced SQL to get all available fields
|
||||
logs_sql = """
|
||||
SELECT
|
||||
event_id,
|
||||
event_type,
|
||||
event_category,
|
||||
event_description,
|
||||
event_data,
|
||||
severity_level,
|
||||
created_timestamp,
|
||||
username,
|
||||
user_id,
|
||||
ip_address
|
||||
FROM log_events
|
||||
WHERE created_timestamp >= :cutoff_date
|
||||
ORDER BY created_timestamp DESC
|
||||
LIMIT :limit
|
||||
"""
|
||||
|
||||
# Log the API access for audit purposes
|
||||
logger_handler.log_security_event(
|
||||
event_type="admin_logs_accessed",
|
||||
description=f"Admin {session.get('username', 'unknown')} accessed log data (days={days}, limit={limit})",
|
||||
severity="LOW",
|
||||
additional_data={
|
||||
'filters': {
|
||||
'category': category_filter,
|
||||
'severity': severity_filter,
|
||||
'search': search_term
|
||||
}
|
||||
}
|
||||
)
|
||||
result = db.session.execute(text(logs_sql), {
|
||||
'cutoff_date': cutoff_date,
|
||||
'limit': limit
|
||||
}).fetchall()
|
||||
|
||||
logs = []
|
||||
for row in result:
|
||||
# Parse event_data if it's JSON
|
||||
event_data = None
|
||||
if row.event_data:
|
||||
try:
|
||||
event_data = json.loads(row.event_data) if isinstance(row.event_data, str) else row.event_data
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
event_data = row.event_data
|
||||
|
||||
logs.append({
|
||||
'event_id': row.event_id,
|
||||
'event_type': row.event_type,
|
||||
'event_category': row.event_category,
|
||||
'description': row.event_description,
|
||||
'event_data': event_data,
|
||||
'severity': row.severity_level,
|
||||
'timestamp': row.created_timestamp.isoformat(),
|
||||
'username': row.username or 'System',
|
||||
'user_id': row.user_id,
|
||||
'ip_address': row.ip_address or '-'
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'logs': logs,
|
||||
'total': len(logs),
|
||||
'filters_applied': {
|
||||
'days': days,
|
||||
'category': category_filter,
|
||||
'severity': severity_filter,
|
||||
'search': search_term
|
||||
}
|
||||
'total': len(logs)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
@@ -2037,111 +2044,95 @@ def api_recent_logs():
|
||||
print(f"Error in api_recent_logs: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Failed to fetch recent logs: {str(e)}',
|
||||
'logs': [],
|
||||
'total': 0
|
||||
'error': f'Failed to fetch recent logs: {str(e)}'
|
||||
}), 500
|
||||
|
||||
@app.route('/api/logs/stats')
|
||||
@admin_required
|
||||
def api_log_stats():
|
||||
"""Enhanced API endpoint to get logging statistics"""
|
||||
"""API endpoint to get logging statistics"""
|
||||
try:
|
||||
days = request.args.get('days', 7, type=int)
|
||||
print(f"📊 Getting log statistics for last {days} days")
|
||||
|
||||
# Verify log table exists
|
||||
if not logger_handler.verify_log_table_exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Log table not found or inaccessible',
|
||||
'stats': {}
|
||||
}), 500
|
||||
|
||||
# Get statistics
|
||||
# Get statistics from logger handler
|
||||
stats = logger_handler.get_log_statistics(days=days)
|
||||
print(f"📈 Retrieved stats: {stats}")
|
||||
|
||||
# Add some additional metadata
|
||||
enhanced_stats = {
|
||||
# Ensure all expected keys exist
|
||||
expected_stats = {
|
||||
'total_events': stats.get('total_events', 0),
|
||||
'security_events': stats.get('security_events', 0),
|
||||
'database_errors': stats.get('database_errors', 0),
|
||||
'user_activities': stats.get('user_activities', 0),
|
||||
'system_events': stats.get('system_events', 0),
|
||||
'unique_users': stats.get('unique_users', 0),
|
||||
'severity_breakdown': {
|
||||
'high': stats.get('high_severity', 0),
|
||||
'medium': stats.get('medium_severity', 0),
|
||||
'low': stats.get('low_severity', 0),
|
||||
'info': stats.get('info_severity', 0)
|
||||
},
|
||||
'category_breakdown': {
|
||||
'security': stats.get('security_events', 0),
|
||||
'database': stats.get('database_errors', 0),
|
||||
'user_activity': stats.get('user_activities', 0),
|
||||
'system': stats.get('system_events', 0)
|
||||
}
|
||||
'system_events': stats.get('system_events', 0)
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'stats': enhanced_stats,
|
||||
'stats': expected_stats,
|
||||
'days': days,
|
||||
'generated_at': datetime.now().isoformat()
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger_handler.log_database_error('api_log_stats', e)
|
||||
print(f"Error in api_log_stats: {e}")
|
||||
print(f"❌ Error in api_log_stats: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Failed to fetch log statistics: {str(e)}',
|
||||
'stats': {}
|
||||
'stats': {
|
||||
'total_events': 0,
|
||||
'security_events': 0,
|
||||
'database_errors': 0,
|
||||
'user_activities': 0,
|
||||
'system_events': 0
|
||||
}
|
||||
}), 500
|
||||
|
||||
@app.route('/api/logs/cleanup', methods=['POST'])
|
||||
@admin_required
|
||||
def api_cleanup_logs():
|
||||
"""Enhanced API endpoint to cleanup old log entries"""
|
||||
"""API endpoint to cleanup old log entries"""
|
||||
try:
|
||||
# Get parameters from request
|
||||
# Get JSON data
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
print("❌ No JSON data provided")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'No JSON data provided'
|
||||
}), 400
|
||||
|
||||
days_to_keep = data.get('days_to_keep', 90)
|
||||
print(f"🧹 Cleanup request: keep last {days_to_keep} days")
|
||||
|
||||
# Validate input
|
||||
if not isinstance(days_to_keep, int) or days_to_keep < 7:
|
||||
print(f"❌ Invalid days_to_keep: {days_to_keep}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'days_to_keep must be an integer >= 7'
|
||||
}), 400
|
||||
|
||||
if days_to_keep > 365:
|
||||
print(f"❌ days_to_keep too large: {days_to_keep}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'days_to_keep cannot exceed 365 days'
|
||||
}), 400
|
||||
|
||||
# Verify log table exists
|
||||
if not logger_handler.verify_log_table_exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Log table not found or inaccessible'
|
||||
}), 500
|
||||
|
||||
# Perform cleanup
|
||||
# Perform cleanup using logger handler
|
||||
deleted_count = logger_handler.cleanup_old_logs(days_to_keep=days_to_keep)
|
||||
|
||||
# Enhanced logging for audit trail
|
||||
admin_username = session.get('username', 'unknown')
|
||||
print(f"✅ Cleanup completed by {admin_username}: {deleted_count} records deleted")
|
||||
|
||||
# Log the admin action
|
||||
logger_handler.log_security_event(
|
||||
event_type="admin_log_cleanup",
|
||||
description=f"Admin {admin_username} performed log cleanup: {deleted_count} entries removed (keeping last {days_to_keep} days)",
|
||||
severity="HIGH", # High because this is a data deletion operation
|
||||
severity="HIGH",
|
||||
additional_data={
|
||||
'admin_user': admin_username,
|
||||
'days_to_keep': days_to_keep,
|
||||
@@ -2161,7 +2152,7 @@ def api_cleanup_logs():
|
||||
|
||||
except Exception as e:
|
||||
logger_handler.log_database_error('api_cleanup_logs', e)
|
||||
print(f"Error in api_cleanup_logs: {e}")
|
||||
print(f"❌ Error in api_cleanup_logs: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Failed to cleanup old logs: {str(e)}'
|
||||
|
||||
+80
-51
@@ -23,7 +23,7 @@ import logging.handlers
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
from datetime import datetime, date
|
||||
from datetime import datetime, date, timedelta
|
||||
from functools import wraps
|
||||
from flask import request, session, g
|
||||
from sqlalchemy import text
|
||||
@@ -594,24 +594,48 @@ class AppLogger:
|
||||
)
|
||||
|
||||
# UTILITY METHODS
|
||||
|
||||
def get_log_statistics(self, days=7):
|
||||
"""Get logging statistics for the specified number of days"""
|
||||
try:
|
||||
from datetime import datetime, timedelta # Import here as backup
|
||||
cutoff_date = datetime.now() - timedelta(days=days)
|
||||
|
||||
# Get total events
|
||||
# Initialize default stats
|
||||
stats = {
|
||||
'total_events': 0,
|
||||
'security_events': 0,
|
||||
'database_errors': 0,
|
||||
'user_activities': 0,
|
||||
'system_events': 0
|
||||
}
|
||||
|
||||
# Check if table exists first
|
||||
try:
|
||||
table_check = self.db.session.execute(text("SHOW TABLES LIKE 'log_events'")).fetchone()
|
||||
if not table_check:
|
||||
print("⚠️ log_events table does not exist")
|
||||
return stats
|
||||
except Exception as table_error:
|
||||
print(f"⚠️ Cannot check if log_events table exists: {table_error}")
|
||||
return stats
|
||||
|
||||
# Get total events count
|
||||
try:
|
||||
total_sql = """
|
||||
SELECT COUNT(*) as total_events
|
||||
FROM log_events
|
||||
WHERE created_timestamp >= :cutoff_date
|
||||
"""
|
||||
|
||||
total_result = self.db.session.execute(text(total_sql), {
|
||||
'cutoff_date': cutoff_date
|
||||
}).fetchone()
|
||||
total_result = self.db.session.execute(text(total_sql), {'cutoff_date': cutoff_date}).fetchone()
|
||||
if total_result:
|
||||
stats['total_events'] = total_result.total_events
|
||||
print(f"✅ Found {stats['total_events']} total events in last {days} days")
|
||||
except Exception as total_error:
|
||||
print(f"⚠️ Error getting total events: {total_error}")
|
||||
|
||||
# Get events by category
|
||||
try:
|
||||
category_sql = """
|
||||
SELECT
|
||||
event_category,
|
||||
@@ -621,36 +645,31 @@ class AppLogger:
|
||||
GROUP BY event_category
|
||||
"""
|
||||
|
||||
category_result = self.db.session.execute(text(category_sql), {
|
||||
'cutoff_date': cutoff_date
|
||||
}).fetchall()
|
||||
category_result = self.db.session.execute(text(category_sql), {'cutoff_date': cutoff_date}).fetchall()
|
||||
|
||||
# Build simple statistics dictionary (not nested)
|
||||
stats = {
|
||||
'total_events': total_result.total_events if total_result else 0,
|
||||
'security_events': 0,
|
||||
'database_errors': 0,
|
||||
'user_activities': 0,
|
||||
'system_events': 0
|
||||
}
|
||||
|
||||
# Process category results
|
||||
for row in category_result:
|
||||
if row.event_category == 'security':
|
||||
stats['security_events'] = row.event_count
|
||||
elif row.event_category == 'database':
|
||||
stats['database_errors'] = row.event_count
|
||||
elif row.event_category == 'user_activity':
|
||||
stats['user_activities'] = row.event_count
|
||||
elif row.event_category == 'system':
|
||||
stats['system_events'] = row.event_count
|
||||
category = row.event_category
|
||||
count = row.event_count
|
||||
print(f"✅ Found {count} events in category: {category}")
|
||||
|
||||
if category == 'security':
|
||||
stats['security_events'] = count
|
||||
elif category == 'database':
|
||||
stats['database_errors'] = count
|
||||
elif category == 'user_activity':
|
||||
stats['user_activities'] = count
|
||||
elif category == 'system':
|
||||
stats['system_events'] = count
|
||||
|
||||
except Exception as category_error:
|
||||
print(f"⚠️ Error getting category stats: {category_error}")
|
||||
|
||||
print(f"📊 Final stats: {stats}")
|
||||
return stats
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error in get_log_statistics: {e}")
|
||||
self.log_database_error('get_log_statistics', e)
|
||||
print(f"Error in get_log_statistics: {e}")
|
||||
# Return default stats structure
|
||||
return {
|
||||
'total_events': 0,
|
||||
'security_events': 0,
|
||||
@@ -662,9 +681,22 @@ class AppLogger:
|
||||
def cleanup_old_logs(self, days_to_keep=90):
|
||||
"""Clean up old log entries from database"""
|
||||
try:
|
||||
from datetime import datetime, timedelta # Import here as backup
|
||||
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
|
||||
print(f"🧹 Starting log cleanup: removing entries older than {cutoff_date}")
|
||||
|
||||
# Check if table exists first
|
||||
try:
|
||||
table_check = self.db.session.execute(text("SHOW TABLES LIKE 'log_events'")).fetchone()
|
||||
if not table_check:
|
||||
print("⚠️ log_events table does not exist")
|
||||
return 0
|
||||
except Exception as table_error:
|
||||
print(f"⚠️ Cannot check if log_events table exists: {table_error}")
|
||||
return 0
|
||||
|
||||
# First, count how many records will be deleted
|
||||
try:
|
||||
count_sql = """
|
||||
SELECT COUNT(*) as count_to_delete
|
||||
FROM log_events
|
||||
@@ -672,50 +704,47 @@ class AppLogger:
|
||||
AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH')
|
||||
"""
|
||||
|
||||
count_result = self.db.session.execute(text(count_sql), {
|
||||
'cutoff_date': cutoff_date
|
||||
}).fetchone()
|
||||
|
||||
count_result = self.db.session.execute(text(count_sql), {'cutoff_date': cutoff_date}).fetchone()
|
||||
count_to_delete = count_result.count_to_delete if count_result else 0
|
||||
|
||||
print(f"📊 Found {count_to_delete} records to delete")
|
||||
|
||||
if count_to_delete == 0:
|
||||
print("✅ No old records found to cleanup")
|
||||
return 0
|
||||
|
||||
except Exception as count_error:
|
||||
print(f"⚠️ Error counting records to delete: {count_error}")
|
||||
return 0
|
||||
|
||||
# Perform the cleanup - exclude critical logs
|
||||
try:
|
||||
cleanup_sql = """
|
||||
DELETE FROM log_events
|
||||
WHERE created_timestamp < :cutoff_date
|
||||
AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH')
|
||||
"""
|
||||
|
||||
result = self.db.session.execute(text(cleanup_sql), {
|
||||
'cutoff_date': cutoff_date
|
||||
})
|
||||
|
||||
result = self.db.session.execute(text(cleanup_sql), {'cutoff_date': cutoff_date})
|
||||
deleted_count = result.rowcount
|
||||
self.db.session.commit()
|
||||
|
||||
# Log the cleanup operation
|
||||
self.logger.info(f"Log cleanup completed: {deleted_count} entries removed (keeping entries older than {days_to_keep} days)")
|
||||
print(f"🗑️ Successfully deleted {deleted_count} old log entries")
|
||||
|
||||
# Also log to security log for audit
|
||||
self.log_security_event(
|
||||
event_type="log_cleanup",
|
||||
description=f"Admin cleaned up {deleted_count} old log entries (keeping last {days_to_keep} days)",
|
||||
severity="MEDIUM",
|
||||
additional_data={
|
||||
'days_to_keep': days_to_keep,
|
||||
'deleted_count': deleted_count,
|
||||
'cutoff_date': cutoff_date.isoformat()
|
||||
}
|
||||
)
|
||||
# Log the cleanup operation
|
||||
self.logger.info(f"Log cleanup completed: {deleted_count} entries removed (keeping entries newer than {days_to_keep} days)")
|
||||
|
||||
return deleted_count
|
||||
|
||||
except Exception as delete_error:
|
||||
print(f"❌ Error during deletion: {delete_error}")
|
||||
self.db.session.rollback()
|
||||
return 0
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error in cleanup_old_logs: {e}")
|
||||
self.db.session.rollback()
|
||||
self.log_database_error('cleanup_old_logs', e)
|
||||
print(f"Error in cleanup_old_logs: {e}")
|
||||
return 0
|
||||
|
||||
def get_recent_logs(self, days=7, limit=100, category_filter=None, severity_filter=None, search_term=None):
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
+435
-102
@@ -1,13 +1,12 @@
|
||||
<!-- templates/admin/logs.html -->
|
||||
{% extends "base_authenticated.html" %}
|
||||
{% block title %}System Logs - QR Code Management{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{% extends "base_authenticated.html" %} {% block title %}System Logs - QR Code
|
||||
Management{% endblock %} {% block extra_head %}
|
||||
<!-- Admin Logs CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/admin-logs.css') }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', filename='css/admin_logs.css') }}"
|
||||
/>
|
||||
{% endblock %} {% block content %}
|
||||
<div class="logs-page">
|
||||
<!-- Page Header -->
|
||||
<div class="logs-header">
|
||||
@@ -22,6 +21,10 @@
|
||||
<p>Monitor system activities and security events</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-secondary" onclick="testLogging()">
|
||||
<i class="fas fa-flask"></i>
|
||||
Test Logging
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="refreshLogs()">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
Refresh
|
||||
@@ -34,14 +37,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Log Statistics -->
|
||||
{% if log_stats %}
|
||||
<div class="log-stats">
|
||||
<div class="stat-card total">
|
||||
<div class="stat-icon">
|
||||
<i class="fas fa-list"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ log_stats.total_events or 0 }}</h3>
|
||||
<h3 id="totalEventsCount">
|
||||
{{ log_stats.total_events if log_stats else 0 }}
|
||||
</h3>
|
||||
<p>Total Events (7 days)</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,7 +55,9 @@
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ log_stats.security_events or 0 }}</h3>
|
||||
<h3 id="securityEventsCount">
|
||||
{{ log_stats.security_events if log_stats else 0 }}
|
||||
</h3>
|
||||
<p>Security Events</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -61,7 +67,9 @@
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ log_stats.database_errors or 0 }}</h3>
|
||||
<h3 id="databaseErrorsCount">
|
||||
{{ log_stats.database_errors if log_stats else 0 }}
|
||||
</h3>
|
||||
<p>Database Errors</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,11 +79,36 @@
|
||||
<i class="fas fa-users"></i>
|
||||
</div>
|
||||
<div class="stat-info">
|
||||
<h3>{{ log_stats.user_activities or 0 }}</h3>
|
||||
<h3 id="userActivitiesCount">
|
||||
{{ log_stats.user_activities if log_stats else 0 }}
|
||||
</h3>
|
||||
<p>User Activities</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Debug Info (only visible in development) -->
|
||||
{% if config.DEBUG %}
|
||||
<div
|
||||
class="debug-info"
|
||||
style="
|
||||
background: #f3f4f6;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 2rem;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
"
|
||||
>
|
||||
<strong>Debug Info (Development Mode):</strong><br />
|
||||
Log Stats Available: {{ log_stats is not none }}<br />
|
||||
{% if log_stats %} Log Stats Keys: {{ log_stats.keys() | list }}<br />
|
||||
Total Events: {{ log_stats.total_events }}<br />
|
||||
Security Events: {{ log_stats.security_events }}<br />
|
||||
Database Errors: {{ log_stats.database_errors }}<br />
|
||||
User Activities: {{ log_stats.user_activities }}<br />
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Log Controls -->
|
||||
@@ -132,13 +165,13 @@
|
||||
<p>Loading log entries...</p>
|
||||
</div>
|
||||
|
||||
<div class="empty-state" id="emptyState" style="display: none;">
|
||||
<div class="empty-state" id="emptyState" style="display: none">
|
||||
<i class="fas fa-clipboard-list"></i>
|
||||
<h3>No Log Entries Found</h3>
|
||||
<p>No log entries match your current filter criteria.</p>
|
||||
</div>
|
||||
|
||||
<table class="logs-table" id="logsTable" style="display: none;">
|
||||
<table class="logs-table" id="logsTable" style="display: none">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
@@ -148,6 +181,7 @@
|
||||
<th>Severity</th>
|
||||
<th>User</th>
|
||||
<th>IP Address</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="logsTableBody">
|
||||
@@ -158,7 +192,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination-wrapper" id="paginationWrapper" style="display: none;">
|
||||
<div class="pagination-wrapper" id="paginationWrapper" style="display: none">
|
||||
<div class="pagination-info">
|
||||
<span id="paginationInfo">Showing 0 of 0 entries</span>
|
||||
</div>
|
||||
@@ -178,13 +212,93 @@
|
||||
|
||||
<!-- Cleanup Modal -->
|
||||
<div class="modal" id="cleanupModal">
|
||||
<div class="modal" id="logDetailsModal">
|
||||
<div class="modal-content log-details-modal">
|
||||
<div class="modal-header">
|
||||
<h3><i class="fas fa-info-circle"></i> Log Entry Details</h3>
|
||||
<button class="modal-close" onclick="closeLogDetailsModal()">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="log-details-grid">
|
||||
<div class="detail-section">
|
||||
<h4><i class="fas fa-tag"></i> Event Information</h4>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Event ID:</span>
|
||||
<span class="detail-value" id="detailEventId">-</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Event Type:</span>
|
||||
<span class="detail-value" id="detailEventType">-</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Category:</span>
|
||||
<span class="detail-value" id="detailCategory">-</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Severity:</span>
|
||||
<span class="detail-value" id="detailSeverity">-</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Timestamp:</span>
|
||||
<span class="detail-value" id="detailTimestamp">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section">
|
||||
<h4><i class="fas fa-user"></i> User Information</h4>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Username:</span>
|
||||
<span class="detail-value" id="detailUsername">-</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">User ID:</span>
|
||||
<span class="detail-value" id="detailUserId">-</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">IP Address:</span>
|
||||
<span class="detail-value" id="detailIpAddress">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-section full-width">
|
||||
<h4><i class="fas fa-align-left"></i> Description</h4>
|
||||
<div class="description-box" id="detailDescription">-</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="detail-section full-width"
|
||||
id="eventDataSection"
|
||||
style="display: none"
|
||||
>
|
||||
<h4><i class="fas fa-code"></i> Additional Data</h4>
|
||||
<div class="json-box" id="detailEventData">-</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="copyLogDetails()">
|
||||
<i class="fas fa-copy"></i>
|
||||
Copy Details
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="closeLogDetailsModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h3><i class="fas fa-trash-alt"></i> Cleanup Old Logs</h3>
|
||||
<button class="modal-close" onclick="closeCleanupModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>This action will permanently delete log entries older than the specified number of days.</p>
|
||||
<p>
|
||||
This action will permanently delete log entries older than the specified
|
||||
number of days.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="daysToKeep">Keep logs for the last:</label>
|
||||
<select id="daysToKeep" class="form-control">
|
||||
@@ -197,11 +311,14 @@
|
||||
</div>
|
||||
<div class="warning-note">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>Warning:</strong> This action cannot be undone. Deleted log entries will be permanently removed from the system.
|
||||
<strong>Warning:</strong> This action cannot be undone. Deleted log
|
||||
entries will be permanently removed from the system.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeCleanupModal()">Cancel</button>
|
||||
<button class="btn btn-secondary" onclick="closeCleanupModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="confirmCleanup()">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
Delete Old Logs
|
||||
@@ -216,14 +333,15 @@ let currentPage = 1;
|
||||
let logsPerPage = 50;
|
||||
let totalLogs = 0;
|
||||
let currentFilters = {
|
||||
search: '',
|
||||
category: '',
|
||||
severity: '',
|
||||
days: 7
|
||||
search: "",
|
||||
category: "",
|
||||
severity: "",
|
||||
days: 7,
|
||||
};
|
||||
|
||||
// Initialize page
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
console.log("Admin logs page loading...");
|
||||
loadLogs();
|
||||
setupEventListeners();
|
||||
});
|
||||
@@ -231,9 +349,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Setup event listeners
|
||||
function setupEventListeners() {
|
||||
// Search input
|
||||
const searchInput = document.getElementById('searchLogs');
|
||||
const searchInput = document.getElementById("searchLogs");
|
||||
if (searchInput) {
|
||||
let searchTimeout;
|
||||
searchInput.addEventListener('input', function() {
|
||||
searchInput.addEventListener("input", function () {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
currentFilters.search = this.value;
|
||||
@@ -241,40 +360,56 @@ function setupEventListeners() {
|
||||
loadLogs();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
// Filter selects
|
||||
document.getElementById('categoryFilter').addEventListener('change', function() {
|
||||
const categoryFilter = document.getElementById("categoryFilter");
|
||||
if (categoryFilter) {
|
||||
categoryFilter.addEventListener("change", function () {
|
||||
currentFilters.category = this.value;
|
||||
currentPage = 1;
|
||||
loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('severityFilter').addEventListener('change', function() {
|
||||
const severityFilter = document.getElementById("severityFilter");
|
||||
if (severityFilter) {
|
||||
severityFilter.addEventListener("change", function () {
|
||||
currentFilters.severity = this.value;
|
||||
currentPage = 1;
|
||||
loadLogs();
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('daysFilter').addEventListener('change', function() {
|
||||
const daysFilter = document.getElementById("daysFilter");
|
||||
if (daysFilter) {
|
||||
daysFilter.addEventListener("change", function () {
|
||||
currentFilters.days = parseInt(this.value);
|
||||
currentPage = 1;
|
||||
loadLogs();
|
||||
loadStats();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Load logs from API
|
||||
async function loadLogs() {
|
||||
console.log("Loading logs...");
|
||||
showLoading();
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({
|
||||
days: currentFilters.days,
|
||||
limit: logsPerPage,
|
||||
page: currentPage
|
||||
page: currentPage,
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/logs/recent?${params}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
@@ -282,32 +417,36 @@ async function loadLogs() {
|
||||
totalLogs = data.total || data.logs.length;
|
||||
updatePagination();
|
||||
} else {
|
||||
showError('Failed to load logs: ' + (data.error || 'Unknown error'));
|
||||
showError("Failed to load logs: " + (data.error || "Unknown error"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading logs:', error);
|
||||
showError('Failed to load logs. Please try again.');
|
||||
console.error("Error loading logs:", error);
|
||||
showError("Failed to load logs: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Load log statistics
|
||||
async function loadStats() {
|
||||
try {
|
||||
const response = await fetch(`/api/logs/stats?days=${currentFilters.days}`);
|
||||
const response = await fetch(
|
||||
`/api/logs/stats?days=${currentFilters.days}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success && data.stats) {
|
||||
updateStatsDisplay(data.stats);
|
||||
} else {
|
||||
console.error("Failed to load stats:", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading stats:', error);
|
||||
console.error("Error loading stats:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Display logs in table
|
||||
function displayLogs(logs) {
|
||||
const tbody = document.getElementById('logsTableBody');
|
||||
tbody.innerHTML = '';
|
||||
const tbody = document.getElementById("logsTableBody");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
if (logs.length === 0) {
|
||||
showEmpty();
|
||||
@@ -315,17 +454,22 @@ function displayLogs(logs) {
|
||||
}
|
||||
|
||||
// Filter logs based on current filters
|
||||
let filteredLogs = logs.filter(log => {
|
||||
let filteredLogs = logs.filter((log) => {
|
||||
if (currentFilters.search) {
|
||||
const searchLower = currentFilters.search.toLowerCase();
|
||||
if (!log.event_type.toLowerCase().includes(searchLower) &&
|
||||
if (
|
||||
!log.event_type.toLowerCase().includes(searchLower) &&
|
||||
!log.description.toLowerCase().includes(searchLower) &&
|
||||
!(log.username && log.username.toLowerCase().includes(searchLower))) {
|
||||
!(log.username && log.username.toLowerCase().includes(searchLower))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFilters.category && log.event_category !== currentFilters.category) {
|
||||
if (
|
||||
currentFilters.category &&
|
||||
log.event_category !== currentFilters.category
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -336,88 +480,244 @@ function displayLogs(logs) {
|
||||
return true;
|
||||
});
|
||||
|
||||
filteredLogs.forEach(log => {
|
||||
const row = document.createElement('tr');
|
||||
filteredLogs.forEach((log, index) => {
|
||||
const row = document.createElement("tr");
|
||||
row.className = getSeverityClass(log.severity);
|
||||
|
||||
const timestamp = new Date(log.timestamp).toLocaleString();
|
||||
|
||||
// Truncate description for table display
|
||||
const shortDescription =
|
||||
log.description && log.description.length > 80
|
||||
? log.description.substring(0, 80) + "..."
|
||||
: log.description || "No description";
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="timestamp">${timestamp}</td>
|
||||
<td class="event-type">${escapeHtml(log.event_type)}</td>
|
||||
<td class="event-type">${escapeHtml(log.event_type || "Unknown")}</td>
|
||||
<td class="category">
|
||||
<span class="category-badge ${log.event_category}">${log.event_category}</span>
|
||||
<span class="category-badge ${log.event_category || "system"}">${
|
||||
log.event_category || "system"
|
||||
}</span>
|
||||
</td>
|
||||
<td class="description">${escapeHtml(log.description)}</td>
|
||||
<td class="description" title="${escapeHtml(
|
||||
log.description || ""
|
||||
)}">${escapeHtml(shortDescription)}</td>
|
||||
<td class="severity">
|
||||
<span class="severity-badge ${log.severity.toLowerCase()}">${log.severity}</span>
|
||||
<span class="severity-badge ${(
|
||||
log.severity || "info"
|
||||
).toLowerCase()}">${log.severity || "INFO"}</span>
|
||||
</td>
|
||||
<td class="username">${
|
||||
log.username ? escapeHtml(log.username) : "<em>System</em>"
|
||||
}</td>
|
||||
<td class="ip-address">${log.ip_address || "-"}</td>
|
||||
<td class="actions">
|
||||
<button class="btn btn-sm btn-info" onclick="viewLogDetails(${index})" title="View Details">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
<td class="username">${log.username ? escapeHtml(log.username) : '<em>System</em>'}</td>
|
||||
<td class="ip-address">${log.ip_address || '-'}</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Store filtered logs globally for details modal
|
||||
window.currentLogs = filteredLogs;
|
||||
showTable();
|
||||
}
|
||||
|
||||
// View log details in modal
|
||||
function viewLogDetails(logIndex) {
|
||||
const log = window.currentLogs[logIndex];
|
||||
if (!log) return;
|
||||
|
||||
// Populate modal with log details
|
||||
document.getElementById("detailEventId").textContent = log.event_id || "-";
|
||||
document.getElementById("detailEventType").textContent =
|
||||
log.event_type || "-";
|
||||
document.getElementById("detailCategory").textContent =
|
||||
log.event_category || "-";
|
||||
document.getElementById(
|
||||
"detailSeverity"
|
||||
).innerHTML = `<span class="severity-badge ${(
|
||||
log.severity || "info"
|
||||
).toLowerCase()}">${log.severity || "INFO"}</span>`;
|
||||
document.getElementById("detailTimestamp").textContent = new Date(
|
||||
log.timestamp
|
||||
).toLocaleString();
|
||||
document.getElementById("detailUsername").textContent =
|
||||
log.username || "System";
|
||||
document.getElementById("detailUserId").textContent = log.user_id || "-";
|
||||
document.getElementById("detailIpAddress").textContent =
|
||||
log.ip_address || "-";
|
||||
document.getElementById("detailDescription").textContent =
|
||||
log.description || "-";
|
||||
|
||||
// Show additional data if available
|
||||
const eventDataSection = document.getElementById("eventDataSection");
|
||||
const eventDataElement = document.getElementById("detailEventData");
|
||||
|
||||
if (log.event_data) {
|
||||
try {
|
||||
const formattedData =
|
||||
typeof log.event_data === "string"
|
||||
? JSON.stringify(JSON.parse(log.event_data), null, 2)
|
||||
: JSON.stringify(log.event_data, null, 2);
|
||||
eventDataElement.textContent = formattedData;
|
||||
eventDataSection.style.display = "block";
|
||||
} catch (e) {
|
||||
eventDataElement.textContent = log.event_data;
|
||||
eventDataSection.style.display = "block";
|
||||
}
|
||||
} else {
|
||||
eventDataSection.style.display = "none";
|
||||
}
|
||||
|
||||
// Store current log for copying
|
||||
window.currentLogDetails = log;
|
||||
|
||||
// Show modal
|
||||
document.getElementById("logDetailsModal").style.display = "flex";
|
||||
}
|
||||
|
||||
// Close log details modal
|
||||
function closeLogDetailsModal() {
|
||||
document.getElementById("logDetailsModal").style.display = "none";
|
||||
}
|
||||
|
||||
// Copy log details to clipboard
|
||||
function copyLogDetails() {
|
||||
const log = window.currentLogDetails;
|
||||
if (!log) return;
|
||||
|
||||
const details = `Log Entry Details
|
||||
==================
|
||||
Event ID: ${log.event_id || "-"}
|
||||
Event Type: ${log.event_type || "-"}
|
||||
Category: ${log.event_category || "-"}
|
||||
Severity: ${log.severity || "-"}
|
||||
Timestamp: ${new Date(log.timestamp).toLocaleString()}
|
||||
Username: ${log.username || "System"}
|
||||
User ID: ${log.user_id || "-"}
|
||||
IP Address: ${log.ip_address || "-"}
|
||||
|
||||
Description:
|
||||
${log.description || "-"}
|
||||
|
||||
${
|
||||
log.event_data
|
||||
? "Additional Data:\n" +
|
||||
(typeof log.event_data === "string"
|
||||
? log.event_data
|
||||
: JSON.stringify(log.event_data, null, 2))
|
||||
: ""
|
||||
}`;
|
||||
|
||||
navigator.clipboard
|
||||
.writeText(details)
|
||||
.then(() => {
|
||||
showSuccess("Log details copied to clipboard!");
|
||||
})
|
||||
.catch(() => {
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = details;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
showSuccess("Log details copied to clipboard!");
|
||||
});
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function getSeverityClass(severity) {
|
||||
switch (severity) {
|
||||
case 'HIGH': return 'severity-high';
|
||||
case 'MEDIUM': return 'severity-medium';
|
||||
case 'LOW': return 'severity-low';
|
||||
default: return 'severity-info';
|
||||
switch ((severity || "info").toLowerCase()) {
|
||||
case "high":
|
||||
return "severity-high";
|
||||
case "medium":
|
||||
return "severity-medium";
|
||||
case "low":
|
||||
return "severity-low";
|
||||
default:
|
||||
return "severity-info";
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
if (!text) return "";
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
document.getElementById('loadingState').style.display = 'block';
|
||||
document.getElementById('emptyState').style.display = 'none';
|
||||
document.getElementById('logsTable').style.display = 'none';
|
||||
document.getElementById('paginationWrapper').style.display = 'none';
|
||||
document.getElementById("loadingState").style.display = "block";
|
||||
document.getElementById("emptyState").style.display = "none";
|
||||
document.getElementById("logsTable").style.display = "none";
|
||||
document.getElementById("paginationWrapper").style.display = "none";
|
||||
}
|
||||
|
||||
function showEmpty() {
|
||||
document.getElementById('loadingState').style.display = 'none';
|
||||
document.getElementById('emptyState').style.display = 'block';
|
||||
document.getElementById('logsTable').style.display = 'none';
|
||||
document.getElementById('paginationWrapper').style.display = 'none';
|
||||
document.getElementById("loadingState").style.display = "none";
|
||||
document.getElementById("emptyState").style.display = "block";
|
||||
document.getElementById("logsTable").style.display = "none";
|
||||
document.getElementById("paginationWrapper").style.display = "none";
|
||||
}
|
||||
|
||||
function showTable() {
|
||||
document.getElementById('loadingState').style.display = 'none';
|
||||
document.getElementById('emptyState').style.display = 'none';
|
||||
document.getElementById('logsTable').style.display = 'table';
|
||||
document.getElementById('paginationWrapper').style.display = 'flex';
|
||||
document.getElementById("loadingState").style.display = "none";
|
||||
document.getElementById("emptyState").style.display = "none";
|
||||
document.getElementById("logsTable").style.display = "table";
|
||||
document.getElementById("paginationWrapper").style.display = "flex";
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
// Simple error display - you could enhance this with a proper toast/notification system
|
||||
alert(message);
|
||||
showMessage("❌ " + message, "error");
|
||||
showEmpty();
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
showMessage("✅ " + message, "success");
|
||||
}
|
||||
|
||||
function showMessage(message, type) {
|
||||
const messageDiv = document.createElement("div");
|
||||
messageDiv.className = `flash-message flash-${type}`;
|
||||
messageDiv.innerHTML = `
|
||||
<span>${message}</span>
|
||||
<button onclick="this.parentElement.remove()" style="background: none; border: none; color: inherit; font-size: 1.2em; cursor: pointer; margin-left: 10px;">×</button>
|
||||
`;
|
||||
|
||||
const container = document.querySelector(".logs-page");
|
||||
if (container) {
|
||||
container.insertBefore(messageDiv, container.firstChild);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (messageDiv.parentElement) {
|
||||
messageDiv.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Update pagination
|
||||
function updatePagination() {
|
||||
const totalPages = Math.ceil(totalLogs / logsPerPage);
|
||||
const info = document.getElementById('paginationInfo');
|
||||
const info = document.getElementById("paginationInfo");
|
||||
|
||||
if (info) {
|
||||
const start = (currentPage - 1) * logsPerPage + 1;
|
||||
const end = Math.min(currentPage * logsPerPage, totalLogs);
|
||||
|
||||
info.textContent = `Showing ${start}-${end} of ${totalLogs} entries`;
|
||||
}
|
||||
|
||||
document.getElementById('prevPage').disabled = currentPage <= 1;
|
||||
document.getElementById('nextPage').disabled = currentPage >= totalPages;
|
||||
const prevBtn = document.getElementById("prevPage");
|
||||
const nextBtn = document.getElementById("nextPage");
|
||||
|
||||
if (prevBtn) prevBtn.disabled = currentPage <= 1;
|
||||
if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
|
||||
}
|
||||
|
||||
// Pagination controls
|
||||
@@ -439,64 +739,92 @@ function refreshLogs() {
|
||||
loadStats();
|
||||
}
|
||||
|
||||
// Export logs
|
||||
function exportLogs() {
|
||||
const params = new URLSearchParams({
|
||||
days: currentFilters.days,
|
||||
format: 'csv'
|
||||
});
|
||||
// Test logging functionality
|
||||
async function testLogging() {
|
||||
try {
|
||||
showMessage("Testing logging system...", "info");
|
||||
|
||||
window.open(`/api/logs/export?${params}`, '_blank');
|
||||
const response = await fetch("/api/logs/test");
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showMessage("✅ Logging system test passed!", "success");
|
||||
setTimeout(() => {
|
||||
loadLogs();
|
||||
loadStats();
|
||||
}, 1000);
|
||||
} else {
|
||||
showError(
|
||||
"❌ Logging system test failed: " + (data.error || "Unknown error")
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error testing logging:", error);
|
||||
showError("❌ Failed to run logging test: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup modal functions
|
||||
// Cleanup logs
|
||||
function cleanupLogs() {
|
||||
document.getElementById('cleanupModal').style.display = 'flex';
|
||||
document.getElementById("cleanupModal").style.display = "flex";
|
||||
}
|
||||
|
||||
function closeCleanupModal() {
|
||||
document.getElementById('cleanupModal').style.display = 'none';
|
||||
document.getElementById("cleanupModal").style.display = "none";
|
||||
}
|
||||
|
||||
async function confirmCleanup() {
|
||||
const daysToKeep = parseInt(document.getElementById('daysToKeep').value);
|
||||
const daysToKeep = parseInt(document.getElementById("daysToKeep").value);
|
||||
|
||||
if (daysToKeep < 7) {
|
||||
showError("Cannot keep logs for less than 7 days");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/logs/cleanup', {
|
||||
method: 'POST',
|
||||
showMessage("Cleaning up old logs...", "info");
|
||||
|
||||
const response = await fetch("/api/logs/cleanup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ days_to_keep: daysToKeep })
|
||||
body: JSON.stringify({ days_to_keep: daysToKeep }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert(`Successfully cleaned up ${data.deleted_count} old log entries.`);
|
||||
showSuccess(
|
||||
`Successfully cleaned up ${data.deleted_count} old log entries`
|
||||
);
|
||||
closeCleanupModal();
|
||||
setTimeout(() => {
|
||||
loadLogs();
|
||||
loadStats();
|
||||
}, 1000);
|
||||
} else {
|
||||
alert('Error cleaning up logs: ' + (data.error || 'Unknown error'));
|
||||
showError("Error cleaning up logs: " + (data.error || "Unknown error"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error cleaning up logs:', error);
|
||||
alert('Failed to cleanup logs. Please try again.');
|
||||
console.error("Error cleaning up logs:", error);
|
||||
showError("Failed to cleanup logs. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatsDisplay(stats) {
|
||||
// Update stat cards if they exist
|
||||
const totalElement = document.querySelector('.stat-card.total h3');
|
||||
const securityElement = document.querySelector('.stat-card.security h3');
|
||||
const errorsElement = document.querySelector('.stat-card.errors h3');
|
||||
const usersElement = document.querySelector('.stat-card.users h3');
|
||||
const totalElement = document.getElementById("totalEventsCount");
|
||||
const securityElement = document.getElementById("securityEventsCount");
|
||||
const errorsElement = document.getElementById("databaseErrorsCount");
|
||||
const usersElement = document.getElementById("userActivitiesCount");
|
||||
|
||||
if (totalElement) totalElement.textContent = stats.total_events || 0;
|
||||
if (securityElement) securityElement.textContent = stats.security_events || 0;
|
||||
if (securityElement)
|
||||
securityElement.textContent = stats.security_events || 0;
|
||||
if (errorsElement) errorsElement.textContent = stats.database_errors || 0;
|
||||
if (usersElement) usersElement.textContent = stats.user_activities || 0;
|
||||
|
||||
console.log("Stats updated:", stats);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -504,7 +832,7 @@ function updateStatsDisplay(stats) {
|
||||
/* Admin Logs Styles */
|
||||
.logs-page {
|
||||
padding: var(--spacing-6);
|
||||
max-width: 1400px;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -585,7 +913,11 @@ function updateStatsDisplay(stats) {
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-xl);
|
||||
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 {
|
||||
@@ -639,7 +971,8 @@ function updateStatsDisplay(stats) {
|
||||
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-3) var(--spacing-3) var(--spacing-3) var(--spacing-10);
|
||||
padding: var(--spacing-3) var(--spacing-3) var(--spacing-3)
|
||||
var(--spacing-10);
|
||||
border: 1px solid var(--gray-300);
|
||||
border-radius: var(--radius);
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
+2
-4
@@ -24,7 +24,7 @@
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="{{ url_for('static', filename='favicon.ico') }}"
|
||||
href="{{ url_for('static', filename='images/favicon.ico') }}"
|
||||
/>
|
||||
</head>
|
||||
<body class="login-layout">
|
||||
@@ -52,9 +52,7 @@
|
||||
{% endif %} {% endwith %}
|
||||
|
||||
<!-- Page Content -->
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<div class="container">{% block content %}{% endblock %}</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
|
||||
Reference in New Issue
Block a user