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:
+133
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user