# Ivanti Workflows Panel Bug Fixes — 2026-05-12 ## Summary Multiple bugs in the Ivanti Workflows panel on the homepage caused incorrect data display, a React crash on archive card clicks, and missing BU scope filtering. These issues emerged after the PostgreSQL migration (timestamps changed format) and the multi-BU tenancy feature (archive panel was never wired to respect the admin scope toggle). **Commit:** `8c93e86` on `master` --- ## Bug 1: "Synced Invalid Date" **Symptom:** The Ivanti Workflows panel and Vulnerability Triage page both display "Synced Invalid Date" instead of the last sync timestamp. **Cause:** The frontend date parsing code was written for SQLite's `datetime('now')` format (`2024-01-15 10:30:00`) and applied `.replace(' ', 'T') + 'Z'` to convert it to ISO. After the PostgreSQL migration, the `pg` driver returns timestamps as ISO strings already (`2026-05-12T16:29:39.154Z`). Appending a second `Z` produces `"2026-05-12T16:29:39.154ZZ"` which is unparseable. **Fix:** Removed the `.replace(' ', 'T') + 'Z'` transformation. Now uses `new Date(syncedAt)` directly, which handles ISO strings correctly. **Files changed:** - `frontend/src/App.js` — home page sync display - `frontend/src/components/pages/ReportingPage.js` — triage page sync display --- ## Bug 2: Incorrect Total Workflows Count **Symptom:** The "Total Workflows" number displayed a value far higher than the actual number of workflows visible in the list (e.g., 6443 instead of 13). **Cause:** The backend `readState()` function returned `row.total` from the database, which stored the Ivanti API's `page.totalElements` value. This represents the total matching the user filter across all pages on the Ivanti platform — not the count of workflows actually cached locally. The Ivanti API's total count appears unreliable (returns inflated numbers that don't match the actual page content). **Fix:** Changed `readState()` to return `workflows.length` — the actual number of workflows in the cached JSON array. **Files changed:** - `backend/routes/ivantiWorkflows.js` --- ## Bug 3: React Crash on Archive Card Click (Blank Page) **Symptom:** Clicking any state card (Active, Archived, Returned, Closed) in the ArchiveSummaryBar caused the entire page to go blank. No error message visible to the user. **Cause:** PostgreSQL returns `numeric` columns as strings (e.g., `"9.38"` instead of `9.38`). The archive list rendering called `.toFixed(1)` directly on these string values. Strings do not have a `.toFixed()` method, causing a `TypeError` that crashed the React component tree (no ErrorBoundary exists to catch it). **Fix:** Wrapped severity values in `Number()` before calling `.toFixed()`: - `Number(a.last_severity).toFixed(1)` for archive severity - `Number(a.related_active.severity).toFixed(1)` for related finding severity **Files changed:** - `frontend/src/App.js` --- ## Bug 4: Archive Panel Not Respecting BU Scope **Symptom:** The ArchiveSummaryBar stat cards (Active, Archived, Returned, Closed) always showed counts for ALL business units, even when the admin scope toggle was set to "My Teams" (STEAM, ACCESS-ENG). Clicking a card also showed findings from all BUs. **Cause:** The archive endpoints (`/api/ivanti/archive/stats` and `/api/ivanti/archive`) had no `teams` query parameter support. The `ArchiveSummaryBar` component fetched stats without passing the active scope. The `handleArchiveStateClick` function in `App.js` fetched the archive list without a teams filter. This was a gap from the multi-BU tenancy implementation — the archive panel was never wired to respect the admin scope. **Fix:** - Backend: Added `teams` query parameter to both `/api/ivanti/archive/stats` and `/api/ivanti/archive` endpoints. When provided, the endpoints JOIN with `ivanti_findings` on `finding_id` to filter by `bu_ownership ILIKE ANY(patterns)`. - Frontend: `ArchiveSummaryBar` now accepts a `teamsParam` prop and passes it to the stats fetch. `App.js` passes `getActiveTeamsParam()` to the component and to `handleArchiveStateClick`. - The stats and list re-fetch automatically when the admin scope toggle changes. **Files changed:** - `backend/routes/ivantiArchive.js` - `frontend/src/components/pages/ArchiveSummaryBar.js` - `frontend/src/App.js` --- ## Bug 5: ACTIVE State Click Returns Empty **Symptom:** Clicking the "Active" stat card showed "No active findings" even though the count displayed a non-zero number. **Cause:** The stats endpoint counted ACTIVE findings from `ivanti_findings WHERE state = 'open'` (correct), but the list endpoint queried `ivanti_finding_archives WHERE current_state = 'ACTIVE'` — which has zero records. Active findings are not archived; they live in `ivanti_findings`. The archive table only contains ARCHIVED, RETURNED, CLOSED, and CLOSED_GONE states. **Fix:** When `state=ACTIVE` is requested, the list endpoint now queries `ivanti_findings` directly instead of `ivanti_finding_archives`. Results are formatted to match the archive card shape (finding_id, finding_title, host_name, ip_address, last_severity, etc.). **Files changed:** - `backend/routes/ivantiArchive.js` --- ## Bug 6: CLOSED_GONE Records Not Counted **Symptom:** The "Closed" stat card showed a lower count than expected. 63 findings with `current_state = 'CLOSED_GONE'` were not included in the Closed count or shown when clicking the Closed card. **Cause:** The stats endpoint only counted records matching the four known states (ACTIVE, ARCHIVED, RETURNED, CLOSED). The `CLOSED_GONE` state — added during the findings count investigation on 2026-04-24 — was not mapped to any display category. **Fix:** `CLOSED_GONE` records are now rolled into the CLOSED count in stats, and clicking the Closed card queries for both `CLOSED` and `CLOSED_GONE` states. **Files changed:** - `backend/routes/ivantiArchive.js` --- ## Verification - Frontend build passes without errors - Backend syntax validated with `node -c` - Archive stats with teams filter returns scoped counts (64 active for STEAM+ACCESS-ENG vs 6545 unfiltered) - `new Date(syncedAt).toLocaleString()` produces valid date strings - `Number("9.38").toFixed(1)` returns `"9.4"` without throwing