Migrate all raw fetch() calls to lt.api, fix CSS fallback values

- Replace all 23 raw fetch() calls in dashboard.js and ticket.js with
  lt.api.get/post/delete — removes manual CSRF header injection,
  manual JSON parsing boilerplate, and response.ok checks throughout
- dashboard.js: 10 calls (inline save x2, template GET, 5x bulk ops,
  quick-status, quick-assign)
- ticket.js: 13 calls (main save, add/update/delete comment x3, reply,
  assign, metadata update, status change, deps GET/POST/DELETE,
  attachments GET, delete attachment)
- Remove stale csrf_token from deleteAttachment body (lt.api sends the
  X-CSRF-Token header automatically)
- Fix CSS variable fallbacks in ticket.css: replace
  var(--text-primary, #f7fafc) and var(--bg-secondary, #1a202c)
  with plain var(--text-primary) and var(--bg-secondary)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 11:27:46 -04:00
parent e179709fc3
commit 11f75fd823
3 changed files with 55 additions and 316 deletions

View File

@@ -1410,16 +1410,16 @@ body.dark-mode .status-select option {
/* Dark mode for Activity tab and general improvements */ /* Dark mode for Activity tab and general improvements */
body.dark-mode .tab-content { body.dark-mode .tab-content {
color: var(--text-primary, #f7fafc); color: var(--text-primary);
} }
body.dark-mode #activity-tab { body.dark-mode #activity-tab {
background: var(--bg-secondary, #1a202c); background: var(--bg-secondary);
color: var(--text-primary, #f7fafc); color: var(--text-primary);
} }
body.dark-mode #activity-tab p { body.dark-mode #activity-tab p {
color: var(--text-primary, #f7fafc); color: var(--text-primary);
} }
/* Comprehensive Dark Mode Fix - terminal CSS variables apply throughout */ /* Comprehensive Dark Mode Fix - terminal CSS variables apply throughout */

View File

@@ -522,24 +522,7 @@ function quickSave() {
priority: parseInt(prioritySelect.value) priority: parseInt(prioritySelect.value)
}; };
fetch('/api/update_ticket.php', { lt.api.post('/api/update_ticket.php', data)
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify(data)
})
.then(response => {
return response.text().then(text => {
try {
return JSON.parse(text);
} catch (e) {
throw new Error('Invalid JSON response: ' + text);
}
});
})
.then(result => { .then(result => {
if (result.success) { if (result.success) {
// Update the hamburger menu display // Update the hamburger menu display
@@ -589,19 +572,7 @@ function saveTicket() {
} }
}); });
fetch('/api/update_ticket.php', { lt.api.post('/api/update_ticket.php', { ticket_id: ticketId, ...data })
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
...data
})
})
.then(response => response.json())
.then(data => { .then(data => {
if(data.success) { if(data.success) {
const statusDisplay = document.getElementById('statusDisplay'); const statusDisplay = document.getElementById('statusDisplay');
@@ -640,15 +611,7 @@ function loadTemplate() {
} }
// Fetch template data // Fetch template data
fetch(`/api/get_template.php?template_id=${templateId}`, { lt.api.get(`/api/get_template.php?template_id=${templateId}`)
credentials: 'same-origin'
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch template');
}
return response.json();
})
.then(data => { .then(data => {
if (data.success && data.template) { if (data.success && data.template) {
const template = data.template; const template = data.template;
@@ -770,19 +733,10 @@ function bulkClose() {
function performBulkCloseAction(ticketIds) { function performBulkCloseAction(ticketIds) {
fetch('/api/bulk_operation.php', { lt.api.post('/api/bulk_operation.php', {
method: 'POST', operation_type: 'bulk_close',
credentials: 'same-origin', ticket_ids: ticketIds
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
operation_type: 'bulk_close',
ticket_ids: ticketIds
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
if (data.failed > 0) { if (data.failed > 0) {
@@ -866,20 +820,11 @@ function performBulkAssign() {
return; return;
} }
fetch('/api/bulk_operation.php', { lt.api.post('/api/bulk_operation.php', {
method: 'POST', operation_type: 'bulk_assign',
credentials: 'same-origin', ticket_ids: ticketIds,
headers: { parameters: { assigned_to: parseInt(userId) }
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
operation_type: 'bulk_assign',
ticket_ids: ticketIds,
parameters: { assigned_to: parseInt(userId) }
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
closeBulkAssignModal(); closeBulkAssignModal();
@@ -951,20 +896,11 @@ function performBulkPriority() {
return; return;
} }
fetch('/api/bulk_operation.php', { lt.api.post('/api/bulk_operation.php', {
method: 'POST', operation_type: 'bulk_priority',
credentials: 'same-origin', ticket_ids: ticketIds,
headers: { parameters: { priority: parseInt(priority) }
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
operation_type: 'bulk_priority',
ticket_ids: ticketIds,
parameters: { priority: parseInt(priority) }
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
closeBulkPriorityModal(); closeBulkPriorityModal();
@@ -1067,20 +1003,11 @@ function performBulkStatusChange() {
return; return;
} }
fetch('/api/bulk_operation.php', { lt.api.post('/api/bulk_operation.php', {
method: 'POST', operation_type: 'bulk_status',
credentials: 'same-origin', ticket_ids: ticketIds,
headers: { parameters: { status: status }
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
operation_type: 'bulk_status',
ticket_ids: ticketIds,
parameters: { status: status }
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
closeBulkStatusModal(); closeBulkStatusModal();
if (data.success) { if (data.success) {
@@ -1140,19 +1067,10 @@ function closeBulkDeleteModal() {
function performBulkDelete() { function performBulkDelete() {
const ticketIds = getSelectedTicketIds(); const ticketIds = getSelectedTicketIds();
fetch('/api/bulk_operation.php', { lt.api.post('/api/bulk_operation.php', {
method: 'POST', operation_type: 'bulk_delete',
credentials: 'same-origin', ticket_ids: ticketIds
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
operation_type: 'bulk_delete',
ticket_ids: ticketIds
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
closeBulkDeleteModal(); closeBulkDeleteModal();
if (data.success) { if (data.success) {
@@ -1341,19 +1259,7 @@ function closeQuickStatusModal() {
function performQuickStatusChange(ticketId) { function performQuickStatusChange(ticketId) {
const newStatus = document.getElementById('quickStatusSelect').value; const newStatus = document.getElementById('quickStatusSelect').value;
fetch('/api/update_ticket.php', { lt.api.post('/api/update_ticket.php', { ticket_id: ticketId, status: newStatus })
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
status: newStatus
})
})
.then(response => response.json())
.then(data => { .then(data => {
closeQuickStatusModal(); closeQuickStatusModal();
if (data.success) { if (data.success) {
@@ -1423,19 +1329,7 @@ function closeQuickAssignModal() {
function performQuickAssign(ticketId) { function performQuickAssign(ticketId) {
const assignedTo = document.getElementById('quickAssignSelect').value || null; const assignedTo = document.getElementById('quickAssignSelect').value || null;
fetch('/api/assign_ticket.php', { lt.api.post('/api/assign_ticket.php', { ticket_id: ticketId, assigned_to: assignedTo })
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
assigned_to: assignedTo
})
})
.then(response => response.json())
.then(data => { .then(data => {
closeQuickAssignModal(); closeQuickAssignModal();
if (data.success) { if (data.success) {

View File

@@ -48,28 +48,7 @@ function saveTicket() {
} }
// Use the correct API path // Use the correct API path
const apiUrl = '/api/update_ticket.php'; lt.api.post('/api/update_ticket.php', { ticket_id: ticketId, ...data })
fetch(apiUrl, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
...data
})
})
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error('Network response was not ok');
});
}
return response.json();
})
.then(data => { .then(data => {
if(data.success) { if(data.success) {
const statusDisplay = document.getElementById('statusDisplay'); const statusDisplay = document.getElementById('statusDisplay');
@@ -150,26 +129,10 @@ function addComment() {
const markdownMaster = document.getElementById('markdownMaster'); const markdownMaster = document.getElementById('markdownMaster');
const isMarkdownEnabled = markdownMaster ? markdownMaster.checked : false; const isMarkdownEnabled = markdownMaster ? markdownMaster.checked : false;
fetch('/api/add_comment.php', { lt.api.post('/api/add_comment.php', {
method: 'POST', ticket_id: ticketId,
credentials: 'same-origin', comment_text: commentText,
headers: { markdown_enabled: isMarkdownEnabled
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
comment_text: commentText,
markdown_enabled: isMarkdownEnabled
})
})
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error('Network response was not ok');
});
}
return response.json();
}) })
.then(data => { .then(data => {
if(data.success) { if(data.success) {
@@ -317,16 +280,7 @@ function handleAssignmentChange() {
const ticketId = window.ticketData.id; const ticketId = window.ticketData.id;
const assignedTo = this.value || null; const assignedTo = this.value || null;
fetch('/api/assign_ticket.php', { lt.api.post('/api/assign_ticket.php', { ticket_id: ticketId, assigned_to: assignedTo })
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({ ticket_id: ticketId, assigned_to: assignedTo })
})
.then(response => response.json())
.then(data => { .then(data => {
if (!data.success) { if (!data.success) {
lt.toast.error('Error updating assignment'); lt.toast.error('Error updating assignment');
@@ -350,19 +304,10 @@ function handleMetadataChanges() {
function updateTicketField(fieldName, newValue) { function updateTicketField(fieldName, newValue) {
const ticketId = window.ticketData.id; const ticketId = window.ticketData.id;
fetch('/api/update_ticket.php', { lt.api.post('/api/update_ticket.php', {
method: 'POST', ticket_id: ticketId,
credentials: 'same-origin', [fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
[fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (!data.success) { if (!data.success) {
lt.toast.error(`Error updating ${fieldName}`); lt.toast.error(`Error updating ${fieldName}`);
@@ -456,35 +401,7 @@ function performStatusChange(statusSelect, selectedOption, newStatus) {
} }
// Update status via API // Update status via API
fetch('/api/update_ticket.php', { lt.api.post('/api/update_ticket.php', { ticket_id: ticketId, status: newStatus })
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
status: newStatus
})
})
.then(async response => {
const text = await response.text();
if (!response.ok) {
try {
const data = JSON.parse(text);
throw new Error(data.error || 'Server returned an error');
} catch (parseError) {
throw new Error(text || 'Network response was not ok');
}
}
try {
return JSON.parse(text);
} catch (parseError) {
throw new Error('Invalid JSON response from server');
}
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Update the dropdown to show new status as current // Update the dropdown to show new status as current
@@ -575,15 +492,7 @@ function showTab(tabName) {
function loadDependencies() { function loadDependencies() {
const ticketId = window.ticketData.id; const ticketId = window.ticketData.id;
fetch(`/api/ticket_dependencies.php?ticket_id=${ticketId}`, { lt.api.get(`/api/ticket_dependencies.php?ticket_id=${ticketId}`)
credentials: 'same-origin'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
renderDependencies(data.dependencies); renderDependencies(data.dependencies);
@@ -694,20 +603,11 @@ function addDependency() {
return; return;
} }
fetch('/api/ticket_dependencies.php', { lt.api.post('/api/ticket_dependencies.php', {
method: 'POST', ticket_id: ticketId,
credentials: 'same-origin', depends_on_id: dependsOnId,
headers: { dependency_type: dependencyType
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
depends_on_id: dependsOnId,
dependency_type: dependencyType
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
lt.toast.success('Dependency added', 3000); lt.toast.success('Dependency added', 3000);
@@ -727,18 +627,7 @@ function removeDependency(dependencyId) {
return; return;
} }
fetch('/api/ticket_dependencies.php', { lt.api.delete('/api/ticket_dependencies.php', { dependency_id: dependencyId })
method: 'DELETE',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
dependency_id: dependencyId
})
})
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
lt.toast.success('Dependency removed', 3000); lt.toast.success('Dependency removed', 3000);
@@ -895,10 +784,7 @@ function loadAttachments() {
if (!container) return; if (!container) return;
fetch(`/api/upload_attachment.php?ticket_id=${ticketId}`, { lt.api.get(`/api/upload_attachment.php?ticket_id=${ticketId}`)
credentials: 'same-origin'
})
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
renderAttachments(data.attachments || []); renderAttachments(data.attachments || []);
@@ -973,19 +859,7 @@ function deleteAttachment(attachmentId) {
return; return;
} }
fetch('/api/delete_attachment.php', { lt.api.post('/api/delete_attachment.php', { attachment_id: attachmentId })
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
attachment_id: attachmentId,
csrf_token: window.CSRF_TOKEN
})
})
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
lt.toast.success('Attachment deleted', 3000); lt.toast.success('Attachment deleted', 3000);
@@ -1312,20 +1186,11 @@ function saveEditComment(commentId) {
const markdownEnabled = markdownCheckbox ? markdownCheckbox.checked : false; const markdownEnabled = markdownCheckbox ? markdownCheckbox.checked : false;
// Send update request // Send update request
fetch('/api/update_comment.php', { lt.api.post('/api/update_comment.php', {
method: 'POST', comment_id: commentId,
credentials: 'same-origin', comment_text: newText,
headers: { markdown_enabled: markdownEnabled
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
comment_id: commentId,
comment_text: newText,
markdown_enabled: markdownEnabled
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Update the comment display // Update the comment display
@@ -1396,18 +1261,7 @@ function deleteComment(commentId) {
return; return;
} }
fetch('/api/delete_comment.php', { lt.api.post('/api/delete_comment.php', { comment_id: commentId })
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
comment_id: commentId
})
})
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Remove the comment from the DOM // Remove the comment from the DOM
@@ -1498,21 +1352,12 @@ function submitReply(parentCommentId) {
const commentText = replyText.value.trim(); const commentText = replyText.value.trim();
const isMarkdownEnabled = replyMarkdown ? replyMarkdown.checked : false; const isMarkdownEnabled = replyMarkdown ? replyMarkdown.checked : false;
fetch('/api/add_comment.php', { lt.api.post('/api/add_comment.php', {
method: 'POST', ticket_id: ticketId,
credentials: 'same-origin', comment_text: commentText,
headers: { markdown_enabled: isMarkdownEnabled,
'Content-Type': 'application/json', parent_comment_id: parentCommentId
'X-CSRF-Token': window.CSRF_TOKEN
},
body: JSON.stringify({
ticket_id: ticketId,
comment_text: commentText,
markdown_enabled: isMarkdownEnabled,
parent_comment_id: parentCommentId
})
}) })
.then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Close the reply form // Close the reply form