diff --git a/backend/migrations/add_card_workflow_type.js b/backend/migrations/add_card_workflow_type.js new file mode 100644 index 0000000..143d727 --- /dev/null +++ b/backend/migrations/add_card_workflow_type.js @@ -0,0 +1,78 @@ +// Migration: Add CARD to workflow_type CHECK constraint on ivanti_todo_queue +// SQLite cannot ALTER a CHECK constraint, so this recreates the table. +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 add_card_workflow_type migration...'); + +db.serialize(() => { + db.run('PRAGMA foreign_keys = OFF', (err) => { + if (err) console.error('PRAGMA error:', err); + }); + + db.run('BEGIN TRANSACTION', (err) => { + if (err) { console.error('BEGIN error:', err); return; } + }); + + db.run(` + CREATE TABLE ivanti_todo_queue_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + finding_id TEXT NOT NULL, + finding_title TEXT, + cves_json TEXT, + vendor TEXT NOT NULL, + workflow_type TEXT NOT NULL CHECK(workflow_type IN ('FP', 'Archer', 'CARD')), + status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'complete')), + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ) + `, (err) => { + if (err) console.error('Error creating new table:', err); + else console.log('✓ ivanti_todo_queue_new created'); + }); + + db.run( + 'INSERT INTO ivanti_todo_queue_new SELECT * FROM ivanti_todo_queue', + (err) => { + if (err) console.error('Error copying data:', err); + else console.log('✓ Data copied'); + } + ); + + db.run('DROP TABLE ivanti_todo_queue', (err) => { + if (err) console.error('Error dropping old table:', err); + else console.log('✓ Old table dropped'); + }); + + db.run( + 'ALTER TABLE ivanti_todo_queue_new RENAME TO ivanti_todo_queue', + (err) => { + if (err) console.error('Error renaming table:', err); + else console.log('✓ Table renamed'); + } + ); + + db.run( + 'CREATE INDEX IF NOT EXISTS idx_todo_queue_user ON ivanti_todo_queue(user_id, status)', + (err) => { + if (err) console.error('Error creating index:', err); + else console.log('✓ Index recreated'); + } + ); + + db.run('COMMIT', (err) => { + if (err) console.error('COMMIT error:', err); + else console.log('✓ Transaction committed'); + }); + + db.run('PRAGMA foreign_keys = ON', () => {}); +}); + +db.close(() => { + console.log('Migration complete!'); +}); diff --git a/backend/routes/ivantiTodoQueue.js b/backend/routes/ivantiTodoQueue.js index 57b330e..6b83f18 100644 --- a/backend/routes/ivantiTodoQueue.js +++ b/backend/routes/ivantiTodoQueue.js @@ -1,7 +1,7 @@ // routes/ivantiTodoQueue.js const express = require('express'); -const VALID_WORKFLOW_TYPES = ['FP', 'Archer']; +const VALID_WORKFLOW_TYPES = ['FP', 'Archer', 'CARD']; const VALID_STATUSES = ['pending', 'complete']; function isValidVendor(vendor) { diff --git a/backend/server.js b/backend/server.js index 1166f44..e74e12d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -139,7 +139,7 @@ const db = new sqlite3.Database('./cve_database.db', (err) => { finding_title TEXT, cves_json TEXT, vendor TEXT NOT NULL, - workflow_type TEXT NOT NULL CHECK(workflow_type IN ('FP', 'Archer')), + workflow_type TEXT NOT NULL CHECK(workflow_type IN ('FP', 'Archer', 'CARD')), status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'complete')), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index 146292c..d1904ee 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -1084,7 +1084,14 @@ function AddToQueuePopover({ finding, anchorRect, queueForm, setQueueForm, onAdd useEffect(() => { if (!anchorRect) return; - setPos({ top: anchorRect.bottom + 6, left: anchorRect.left }); + const PANEL_W = 260; + const PANEL_H = 360; // conservative estimate (3 workflow buttons) + const spaceBelow = window.innerHeight - anchorRect.bottom - 6; + const top = spaceBelow >= PANEL_H + ? anchorRect.bottom + 6 + : Math.max(8, anchorRect.top - PANEL_H - 6); + const left = Math.min(anchorRect.left, window.innerWidth - PANEL_W - 8); + setPos({ top, left }); setTimeout(() => inputRef.current?.focus(), 0); }, [anchorRect]); @@ -1156,16 +1163,19 @@ function AddToQueuePopover({ finding, anchorRect, queueForm, setQueueForm, onAdd Workflow Type
- {['FP', 'Archer'].map((wt) => { - const active = queueForm.workflowType === wt; - const col = wt === 'FP' ? '#F59E0B' : '#0EA5E9'; + {[ + { key: 'FP', col: '#F59E0B', rgb: '245,158,11' }, + { key: 'Archer', col: '#0EA5E9', rgb: '14,165,233' }, + { key: 'CARD', col: '#10B981', rgb: '16,185,129' }, + ].map(({ key, col, rgb }) => { + const active = queueForm.workflowType === key; return ( ); })} @@ -1322,7 +1332,9 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onClearCompleted {/* Items */} {vendorItems.map((item) => { const done = item.status === 'complete'; - const isFP = item.workflow_type === 'FP'; + const wfColor = item.workflow_type === 'FP' ? { col: '#F59E0B', rgb: '245,158,11' } + : item.workflow_type === 'Archer' ? { col: '#0EA5E9', rgb: '14,165,233' } + : { col: '#10B981', rgb: '16,185,129' }; const cves = item.cves || []; const cveDisplay = cves.length > 0 ? cves.slice(0, 3).join(', ') + (cves.length > 3 ? ` +${cves.length - 3}` : '') @@ -1377,9 +1389,9 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onClearCompleted flexShrink: 0, padding: '0.1rem 0.35rem', borderRadius: '0.2rem', - background: isFP ? 'rgba(245,158,11,0.12)' : 'rgba(14,165,233,0.12)', - border: `1px solid ${isFP ? 'rgba(245,158,11,0.3)' : 'rgba(14,165,233,0.3)'}`, - color: isFP ? '#F59E0B' : '#0EA5E9', + background: `rgba(${wfColor.rgb},0.12)`, + border: `1px solid rgba(${wfColor.rgb},0.3)`, + color: wfColor.col, fontFamily: 'monospace', fontSize: '0.6rem', fontWeight: '700', }}> {item.workflow_type}