diff --git a/app.py b/app.py index 928ae2d..80eac14 100644 --- a/app.py +++ b/app.py @@ -2166,6 +2166,193 @@ def api_cleanup_logs(): 'success': False, 'error': f'Failed to cleanup old logs: {str(e)}' }), 500 + +@app.route('/api/logs/clear', methods=['POST']) +@admin_required +def api_clear_logs(): + """API endpoint to clear ALL log entries""" + try: + admin_username = session.get('username', 'unknown') + print(f"๐Ÿงน Clear logs request by admin: {admin_username}") + + # Count existing logs before deletion + try: + count_sql = "SELECT COUNT(*) as total_logs FROM log_events" + count_result = db.session.execute(text(count_sql)).fetchone() + total_logs = count_result.total_logs if count_result else 0 + + print(f"๐Ÿ“Š Total logs to be cleared: {total_logs}") + + if total_logs == 0: + print("โœ… No logs found to clear") + return jsonify({ + 'success': True, + 'deleted_count': 0, + 'message': 'No logs found to clear' + }) + + except Exception as count_error: + print(f"โš ๏ธ Error counting logs: {count_error}") + total_logs = 0 + + # Perform the clear operation + try: + clear_sql = "DELETE FROM log_events" + result = db.session.execute(text(clear_sql)) + deleted_count = result.rowcount + db.session.commit() + + print(f"๐Ÿ—‘๏ธ Successfully cleared {deleted_count} log entries") + + # Log the clear operation (this will be the first entry in the new log) + logger_handler.log_security_event( + event_type="admin_log_clear", + description=f"Admin {admin_username} cleared all log entries: {deleted_count} records deleted", + severity="HIGH", + additional_data={ + 'admin_user': admin_username, + 'deleted_count': deleted_count, + 'ip_address': request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr) + } + ) + + return jsonify({ + 'success': True, + 'deleted_count': deleted_count, + 'message': f'Successfully cleared {deleted_count} log entries', + 'performed_by': admin_username, + 'performed_at': datetime.now().isoformat() + }) + + except Exception as delete_error: + print(f"โŒ Error during log clearing: {delete_error}") + db.session.rollback() + return jsonify({ + 'success': False, + 'error': f'Failed to clear logs: {str(delete_error)}' + }), 500 + + except Exception as e: + logger_handler.log_database_error('api_clear_logs', e) + print(f"โŒ Error in api_clear_logs: {e}") + return jsonify({ + 'success': False, + 'error': f'Failed to clear logs: {str(e)}' + }), 500 + +@app.route('/api/logs/export') +@admin_required +def api_export_logs(): + """API endpoint to export log entries""" + try: + days = request.args.get('days', 7, type=int) + category = request.args.get('category', '') + severity = request.args.get('severity', '') + search = request.args.get('search', '') + + admin_username = session.get('username', 'unknown') + print(f"๐Ÿ“Š Export logs request by admin: {admin_username}") + + cutoff_date = datetime.now() - timedelta(days=days) + + # Build the SQL query with filters + base_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 + """ + + params = {'cutoff_date': cutoff_date} + + # Add category filter + if category: + base_sql += " AND event_category = :category" + params['category'] = category + + # Add severity filter + if severity: + base_sql += " AND severity_level = :severity" + params['severity'] = severity + + # Add search filter + if search: + base_sql += " AND (event_type LIKE :search OR event_description LIKE :search OR username LIKE :search)" + params['search'] = f'%{search}%' + + base_sql += " ORDER BY created_timestamp DESC" + + result = db.session.execute(text(base_sql), params).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 '-' + }) + + # Log the export operation + logger_handler.log_security_event( + event_type="admin_log_export", + description=f"Admin {admin_username} exported {len(logs)} log entries (last {days} days)", + severity="MEDIUM", + additional_data={ + 'admin_user': admin_username, + 'exported_count': len(logs), + 'days_exported': days, + 'filters': { + 'category': category, + 'severity': severity, + 'search': search + }, + 'ip_address': request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr) + } + ) + + return jsonify({ + 'success': True, + 'logs': logs, + 'total': len(logs), + 'filters_applied': { + 'days': days, + 'category': category, + 'severity': severity, + 'search': search + } + }) + + except Exception as e: + logger_handler.log_database_error('api_export_logs', e) + print(f"โŒ Error in api_export_logs: {e}") + return jsonify({ + 'success': False, + 'error': f'Failed to export logs: {str(e)}' + }), 500 # PROJECT MANAGEMENT ROUTES @app.route('/projects') diff --git a/templates/admin_logs.html b/templates/admin_logs.html index f229c7c..aa90b61 100644 --- a/templates/admin_logs.html +++ b/templates/admin_logs.html @@ -11,27 +11,17 @@ Management{% endblock %} {% block extra_head %}
-

