You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

793 lines
31 KiB

{% extends "base.html" %}
{% block title %}Analytics Dashboard - Plutus Payment System{% endblock %}
{% block head %}
<style>
/* Background styling */
body {
background-color: #3a3a3a !important;
background-image: url("{{ url_for('static', filename='images/plutus3.JPG') }}") !important;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
position: relative;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(58, 58, 58, 0.85);
z-index: -1;
}
.container-fluid, .container {
position: relative;
z-index: 1;
}
/* Ensure navbar and footer are above background */
.navbar, .footer, main {
position: relative;
z-index: 1;
}
/* Page title and breadcrumb styling */
h1, h2, h3, h4, h5, h6 {
color: #faf8f0 !important;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
}
.breadcrumb-item a {
color: #d4af37 !important;
}
.breadcrumb-item.active {
color: #faf8f0 !important;
}
.card {
background-color: rgba(250, 248, 240, 0.98) !important;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
}
.metric-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
text-align: center;
}
.metric-value {
font-size: 2rem;
font-weight: bold;
margin-bottom: 5px;
}
.metric-label {
color: #666;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.health-score {
display: flex;
align-items: center;
justify-content: center;
width: 100px;
height: 100px;
border-radius: 50%;
margin: 0 auto 15px;
font-size: 1.5rem;
font-weight: bold;
color: white;
}
.health-excellent { background: linear-gradient(135deg, #48c774, #00d1b2); }
.health-good { background: linear-gradient(135deg, #48c774, #ffdd57); }
.health-warning { background: linear-gradient(135deg, #ffdd57, #ff9800); }
.health-critical { background: linear-gradient(135deg, #ff9800, #f14668); }
.tab-content {
margin-top: 20px;
min-height: 400px;
}
.log-entry {
background: #f8f9fa;
border-left: 4px solid #0d6efd;
padding: 12px;
margin-bottom: 10px;
border-radius: 4px;
}
.loading {
opacity: 0.6;
pointer-events: none;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
{% endblock %}
{% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-start mb-4">
<div>
<h1 class="h2 mb-1">
<i class="fas fa-chart-line"></i> Analytics Dashboard
</h1>
</div>
<div class="d-flex gap-2">
<button class="btn btn-light" onclick="refreshDashboard()">
<i class="fas fa-sync-alt"></i> Refresh
</button>
<button class="btn btn-primary" onclick="exportReport()">
<i class="fas fa-download"></i> Export
</button>
</div>
</div>
<!-- System Health Overview -->
<div class="row">
<div class="col-md-3">
<div class="metric-card">
<div id="healthScore" class="health-score">
<span id="healthValue">--</span>
</div>
<div class="metric-label">System Health Score</div>
<p class="small text-muted">Overall system performance</p>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div id="paymentSuccessRate" class="metric-value text-success">--%</div>
<div class="metric-label">Payment Success Rate</div>
<p class="small text-muted">Last 24 hours</p>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div id="errorRate" class="metric-value text-warning">--%</div>
<div class="metric-label">Error Rate</div>
<p class="small text-muted">System errors in logs</p>
</div>
</div>
<div class="col-md-3">
<div class="metric-card">
<div id="totalPayments" class="metric-value text-info">--</div>
<div class="metric-label">Total Payments</div>
<p class="small text-muted">Recent activity</p>
</div>
</div>
</div>
<!-- Tabs -->
<ul class="nav nav-tabs justify-content-center" id="analyticsTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="performance-tab" data-bs-toggle="tab" data-bs-target="#performance-content" type="button" role="tab" data-tab="performance">
<i class="fas fa-tachometer-alt"></i> Performance
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="payments-tab" data-bs-toggle="tab" data-bs-target="#payments-content" type="button" role="tab" data-tab="payments">
<i class="fas fa-credit-card"></i> Payments
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="security-tab" data-bs-toggle="tab" data-bs-target="#security-content" type="button" role="tab" data-tab="security">
<i class="fas fa-shield-alt"></i> Security
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="logs-tab" data-bs-toggle="tab" data-bs-target="#logs-content" type="button" role="tab" data-tab="logs">
<i class="fas fa-list-alt"></i> Logs
</button>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content">
<!-- Performance Tab -->
<div class="tab-pane fade show active" id="performance-content" role="tabpanel">
<div class="card shadow mt-3">
<div class="card-body">
<h4 class="h5 mb-3">
<i class="fas fa-clock"></i> System Performance
</h4>
<div id="performanceMetrics">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading performance metrics...</p>
</div>
</div>
</div>
</div>
</div>
<!-- Payments Tab -->
<div class="tab-pane fade" id="payments-content" role="tabpanel">
<div class="card shadow mt-3">
<div class="card-body">
<h4 class="h5 mb-3">
<i class="fas fa-chart-line"></i> Payment Analytics
</h4>
<div id="paymentMetrics">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading payment analytics...</p>
</div>
</div>
</div>
</div>
</div>
<!-- Security Tab -->
<div class="tab-pane fade" id="security-content" role="tabpanel">
<div class="row mt-3">
<div class="col-md-4">
<div class="metric-card">
<div id="securityEvents" class="metric-value text-success">--</div>
<div class="metric-label">Security Events</div>
<p class="small text-muted">Last 7 days</p>
</div>
</div>
<div class="col-md-4">
<div class="metric-card">
<div id="failedLogins" class="metric-value text-warning">--</div>
<div class="metric-label">Failed Logins</div>
<p class="small text-muted">Authentication failures</p>
</div>
</div>
<div class="col-md-4">
<div class="metric-card">
<div id="blockedRequests" class="metric-value text-danger">--</div>
<div class="metric-label">Blocked Requests</div>
<p class="small text-muted">Suspicious activity</p>
</div>
</div>
</div>
<div class="card shadow">
<div class="card-body">
<h4 class="h5 mb-3">
<i class="fas fa-shield-alt"></i> Security Events
</h4>
<div id="securityEventsList">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading security events...</p>
</div>
</div>
</div>
</div>
</div>
<!-- Logs Tab -->
<div class="tab-pane fade" id="logs-content" role="tabpanel">
<div class="card shadow mt-3">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="h5 mb-0">
<i class="fas fa-search"></i> Log Search
</h4>
<div class="d-flex gap-2">
<input type="text" id="logSearch" class="form-control form-control-sm" placeholder="Search logs..." style="max-width: 200px;">
<select id="logAction" class="form-select form-select-sm" style="max-width: 200px;">
<option value="">All Actions</option>
<option value="LOGIN_SUCCESS">Login Success</option>
<option value="LOGIN_FAILED">Login Failed</option>
<option value="PAYMENT_PROCESSED">Payment Processed</option>
<option value="BATCH_CREATED">Batch Created</option>
</select>
<button class="btn btn-sm btn-primary" onclick="searchLogs()">
<i class="fas fa-search"></i>
</button>
</div>
</div>
<div id="logResults">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading recent logs...</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
// Initialize dashboard
refreshSystemHealth();
// Load the default active tab content (Performance)
loadTabContent('performance');
// Tab switching
$('button[data-bs-toggle="tab"]').on('shown.bs.tab', function (e) {
const tab = $(e.target).data('tab');
loadTabContent(tab);
});
// Auto-refresh every 30 seconds
setInterval(refreshSystemHealth, 30000);
});
function refreshSystemHealth() {
fetch('/analytics/api/system-health')
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Error loading system health:', data.error);
if (data.debug_error) {
console.error('Debug error:', data.debug_error);
}
}
updateHealthScore(data.health_score);
$('#paymentSuccessRate').text(data.payment_success_rate + '%');
$('#errorRate').text(data.error_rate + '%');
$('#totalPayments').text((data.metrics.total_payments || 0).toLocaleString());
// Update health score color
const healthElement = $('#healthScore');
healthElement.removeClass('health-excellent health-good health-warning health-critical');
if (data.health_score >= 90) {
healthElement.addClass('health-excellent');
} else if (data.health_score >= 75) {
healthElement.addClass('health-good');
} else if (data.health_score >= 60) {
healthElement.addClass('health-warning');
} else {
healthElement.addClass('health-critical');
}
})
.catch(error => {
console.error('Error loading system health:', error);
// Show mock data
updateHealthScore(85);
$('#paymentSuccessRate').text('95%');
$('#errorRate').text('2.5%');
$('#totalPayments').text('45');
});
}
function updateHealthScore(score) {
$('#healthValue').text(Math.round(score || 0));
}
function loadTabContent(tab) {
switch(tab) {
case 'performance':
loadPerformanceMetrics();
break;
case 'payments':
loadPaymentAnalytics();
break;
case 'security':
loadSecurityEvents();
break;
case 'logs':
searchLogs();
break;
}
}
function loadPerformanceMetrics() {
console.log('Loading performance metrics...');
$('#performanceMetrics').html(`
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading performance metrics...</p>
</div>
`);
// Add timeout to the fetch
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
fetch('/analytics/api/performance-metrics', {
signal: controller.signal,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => {
clearTimeout(timeoutId);
console.log('Performance metrics response:', response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
console.log('Performance metrics data:', data);
if (data.error) {
$('#performanceMetrics').html(`
<div class="alert alert-info">
<h5 class="h5">
<i class="fas fa-chart-line"></i> Performance Monitoring
</h5>
<p><strong>Status:</strong> ${data.message || data.error}</p>
<p>The system is actively collecting performance data. Check back after some activity.</p>
${data.debug_error ? `<details><summary>Debug Info</summary><pre>${data.debug_error}</pre></details>` : ''}
</div>
`);
return;
}
let html = '<div>';
// System overview
if (data.system_info) {
html += '<div class="row">';
html += '<div class="col-md-4">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Monitoring Status</p>';
html += `<p class="h4 ${data.system_info.monitoring_active ? 'text-success' : 'text-warning'}">`;
html += `<i class="fas ${data.system_info.monitoring_active ? 'fa-check-circle' : 'fa-exclamation-triangle'}"></i> `;
html += `${data.system_info.monitoring_active ? 'Active' : 'Inactive'}`;
html += '</p>';
html += '</div></div></div>';
html += '<div class="col-md-4">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Log Files</p>';
html += `<p class="h4 text-info">${data.system_info.log_files_found || 0}</p>`;
html += '</div></div></div>';
html += '<div class="col-md-4">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Collection Period</p>';
html += `<p class="h4 text-muted">${data.system_info.data_collection_period || '7 days'}</p>`;
html += '</div></div></div>';
html += '</div>';
}
// Performance summary
if (data.summary) {
html += '<h5 class="h5 mt-4">Performance Summary</h5>';
html += '<div class="row">';
html += '<div class="col-md-3">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Slow Requests</p>';
html += `<p class="h4 ${data.summary.slow_request_count > 0 ? 'text-warning' : 'text-success'}">${data.summary.slow_request_count || 0}</p>`;
html += '<p class="small text-muted">Requests > 1 second</p>';
html += '</div></div></div>';
html += '<div class="col-md-3">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Slow Queries</p>';
html += `<p class="h4 ${data.summary.database_queries > 0 ? 'text-warning' : 'text-success'}">${data.summary.database_queries || 0}</p>`;
html += '<p class="small text-muted">DB queries > 100ms</p>';
html += '</div></div></div>';
html += '<div class="col-md-3">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Total Requests</p>';
html += `<p class="h4 text-info">${data.summary.total_requests || 'N/A'}</p>`;
html += '<p class="small text-muted">Recent requests</p>';
html += '</div></div></div>';
html += '<div class="col-md-3">';
html += '<div class="card text-center">';
html += '<div class="card-body">';
html += '<p class="text-muted text-uppercase small">Avg Response</p>';
html += `<p class="h4 text-info">${data.summary.avg_response_time || 'N/A'}</p>`;
html += '<p class="small text-muted">Milliseconds</p>';
html += '</div></div></div>';
html += '</div>';
}
// Slow requests table
if (data.slow_requests && data.slow_requests.length > 0) {
html += '<h5 class="h5 mt-4">Recent Slow Requests</h5>';
html += '<div class="table-responsive">';
html += '<table class="table table-striped">';
html += '<thead><tr><th>Time</th><th>Endpoint</th><th>Duration</th><th>Status</th></tr></thead>';
html += '<tbody>';
data.slow_requests.slice(0, 10).forEach(req => {
html += '<tr>';
html += `<td>${new Date(req.timestamp).toLocaleString()}</td>`;
html += `<td><code>${req.endpoint}</code></td>`;
html += `<td><span class="badge bg-warning">${Math.round(req.duration_ms)}ms</span></td>`;
html += `<td><span class="badge ${req.status_code < 400 ? 'bg-success' : 'bg-danger'}">${req.status_code}</span></td>`;
html += '</tr>';
});
html += '</tbody></table>';
html += '</div>';
} else {
html += '<div class="alert alert-success mt-3">';
html += '<i class="fas fa-check"></i> ';
html += '<strong>Good Performance!</strong> No slow requests detected recently.';
html += '</div>';
}
// Slow queries table
if (data.slow_queries && data.slow_queries.length > 0) {
html += '<h5 class="h5 mt-4">Recent Slow Database Queries</h5>';
html += '<div class="table-responsive">';
html += '<table class="table table-striped">';
html += '<thead><tr><th>Time</th><th>Table</th><th>Type</th><th>Duration</th></tr></thead>';
html += '<tbody>';
data.slow_queries.slice(0, 10).forEach(query => {
html += '<tr>';
html += `<td>${new Date(query.timestamp).toLocaleString()}</td>`;
html += `<td><code>${query.table}</code></td>`;
html += `<td>${query.query_type}</td>`;
html += `<td><span class="badge bg-warning">${Math.round(query.duration_ms)}ms</span></td>`;
html += '</tr>';
});
html += '</tbody></table>';
html += '</div>';
}
html += '</div>';
$('#performanceMetrics').html(html);
})
.catch(error => {
clearTimeout(timeoutId);
console.error('Error loading performance metrics:', error);
let errorMessage = 'Unable to load performance metrics at this time.';
if (error.name === 'AbortError') {
errorMessage = 'Request timed out after 10 seconds.';
} else if (error.message.includes('HTTP')) {
errorMessage = `Server error: ${error.message}`;
}
$('#performanceMetrics').html(`
<div class="alert alert-warning">
<h5 class="h5">Performance Data Loading Error</h5>
<p><strong>Error:</strong> ${errorMessage}</p>
<p>The system may be initializing or there may be a connectivity issue.</p>
<button class="btn btn-sm btn-primary" onclick="loadPerformanceMetrics()">Try Again</button>
<details class="mt-2">
<summary>Debug Information</summary>
<pre>${error.stack || error.message}</pre>
</details>
</div>
`);
});
}
function loadPaymentAnalytics() {
$('#paymentMetrics').html(`
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading payment analytics...</p>
</div>
`);
fetch('/analytics/api/payment-analytics')
.then(response => response.json())
.then(data => {
if (data.error) {
$('#paymentMetrics').html(`
<div class="alert alert-info">
<p><strong>Payment analytics:</strong> No recent payment data available.</p>
<p>Analytics will appear after payment processing activity.</p>
</div>
`);
return;
}
let html = '<div>';
html += '<h5>Payment Analytics Overview</h5>';
html += '<p>Payment analytics are being collected. Detailed metrics will appear with payment activity.</p>';
html += '</div>';
$('#paymentMetrics').html(html);
})
.catch(error => {
console.error('Error loading payment analytics:', error);
$('#paymentMetrics').html(`
<div class="alert alert-info">
<p>Payment analytics will be available after payment processing activity.</p>
</div>
`);
});
}
function loadSecurityEvents() {
$('#securityEventsList').html(`
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Loading security events...</p>
</div>
`);
fetch('/analytics/api/security-events')
.then(response => response.json())
.then(data => {
if (data.error) {
$('#securityEventsList').html(`
<div class="alert alert-success">
<p><strong>Security status:</strong> All clear - no security events detected.</p>
<p>The system is actively monitoring for security threats.</p>
</div>
`);
$('#securityEvents').text('0');
$('#failedLogins').text('0');
$('#blockedRequests').text('0');
return;
}
$('#securityEvents').text(data.summary.total_events);
$('#failedLogins').text(data.summary.failed_logins);
$('#blockedRequests').text(data.summary.blocked_requests);
if (data.summary.total_events === 0) {
$('#securityEventsList').html(`
<div class="alert alert-success">
<i class="fas fa-shield-alt"></i>
<strong>All clear!</strong> No security events detected.
</div>
`);
} else {
$('#securityEventsList').html('<p>Security events detected. Review logs for details.</p>');
}
})
.catch(error => {
console.error('Error loading security events:', error);
$('#securityEventsList').html(`
<div class="alert alert-success">
<i class="fas fa-shield-alt"></i>
Security monitoring is active.
</div>
`);
$('#securityEvents').text('0');
$('#failedLogins').text('0');
$('#blockedRequests').text('0');
});
}
function searchLogs() {
const query = $('#logSearch').val();
const action = $('#logAction').val();
$('#logResults').html(`
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3">Searching logs...</p>
</div>
`);
const params = new URLSearchParams({ page: 1, per_page: 20 });
if (query) params.append('q', query);
if (action) params.append('action', action);
fetch(`/analytics/api/logs/search?${params}`)
.then(response => response.json())
.then(data => {
if (data.error) {
$('#logResults').html(`
<div class="alert alert-warning">
<p><strong>Log search error:</strong> ${data.error}</p>
</div>
`);
return;
}
if (!data.logs || data.logs.length === 0) {
$('#logResults').html(`
<div class="alert alert-info">
<p>No logs found matching your search criteria.</p>
</div>
`);
return;
}
let html = '';
data.logs.forEach(log => {
const logClass = log.action.includes('ERROR') || log.action.includes('FAILED') ? 'bg-danger bg-opacity-10' :
log.action.includes('WARNING') ? 'bg-warning bg-opacity-10' :
log.action.includes('SUCCESS') ? 'bg-success bg-opacity-10' : '';
html += `<div class="log-entry ${logClass}">
<div class="d-flex justify-content-between">
<div>
<strong>${log.action}</strong>
</div>
<div>
<small class="text-muted">${new Date(log.timestamp).toLocaleString()}</small>
</div>
</div>
<div>${log.message || 'No message'}</div>
<small class="text-muted">
${log.entity_type} ${log.entity_id || ''} | User ID: ${log.user_id || 'System'} | IP: ${log.ip_address || 'Unknown'}
</small>
</div>`;
});
$('#logResults').html(html);
})
.catch(error => {
console.error('Error searching logs:', error);
$('#logResults').html(`
<div class="alert alert-warning">
<p>Error searching logs. Please try again.</p>
</div>
`);
});
}
function refreshDashboard() {
document.body.classList.add('loading');
refreshSystemHealth();
// Refresh current tab content
const activeTabButton = document.querySelector('.nav-link.active');
const activeTab = activeTabButton ? activeTabButton.getAttribute('data-tab') : null;
if (activeTab) {
loadTabContent(activeTab);
}
setTimeout(() => {
document.body.classList.remove('loading');
}, 1000);
}
function exportReport() {
const activeTabButton = document.querySelector('.nav-link.active');
const activeTab = activeTabButton ? activeTabButton.getAttribute('data-tab') : 'system';
fetch(`/analytics/api/generate-report?type=${activeTab}&days=7`)
.then(response => response.json())
.then(data => {
if (data.error) {
alert('Error generating report: ' + data.error);
return;
}
// Create and download JSON report
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `plutus_${activeTab}_report_${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
})
.catch(error => {
console.error('Error generating report:', error);
alert('Error generating report');
});
}
</script>
{% endblock %}