e67fbdfe2b
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Authored-by: Moko Consulting
294 lines
15 KiB
PHP
294 lines
15 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package MokoSuiteCross
|
|
* @subpackage com_mokosuitecross
|
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Component\ComponentHelper;
|
|
use Joomla\CMS\Language\Text;
|
|
use Joomla\CMS\Router\Route;
|
|
use Joomla\Component\MokoSuiteCross\Administrator\Helper\ServiceIconHelper;
|
|
|
|
/** @var \Joomla\Component\MokoSuiteCross\Administrator\View\Dashboard\HtmlView $this */
|
|
$stats = $this->stats;
|
|
$componentParams = ComponentHelper::getParams('com_mokosuitecross');
|
|
$queueProcessing = $componentParams->get('queue_processing', 'scheduler');
|
|
?>
|
|
<?php if ($queueProcessing === 'pageload' || $queueProcessing === 'both') : ?>
|
|
<div class="alert alert-warning d-flex align-items-start mb-3">
|
|
<span class="icon-exclamation-triangle me-2 mt-1" aria-hidden="true"></span>
|
|
<div>
|
|
<strong><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_PAGELOAD_WARNING_TITLE'); ?></strong><br>
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_PAGELOAD_WARNING'); ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($stats->queued_count > 50) : ?>
|
|
<div class="alert alert-warning d-flex align-items-start mb-3">
|
|
<span class="icon-exclamation-triangle me-2 mt-1" aria-hidden="true"></span>
|
|
<div>
|
|
<strong><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING_TITLE'); ?></strong><br>
|
|
<?php echo Text::sprintf('COM_MOKOSUITECROSS_DASHBOARD_QUEUE_DEPTH_WARNING', $stats->queued_count); ?>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="row">
|
|
<div class="col-lg-9">
|
|
<div class="row">
|
|
<div class="col-sm-6 col-md-3">
|
|
<div class="card text-center mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_ACTIVE_SERVICES'); ?></h5>
|
|
<p class="display-4"><?php echo $stats->active_services; ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<div class="card text-center mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUEUED'); ?></h5>
|
|
<p class="display-4 text-warning"><?php echo $stats->queued_count; ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<div class="card text-center mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_POSTED'); ?></h5>
|
|
<p class="display-4 text-success"><?php echo $stats->posted_count; ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-md-3">
|
|
<div class="card text-center mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_FAILED'); ?></h5>
|
|
<p class="display-4 text-danger"><?php echo $stats->failed_count; ?></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trend Chart -->
|
|
<?php if (!empty($this->dailyTrend)) : ?>
|
|
<div class="card mb-3">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_TREND_CHART'); ?></h5>
|
|
<form method="get" class="d-inline">
|
|
<input type="hidden" name="option" value="com_mokosuitecross" />
|
|
<input type="hidden" name="view" value="dashboard" />
|
|
<select name="period" class="form-select form-select-sm" style="width: auto; display: inline-block;" onchange="this.form.submit();">
|
|
<option value="7" <?php echo $this->period == 7 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_7_DAYS'); ?></option>
|
|
<option value="30" <?php echo $this->period == 30 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_30_DAYS'); ?></option>
|
|
<option value="90" <?php echo $this->period == 90 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_90_DAYS'); ?></option>
|
|
<option value="0" <?php echo $this->period == 0 ? 'selected' : ''; ?>><?php echo Text::_('COM_MOKOSUITECROSS_PERIOD_ALL_TIME'); ?></option>
|
|
</select>
|
|
</form>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="trendChart" height="80"></canvas>
|
|
</div>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js" integrity="sha384-UPIssOjNMqMfON6mDKHvO4sOY4hhxN1ymYcfl2MrDz69idMU/L3MNFlyJGlIRjQH" crossorigin="anonymous"></script>
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var trendData = <?php echo json_encode($this->dailyTrend); ?>;
|
|
var labels = trendData.map(function(d) { return d.day; });
|
|
var posted = trendData.map(function(d) { return parseInt(d.posted, 10); });
|
|
var failed = trendData.map(function(d) { return parseInt(d.failed, 10); });
|
|
|
|
new Chart(document.getElementById('trendChart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: '<?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_POSTED', true); ?>',
|
|
data: posted,
|
|
borderColor: '#198754',
|
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
|
fill: true,
|
|
tension: 0.3
|
|
},
|
|
{
|
|
label: '<?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_FAILED', true); ?>',
|
|
data: failed,
|
|
borderColor: '#dc3545',
|
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
|
fill: true,
|
|
tension: 0.3
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: { beginAtZero: true, ticks: { stepSize: 1 } }
|
|
},
|
|
plugins: {
|
|
legend: { position: 'bottom' }
|
|
}
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($this->migrationAvailable) : ?>
|
|
<div class="alert alert-info">
|
|
<h4 class="alert-heading"><?php echo Text::_('COM_MOKOSUITECROSS_MIGRATION_TITLE'); ?></h4>
|
|
<p><?php echo Text::_('COM_MOKOSUITECROSS_MIGRATION_DESCRIPTION'); ?></p>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&task=dashboard.migrate&' . \Joomla\CMS\Session\Session::getFormToken() . '=1'); ?>"
|
|
class="btn btn-primary">
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_MIGRATION_BUTTON'); ?>
|
|
</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Analytics: Service Breakdown -->
|
|
<?php if (!empty($this->serviceBreakdown)) : ?>
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_SERVICE_BREAKDOWN'); ?></h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-sm table-striped mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th><?php echo Text::_('COM_MOKOSUITECROSS_HEADING_SERVICE'); ?></th>
|
|
<th class="text-center text-success"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_POSTED'); ?></th>
|
|
<th class="text-center text-danger"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_FAILED'); ?></th>
|
|
<th class="text-center text-warning"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUEUED'); ?></th>
|
|
<th class="text-center"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_TOTAL_POSTS'); ?></th>
|
|
<th class="text-center"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_SUCCESS_RATE'); ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($this->serviceBreakdown as $row) :
|
|
$rate = $row['total'] > 0 ? round(($row['posted'] / $row['total']) * 100) : 0;
|
|
$rateClass = $rate >= 80 ? 'text-success' : ($rate >= 50 ? 'text-warning' : 'text-danger');
|
|
?>
|
|
<tr>
|
|
<td>
|
|
<?php echo ServiceIconHelper::renderIcon($row['service_type']); ?>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=servicestats&id=' . $row['service_id']); ?>">
|
|
<?php echo htmlspecialchars($row['service_title'] . ' (' . ucfirst($row['service_type']) . ')'); ?>
|
|
</a>
|
|
</td>
|
|
<td class="text-center"><span class="badge bg-success"><?php echo (int) $row['posted']; ?></span></td>
|
|
<td class="text-center"><span class="badge bg-danger"><?php echo (int) $row['failed']; ?></span></td>
|
|
<td class="text-center"><span class="badge bg-warning text-dark"><?php echo (int) $row['queued']; ?></span></td>
|
|
<td class="text-center"><?php echo (int) $row['total']; ?></td>
|
|
<td class="text-center <?php echo $rateClass; ?> fw-bold"><?php echo $rate; ?>%</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Analytics: Top Articles -->
|
|
<?php if (!empty($this->topArticles)) : ?>
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_TOP_ARTICLES'); ?></h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="list-group list-group-flush">
|
|
<?php foreach ($this->topArticles as $row) : ?>
|
|
<div class="list-group-item d-flex justify-content-between align-items-center">
|
|
<span><?php echo htmlspecialchars($row['title']); ?></span>
|
|
<span>
|
|
<span class="badge bg-success"><?php echo (int) $row['success_count']; ?></span>
|
|
/
|
|
<span class="badge bg-secondary"><?php echo (int) $row['post_count']; ?></span>
|
|
</span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="card mt-3">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_RECENT_ACTIVITY'); ?></h5>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<?php if (empty($this->recentActivity)) : ?>
|
|
<p class="p-3 mb-0 text-muted"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_NO_RECENT'); ?></p>
|
|
<?php else : ?>
|
|
<div class="list-group list-group-flush">
|
|
<?php foreach ($this->recentActivity as $entry) :
|
|
$levelClass = match ($entry->level) {
|
|
'error' => 'text-danger',
|
|
'warning' => 'text-warning',
|
|
default => 'text-muted',
|
|
};
|
|
$levelIcon = match ($entry->level) {
|
|
'error' => 'icon-times-circle',
|
|
'warning' => 'icon-exclamation-triangle',
|
|
default => 'icon-info-circle',
|
|
};
|
|
?>
|
|
<div class="list-group-item">
|
|
<div class="d-flex w-100 justify-content-between">
|
|
<span class="<?php echo $levelClass; ?>">
|
|
<span class="<?php echo $levelIcon; ?>" aria-hidden="true"></span>
|
|
<?php echo htmlspecialchars(mb_substr($entry->message, 0, 120)); ?>
|
|
</span>
|
|
<small class="text-muted"><?php echo \Joomla\CMS\HTML\HTMLHelper::_('date', $entry->created, 'Y-m-d H:i'); ?></small>
|
|
</div>
|
|
<?php if ($entry->service_title) : ?>
|
|
<small class="text-muted"><?php echo htmlspecialchars($entry->service_title); ?></small>
|
|
<?php endif; ?>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-lg-3">
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<h5 class="card-title"><?php echo Text::_('COM_MOKOSUITECROSS_DASHBOARD_QUICK_LINKS'); ?></h5>
|
|
<div class="list-group list-group-flush">
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=services'); ?>"
|
|
class="list-group-item list-group-item-action">
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_SERVICES'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=posts'); ?>"
|
|
class="list-group-item list-group-item-action">
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_POSTS'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=templates'); ?>"
|
|
class="list-group-item list-group-item-action">
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_TEMPLATES'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=logs'); ?>"
|
|
class="list-group-item list-group-item-action">
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_SUBMENU_LOGS'); ?>
|
|
</a>
|
|
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=calendar'); ?>"
|
|
class="list-group-item list-group-item-action">
|
|
<?php echo Text::_('COM_MOKOSUITECROSS_CALENDAR'); ?>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|