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 its purpose is to give instructions to developers to create specific Health Check plugins.
Note: the Health Check menu item will only appear if there is at least one instance of the Health Check module available for the cpanel-healthcheck position.
Download and install the full package. The full package gives you a 6.2 alpha 3 dev instance of Joomla with the Health Check functionality. The update package will update your site to a version 6.2 alpha 3 dev.
Download and install the plugin plg_healthcheck_showcase.zip. This plugin is meant to show all layout options plugin developers have at their disposal to create plugins for the Health Check.
cpanel-healthcheck. Open the 'Health Check' module. Check the context (or group). It should be set to 'general'. Publish, Save and Close. This will enable the Health Check dashboard. You should now see a 'heart' icon on the left menu, below 'System'.Note: if just updated your system, you will need to create an instance of the Health Check module with context (or group) 'general' and a position of cpanel-healthcheck.
Health Check Group parameter).When plugins and modules share the same context (or group), information generated by the plugins show in their respective module instance.
Go to System -> Plugins and filter by healthcheck type. Make sure both plugins are enabled. The User Maintenance plugin has settings you can modify.
While testing, if you give the same context (or group) to both plugins, they will 'blend' together. Gauges first, then icons, then lists, then tables.
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, onclickonclick — name of a handler registered via Joomla.registerHealthCheckAction(name, fn) (rendered as data-onclick; no inline JS)group (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.Icon buttons support a click handler via the onclick field. To stay Content Security Policy (CSP) compliant, the layout renders it as a data-onclick attribute — never as an inline onclick="...". The module script healthcheck-onclick.js listens for clicks and dispatches to a named handler you register in JavaScript.
In your plugin's JavaScript asset, call Joomla.registerHealthCheckAction before any click can occur:
Joomla.registerHealthCheckAction('myHandlerName', function (event) {
// `this` is the clicked <a> element
const link = this;
console.log('Clicked icon:', link);
});The handler is called with the clicked element as this and the native MouseEvent as the first argument. event.preventDefault() is called automatically by the dispatcher, so the icon's href is not followed.
onclick in your PHP payloadPass the registered handler name as the onclick value:
$checks[] = [
'link' => '#',
'icon' => 'fas fa-rotate',
'text' => 'Run manual check',
'onclick' => 'myHandlerName', // must match the name passed to registerHealthCheckAction
'status' => 'info',
];Register and load your script through the Web Asset Manager so it is guaranteed to run after healthcheck-onclick.js:
$wa = $app->getDocument()->getWebAssetManager();
$wa->registerAndUseScript(
'plg_healthcheck_yourplugin.script',
'plg_healthcheck/yourplugin/yourplugin.js',
['dependencies' => ['mod_healthcheck.onclick']],
['defer' => true]
);plugins/healthcheck/yourplugin/src/Extension/YourPlugin.php
$checks[] = [
'link' => '#',
'icon' => 'fas fa-rotate',
'text' => 'Refresh cache',
'onclick' => 'plgHealthcheckYourpluginRefresh',
'status' => 'warning',
];plugins/healthcheck/yourplugin/media/js/yourplugin.js
Joomla.registerHealthCheckAction('plgHealthcheckYourpluginRefresh', function (event) {
fetch('index.php?option=com_ajax&plugin=yourplugin&group=healthcheck&format=json', {
method: 'POST',
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
}).then(() => window.location.reload());
});Note: Use a unique, namespaced handler name (e.g. prefixed with your plugin name) to avoid collisions with other plugins.
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/
Thanks for all the work, my first thing would be, please remove it from the "main" menu and move it to the "system" screen.
When I think about use cases for my extensions, then I think it would be good to have the check done in an ajax request and not within the page load. Especially when there are many checks done it can take some time to finish.
When I think about use cases for my extensions, then it would be good to have the check done in an ajax request and not within the page load. Especially when there are many checks done it can take some time to finish.
| Title |
|
||||||
Here is a summary of the unresolved items to take care of this week:
administrator/language/en-GB/mod_healthcheck.ini:28
Reviewer brianteeman: not convinced %% is correct / not testable.
Link: #47947 (comment)
Action: confirm rendered output path for this string (or add clarifying comment/test evidence) and reply with proof.
plugins/healthcheck/usermaintenance/src/Extension/UserMaintenance.php:65
Reviewer heelc29: logic “doesn't make sense if there is more than one active module”.
Link: #47947 (comment)
Action: review module selection/lookup logic for multi-module setups and patch or explain intended behavior.
administrator/language/en-GB/plg_healthcheck_usermaintenance.ini:36
Reviewer brianteeman: description is redundant/useless vs label.
Link: #47947 (comment)
Action: remove or rewrite the description to add distinct value.
layouts/joomla/healthchecks/gauge.php:112
Reviewer brianteeman: tabindex="-1" makes it unreachable for screen readers.
Link: #47947 (comment)
Action: revisit accessibility approach (focusability + ARIA semantics) and adjust markup.
administrator/language/en-GB/plg_healthcheck_usermaintenance.ini:9
Reviewer brianteeman: suggested text change to “disabled users” in error string.
Link: #47947 (comment)
Action: accept/edit suggestion or justify current wording.
administrator/language/en-GB/plg_healthcheck_usermaintenance.ini:23
Reviewer brianteeman: suggested label Disabled Users.
Link: #47947 (comment)
Action: align label with actual metric or explain distinction if label remains Inactive.
administrator/language/en-GB/plg_healthcheck_usermaintenance.ini:22
Long unresolved thread (brianteeman + obuisard) around inactive vs disabled/unactivated wording and incorrect links.
Latest note asks whether to keep “Inactive” and rename “Unactivated” to “Unactivated/Disabled”.
Link: #47947 (comment)
Action: finalize terminology + fix link behavior consistently, then resolve the entire wording/UX thread.
Brian @brianteeman, can you confirm? Thank you!
maybe you had these as I might have misunderstood your notes
Follow the style guide for UI strings #47947 (comment)
Check the usage of title/aria-label #47947 (comment)
Guage strings with % values #47947 (comment)
maybe you had these as I might have misunderstood your notes
Follow the style guide for UI strings #47947 (comment)
Check the usage of title/aria-label #47947 (comment)
Guage strings with % values #47947 (comment)
Yes, that was in my notes, I used copilot to make sure I had all covered :-)
one more which I dont think there was an open issue for but was mentioned in the meeting
Don't show the HealthCheck menu item in the sidebar if there are no healthchecks published on that dashboard
| Category | Administration com_cpanel com_menus Language & Strings Modules Layout Libraries JavaScript SQL Installation Postgresql Repository com_admin | ⇒ | SQL Administration com_admin Postgresql com_cpanel com_menus com_users Language & Strings Modules JavaScript Repository Installation Layout Libraries |
| Category | Administration com_cpanel com_menus Language & Strings Modules Layout Libraries JavaScript SQL Installation Postgresql Repository com_admin com_users | ⇒ | SQL Administration com_admin Postgresql com_cpanel com_menus com_users Language & Strings Modules JavaScript Repository Installation Layout |
Thank you @obuisard Would it be possible to implement this idea into this PR? #48232
Hi Elisa @coolcat-creations this is definitely in line with what we are trying to do with Health-Check.
The health indicator you mentioned could be a quickicon that seats in the main dashboard of the Joomla console, gives a health degree and links to the Health Check dashboard.
In this initial implementation, we do not want to put Health Check forward to the users, we want to put everything in place so that developers can start implementing plugins for it.
I see your idea part of 'phase 2' (planned for 6.3) where many plugins are in place or available to download and where the Health Check dashboard gives users meaningful information and tools to improve their sites.
is this ready for new tests or do you still have more work planned?
is this ready for new tests or do you still have more work planned?
Hi Brian @brianteeman this is ready for testing. Thank you for your help.
The code has a comment
<!-- Health Check: only show this entry when at least one mod_healthcheck instance
is published in the cpanel-healthcheck position. If no such module exists
(e.g. not yet installed or unpublished) the menu item is suppressed entirely. -->
But this is what I have after applying the PR - so its not working as intended?
The code has a comment
<!-- Health Check: only show this entry when at least one mod_healthcheck instance
is published in the cpanel-healthcheck position. If no such module exists
(e.g. not yet installed or unpublished) the menu item is suppressed entirely. -->
But this is what I have after applying the PR - so its not working as intended?
Checking further and the problem is not with the check mantioned above. There is a module - its just not displayed
| Labels |
Added:
Feature
|
||
The code has a comment
<!-- Health Check: only show this entry when at least one mod_healthcheck instance is published in the cpanel-healthcheck position. If no such module exists (e.g. not yet installed or unpublished) the menu item is suppressed entirely. -->But this is what I have after applying the PR - so its not working as intended?
Checking further and the problem is not with the check mantioned above. There is a module - its just not displayed
I noticed it works fine if the module is saved but NOT if the module is just published/unpublished from the modules list.
The code has a comment
<!-- Health Check: only show this entry when at least one mod_healthcheck instance is published in the cpanel-healthcheck position. If no such module exists (e.g. not yet installed or unpublished) the menu item is suppressed entirely. -->But this is what I have after applying the PR - so its not working as intended?
Checking further and the problem is not with the check mantioned above. There is a module - its just not displayed
I noticed it works fine if the module is saved but NOT if the module is just published/unpublished from the modules list.
Not sure why.
You need to decide on the terminology and be consistent with its use