first commit
This commit is contained in:
264
static/css/style.css
Normal file
264
static/css/style.css
Normal file
@@ -0,0 +1,264 @@
|
||||
/* CVE Monitor - Custom Styles */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 250px;
|
||||
--navbar-height: 56px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 0.95rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: var(--navbar-height);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
padding: 0;
|
||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
padding: 0.75rem 1rem;
|
||||
border-left: 3px solid transparent;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.sidebar .nav-link:hover {
|
||||
color: #0d6efd;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.sidebar .nav-link.active {
|
||||
color: #0d6efd;
|
||||
border-left-color: #0d6efd;
|
||||
background-color: rgba(13, 110, 253, 0.1);
|
||||
}
|
||||
|
||||
.sidebar .nav-link i {
|
||||
margin-right: 0.5rem;
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* Dark mode */
|
||||
[data-bs-theme="dark"] .sidebar {
|
||||
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .nav-link {
|
||||
color: #dee2e6;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .nav-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .sidebar .nav-link.active {
|
||||
background-color: rgba(13, 110, 253, 0.2);
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
main {
|
||||
margin-left: var(--sidebar-width);
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.sidebar {
|
||||
position: static;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
/* Stats cards */
|
||||
.card .fs-1 {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.card:hover .fs-1 {
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.table {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.table thead th {
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.05em;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.table tbody tr {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.table tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .table tbody tr:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Severity badges */
|
||||
.badge-critical {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-high {
|
||||
background-color: #fd7e14;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.badge-medium {
|
||||
background-color: #0dcaf0;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.badge-low {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* CVE ID column */
|
||||
.cve-id {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-weight: 600;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
/* Loading spinner overlay */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .loading-overlay {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
/* Pagination */
|
||||
.pagination {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
/* Search results */
|
||||
#searchResults .list-group-item {
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
#searchResults .list-group-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Charts */
|
||||
canvas {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 575.98px) {
|
||||
.btn-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scrollbar customization */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] ::-webkit-scrollbar-track {
|
||||
background: #2d2d2d;
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] ::-webkit-scrollbar-thumb {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 0.3s ease-in;
|
||||
}
|
||||
|
||||
/* Vendor badge counter */
|
||||
.vendor-badge {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
425
static/js/app.js
Normal file
425
static/js/app.js
Normal file
@@ -0,0 +1,425 @@
|
||||
const state = {
|
||||
currentVendor: null,
|
||||
currentPage: 1,
|
||||
itemsPerPage: 50,
|
||||
filters: { severity: '', year: '' },
|
||||
charts: { trend: null, severity: null }
|
||||
};
|
||||
|
||||
function updateThemeIcon(theme) {
|
||||
const icon = document.querySelector('#darkModeToggle i');
|
||||
if (icon) icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
const currentTheme = document.documentElement.getAttribute('data-bs-theme');
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-bs-theme', newTheme);
|
||||
localStorage.setItem('theme', newTheme);
|
||||
updateThemeIcon(newTheme);
|
||||
if (state.currentVendor) {
|
||||
fetch(`/api/stats/${state.currentVendor}`)
|
||||
.then(r => r.json())
|
||||
.then(data => updateCharts(data.stats))
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
|
||||
function getSeverityIcon(severity) {
|
||||
const icons = {
|
||||
'CRITICAL': '<i class="fas fa-skull-crossbones"></i>',
|
||||
'HIGH': '<i class="fas fa-exclamation-triangle"></i>',
|
||||
'MEDIUM': '<i class="fas fa-exclamation-circle"></i>',
|
||||
'LOW': '<i class="fas fa-info-circle"></i>'
|
||||
};
|
||||
return icons[severity] || '<i class="fas fa-question"></i>';
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return 'N/A';
|
||||
return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function formatMonth(monthString) {
|
||||
const [year, month] = monthString.split('-');
|
||||
return new Date(year, month - 1).toLocaleDateString('en-US', { year: 'numeric', month: 'short' });
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function updateLastUpdate() {
|
||||
const elem = document.getElementById('lastUpdate');
|
||||
if (elem) elem.innerHTML = `<i class="fas fa-clock me-1"></i>Last update: ${new Date().toLocaleTimeString()}`;
|
||||
}
|
||||
|
||||
function showLoading() {
|
||||
const tbody = document.getElementById('cveTableBody');
|
||||
if (tbody) tbody.innerHTML = `<tr><td colspan="5" class="text-center py-4"><div class="spinner-border text-primary"></div><p class="mt-2 text-muted">Loading...</p></td></tr>`;
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
console.error(message);
|
||||
alert(message);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeApp();
|
||||
setupEventListeners();
|
||||
loadVendors();
|
||||
});
|
||||
|
||||
function initializeApp() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
document.documentElement.setAttribute('data-bs-theme', savedTheme);
|
||||
updateThemeIcon(savedTheme);
|
||||
}
|
||||
|
||||
function setupEventListeners() {
|
||||
const handlers = {
|
||||
'darkModeToggle': toggleDarkMode,
|
||||
'refreshBtn': () => state.currentVendor && loadVendorData(state.currentVendor, true),
|
||||
'clearFilters': clearFiltersHandler,
|
||||
'exportJSON': () => exportData('json'),
|
||||
'exportCSV': () => exportData('csv'),
|
||||
'searchBtn': performSearch
|
||||
};
|
||||
|
||||
Object.entries(handlers).forEach(([id, handler]) => {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem) elem.addEventListener('click', handler);
|
||||
});
|
||||
|
||||
const filterForm = document.getElementById('filterForm');
|
||||
if (filterForm) filterForm.addEventListener('submit', (e) => { e.preventDefault(); applyFilters(); });
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (searchInput) searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') performSearch(); });
|
||||
|
||||
const prevPage = document.getElementById('prevPage');
|
||||
const nextPage = document.getElementById('nextPage');
|
||||
if (prevPage) prevPage.querySelector('a')?.addEventListener('click', (e) => { e.preventDefault(); changePage(-1); });
|
||||
if (nextPage) nextPage.querySelector('a')?.addEventListener('click', (e) => { e.preventDefault(); changePage(1); });
|
||||
}
|
||||
|
||||
async function loadVendors() {
|
||||
try {
|
||||
const response = await fetch('/api/vendors');
|
||||
const data = await response.json();
|
||||
if (data.vendors) {
|
||||
renderVendorList(data.vendors);
|
||||
renderVendorDropdown(data.vendors);
|
||||
if (data.vendors.length > 0) loadVendorData(data.vendors[0].code);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading vendors:', error);
|
||||
showError('Failed to load vendors list');
|
||||
}
|
||||
}
|
||||
|
||||
function renderVendorList(vendors) {
|
||||
const vendorList = document.getElementById('vendorList');
|
||||
if (!vendorList) return;
|
||||
|
||||
vendorList.innerHTML = vendors.map(vendor => `
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" data-vendor="${vendor.code}">
|
||||
${vendor.name}
|
||||
<span class="badge bg-primary float-end">${vendor.total || 0}</span>
|
||||
</a>
|
||||
</li>
|
||||
`).join('');
|
||||
|
||||
vendorList.querySelectorAll('a[data-vendor]').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
loadVendorData(link.getAttribute('data-vendor'));
|
||||
vendorList.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
||||
link.classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderVendorDropdown(vendors) {
|
||||
const dropdown = document.getElementById('vendorDropdown');
|
||||
if (!dropdown) return;
|
||||
|
||||
dropdown.innerHTML = vendors.map(vendor => `
|
||||
<li><a class="dropdown-item" href="#" data-vendor="${vendor.code}">${vendor.name}</a></li>
|
||||
`).join('');
|
||||
|
||||
dropdown.querySelectorAll('a[data-vendor]').forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
loadVendorData(link.getAttribute('data-vendor'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function loadVendorData(vendorCode, forceRefresh = false) {
|
||||
state.currentVendor = vendorCode;
|
||||
state.currentPage = 1;
|
||||
showLoading();
|
||||
|
||||
try {
|
||||
const [cvesResponse, statsResponse] = await Promise.all([
|
||||
fetch(`/api/cve/${vendorCode}?limit=${state.itemsPerPage}&offset=0`),
|
||||
fetch(`/api/stats/${vendorCode}`)
|
||||
]);
|
||||
|
||||
const cvesData = await cvesResponse.json();
|
||||
const statsData = await statsResponse.json();
|
||||
|
||||
updateTitle(vendorCode);
|
||||
updateStats(statsData.stats);
|
||||
renderCVETable(cvesData.cves);
|
||||
updateCharts(statsData.stats);
|
||||
updateLastUpdate();
|
||||
} catch (error) {
|
||||
console.error('Error loading vendor data:', error);
|
||||
showError('Failed to load CVE data');
|
||||
}
|
||||
}
|
||||
|
||||
function updateTitle(vendorCode) {
|
||||
const vendors = {
|
||||
'microsoft': 'Microsoft', 'apple': 'Apple', 'fortinet': 'Fortinet',
|
||||
'cisco': 'Cisco', 'adobe': 'Adobe', 'oracle': 'Oracle',
|
||||
'google': 'Google', 'linux': 'Linux Kernel', 'vmware': 'VMware',
|
||||
'paloalto': 'Palo Alto Networks', 'docker': 'Docker', 'kubernetes': 'Kubernetes'
|
||||
};
|
||||
|
||||
const mainTitle = document.getElementById('mainTitle');
|
||||
if (mainTitle) mainTitle.innerHTML = `<i class="fas fa-shield-alt text-primary me-2"></i>${vendors[vendorCode] || vendorCode} CVEs`;
|
||||
}
|
||||
|
||||
function updateStats(stats) {
|
||||
const elements = {
|
||||
statTotal: stats.total || 0,
|
||||
statCritical: stats.severity?.CRITICAL || 0,
|
||||
statHigh: stats.severity?.HIGH || 0,
|
||||
statMonth: stats.this_month || 0
|
||||
};
|
||||
Object.entries(elements).forEach(([id, value]) => {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem) elem.textContent = value;
|
||||
});
|
||||
}
|
||||
|
||||
function renderCVETable(cves) {
|
||||
const tbody = document.getElementById('cveTableBody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (!cves || cves.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="5" class="text-center py-4 text-muted"><i class="fas fa-inbox fa-3x mb-3"></i><p>No CVEs found</p></td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = cves.map(cve => `
|
||||
<tr onclick="showCVEDetails('${cve.cve_id}')" style="cursor: pointer;">
|
||||
<td><span class="cve-id">${cve.cve_id}</span></td>
|
||||
<td><span class="badge badge-${(cve.severity || 'unknown').toLowerCase()}">${getSeverityIcon(cve.severity)} ${cve.severity || 'UNKNOWN'}</span></td>
|
||||
<td><strong>${cve.cvss_score ? cve.cvss_score.toFixed(1) : 'N/A'}</strong></td>
|
||||
<td><div class="text-truncate" style="max-width: 400px;">${escapeHtml(cve.description || 'No description available')}</div></td>
|
||||
<td><small class="text-muted">${formatDate(cve.published_date)}</small></td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
if (cves.length >= state.itemsPerPage) {
|
||||
const paginationNav = document.getElementById('paginationNav');
|
||||
if (paginationNav) paginationNav.classList.remove('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
async function showCVEDetails(cveId) {
|
||||
const modalElement = document.getElementById('cveModal');
|
||||
if (!modalElement) return;
|
||||
|
||||
const modal = new bootstrap.Modal(modalElement);
|
||||
const modalTitle = document.getElementById('cveModalTitle');
|
||||
const modalBody = document.getElementById('cveModalBody');
|
||||
|
||||
if (modalTitle) modalTitle.textContent = cveId;
|
||||
if (modalBody) modalBody.innerHTML = '<div class="text-center"><div class="spinner-border"></div></div>';
|
||||
|
||||
modal.show();
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/cve/${state.currentVendor}`);
|
||||
const data = await response.json();
|
||||
const cve = data.cves.find(c => c.cve_id === cveId);
|
||||
|
||||
if (cve && modalBody) {
|
||||
const references = cve.references ? JSON.parse(cve.references) : [];
|
||||
const cweIds = cve.cwe_ids ? JSON.parse(cve.cwe_ids) : [];
|
||||
|
||||
modalBody.innerHTML = `
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<h6>Severity</h6>
|
||||
<span class="badge badge-${(cve.severity || 'unknown').toLowerCase()} fs-6">${getSeverityIcon(cve.severity)} ${cve.severity || 'UNKNOWN'}</span>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<h6>CVSS Score</h6>
|
||||
<p class="fs-4 mb-0"><strong>${cve.cvss_score ? cve.cvss_score.toFixed(1) : 'N/A'}</strong></p>
|
||||
${cve.cvss_vector ? `<small class="text-muted">${cve.cvss_vector}</small>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3"><h6>Description</h6><p>${escapeHtml(cve.description || 'N/A')}</p></div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3"><h6>Published</h6><p>${formatDate(cve.published_date)}</p></div>
|
||||
<div class="col-md-6 mb-3"><h6>Modified</h6><p>${formatDate(cve.last_modified)}</p></div>
|
||||
</div>
|
||||
${cweIds.length > 0 ? `<div class="mb-3"><h6>CWE IDs</h6><p>${cweIds.map(cwe => `<span class="badge bg-secondary me-1">${cwe}</span>`).join('')}</p></div>` : ''}
|
||||
${references.length > 0 ? `<div class="mb-3"><h6>References</h6><ul class="list-unstyled">${references.slice(0, 5).map(ref => `<li><a href="${ref}" target="_blank"><i class="fas fa-external-link-alt me-1"></i>${ref}</a></li>`).join('')}</ul></div>` : ''}
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading CVE details:', error);
|
||||
if (modalBody) modalBody.innerHTML = '<p class="text-danger">Error loading CVE details</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function updateCharts(stats) {
|
||||
updateTrendChart(stats.monthly || {});
|
||||
updateSeverityChart(stats.severity || {});
|
||||
}
|
||||
|
||||
function updateTrendChart(monthlyData) {
|
||||
const ctx = document.getElementById('trendChart');
|
||||
if (!ctx) return;
|
||||
if (state.charts.trend) state.charts.trend.destroy();
|
||||
|
||||
const months = Object.keys(monthlyData).sort();
|
||||
state.charts.trend = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: months.map(m => formatMonth(m)),
|
||||
datasets: [{
|
||||
label: 'CVE Count',
|
||||
data: months.map(m => monthlyData[m]),
|
||||
backgroundColor: 'rgba(13, 110, 253, 0.5)',
|
||||
borderColor: 'rgba(13, 110, 253, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
scales: { y: { beginAtZero: true } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateSeverityChart(severityData) {
|
||||
const ctx = document.getElementById('severityChart');
|
||||
if (!ctx) return;
|
||||
if (state.charts.severity) state.charts.severity.destroy();
|
||||
|
||||
state.charts.severity = new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Critical', 'High', 'Medium', 'Low'],
|
||||
datasets: [{
|
||||
data: [severityData.CRITICAL || 0, severityData.HIGH || 0, severityData.MEDIUM || 0, severityData.LOW || 0],
|
||||
backgroundColor: ['rgba(220, 53, 69, 0.8)', 'rgba(253, 126, 20, 0.8)', 'rgba(13, 202, 240, 0.8)', 'rgba(108, 117, 125, 0.8)']
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { position: 'bottom' } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function applyFilters() {
|
||||
state.filters.severity = document.getElementById('filterSeverity')?.value || '';
|
||||
state.filters.year = document.getElementById('filterYear')?.value || '';
|
||||
state.currentPage = 1;
|
||||
showLoading();
|
||||
|
||||
try {
|
||||
let url = `/api/cve/${state.currentVendor}/filter?limit=${state.itemsPerPage}&offset=0`;
|
||||
if (state.filters.severity) url += `&severity=${state.filters.severity}`;
|
||||
if (state.filters.year) url += `&year=${state.filters.year}`;
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
renderCVETable(data.cves);
|
||||
} catch (error) {
|
||||
console.error('Error applying filters:', error);
|
||||
showError('Failed to apply filters');
|
||||
}
|
||||
}
|
||||
|
||||
function clearFiltersHandler() {
|
||||
const severityFilter = document.getElementById('filterSeverity');
|
||||
const yearFilter = document.getElementById('filterYear');
|
||||
if (severityFilter) severityFilter.value = '';
|
||||
if (yearFilter) yearFilter.value = '';
|
||||
state.filters = { severity: '', year: '' };
|
||||
if (state.currentVendor) loadVendorData(state.currentVendor);
|
||||
}
|
||||
|
||||
function changePage(delta) {
|
||||
state.currentPage += delta;
|
||||
if (state.currentPage < 1) state.currentPage = 1;
|
||||
|
||||
const offset = (state.currentPage - 1) * state.itemsPerPage;
|
||||
fetch(`/api/cve/${state.currentVendor}?limit=${state.itemsPerPage}&offset=${offset}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
renderCVETable(data.cves);
|
||||
const currentPage = document.getElementById('currentPage');
|
||||
if (currentPage) currentPage.textContent = state.currentPage;
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
async function performSearch() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (!searchInput) return;
|
||||
|
||||
const query = searchInput.value.trim();
|
||||
const resultsDiv = document.getElementById('searchResults');
|
||||
if (!resultsDiv) return;
|
||||
|
||||
if (query.length < 3) {
|
||||
resultsDiv.innerHTML = '<p class="text-muted">Please enter at least 3 characters</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
resultsDiv.innerHTML = '<div class="text-center"><div class="spinner-border"></div></div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.results && data.results.length > 0) {
|
||||
resultsDiv.innerHTML = `<div class="list-group">${data.results.map(cve => `
|
||||
<a href="#" class="list-group-item list-group-item-action" onclick="showCVEDetails('${cve.cve_id}'); return false;">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1 cve-id">${cve.cve_id}</h6>
|
||||
<span class="badge badge-${(cve.severity || 'unknown').toLowerCase()}">${cve.severity}</span>
|
||||
</div>
|
||||
<p class="mb-1 small">${escapeHtml(cve.description.substring(0, 150))}...</p>
|
||||
<small class="text-muted">${formatDate(cve.published_date)}</small>
|
||||
</a>`).join('')}</div>`;
|
||||
} else {
|
||||
resultsDiv.innerHTML = '<p class="text-muted">No results found</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error searching:', error);
|
||||
resultsDiv.innerHTML = '<p class="text-danger">Search failed</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function exportData(format) {
|
||||
if (!state.currentVendor) return;
|
||||
window.open(`/api/export/${state.currentVendor}/${format}`, '_blank');
|
||||
}
|
||||
Reference in New Issue
Block a user