This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
+26
-12
@@ -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
|
||||
|
||||
+45
-33
@@ -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]); };
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
@@ -90,8 +90,8 @@
|
||||
<button type="submit" class="btn disabled" id="save-btn" style="width: 100%; background: var(--primary); color: white; border: none; padding: 12px;">Save & Enable Adapter</button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 15px; margin-top: 20px;">
|
||||
<button class="btn" style="background: #2563eb; color: white;" onclick="saveAdapter()">💾 Save to Database</button>
|
||||
<button class="btn btn-secondary" onclick="exportAdapterJSON()">⬇️ Export JSON Profile</button>
|
||||
<button type="button" class="btn" style="background: #2563eb; color: white;" onclick="saveAdapter()">💾 Save to Database</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="exportAdapterJSON()">⬇️ Export JSON Profile</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user