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.
 
 
 

838 lines
32 KiB

{% extends "base.html" %}
{% block title %}Analytics Dashboard - Plutus Payment System{% endblock %}
{% block head %}
<style>
.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 #3273dc;
padding: 12px;
margin-bottom: 10px;
border-radius: 4px;
}
.loading {
opacity: 0.6;
pointer-events: none;
}
/* Tab styling improvements */
.tabs ul {
border-bottom: 2px solid #dbdbdb;
}
.tabs li {
background-color: #f5f5f5;
border: 1px solid #dbdbdb;
border-bottom: none;
margin-right: 2px;
}
.tabs li:hover {
background-color: #e8e8e8;
}
.tabs li.is-active {
background-color: #ffffff;
border-color: #3273dc;
border-bottom: 2px solid #ffffff;
margin-bottom: -2px;
position: relative;
z-index: 1;
}
.tabs li a {
color: #4a4a4a;
font-weight: 500;
padding: 0.75em 1em;
border: none;
background: transparent;
}
.tabs li.is-active a {
color: #3273dc;
font-weight: 600;
}
.tabs li:hover a {
color: #363636;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
{% endblock %}
{% block content %}
<section class="section">
<div class="container">
<div class="level">
<div class="level-left">
<div class="level-item">
<h1 class="title">
<span class="icon">
<i class="fas fa-chart-line"></i>
</span>
Analytics Dashboard
</h1>
</div>
</div>
<div class="level-right">
<div class="level-item">
<div class="field is-grouped">
<p class="control">
<button class="button is-light" onclick="refreshDashboard()">
<span class="icon">
<i class="fas fa-sync-alt"></i>
</span>
<span>Refresh</span>
</button>
</p>
<p class="control">
<button class="button is-primary" onclick="exportReport()">
<span class="icon">
<i class="fas fa-download"></i>
</span>
<span>Export</span>
</button>
</p>
</div>
</div>
</div>
</div>
<!-- System Health Overview -->
<div class="columns">
<div class="column is-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="is-size-7 has-text-grey">Overall system performance</p>
</div>
</div>
<div class="column is-3">
<div class="metric-card">
<div id="paymentSuccessRate" class="metric-value has-text-success">--%</div>
<div class="metric-label">Payment Success Rate</div>
<p class="is-size-7 has-text-grey">Last 24 hours</p>
</div>
</div>
<div class="column is-3">
<div class="metric-card">
<div id="errorRate" class="metric-value has-text-warning">--%</div>
<div class="metric-label">Error Rate</div>
<p class="is-size-7 has-text-grey">System errors in logs</p>
</div>
</div>
<div class="column is-3">
<div class="metric-card">
<div id="totalPayments" class="metric-value has-text-info">--</div>
<div class="metric-label">Total Payments</div>
<p class="is-size-7 has-text-grey">Recent activity</p>
</div>
</div>
</div>
<!-- Tabs -->
<div class="tabs is-centered">
<ul>
<li class="is-active" data-tab="performance">
<a>
<span class="icon is-small"><i class="fas fa-tachometer-alt"></i></span>
<span>Performance</span>
</a>
</li>
<li data-tab="payments">
<a>
<span class="icon is-small"><i class="fas fa-credit-card"></i></span>
<span>Payments</span>
</a>
</li>
<li data-tab="security">
<a>
<span class="icon is-small"><i class="fas fa-shield-alt"></i></span>
<span>Security</span>
</a>
</li>
<li data-tab="logs">
<a>
<span class="icon is-small"><i class="fas fa-list-alt"></i></span>
<span>Logs</span>
</a>
</li>
</ul>
</div>
<!-- Tab Content -->
<div class="tab-content">
<!-- Performance Tab -->
<div id="performance-content" class="tab-pane is-active">
<div class="box">
<h4 class="title is-4">
<span class="icon">
<i class="fas fa-clock"></i>
</span>
System Performance
</h4>
<div id="performanceMetrics">
<div class="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>Loading performance metrics...</p>
</div>
</div>
</div>
</div>
<!-- Payments Tab -->
<div id="payments-content" class="tab-pane" style="display: none;">
<div class="box">
<h4 class="title is-4">
<span class="icon">
<i class="fas fa-chart-line"></i>
</span>
Payment Analytics
</h4>
<div id="paymentMetrics">
<div class="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>Loading payment analytics...</p>
</div>
</div>
</div>
</div>
<!-- Security Tab -->
<div id="security-content" class="tab-pane" style="display: none;">
<div class="columns">
<div class="column is-4">
<div class="metric-card">
<div id="securityEvents" class="metric-value has-text-success">--</div>
<div class="metric-label">Security Events</div>
<p class="is-size-7 has-text-grey">Last 7 days</p>
</div>
</div>
<div class="column is-4">
<div class="metric-card">
<div id="failedLogins" class="metric-value has-text-warning">--</div>
<div class="metric-label">Failed Logins</div>
<p class="is-size-7 has-text-grey">Authentication failures</p>
</div>
</div>
<div class="column is-4">
<div class="metric-card">
<div id="blockedRequests" class="metric-value has-text-danger">--</div>
<div class="metric-label">Blocked Requests</div>
<p class="is-size-7 has-text-grey">Suspicious activity</p>
</div>
</div>
</div>
<div class="box">
<h4 class="title is-4">
<span class="icon">
<i class="fas fa-shield-alt"></i>
</span>
Security Events
</h4>
<div id="securityEventsList">
<div class="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>Loading security events...</p>
</div>
</div>
</div>
</div>
<!-- Logs Tab -->
<div id="logs-content" class="tab-pane" style="display: none;">
<div class="box">
<div class="level">
<div class="level-left">
<div class="level-item">
<h4 class="title is-4">
<span class="icon">
<i class="fas fa-search"></i>
</span>
Log Search
</h4>
</div>
</div>
<div class="level-right">
<div class="level-item">
<div class="field has-addons">
<div class="control">
<input type="text" id="logSearch" class="input" placeholder="Search logs...">
</div>
<div class="control">
<div class="select">
<select id="logAction">
<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>
</div>
</div>
<div class="control">
<button class="button is-primary" onclick="searchLogs()">
<span class="icon">
<i class="fas fa-search"></i>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div id="logResults">
<div class="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>Loading recent logs...</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<script>
$(document).ready(function() {
// Initialize dashboard
refreshSystemHealth();
// Load the default active tab content (Performance)
loadTabContent('performance');
// Tab switching
$('.tabs li').click(function() {
var tab = $(this).data('tab');
// Update tab appearance
$('.tabs li').removeClass('is-active');
$(this).addClass('is-active');
// Show/hide content
$('.tab-pane').hide().removeClass('is-active');
$('#' + tab + '-content').show().addClass('is-active');
// Load tab content
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="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>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="notification is-info">
<h5 class="title is-5">
<span class="icon"><i class="fas fa-chart-line"></i></span>
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 class="content">';
// System overview
if (data.system_info) {
html += '<div class="columns">';
html += '<div class="column is-4">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Monitoring Status</p>';
html += `<p class="title ${data.system_info.monitoring_active ? 'has-text-success' : 'has-text-warning'}">`;
html += `<span class="icon"><i class="fas ${data.system_info.monitoring_active ? 'fa-check-circle' : 'fa-exclamation-triangle'}"></i></span>`;
html += `${data.system_info.monitoring_active ? 'Active' : 'Inactive'}`;
html += '</p>';
html += '</div></div>';
html += '<div class="column is-4">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Log Files</p>';
html += `<p class="title has-text-info">${data.system_info.log_files_found || 0}</p>`;
html += '</div></div>';
html += '<div class="column is-4">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Collection Period</p>';
html += `<p class="title has-text-grey">${data.system_info.data_collection_period || '7 days'}</p>`;
html += '</div></div>';
html += '</div>';
}
// Performance summary
if (data.summary) {
html += '<h5 class="title is-5">Performance Summary</h5>';
html += '<div class="columns">';
html += '<div class="column is-3">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Slow Requests</p>';
html += `<p class="title ${data.summary.slow_request_count > 0 ? 'has-text-warning' : 'has-text-success'}">${data.summary.slow_request_count || 0}</p>`;
html += '<p class="help">Requests > 1 second</p>';
html += '</div></div>';
html += '<div class="column is-3">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Slow Queries</p>';
html += `<p class="title ${data.summary.database_queries > 0 ? 'has-text-warning' : 'has-text-success'}">${data.summary.database_queries || 0}</p>`;
html += '<p class="help">DB queries > 100ms</p>';
html += '</div></div>';
html += '<div class="column is-3">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Total Requests</p>';
html += `<p class="title has-text-info">${data.summary.total_requests || 'N/A'}</p>`;
html += '<p class="help">Recent requests</p>';
html += '</div></div>';
html += '<div class="column is-3">';
html += '<div class="box has-text-centered">';
html += '<p class="heading">Avg Response</p>';
html += `<p class="title has-text-info">${data.summary.avg_response_time || 'N/A'}</p>`;
html += '<p class="help">Milliseconds</p>';
html += '</div></div>';
html += '</div>';
}
// Slow requests table
if (data.slow_requests && data.slow_requests.length > 0) {
html += '<h5 class="title is-5">Recent Slow Requests</h5>';
html += '<table class="table is-striped is-fullwidth">';
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="tag is-warning">${Math.round(req.duration_ms)}ms</span></td>`;
html += `<td><span class="tag ${req.status_code < 400 ? 'is-success' : 'is-danger'}">${req.status_code}</span></td>`;
html += '</tr>';
});
html += '</tbody></table>';
} else {
html += '<div class="notification is-success">';
html += '<span class="icon"><i class="fas fa-check"></i></span>';
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="title is-5">Recent Slow Database Queries</h5>';
html += '<table class="table is-striped is-fullwidth">';
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="tag is-warning">${Math.round(query.duration_ms)}ms</span></td>`;
html += '</tr>';
});
html += '</tbody></table>';
}
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="notification is-warning">
<h5 class="title is-5">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="button is-small is-primary" onclick="loadPerformanceMetrics()">Try Again</button>
<details style="margin-top: 10px;">
<summary>Debug Information</summary>
<pre>${error.stack || error.message}</pre>
</details>
</div>
`);
});
}
function loadPaymentAnalytics() {
$('#paymentMetrics').html(`
<div class="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>Loading payment analytics...</p>
</div>
`);
fetch('/analytics/api/payment-analytics')
.then(response => response.json())
.then(data => {
if (data.error) {
$('#paymentMetrics').html(`
<div class="notification is-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 class="content">';
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="notification is-info">
<p>Payment analytics will be available after payment processing activity.</p>
</div>
`);
});
}
function loadSecurityEvents() {
$('#securityEventsList').html(`
<div class="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>Loading security events...</p>
</div>
`);
fetch('/analytics/api/security-events')
.then(response => response.json())
.then(data => {
if (data.error) {
$('#securityEventsList').html(`
<div class="notification is-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="notification is-success">
<span class="icon">
<i class="fas fa-shield-alt"></i>
</span>
<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="notification is-success">
<span class="icon">
<i class="fas fa-shield-alt"></i>
</span>
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="has-text-centered">
<span class="icon is-large">
<i class="fas fa-spinner fa-pulse"></i>
</span>
<p>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="notification is-warning">
<p><strong>Log search error:</strong> ${data.error}</p>
</div>
`);
return;
}
if (!data.logs || data.logs.length === 0) {
$('#logResults').html(`
<div class="notification is-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') ? 'has-background-danger-light' :
log.action.includes('WARNING') ? 'has-background-warning-light' :
log.action.includes('SUCCESS') ? 'has-background-success-light' : '';
html += `<div class="log-entry ${logClass}">
<div class="level">
<div class="level-left">
<div class="level-item">
<strong>${log.action}</strong>
</div>
</div>
<div class="level-right">
<div class="level-item">
<small class="has-text-grey">${new Date(log.timestamp).toLocaleString()}</small>
</div>
</div>
</div>
<div>${log.message || 'No message'}</div>
<small class="has-text-grey">
${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="notification is-warning">
<p>Error searching logs. Please try again.</p>
</div>
`);
});
}
function refreshDashboard() {
document.body.classList.add('loading');
refreshSystemHealth();
// Refresh current tab content
const activeTab = $('.tabs li.is-active').data('tab');
if (activeTab) {
loadTabContent(activeTab);
}
setTimeout(() => {
document.body.classList.remove('loading');
}, 1000);
}
function exportReport() {
const activeTab = $('.tabs li.is-active').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 %}