diff --git a/pkg/ingest/ingest.go b/pkg/ingest/ingest.go index 46a387f..8af301d 100644 --- a/pkg/ingest/ingest.go +++ b/pkg/ingest/ingest.go @@ -7,7 +7,6 @@ import ( "encoding/json" "log" "net/http" - "strconv" "code.riskrancher.com/RiskRancher/core/pkg/domain" ) @@ -76,14 +75,15 @@ func (h *Handler) HandleCSVIngest(w http.ResponseWriter, r *http.Request) { return } - adapterIDStr := r.FormValue("adapter_id") - adapterID, err := strconv.Atoi(adapterIDStr) - if err != nil { - http.Error(w, "Invalid adapter_id", http.StatusBadRequest) + // 1. Grab the adapter_name sent by the frontend JS + adapterName := r.FormValue("adapter_name") + if adapterName == "" { + http.Error(w, "Missing adapter_name", http.StatusBadRequest) return } - adapter, err := h.Store.GetAdapterByID(r.Context(), adapterID) + // 2. Look up the adapter by Name instead of ID + adapter, err := h.Store.GetAdapterByName(r.Context(), adapterName) if err != nil { http.Error(w, "Adapter mapping not found", http.StatusNotFound) return diff --git a/pkg/server/routes.go b/pkg/server/routes.go index f7d2555..5f9f197 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -66,6 +66,8 @@ func RegisterRoutes(app *App) { // Adapters & Configuration app.Router.Handle("GET /api/adapters", protected(adapterH.HandleGetAdapters)) app.Router.Handle("GET /api/config", protected(adminH.HandleGetConfig)) + app.Router.Handle("POST /api/adapters", protected(adapterH.HandleCreateAdapter)) + app.Router.Handle("DELETE /api/adapters/{id}", protected(adapterH.HandleDeleteAdapter)) // Analytics app.Router.Handle("GET /api/analytics/summary", protected(analyticsH.HandleGetAnalyticsSummary)) @@ -83,9 +85,6 @@ func RegisterRoutes(app *App) { app.Router.Handle("GET /admin", sheriffOnly(ui.HandleAdminDashboard(app.Store))) - app.Router.Handle("POST /api/adapters", adminOnly(adapterH.HandleCreateAdapter)) - app.Router.Handle("DELETE /api/adapters/{id}", adminOnly(adapterH.HandleDeleteAdapter)) - app.Router.Handle("GET /api/admin/export", sheriffOnly(adminH.HandleExportState)) app.Router.Handle("GET /api/admin/check-updates", sheriffOnly(adminH.HandleCheckUpdates)) app.Router.Handle("POST /api/admin/shutdown", sheriffOnly(adminH.HandleShutdown)) diff --git a/ui/static/builder.js b/ui/static/builder.js index 3c71906..cf03add 100644 --- a/ui/static/builder.js +++ b/ui/static/builder.js @@ -62,7 +62,6 @@ function autoDetectArrayPath(obj) { return bestPath || "."; } - function processPreview() { let headers = []; let rows = []; @@ -72,7 +71,6 @@ function processPreview() { const parsed = JSON.parse(currentRawData); const findings = getNestedValue(parsed, pathInput.value); - if (!Array.isArray(findings) || findings.length === 0) { const rawPreview = JSON.stringify(parsed, null, 2).substring(0, 1500) + "\n\n... (file truncated for preview)"; @@ -140,8 +138,8 @@ function populateDropdowns(headers) { }); } -document.getElementById('adapter-form').onsubmit = async (e) => { - e.preventDefault(); +// Reusable save function mapped to the API +async function saveAdapterToAPI() { const data = { name: document.getElementById('name').value, source_name: document.getElementById('source_name').value, @@ -165,12 +163,28 @@ document.getElementById('adapter-form').onsubmit = async (e) => { } else { alert("Failed to save adapter: " + await resp.text()); } +} + +// Bound to the primary "Save & Enable Adapter" submit button +document.getElementById('adapter-form').onsubmit = async (e) => { + e.preventDefault(); + await saveAdapterToAPI(); +}; + +// Bound to the secondary "Save to Database" button +window.saveAdapter = async function() { + const form = document.getElementById('adapter-form'); + // Ensure HTML validations (like 'required') are checked before saving + if (form.reportValidity()) { + await saveAdapterToAPI(); + } }; window.exportAdapterJSON = function() { - const name = document.getElementById("adapterName").value.trim(); - const sourceName = document.getElementById("sourceName").value.trim(); - const rootPath = document.getElementById("rootPath").value.trim(); + // Fixed mismatched Element IDs + const name = document.getElementById("name").value.trim(); + const sourceName = document.getElementById("source_name").value.trim(); + const rootPath = document.getElementById("findings_path").value.trim(); if (!name || !sourceName) { return alert("Adapter Name and Source Name are required to export."); @@ -180,11 +194,11 @@ window.exportAdapterJSON = function() { name: name, source_name: sourceName, findings_path: rootPath, - mapping_title: document.getElementById("mapTitle").value.trim(), - mapping_asset: document.getElementById("mapAsset").value.trim(), - mapping_severity: document.getElementById("mapSeverity").value.trim(), - mapping_description: document.getElementById("mapDesc").value.trim(), - mapping_remediation: document.getElementById("mapRem").value.trim() + mapping_title: document.getElementById("mapping_title").value.trim(), + mapping_asset: document.getElementById("mapping_asset").value.trim(), + mapping_severity: document.getElementById("mapping_severity").value.trim(), + mapping_description: document.getElementById("mapping_description").value.trim(), + mapping_remediation: document.getElementById("mapping_remediation").value.trim() }; // Create a downloadable JSON blob diff --git a/ui/static/ingest.js b/ui/static/ingest.js index bb6679b..cac42e6 100644 --- a/ui/static/ingest.js +++ b/ui/static/ingest.js @@ -1,50 +1,47 @@ -const dropZone = document.getElementById('drop-zone'); -const fileInput = document.getElementById('file-input'); +async function uploadScan() { + const fileInput = document.getElementById('scanFile'); + const adapterSelect = document.getElementById('adapterSelect'); + const resultDiv = document.getElementById('ingestResult'); -async function processFile(file) { - const statusText = document.getElementById('status-text'); - document.getElementById('status-area').classList.remove('d-none'); + const file = fileInput.files[0]; + const adapterName = adapterSelect.value; - const adapterSelect = document.getElementById('adapter-select'); - const adapterId = adapterSelect.value; - - let adapterName = ""; - if (adapterSelect.selectedIndex > 0) { - adapterName = adapterSelect.options[adapterSelect.selectedIndex].getAttribute('data-name'); + if (!file) { + showResult("Please select a file to upload.", false); + return; } + if (!adapterName) { + showResult("Please select an adapter.", false); + return; + } + + // Show processing state + showResult("Processing...", true, true); + try { let response; + // Route appropriately based on file extension if (file.name.toLowerCase().endsWith('.json')) { - if (!adapterName) { - statusText.innerText = "Unknown JSON format. Redirecting to Adapter Builder..."; - setTimeout(() => { - window.location.href = `/admin/adapters/new?filename=${encodeURIComponent(file.name)}`; - }, 1200); - return; - } - const rawText = await file.text(); response = await fetch(`/api/ingest/${encodeURIComponent(adapterName)}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: rawText }); - } - else { + } else { let formData = new FormData(); formData.append('file', file); - if (adapterId) { - formData.append('adapter_id', adapterId); - } + formData.append('adapter_name', adapterName); // Pass adapter name for CSVs response = await fetch('/api/ingest/csv', { method: 'POST', body: formData }); } if (!response.ok) { + // Auto-redirect to builder if the adapter doesn't match the file structure if (response.status === 404) { - statusText.innerText = "Format not recognized. Redirecting to Adapter Builder..."; + showResult("Format not recognized. Redirecting to Adapter Builder...", false); setTimeout(() => { window.location.href = `/admin/adapters/new?filename=${encodeURIComponent(file.name)}`; }, 1200); @@ -53,16 +50,31 @@ async function processFile(file) { throw new Error(errText); } } else { - statusText.innerText = "Yeehaw! Tickets corralled successfully."; - setTimeout(() => window.location.href = "/dashboard", 800); + showResult("Yeehaw! Tickets corralled successfully.", true); + setTimeout(() => window.location.href = "/dashboard", 1000); } } catch (err) { - statusText.innerText = "Stampede! Error: " + err.message; + showResult("Stampede! Error: " + err.message, false); } } -dropZone.onclick = () => fileInput.click(); -fileInput.onchange = (e) => processFile(e.target.files[0]); -dropZone.ondragover = (e) => { e.preventDefault(); dropZone.style.background = "#e1f5fe"; }; -dropZone.ondragleave = () => dropZone.style.background = "#f8f9fa"; -dropZone.ondrop = (e) => { e.preventDefault(); processFile(e.dataTransfer.files[0]); }; \ No newline at end of file +// Helper function to handle status messages nicely +function showResult(msg, isSuccess, isInfo = false) { + const div = document.getElementById('ingestResult'); + div.style.display = 'block'; + div.innerText = msg; + + if (isInfo) { + div.style.backgroundColor = '#e0f2fe'; + div.style.color = '#0369a1'; + div.style.border = '1px solid #bae6fd'; + } else if (isSuccess) { + div.style.backgroundColor = '#dcfce7'; + div.style.color = '#166534'; + div.style.border = '1px solid #bbf7d0'; + } else { + div.style.backgroundColor = '#fee2e2'; + div.style.color = '#991b1b'; + div.style.border = '1px solid #fecaca'; + } +} \ No newline at end of file diff --git a/ui/templates/adapter_builder.gohtml b/ui/templates/adapter_builder.gohtml index 9b61b5c..5c8d47e 100644 --- a/ui/templates/adapter_builder.gohtml +++ b/ui/templates/adapter_builder.gohtml @@ -90,8 +90,8 @@
- - + +