Update log page

This commit is contained in:
2025-08-16 14:48:54 -04:00
parent 9066be25bf
commit 4b5d21bc73
2 changed files with 307 additions and 32 deletions
+187
View File
@@ -2166,6 +2166,193 @@ def api_cleanup_logs():
'success': False, 'success': False,
'error': f'Failed to cleanup old logs: {str(e)}' 'error': f'Failed to cleanup old logs: {str(e)}'
}), 500 }), 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 # PROJECT MANAGEMENT ROUTES
@app.route('/projects') @app.route('/projects')
+120 -32
View File
@@ -11,27 +11,17 @@ Management{% endblock %} {% block extra_head %}
<!-- Page Header --> <!-- Page Header -->
<div class="logs-header"> <div class="logs-header">
<div class="header-content"> <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> <h1><i class="fas fa-clipboard-list"></i> System Logs</h1>
<p>Monitor system activities and security events</p> <p>Monitor system activities and security events</p>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<button class="btn btn-secondary" onclick="testLogging()"> <button class="btn btn-secondary btn-sm" onclick="refreshLogs()">
<i class="fas fa-flask"></i>
Test Logging
</button>
<button class="btn btn-secondary" onclick="refreshLogs()">
<i class="fas fa-sync-alt"></i> <i class="fas fa-sync-alt"></i>
Refresh Refresh
</button> </button>
<button class="btn btn-warning" onclick="cleanupLogs()"> <button class="btn btn-secondary btn-sm" onclick="exportLogs()">
<i class="fas fa-trash-alt"></i> <i class="fas fa-download"></i>
Cleanup Old Logs Export
</button> </button>
</div> </div>
</div> </div>
@@ -148,13 +138,6 @@ Management{% endblock %} {% block extra_head %}
</select> </select>
</div> </div>
</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> </div>
<!-- Logs Table --> <!-- 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 // Display logs in table
function displayLogs(logs) { function displayLogs(logs) {
const tbody = document.getElementById("logsTableBody"); const tbody = document.getElementById("logsTableBody");
@@ -739,28 +804,51 @@ ${
loadStats(); loadStats();
} }
// Test logging functionality async function clearLogs() {
async function testLogging() { // Show confirmation dialog
try { if (
showMessage("Testing logging system...", "info"); !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(); const data = await response.json();
if (data.success) { if (data.success) {
showMessage("✅ Logging system test passed!", "success"); showSuccess(
`✅ Successfully cleared ${data.deleted_count} log entries`
);
setTimeout(() => { setTimeout(() => {
loadLogs(); loadLogs();
loadStats(); loadStats();
}, 1000); }, 1000);
} else { } else {
showError( showError("❌ Error clearing logs: " + (data.error || "Unknown error"));
"❌ Logging system test failed: " + (data.error || "Unknown error")
);
} }
} catch (error) { } catch (error) {
console.error("Error testing logging:", error); console.error("Error clearing logs:", error);
showError("❌ Failed to run logging test: " + error.message); showError("❌ Failed to clear logs: " + error.message);
} }
} }