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
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 %}
|