User tests: Successful: Unsuccessful:
Pull Request resolves # .
This PR aims at adding Health Check functionality to Joomla, in a dedicated dashboard.
It lays out the underlying initial implementation and aims at giving instructions to developers to create specific Health Check plugins.
There is one piece missing, it is the installation of a module healthcheck instance. It is incoming in this draft.
Right now, you need to use the 'discover' functionality to try this PR out.
Thank you for your patience.
This guide explains how to build a healthcheck plugin for Joomla's Health Check module, using the existing usermaintenance plugin as the baseline example.
Relevant core code in this workspace:
administrator/modules/mod_healthcheck/src/Helper/HealthCheckHelper.phpadministrator/modules/mod_healthcheck/tmpl/default.phplibraries/src/HTML/Helpers/HealthChecks.phplayouts/joomla/healthchecks/icon.phplayouts/joomla/healthchecks/gauge.phplayouts/joomla/healthchecks/list.phplayouts/joomla/healthchecks/table.phpplugins/healthcheck/usermaintenancehealthcheck plugin group and dispatches events like onHealthcheckGetIcons, onHealthcheckGetGauges, onHealthcheckGetLists, and onHealthcheckGetTables.result argument.HealthCheckHelper methods getButtons(), getGauges(), getLists(), getTables()).HTMLHelper::_('healthchecks.*', ...) helper renders each item through the matching layout file.The module passes a context value to events. Your plugin should usually ignore events for other contexts.
Use the same structure as plugins/healthcheck/usermaintenance:
plugins/healthcheck/yourplugin/
yourplugin.xml
services/provider.php
src/Extension/YourPlugin.php
language/en-GB/plg_healthcheck_yourplugin.ini
language/en-GB/plg_healthcheck_yourplugin.sys.ini
yourplugin.xml essentialsgroup="healthcheck"context text field)The usermaintenance example defines:
<extension type="plugin" group="healthcheck" method="upgrade">services/provider.php essentialsRegister your plugin as PluginInterface with lazy loading, like plugins/healthcheck/usermaintenance/services/provider.php.
src/Extension/YourPlugin.php essentialsCMSPluginSubscriberInterfacegetSubscribedEvents()onHealthcheckGetIconsonHealthcheckGetGaugesonHealthcheckGetListsonHealthcheckGetTablesEach event method should:
$result = $event->getArgument('result', []);$result[] = $items;$event->setArgument('result', $result);This matches the pattern in usermaintenance (onHealthcheckGetIcons).
Use the same guard style as usermaintenance:
$context = $event->getContext();
if ($context !== $this->params->get('context', 'general')) {
return;
}Icons are dispatched through onHealthcheckGetIcons and rendered by layouts/joomla/healthchecks/icon.php.
From HealthCheckHelper::getButtons():
link (required)text or name (required)icon (for icon class, used by layout)image (optional image URL)amount (numeric/string badge amount)status (success, warning, error, etc.; affects color/filter)id, class, target, title, onclickgroup (defaults to general)access (boolean or ACL pair array)usermaintenance)public function onHealthcheckGetIcons(HealthChecksEvent $event): void
{
if ($event->getContext() !== $this->params->get('context', 'usermanagement')) {
return;
}
$checks = [];
$checks[] = [
'link' => 'index.php?option=com_users&view=users&filter[state]=1',
'icon' => 'fas fa-users-gear',
'amount' => 12,
'text' => 'Inactive users',
'id' => 'plg_healthcheck_example_inactive',
'status' => 'warning',
];
$checks[] = [
'link' => 'index.php?option=com_users&view=users&filter[mfa]=0',
'icon' => 'fas fa-shield-halved',
'amount' => 0,
'text' => 'Users without MFA',
'status' => 'success',
];
$result = $event->getArgument('result', []);
$result[] = $checks;
$event->setArgument('result', $result);
}Gauges are dispatched through onHealthcheckGetGauges and rendered by layouts/joomla/healthchecks/gauge.php.
From HealthCheckHelper::getGauges():
scoreunitlabel, sublabel, notescore_min, score_maxscore_threshold_warning, score_threshold_successlinklinktitle (used by layout)group, access, class, idpublic function onHealthcheckGetGauges(HealthChecksEvent $event): void
{
if ($event->getContext() !== $this->params->get('context', 'performance')) {
return;
}
$gauges = [[
'id' => 'plg_healthcheck_example_php_memory',
'label' => 'PHP memory usage',
'sublabel' => 'Current process',
'note' => 'Values over 80% should be reviewed.',
'score' => 72,
'unit' => '%',
'score_min' => 0,
'score_max' => 100,
'score_threshold_warning' => 70,
'score_threshold_success' => 90,
'link' => 'index.php?option=com_config',
'linktitle' => 'Open Global Configuration',
'status' => 'warning',
]];
$result = $event->getArgument('result', []);
$result[] = $gauges;
$event->setArgument('result', $result);
}Lists are dispatched through onHealthcheckGetLists and rendered by layouts/joomla/healthchecks/list.php.
From HealthCheckHelper::getLists():
items (array)type (ul, ol, or div)class, id, itemClassgroup, accesspublic function onHealthcheckGetLists(HealthChecksEvent $event): void
{
if ($event->getContext() !== $this->params->get('context', 'security')) {
return;
}
$lists = [[
'id' => 'plg_healthcheck_example_security_tips',
'class' => 'list-group list-group-flush',
'itemClass'=> 'list-group-item',
'type' => 'ul',
'items' => [
'Enable MFA for all administrators',
'Review inactive accounts monthly',
'Remove users not assigned to any group',
],
]];
$result = $event->getArgument('result', []);
$result[] = $lists;
$event->setArgument('result', $result);
}Tables are dispatched through onHealthcheckGetTables and rendered by layouts/joomla/healthchecks/table.php.
From HealthCheckHelper::getTables():
columns (array)data (array)Each column typically uses:
key (data key)title (header)type (text, badge, link, date, boolean, progress, icon, custom)align, width, scope, cellClass, etc.)type rendering is handled by HealthCheckHelper::renderTableCellContent().
public function onHealthcheckGetTables(HealthChecksEvent $event): void
{
if ($event->getContext() !== $this->params->get('context', 'usermanagement')) {
return;
}
$tables = [[
'id' => 'plg_healthcheck_example_user_risk',
'caption' => 'Users requiring attention',
'class' => 'table-sm',
'columns' => [
['key' => 'name', 'title' => 'User'],
['key' => 'lastvisitDate', 'title' => 'Last login', 'type' => 'date'],
['key' => 'mfa', 'title' => 'MFA', 'type' => 'boolean'],
['key' => 'risk', 'title' => 'Risk', 'type' => 'badge', 'badgeClass' => static function ($value) {
return $value === 'high' ? 'danger' : ($value === 'medium' ? 'warning' : 'success');
}],
],
'data' => [
['name' => 'Alice Admin', 'lastvisitDate' => '2026-05-14 09:05:00', 'mfa' => 1, 'risk' => 'low'],
['name' => 'Bob Manager', 'lastvisitDate' => '2026-01-07 11:21:00', 'mfa' => 0, 'risk' => 'high'],
],
]];
$result = $event->getArgument('result', []);
$result[] = $tables;
$event->setArgument('result', $result);
}public static function getSubscribedEvents(): array
{
return [
'onHealthcheckGetIcons' => 'onHealthcheckGetIcons',
'onHealthcheckGetGauges' => 'onHealthcheckGetGauges',
'onHealthcheckGetLists' => 'onHealthcheckGetLists',
'onHealthcheckGetTables' => 'onHealthcheckGetTables',
];
}You can implement only the events you need.
group: defaults to general; use it to classify data by module context strategy.access:
true/false for quick allow/deny['core.manage', 'com_users', 'core.admin', 'com_users']Access is evaluated in libraries/src/HTML/Helpers/HealthChecks.php (canAccess()).
healthcheck.mod_healthcheck is enabled in Administrator.context to match your plugin context parameter.linktitle for link title text.link_title for gauges; if you need guaranteed title output in the current layout, set linktitle in your payload.healthcheck-filters worksThe filter bar is part of the module template (administrator/modules/mod_healthcheck/tmpl/default.php), not the plugin itself.
The module renders four filter buttons:
allhealthywarningcriticalSelecting a button shows only matching health-check items in the module.
To make your items filter correctly, provide status on each item payload.
Use these values:
success for healthy itemswarning for warning itemserror for critical itemsIf status is omitted, items default to the healthy group.
Filtering currently applies to items rendered by these layouts:
layouts/joomla/healthchecks/icon.phplayouts/joomla/healthchecks/gauge.phplayouts/joomla/healthchecks/list.phplayouts/joomla/healthchecks/table.phpAll filterable items are tagged with data-healthcheck-status, and the module script (media_source/mod_healthcheck/js/healthcheck-filter.js) uses that value to show/hide items.
The filter normalization accepts these aliases:
success, ok, info -> healthywarning, warn, alert -> warningerror, danger -> critical$checks[] = [
'link' => 'index.php?option=com_users&view=users&filter[mfa]=0',
'icon' => 'fas fa-shield-halved',
'amount' => 3,
'text' => 'Users without MFA',
'status' => 'warning', // appears when the Warning filter is selected
];healthcheck plugin and mod_healthcheck.success, warning, and error statuses.All, Healthy, Warning, and Critical.Please select:
Documentation link for guide.joomla.org:
No documentation changes for guide.joomla.org needed
Pull Request link for manual.joomla.org:
No documentation changes for manual.joomla.org needed
| Status | New | ⇒ | Pending |
| Category | ⇒ | Administration com_cpanel com_menus Language & Strings Modules Layout Libraries JavaScript Front End Plugins |
| Labels |
Added:
Language Change
PR-6.2-dev
|
||
- Healthcheck
I sorted it out a bit:
Health Check: the name
Health Check: the module
health check: the noun
HealthCheck: the plugin group
| Category | Administration com_cpanel com_menus Language & Strings Modules Layout Libraries JavaScript Front End Plugins | ⇒ | Administration com_cpanel com_menus Language & Strings Modules SQL Installation Postgresql Layout Libraries JavaScript Front End Plugins |
dont forget the sql for updates
the count says 0 but I have multiple users that are not activated
when you click on the button it takes you to a filter list of disabled users. thats not the same thing as non activated users
The reason that the filters dont work as reported #47947 (comment) is that neither the js not the css files are being created in the media folder.
With the new build system you have to explicitly tell the build scripts to create the js and css for the module. With the old build system you could just create the media source folder but now you have to be explicit. (dont ask me why)
So you need to add mod_healthcheck here
When you hover over a button it is moved -1px with css.
.healthcheck-filters .btn:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px #0000001a;
}
I am assuming that this is to give a pseudo real button effect when you click on it. No where else in the admin ui do we do this - but maybe one of the css gurus will correct me - personally I woujld just remove it as its ugly when you hover across the buttons as shown below.
The usermaintenance plugin currently demonstrates the icon event only.
Makes it very hard to test the other functionality
| Category | Administration com_cpanel com_menus Language & Strings Modules Layout Libraries JavaScript Front End Plugins SQL Installation Postgresql | ⇒ | Administration com_cpanel com_menus Language & Strings Modules JavaScript Repository SQL Installation Postgresql Layout Libraries |
The usermaintenance plugin currently demonstrates the icon event only.
Makes it very hard to test the other functionality
We have other plugins in the work that show more of the layouts that are available.
We still wanted to have a basic implementation with this PR.
When you hover over a button it is moved -1px with css.
.healthcheck-filters .btn:hover { transform: translateY(-1px); box-shadow: 0 2px 4px #0000001a; }I am assuming that this is to give a pseudo real button effect when you click on it. No where else in the admin ui do we do this - but maybe one of the css gurus will correct me - personally I woujld just remove it as its ugly when you hover across the buttons as shown below.
Right on, I removed it, it is indeed not consistent with other buttons.
The usermaintenance plugin currently demonstrates the icon event only.
Makes it very hard to test the other functionality
We have other plugins in the work that show more of the layouts that are available. We still wanted to have a basic implementation with this PR.
without those plugins (or something even just for testing purposes) its not possible to report a successful test of that part of the pr
without those plugins (or something even just for testing purposes) its not possible to report a successful test of that part of the pr
In that case, I would suggest we complement the Users Maintenance plugin to use all layouts.
Add an optional gauge showing the amount of inactive users compared to the total number of users, for instance, data in a table layout and in a list layout.
without those plugins (or something even just for testing purposes) its not possible to report a successful test of that part of the pr
In that case, I would suggest we complement the Users Maintenance plugin to use all layouts.
Add an optional gauge showing the amount of inactive users compared to the total number of users, for instance, data in a table layout and in a list layout.
Better, I think I will create an additional plugin that highlights all cases, that will not be included in the core.
| Category | Administration com_cpanel com_menus Language & Strings Modules Layout Libraries JavaScript SQL Installation Postgresql Repository | ⇒ | SQL Administration com_admin Postgresql com_cpanel com_menus Language & Strings Modules JavaScript Repository Installation Layout Libraries |
Better, I think I will create an additional plugin that highlights all cases, that will not be included in the core.
Please do so that this PR can be really tested - otherwise its not possible for someone to test the entirety of the PR
inconsistency in the language strings
Score: 68 % out of 100 %. This represents 68.0% of the range from 0 to 100. Status: Good performance with room for improvement.
using link text and title and aria-label is not a good idea. It is generally bad of accessibility espec with screen readers. what are you trying to achieve by doing this
Thanks for getting the ai to find the issue with the language strings
This makes extensive use of (often invisible) links with both title and aria-labels to explain the purpose of the link with different levels of information. This can be tricky as screen readers will use one or both which is overkill.
Titles are also invisible to keyboard users and mobile users. On the gauges it's not even obvious that they have a link behind them.
Ideally every link should have anchor text which describe the link and an aria-labels only used where the anchor text is insufficient.
Additional reading https://www.deque.com/blog/text-links-practices-screen-readers/
You need to decide on the terminology and be consistent with its use