From 690c30aac0026b81b98c012aa490f6b9994b85bb Mon Sep 17 00:00:00 2001 From: jramos Date: Thu, 9 Apr 2026 11:56:56 -0600 Subject: [PATCH] feat: add hostname and IP display to Ivanti queue panel - Add migration to add hostname column to ivanti_todo_queue table - Update POST and batch POST endpoints to accept and store hostname - Pass hostName from findings data when adding items to queue - Display hostname and IP address in CARD queue section - Display hostname and IP address in vendor (FP/Archer) queue sections --- .gitignore | 3 + .../queue-hostname-ip-display/.config.kiro | 1 + .../specs/queue-hostname-ip-display/design.md | 175 ++++++++++++++++++ .../queue-hostname-ip-display/requirements.md | 70 +++++++ .../specs/queue-hostname-ip-display/tasks.md | 56 ++++++ backend/migrations/add_todo_queue_hostname.js | 25 +++ backend/routes/ivantiTodoQueue.js | 21 ++- docs/ivanti-api-reference.md | 4 +- .../src/components/pages/ReportingPage.js | 78 +++++--- 9 files changed, 402 insertions(+), 31 deletions(-) create mode 100644 .kiro/specs/queue-hostname-ip-display/.config.kiro create mode 100644 .kiro/specs/queue-hostname-ip-display/design.md create mode 100644 .kiro/specs/queue-hostname-ip-display/requirements.md create mode 100644 .kiro/specs/queue-hostname-ip-display/tasks.md create mode 100644 backend/migrations/add_todo_queue_hostname.js diff --git a/.gitignore b/.gitignore index 002823f..92639d1 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,6 @@ backend/add_vendor_to_documents.js backend/fix_multivendor_constraint.js backend/server.js-backup backend/setup.js-backup + +# Kiro implementation summary (internal only) +docs/kiro-implementation-summary.md diff --git a/.kiro/specs/queue-hostname-ip-display/.config.kiro b/.kiro/specs/queue-hostname-ip-display/.config.kiro new file mode 100644 index 0000000..24d668d --- /dev/null +++ b/.kiro/specs/queue-hostname-ip-display/.config.kiro @@ -0,0 +1 @@ +{"specId": "b8855eb4-3949-426e-86ac-36fe069a6bb1", "workflowType": "requirements-first", "specType": "feature"} \ No newline at end of file diff --git a/.kiro/specs/queue-hostname-ip-display/design.md b/.kiro/specs/queue-hostname-ip-display/design.md new file mode 100644 index 0000000..f3e8dd7 --- /dev/null +++ b/.kiro/specs/queue-hostname-ip-display/design.md @@ -0,0 +1,175 @@ +# Design Document: Queue Hostname & IP Display + +## Overview + +This feature adds hostname tracking to the Ivanti todo queue. Currently the queue stores `ip_address` but not `hostname`. The change spans three layers: + +1. **Database** — A migration adds a `hostname TEXT` column to `ivanti_todo_queue`. +2. **Backend API** — The POST (single + batch) endpoints accept and store an optional `hostname` field. The GET endpoint already uses `SELECT *`, so hostname is returned automatically once the column exists. +3. **Frontend** — The `addToQueue` and `submitBatch` functions pass `finding.hostName` as `hostname`. The QueuePanel renders hostname and IP address for both CARD and vendor-grouped (FP/Archer) sections. + +The change is additive and backward-compatible. Existing rows get `NULL` for hostname. No existing behavior changes unless both hostname and ip_address are present. + +## Architecture + +The data flows through three layers in a straight pipeline: + +```mermaid +flowchart LR + A[Ivanti Finding
hostName, ipAddress] -->|POST /todo-queue| B[Express Route
ivantiTodoQueue.js] + B -->|INSERT hostname, ip_address| C[SQLite
ivanti_todo_queue] + C -->|SELECT *| B + B -->|GET response| D[QueuePanel
ReportingPage.js] +``` + +No new services, tables, or route modules are introduced. The migration script is a standalone Node.js file following the existing pattern in `backend/migrations/`. + +## Components and Interfaces + +### Migration Script: `backend/migrations/add_todo_queue_hostname.js` + +Follows the exact pattern of `add_todo_queue_ip_address.js`: + +- Opens `cve_database.db` via `sqlite3` +- Runs `ALTER TABLE ivanti_todo_queue ADD COLUMN hostname TEXT` +- Catches `duplicate column name` error to make it idempotent +- Closes the database connection + +### Backend Route: `backend/routes/ivantiTodoQueue.js` + +Changes to two endpoints: + +**POST `/` (single-item)** +- Extract `hostname` from `req.body` +- Sanitize: if present and a string, trim and slice to 255 chars; otherwise `null` +- Add to the INSERT column list and parameter array + +**POST `/batch`** +- For each finding in the `findings` array, extract `hostname` from `f.hostname` +- Same sanitization as single-item +- Add to the per-row INSERT column list and parameter array + +**GET `/`** — No code change needed. `SELECT *` already returns all columns. + +**PUT `/:id`** — No change. Hostname is set at insert time and not editable. + +### Frontend: `ReportingPage.js` + +**`addToQueue` function** +- Add `hostname: finding.hostName || null` to the POST body + +**`submitBatch` function** +- Add `hostname: f.hostName || null` to each finding object in `findingsPayload` + +**QueuePanel rendering (per item)** + +For CARD items, the content `
` currently shows: +1. `finding_id` +2. `ip_address` (if present) + +New rendering for CARD items: +1. `finding_id` +2. `hostname` (if present) +3. `ip_address` (if present) + +For vendor-grouped items (FP/Archer), the content `
` currently shows: +1. `finding_id` +2. CVE list (if present) + +New rendering for vendor-grouped items: +1. `finding_id` +2. CVE list (if present) +3. `hostname` (if present) +4. `ip_address` (if present) + +Both hostname and IP use the same monospace styling at `0.68rem` / `0.62rem` with muted colors consistent with the existing design system. + +## Data Models + +### `ivanti_todo_queue` table (after migration) + +| Column | Type | Nullable | Notes | +|--------|------|----------|-------| +| id | INTEGER | NO | PRIMARY KEY AUTOINCREMENT | +| user_id | INTEGER | NO | FK → users(id) | +| finding_id | TEXT | NO | | +| finding_title | TEXT | YES | max 500 chars | +| cves_json | TEXT | YES | JSON array string | +| ip_address | TEXT | YES | max 64 chars | +| **hostname** | **TEXT** | **YES** | **max 255 chars (new)** | +| vendor | TEXT | NO | | +| workflow_type | TEXT | NO | FP, Archer, or CARD | +| status | TEXT | NO | pending or complete | +| created_at | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP | +| updated_at | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP | + +### API Request/Response Changes + +**POST `/api/ivanti/todo-queue` body** — adds optional field: +```json +{ + "finding_id": "...", + "finding_title": "...", + "cves": [], + "ip_address": "...", + "hostname": "server01.example.com", + "vendor": "...", + "workflow_type": "CARD" +} +``` + +**POST `/api/ivanti/todo-queue/batch` body** — adds optional field per finding: +```json +{ + "findings": [ + { "finding_id": "...", "ip_address": "...", "hostname": "server01.example.com" } + ], + "workflow_type": "FP", + "vendor": "VendorName" +} +``` + +**GET response** — `hostname` field included automatically via `SELECT *`: +```json +{ + "id": 1, + "finding_id": "...", + "hostname": "server01.example.com", + "ip_address": "10.0.0.1", + "..." +} +``` + + +## Correctness Properties + +*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* + +### Property 1: Hostname storage round-trip + +*For any* valid hostname string (up to 255 characters), storing it via the queue API (single or batch endpoint) and then retrieving it via GET should return the exact same trimmed string. When the hostname is omitted, null, or empty, the stored and returned value should be null. + +**Validates: Requirements 2.1, 2.2, 2.3, 2.4** + +### Property 2: Hostname display presence + +*For any* queue item with a non-null hostname value, the rendered QueuePanel output should contain the hostname text, regardless of whether the item is a CARD item or a vendor-grouped (FP/Archer) item. + +**Validates: Requirements 4.1, 5.1** + +## Error Handling + +| Scenario | Handling | +|----------|----------| +| Migration run when column already exists | Catch `duplicate column name` SQLite error, log skip message, exit cleanly | +| `hostname` field is not a string | Treat as null — store NULL in database | +| `hostname` exceeds 255 characters | Truncate to 255 characters via `.slice(0, 255)` | +| `hostname` is undefined/null/empty string | Store NULL in database | +| GET returns item with null hostname | Frontend conditionally renders — no hostname line shown | +| GET returns item with null ip_address and null hostname | CARD: show only finding_id. Vendor: show finding_id + CVEs only | + +No new error codes or HTTP status changes are introduced. The hostname field is optional and its absence is a normal case, not an error. + +## Testing Strategy + +Testing is out of scope for this feature. Manual verification will be performed after implementation. diff --git a/.kiro/specs/queue-hostname-ip-display/requirements.md b/.kiro/specs/queue-hostname-ip-display/requirements.md new file mode 100644 index 0000000..b945a61 --- /dev/null +++ b/.kiro/specs/queue-hostname-ip-display/requirements.md @@ -0,0 +1,70 @@ +# Requirements Document + +## Introduction + +The Ivanti Queue (todo queue) in the STEAM Security Dashboard currently stores and displays `ip_address` for CARD workflow items but omits hostname entirely. Vendor-grouped sections (FP/Archer) display only `finding_id` and CVEs, hiding the `ip_address` that is already stored. This feature adds a `hostname` column to the database, passes hostname through the backend API, and displays both hostname and IP address across all queue sections (CARD, FP, Archer). + +## Glossary + +- **Queue_Panel**: The slide-out side panel (`QueuePanel` component) that displays the user's staged Ivanti findings grouped by workflow type and vendor. +- **Queue_API**: The Express route module (`ivantiTodoQueue.js`) that handles CRUD operations on the `ivanti_todo_queue` table. +- **Queue_Table**: The SQLite table `ivanti_todo_queue` that persists per-user queue items. +- **CARD_Section**: The top group in the Queue_Panel that displays items with `workflow_type = 'CARD'`. +- **Vendor_Section**: Groups in the Queue_Panel for FP and Archer workflow items, organized by vendor name. +- **Finding**: An Ivanti host finding record containing fields such as `id`, `title`, `hostName`, `ipAddress`, `cves`, and `severity`. +- **Migration_Script**: A standalone Node.js script in `backend/migrations/` that alters the SQLite schema. + +## Requirements + +### Requirement 1: Add hostname column to the queue database table + +**User Story:** As a developer, I want the queue table to have a `hostname` column, so that hostname data can be persisted alongside each queued finding. + +#### Acceptance Criteria + +1. THE Migration_Script SHALL add a `hostname` TEXT column to the Queue_Table. +2. WHEN the `hostname` column already exists, THE Migration_Script SHALL skip the alteration and log a message indicating the column already exists. +3. THE Migration_Script SHALL preserve all existing rows and column data in the Queue_Table. + +### Requirement 2: Accept and store hostname in queue API endpoints + +**User Story:** As a developer, I want the queue API to accept a `hostname` field, so that hostname data is stored when findings are added to the queue. + +#### Acceptance Criteria + +1. WHEN a POST request is received at the single-item endpoint, THE Queue_API SHALL accept an optional `hostname` string field (max 255 characters) and store it in the Queue_Table. +2. WHEN a POST request is received at the batch endpoint, THE Queue_API SHALL accept an optional `hostname` string field on each finding object (max 255 characters) and store it in the Queue_Table. +3. WHEN the `hostname` field is omitted or empty, THE Queue_API SHALL store NULL for the `hostname` column. +4. WHEN a GET request is received, THE Queue_API SHALL return the `hostname` field for each queue item in the response. + +### Requirement 3: Pass hostname from the frontend to the queue API + +**User Story:** As a developer, I want the frontend to send hostname data when adding findings to the queue, so that hostname is captured from the Ivanti findings data. + +#### Acceptance Criteria + +1. WHEN a single finding is added to the queue, THE ReportingPage SHALL include the finding's `hostName` value in the `hostname` field of the POST request body. +2. WHEN findings are added via batch submission, THE ReportingPage SHALL include each finding's `hostName` value in the `hostname` field of the corresponding finding object in the POST request body. + +### Requirement 4: Display hostname and IP address in the CARD section + +**User Story:** As a security analyst, I want to see both hostname and IP address for CARD items in the queue, so that I can identify the affected host at a glance. + +#### Acceptance Criteria + +1. WHEN a CARD item has a `hostname` value, THE CARD_Section SHALL display the hostname below the finding ID. +2. WHEN a CARD item has an `ip_address` value, THE CARD_Section SHALL display the IP address below the hostname. +3. WHEN a CARD item has both `hostname` and `ip_address`, THE CARD_Section SHALL display hostname on one line and IP address on the next line. +4. WHEN a CARD item has only `ip_address` and no `hostname`, THE CARD_Section SHALL display the IP address (preserving current behavior). +5. WHEN a CARD item has only `hostname` and no `ip_address`, THE CARD_Section SHALL display the hostname. + +### Requirement 5: Display hostname and IP address in vendor sections (FP/Archer) + +**User Story:** As a security analyst, I want to see hostname and IP address for FP and Archer items in the queue, so that I can identify affected hosts without leaving the queue panel. + +#### Acceptance Criteria + +1. WHEN a vendor-grouped item has a `hostname` value, THE Vendor_Section SHALL display the hostname below the CVE list. +2. WHEN a vendor-grouped item has an `ip_address` value, THE Vendor_Section SHALL display the IP address below the hostname (or below the CVE list if no hostname exists). +3. WHEN a vendor-grouped item has both `hostname` and `ip_address`, THE Vendor_Section SHALL display hostname on one line and IP address on the next line, both below the CVE list. +4. WHEN a vendor-grouped item has neither `hostname` nor `ip_address`, THE Vendor_Section SHALL display only the finding ID and CVE list (preserving current behavior). diff --git a/.kiro/specs/queue-hostname-ip-display/tasks.md b/.kiro/specs/queue-hostname-ip-display/tasks.md new file mode 100644 index 0000000..38079dc --- /dev/null +++ b/.kiro/specs/queue-hostname-ip-display/tasks.md @@ -0,0 +1,56 @@ +# Implementation Plan: Queue Hostname & IP Display + +## Overview + +Add hostname tracking to the Ivanti todo queue across database, backend API, and frontend display layers. All changes are additive and backward-compatible. + +## Tasks + +- [x] 1. Create database migration to add hostname column + - Create `backend/migrations/add_todo_queue_hostname.js` following the exact pattern of `add_todo_queue_ip_address.js` + - Use `ALTER TABLE ivanti_todo_queue ADD COLUMN hostname TEXT` + - Handle `duplicate column name` error for idempotency + - Log appropriate messages for success and skip scenarios + - _Requirements: 1.1, 1.2, 1.3_ + +- [x] 2. Update backend API endpoints to accept and store hostname + - [x] 2.1 Update POST `/` (single-item) endpoint in `backend/routes/ivantiTodoQueue.js` + - Extract `hostname` from `req.body` + - Sanitize: if present and a string, trim and slice to 255 chars; otherwise `null` + - Add `hostname` to the INSERT column list and parameter array + - _Requirements: 2.1, 2.3_ + + - [x] 2.2 Update POST `/batch` endpoint in `backend/routes/ivantiTodoQueue.js` + - For each finding, extract `hostname` from `f.hostname` + - Apply same sanitization as single-item (trim, slice to 255, or null) + - Add `hostname` to the per-row INSERT column list and parameter array + - _Requirements: 2.2, 2.3_ + +- [x] 3. Checkpoint + - Ensure all backend changes are consistent, ask the user if questions arise. + +- [x] 4. Update frontend to pass hostname and display it in the queue panel + - [x] 4.1 Update `addToQueue` function in `ReportingPage.js` + - Add `hostname: finding.hostName || null` to the POST request body + - _Requirements: 3.1_ + + - [x] 4.2 Update `submitBatch` function in `ReportingPage.js` + - Add `hostname: f.hostName || null` to each finding object in the payload + - _Requirements: 3.2_ + + - [x] 4.3 Update CARD section rendering in QueuePanel (`ReportingPage.js`) + - Display `hostname` below finding_id (when present) + - Display `ip_address` below hostname (when present) + - Handle all combinations: both present, only hostname, only ip_address, neither + - Use monospace styling at `0.68rem` consistent with existing ip_address display + - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_ + + - [x] 4.4 Update vendor section (FP/Archer) rendering in QueuePanel (`ReportingPage.js`) + - Display `hostname` below the CVE list (when present) + - Display `ip_address` below hostname or below CVE list if no hostname + - Handle all combinations: both present, only one, neither + - Use monospace styling at `0.62rem` / `0.68rem` with muted colors matching existing design + - _Requirements: 5.1, 5.2, 5.3, 5.4_ + +- [x] 5. Final checkpoint + - Ensure all changes are wired together end-to-end, ask the user if questions arise. diff --git a/backend/migrations/add_todo_queue_hostname.js b/backend/migrations/add_todo_queue_hostname.js new file mode 100644 index 0000000..6004557 --- /dev/null +++ b/backend/migrations/add_todo_queue_hostname.js @@ -0,0 +1,25 @@ +// Migration: Add hostname column to ivanti_todo_queue +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_todo_queue_hostname migration...'); + +db.run( + 'ALTER TABLE ivanti_todo_queue ADD COLUMN hostname TEXT', + (err) => { + if (err) { + // Column may already exist if migration was run before + if (err.message.includes('duplicate column name')) { + console.log('✓ hostname column already exists, skipping'); + } else { + console.error('Error adding column:', err); + } + } else { + console.log('✓ hostname column added'); + } + db.close(() => console.log('Migration complete!')); + } +); diff --git a/backend/routes/ivantiTodoQueue.js b/backend/routes/ivantiTodoQueue.js index a1e8500..cae581a 100644 --- a/backend/routes/ivantiTodoQueue.js +++ b/backend/routes/ivantiTodoQueue.js @@ -56,6 +56,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) { * @body {string} [findings[].finding_title] - Optional finding title (max 500 chars) * @body {string[]} [findings[].cves] - Optional array of CVE identifiers * @body {string} [findings[].ip_address] - Optional IP address (max 64 chars) + * @body {string} [findings[].hostname] - Optional hostname (max 255 chars) * @body {string} workflow_type - One of 'FP', 'Archer', 'CARD' * @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD * @@ -108,7 +109,10 @@ function createIvantiTodoQueueRouter(db, requireAuth) { const ipVal = f.ip_address && typeof f.ip_address === 'string' ? f.ip_address.trim().slice(0, 64) : null; - return [userId, findingId, title, cvesJson, ipVal, vendorVal, workflow_type]; + const hostVal = f.hostname && typeof f.hostname === 'string' + ? f.hostname.trim().slice(0, 255) + : null; + return [userId, findingId, title, cvesJson, ipVal, hostVal, vendorVal, workflow_type]; }); const insertedIds = []; @@ -121,8 +125,8 @@ function createIvantiTodoQueueRouter(db, requireAuth) { rows.forEach((params) => { db.run( `INSERT INTO ivanti_todo_queue - (user_id, finding_id, finding_title, cves_json, ip_address, vendor, workflow_type) - VALUES (?, ?, ?, ?, ?, ?, ?)`, + (user_id, finding_id, finding_title, cves_json, ip_address, hostname, vendor, workflow_type) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, params, function (err) { if (err && !insertError) { @@ -199,7 +203,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) { * @body {string} [finding_title] - Optional finding title (max 500 chars) * @body {string[]} [cves] - Optional array of CVE identifiers * @body {string} [ip_address] - Optional IP address (max 64 chars) - * @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD + * @body {string} [hostname] - Optional hostname (max 255 chars) * @body {string} vendor - Required for FP/Archer (max 200 chars); optional for CARD * @body {string} workflow_type - One of 'FP', 'Archer', 'CARD' * * @returns {Object} 201 - Created queue item with parsed cves array: @@ -209,7 +213,7 @@ function createIvantiTodoQueueRouter(db, requireAuth) { * @returns {Object} 500 - { error: string } on database error */ router.post('/', requireAuth(db), requireGroup('Admin', 'Standard_User'), (req, res) => { - const { finding_id, finding_title, cves, ip_address, vendor, workflow_type } = req.body; + const { finding_id, finding_title, cves, ip_address, hostname, vendor, workflow_type } = req.body; if (!finding_id || typeof finding_id !== 'string' || finding_id.trim().length === 0) { return res.status(400).json({ error: 'finding_id is required.' }); @@ -228,15 +232,16 @@ function createIvantiTodoQueueRouter(db, requireAuth) { const vendorVal = workflow_type === 'CARD' ? '' : vendor.trim(); const cvesJson = Array.isArray(cves) ? JSON.stringify(cves) : null; const ipVal = ip_address && typeof ip_address === 'string' ? ip_address.trim().slice(0, 64) : null; + const hostVal = hostname && typeof hostname === 'string' ? hostname.trim().slice(0, 255) : null; const title = finding_title && typeof finding_title === 'string' ? finding_title.slice(0, 500) : null; db.run( `INSERT INTO ivanti_todo_queue - (user_id, finding_id, finding_title, cves_json, ip_address, vendor, workflow_type) - VALUES (?, ?, ?, ?, ?, ?, ?)`, - [req.user.id, finding_id.trim(), title, cvesJson, ipVal, vendorVal, workflow_type], + (user_id, finding_id, finding_title, cves_json, ip_address, hostname, vendor, workflow_type) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [req.user.id, finding_id.trim(), title, cvesJson, ipVal, hostVal, vendorVal, workflow_type], function (err) { if (err) { console.error('Error adding to queue:', err); diff --git a/docs/ivanti-api-reference.md b/docs/ivanti-api-reference.md index 509d13c..c1807b6 100644 --- a/docs/ivanti-api-reference.md +++ b/docs/ivanti-api-reference.md @@ -63,7 +63,7 @@ Key details: - `operator` values: `EXACT`, `IN`, `LIKE`, `WILDCARD`, `RANGE`, `CIDR` - For empty workflows, use `{"subject":"hostFinding","filterRequest":{"filters":[]}}` with `isEmptyWorkflow=true` -#### Response (200) +#### Response (200/202) ```json { @@ -72,7 +72,7 @@ Key details: } ``` -Returns a numeric `id` (the workflow batch job ID) and `created` timestamp. No `generatedId` or `uuid` in this response. +Returns HTTP 200 or 202 (Accepted — async job creation). Response contains a numeric `id` (the workflow batch job ID) and `created` timestamp. No `generatedId` or `uuid` in this response. ### Other Workflow Endpoints (from Swagger) diff --git a/frontend/src/components/pages/ReportingPage.js b/frontend/src/components/pages/ReportingPage.js index 562e883..01fde5a 100644 --- a/frontend/src/components/pages/ReportingPage.js +++ b/frontend/src/components/pages/ReportingPage.js @@ -1430,28 +1430,62 @@ function QueuePanel({ open, items, onClose, onUpdate, onDelete, onDeleteMany, on {item.finding_id}
{isCardItem ? ( - item.ip_address && ( -
- {item.ip_address} -
- ) + <> + {item.hostname && ( +
+ {item.hostname} +
+ )} + {item.ip_address && ( +
+ {item.ip_address} +
+ )} + ) : ( - cves.length > 0 && ( -
- {cveDisplay} -
- ) + <> + {cves.length > 0 && ( +
+ {cveDisplay} +
+ )} + {item.hostname && ( +
+ {item.hostname} +
+ )} + {item.ip_address && ( +
+ {item.ip_address} +
+ )} + )}
@@ -2484,6 +2518,7 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) { finding_title: finding.title || null, cves: finding.cves || [], ip_address: finding.ipAddress || null, + hostname: finding.hostName || null, vendor: queueForm.vendor.trim(), workflow_type: queueForm.workflowType, }), @@ -2536,6 +2571,7 @@ export default function VulnerabilityTriagePage({ filterDate, filterEXC }) { finding_title: f.title || null, cves: f.cves || [], ip_address: f.ipAddress || null, + hostname: f.hostName || null, } : { finding_id: id }; }); const res = await fetch(`${API_BASE}/ivanti/todo-queue/batch`, {