mirror of
https://github.com/0xMarcio/cve.git
synced 2026-02-12 18:42:46 +00:00
Improve CVE detail layout and search performance
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
|
||||
const detailSection = document.getElementById("cve-details");
|
||||
const notFoundSection = document.getElementById("not-found");
|
||||
const descriptionBlock = document.getElementById("cve-description-block");
|
||||
|
||||
function setLoading(message) {
|
||||
titleEl.textContent = cveId || "CVE details";
|
||||
@@ -27,14 +28,37 @@
|
||||
return "No description available.";
|
||||
}
|
||||
|
||||
function hasKevData(kev) {
|
||||
if (!kev || typeof kev !== "object") return false;
|
||||
return Boolean(
|
||||
(kev.short_description && kev.short_description.trim()) ||
|
||||
kev.date_added ||
|
||||
kev.due_date ||
|
||||
kev.required_action ||
|
||||
kev.notes
|
||||
);
|
||||
}
|
||||
|
||||
function renderFacts(data) {
|
||||
const pocCount = data.poc_count ?? (Array.isArray(data.poc_links) ? data.poc_links.length : Array.isArray(data.poc) ? data.poc.length : undefined);
|
||||
const items = [];
|
||||
if (data.vendor) items.push({ label: "Vendor", value: data.vendor });
|
||||
if (data.product) items.push({ label: "Product", value: data.product });
|
||||
if (data.epss !== undefined && data.epss !== null) items.push({ label: "EPSS", value: data.epss.toFixed(3) });
|
||||
if (data.percentile !== undefined && data.percentile !== null) items.push({ label: "Percentile", value: `${Math.round(data.percentile * 100)}th` });
|
||||
if (data.poc_count !== undefined) items.push({ label: "PoCs", value: data.poc_count });
|
||||
if (data.kev) items.push({ label: "KEV status", value: data.kev.date_added ? `Added ${data.kev.date_added}` : "Listed" });
|
||||
const vendorValue = data.vendor || "Unknown vendor";
|
||||
const productValue = data.product || "Unknown product";
|
||||
const epssValue = typeof data.epss === "number" ? data.epss.toFixed(3) : "n/a";
|
||||
const percentileValue = typeof data.percentile === "number" ? `${Math.round(data.percentile * 100)}th` : "n/a";
|
||||
const pocValue = pocCount ?? 0;
|
||||
|
||||
items.push({ label: "Vendor", value: vendorValue });
|
||||
items.push({ label: "Product", value: productValue });
|
||||
items.push({ label: "EPSS", value: epssValue });
|
||||
items.push({ label: "Percentile", value: percentileValue });
|
||||
items.push({ label: "PoCs", value: pocValue });
|
||||
if (hasKevData(data.kev)) items.push({ label: "KEV status", value: data.kev.date_added ? `Added ${data.kev.date_added}` : "Listed" });
|
||||
|
||||
if (items.length === 0) {
|
||||
factsEl.innerHTML = `<div class="stat"><strong>—</strong><span>No overview data yet.</span></div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
factsEl.innerHTML = items
|
||||
.map((item) => `<div class="stat"><strong>${item.value}</strong><span>${item.label}</span></div>`)
|
||||
@@ -56,13 +80,13 @@
|
||||
const pills = [];
|
||||
if (data.vendor) pills.push(`Vendor: ${data.vendor}`);
|
||||
if (data.product) pills.push(`Product: ${data.product}`);
|
||||
if (data.kev) pills.push("On KEV list");
|
||||
if (hasKevData(data.kev)) pills.push("On KEV list");
|
||||
|
||||
metaEl.innerHTML = pills.map((text) => `<span class="pill">${text}</span>`).join("");
|
||||
}
|
||||
|
||||
function renderKev(kev) {
|
||||
if (!kev) {
|
||||
if (!hasKevData(kev)) {
|
||||
document.getElementById("kev-section").style.display = "none";
|
||||
return;
|
||||
}
|
||||
@@ -109,7 +133,14 @@
|
||||
titleEl.textContent = data.cve || cveId;
|
||||
const desc = getDescriptionText(data);
|
||||
summaryEl.textContent = desc;
|
||||
descEl.textContent = desc;
|
||||
const hasDescContent = desc && desc !== "No description available.";
|
||||
if (hasDescContent) {
|
||||
descEl.textContent = desc;
|
||||
descriptionBlock.style.display = "";
|
||||
} else {
|
||||
descEl.textContent = "";
|
||||
descriptionBlock.style.display = "none";
|
||||
}
|
||||
renderFacts(data);
|
||||
renderPocs(data.poc_links || data.poc || []);
|
||||
renderKev(data.kev);
|
||||
@@ -126,7 +157,14 @@
|
||||
titleEl.textContent = fallback.cve;
|
||||
const desc = getDescriptionText(fallback);
|
||||
summaryEl.textContent = desc;
|
||||
descEl.textContent = desc;
|
||||
const hasDescContent = desc && desc !== "No description available.";
|
||||
if (hasDescContent) {
|
||||
descEl.textContent = desc;
|
||||
descriptionBlock.style.display = "";
|
||||
} else {
|
||||
descEl.textContent = "";
|
||||
descriptionBlock.style.display = "none";
|
||||
}
|
||||
renderFacts(fallback);
|
||||
renderPocs(fallback.poc_links || fallback.poc || []);
|
||||
renderKev(null);
|
||||
@@ -138,6 +176,7 @@
|
||||
notFoundSection.style.display = "";
|
||||
detailSection.style.display = "none";
|
||||
metaEl.innerHTML = "";
|
||||
descriptionBlock.style.display = "none";
|
||||
titleEl.textContent = cveId;
|
||||
summaryEl.textContent = "No data found for this CVE.";
|
||||
}
|
||||
|
||||
@@ -31,9 +31,11 @@
|
||||
<h2>Overview</h2>
|
||||
<div class="subtle-grid" id="cve-facts"></div>
|
||||
|
||||
<h2>CVE detail</h2>
|
||||
<div class="detail-card">
|
||||
<p id="cve-description" class="muted"></p>
|
||||
<div id="cve-description-block" style="display:none;">
|
||||
<h2>CVE detail</h2>
|
||||
<div class="detail-card">
|
||||
<p id="cve-description" class="muted"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>PoC Links</h2>
|
||||
|
||||
@@ -54,6 +54,19 @@ function getCveLink(cveId) {
|
||||
return `<a href="/cve/?id=${cveId}"><b>${cveId}</b></a>`;
|
||||
}
|
||||
|
||||
function prepareDataset(raw) {
|
||||
if (!Array.isArray(raw)) return [];
|
||||
const descKeyCleaned = (entry) => {
|
||||
const base = entry.desc || '';
|
||||
return replaceStrings.reduce((desc, str) => desc.replace(str, ''), base);
|
||||
};
|
||||
return raw.map(entry => {
|
||||
const descCleaned = descKeyCleaned(entry);
|
||||
const searchText = `${entry.cve || ''} ${descCleaned}`.toLowerCase();
|
||||
return { ...entry, _searchText: searchText };
|
||||
});
|
||||
}
|
||||
|
||||
const controls = {
|
||||
oldColor: '',
|
||||
displayResults(results, resultsTableHideable) {
|
||||
@@ -70,8 +83,7 @@ const controls = {
|
||||
const negmatch = words.filter(word => word[0] === '-').map(word => word.substring(1));
|
||||
|
||||
return dataset.filter(e => {
|
||||
const description = replaceStrings.reduce((desc, str) => desc.replace(str, ''), e.desc).toLowerCase();
|
||||
const combinedText = (e.cve + description).toLowerCase();
|
||||
const combinedText = e._searchText || '';
|
||||
|
||||
const positiveMatch = posmatch.every(word => combinedText.includes(word));
|
||||
const negativeMatch = negmatch.some(word => combinedText.includes(word));
|
||||
@@ -95,17 +107,14 @@ const controls = {
|
||||
noResults.style.display = 'none';
|
||||
resultsTableHideable.classList.remove('hide');
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
results.forEach(r => {
|
||||
const el = searchResultFormat
|
||||
const html = results.map(r => {
|
||||
const desc = r.desc || '';
|
||||
return searchResultFormat
|
||||
.replace('$cve', getCveLink(r.cve))
|
||||
.replace('$description', escapeHTML(r.desc))
|
||||
.replace('$poc', convertLinksToList(r.poc));
|
||||
const wrapper = document.createElement('table');
|
||||
wrapper.innerHTML = el;
|
||||
fragment.appendChild(wrapper.querySelector('tr'));
|
||||
});
|
||||
loc.appendChild(fragment);
|
||||
.replace('$description', escapeHTML(desc))
|
||||
.replace('$poc', convertLinksToList(r.poc || []));
|
||||
}).join('');
|
||||
loc.innerHTML = html;
|
||||
}
|
||||
},
|
||||
setColor(loc, indicator) {
|
||||
@@ -176,7 +185,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
throw new Error(`Failed to load ${url} (${res.status})`);
|
||||
}
|
||||
const data = await res.json();
|
||||
window.dataset = Array.isArray(data) ? data : [];
|
||||
window.dataset = prepareDataset(data);
|
||||
currentSet = window.dataset;
|
||||
controls.hideResults(results, resultsTableHideable);
|
||||
noResults.style.display = 'none';
|
||||
|
||||
Reference in New Issue
Block a user