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': '', 'HIGH': '', 'MEDIUM': '', 'LOW': '' }; return icons[severity] || ''; } 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 = `Last update: ${new Date().toLocaleTimeString()}`; } function showLoading() { const tbody = document.getElementById('cveTableBody'); if (tbody) tbody.innerHTML = `

Loading...

`; } 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 => ` `).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 => `
  • ${vendor.name}
  • `).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 = `${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 = `

    No CVEs found

    `; return; } tbody.innerHTML = cves.map(cve => ` ${cve.cve_id} ${getSeverityIcon(cve.severity)} ${cve.severity || 'UNKNOWN'} ${cve.cvss_score ? cve.cvss_score.toFixed(1) : 'N/A'}
    ${escapeHtml(cve.description || 'No description available')}
    ${formatDate(cve.published_date)} `).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 = '
    '; 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 = `
    Severity
    ${getSeverityIcon(cve.severity)} ${cve.severity || 'UNKNOWN'}
    CVSS Score

    ${cve.cvss_score ? cve.cvss_score.toFixed(1) : 'N/A'}

    ${cve.cvss_vector ? `${cve.cvss_vector}` : ''}
    Description

    ${escapeHtml(cve.description || 'N/A')}

    Published

    ${formatDate(cve.published_date)}

    Modified

    ${formatDate(cve.last_modified)}

    ${cweIds.length > 0 ? `
    CWE IDs

    ${cweIds.map(cwe => `${cwe}`).join('')}

    ` : ''} ${references.length > 0 ? `
    References
    ` : ''} `; } } catch (error) { console.error('Error loading CVE details:', error); if (modalBody) modalBody.innerHTML = '

    Error loading CVE details

    '; } } 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 = '

    Please enter at least 3 characters

    '; return; } resultsDiv.innerHTML = '
    '; try { const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`); const data = await response.json(); if (data.results && data.results.length > 0) { resultsDiv.innerHTML = `
    ${data.results.map(cve => `
    ${cve.cve_id}
    ${cve.severity}

    ${escapeHtml(cve.description.substring(0, 150))}...

    ${formatDate(cve.published_date)}
    `).join('')}
    `; } else { resultsDiv.innerHTML = '

    No results found

    '; } } catch (error) { console.error('Error searching:', error); resultsDiv.innerHTML = '

    Search failed

    '; } } function exportData(format) { if (!state.currentVendor) return; window.open(`/api/export/${state.currentVendor}/${format}`, '_blank'); }