Minor changes
This commit is contained in:
@@ -1979,95 +1979,192 @@ 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"""
|
"""Enhanced API endpoint to get recent log entries with filtering"""
|
||||||
try:
|
try:
|
||||||
days = request.args.get('days', 1, type=int)
|
# Get parameters
|
||||||
|
days = request.args.get('days', 7, type=int)
|
||||||
limit = request.args.get('limit', 50, 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)
|
||||||
|
|
||||||
cutoff_date = datetime.now() - timedelta(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',
|
||||||
|
'logs': [],
|
||||||
|
'total': 0
|
||||||
|
}), 500
|
||||||
|
|
||||||
logs_sql = """
|
# Get logs using the enhanced method
|
||||||
SELECT event_type, event_category, event_description,
|
logs = logger_handler.get_recent_logs(
|
||||||
severity_level, created_timestamp, username, ip_address
|
days=days,
|
||||||
FROM log_events
|
limit=limit,
|
||||||
WHERE created_timestamp >= :cutoff_date
|
category_filter=category_filter,
|
||||||
ORDER BY created_timestamp DESC
|
severity_filter=severity_filter,
|
||||||
LIMIT :limit
|
search_term=search_term
|
||||||
"""
|
)
|
||||||
|
|
||||||
result = db.session.execute(text(logs_sql), {
|
# Log the API access for audit purposes
|
||||||
'cutoff_date': cutoff_date,
|
logger_handler.log_security_event(
|
||||||
'limit': limit
|
event_type="admin_logs_accessed",
|
||||||
}).fetchall()
|
description=f"Admin {session.get('username', 'unknown')} accessed log data (days={days}, limit={limit})",
|
||||||
|
severity="LOW",
|
||||||
logs = []
|
additional_data={
|
||||||
for row in result:
|
'filters': {
|
||||||
logs.append({
|
'category': category_filter,
|
||||||
'event_type': row.event_type,
|
'severity': severity_filter,
|
||||||
'event_category': row.event_category,
|
'search': search_term
|
||||||
'description': row.event_description,
|
}
|
||||||
'severity': row.severity_level,
|
}
|
||||||
'timestamp': row.created_timestamp.isoformat(),
|
)
|
||||||
'username': row.username,
|
|
||||||
'ip_address': row.ip_address
|
|
||||||
})
|
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'logs': logs,
|
'logs': logs,
|
||||||
'total': len(logs)
|
'total': len(logs),
|
||||||
|
'filters_applied': {
|
||||||
|
'days': days,
|
||||||
|
'category': category_filter,
|
||||||
|
'severity': severity_filter,
|
||||||
|
'search': search_term
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('api_recent_logs', e)
|
logger_handler.log_database_error('api_recent_logs', e)
|
||||||
|
print(f"Error in api_recent_logs: {e}")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Failed to fetch recent logs'
|
'error': f'Failed to fetch recent logs: {str(e)}',
|
||||||
|
'logs': [],
|
||||||
|
'total': 0
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
@app.route('/api/logs/stats')
|
@app.route('/api/logs/stats')
|
||||||
@admin_required
|
@admin_required
|
||||||
def api_log_stats():
|
def api_log_stats():
|
||||||
"""API endpoint to get logging statistics"""
|
"""Enhanced API endpoint to get logging statistics"""
|
||||||
try:
|
try:
|
||||||
days = request.args.get('days', 7, type=int)
|
days = request.args.get('days', 7, type=int)
|
||||||
|
|
||||||
|
# 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
|
||||||
stats = logger_handler.get_log_statistics(days=days)
|
stats = logger_handler.get_log_statistics(days=days)
|
||||||
|
|
||||||
|
# Add some additional metadata
|
||||||
|
enhanced_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'stats': stats,
|
'stats': enhanced_stats,
|
||||||
'days': days
|
'days': days,
|
||||||
|
'generated_at': datetime.now().isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('api_log_stats', e)
|
logger_handler.log_database_error('api_log_stats', e)
|
||||||
|
print(f"Error in api_log_stats: {e}")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Failed to fetch log statistics'
|
'error': f'Failed to fetch log statistics: {str(e)}',
|
||||||
|
'stats': {}
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
@app.route('/api/logs/cleanup', methods=['POST'])
|
@app.route('/api/logs/cleanup', methods=['POST'])
|
||||||
@admin_required
|
@admin_required
|
||||||
def api_cleanup_logs():
|
def api_cleanup_logs():
|
||||||
"""API endpoint to cleanup old log entries"""
|
"""Enhanced API endpoint to cleanup old log entries"""
|
||||||
try:
|
try:
|
||||||
days_to_keep = request.json.get('days_to_keep', 90)
|
# Get parameters from request
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'No JSON data provided'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
days_to_keep = data.get('days_to_keep', 90)
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if not isinstance(days_to_keep, int) or days_to_keep < 7:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': 'days_to_keep must be an integer >= 7'
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
if days_to_keep > 365:
|
||||||
|
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
|
||||||
deleted_count = logger_handler.cleanup_old_logs(days_to_keep=days_to_keep)
|
deleted_count = logger_handler.cleanup_old_logs(days_to_keep=days_to_keep)
|
||||||
|
|
||||||
# Log the cleanup operation
|
# Enhanced logging for audit trail
|
||||||
logger_handler.logger.info(f"Log cleanup completed: {deleted_count} entries removed")
|
admin_username = session.get('username', 'unknown')
|
||||||
|
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
|
||||||
|
additional_data={
|
||||||
|
'admin_user': admin_username,
|
||||||
|
'days_to_keep': days_to_keep,
|
||||||
|
'deleted_count': deleted_count,
|
||||||
|
'ip_address': request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': True,
|
'success': True,
|
||||||
'deleted_count': deleted_count,
|
'deleted_count': deleted_count,
|
||||||
'message': f'Successfully cleaned up {deleted_count} old log entries'
|
'days_to_keep': days_to_keep,
|
||||||
|
'message': f'Successfully cleaned up {deleted_count} old log entries (keeping last {days_to_keep} days)',
|
||||||
|
'performed_by': admin_username,
|
||||||
|
'performed_at': datetime.now().isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_handler.log_database_error('api_cleanup_logs', e)
|
logger_handler.log_database_error('api_cleanup_logs', e)
|
||||||
|
print(f"Error in api_cleanup_logs: {e}")
|
||||||
return jsonify({
|
return jsonify({
|
||||||
'success': False,
|
'success': False,
|
||||||
'error': 'Failed to cleanup old logs'
|
'error': f'Failed to cleanup old logs: {str(e)}'
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
# PROJECT MANAGEMENT ROUTES
|
# PROJECT MANAGEMENT ROUTES
|
||||||
|
|||||||
+171
-18
@@ -600,43 +600,92 @@ class AppLogger:
|
|||||||
try:
|
try:
|
||||||
cutoff_date = datetime.now() - timedelta(days=days)
|
cutoff_date = datetime.now() - timedelta(days=days)
|
||||||
|
|
||||||
stats_sql = """
|
# Get total events
|
||||||
|
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()
|
||||||
|
|
||||||
|
# Get events by category
|
||||||
|
category_sql = """
|
||||||
SELECT
|
SELECT
|
||||||
event_category,
|
event_category,
|
||||||
severity_level,
|
|
||||||
COUNT(*) as event_count
|
COUNT(*) as event_count
|
||||||
FROM log_events
|
FROM log_events
|
||||||
WHERE created_timestamp >= :cutoff_date
|
WHERE created_timestamp >= :cutoff_date
|
||||||
GROUP BY event_category, severity_level
|
GROUP BY event_category
|
||||||
ORDER BY event_count DESC
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = self.db.session.execute(text(stats_sql), {
|
category_result = self.db.session.execute(text(category_sql), {
|
||||||
'cutoff_date': cutoff_date
|
'cutoff_date': cutoff_date
|
||||||
}).fetchall()
|
}).fetchall()
|
||||||
|
|
||||||
stats = {}
|
# Build simple statistics dictionary (not nested)
|
||||||
for row in result:
|
stats = {
|
||||||
category = row.event_category
|
'total_events': total_result.total_events if total_result else 0,
|
||||||
if category not in stats:
|
'security_events': 0,
|
||||||
stats[category] = {}
|
'database_errors': 0,
|
||||||
stats[category][row.severity_level] = row.event_count
|
'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
|
||||||
|
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log_database_error('get_log_statistics', e)
|
self.log_database_error('get_log_statistics', e)
|
||||||
return {}
|
print(f"Error in get_log_statistics: {e}")
|
||||||
|
# Return default stats structure
|
||||||
|
return {
|
||||||
|
'total_events': 0,
|
||||||
|
'security_events': 0,
|
||||||
|
'database_errors': 0,
|
||||||
|
'user_activities': 0,
|
||||||
|
'system_events': 0
|
||||||
|
}
|
||||||
|
|
||||||
def cleanup_old_logs(self, days_to_keep=90):
|
def cleanup_old_logs(self, days_to_keep=90):
|
||||||
"""Clean up old log entries from database"""
|
"""Clean up old log entries from database"""
|
||||||
try:
|
try:
|
||||||
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
|
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
|
||||||
|
|
||||||
|
# First, count how many records will be deleted
|
||||||
|
count_sql = """
|
||||||
|
SELECT COUNT(*) as count_to_delete
|
||||||
|
FROM log_events
|
||||||
|
WHERE created_timestamp < :cutoff_date
|
||||||
|
AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH')
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if count_to_delete == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Perform the cleanup - exclude critical logs
|
||||||
cleanup_sql = """
|
cleanup_sql = """
|
||||||
DELETE FROM log_events
|
DELETE FROM log_events
|
||||||
WHERE created_timestamp < :cutoff_date
|
WHERE created_timestamp < :cutoff_date
|
||||||
AND severity_level NOT IN ('ERROR', 'CRITICAL')
|
AND severity_level NOT IN ('ERROR', 'CRITICAL', 'HIGH')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = self.db.session.execute(text(cleanup_sql), {
|
result = self.db.session.execute(text(cleanup_sql), {
|
||||||
@@ -646,14 +695,92 @@ class AppLogger:
|
|||||||
deleted_count = result.rowcount
|
deleted_count = result.rowcount
|
||||||
self.db.session.commit()
|
self.db.session.commit()
|
||||||
|
|
||||||
self.logger.info(f"Cleaned up {deleted_count} old log entries")
|
# Log the cleanup operation
|
||||||
|
self.logger.info(f"Log cleanup completed: {deleted_count} entries removed (keeping entries older than {days_to_keep} days)")
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return deleted_count
|
return deleted_count
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.db.session.rollback()
|
||||||
self.log_database_error('cleanup_old_logs', e)
|
self.log_database_error('cleanup_old_logs', e)
|
||||||
|
print(f"Error in cleanup_old_logs: {e}")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def get_recent_logs(self, days=7, limit=100, category_filter=None, severity_filter=None, search_term=None):
|
||||||
|
"""Enhanced method to get recent logs with filtering options"""
|
||||||
|
try:
|
||||||
|
cutoff_date = datetime.now() - timedelta(days=days)
|
||||||
|
|
||||||
|
# Build the base query
|
||||||
|
base_sql = """
|
||||||
|
SELECT
|
||||||
|
event_id,
|
||||||
|
event_type,
|
||||||
|
event_category,
|
||||||
|
event_description,
|
||||||
|
severity_level,
|
||||||
|
created_timestamp,
|
||||||
|
username,
|
||||||
|
ip_address,
|
||||||
|
user_id
|
||||||
|
FROM log_events
|
||||||
|
WHERE created_timestamp >= :cutoff_date
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Add filters
|
||||||
|
params = {'cutoff_date': cutoff_date}
|
||||||
|
|
||||||
|
if category_filter:
|
||||||
|
base_sql += " AND event_category = :category_filter"
|
||||||
|
params['category_filter'] = category_filter
|
||||||
|
|
||||||
|
if severity_filter:
|
||||||
|
base_sql += " AND severity_level = :severity_filter"
|
||||||
|
params['severity_filter'] = severity_filter
|
||||||
|
|
||||||
|
if search_term:
|
||||||
|
base_sql += " AND (event_description LIKE :search_term OR event_type LIKE :search_term OR username LIKE :search_term)"
|
||||||
|
params['search_term'] = f"%{search_term}%"
|
||||||
|
|
||||||
|
# Add ordering and limit
|
||||||
|
base_sql += " ORDER BY created_timestamp DESC LIMIT :limit"
|
||||||
|
params['limit'] = limit
|
||||||
|
|
||||||
|
result = self.db.session.execute(text(base_sql), params).fetchall()
|
||||||
|
|
||||||
|
logs = []
|
||||||
|
for row in result:
|
||||||
|
logs.append({
|
||||||
|
'event_id': row.event_id,
|
||||||
|
'event_type': row.event_type,
|
||||||
|
'event_category': row.event_category,
|
||||||
|
'description': row.event_description,
|
||||||
|
'severity': row.severity_level,
|
||||||
|
'timestamp': row.created_timestamp.isoformat(),
|
||||||
|
'username': row.username or 'System',
|
||||||
|
'ip_address': row.ip_address or '-',
|
||||||
|
'user_id': row.user_id
|
||||||
|
})
|
||||||
|
|
||||||
|
return logs
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_database_error('get_recent_logs', e)
|
||||||
|
print(f"Error in get_recent_logs: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
# DECORATOR FUNCTIONS FOR AUTOMATIC LOGGING
|
# DECORATOR FUNCTIONS FOR AUTOMATIC LOGGING
|
||||||
|
|
||||||
def log_user_activity(activity_type):
|
def log_user_activity(activity_type):
|
||||||
@@ -693,7 +820,6 @@ def log_user_activity(activity_type):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def log_database_operations(operation_name):
|
def log_database_operations(operation_name):
|
||||||
"""Decorator to automatically log database operations"""
|
"""Decorator to automatically log database operations"""
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
@@ -716,9 +842,36 @@ def log_database_operations(operation_name):
|
|||||||
return decorated_function
|
return decorated_function
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
def verify_log_table_exists(self):
|
||||||
|
"""Verify that the log_events table exists and has the correct structure"""
|
||||||
|
try:
|
||||||
|
# Check if table exists
|
||||||
|
check_table_sql = """
|
||||||
|
SELECT COUNT(*) as table_exists
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND table_name = 'log_events'
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = self.db.session.execute(text(check_table_sql)).fetchone()
|
||||||
|
|
||||||
|
if result.table_exists == 0:
|
||||||
|
print("⚠️ log_events table does not exist. Creating it now...")
|
||||||
|
self._create_log_table()
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if table has records
|
||||||
|
count_sql = "SELECT COUNT(*) as record_count FROM log_events"
|
||||||
|
count_result = self.db.session.execute(text(count_sql)).fetchone()
|
||||||
|
|
||||||
|
print(f"✅ log_events table exists with {count_result.record_count} records")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error verifying log table: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
# INITIALIZATION FUNCTION
|
# INITIALIZATION FUNCTION
|
||||||
|
|
||||||
def init_logging(app, db):
|
def init_logging(app, db):
|
||||||
"""Initialize the logging system with the Flask app"""
|
"""Initialize the logging system with the Flask app"""
|
||||||
logger_handler = AppLogger(app, db)
|
logger_handler = AppLogger(app, db)
|
||||||
|
|||||||
Reference in New Issue
Block a user