- Added GitHub spec-kit for development workflow - Standardized file signatures to Descomplicar® format - Updated development configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
710 lines
29 KiB
PHP
710 lines
29 KiB
PHP
/**
|
|
* Descomplicar® Crescimento Digital
|
|
* https://descomplicar.pt
|
|
*/
|
|
|
|
<?php defined('BASEPATH') or exit('No direct script access allowed'); ?>
|
|
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h4 class="no-margin">
|
|
<i class="fa-regular fa-chart-bar" aria-hidden="true"></i>
|
|
<?php echo _l('desk_moloni_dashboard'); ?>
|
|
</h4>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="text-right">
|
|
<div class="btn-group">
|
|
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
|
<i class="fa fa-filter"></i> <?php echo _l('filter'); ?> <span class="caret"></span>
|
|
</button>
|
|
<ul class="dropdown-menu dropdown-menu-right">
|
|
<li><a href="#" data-filter="7"><?php echo _l('desk_moloni_last_7_days'); ?></a></li>
|
|
<li><a href="#" data-filter="30"><?php echo _l('desk_moloni_last_30_days'); ?></a></li>
|
|
<li><a href="#" data-filter="90"><?php echo _l('desk_moloni_last_90_days'); ?></a></li>
|
|
</ul>
|
|
</div>
|
|
<button type="button" class="btn btn-info" id="refresh-dashboard">
|
|
<i class="fa fa-refresh"></i> <?php echo _l('refresh'); ?>
|
|
</button>
|
|
<?php if (has_permission('desk_moloni_config', '', 'edit')) { ?>
|
|
<a href="<?php echo admin_url('modules/desk_moloni/config'); ?>" class="btn btn-primary">
|
|
<i class="fa fa-cog"></i> <?php echo _l('settings'); ?>
|
|
</a>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<hr class="hr-panel-separator" />
|
|
|
|
<!-- Status Cards -->
|
|
<div class="row" id="status-cards">
|
|
<div class="col-lg-3 col-md-6">
|
|
<div class="panel panel-info">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-xs-3">
|
|
<i class="fa fa-check-circle fa-3x"></i>
|
|
</div>
|
|
<div class="col-xs-9 text-right">
|
|
<div class="huge" id="successful-syncs">-</div>
|
|
<div><?php echo _l('desk_moloni_successful_syncs'); ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel-footer">
|
|
<span class="pull-left" id="success-rate">-</span>
|
|
<span class="pull-right">
|
|
<i class="fa fa-arrow-circle-right"></i>
|
|
</span>
|
|
<div class="clearfix"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-md-6">
|
|
<div class="panel panel-warning">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-xs-3">
|
|
<i class="fa fa-clock-o fa-3x"></i>
|
|
</div>
|
|
<div class="col-xs-9 text-right">
|
|
<div class="huge" id="pending-tasks">-</div>
|
|
<div><?php echo _l('desk_moloni_pending_tasks'); ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel-footer">
|
|
<span class="pull-left"><?php echo _l('desk_moloni_in_queue'); ?></span>
|
|
<span class="pull-right">
|
|
<i class="fa fa-arrow-circle-right"></i>
|
|
</span>
|
|
<div class="clearfix"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-md-6">
|
|
<div class="panel panel-danger">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-xs-3">
|
|
<i class="fa fa-exclamation-triangle fa-3x"></i>
|
|
</div>
|
|
<div class="col-xs-9 text-right">
|
|
<div class="huge" id="failed-syncs">-</div>
|
|
<div><?php echo _l('desk_moloni_failed_syncs'); ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel-footer">
|
|
<span class="pull-left" id="error-rate">-</span>
|
|
<span class="pull-right">
|
|
<i class="fa fa-arrow-circle-right"></i>
|
|
</span>
|
|
<div class="clearfix"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-3 col-md-6">
|
|
<div class="panel panel-success">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-xs-3">
|
|
<i class="fa fa-tachometer fa-3x"></i>
|
|
</div>
|
|
<div class="col-xs-9 text-right">
|
|
<div class="huge" id="sync-rate">-</div>
|
|
<div><?php echo _l('desk_moloni_sync_rate_24h'); ?></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="panel-footer">
|
|
<span class="pull-left"><?php echo _l('desk_moloni_per_hour'); ?></span>
|
|
<span class="pull-right">
|
|
<i class="fa fa-arrow-circle-right"></i>
|
|
</span>
|
|
<div class="clearfix"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Charts Row -->
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<h4><?php echo _l('desk_moloni_sync_volume_chart'); ?></h4>
|
|
<canvas id="syncVolumeChart" height="100"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<h4><?php echo _l('desk_moloni_entity_distribution'); ?></h4>
|
|
<canvas id="entityDistributionChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Second Row -->
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<h4><?php echo _l('desk_moloni_success_rate_trend'); ?></h4>
|
|
<canvas id="successRateChart" height="150"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<h4><?php echo _l('desk_moloni_performance_metrics'); ?></h4>
|
|
<canvas id="performanceChart" height="150"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Activity Feed and Errors -->
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h4><?php echo _l('desk_moloni_recent_activity'); ?></h4>
|
|
</div>
|
|
<div class="col-md-4 text-right">
|
|
<a href="<?php echo admin_url('modules/desk_moloni/logs'); ?>" class="btn btn-default btn-sm">
|
|
<?php echo _l('desk_moloni_view_all_logs'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" id="recent-activity-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php echo _l('desk_moloni_timestamp'); ?></th>
|
|
<th><?php echo _l('desk_moloni_entity'); ?></th>
|
|
<th><?php echo _l('desk_moloni_operation'); ?></th>
|
|
<th><?php echo _l('desk_moloni_status'); ?></th>
|
|
<th><?php echo _l('desk_moloni_duration'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td colspan="5" class="text-center">
|
|
<i class="fa fa-spinner fa-spin"></i> <?php echo _l('loading'); ?>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<div class="row">
|
|
<div class="col-md-8">
|
|
<h4><?php echo _l('desk_moloni_recent_errors'); ?></h4>
|
|
</div>
|
|
<div class="col-md-4 text-right">
|
|
<a href="<?php echo admin_url('modules/desk_moloni/logs?status=error'); ?>" class="btn btn-default btn-sm">
|
|
<?php echo _l('desk_moloni_view_all'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
<div id="recent-errors-container">
|
|
<div class="text-center">
|
|
<i class="fa fa-spinner fa-spin"></i> <?php echo _l('loading'); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Configuration Status -->
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="panel_s">
|
|
<div class="panel-body">
|
|
<h4><?php echo _l('desk_moloni_system_status'); ?></h4>
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<div id="oauth-status" class="status-indicator">
|
|
<i class="fa fa-question-circle fa-2x text-muted"></i>
|
|
<p><?php echo _l('desk_moloni_oauth_status'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<div id="sync-status" class="status-indicator">
|
|
<i class="fa fa-question-circle fa-2x text-muted"></i>
|
|
<p><?php echo _l('desk_moloni_sync_status'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<div id="queue-status" class="status-indicator">
|
|
<i class="fa fa-question-circle fa-2x text-muted"></i>
|
|
<p><?php echo _l('desk_moloni_queue_status'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<div id="api-status" class="status-indicator">
|
|
<i class="fa fa-question-circle fa-2x text-muted"></i>
|
|
<p><?php echo _l('desk_moloni_api_status'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Modal -->
|
|
<div class="modal fade" id="loading-modal" tabindex="-1" role="dialog" aria-labelledby="loadingModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-sm" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-body text-center">
|
|
<i class="fa fa-spinner fa-spin fa-3x"></i>
|
|
<p class="mt-3"><?php echo _l('desk_moloni_loading'); ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Global dashboard variables
|
|
var dashboardData = {};
|
|
var charts = {};
|
|
var currentFilter = 7;
|
|
var refreshInterval;
|
|
|
|
// Initialize dashboard
|
|
initializeDashboard();
|
|
|
|
// Event handlers
|
|
$('#refresh-dashboard').on('click', function() {
|
|
refreshDashboard();
|
|
});
|
|
|
|
$('[data-filter]').on('click', function(e) {
|
|
e.preventDefault();
|
|
currentFilter = $(this).data('filter');
|
|
refreshDashboard();
|
|
});
|
|
|
|
function initializeDashboard() {
|
|
showLoadingModal();
|
|
loadDashboardData();
|
|
startAutoRefresh();
|
|
}
|
|
|
|
function loadDashboardData() {
|
|
$.ajax({
|
|
url: admin_url + 'modules/desk_moloni/get_status',
|
|
type: 'GET',
|
|
data: {
|
|
days: currentFilter
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
dashboardData = response.data;
|
|
updateStatusCards();
|
|
updateCharts();
|
|
updateActivity();
|
|
updateSystemStatus();
|
|
} else {
|
|
showError(response.message);
|
|
}
|
|
hideLoadingModal();
|
|
},
|
|
error: function(xhr, status, error) {
|
|
showError('<?php echo _l("desk_moloni_dashboard_load_error"); ?>');
|
|
hideLoadingModal();
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshDashboard() {
|
|
loadDashboardData();
|
|
}
|
|
|
|
function updateStatusCards() {
|
|
var stats = dashboardData.sync_stats;
|
|
|
|
$('#successful-syncs').text(numberFormat(stats.successful_syncs || 0));
|
|
$('#pending-tasks').text(numberFormat(stats.pending_tasks || 0));
|
|
$('#failed-syncs').text(numberFormat(stats.failed_syncs || 0));
|
|
$('#sync-rate').text(numberFormat(stats.sync_rate_24h || 0));
|
|
|
|
var successRate = stats.total_synced > 0 ?
|
|
((stats.successful_syncs / stats.total_synced) * 100).toFixed(1) + '%' : '0%';
|
|
$('#success-rate').text('<?php echo _l("desk_moloni_success_rate"); ?>: ' + successRate);
|
|
|
|
var errorRate = stats.total_synced > 0 ?
|
|
((stats.failed_syncs / stats.total_synced) * 100).toFixed(1) + '%' : '0%';
|
|
$('#error-rate').text('<?php echo _l("desk_moloni_error_rate"); ?>: ' + errorRate);
|
|
}
|
|
|
|
function updateCharts() {
|
|
// Load chart data and update charts
|
|
$.ajax({
|
|
url: admin_url + 'modules/desk_moloni/dashboard/get_analytics',
|
|
type: 'GET',
|
|
data: {
|
|
days: currentFilter
|
|
},
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
if (response.success) {
|
|
updateSyncVolumeChart(response.data.charts.sync_volume_chart);
|
|
updateEntityDistributionChart(response.data.charts.entity_distribution);
|
|
updateSuccessRateChart(response.data.charts.success_rate_chart);
|
|
updatePerformanceChart(response.data.charts.performance_chart);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateSyncVolumeChart(data) {
|
|
var ctx = document.getElementById('syncVolumeChart').getContext('2d');
|
|
|
|
if (charts.syncVolume) {
|
|
charts.syncVolume.destroy();
|
|
}
|
|
|
|
charts.syncVolume = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
label: '<?php echo _l("desk_moloni_successful_syncs"); ?>',
|
|
data: data.successful || [],
|
|
borderColor: 'rgb(75, 192, 192)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
|
tension: 0.1
|
|
}, {
|
|
label: '<?php echo _l("desk_moloni_failed_syncs"); ?>',
|
|
data: data.failed || [],
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
tension: 0.1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateEntityDistributionChart(data) {
|
|
var ctx = document.getElementById('entityDistributionChart').getContext('2d');
|
|
|
|
if (charts.entityDistribution) {
|
|
charts.entityDistribution.destroy();
|
|
}
|
|
|
|
charts.entityDistribution = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.values || [],
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.8)',
|
|
'rgba(54, 162, 235, 0.8)',
|
|
'rgba(255, 205, 86, 0.8)',
|
|
'rgba(75, 192, 192, 0.8)',
|
|
'rgba(153, 102, 255, 0.8)'
|
|
]
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: true,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateSuccessRateChart(data) {
|
|
var ctx = document.getElementById('successRateChart').getContext('2d');
|
|
|
|
if (charts.successRate) {
|
|
charts.successRate.destroy();
|
|
}
|
|
|
|
charts.successRate = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
label: '<?php echo _l("desk_moloni_success_rate"); ?> (%)',
|
|
data: data.values || [],
|
|
borderColor: 'rgb(75, 192, 192)',
|
|
backgroundColor: 'rgba(75, 192, 192, 0.1)',
|
|
tension: 0.1,
|
|
fill: true
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
max: 100,
|
|
ticks: {
|
|
callback: function(value) {
|
|
return value + '%';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updatePerformanceChart(data) {
|
|
var ctx = document.getElementById('performanceChart').getContext('2d');
|
|
|
|
if (charts.performance) {
|
|
charts.performance.destroy();
|
|
}
|
|
|
|
charts.performance = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
label: '<?php echo _l("desk_moloni_avg_execution_time"); ?> (ms)',
|
|
data: data.values || [],
|
|
backgroundColor: 'rgba(153, 102, 255, 0.8)',
|
|
borderColor: 'rgba(153, 102, 255, 1)',
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateActivity() {
|
|
var tbody = $('#recent-activity-table tbody');
|
|
tbody.empty();
|
|
|
|
if (dashboardData.recent_activity && dashboardData.recent_activity.length > 0) {
|
|
$.each(dashboardData.recent_activity.slice(0, 10), function(index, activity) {
|
|
var statusClass = getStatusClass(activity.status);
|
|
var statusIcon = getStatusIcon(activity.status);
|
|
|
|
var row = '<tr>' +
|
|
'<td>' + formatDateTime(activity.created_at) + '</td>' +
|
|
'<td>' + activity.entity_type + ' #' + activity.entity_id + '</td>' +
|
|
'<td>' + activity.operation_type + '</td>' +
|
|
'<td><span class="label label-' + statusClass + '"><i class="fa ' + statusIcon + '"></i> ' + activity.status + '</span></td>' +
|
|
'<td>' + (activity.execution_time_ms || 0) + 'ms</td>' +
|
|
'</tr>';
|
|
|
|
tbody.append(row);
|
|
});
|
|
} else {
|
|
tbody.append('<tr><td colspan="5" class="text-center"><?php echo _l("desk_moloni_no_recent_activity"); ?></td></tr>');
|
|
}
|
|
}
|
|
|
|
function updateSystemStatus() {
|
|
// OAuth Status
|
|
var oauthIcon = dashboardData.oauth_configured ?
|
|
'<i class="fa fa-check-circle fa-2x text-success"></i>' :
|
|
'<i class="fa fa-exclamation-triangle fa-2x text-warning"></i>';
|
|
$('#oauth-status').html(oauthIcon + '<p><?php echo _l("desk_moloni_oauth_status"); ?></p>');
|
|
|
|
// Sync Status
|
|
var syncIcon = dashboardData.sync_enabled ?
|
|
'<i class="fa fa-play-circle fa-2x text-success"></i>' :
|
|
'<i class="fa fa-pause-circle fa-2x text-warning"></i>';
|
|
$('#sync-status').html(syncIcon + '<p><?php echo _l("desk_moloni_sync_status"); ?></p>');
|
|
|
|
// Queue Status
|
|
var queueIcon = dashboardData.queue_status.processing_tasks > 0 ?
|
|
'<i class="fa fa-cog fa-spin fa-2x text-info"></i>' :
|
|
'<i class="fa fa-check-circle fa-2x text-success"></i>';
|
|
$('#queue-status').html(queueIcon + '<p><?php echo _l("desk_moloni_queue_status"); ?></p>');
|
|
|
|
// API Status (will be updated via separate call)
|
|
updateApiStatus();
|
|
}
|
|
|
|
function updateApiStatus() {
|
|
$.ajax({
|
|
url: admin_url + 'modules/desk_moloni/test_connection',
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
success: function(response) {
|
|
var apiIcon = response.success ?
|
|
'<i class="fa fa-check-circle fa-2x text-success"></i>' :
|
|
'<i class="fa fa-exclamation-triangle fa-2x text-danger"></i>';
|
|
$('#api-status').html(apiIcon + '<p><?php echo _l("desk_moloni_api_status"); ?></p>');
|
|
},
|
|
error: function() {
|
|
$('#api-status').html('<i class="fa fa-exclamation-triangle fa-2x text-danger"></i><p><?php echo _l("desk_moloni_api_status"); ?></p>');
|
|
}
|
|
});
|
|
}
|
|
|
|
function startAutoRefresh() {
|
|
refreshInterval = setInterval(function() {
|
|
loadDashboardData();
|
|
}, 30000); // Refresh every 30 seconds
|
|
}
|
|
|
|
function stopAutoRefresh() {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
}
|
|
|
|
function showLoadingModal() {
|
|
$('#loading-modal').modal('show');
|
|
}
|
|
|
|
function hideLoadingModal() {
|
|
$('#loading-modal').modal('hide');
|
|
}
|
|
|
|
function showError(message) {
|
|
alert_float('danger', message);
|
|
}
|
|
|
|
function numberFormat(num) {
|
|
return new Intl.NumberFormat().format(num);
|
|
}
|
|
|
|
function formatDateTime(dateString) {
|
|
var date = new Date(dateString);
|
|
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
|
}
|
|
|
|
function getStatusClass(status) {
|
|
switch (status) {
|
|
case 'success': return 'success';
|
|
case 'error': return 'danger';
|
|
case 'warning': return 'warning';
|
|
default: return 'default';
|
|
}
|
|
}
|
|
|
|
function getStatusIcon(status) {
|
|
switch (status) {
|
|
case 'success': return 'fa-check';
|
|
case 'error': return 'fa-exclamation-triangle';
|
|
case 'warning': return 'fa-exclamation-circle';
|
|
default: return 'fa-info-circle';
|
|
}
|
|
}
|
|
|
|
// Cleanup on page unload
|
|
$(window).on('beforeunload', function() {
|
|
stopAutoRefresh();
|
|
// Destroy all charts
|
|
Object.keys(charts).forEach(function(key) {
|
|
if (charts[key]) {
|
|
charts[key].destroy();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.status-indicator {
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
background-color: #f8f9fa;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.status-indicator i {
|
|
display: block;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.status-indicator p {
|
|
margin: 0;
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.huge {
|
|
font-size: 40px;
|
|
}
|
|
|
|
.panel-footer {
|
|
background-color: transparent;
|
|
border-top: 1px solid #e7e7e7;
|
|
border-bottom-right-radius: 3px;
|
|
border-bottom-left-radius: 3px;
|
|
}
|
|
|
|
#recent-activity-table .label {
|
|
font-size: 11px;
|
|
}
|
|
|
|
.dashboard-loading {
|
|
text-align: center;
|
|
padding: 50px;
|
|
color: #999;
|
|
}
|
|
|
|
.dashboard-loading i {
|
|
font-size: 24px;
|
|
margin-bottom: 10px;
|
|
}
|
|
</style>
|