feat: enhanced cpanel module with stats, disk, IP, and quick actions (#117)
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Update Server / Update Server (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Update Server / Update Server (push) Has been cancelled
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
- Article count, user count, pending updates with badges - Disk usage progress bar with color coding - Current IP display - Clear Cache button with AJAX spinner - Check Updates + update count button - Module defaults to ordering=-1 (top) and access=Super Users - Added padding to module container Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,9 @@ class Dispatcher extends AbstractModuleDispatcher implements HelperFactoryAwareI
|
||||
$data['siteInfo'] = $helper->getSiteInfo($db);
|
||||
$data['plugins'] = $helper->getFeaturePlugins($db);
|
||||
$data['healthOk'] = $helper->isDatabaseOk($db);
|
||||
$data['counts'] = $helper->getCounts($db);
|
||||
$data['disk'] = $helper->getDiskInfo();
|
||||
$data['currentIp'] = $helper->getCurrentIp();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -79,4 +79,60 @@ class CpanelHelper
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content and system counts.
|
||||
*/
|
||||
public function getCounts(DatabaseInterface $db): object
|
||||
{
|
||||
$counts = (object) [
|
||||
'articles' => 0,
|
||||
'users' => 0,
|
||||
'extensions' => 0,
|
||||
'updates' => 0,
|
||||
];
|
||||
|
||||
try
|
||||
{
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__content')));
|
||||
$counts->articles = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__users')));
|
||||
$counts->users = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__extensions'))->where($db->quoteName('enabled') . ' = 1'));
|
||||
$counts->extensions = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__updates'))->where($db->quoteName('extension_id') . ' != 0'));
|
||||
$counts->updates = (int) $db->loadResult();
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Silent
|
||||
}
|
||||
|
||||
return $counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get disk usage info.
|
||||
*/
|
||||
public function getDiskInfo(): object
|
||||
{
|
||||
$free = @disk_free_space(JPATH_ROOT);
|
||||
$total = @disk_total_space(JPATH_ROOT);
|
||||
|
||||
return (object) [
|
||||
'free_mb' => $free !== false ? round($free / 1048576) : null,
|
||||
'total_mb' => $total !== false ? round($total / 1048576) : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current visitor's IP address.
|
||||
*/
|
||||
public function getCurrentIp(): string
|
||||
{
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,20 @@ defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Session\Session;
|
||||
|
||||
$siteInfo = $siteInfo ?? (object) [];
|
||||
$plugins = $plugins ?? [];
|
||||
$healthOk = $healthOk ?? true;
|
||||
$showHealth = $params->get('show_health', 1);
|
||||
$siteInfo = $siteInfo ?? (object) [];
|
||||
$plugins = $plugins ?? [];
|
||||
$healthOk = $healthOk ?? true;
|
||||
$counts = $counts ?? (object) ['articles' => 0, 'users' => 0, 'extensions' => 0, 'updates' => 0];
|
||||
$disk = $disk ?? (object) ['free_mb' => null, 'total_mb' => null];
|
||||
$currentIp = $currentIp ?? '';
|
||||
$showHealth = $params->get('show_health', 1);
|
||||
$showPlugins = $params->get('show_plugins', 1);
|
||||
$token = Session::getFormToken();
|
||||
|
||||
$enabledCount = 0;
|
||||
$totalCount = count($plugins);
|
||||
$enabledCount = 0;
|
||||
$totalCount = count($plugins);
|
||||
|
||||
foreach ($plugins as $p)
|
||||
{
|
||||
@@ -28,7 +33,6 @@ foreach ($plugins as $p)
|
||||
}
|
||||
}
|
||||
|
||||
// Label map for plugin elements
|
||||
$labels = [
|
||||
'mokowaas' => 'Core',
|
||||
'mokowaas_firewall' => 'Firewall',
|
||||
@@ -36,58 +40,159 @@ $labels = [
|
||||
'mokowaas_devtools' => 'DevTools',
|
||||
'mokowaas_monitor' => 'Monitor',
|
||||
];
|
||||
|
||||
$diskPct = ($disk->total_mb && $disk->total_mb > 0)
|
||||
? round((($disk->total_mb - ($disk->free_mb ?? 0)) / $disk->total_mb) * 100)
|
||||
: null;
|
||||
$diskColor = ($diskPct !== null && $diskPct > 90) ? 'bg-danger' : (($diskPct !== null && $diskPct > 75) ? 'bg-warning' : 'bg-success');
|
||||
?>
|
||||
|
||||
<div class="mod-mokowaas-cpanel p-3">
|
||||
<!-- Header with version -->
|
||||
<!-- Header row -->
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="icon-shield-alt" aria-hidden="true" style="font-size:1.25rem;color:#1a2744"></span>
|
||||
<strong>MokoWaaS</strong>
|
||||
<span class="badge bg-primary"><?php echo htmlspecialchars($siteInfo->mokowaas_version ?? ''); ?></span>
|
||||
<?php if (!empty($siteInfo->debug)): ?>
|
||||
<span class="badge bg-warning text-dark"><?php echo Text::_('MOD_MOKOWAAS_CPANEL_DEBUG'); ?></span>
|
||||
<span class="badge bg-warning text-dark">Debug</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($siteInfo->offline)): ?>
|
||||
<span class="badge bg-danger"><?php echo Text::_('MOD_MOKOWAAS_CPANEL_OFFLINE'); ?></span>
|
||||
<span class="badge bg-danger">Offline</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokowaas'); ?>" class="btn btn-sm btn-outline-primary">
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokowaas'); ?>" class="btn btn-sm btn-primary">
|
||||
<span class="icon-cogs" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_MOKOWAAS_CPANEL_OPEN_DASHBOARD'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if ($showHealth): ?>
|
||||
<!-- Health indicator -->
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<?php if ($healthOk): ?>
|
||||
<span class="icon-check-circle text-success" aria-hidden="true" style="font-size:1.25rem"></span>
|
||||
<span class="text-success fw-bold"><?php echo Text::_('MOD_MOKOWAAS_CPANEL_HEALTH_OK'); ?></span>
|
||||
<?php else: ?>
|
||||
<span class="icon-exclamation-circle text-danger" aria-hidden="true" style="font-size:1.25rem"></span>
|
||||
<span class="text-danger fw-bold"><?php echo Text::_('MOD_MOKOWAAS_CPANEL_HEALTH_ERROR'); ?></span>
|
||||
<!-- Health + stats row -->
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="border rounded p-2 text-center h-100">
|
||||
<?php if ($healthOk): ?>
|
||||
<span class="icon-check-circle text-success d-block" style="font-size:1.5rem"></span>
|
||||
<small class="text-success fw-bold">Healthy</small>
|
||||
<?php else: ?>
|
||||
<span class="icon-exclamation-circle text-danger d-block" style="font-size:1.5rem"></span>
|
||||
<small class="text-danger fw-bold">DB Error</small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="border rounded p-2 text-center h-100">
|
||||
<span class="fw-bold d-block" style="font-size:1.25rem"><?php echo $counts->articles; ?></span>
|
||||
<small class="text-muted">Articles</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="border rounded p-2 text-center h-100">
|
||||
<span class="fw-bold d-block" style="font-size:1.25rem"><?php echo $counts->users; ?></span>
|
||||
<small class="text-muted">Users</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="border rounded p-2 text-center h-100">
|
||||
<?php if ($counts->updates > 0): ?>
|
||||
<span class="fw-bold d-block text-warning" style="font-size:1.25rem"><?php echo $counts->updates; ?></span>
|
||||
<small class="text-warning">Updates</small>
|
||||
<?php else: ?>
|
||||
<span class="icon-check d-block text-success" style="font-size:1.25rem"></span>
|
||||
<small class="text-muted">Up to date</small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disk + IP row -->
|
||||
<div class="d-flex flex-wrap align-items-center gap-3 mb-3 small text-muted">
|
||||
<?php if ($diskPct !== null): ?>
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<span class="icon-hdd" aria-hidden="true"></span>
|
||||
<span>Disk <?php echo $diskPct; ?>%</span>
|
||||
<div class="progress" style="width:60px;height:6px">
|
||||
<div class="progress-bar <?php echo $diskColor; ?>" style="width:<?php echo $diskPct; ?>%"></div>
|
||||
</div>
|
||||
<span><?php echo number_format(($disk->free_mb ?? 0) / 1024, 1); ?> GB free</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<span class="text-muted small ms-auto">
|
||||
<?php if ($currentIp): ?>
|
||||
<div>
|
||||
<span class="icon-globe" aria-hidden="true"></span>
|
||||
Your IP: <code><?php echo htmlspecialchars($currentIp); ?></code>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="ms-auto">
|
||||
Joomla <?php echo htmlspecialchars($siteInfo->joomla_version ?? ''); ?> / PHP <?php echo htmlspecialchars($siteInfo->php_version ?? ''); ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showPlugins && !empty($plugins)): ?>
|
||||
<!-- Feature plugins -->
|
||||
<div class="mb-2">
|
||||
<!-- Feature plugin badges -->
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<small class="text-muted"><?php echo Text::sprintf('MOD_MOKOWAAS_CPANEL_PLUGINS_SUMMARY', $enabledCount, $totalCount); ?></small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<?php foreach ($plugins as $p): ?>
|
||||
<?php
|
||||
$label = $labels[$p->element] ?? $p->element;
|
||||
$badge = $p->enabled ? 'bg-success' : 'bg-secondary';
|
||||
$icon = $p->enabled ? 'icon-check' : 'icon-times';
|
||||
?>
|
||||
<span class="badge <?php echo $badge; ?>" title="<?php echo htmlspecialchars($p->name); ?>">
|
||||
<span class="<?php echo $icon; ?>" aria-hidden="true"></span>
|
||||
<?php echo htmlspecialchars($label); ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<?php foreach ($plugins as $p): ?>
|
||||
<?php
|
||||
$label = $labels[$p->element] ?? $p->element;
|
||||
$badge = $p->enabled ? 'bg-success' : 'bg-secondary';
|
||||
$icon = $p->enabled ? 'icon-check' : 'icon-times';
|
||||
?>
|
||||
<span class="badge <?php echo $badge; ?>" title="<?php echo htmlspecialchars($p->name); ?>">
|
||||
<span class="<?php echo $icon; ?>" aria-hidden="true"></span>
|
||||
<?php echo htmlspecialchars($label); ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Quick action buttons -->
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="mokowaas-cpanel-cache"
|
||||
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.clearCache&format=json'); ?>"
|
||||
data-token="<?php echo $token; ?>">
|
||||
<span class="icon-trash" aria-hidden="true"></span> Clear Cache
|
||||
</button>
|
||||
<a href="<?php echo Route::_('index.php?option=com_installer&view=update'); ?>" class="btn btn-sm btn-outline-secondary">
|
||||
<span class="icon-refresh" aria-hidden="true"></span> Check Updates
|
||||
</a>
|
||||
<?php if ($counts->updates > 0): ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_installer&view=update'); ?>" class="btn btn-sm btn-warning">
|
||||
<span class="icon-upload" aria-hidden="true"></span> <?php echo $counts->updates; ?> Update<?php echo $counts->updates > 1 ? 's' : ''; ?> Available
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var btn = document.getElementById('mokowaas-cpanel-cache');
|
||||
if (!btn) return;
|
||||
btn.addEventListener('click', function() {
|
||||
var el = this;
|
||||
var url = el.dataset.url;
|
||||
var token = el.dataset.token;
|
||||
el.disabled = true;
|
||||
var icon = el.querySelector('span');
|
||||
var origClass = icon ? icon.className : '';
|
||||
if (icon) icon.className = 'icon-spinner icon-spin';
|
||||
var fd = new FormData();
|
||||
fd.append(token, '1');
|
||||
fetch(url, {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.success) Joomla.renderMessages({message:['Cache cleared.']});
|
||||
else Joomla.renderMessages({error:[d.message||'Failed']});
|
||||
})
|
||||
.catch(function(){Joomla.renderMessages({error:['Network error']})})
|
||||
.finally(function(){
|
||||
el.disabled = false;
|
||||
if (icon) icon.className = origClass;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
+2
-2
@@ -472,7 +472,7 @@ class Pkg_MokowaasInstallerScript
|
||||
'title' => 'MokoWaaS',
|
||||
'note' => '',
|
||||
'content' => '',
|
||||
'ordering' => 1,
|
||||
'ordering' => -1,
|
||||
'position' => 'cpanel',
|
||||
'checked_out' => null,
|
||||
'checked_out_time' => null,
|
||||
@@ -480,7 +480,7 @@ class Pkg_MokowaasInstallerScript
|
||||
'publish_down' => null,
|
||||
'published' => 1,
|
||||
'module' => 'mod_mokowaas_cpanel',
|
||||
'access' => 3, // Special access (admin only)
|
||||
'access' => 6, // Super Users only
|
||||
'showtitle' => 1,
|
||||
'params' => '{"show_health":"1","show_plugins":"1"}',
|
||||
'client_id' => 1, // Administrator
|
||||
|
||||
Reference in New Issue
Block a user