Update log page
This commit is contained in:
@@ -2167,6 +2167,193 @@ def api_cleanup_logs():
|
||||
'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')
|
||||
@admin_required
|
||||
|
||||
+120
-32
@@ -11,27 +11,17 @@ Management{% endblock %} {% block extra_head %}
|
||||
<!-- Page Header -->
|
||||
<div class="logs-header">
|
||||
<div class="header-content">
|
||||
<div class="header-navigation">
|
||||
<a href="{{ url_for('dashboard') }}" class="back-button">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<span>Back to Dashboard</span>
|
||||
</a>
|
||||
</div>
|
||||
<h1><i class="fas fa-clipboard-list"></i> System Logs</h1>
|
||||
<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()">
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshLogs()">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
Refresh
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="cleanupLogs()">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
Cleanup Old Logs
|
||||
<button class="btn btn-secondary btn-sm" onclick="exportLogs()">
|
||||
<i class="fas fa-download"></i>
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,13 +138,6 @@ Management{% endblock %} {% block extra_head %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="view-options">
|
||||
<button class="btn btn-secondary btn-sm" onclick="exportLogs()">
|
||||
<i class="fas fa-download"></i>
|
||||
Export
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Table -->
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user