feat: Send Heartbeat button next to token field
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 / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (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
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 / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Report Issues (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
Platform: moko-platform CI / Gate 1: Code Quality (push) Has been cancelled
Adds a heart icon button in the token field input group that triggers an AJAX heartbeat to MokoWaaSBase. Shows spinner while sending, green check on success, red X on failure. Uses the monitor plugin's configured base_url and the core plugin's health_api_token. Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,96 @@ class DisplayController extends BaseController
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// Heartbeat
|
||||
// ==================================================================
|
||||
|
||||
public function sendHeartbeat()
|
||||
{
|
||||
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
|
||||
|
||||
try
|
||||
{
|
||||
$monitorPlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokowaas_monitor');
|
||||
|
||||
if (!$monitorPlugin)
|
||||
{
|
||||
$this->jsonResponse(['success' => false, 'message' => 'Monitor plugin not enabled.']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$params = new \Joomla\Registry\Registry($monitorPlugin->params);
|
||||
$baseUrl = rtrim($params->get('base_url', ''), '/');
|
||||
|
||||
if (empty($baseUrl))
|
||||
{
|
||||
$this->jsonResponse(['success' => false, 'message' => 'MokoWaaSBase URL not configured in monitor plugin.']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$corePlugin = \Joomla\CMS\Plugin\PluginHelper::getPlugin('system', 'mokowaas');
|
||||
$coreParams = new \Joomla\Registry\Registry($corePlugin ? $corePlugin->params : '{}');
|
||||
$healthToken = $coreParams->get('health_api_token', '');
|
||||
|
||||
if (empty($healthToken))
|
||||
{
|
||||
$this->jsonResponse(['success' => false, 'message' => 'Health token not configured.']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/');
|
||||
$domain = parse_url($siteUrl, PHP_URL_HOST) ?: '';
|
||||
|
||||
$payload = json_encode([
|
||||
'token' => $healthToken,
|
||||
'domain' => $domain,
|
||||
'site_name' => Factory::getConfig()->get('sitename', 'Joomla'),
|
||||
'site_url' => $siteUrl,
|
||||
'joomla_version' => (new \Joomla\CMS\Version())->getShortVersion(),
|
||||
'php_version' => PHP_VERSION,
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$endpoint = $baseUrl . '/api/index.php/v1/mokowaasbase/heartbeat';
|
||||
|
||||
$ch = curl_init($endpoint);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_SSL_VERIFYPEER => false,
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error)
|
||||
{
|
||||
$this->jsonResponse(['success' => false, 'message' => 'Connection failed: ' . $error]);
|
||||
}
|
||||
elseif ($code >= 200 && $code < 300)
|
||||
{
|
||||
$body = json_decode($response, true);
|
||||
$this->jsonResponse(['success' => true, 'message' => 'Heartbeat sent: ' . ($body['status'] ?? 'ok')]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$body = json_decode($response, true);
|
||||
$this->jsonResponse(['success' => false, 'message' => 'HTTP ' . $code . ': ' . ($body['error'] ?? $body['message'] ?? 'Unknown')]);
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->jsonResponse(['success' => false, 'message' => 'Error: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache
|
||||
// ==================================================================
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Form\FormField;
|
||||
use Joomla\CMS\Session\Session;
|
||||
|
||||
/**
|
||||
* Renders a read-only text input with a "Copy" button, similar to
|
||||
@@ -39,8 +40,9 @@ class CopyableTokenField extends FormField
|
||||
return '<div class="alert alert-warning mb-0 py-2">Token will be generated automatically on first save.</div>';
|
||||
}
|
||||
|
||||
// Derive a human-readable support PIN from the token
|
||||
$pin = strtoupper(substr($this->value, 0, 4) . '-' . substr($this->value, 4, 4));
|
||||
$pin = strtoupper(substr($this->value, 0, 4) . '-' . substr($this->value, 4, 4));
|
||||
$token = Session::getFormToken();
|
||||
$ajaxUrl = 'index.php?option=com_mokowaas&task=display.sendHeartbeat&format=json';
|
||||
|
||||
return <<<HTML
|
||||
<div class="input-group mb-2">
|
||||
@@ -60,6 +62,31 @@ class CopyableTokenField extends FormField
|
||||
inp.select(); document.execCommand('copy');
|
||||
}
|
||||
"><span class="icon-copy" aria-hidden="true"></span> Copy</button>
|
||||
<button type="button" class="btn btn-outline-primary" id="mokowaas-send-heartbeat" onclick="
|
||||
var btn = this;
|
||||
btn.disabled = true;
|
||||
var orig = btn.innerHTML;
|
||||
btn.innerHTML = '<span class="icon-spinner icon-spin" aria-hidden="true"></span> Sending...';
|
||||
var fd = new FormData();
|
||||
fd.append('{$token}', '1');
|
||||
fetch('{$ajaxUrl}', {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if(d.success){
|
||||
btn.innerHTML='<span class="icon-check" aria-hidden="true"></span> Sent';
|
||||
btn.classList.replace('btn-outline-primary','btn-success');
|
||||
} else {
|
||||
btn.innerHTML='<span class="icon-times" aria-hidden="true"></span> Failed';
|
||||
btn.classList.replace('btn-outline-primary','btn-danger');
|
||||
}
|
||||
setTimeout(function(){btn.innerHTML=orig;btn.className='btn btn-outline-primary';btn.disabled=false;},3000);
|
||||
})
|
||||
.catch(function(){
|
||||
btn.innerHTML='<span class="icon-times" aria-hidden="true"></span> Error';
|
||||
btn.classList.replace('btn-outline-primary','btn-danger');
|
||||
setTimeout(function(){btn.innerHTML=orig;btn.className='btn btn-outline-primary';btn.disabled=false;},3000);
|
||||
});
|
||||
"><span class="icon-heart" aria-hidden="true"></span> Send Heartbeat</button>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge bg-dark" style="font-family:monospace;font-size:1rem;letter-spacing:0.1em;">MOKO-{$pin}</span>
|
||||
|
||||
Reference in New Issue
Block a user