fix(web-ui): fix quick actions not firing, add toast feedback, suppress install handler warning

- base.html: add htmx:afterSettle listener to set data-loaded on tab
  containers after HTMX swaps their content, preventing the overview
  partial from being re-fetched (and handlers lost) on every tab switch
- base.html: call htmx.process() in loadOverviewDirect/loadPluginsDirect
  fallbacks so buttons get HTMX handlers even if HTMX finished its
  initial body scan before the fallback fetch completed
- overview.html + index.html (11 buttons): replace event.detail.xhr.responseJSON
  (undefined in HTMX 1.9.x) with JSON.parse(event.detail.xhr.responseText)
  so quick action toast notifications actually fire
- plugins_manager.js: add guarded htmx:afterSettle listener that only calls
  attachInstallButtonHandler when #install-plugin-from-url is in the DOM,
  eliminating the spurious console warning on non-plugin tab loads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-05-23 10:21:20 -04:00
parent 327e87f735
commit a5c7ef20ec
4 changed files with 41 additions and 11 deletions

View File

@@ -7484,6 +7484,16 @@ setTimeout(function() {
}, 500); }, 500);
}, 200); }, 200);
// Re-run install button wiring after HTMX settles the plugins tab content.
// Guard with element check so it only fires when the plugins partial is in the DOM,
// preventing spurious warnings on other tab loads.
document.addEventListener('htmx:afterSettle', function() {
if (document.getElementById('install-plugin-from-url') &&
typeof window.attachInstallButtonHandler === 'function') {
window.attachInstallButtonHandler();
}
});
// ─── Starlark Apps Integration ────────────────────────────────────────────── // ─── Starlark Apps Integration ──────────────────────────────────────────────
(function() { (function() {

View File

@@ -349,6 +349,20 @@
} }
} }
}); });
// Set data-loaded on tab containers after HTMX settles their content,
// preventing repeated re-fetches on every tab switch.
// Scoped to elements with hx-trigger="revealed" (tab containers only) so
// modals and plugin config panels that legitimately reload are unaffected.
document.body.addEventListener('htmx:afterSettle', function(event) {
if (event.detail && event.detail.target) {
var target = event.detail.target;
var trigger = target.getAttribute('hx-trigger') || '';
if (trigger.includes('revealed')) {
target.setAttribute('data-loaded', 'true');
}
}
});
} else { } else {
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setupScriptExecution); document.addEventListener('DOMContentLoaded', setupScriptExecution);
@@ -411,6 +425,9 @@
.then(html => { .then(html => {
clearTimeout(timeout); clearTimeout(timeout);
content.innerHTML = html; content.innerHTML = html;
if (typeof htmx !== 'undefined') {
htmx.process(content);
}
// Trigger full initialization chain // Trigger full initialization chain
if (window.pluginManager) { if (window.pluginManager) {
window.pluginManager.initialized = false; window.pluginManager.initialized = false;
@@ -1030,6 +1047,9 @@
.then(html => { .then(html => {
overviewContent.innerHTML = html; overviewContent.innerHTML = html;
overviewContent.setAttribute('data-loaded', 'true'); overviewContent.setAttribute('data-loaded', 'true');
if (typeof htmx !== 'undefined') {
htmx.process(overviewContent);
}
// Re-initialize Alpine.js for the new content // Re-initialize Alpine.js for the new content
if (window.Alpine) { if (window.Alpine) {
window.Alpine.initTree(overviewContent); window.Alpine.initTree(overviewContent);

View File

@@ -73,7 +73,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "start_display"}' hx-vals='{"action": "start_display"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Display started', event.detail.xhr.responseJSON.status || 'success'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Display started', d.status || 'success'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700"> class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700">
<i class="fas fa-play mr-2"></i> <i class="fas fa-play mr-2"></i>
Start Display Start Display
@@ -82,7 +82,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "stop_display"}' hx-vals='{"action": "stop_display"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Display stopped', event.detail.xhr.responseJSON.status || 'success'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Display stopped', d.status || 'success'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700"> class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700">
<i class="fas fa-stop mr-2"></i> <i class="fas fa-stop mr-2"></i>
Stop Display Stop Display
@@ -91,7 +91,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "git_pull"}' hx-vals='{"action": "git_pull"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Code update completed', event.detail.xhr.responseJSON.status || 'info'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Code update completed', d.status || 'info'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"> class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50">
<i class="fas fa-download mr-2"></i> <i class="fas fa-download mr-2"></i>
Update Code Update Code
@@ -101,7 +101,7 @@
hx-vals='{"action": "reboot_system"}' hx-vals='{"action": "reboot_system"}'
hx-confirm="Are you sure you want to reboot the system?" hx-confirm="Are you sure you want to reboot the system?"
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'System rebooting...', event.detail.xhr.responseJSON.status || 'info'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'System rebooting...', d.status || 'info'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-yellow-600 hover:bg-yellow-700"> class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-yellow-600 hover:bg-yellow-700">
<i class="fas fa-power-off mr-2"></i> <i class="fas fa-power-off mr-2"></i>
Reboot System Reboot System

View File

@@ -151,7 +151,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "start_display"}' hx-vals='{"action": "start_display"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Display started', event.detail.xhr.responseJSON.status || 'success'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Display started', d.status || 'success'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-green-600 hover:bg-green-700"> class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-green-600 hover:bg-green-700">
<i class="fas fa-play mr-2"></i> <i class="fas fa-play mr-2"></i>
Start Display Start Display
@@ -160,7 +160,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "stop_display"}' hx-vals='{"action": "stop_display"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Display stopped', event.detail.xhr.responseJSON.status || 'success'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Display stopped', d.status || 'success'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-red-600 hover:bg-red-700"> class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-red-600 hover:bg-red-700">
<i class="fas fa-stop mr-2"></i> <i class="fas fa-stop mr-2"></i>
Stop Display Stop Display
@@ -170,7 +170,7 @@
hx-vals='{"action": "git_pull"}' hx-vals='{"action": "git_pull"}'
hx-confirm="This will stash any local changes and update the code. Continue?" hx-confirm="This will stash any local changes and update the code. Continue?"
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Code update completed', event.detail.xhr.responseJSON.status || 'info'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Code update completed', d.status || 'info'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50"> class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50">
<i class="fas fa-download mr-2"></i> <i class="fas fa-download mr-2"></i>
Update Code Update Code
@@ -180,7 +180,7 @@
hx-vals='{"action": "reboot_system"}' hx-vals='{"action": "reboot_system"}'
hx-confirm="Are you sure you want to reboot the system?" hx-confirm="Are you sure you want to reboot the system?"
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'System rebooting...', event.detail.xhr.responseJSON.status || 'info'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'System rebooting...', d.status || 'info'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-yellow-600 hover:bg-yellow-700"> class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-yellow-600 hover:bg-yellow-700">
<i class="fas fa-power-off mr-2"></i> <i class="fas fa-power-off mr-2"></i>
Reboot System Reboot System
@@ -190,7 +190,7 @@
hx-vals='{"action": "shutdown_system"}' hx-vals='{"action": "shutdown_system"}'
hx-confirm="Are you sure you want to shut down the system? This will power off the Raspberry Pi." hx-confirm="Are you sure you want to shut down the system? This will power off the Raspberry Pi."
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'System shutting down...', event.detail.xhr.responseJSON.status || 'info'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'System shutting down...', d.status || 'info'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-red-800 hover:bg-red-900"> class="inline-flex items-center px-4 py-2 border border-transparent text-base font-semibold rounded-md text-white bg-red-800 hover:bg-red-900">
<i class="fas fa-power-off mr-2"></i> <i class="fas fa-power-off mr-2"></i>
Shutdown System Shutdown System
@@ -199,7 +199,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "restart_display_service"}' hx-vals='{"action": "restart_display_service"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Display service restarted', event.detail.xhr.responseJSON.status || 'success'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Display service restarted', d.status || 'success'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50"> class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50">
<i class="fas fa-redo mr-2"></i> <i class="fas fa-redo mr-2"></i>
Restart Display Service Restart Display Service
@@ -208,7 +208,7 @@
<button hx-post="/api/v3/system/action" <button hx-post="/api/v3/system/action"
hx-vals='{"action": "restart_web_service"}' hx-vals='{"action": "restart_web_service"}'
hx-swap="none" hx-swap="none"
hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr && event.detail.xhr.responseJSON) { showNotification(event.detail.xhr.responseJSON.message || 'Web service restarted', event.detail.xhr.responseJSON.status || 'success'); }" hx-on:htmx:after-request="if (typeof showNotification !== 'undefined' && event.detail.xhr) { try { var d = JSON.parse(event.detail.xhr.responseText); showNotification(d.message || 'Web service restarted', d.status || 'success'); } catch(e) {} }"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50"> class="inline-flex items-center px-4 py-2 border border-gray-300 text-base font-semibold rounded-md text-gray-900 bg-white hover:bg-gray-50">
<i class="fas fa-redo mr-2"></i> <i class="fas fa-redo mr-2"></i>
Restart Web Service Restart Web Service