Fix CSP-blocked chart scripts, undefined CSS classes, and double-firing click handlers
- Add nonce to charts and ticket-preview drawer inline <script> blocks in DashboardView.php (both were CSP-blocked — charts never rendered) - Add .lt-modal-xs (280px) to base.css — used by quickStatus/quickAssign modals but was undefined, causing them to use full modal width - Fix showConfirmModal in utils.js: class="text-center" → "lt-text-center" (undefined class); escape newlines as <br> so multi-line messages render - Remove duplicate click-handler cases from DashboardView.php inline script that were already handled by dashboard.js, preventing double-firing (export-tickets, open-settings, remove-filter, etc. were all called twice) - Fix manual-refresh action to use lt.autoRefresh.now() instead of bare window.location.reload() so modal/focus guards are respected Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -1317,7 +1317,8 @@ select option:checked {
|
|||||||
.lt-modal-close:active { color: var(--accent-red); opacity: 0.7; }
|
.lt-modal-close:active { color: var(--accent-red); opacity: 0.7; }
|
||||||
.lt-modal-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
.lt-modal-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||||
|
|
||||||
/* Modal size modifier */
|
/* Modal size modifiers */
|
||||||
|
.lt-modal-xs { width: min(280px, 92vw); }
|
||||||
.lt-modal-sm { width: min(360px, 92vw); }
|
.lt-modal-sm { width: min(360px, 92vw); }
|
||||||
|
|
||||||
/* Modal header danger variant */
|
/* Modal header danger variant */
|
||||||
|
|||||||
@@ -198,9 +198,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
case 'open-settings-modal':
|
case 'open-settings-modal':
|
||||||
if (typeof openSettingsModal === 'function') openSettingsModal();
|
if (typeof openSettingsModal === 'function') openSettingsModal();
|
||||||
break;
|
break;
|
||||||
// Refresh
|
// Refresh — use lt.autoRefresh.now() so modal/focus guards are respected
|
||||||
case 'manual-refresh':
|
case 'manual-refresh':
|
||||||
window.location.reload();
|
if (window.lt && lt.autoRefresh) lt.autoRefresh.now();
|
||||||
|
else window.location.reload();
|
||||||
break;
|
break;
|
||||||
// Export
|
// Export
|
||||||
case 'toggle-export-menu':
|
case 'toggle-export-menu':
|
||||||
|
|||||||
+3
-3
@@ -26,7 +26,7 @@ function showConfirmModal(title, message, type = 'warning', onConfirm, onCancel
|
|||||||
const color = colors[type] || colors.warning;
|
const color = colors[type] || colors.warning;
|
||||||
const icon = icons[type] || icons.warning;
|
const icon = icons[type] || icons.warning;
|
||||||
const safeTitle = lt.escHtml(title);
|
const safeTitle = lt.escHtml(title);
|
||||||
const safeMessage = lt.escHtml(message);
|
const safeMessage = lt.escHtml(message).replace(/\n/g, '<br>');
|
||||||
|
|
||||||
document.body.insertAdjacentHTML('beforeend', `
|
document.body.insertAdjacentHTML('beforeend', `
|
||||||
<div class="lt-modal-overlay" id="${modalId}" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="${modalId}_title">
|
<div class="lt-modal-overlay" id="${modalId}" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="${modalId}_title">
|
||||||
@@ -35,8 +35,8 @@ function showConfirmModal(title, message, type = 'warning', onConfirm, onCancel
|
|||||||
<span class="lt-modal-title" id="${modalId}_title">${icon} ${safeTitle}</span>
|
<span class="lt-modal-title" id="${modalId}_title">${icon} ${safeTitle}</span>
|
||||||
<button class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
<button class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="lt-modal-body text-center">
|
<div class="lt-modal-body lt-text-center">
|
||||||
<p class="modal-message">${safeMessage}</p>
|
<p>${safeMessage}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="lt-modal-footer">
|
<div class="lt-modal-footer">
|
||||||
<button class="lt-btn lt-btn-primary" id="${modalId}_confirm">CONFIRM</button>
|
<button class="lt-btn lt-btn-primary" id="${modalId}_confirm">CONFIRM</button>
|
||||||
|
|||||||
+6
-14
@@ -205,7 +205,7 @@ include __DIR__ . '/layout_header.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script nonce="<?= $nonce ?>">
|
||||||
// ── Dashboard charts (Chart.js, loaded from CDN) ─────────────────
|
// ── Dashboard charts (Chart.js, loaded from CDN) ─────────────────
|
||||||
(function() {
|
(function() {
|
||||||
function waitForChart(cb, tries) {
|
function waitForChart(cb, tries) {
|
||||||
@@ -1124,27 +1124,19 @@ document.querySelectorAll('.lt-stat-card').forEach(function (card) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event delegation for click actions — only handles cases NOT covered by dashboard.js
|
// Event delegation — handles ONLY cases NOT covered by dashboard.js
|
||||||
// bulk-*, navigate, view-ticket, quick-*, set-view-mode, clear-selection, toggle-*
|
// (bulk-*, navigate, view-ticket, quick-*, set-view-mode, clear-selection, toggle-select-all,
|
||||||
// are all handled by dashboard.js to avoid double-firing (duplicate handlers = duplicate users in selects).
|
// toggle-row-checkbox, remove-filter, clear-all-filters, open/close/save-settings,
|
||||||
|
// open/toggle-export-menu, export-tickets, open-advanced-search are in dashboard.js)
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
var target = e.target.closest('[data-action]');
|
var target = e.target.closest('[data-action]');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
switch (target.getAttribute('data-action')) {
|
switch (target.getAttribute('data-action')) {
|
||||||
case 'open-settings': openSettingsModal(); break;
|
|
||||||
case 'close-settings': closeSettingsModal(); break;
|
|
||||||
case 'save-settings': saveSettings(); break;
|
|
||||||
case 'manual-refresh': if (lt.autoRefresh) lt.autoRefresh.now(); break;
|
|
||||||
case 'toggle-sidebar': if (typeof toggleSidebar==='function') toggleSidebar(); break;
|
case 'toggle-sidebar': if (typeof toggleSidebar==='function') toggleSidebar(); break;
|
||||||
case 'open-advanced-search': openAdvancedSearch(); break;
|
|
||||||
case 'close-advanced-search': closeAdvancedSearch(); break;
|
case 'close-advanced-search': closeAdvancedSearch(); break;
|
||||||
case 'reset-advanced-search': resetAdvancedSearch(); break;
|
case 'reset-advanced-search': resetAdvancedSearch(); break;
|
||||||
case 'toggle-export-menu': e.stopPropagation(); toggleExportMenu(e); break;
|
|
||||||
case 'export-tickets': e.preventDefault(); exportSelectedTickets(target.getAttribute('data-format')); break;
|
|
||||||
case 'save-filter': saveCurrentFilter(); break;
|
case 'save-filter': saveCurrentFilter(); break;
|
||||||
case 'delete-filter': deleteSavedFilter(); break;
|
case 'delete-filter': deleteSavedFilter(); break;
|
||||||
case 'remove-filter': removeFilter(target.getAttribute('data-filter-type'), target.getAttribute('data-filter-value')); break;
|
|
||||||
case 'clear-all-filters': window.location.href = '/'; break;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1206,7 +1198,7 @@ if (advForm) advForm.addEventListener('submit', performAdvancedSearch);
|
|||||||
</aside>
|
</aside>
|
||||||
<div class="lt-drawer-right-overlay" id="ticketPreviewDrawerOverlay"></div>
|
<div class="lt-drawer-right-overlay" id="ticketPreviewDrawerOverlay"></div>
|
||||||
|
|
||||||
<script>
|
<script nonce="<?= $nonce ?>">
|
||||||
// ── Ticket Preview Drawer ──────────────────────────────────────────
|
// ── Ticket Preview Drawer ──────────────────────────────────────────
|
||||||
(function() {
|
(function() {
|
||||||
var drawer = document.getElementById('ticketPreviewDrawer');
|
var drawer = document.getElementById('ticketPreviewDrawer');
|
||||||
|
|||||||
Reference in New Issue
Block a user