First release of open core
This commit is contained in:
198
ui/static/builder.js
Normal file
198
ui/static/builder.js
Normal file
@@ -0,0 +1,198 @@
|
||||
const fileInput = document.getElementById('local-file');
|
||||
const pathInput = document.getElementById('findings_path');
|
||||
let currentRawData = null;
|
||||
let isJson = false;
|
||||
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
isJson = file.name.toLowerCase().endsWith('.json');
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
currentRawData = event.target.result;
|
||||
document.getElementById('preview-placeholder').style.display = 'none';
|
||||
|
||||
if (isJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(currentRawData);
|
||||
const guessedPath = autoDetectArrayPath(parsed);
|
||||
if (guessedPath) {
|
||||
pathInput.value = guessedPath;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Auto-detect failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
processPreview();
|
||||
};
|
||||
reader.readAsText(file);
|
||||
});
|
||||
|
||||
pathInput.addEventListener('input', () => {
|
||||
if (currentRawData && isJson) processPreview();
|
||||
});
|
||||
|
||||
function autoDetectArrayPath(obj) {
|
||||
if (Array.isArray(obj)) return ".";
|
||||
|
||||
let bestPath = "";
|
||||
let maxLen = -1;
|
||||
|
||||
function search(currentObj, currentPath) {
|
||||
if (Array.isArray(currentObj)) {
|
||||
if (currentObj.length > 0 && typeof currentObj[0] === 'object') {
|
||||
if (currentObj.length > maxLen) {
|
||||
maxLen = currentObj.length;
|
||||
bestPath = currentPath;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (currentObj !== null && typeof currentObj === 'object') {
|
||||
for (let key in currentObj) {
|
||||
let nextPath = currentPath ? currentPath + "." + key : key;
|
||||
search(currentObj[key], nextPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
search(obj, "");
|
||||
return bestPath || ".";
|
||||
}
|
||||
|
||||
|
||||
function processPreview() {
|
||||
let headers = [];
|
||||
let rows = [];
|
||||
|
||||
if (isJson) {
|
||||
try {
|
||||
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)";
|
||||
|
||||
document.getElementById('preview-table-container').innerHTML =
|
||||
`<div style="padding: 15px; background: #1e293b; border-radius: 6px; font-family: monospace; overflow-x: auto; text-align: left; font-size: 0.85rem;">
|
||||
<p style="color: #fca5a5; margin-top: 0; font-weight: bold;">⚠️ Path "${pathInput.value}" is not an array.</p>
|
||||
<p style="color: #cbd5e1; margin-bottom: 10px;">Here is the structure of your file to help you find the correct path:</p>
|
||||
<pre style="margin: 0; color: #a6e22e;">${rawPreview}</pre>
|
||||
</div>`;
|
||||
|
||||
document.getElementById('save-btn').classList.add('disabled');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('save-btn').classList.remove('disabled');
|
||||
headers = Object.keys(findings[0]);
|
||||
rows = findings.slice(0, 5).map(obj => headers.map(h => formatCell(obj[h])));
|
||||
} catch(e) {
|
||||
document.getElementById('preview-table-container').innerHTML = `<div style="color: var(--critical); padding: 20px; font-weight: bold;">JSON Parse Error: ${e.message}</div>`;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const lines = currentRawData.split('\n').filter(l => l.trim() !== '');
|
||||
headers = lines[0].split(',').map(h => h.trim());
|
||||
rows = lines.slice(1, 6).map(line => line.split(',').map(c => c.trim()));
|
||||
document.getElementById('save-btn').classList.remove('disabled');
|
||||
}
|
||||
|
||||
renderTable(headers, rows);
|
||||
populateDropdowns(headers);
|
||||
}
|
||||
|
||||
function getNestedValue(obj, path) {
|
||||
if (path === '' || path === '.') return obj;
|
||||
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
||||
}
|
||||
|
||||
function formatCell(val) {
|
||||
if (typeof val === 'object') return JSON.stringify(val);
|
||||
if (val === undefined || val === null) return "";
|
||||
const str = String(val);
|
||||
return str.length > 50 ? str.substring(0, 47) + "..." : str;
|
||||
}
|
||||
|
||||
function renderTable(headers, rows) {
|
||||
let html = '<table style="width: 100%; border-collapse: collapse; font-size: 0.85rem;">';
|
||||
html += '<thead style="background: #f8fafc; text-transform: uppercase; color: #64748b;"><tr>' + headers.map(h => `<th style="padding: 10px; border-bottom: 2px solid #e2e8f0; text-align: left;">${h}</th>`).join('') + '</tr></thead><tbody>';
|
||||
rows.forEach(row => {
|
||||
html += '<tr>' + row.map(cell => `<td style="padding: 10px; border-bottom: 1px solid #e2e8f0;">${cell}</td>`).join('') + '</tr>';
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('preview-table-container').innerHTML = html;
|
||||
}
|
||||
|
||||
function populateDropdowns(headers) {
|
||||
const selects = document.querySelectorAll('.source-header');
|
||||
selects.forEach(select => {
|
||||
select.innerHTML = '<option value="">-- Select Column --</option>';
|
||||
headers.forEach(h => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = h;
|
||||
opt.textContent = h;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('adapter-form').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const data = {
|
||||
name: document.getElementById('name').value,
|
||||
source_name: document.getElementById('source_name').value,
|
||||
findings_path: document.getElementById('findings_path').value,
|
||||
mapping_title: document.getElementById('mapping_title').value,
|
||||
mapping_asset: document.getElementById('mapping_asset').value,
|
||||
mapping_severity: document.getElementById('mapping_severity').value,
|
||||
mapping_description: document.getElementById('mapping_description').value,
|
||||
mapping_remediation: document.getElementById('mapping_remediation').value
|
||||
};
|
||||
|
||||
const resp = await fetch('/api/adapters', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
alert("Adapter Saved! Taking you back to the Landing Zone.");
|
||||
window.location.href = "/ingest";
|
||||
} else {
|
||||
alert("Failed to save adapter: " + await resp.text());
|
||||
}
|
||||
};
|
||||
|
||||
window.exportAdapterJSON = function() {
|
||||
const name = document.getElementById("adapterName").value.trim();
|
||||
const sourceName = document.getElementById("sourceName").value.trim();
|
||||
const rootPath = document.getElementById("rootPath").value.trim();
|
||||
|
||||
if (!name || !sourceName) {
|
||||
return alert("Adapter Name and Source Name are required to export.");
|
||||
}
|
||||
|
||||
const payload = {
|
||||
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()
|
||||
};
|
||||
|
||||
// Create a downloadable JSON blob
|
||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(payload, null, 4));
|
||||
const downloadAnchorNode = document.createElement('a');
|
||||
downloadAnchorNode.setAttribute("href", dataStr);
|
||||
downloadAnchorNode.setAttribute("download", `${sourceName.toLowerCase().replace(/\s+/g, '_')}_adapter.json`);
|
||||
document.body.appendChild(downloadAnchorNode);
|
||||
downloadAnchorNode.click();
|
||||
downloadAnchorNode.remove();
|
||||
};
|
||||
Reference in New Issue
Block a user