diff --git a/orchestrator/src/buttercup/orchestrator/ui/static/index.html b/orchestrator/src/buttercup/orchestrator/ui/static/index.html
index ba455589..75f786e0 100644
--- a/orchestrator/src/buttercup/orchestrator/ui/static/index.html
+++ b/orchestrator/src/buttercup/orchestrator/ui/static/index.html
@@ -12,6 +12,7 @@
+
+
@@ -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 =
+ '';
+ 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 = ' 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 {