From 3ee848728667257c2a0b88a9362046cbcba2909a Mon Sep 17 00:00:00 2001 From: Jordan Ramos Date: Tue, 12 May 2026 14:23:56 -0600 Subject: [PATCH] Add bug report: Ivanti panel fixes 2026-05-12 (Invalid Date, workflow count, crash, BU scope) --- .../ivanti-panel-bugs-2026-05-12.md | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/bug-reports/ivanti-panel-bugs-2026-05-12.md diff --git a/docs/bug-reports/ivanti-panel-bugs-2026-05-12.md b/docs/bug-reports/ivanti-panel-bugs-2026-05-12.md new file mode 100644 index 0000000..5489828 --- /dev/null +++ b/docs/bug-reports/ivanti-panel-bugs-2026-05-12.md @@ -0,0 +1,103 @@ +# 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