System Logs

Monitor system activities and security events

- - -
@@ -148,13 +138,6 @@ Management{% endblock %} {% block extra_head %} - -
- -
@@ -443,6 +426,88 @@ Management{% endblock %} {% block extra_head %} } } + async function exportLogs() { + try { + showMessage("Preparing log export...", "info"); + + // Get current filters to include in export + const params = new URLSearchParams({ + days: currentFilters.days, + category: currentFilters.category || "", + severity: currentFilters.severity || "", + search: currentFilters.search || "", + export: "csv", + }); + + const response = await fetch(`/api/logs/export?${params}`); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + if (data.success) { + // Create CSV content + const headers = [ + "Timestamp", + "Event Type", + "Category", + "Description", + "Severity", + "Username", + "User ID", + "IP Address", + ]; + const csvData = [headers]; + + data.logs.forEach((log) => { + csvData.push([ + new Date(log.timestamp).toLocaleString(), + log.event_type || "", + log.event_category || "", + (log.description || "").replace(/"/g, '""'), // Escape quotes + log.severity || "", + log.username || "System", + log.user_id || "", + log.ip_address || "", + ]); + }); + + // Generate CSV string + const csvContent = csvData + .map((row) => row.map((field) => `"${field}"`).join(",")) + .join("\n"); + + // Create and download file + const blob = new Blob([csvContent], { + type: "text/csv;charset=utf-8;", + }); + const link = document.createElement("a"); + const url = URL.createObjectURL(blob); + link.setAttribute("href", url); + + const timestamp = new Date().toISOString().split("T")[0]; + const filename = `system_logs_${timestamp}.csv`; + link.setAttribute("download", filename); + + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + showSuccess(`โœ… Logs exported successfully as ${filename}`); + } else { + showError( + "โŒ Error exporting logs: " + (data.error || "Unknown error") + ); + } + } catch (error) { + console.error("Error exporting logs:", error); + showError("โŒ Failed to export logs: " + error.message); + } + } + // Display logs in table function displayLogs(logs) { const tbody = document.getElementById("logsTableBody"); @@ -739,28 +804,51 @@ ${ loadStats(); } - // Test logging functionality - async function testLogging() { - try { - showMessage("Testing logging system...", "info"); + async function clearLogs() { + // Show confirmation dialog + if ( + !confirm( + "โš ๏ธ Are you sure you want to clear ALL logs?\n\nThis action will permanently delete ALL log entries from the system and cannot be undone." + ) + ) { + return; + } + + // Second confirmation for safety + if ( + !confirm( + "๐Ÿšจ FINAL WARNING: This will delete ALL logs!\n\nClick OK to proceed or Cancel to abort." + ) + ) { + return; + } + + try { + showMessage("Clearing all logs...", "info"); + + const response = await fetch("/api/logs/clear", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + }); - const response = await fetch("/api/logs/test"); const data = await response.json(); if (data.success) { - showMessage("โœ… Logging system test passed!", "success"); + showSuccess( + `โœ… Successfully cleared ${data.deleted_count} log entries` + ); setTimeout(() => { loadLogs(); loadStats(); }, 1000); } else { - showError( - "โŒ Logging system test failed: " + (data.error || "Unknown error") - ); + showError("โŒ Error clearing logs: " + (data.error || "Unknown error")); } } catch (error) { - console.error("Error testing logging:", error); - showError("โŒ Failed to run logging test: " + error.message); + console.error("Error clearing logs:", error); + showError("โŒ Failed to clear logs: " + error.message); } }