feat: implement finding archive tracking system
- Add migration script for ivanti_finding_archives and ivanti_archive_transitions tables - Add archive detection logic (detectArchiveChanges, detectClosedFindings) in sync pipeline - Add archive API router with list, stats, and history endpoints at /api/ivanti/archive - Add ArchiveSummaryBar UI component with four state cards (ACTIVE, ARCHIVED, RETURNED, CLOSED) - Integrate ArchiveSummaryBar into Ivanti findings page in App.js - Register archive router in server.js
This commit is contained in:
75
backend/migrations/add_finding_archive_tables.js
Normal file
75
backend/migrations/add_finding_archive_tables.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Migration: Add ivanti_finding_archives and ivanti_archive_transitions tables
|
||||
const sqlite3 = require('sqlite3').verbose();
|
||||
const path = require('path');
|
||||
|
||||
const dbPath = path.join(__dirname, '..', 'cve_database.db');
|
||||
const db = new sqlite3.Database(dbPath);
|
||||
|
||||
console.log('Starting finding archive tables migration...');
|
||||
|
||||
db.serialize(() => {
|
||||
// Archive records — one row per finding that has entered the archive lifecycle
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS ivanti_finding_archives (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
finding_id TEXT NOT NULL UNIQUE,
|
||||
finding_title TEXT NOT NULL DEFAULT '',
|
||||
host_name TEXT NOT NULL DEFAULT '',
|
||||
ip_address TEXT NOT NULL DEFAULT '',
|
||||
current_state TEXT NOT NULL CHECK(current_state IN ('ACTIVE', 'ARCHIVED', 'RETURNED', 'CLOSED')),
|
||||
last_severity REAL NOT NULL DEFAULT 0,
|
||||
first_archived_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_transition_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`, (err) => {
|
||||
if (err) console.error('Error creating ivanti_finding_archives table:', err);
|
||||
else console.log('✓ ivanti_finding_archives table created');
|
||||
});
|
||||
|
||||
// Transition history — one row per state change on an archive record
|
||||
db.run(`
|
||||
CREATE TABLE IF NOT EXISTS ivanti_archive_transitions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
archive_id INTEGER NOT NULL,
|
||||
from_state TEXT NOT NULL,
|
||||
to_state TEXT NOT NULL,
|
||||
severity_at_transition REAL NOT NULL DEFAULT 0,
|
||||
reason TEXT NOT NULL DEFAULT '',
|
||||
transitioned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (archive_id) REFERENCES ivanti_finding_archives(id)
|
||||
)
|
||||
`, (err) => {
|
||||
if (err) console.error('Error creating ivanti_archive_transitions table:', err);
|
||||
else console.log('✓ ivanti_archive_transitions table created');
|
||||
});
|
||||
|
||||
// Indexes for query performance
|
||||
db.run(`
|
||||
CREATE INDEX IF NOT EXISTS idx_archive_finding_id
|
||||
ON ivanti_finding_archives(finding_id)
|
||||
`, (err) => {
|
||||
if (err) console.error('Error creating idx_archive_finding_id:', err);
|
||||
else console.log('✓ idx_archive_finding_id index created');
|
||||
});
|
||||
|
||||
db.run(`
|
||||
CREATE INDEX IF NOT EXISTS idx_archive_current_state
|
||||
ON ivanti_finding_archives(current_state)
|
||||
`, (err) => {
|
||||
if (err) console.error('Error creating idx_archive_current_state:', err);
|
||||
else console.log('✓ idx_archive_current_state index created');
|
||||
});
|
||||
|
||||
db.run(`
|
||||
CREATE INDEX IF NOT EXISTS idx_transition_archive_id
|
||||
ON ivanti_archive_transitions(archive_id)
|
||||
`, (err) => {
|
||||
if (err) console.error('Error creating idx_transition_archive_id:', err);
|
||||
else console.log('✓ idx_transition_archive_id index created');
|
||||
});
|
||||
});
|
||||
|
||||
db.close(() => {
|
||||
console.log('Migration complete!');
|
||||
});
|
||||
Reference in New Issue
Block a user