Add lt.autoRefresh, fix showToast in admin, clean up inline styles
- Replace all 8 showToast() calls in ApiKeysView.php with lt.toast.* — all toast calls in the codebase now use lt.toast directly - Add .duplicate-list, .duplicate-meta, .duplicate-hint CSS classes to dashboard.css; replace inline styles in duplicate detection JS with them - Add dashboardAutoRefresh() using lt.autoRefresh — reloads page every 5 minutes, skipping if a modal is open or user is typing in an input - Add REFRESH button to dashboard header that triggers lt.autoRefresh.now() for immediate manual refresh with timer restart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4642,6 +4642,32 @@ table td:nth-child(4) {
|
|||||||
border-top: 1px solid var(--terminal-green);
|
border-top: 1px solid var(--terminal-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== DUPLICATE DETECTION LIST ===== */
|
||||||
|
|
||||||
|
.duplicate-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
color: var(--terminal-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duplicate-list li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duplicate-list a {
|
||||||
|
color: var(--terminal-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duplicate-meta {
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
.duplicate-hint {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--terminal-green-dim);
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== INLINE ALERT BOXES ===== */
|
/* ===== INLINE ALERT BOXES ===== */
|
||||||
|
|
||||||
.inline-error {
|
.inline-error {
|
||||||
|
|||||||
@@ -1819,6 +1819,30 @@ function hideLoadingOverlay(element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// AUTO-REFRESH (lt.autoRefresh integration)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the dashboard, but skip if a modal is open or user is typing.
|
||||||
|
* Registered with lt.autoRefresh so it runs every 5 minutes automatically.
|
||||||
|
*/
|
||||||
|
function dashboardAutoRefresh() {
|
||||||
|
// Don't interrupt the user if a modal is open
|
||||||
|
if (document.querySelector('.lt-modal-overlay[aria-hidden="false"]')) return;
|
||||||
|
// Don't interrupt if focus is in a text input
|
||||||
|
const tag = document.activeElement?.tagName;
|
||||||
|
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Only run auto-refresh on the dashboard, not on ticket pages
|
||||||
|
if (!window.location.pathname.includes('/ticket/')) {
|
||||||
|
lt.autoRefresh.start(dashboardAutoRefresh, 5 * 60 * 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// RELATIVE TIMESTAMPS
|
// RELATIVE TIMESTAMPS
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@@ -295,18 +295,18 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
const listDiv = document.getElementById('duplicatesList');
|
const listDiv = document.getElementById('duplicatesList');
|
||||||
|
|
||||||
if (data.success && data.duplicates && data.duplicates.length > 0) {
|
if (data.success && data.duplicates && data.duplicates.length > 0) {
|
||||||
let html = '<ul style="margin: 0; padding-left: 1.5rem; color: var(--terminal-green);">';
|
let html = '<ul class="duplicate-list">';
|
||||||
data.duplicates.forEach(dup => {
|
data.duplicates.forEach(dup => {
|
||||||
html += `<li style="margin-bottom: 0.5rem;">
|
html += `<li>
|
||||||
<a href="/ticket/${escapeHtml(dup.ticket_id)}" target="_blank" style="color: var(--terminal-green);">
|
<a href="/ticket/${escapeHtml(dup.ticket_id)}" target="_blank">
|
||||||
#${escapeHtml(dup.ticket_id)}
|
#${escapeHtml(dup.ticket_id)}
|
||||||
</a>
|
</a>
|
||||||
- ${escapeHtml(dup.title)}
|
- ${escapeHtml(dup.title)}
|
||||||
<span style="color: var(--terminal-amber);">(${dup.similarity}% match, ${escapeHtml(dup.status)})</span>
|
<span class="duplicate-meta">(${dup.similarity}% match, ${escapeHtml(dup.status)})</span>
|
||||||
</li>`;
|
</li>`;
|
||||||
});
|
});
|
||||||
html += '</ul>';
|
html += '</ul>';
|
||||||
html += '<p style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--terminal-green-dim);">Consider checking these tickets before creating a new one.</p>';
|
html += '<p class="duplicate-hint">Consider checking these tickets before creating a new one.</p>';
|
||||||
|
|
||||||
listDiv.innerHTML = html;
|
listDiv.innerHTML = html;
|
||||||
warningDiv.style.display = 'block';
|
warningDiv.style.display = 'block';
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<button class="btn" style="font-size:0.75rem; padding: 0.2rem 0.5rem;" data-action="manual-refresh" title="Refresh now (auto-refreshes every 5 min)" aria-label="Refresh dashboard">REFRESH</button>
|
||||||
<button class="settings-icon" title="Settings (Alt+S)" data-action="open-settings" aria-label="Settings">[ CFG ]</button>
|
<button class="settings-icon" title="Settings (Alt+S)" data-action="open-settings" aria-label="Settings">[ CFG ]</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -861,6 +862,10 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
openSettingsModal();
|
openSettingsModal();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'manual-refresh':
|
||||||
|
lt.autoRefresh.now();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'close-settings':
|
case 'close-settings':
|
||||||
closeSettingsModal();
|
closeSettingsModal();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
const expiresIn = document.getElementById('expiresIn').value;
|
const expiresIn = document.getElementById('expiresIn').value;
|
||||||
|
|
||||||
if (!keyName) {
|
if (!keyName) {
|
||||||
showToast('Please enter a key name', 'error');
|
lt.toast.error('Please enter a key name');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,15 +212,15 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
document.getElementById('newKeyDisplay').style.display = 'block';
|
document.getElementById('newKeyDisplay').style.display = 'block';
|
||||||
document.getElementById('keyName').value = '';
|
document.getElementById('keyName').value = '';
|
||||||
|
|
||||||
showToast('API key generated successfully', 'success');
|
lt.toast.success('API key generated successfully');
|
||||||
|
|
||||||
// Reload page after 5 seconds to show new key in table
|
// Reload page after 5 seconds to show new key in table
|
||||||
setTimeout(() => location.reload(), 5000);
|
setTimeout(() => location.reload(), 5000);
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error || 'Failed to generate API key', 'error');
|
lt.toast.error(data.error || 'Failed to generate API key');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('Error generating API key: ' + error.message, 'error');
|
lt.toast.error('Error generating API key: ' + error.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
const keyInput = document.getElementById('newKeyValue');
|
const keyInput = document.getElementById('newKeyValue');
|
||||||
keyInput.select();
|
keyInput.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
showToast('API key copied to clipboard', 'success');
|
lt.toast.success('API key copied to clipboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function revokeKey(keyId) {
|
async function revokeKey(keyId) {
|
||||||
@@ -249,13 +249,13 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
showToast('API key revoked successfully', 'success');
|
lt.toast.success('API key revoked successfully');
|
||||||
location.reload();
|
location.reload();
|
||||||
} else {
|
} else {
|
||||||
showToast(data.error || 'Failed to revoke API key', 'error');
|
lt.toast.error(data.error || 'Failed to revoke API key');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('Error revoking API key: ' + error.message, 'error');
|
lt.toast.error('Error revoking API key: ' + error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user