256 Commits

Author SHA1 Message Date
Jordan Ramos
416bfd2e28 Add Infoblox DNS lookup, Scan Posture page, and Ivanti OS fields
- Infoblox WAPI integration (read-only) for resolving IPs to FQDNs
  - backend/helpers/infobloxApi.js — Basic auth, host/PTR/IPv6 lookups
  - backend/routes/infoblox.js — /api/infoblox endpoints
  - Globe icon on Reporting page hostName/dns columns for one-click DNS lookup
  - Pending: WAPI credentials with API access permissions

- Scan Posture page for Access Ops platform/version tracking
  - backend/routes/scanPosture.js
  - frontend/src/components/pages/ScanPosturePage.js
  - Page visibility and nav drawer entries

- Ivanti findings OS field enrichment
  - Migration to add os_name/os_class/os_version columns
  - Backfill script for existing findings
  - ivantiFindings route updates to persist OS data on sync
2026-08-21 10:33:46 -06:00
Jordan Ramos
7484e81b9a Add interactive report viewer to Exports page
Transform Exports page from download-only to interactive report workbench.
All export buttons now open a full-page ReportViewer with sort, filter,
inline edit, row deletion, and xlsx download of the curated data.

New features:
- ReportViewer component with multi-sheet tab support
- Atlas Commitment Dates report (overdue highlighting, hostname resolution from DB)
- Scan Type Coverage report (agent/network/mixed per host with summary bar)
- GET /api/atlas/commitments endpoint (JOINs atlas cache with findings for hostnames)
2026-08-18 14:58:27 -06:00
Jordan Ramos
d3adc8e1fc Add scan type indicator (agent vs network appliance) to Ivanti findings
Detect whether a finding was discovered by Qualys Cloud Agent (authenticated)
or a network appliance scan (unauthenticated) based on the presence of
'Agent ID' in hostAdditionalDetails from the Ivanti API response.

- Add scan_type column to ivanti_findings table (migration)
- Extract scanType in extractFinding() during sync
- Include scan_type in upsert and API response
- Add ScanTypeBadge component (green AGT / orange NET) on ReportingPage
- Add /raw-inspect diagnostic endpoint for inspecting raw Ivanti data
2026-08-18 10:54:23 -06:00
Jordan Ramos
3aa7a6e49e Surface NetBox IPAM records when IP has no assigned device
The /devices/by-ip/:ip endpoint previously returned 404 when an IP existed
in NetBox IPAM but wasn't assigned to a device interface. Now returns the
IPAM record (dns_name, status, role, description, comments) with a 200
response and ipamOnly flag.

Frontend NetBoxBadge gains a new 'ipam_only' state — purple 'IP' pill badge
with a dedicated tooltip showing available IPAM metadata (address, status,
DNS name, FQDN, role, VRF, tenant). Distinguishes clearly from full device
records while still surfacing useful infrastructure context.
2026-08-10 15:40:53 -06:00
Jordan Ramos
9d0e4b910e Release v2.6.0 — Granite Loader enhancements, NetBox badge, compliance fixes
New features:
- Granite supplemental workbook ingest with reconciliation
- Drag-fill and bulk default cascade in Granite Loader
- OS type searchable dropdown in Granite Loader
- Persist selections across pages + Select All
- NetBox badge on Reporting page with CARD cross-check
- CARD API connection retry for DNS round-robin dead nodes
- Granite Loader export to supplemental hygiene section

Fixes:
- Compliance reconcile stale core columns (type field vs string matching)
- Compliance upload case-insensitive column matching
- Compliance summary query NULL vertical handling
- Compliance page state persistence across uploads
- TLS for GitLab feedback integration after v19 upgrade

