Add SARIF file submission button and form

This commit is contained in:
Francesco Bertolaccini
2026-03-26 13:26:36 +00:00
parent a6c9a3755c
commit fbd9c1a403
2 changed files with 204 additions and 8 deletions
@@ -12,6 +12,7 @@
<h1 class="nav-title">Buttercup CRS Dashboard</h1>
<div class="nav-buttons">
<button id="submit-task-btn" class="btn btn-primary">Submit Task</button>
<button id="submit-sarif-btn" class="btn btn-primary">Submit SARIF</button>
<button id="refresh-btn" class="btn btn-secondary">Refresh</button>
</div>
</div>
@@ -179,6 +180,36 @@
</div>
</div>
<!-- SARIF Upload Modal -->
<div id="sarif-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Submit SARIF File</h2>
<span class="close" id="close-sarif-modal">&times;</span>
</div>
<form id="sarif-form">
<div class="form-group">
<label for="sarif-task-select">Task:</label>
<select id="sarif-task-select" required>
<option value="">Select a task...</option>
</select>
</div>
<div class="form-group">
<label for="sarif-file-input">SARIF File (.sarif or .json):</label>
<input type="file" id="sarif-file-input" accept=".sarif,.json" required>
</div>
<div id="sarif-preview" class="form-group" style="display: none;">
<label>Preview:</label>
<pre id="sarif-preview-content" style="max-height: 200px; overflow: auto; background: var(--bg-secondary); padding: 0.75rem; border-radius: 6px; font-size: 0.8rem;"></pre>
</div>
<div class="form-buttons">
<button type="button" id="cancel-sarif-btn" class="btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary">Submit SARIF</button>
</div>
</form>
</div>
</div>
<!-- Notifications container -->
<div id="notifications"></div>
@@ -32,6 +32,16 @@ const elements = {
loadJsonBtn: document.getElementById('load-json-btn'),
loadJsonInput: document.getElementById('load-json-input'),
cancelBtn: document.getElementById('cancel-btn'),
submitSarifBtn: document.getElementById('submit-sarif-btn'),
sarifModal: document.getElementById('sarif-modal'),
sarifModalContent: document.querySelector('#sarif-modal .modal-content'),
closeSarifModal: document.getElementById('close-sarif-modal'),
sarifForm: document.getElementById('sarif-form'),
sarifTaskSelect: document.getElementById('sarif-task-select'),
sarifFileInput: document.getElementById('sarif-file-input'),
sarifPreview: document.getElementById('sarif-preview'),
sarifPreviewContent: document.getElementById('sarif-preview-content'),
cancelSarifBtn: document.getElementById('cancel-sarif-btn'),
tasksContainer: document.getElementById('tasks-container'),
povsContainer: document.getElementById('povs-container'),
patchesContainer: document.getElementById('patches-container'),
@@ -94,6 +104,21 @@ function setupEventListeners() {
});
elements.loadJsonInput.addEventListener('change', handleJsonFileLoad);
}
elements.submitSarifBtn.addEventListener('click', () => {
openSarifModal();
});
elements.closeSarifModal.addEventListener('click', () => {
elements.sarifModal.style.display = 'none';
});
elements.cancelSarifBtn.addEventListener('click', () => {
elements.sarifModal.style.display = 'none';
});
elements.sarifFileInput.addEventListener('change', handleSarifFilePreview);
elements.sarifForm.addEventListener('submit', handleSarifSubmission);
elements.refreshBtn.addEventListener('click', loadDashboard);
@@ -138,19 +163,24 @@ function setupEventListeners() {
let mousePressedOutside = false;
window.addEventListener('mousedown', (event) => {
// Check if mouse was pressed outside both modal contents
if (!elements.taskModalContent.contains(event.target) && !elements.detailModalContent.contains(event.target)) {
mousePressedOutside = true;
} else {
mousePressedOutside = false;
}
// Check if mouse was pressed outside all modal contents
const insideAnyModal =
elements.taskModalContent.contains(event.target) ||
elements.detailModalContent.contains(event.target) ||
elements.sarifModalContent.contains(event.target);
mousePressedOutside = !insideAnyModal;
});
window.addEventListener('mouseup', (event) => {
// Only close if mouse was pressed outside and released outside
if (mousePressedOutside && !elements.taskModalContent.contains(event.target) && !elements.detailModalContent.contains(event.target)) {
const insideAnyModal =
elements.taskModalContent.contains(event.target) ||
elements.detailModalContent.contains(event.target) ||
elements.sarifModalContent.contains(event.target);
if (mousePressedOutside && !insideAnyModal) {
elements.taskModal.style.display = 'none';
elements.detailModal.style.display = 'none';
elements.sarifModal.style.display = 'none';
}
});
}
@@ -800,7 +830,10 @@ function renderTaskDetail(task) {
</div>
${task.status === 'active' ? `
<div style="margin-top: 1rem;">
<div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
<button class="btn btn-primary" onclick="openSarifModal('${task.task_id}')">
Submit SARIF
</button>
<button class="btn btn-danger" onclick="cancelTask('${task.task_id}')">
Cancel Task
</button>
@@ -940,6 +973,138 @@ async function cancelTask(taskId) {
}
}
// Open SARIF modal, optionally pre-selecting a task
function openSarifModal(preselectedTaskId) {
// Populate the task dropdown with active tasks
const activeTaskList = tasks.filter(t => t.status === 'active');
elements.sarifTaskSelect.innerHTML =
'<option value="">Select a task...</option>';
activeTaskList.forEach(t => {
const label = t.name || t.project_name;
const opt = document.createElement('option');
opt.value = t.task_id;
opt.textContent = `${label} (${t.task_id.substring(0, 8)}...)`;
elements.sarifTaskSelect.appendChild(opt);
});
if (preselectedTaskId) {
elements.sarifTaskSelect.value = preselectedTaskId;
}
// Reset file input and preview
elements.sarifFileInput.value = '';
elements.sarifPreview.style.display = 'none';
elements.sarifPreviewContent.textContent = '';
elements.sarifModal.style.display = 'block';
}
// Preview SARIF file contents when selected
function handleSarifFilePreview(event) {
const file = event.target.files[0];
if (!file) {
elements.sarifPreview.style.display = 'none';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const parsed = JSON.parse(e.target.result);
const runsCount = (parsed.runs || []).length;
const resultsCount = (parsed.runs || []).reduce(
(sum, run) => sum + (run.results || []).length,
0,
);
const version = parsed.version || 'unknown';
const preview =
`SARIF version: ${version}\n` +
`Runs: ${runsCount}\n` +
`Results: ${resultsCount}\n\n` +
JSON.stringify(parsed, null, 2).substring(0, 500);
elements.sarifPreviewContent.textContent =
preview.length >= 500 ? preview + '\n...' : preview;
elements.sarifPreview.style.display = 'block';
} catch (err) {
elements.sarifPreviewContent.textContent =
'Invalid JSON: ' + err.message;
elements.sarifPreview.style.display = 'block';
}
};
reader.readAsText(file);
}
// Handle SARIF form submission
async function handleSarifSubmission(event) {
event.preventDefault();
const taskId = elements.sarifTaskSelect.value;
if (!taskId) {
showNotification('Please select a task', 'error');
return;
}
const file = elements.sarifFileInput.files[0];
if (!file) {
showNotification('Please select a SARIF file', 'error');
return;
}
const submitBtn = elements.sarifForm.querySelector(
'button[type="submit"]',
);
const originalText = submitBtn.textContent;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner"></span> Submitting...';
try {
const text = await file.text();
const sarif = JSON.parse(text);
const response = await fetch(`${API_BASE}/webhook/sarif`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task_id: taskId, sarif: sarif }),
});
const result = await response.json();
if (response.ok) {
if (
result.message &&
result.message.includes('failed')
) {
showNotification(result.message, null, result.color);
} else {
showNotification(
result.message || 'SARIF submitted successfully!',
'success',
);
}
elements.sarifModal.style.display = 'none';
} else {
const errorMessage =
result.message ||
result.detail ||
'Failed to submit SARIF';
showNotification(`Error: ${errorMessage}`, 'error');
}
} catch (error) {
if (error instanceof SyntaxError) {
showNotification(
'Invalid SARIF file: not valid JSON',
'error',
);
} else {
console.error('Error submitting SARIF:', error);
showNotification('Network error occurred', 'error');
}
} finally {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
// Approve patch
async function approvePatch(taskId, patchId) {
try {