feat(diagnostics): add health endpoint with Grafana auto-provisioning (#54)

Implements heartbeat telemetry for WaaS dashboard monitoring:
- JSON health endpoint at /?mokowaas=health with token auth
- Database, filesystem, cache, and extension health checks
- Auto-generated API token (separate from Joomla user tokens)
- Grafana Infinity datasource auto-provisioning via REST API
- Shared dashboard with endpoint dropdown variable
- Auto-provision on plugin install/update via script.php
- Grafana plugin install via API (replaces deprecated CLI)
- Deprovisioning on disable (datasource cleanup)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-05-21 15:42:58 -05:00
parent a0f3e42861
commit d1e2555f00
7 changed files with 1367 additions and 9 deletions
+133
View File
@@ -128,6 +128,7 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
$this->updateLoginSupportUrls();
$this->updateAtumBranding();
$this->registerActionLogExtension();
$this->provisionHealthEndpoint();
$this->sendInstallNotification($type);
}
@@ -731,6 +732,138 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
*
* @since 02.01.08
*/
/**
* Provision health endpoint with Grafana if configured.
*
* On install/update, if the health endpoint is enabled and Grafana
* credentials are set, generates a token (if missing) and triggers
* Grafana datasource provisioning via cURL. All data is passed as
* structured JSON — no shell commands are invoked.
*
* @return void
*
* @since 02.01.22
*/
private function provisionHealthEndpoint()
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('params'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = '
. $db->quote('mokowaas'))
->where($db->quoteName('folder') . ' = '
. $db->quote('system'));
$db->setQuery($query);
$rawParams = $db->loadResult();
if (empty($rawParams))
{
return;
}
$params = new \Joomla\Registry\Registry($rawParams);
$changed = false;
if (!(int) $params->get('enable_health_endpoint', 0))
{
return;
}
// Auto-generate token if missing
if (empty($params->get('health_api_token', '')))
{
$params->set(
'health_api_token',
bin2hex(random_bytes(32))
);
$changed = true;
}
if ($changed)
{
$db->setQuery(
$db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = '
. $db->quote($params->toString()))
->where($db->quoteName('element') . ' = '
. $db->quote('mokowaas'))
->where($db->quoteName('folder') . ' = '
. $db->quote('system'))
);
$db->execute();
}
// Trigger Grafana provisioning if credentials are set
$grafanaUrl = rtrim($params->get('grafana_url', ''), '/');
$grafanaKey = $params->get('grafana_api_key', '');
if (empty($grafanaUrl) || empty($grafanaKey))
{
return;
}
$siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/');
$siteName = Factory::getConfig()->get('sitename', 'Joomla');
$dsUid = 'mokowaas-' . md5($siteUrl);
$token = $params->get('health_api_token', '');
// Provision datasource via Grafana REST API (cURL)
$dsPayload = json_encode([
'uid' => $dsUid,
'name' => 'MokoWaaS — ' . $siteName,
'type' => 'yesoreyeram-infinity-datasource',
'access' => 'proxy',
'url' => $siteUrl,
'jsonData' => [
'auth_method' => 'bearerToken',
'global_queries' => [],
],
'secureJsonData' => [
'bearerToken' => $token,
],
], JSON_UNESCAPED_SLASHES);
$headers = [
'Authorization: Bearer ' . $grafanaKey,
'Content-Type: application/json',
'Accept: application/json',
];
// Try PUT (update), fall back to POST (create)
$ch = curl_init($grafanaUrl . '/api/datasources/uid/' . $dsUid);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $dsPayload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$response = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code === 404)
{
$ch = curl_init($grafanaUrl . '/api/datasources');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $dsPayload);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_exec($ch);
curl_close($ch);
}
Log::add(
'Health endpoint provisioned with Grafana on '
. ($code === 200 ? 'update' : 'install'),
Log::INFO,
'mokowaas'
);
}
private function registerActionLogExtension()
{
$db = Factory::getDbo();