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
This commit is contained in:
175
.kiro/specs/queue-hostname-ip-display/design.md
Normal file
175
.kiro/specs/queue-hostname-ip-display/design.md
Normal file
@@ -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<br/>hostName, ipAddress] -->|POST /todo-queue| B[Express Route<br/>ivantiTodoQueue.js]
|
||||
B -->|INSERT hostname, ip_address| C[SQLite<br/>ivanti_todo_queue]
|
||||
C -->|SELECT *| B
|
||||
B -->|GET response| D[QueuePanel<br/>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 `<div>` 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 `<div>` 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.
|
||||
Reference in New Issue
Block a user