Also: updated user guide with all new features, added release comms template.
2026-08-10 14:52:37 -06:00
Jordan Ramos
ab8a14bf1b Sync frontend complianceCategories.json with pruned backend config
The compliance_config.json was pruned to remove metrics no longer tracked,
but the frontend category map was not updated in sync. This caused the
compliance-drift-metric-coverage test to fail in CI.
2026-08-10 14:37:18 -06:00
Jordan Ramos
1d42e09b9b Add OS type searchable dropdown to Granite Loader
Map the OS (UDA#CONTROLLER CONFIG#OS) and OSTYPE (UDA#EQUIP MIGRATION#OSTYPE)
columns to a picklist of 38 standard OS types extracted from the Granite
Team_Device Loader reference sheet. Both columns now render as searchable
dropdowns in the bulk defaults row and inline cell editing.
2026-08-10 14:30:53 -06:00
Jordan Ramos
d0f6460c0a Fix compliance upload: case-insensitive column matching, summary query, page persistence
- Parser (parse_compliance_xlsx.py): column name matching is now
  case-insensitive via _resolve_col() helper. New reports using lowercase
  headers (e.g. 'preferred - hostname') are parsed correctly instead of
  silently returning 0 items.

- Drift checker (driftChecker.js): core column presence check uses
  case-insensitive comparison so lowercase headers no longer trigger
  false 'missing from all detail sheets' breaking findings.

- Summary endpoint (compliance.js): query now selects the most recent
  upload where vertical IS NULL OR vertical = 'NTS_AEO' in a single
  query, instead of preferring NULL-vertical legacy uploads that are
  outdated.

- Page persistence (App.js): localStorage page restore no longer checks
  canAccessPage during useState init (user is null at that point).
  An effect validates the page once auth resolves.

Closes #45
2026-08-10 13:55:10 -06:00
Jordan Ramos
68431f2d3f Persist selections across pages, add Select All for Granite Loader
- Selections persist when paging through the device table
- Select All button fetches all hostnames for the active sheet
- Granite Loader button fetches full device data for all selected
  (not just current page) before opening the modal
- Clear selection button when items are selected across pages
- Page checkbox adds to existing selection instead of replacing
2026-08-05 11:51:00 -06:00
Jordan Ramos
c7a4e411ce Add drag-fill and bulk default cascade to Granite Loader
- Drag-fill: hover bottom-right corner of a cell to get a fill handle,
  drag down to fill that value into rows below (Excel-style)
- Bulk default deletion now cascades: clearing a default also removes
  per-row overrides that matched that value
- initialDevices now passes equip_inst_id through from supplemental data
2026-08-04 15:30:58 -06:00
Jordan Ramos
4852ea3c1a Add Granite Loader export to supplemental hygiene section
Select devices via checkboxes in the Granite Hygiene device table and
generate Granite Loader sheets directly — equip_inst_id is already
present from the supplemental data so no CARD enrichment needed.
2026-08-04 12:57:59 -06:00
Jordan Ramos
eb0d55a35b Add NetBoxBadge to reporting page, CARD hostname extraction, docs
- NetBoxBadge inline pill on ReportingPage host column
- CARD owner endpoint now returns hostname from card_flags/ivanti_assets
- card-to-granite-field-mapping reference doc
- Supplemental workbook sample in docs
- netbox-dry-run diagnostic script
2026-08-03 11:57:35 -06:00
Jordan Ramos
e3dce7dbbc Add Granite supplemental workbook ingest with reconciliation
Separate upload path for the NTS_AEO_(supp only) workbook that tracks
Granite CMDB hygiene findings (Missing OS, Missing App ID, Missing Device
Function, Retired App ID) with weekly history and team scoping.

Backend:
- Python parser for supplemental xlsx (4 recognized sheets)
- supplemental_uploads and supplemental_items tables with per-sheet scoped resolution
- supplemental_notes table for per-device notes
- resolution_date and remediation_plan on supplemental items
- Full route module: preview, commit, summary, items, trends, uploads, rollback,
  device detail, metadata PATCH, notes CRUD

Frontend:
- SupplementalUploadModal with per-sheet diff preview
- GraniteHygieneSection on CompliancePage with cards, device drill-down, pagination
- SupplementalDetailPanel slide-out with findings, metadata editing, and notes
- Upload Supplemental button in CompliancePage header
2026-08-03 11:54:12 -06:00
Jordan Ramos
e8a5bdc196 Fix EQUIP_INST_ID extraction fallbacks and minor cleanups
- Add additional fallback sources for EQUIP_INST_ID in CARD extractGraniteFields
  (card_flags, ivanti_assets, top-level asset fields)
- Add CARD_DEBUG env var gate for troubleshooting extraction logic
- Clarify anomaly/latest endpoint comment (only significant rows returned)
- Remove stale convention comment in LoaderModal.js
2026-07-06 15:18:56 -06:00
Jordan Ramos
ffaaf961e8 Add quick-create CVE option to tooltip for missing CVEs
When hovering a CVE badge on the Reporting Page that doesn't exist in the
database, the tooltip now shows a 'Not yet tracked' message with an 'Add CVE'
button (permission-gated to Admin/Standard_User). Clicking opens a modal that
auto-populates metadata from NVD and submits via POST /api/cves.

Features:
- Create_Tooltip variant in CveTooltip for exists:false responses
- CveCreateModal with NVD auto-population, form validation, error handling
- 150ms hover bridge so users can reliably click the tooltip button
- Cache invalidation on successful creation
- Success toast notification (4s auto-dismiss)
2026-07-02 09:59:51 -06:00
Jordan Ramos
043a20f63d Skip already-enriched devices on CARD retry
On retry, only send devices still missing EQUIP_INST_ID to CARD.
Devices that already came back with data on the first pass are
skipped, reducing API calls and wait time when only a subset of
devices timed out.
2026-06-30 14:56:11 -06:00
Jordan Ramos
3d9481d95b Pin common APPIDs to top of picker with ★ prefix
Add 10 most-used APPIDs (CMTS, Jump Servers, Console Servers, RANCID,
CableOS, NDI, NTP, Vecima, Tools Infra, Adtran) as starred entries
at the top of the dropdown for quick access. Full CMDB list follows
below. Star prefix is stripped during value extraction so cell still
stores clean 'APP_ID - APPREFID' format.
2026-06-30 14:45:41 -06:00
Jordan Ramos
cb49b16f0b Extract APP_ID - APPREFID from display string on selection
The SearchableSelect shows full context for searching (app name, env)
but when a selection is made, only the 'APP_ID - APPREFID' portion is
stored as the cell value. Applies to both bulk defaults and per-cell
inline edits.
2026-06-30 14:41:27 -06:00
Jordan Ramos
0680a82aaf Fix APPID picker crash — use plain strings for SearchableSelect
SearchableSelect expects string arrays, not objects. Convert CMDB
options to strings in format 'APP_NAME | APP_ID - APPREFID [ENV]'
so filtering and display work correctly.
2026-06-30 14:22:27 -06:00
Jordan Ramos
c338e6fa3b Add APPID + APPREFID searchable picker to Granite Loader
Load CMDB app instance data (4797 entries) from static JSON and present
a searchable dropdown in the APP_ID_ASSET_TAG column. Users search by
app name or APP ID, select an entry, and the field populates with the
'APP_ID - APPREFID' format required by Granite.

- Convert Cherwell CMDB CSV export to frontend/public/cmdb-appids.json
- LoaderModal: load APPID options on open, provide via getPicklist()
  helper that overrides COLUMN_PICKLISTS for the APP_ID_ASSET_TAG field
- Options display as 'APP_NAME (APP_ID) [ENV]' for easy identification
- Works in both bulk defaults and per-cell inline editing
2026-06-30 14:17:21 -06:00
Jordan Ramos
93d68bccfe Fix BU reassignment detail — fetch by count instead of time window
The BU history records may have been written days before the anomaly
detected them leaving scope. Instead of a fragile time window, fetch
the N most recent BU change records where N matches the classification
count shown in the banner. This ensures the detail always matches
the summary.
2026-06-29 12:04:18 -06:00
Jordan Ramos
5037d68613 Fix anomaly banner BU reassignment detail display
- Backend: /anomaly/latest now returns the most recent SIGNIFICANT
  anomaly instead of the absolute latest (non-significant ones are
  useless to the banner which skips them anyway)
- Frontend: expand BU detail time window from 60 minutes to 25 hours
  before the anomaly timestamp to capture changes from the full sync
  cycle (syncs run once per 24h)
2026-06-29 12:01:03 -06:00
Jordan Ramos
6f9a5885e1 Add search, filter, and sort to Admin Panel user management
Same UX improvements as the modal — search bar, group filter chips,
sortable column headers, and filtered count. Applied to the in-page
UserManagementPanel in AdminPage.js so the Admin Panel tab has
parity with the modal.
2026-06-26 10:06:54 -06:00
Jordan Ramos
80761022b7 Add search, filter, and sort to User Management table
- Search bar filters users by name, email, or team assignment
- Group filter chips: All, Admin, Standard_User, Leadership, Read_Only, No Teams
- Sortable column headers (User, Group, Teams, Status, Last Login)
- Shows filtered count (e.g., '5 of 12 users')
- 'No Teams' filter quickly identifies users needing team assignment
2026-06-26 09:53:27 -06:00
Jordan Ramos
93de7bbca7 Add SVP org hierarchy discovery to BU Lookup panel
Backend:
- GET /api/ivanti/findings/org-hierarchy — returns all SVP names and
  all BU names from Ivanti suggest API (Admin only)
- GET /api/ivanti/findings/bus-by-svp?svp=<name> — returns BUs under
  a specific SVP with finding counts (Admin only)

Frontend (Admin > BU Lookup tab):
- 'Load Org Hierarchy' button fetches all SVPs and BUs
- SVP dropdown (sorted by finding count) to filter BUs by org leader
- Results table shows BU name, finding count, and configured status
  (green badge for BUs already in the dashboard, grey for unconfigured)
- Enables admin to discover which BUs roll up under an SVP for correct
  team assignment when onboarding new users
2026-06-26 09:33:45 -06:00
Jordan Ramos
58996cf4cf Add BU Lookup tool to Admin panel
Backend:
- GET /api/ivanti/findings/bu-lookup?q=<hostname|ip> — queries the
  live Ivanti API to discover which BU a host is assigned to. Returns
  deduplicated results with hostName, ipAddress, BU, and hostId.
  Admin-only endpoint.

Frontend:
- Add 'BU Lookup' tab to Admin page with search input and results table
- Shows BU assignment badge (blue for tagged, amber for untagged)
- Supports Enter key to search, loading state, error display
- Useful for verifying team assignments when onboarding new users
2026-06-26 09:20:53 -06:00
Jordan Ramos
7866a8577a Fix User Guide TOC — add heading IDs and smooth scroll anchor links
Headings now render with id attributes derived from their text so
TOC anchor links (#getting-started, etc.) have targets. Internal
anchor clicks use scrollIntoView with smooth behavior instead of
full page navigation.
2026-06-24 17:52:37 -06:00
Jordan Ramos
627d957c96 Add in-app User Guide modal accessible from user menu
- UserGuideModal: full-screen overlay rendering user-guide.md as
  styled markdown with search filtering
- UserMenu: add 'User Guide' item with BookOpen icon between
  Feature Request and Sign Out
- Serve user-guide.md as static file from frontend/public/
- Guide renders with dark theme styling matching the dashboard
  aesthetic (blue headers, slate text, amber blockquotes)
2026-06-24 17:48:30 -06:00
Jordan Ramos
b078289c1c Replace React favicon with AEGIS shield logo
Convert shieldlogo.jpeg to favicon.ico (16x16, 32x32), logo192.png,
and logo512.png for the browser tab and PWA manifest.
2026-06-24 17:41:21 -06:00
Jordan Ramos
8c789ce765 Add View As (impersonation) feature for Admin users
Allow Admin users to temporarily view the app as another user to verify
permissions and team scoping without switching accounts.

Backend:
- Migration: add impersonate_user_id column to sessions table
- requireAuth(): when impersonation is active, override req.user with
  target user's identity; store real admin identity in req.realUser
- POST /api/auth/impersonate: start impersonation (Admin only, cannot
  impersonate self or other Admins)
- POST /api/auth/stop-impersonate: end impersonation, revert to real user
- GET /api/auth/me: returns impersonating flag and realUser when active
- Audit logging on impersonate start/stop

Frontend:
- AuthContext: add impersonating, realUser state; startImpersonation()
  and stopImpersonation() helpers
- ImpersonationBanner: fixed amber banner showing target user identity
  with Exit button
- UserManagement: Eye icon button on each non-Admin user row to start
  View As (visible only to Admin, hidden for self and other Admins)
- App.js: mount ImpersonationBanner at top of authenticated view
2026-06-24 12:57:57 -06:00
Jordan Ramos
11d9fec3ec Add page visibility by group with centralized matrix
Introduce a Page Visibility Matrix that controls which pages each user
group can access, enforced in both frontend and backend:

Frontend:
- Create frontend/src/config/pageVisibility.js with PAGE_VISIBILITY
  matrix and canAccessPage() / getAccessiblePages() helpers
- NavDrawer: replace inline requiredGroups with canAccessPage() filter
- App.js: replace per-page isInGroup()/isAdmin() checks with generic
  route guard in setCurrentPage; remove VALID_PAGES constant
- localStorage validation: verify persisted page is accessible on load

Backend (page-level access enforcement):
- jiraTickets.js: add router-level requireGroup('Admin','Standard_User')
- archerTemplates.js: add router-level requireGroup('Admin','Standard_User')
- VCL multi-vertical already had requireGroup('Admin','Leadership')

Visibility matrix:
- Home, Knowledge Base: all groups
- Triage, Compliance, Exports: Admin, Standard_User, Leadership
- CCP Metrics: Admin, Leadership
- Jira, Archer Templates: Admin, Standard_User
- Admin Panel: Admin only
- Read_Only sees only Home and Knowledge Base
2026-06-24 11:41:50 -06:00
Jordan Ramos
ab66d7d813 Add drag-and-drop document upload
Replace the old prompt()-based file upload flow with an inline drop zone.

Before: Click 'Upload Doc' → native file picker → prompt() for type →
prompt() for notes → upload. Three modal interruptions.

After: Drop a file (or click to browse) → inline form shows with type
dropdown and notes field → click Upload. Zero browser dialogs.

New component: DocumentDropZone
- Drag-and-drop with visual feedback (border color change on dragover)
- Click-to-browse fallback
- Inline type selector (advisory, email, screenshot, patch, other)
- Inline notes field
- Cancel button to dismiss without uploading
- Shows filename and size before upload
- Uses toast notifications for success/error

Removed from CVECard:
- handleFileUpload function (createElement('input') + prompt() pattern)
- uploadingFile state variable
- Upload (lucide) icon import
2026-06-23 12:19:56 -06:00
Jordan Ramos
f119cca1d7 Add recent activity feed and tabbed sidebar layout
New features:
- Recent Activity feed widget shows last 8 actions from audit log
  with relative timestamps, auto-refreshes every 60s
- Right sidebar reorganized: Calendar + Activity always visible,
  Tickets/Archer/Ivanti behind tab switcher to eliminate dead space

Backend:
- New GET /api/recent-activity endpoint (any authenticated user)
  Returns last N audit entries excluding login/logout noise
  Lighter than the full admin audit-logs endpoint

Frontend:
- RecentActivityFeed component with action labels, colored dots,
  timeAgo formatting, and manual refresh button
- SidebarTabs component with Tickets/Archer/Ivanti tabs
- OpenTicketsPanel and IvantiWorkflowPanel support embedded prop
  to render without their own panel wrapper when inside tabs

Layout change:
Before: Calendar | Tickets | Archer | Ivanti (4 stacked panels)
After:  Calendar | Activity | [Tickets | Archer | Ivanti] (tabs)

This keeps the sidebar height proportional to the CVE list area
instead of extending far below the main content.
2026-06-23 12:16:40 -06:00
Jordan Ramos
306950e360 Extract inline styles to CSS classes
Move JavaScript style objects from home page components into reusable
CSS classes in App.css. This follows the existing pattern (intel-button,
intel-card, intel-input) and consolidates all visual styling in one place.

New CSS classes added:
- .panel-card (--accent, --warning, --teal) — sidebar panels
- .section-heading (--accent, --warning, --teal) — monospace headings
- .stat-card modifiers (--clickable, --active, --warning, --danger)
- .stat-card__label / .stat-card__value (--accent, --neutral, etc.)
- .severity-badge (--critical, --high, --medium, --low)
- .glow-dot (--critical, --high, --medium, --low)
- .sidebar-ticket — compact ticket cards
- .workflow-item — Ivanti workflow entries
- .workflow-state-badge — teal state pill
- .ticket-status-badge — small status indicator
- .archive-item (--active, --resolved) — finding archive entries
- .big-counter (--warning, --teal) — large centered stat numbers

Benefits:
- 578 fewer lines of JavaScript across components
- Styles are browser-cached separately from JS bundle
- Single source of truth for the design system
- Easier to update colors/spacing project-wide
2026-06-23 11:58:44 -06:00
Jordan Ramos
4a0adfb574 Refactor home page: extract components, add toast system, debounce search
Major restructuring of the monolithic App.js (2484 lines) into focused,
testable components:

Architecture:
- App.js is now a 189-line routing shell (header, nav, page switching)
- HomePage.js orchestrates all home page state and layout
- Each visual section is its own component with clear props API

Extracted components:
- StatsBar: clickable stat cards that filter by severity
- QuickCVELookup: CVE existence check with inline results
- CVEFilters: search + vendor/severity dropdowns
- CVECard: expandable CVE with vendor entries, docs, tickets
- OpenTicketsPanel: right sidebar open JIRA tickets
- IvantiWorkflowPanel: right sidebar Ivanti workflow status + archive

Extracted modals:
- AddCVEModal: self-contained add form with NVD auto-fill
- EditCVEModal: self-contained edit form with NVD update
- JiraTicketModal: unified add/edit JIRA ticket modal
- ArcherTicketModal: unified add/edit Archer ticket modal

Performance optimizations:
- Debounced search (300ms) via useDebounce hook — eliminates
  redundant API calls on every keystroke
- Memoized groupedCVEs, openTicketCount, criticalCount via useMemo
- Proper state updates (no direct mutation of cveDocuments)
- useCallback on fetch functions to stabilize effect dependencies

UX improvements:
- Toast notification system replaces all alert() calls
- Stat cards are now clickable to filter CVE list by severity
- onKeyDown replaces deprecated onKeyPress
- aria-labels added to interactive elements

Infrastructure:
- ToastContext with auto-dismiss, typed toasts (success/error/warning/info)
- useDebounce custom hook for reuse across the app
- Toast slide-in animation in App.css
2026-06-23 11:46:39 -06:00
Jordan Ramos
223b6f22b8 Fix BulkHideToolbar not sticking when scrolling on Reporting page
Add position: sticky, top: 0, zIndex: 20 to BulkHideToolbar so the
Atlas Action Plan button remains visible while scrolling through findings.
Matches the existing sticky behavior of SelectionToolbar.
2026-06-22 15:57:45 -06:00
Jordan Ramos
e9d6038636 Add Granite Loader to AEO Compliance page with CARD enrichment and pagination
- Add checkbox selection + Granite Loader button to compliance device table
- Integrate LoaderModal for generating loader sheets from compliance devices
- Add direct IP resolve path (resolveAssetId + searchByAssetId) for CARD
  enrichment on compliance devices without Ivanti host IDs
- Add searchByAssetId helper for full enriched record via asset-search endpoint
- Include NTS-AEO-ACCESS-OPS in default enrich-batch team search
- Increase CARD quick-mode timeout from 15s to 30s
- Add timeout vs not-found distinction in enrichment error reporting
- Fix LoaderModal enriching state not resetting on modal reopen
- Add pagination to compliance device table (25/50/100/200 per page)
- Page resets on team, tab, filter, or search change
2026-06-19 13:49:26 -06:00
Jordan Ramos
a95fd03f5e Rebrand STEAM → AEGIS, fix BU drift checker previous_bu bug
- Replace all STEAM branding with AEGIS (Advanced Engineering Group
  Intelligence System) across login, header, nav drawer, manifest, and
  browser title
- Add shield logo to login page, main header, and nav drawer
- Fix BU drift checker recording incorrect previous_bu values by
  building a previousBuMap snapshot BEFORE the upsert/delete cycle
  instead of querying the DB after rows are already gone
- Clean 526 bogus BU history entries generated by the broken logic
- Add docs and scripts from prior session
2026-06-17 14:40:38 -06:00
Jordan Ramos
479c61b88f Restrict VCL/CCP Metrics page to Admin and Leadership groups
Add requireGroup('Admin', 'Leadership') as router-level middleware on all
VCL multi-vertical routes. Hide the CCP Metrics nav item from users not in
those groups and guard the page render in App.js with a redirect fallback.
2026-06-17 09:27:01 -06:00
Jordan Ramos
3e8bb1828c Fix remark-gfm version — v3 incompatible with react-markdown v10
remark-gfm@3.0.1 uses the unified v10 ecosystem but react-markdown@10
requires the unified v11 ecosystem. This caused 'this.getRole is not a
function' at runtime, blanking the KB viewer. Upgraded to remark-gfm@4
which is compatible.
2026-06-16 14:22:52 -06:00
Jordan Ramos
8d47f67318 Add remark-gfm for markdown table rendering in Knowledge Base
Tables are a GitHub Flavored Markdown extension not supported by
react-markdown's default parser. Added remark-gfm plugin so tables,
strikethrough, and task lists render correctly in KB articles.
2026-06-16 14:03:57 -06:00
Jordan Ramos
e1dfc35400 Widen BU change query window to 60 minutes before anomaly timestamp
The drift checker runs well before computeAnomalySummary writes to the
anomaly log (20+ minute gap in some syncs). The 10-minute window was
too narrow to capture the BU history records written during drift
checking. Widened to 60 minutes to reliably catch all records from
the same sync cycle.
2026-06-15 09:53:09 -06:00
Jordan Ramos
a2234ccc1a Write BU history records from drift checker for anomaly banner detail view
The drift checker now inserts into ivanti_finding_bu_history when it
classifies archived findings as bu_reassignment. Previously only the
inline per-finding BU comparison (for findings still in sync) wrote
history records — archived findings that moved BU were counted in the
anomaly summary but had no detail records for the banner to display.

Also captures title and hostName from the Ivanti API response in the
drift checker for richer detail display, and adjusts the banner's
time window to 10 minutes before sync_timestamp to catch records
written during the drift check phase.
2026-06-15 09:29:46 -06:00
Jordan Ramos
e45e40d617 Allow CVE/Vendor editing and separate completed Jira tickets
Three changes to the Jira Tickets page:

1. CVE ID and Vendor fields are now editable in the Edit Ticket modal
   (previously disabled when editing). Backend PUT endpoint validates
   CVE format and vendor length on update.

2. Completed tickets (Closed, Done, Resolved, etc.) are shown in a
   separate collapsible section below the active tickets table. This
   keeps the active work front-and-center.

3. Sync All skips completed tickets on subsequent syncs. When a ticket
   first reaches a completed status via sync it gets updated normally,
   but on future syncs it won't be included in the batch query to Jira.
   Response now includes skippedCompleted count.
2026-06-12 15:23:29 -06:00
Jordan Ramos
150a534943 Add atlas_known distinction to prevent badge noise for untracked hosts
Atlas sync now distinguishes between hosts Atlas actively tracks (returned
plans, active or inactive) vs hosts with empty responses (not in Atlas).
Only atlas_known hosts show the badge — ACCESS-OPS hosts not covered by
Atlas won't show the amber '0' warning badge anymore.

Changes:
- Migration adds atlas_known BOOLEAN column to atlas_action_plans_cache
- Sync sets atlas_known = true only when Atlas returns at least one plan
- Metrics endpoint only counts atlas_known hosts in its aggregation
- Status endpoint includes atlas_known in response
- AtlasBadge renders nothing when atlas_known = false
- Bulk-create and refresh-cache upserts set atlas_known = true
- Backfill marks existing hosts with plans + managed BU hosts as known
2026-06-12 13:25:00 -06:00
Jordan Ramos
5105ee2ff8 Scope Atlas sync and metrics to active BU teams
Problem 1: Atlas sync was querying ALL host_ids from ivanti_findings
regardless of BU, writing 'no plan' entries for ACCESS-OPS hosts that
Atlas doesn't cover. Now the sync respects the user's active teams scope
(passed via query param) and falls back to IVANTI_MANAGED_BUS when no
scope is provided.

Problem 2: Atlas /metrics and /status endpoints returned unscoped data
from the full cache, so changing scope didn't update the Atlas Coverage
donut or badge counts. Both endpoints now accept a teams query param and
JOIN against ivanti_findings to scope results by BU.

Frontend changes:
- fetchAtlasStatus and fetchAtlasMetrics now pass teams param
- Atlas sync button passes active teams to the sync endpoint
- Scope change (adminScope) triggers Atlas data refresh

Also purged 6,461 polluted cache entries for non-managed BU hosts.
2026-06-12 12:38:45 -06:00
Jordan Ramos
356ce23462 Add BU reassignment from/to detail view in anomaly banner
The AnomalyBanner BU reassignment row is now clickable, expanding to show
each affected finding with its host name and the team it moved from/to
(e.g. STEAM → PIES). The backend bu-changes endpoint now supports optional
since and limit query params to scope results to the relevant sync cycle.
2026-06-12 12:12:59 -06:00
Jordan Ramos
6465ac2a40 Fix SearchableSelect — only open on focus, close properly on blur/select
Dropdown was opening automatically on render and not closing when clicking
elsewhere. Now opens only on focus/click, closes on blur, selection, Enter,
Escape, and Tab. Selected value persists in the input after selection.
2026-06-10 11:40:20 -06:00
Jordan Ramos
0f83f48cc6 Per-user Ivanti identity for FP workflow filtering
Each user can now have ivanti_first_name and ivanti_last_name configured in
User Management. The workflow sync queries all configured Ivanti identities
and fetches workflows for each. The GET endpoint filters workflows to only
show those belonging to the logged-in user's Ivanti identity.

Users without an Ivanti identity see all workflows (admin fallback).
If no users have identities configured, falls back to IVANTI_FIRST_NAME/
IVANTI_LAST_NAME from .env for backward compatibility.

Changes:
- Migration adds ivanti_first_name, ivanti_last_name to users table
- Users route accepts and returns the new fields
- User Management UI has Ivanti Identity input fields
- Workflow sync iterates all configured user identities
- Workflow GET filters by logged-in user's identity
2026-06-10 11:22:51 -06:00
Jordan Ramos
56ceb81ea5 Add searchable dropdowns for Granite Loader columns
RESPONSIBLE_TEAM, EQUIP_STATUS, and EQUIPMENT_CLASS now show searchable
dropdown selectors in both the Bulk Defaults section and per-row inline
editing. Type to filter options, use arrow keys to navigate, Enter to select.

Picklist values extracted from docs/Team_Device Loader.xlsx reference sheets.
Per-row cells remain click-to-edit for all columns — picklist columns show
the SearchableSelect, free-text columns show a plain input.
2026-06-10 09:47:25 -06:00