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