diff --git a/backend/routes/ivantiFindings.js b/backend/routes/ivantiFindings.js index 98ad0e1..1158497 100644 --- a/backend/routes/ivantiFindings.js +++ b/backend/routes/ivantiFindings.js @@ -175,6 +175,15 @@ function initTables(db) { db.run(` CREATE INDEX IF NOT EXISTS idx_finding_overrides_finding_id ON ivanti_finding_overrides(finding_id) + `, (err) => { if (err) return reject(err); }); + + db.run(` + CREATE TABLE IF NOT EXISTS ivanti_counts_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + open_count INTEGER NOT NULL, + closed_count INTEGER NOT NULL, + recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP + ) `, (err) => { if (err) reject(err); else resolve(); @@ -271,6 +280,14 @@ async function syncClosedCount(db, openCount, apiKey, clientId, skipTls) { `UPDATE ivanti_counts_cache SET open_count=?, closed_count=?, synced_at=datetime('now') WHERE id=1`, [openCount, closedCount] ); + + // Append a snapshot to history — every sync is stored; the history + // endpoint aggregates to last-per-day at query time (Option B). + await dbRun(db, + `INSERT INTO ivanti_counts_history (open_count, closed_count) VALUES (?, ?)`, + [openCount, closedCount] + ); + console.log(`[Ivanti Findings] Counts updated — open: ${openCount}, closed: ${closedCount}`); } catch (err) { console.error('[Ivanti Findings] Failed to fetch closed count:', err.message); @@ -576,6 +593,33 @@ function createIvantiFindingsRouter(db, requireAuth) { } }); + // GET /counts/history — last snapshot per day, ascending, for the trend chart. + // Uses a window function (ROW_NUMBER) to pick the final sync of each calendar day. + router.get('/counts/history', async (req, res) => { + try { + const rows = await new Promise((resolve, reject) => { + db.all( + `SELECT date, open_count, closed_count FROM ( + SELECT DATE(recorded_at) AS date, + open_count, closed_count, + ROW_NUMBER() OVER ( + PARTITION BY DATE(recorded_at) + ORDER BY recorded_at DESC + ) AS rn + FROM ivanti_counts_history + ) WHERE rn = 1 + ORDER BY date ASC`, + [], + (err, rows) => { if (err) reject(err); else resolve(rows || []); } + ); + }); + res.json({ history: rows }); + } catch (err) { + console.error('[Ivanti Findings] GET /counts/history error:', err.message); + res.status(500).json({ error: 'Database error' }); + } + }); + // GET /fp-workflow-counts — FP finding + unique workflow counts (open + closed) router.get('/fp-workflow-counts', async (req, res) => { try { diff --git a/frontend/src/App.js b/frontend/src/App.js index 7d4a8d3..4fdd12e 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -10,7 +10,7 @@ import KnowledgeBaseModal from './components/KnowledgeBaseModal'; import KnowledgeBaseViewer from './components/KnowledgeBaseViewer'; import NavDrawer from './components/NavDrawer'; import CalendarWidget from './components/CalendarWidget'; -import ReportingPage from './components/pages/ReportingPage'; +import VulnerabilityTriagePage from './components/pages/ReportingPage'; import KnowledgeBasePage from './components/pages/KnowledgeBasePage'; import ExportsPage from './components/pages/ExportsPage'; import CompliancePage from './components/pages/CompliancePage'; @@ -966,14 +966,14 @@ export default function App() { currentPage={currentPage} onNavigate={(page) => { // Clear contextual filters when navigating directly via the nav drawer - if (page === 'reporting') { setCalendarFilter(null); setReportingExcFilter(null); } + if (page === 'triage') { setCalendarFilter(null); setReportingExcFilter(null); } setCurrentPage(page); }} /> {/* Scanning line effect */}
-