diff --git a/CHANGELOG.md b/CHANGELOG.md index 20c9416b..7966e302 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - **HQ config sync** — client stores HQ-configured `support_pin_hours` from heartbeat response, PIN TTL now configurable from HQ ### Changed +- **Support PIN UI unified** — `SupportPinHelper::renderBadge()` and `renderScript()` replace 3 separate inline implementations (dashboard, cpanel module, cache module) with click-to-copy on all PIN badges - Admin sidebar menu module now loads component-local language files (fixes untranslated keys for MokoSuiteCross and other components) - Support PIN TTL is now configurable via HQ global options instead of hardcoded 72 hours - Removed MokoSuiteHQ from extension catalog (internal app, not for client sites) diff --git a/source/packages/com_mokosuiteclient/admin/src/Helper/SupportPinHelper.php b/source/packages/com_mokosuiteclient/admin/src/Helper/SupportPinHelper.php index 3d1569e3..cb778d82 100644 --- a/source/packages/com_mokosuiteclient/admin/src/Helper/SupportPinHelper.php +++ b/source/packages/com_mokosuiteclient/admin/src/Helper/SupportPinHelper.php @@ -115,6 +115,145 @@ class SupportPinHelper * * @return array{success: bool, pin?: string, message: string} */ + /** + * Render PIN badge HTML (active PIN with copy, or request button). + * + * @param array $state Result from getState(). + * @param string $token CSRF form token name. + * @param string $context 'dashboard'|'cpanel'|'cache' — controls layout variant. + * + * @return string HTML fragment (no wrapping div). + */ + public static function renderBadge(array $state, string $token, string $context = 'dashboard'): string + { + if (!$state['available']) + { + return ''; + } + + $requestUrl = \Joomla\CMS\Router\Route::_('index.php?option=com_mokosuiteclient&task=display.requestPin&format=json'); + $pin = $state['pin']; + + $html = ''; + + if (!empty($pin)) + { + $escaped = htmlspecialchars($pin, ENT_QUOTES, 'UTF-8'); + + if ($context === 'cache') + { + $html .= ''; + $html .= ''; + $html .= '' . $escaped . ''; + $html .= ''; + } + else + { + $html .= ''; + $html .= '' . $escaped . ''; + } + } + else + { + if ($context === 'cache') + { + $html .= ''; + $html .= ''; + $html .= 'PIN'; + $html .= ''; + } + else + { + $html .= ''; + } + } + + return $html; + } + + /** + * Render shared JS for PIN copy and request functionality. + * + * @return string +JS; + } + public static function requestNew(DatabaseInterface $db): array { $state = self::getState($db); diff --git a/source/packages/com_mokosuiteclient/admin/tmpl/dashboard/default.php b/source/packages/com_mokosuiteclient/admin/tmpl/dashboard/default.php index a9d19bea..ca33d065 100644 --- a/source/packages/com_mokosuiteclient/admin/tmpl/dashboard/default.php +++ b/source/packages/com_mokosuiteclient/admin/tmpl/dashboard/default.php @@ -56,21 +56,17 @@ $actionLogsEnabled = Joomla\CMS\Component\ComponentHelper::isEnabled('com_action escape($siteInfo->sitename); ?> MokoSuite escape($siteInfo->mokosuiteclient_version); ?> + !empty($this->supportPinAvailable), 'pin' => $this->supportPin ?? ''], + $token, 'dashboard' + ); ?> supportPin)): ?> - escape($this->supportPin); ?> - supportPinAvailable)): ?> - Joomla escape($siteInfo->joomla_version); ?> PHP escape($siteInfo->php_version); ?> @@ -459,3 +455,5 @@ document.addEventListener('DOMContentLoaded', function() { } }); + + diff --git a/source/packages/com_mokosuiteclient/media/js/dashboard.js b/source/packages/com_mokosuiteclient/media/js/dashboard.js index cbf39e9d..88ae33c0 100644 --- a/source/packages/com_mokosuiteclient/media/js/dashboard.js +++ b/source/packages/com_mokosuiteclient/media/js/dashboard.js @@ -144,43 +144,6 @@ document.addEventListener('DOMContentLoaded', function () { }); } - // Request PIN button - var pinBtn = document.getElementById('mokosuiteclient-request-pin'); - if (pinBtn) { - pinBtn.addEventListener('click', function () { - var btn = this; - btn.disabled = true; - btn.textContent = '...'; - var fd = new FormData(); - fd.append(btn.dataset.token, '1'); - fetch(btn.dataset.url, {method: 'POST', body: fd, headers: {'X-Requested-With': 'XMLHttpRequest'}}) - .then(function (r) { return r.json(); }) - .then(function (d) { - if (d.success && d.pin) { - var badge = document.createElement('span'); - badge.className = 'badge bg-dark'; - badge.style.cssText = 'font-family:monospace;letter-spacing:0.08em;cursor:help;'; - badge.title = 'Support PIN — valid for 72 hours'; - badge.textContent = d.pin; - var icon = document.createElement('span'); - icon.className = 'icon-key small me-1'; - icon.setAttribute('aria-hidden', 'true'); - badge.prepend(icon); - btn.replaceWith(badge); - } else { - Joomla.renderMessages({error: [d.message || 'Failed to generate PIN']}); - btn.disabled = false; - btn.textContent = 'Request PIN'; - } - }) - .catch(function () { - Joomla.renderMessages({error: ['Network error']}); - btn.disabled = false; - btn.textContent = 'Request PIN'; - }); - }); - } - // Regular Labs import var rlBtn = document.getElementById('btn-import-regularlabs'); if (rlBtn) { diff --git a/source/packages/mod_mokosuiteclient_cache/tmpl/default.php b/source/packages/mod_mokosuiteclient_cache/tmpl/default.php index 1d46d390..34c111e3 100644 --- a/source/packages/mod_mokosuiteclient_cache/tmpl/default.php +++ b/source/packages/mod_mokosuiteclient_cache/tmpl/default.php @@ -12,7 +12,6 @@ use Joomla\CMS\Session\Session; $token = Session::getFormToken(); $cacheUrl = 'index.php?option=com_mokosuiteclient&task=display.clearCache&format=json'; $tempUrl = 'index.php?option=com_mokosuiteclient&task=display.clearTemp&format=json'; -$pinUrl = 'index.php?option=com_mokosuiteclient&task=display.requestPin&format=json'; $pinAvailable = $supportPinAvailable ?? false; $pin = $supportPin ?? ''; $frontendUrl = $frontendUrl ?? ''; @@ -25,12 +24,16 @@ $frontendUrl = $frontendUrl ?? ''; Site - - - - - - + true, 'pin' => $pin], + $token, 'cache' + ); + if (!$frontendUrl) { + $pinHtml = str_replace('rounded-0 border-end-0', 'rounded-0 rounded-start border-end-0', $pinHtml); + } + echo $pinHtml; + endif; ?> Cache @@ -92,55 +95,6 @@ document.addEventListener('DOMContentLoaded', function() { setupCleaner('mokosuiteclient-clear-cache', 'mokosuiteclient-cache-icon', '', ''); setupCleaner('mokosuiteclient-clear-temp', 'mokosuiteclient-temp-icon', '', ''); - // Support PIN button - var pinBtn = document.getElementById('mokosuiteclient-pin'); - var pinIcon = document.getElementById('mokosuiteclient-pin-icon'); - var pinText = document.getElementById('mokosuiteclient-pin-text'); - if (pinBtn && pinText) { - pinBtn.addEventListener('click', function(e) { - e.preventDefault(); - var current = pinText.textContent.trim(); - - if (current.indexOf('MOKO-') === 0) { - navigator.clipboard.writeText(current).then(function() { - var orig = pinText.textContent; - pinText.textContent = 'Copied!'; - setTimeout(function() { pinText.textContent = orig; }, 1500); - }); - return; - } - - if (pinBtn.dataset.busy) return; - pinBtn.dataset.busy = '1'; - if (pinIcon) pinIcon.className = 'icon-spinner icon-spin'; - pinText.textContent = '...'; - - var fd = new FormData(); - fd.append('', '1'); - - fetch('', { - method: 'POST', - headers: {'X-Requested-With': 'XMLHttpRequest'}, - body: fd - }) - .then(function(r) { return r.json(); }) - .then(function(data) { - if (data.success && data.pin) { - pinText.textContent = data.pin; - pinBtn.title = 'Support PIN — click to copy'; - if (pinIcon) pinIcon.className = 'icon-key'; - } else { - pinText.textContent = 'PIN'; - if (pinIcon) pinIcon.className = 'icon-key'; - } - delete pinBtn.dataset.busy; - }) - .catch(function() { - pinText.textContent = 'PIN'; - if (pinIcon) pinIcon.className = 'icon-key'; - delete pinBtn.dataset.busy; - }); - }); - } }); + diff --git a/source/packages/mod_mokosuiteclient_cpanel/tmpl/default.php b/source/packages/mod_mokosuiteclient_cpanel/tmpl/default.php index 4263c6ea..9a3fbc97 100644 --- a/source/packages/mod_mokosuiteclient_cpanel/tmpl/default.php +++ b/source/packages/mod_mokosuiteclient_cpanel/tmpl/default.php @@ -70,16 +70,10 @@ $diskColor = ($diskPct !== null && $diskPct > 90) ? 'bg-danger' : (($diskPct !== sitename ?? ''); ?> MokoSuite mokosuiteclient_version ?? ''); ?> - - - - - + !empty($supportPinAvailable), 'pin' => $supportPin ?? ''], + $token, 'cpanel' + ); ?> Joomla joomla_version ?? ''); ?> PHP php_version ?? ''); ?> db_type ?? ''); ?> @@ -95,37 +89,4 @@ $diskColor = ($diskPct !== null && $diskPct > 90) ? 'bg-danger' : (($diskPct !== - +