Commit Graph

217 Commits

Author SHA1 Message Date
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
Jordan Ramos
1dbde36b53 Improve update_token error — show CARD link for assets that cant be actioned via API
When a CARD action fails with 'update_token not found', display a clear
message explaining the asset cannot be actioned via API, with a prominent
'Open in CARD (ID copied)' button that copies the host ID to clipboard and
opens card.charter.com/ipn-search in a new tab.

Applied to both CardDetailModal (reporting page) and CardActionModal (queue).
2026-06-10 09:31:21 -06:00
Jordan Ramos
10239be83c Add IPv6 fallback display for findings without IPv4
Findings with no IPv4 address now display Qualys IPv6 or Primary IPv6 as
fallback in the IP column, with a badge indicator:
  - 'Q' (amber) = Qualys IPv6 from hostAdditionalDetails
  - 'v6' (indigo) = Primary IPv6 from assetCustomAttributes

Priority: IPv4 > Qualys IPv6 > Primary IPv6

Backend changes:
- extractFinding now captures qualysIpv6 and primaryIpv6
- New extractQualysIpv6 helper parses hostAdditionalDetails
- upsertFindingsBatch stores both fields
- API response includes qualysIpv6 and primaryIpv6
- Migration adds qualys_ipv6 and primary_ipv6 columns

The Qualys IPv6 is preferred over Primary IPv6 because it resolves in CARD
(confirmed via testing with PMADEV-1).
2026-06-09 13:29:43 -06:00
Jordan Ramos
23ea3983c8 Fix Enrich from CARD for items without IP — use host_id lookup
The enrich-batch endpoint now accepts a host_ids array alongside ips.
When queue items have no IP address but have a host_id (from ivanti_findings),
the frontend sends host_ids and the backend resolves them via CARD asset-search.

Results include the resolved IP so it populates the IPV4_ADDRESS column.
The LoaderModal now carries _host_id from initialDevices through to the
enrich call.
2026-06-09 13:00:02 -06:00
Jordan Ramos
f8b420f4e4 Fix clipboard copy on HTTP — use execCommand fallback
navigator.clipboard requires HTTPS (secure context). Fall back to
textarea + execCommand('copy') for the View in CARD button.
2026-06-09 12:35:24 -06:00
Jordan Ramos
f3319ee1f5 Add View in CARD button to tooltip and action modal
Copies the Ivanti Host ID to clipboard and opens card.charter.com/ipn-search
in a new tab. ESSO prevents URL-based pre-fill so the user pastes the ID.
2026-06-09 12:28:56 -06:00
Jordan Ramos
a8d3909798 Add CARD asset-search by Ivanti Host ID for faster lookups
Integrate CARD's new v2 asset-search endpoint that accepts Ivanti Asset ID
integers directly, eliminating the slow suffix-guessing resolution flow.

Changes:
- Add searchByIvantiHostId() helper to cardApi.js
- Add GET /api/card/asset-search/:hostId endpoint
- Update CARD queue confirm/decline/redirect to try host_id fast path first
- Update owner-lookup to accept optional hostId query param for fast resolution
- Pass hostId through CardOwnerTooltip and ReportingPage for tooltip lookups
- Join ivanti_findings in todo queue GET to expose host_id on queue items
- Update CardActionModal to pass host_id for faster owner-lookup
2026-06-09 11:57:13 -06:00
Jordan Ramos
2396a828cc Fix empty description in single-item Jira modal on ReportingPage
openCreateJiraFromQueue only populated the description for Remediate
workflow items. Non-Remediate items got an empty string, while multi-select
worked because it used generateConsolidatedDescription via ConsolidationModal.

Now always includes finding info (vendor, title, CVEs, host/IP) in the
description for all workflow types. Remediate items still append their
notes below.
2026-06-09 08:31:01 -06:00
Jordan Ramos
3b5dfee235 Append remediation notes to Jira ticket description in QueuePanel
- Single-item: openCreateJiraFromQueue fetches notes for Remediate items
  and pre-fills the description with a Remediation Notes section
- Multi-item: ConsolidationModal fetches notes for all Remediate items
  and appends them via appendRemediationNotes utility

Previously notes were only integrated in IvantiTodoQueuePage.js but
the actual Jira creation flow users interact with is in ReportingPage.js
QueuePanel and ConsolidationModal.
2026-06-08 14:59:48 -06:00
Jordan Ramos
889d4658e5 Fix Remediate integration in QueuePanel on Reporting Page
- Add remediationModalItem state to QueuePanel
- Render RemediationModal from QueuePanel for notes access
- Add Remediate color to wfColor mapping in renderQueueItem
- Add Remediate option to SelectionToolbar workflow type buttons

The Notes button and Remediate workflow option were only added to the
standalone IvantiTodoQueuePage but not the QueuePanel slide-out on the
Reporting Page, which is the primary interface for queue interaction.
2026-06-08 14:36:50 -06:00
Jordan Ramos
6c7b8cb2fa Add Remediate and DECOM to RedirectModal workflow options
RedirectModal had a hardcoded WORKFLOW_OPTIONS array that only included
FP, Archer, CARD, and GRANITE. Added DECOM and Remediate options, and
updated needsVendor check to require vendor for Remediate workflows.
2026-06-08 14:19:34 -06:00
Jordan Ramos
79f98414c4 Add Remediate workflow type to Ivanti Queue with remediation notes
- Add 'Remediate' as a valid workflow type (vendor-required, like FP/Archer)
- Create queue_remediation_notes table with FK cascade and 5000 char limit
- Add POST/GET /api/ivanti/todo-queue/:id/notes endpoints
- Include remediation_notes_count in queue item GET response
- Add RemediationModal component for viewing/adding notes
- Add notes count badge on Remediate queue items (purple #A855F7 theme)
- Add delete confirmation warning when removing items with notes
- Append remediation notes to Jira ticket descriptions
- Add property-based tests for all correctness properties
2026-06-08 14:07:59 -06:00
Jordan Ramos
d4c428248a Add non-metric category filters to compliance page
Adds a CategoryFilterBar with pill-shaped FilterChip components below the
metric health cards. Non-metric categories (Missing_AppID, Aging Vulns,
Missing_DF, etc.) are derived dynamically from device data and displayed
as color-coded filterable chips with device counts.

Unified filter state replaces the old metricFilter array, ensuring mutual
exclusivity between metric card filters and non-metric chip filters.
Includes 4 property-based tests validating derivation, filter predicate,
mutual exclusivity, and color resolution correctness.

Closes #26
2026-06-08 10:47:28 -06:00
Jordan Ramos
1f3833989a Replace CCP cross-metric aggregates with per-metric summary views
Add per-metric stats and trend endpoints to vclMultiVertical.js. Refactor
CCPMetricsPage to use a unified MetricSelector that drives StatsBar, TrendChart,
DonutChart, and ForecastBurndownChart for the selected metric only. Remove the
separate Per-Metric Forecast Burndown section (now integrated). Fix trend query
double-counting when multiple uploads exist per vertical per month.

Closes #25
2026-06-08 07:59:56 -06:00
Jordan Ramos
c62409a8f6 Fix Action Coverage donut not updating when notes change
NoteCell now propagates saved notes back to the findings state via
onNoteSaved callback. This allows classifyFinding() to immediately
reclassify items from 'pending' to 'archer' when an EXC- note is
added, updating the Action Coverage donut without a page refresh.
2026-06-05 10:52:01 -06:00
Jordan Ramos
af5fa11421 Fix Archer Jira ticket description auto-population and security audit fixes
Auto-populate description field when creating Jira tickets from the Archer
page with ticket metadata (EXC number, CVE, vendor, status, Archer URL).
Previously the description was always empty, requiring manual entry.

Includes security audit fixes for SQL injection prevention and input
validation in compliance, VCL multi-vertical, and CCP metrics routes.

Updates security audit tracker documentation.
2026-06-05 09:53:53 -06:00
Jordan Ramos
e887fa8946 Add CARD ownership tooltip and direct action modal on IP hover
Hover over any IP address in the findings table to see CARD ownership data
(confirmed/unconfirmed/candidate teams) in an interactive tooltip. Click
'Actions' to open a full modal for confirm/decline/redirect — no queue
item required.

Backend:
- Add direct /api/card/owner/:assetId/confirm|decline|redirect endpoints
- Add quick mode to resolveAssetId (CTEC only, 15s timeout) for tooltip use
- owner-lookup supports ?quick=1 query param with 504 on timeout
- getOwner accepts options for custom timeout

Frontend:
- New CardOwnerTooltip component (portal, hover bridge, cached results)
- New CardDetailModal for confirm/decline/redirect from tooltip
- IP cells show help cursor, trigger tooltip on 400ms hover
- Timeouts (504) not cached — retry on re-hover
- Teams fetch retries silently up to 3x on failure
- Redirect dropdowns show owner-data teams as fallback when teams API fails
2026-06-04 11:15:13 -06:00
Jordan Ramos
d9c47ec030 Add Group by Host toggle to Ivanti findings table
Client-side grouping that collapses duplicate assets (same hostname + IP)
with multiple finding IDs into expandable host rows. Hosts with only one
finding remain as normal flat rows.

- Toggle button in toolbar switches between flat and grouped views
- Group header rows preserve column alignment (severity, host, IP in proper columns)
- Expanded sub-rows show full finding details with all interactions intact
- Selection, queue, hide, and workflow actions all work in both modes
- Groups sorted by highest severity; expand/collapse all controls included
2026-06-03 15:44:48 -06:00
Jordan Ramos
4e8f4cbb10 Allow redirecting pending queue items in place without duplicating
Previously, redirecting a queue item required completing it first, which
created a duplicate entry. Now:
- Pending items: redirect updates workflow_type in place (no new row)
- Completed items: still creates a new pending item (legacy behavior)
- Redirect arrow now visible on all items, not just completed ones
- Frontend handles in-place updates by replacing the item in state
2026-06-03 13:55:10 -06:00
Jordan Ramos
1cc8bd5a4c Improve CARD decline error diagnostics and prevent accidental modal dismiss
- Log the full owner response in audit when update_token is missing so
  we can see what CARD actually returned
- Improve error message to suggest the asset may have already been actioned
- Remove backdrop-click-to-close on TemplateFormModal to prevent
  accidental data loss while filling in template content
2026-06-03 13:33:24 -06:00
Jordan Ramos
50f14c14d2 Add inline view panel to Template Manager with copy buttons
Click the model name or eye icon to expand a read-only view of all
template sections with per-section copy-to-clipboard and Copy All.
2026-06-03 11:04:25 -06:00
Jordan Ramos
3500787851 Add Archer Template Library for risk acceptance form reuse
Adds a template management system to the Ivanti Queue's Archer Risk
Acceptance workflow. Templates store static form content (Environment
Overview, Segmentation, Mitigating Controls, etc.) organized by
Vendor > Platform > Model hierarchy.

Features:
- Full CRUD API at /api/archer-templates with search, filter, clone,
  and hierarchy navigation endpoints
- Template Manager page (nav: Template Mgr) with grouped list view,
  create/edit/clone/delete modals, role-based access
- TemplateSelector component integrated into Ivanti Todo Queue for
  Archer workflow items with per-section copy-to-clipboard buttons
  and Copy All functionality
- Database migration with case-insensitive uniqueness enforcement
- Audit logging for all template mutations

New files:
- backend/migrations/add_archer_templates_table.js
- backend/routes/archerTemplates.js
- frontend/src/components/pages/ArcherTemplatePage.js
- frontend/src/components/TemplateSelector.js
- frontend/src/components/TemplateFormModal.js
- frontend/src/components/DeleteConfirmModal.js
2026-06-02 16:08:25 -06:00
Jordan Ramos
c5225c96a5 Fix 'invalid date' display for ISO datetime resolution_date values
The pg driver returns PostgreSQL DATE columns as ISO datetime strings
(e.g. '2026-07-03T00:00:00.000Z'). The formatResolutionDate helper was
strictly matching YYYY-MM-DD only, so these were classified as 'invalid'.

Now the helper extracts the date prefix from ISO datetime strings before
validating, correctly classifying them as 'set' with the YYYY-MM-DD value.
Updated the property test filter and added an example test for the case.
2026-06-02 14:12:13 -06:00
Jordan Ramos
aae09020e6 Sort metrics numerically on the CCP Metrics page
Add a natural-sort comparator for metric IDs (e.g. 2.3.6i, 5.2.6, 10.1.1)
and apply it to the metric breakdown cards, the vertical detail table, and
the forecast burndown metric dropdown. Metrics now appear in ascending
numerical order instead of arbitrary API response order.

Closes #24
2026-06-02 12:17:28 -06:00
Jordan Ramos
0cf49e6ef1 Move resolution date/remediation plan below failing metrics and fix date picker contrast
The Resolution Date, Remediation Plan, and Apply To Metrics sections
now render immediately after Failing Metrics in the sidebar instead of
after Resolved Metrics and History — no more scrolling past unrelated
sections to reach the edit fields.

The date input also gains colorScheme: 'dark' so the native browser
calendar picker renders with light text on a dark background, fixing
the black-on-dark-blue readability issue.

Closes #21
Closes #22
2026-06-02 12:09:29 -06:00
Jordan Ramos
7545457813 Refresh compliance list after sidebar metadata save
The host list on the compliance page showed stale resolution date and
remediation plan values after editing them in the detail sidebar, until
an unrelated refresh (filter, team, or tab change) ran. handleSaveMetadata
re-fetched only the panel's own detail and never notified the parent.

Add an onMetadataSaved callback invoked after a successful metadata PATCH
and wire it to the existing list refresh in CompliancePage, mirroring the
onNoteAdded pattern. The list now reflects saved changes immediately.

Closes #23
2026-06-02 11:00:38 -06:00
Jordan Ramos
56a4c546d0 Show estimated resolution date per metric in compliance sidebar
Add a read-only estimated resolution date line at the top of each
noncompliant metric's section in the asset sidebar, sourced from that
metric's own resolution_date. Formats valid dates as YYYY-MM-DD and
shows placeholders for unset and invalid dates. Resolved metrics are
unaffected and the existing editable Resolution Date field is unchanged.

Date classification is isolated in a pure helper (frontend/src/utils/
resolutionDate.js) covered by example and fast-check property tests,
with render and interaction tests for the sidebar.

Closes #20
2026-06-01 15:58:23 -06:00
Jordan Ramos
8224183679 Add CARD Action Modal with full owner context
Replace inline CARD action form with a centered modal that:
- Fetches and displays the full CARD owner record (confirmed,
  unconfirmed, candidates, declined teams with scores/sources)
- Shows queue item info (hostname, IP, finding, CVEs)
- Lets user switch between Confirm/Decline/Redirect actions
- Pre-fills team dropdowns from the actual owner data
- Shows CARD API errors inline with full detail

Add GET /api/card/owner-lookup/:ip endpoint that resolves a bare
IP to a CARD asset ID and returns the structured owner record.
2026-05-28 14:58:27 -06:00
Jordan Ramos
a6e455311e Improve CARD action error messages and default loader columns
Show actual CARD API error messages (e.g., 'Cannot redirect asset
because Team is neither confirmed nor pending owner') instead of
generic 'Redirect failed.' or 'confirm failed.' messages.

Also auto-select IPV4_ADDRESS, EQUIP_NAME, and RESPONSIBLE_TEAM
columns by default in the Loader Modal for better initial UX.
2026-05-28 14:25:37 -06:00
Jordan Ramos
5e95e35d26 Add IP address validation to CARD confirm/decline/redirect actions
Show clear error message when a queue item has no IP address
instead of sending null to the backend. Items without IPs cannot
be resolved to CARD asset IDs.
2026-05-27 19:34:22 -06:00
Jordan Ramos
43e10b8c06 Add Loader Sheet button to queue panel on Reporting page
The 'Loader' button appears in the queue panel footer alongside
the existing + Jira, Delete, and Clear Completed buttons. It's
visible whenever CARD/GRANITE/DECOM items exist in the queue.
Clicking it opens the LoaderModal pre-populated with those items'
IPs and hostnames. If specific items are selected, only those are
passed; otherwise all CARD/GRANITE/DECOM items are included.
2026-05-27 17:41:34 -06:00
Jordan Ramos
fe82362afa Add Granite Loader Sheet generator with CARD enrichment
Implement the Granite Team_Device Loader xlsx export feature:
- Add graniteLoaderConfig.js with all 41 columns, groupings, and
  operation-type requirements (Change/Add/Delete/Move)
- Add graniteLoaderExport.js for client-side xlsx generation using
  the xlsx library
- Add LoaderModal component with operation type selection, column
  checkboxes, bulk defaults with per-row overrides, editable preview
  table, CARD enrichment integration, and standalone paste-IPs mode
- Add POST /api/card/enrich-batch endpoint for batch IP lookup in
  CARD returning EQUIP_INST_ID, hostname, site, ASN, team
- Integrate 'Generate Loader Sheet' button in Ivanti Queue floating
  action bar (visible when CARD/GRANITE/DECOM items selected)
- Add card-connectivity-test.js script for verifying CARD API access
2026-05-27 17:18:36 -06:00
Jordan Ramos
1903e41088 Add LIVE and LAST REPORT badges to VCL compliance page
The burndown chart uses live compliance_items data (updates as
remediation plans and resolution dates are entered), while the
metrics overview table shows the snapshot from the last uploaded
report. Add visual badges to clarify this distinction:
- Burndown chart: green 'LIVE' badge
- Metrics overview: grey 'LAST REPORT' badge
2026-05-27 16:25:31 -06:00
Jordan Ramos
9f7703c76f Add all vendor project keys and update docs for issue type dropdown
Expand VENDOR_PROJECT_KEYS to include all vendor projects: AA_ADTRAN,
AA_ADVA, AA_CASA, AA_CISCO, AACOMMSCOP, AA_COMMSCOP, AA_HARMONI,
AA_JUNIPER, AA_VECIMA, AA_VIAVI. Both AACOMMSCOP and AA_COMMSCOP
variants are included for safety.

Update property tests to exercise the full vendor key list instead of
only AA_VECIMA. Update full-reference-manual.md with vendor-specific
issue type dropdown documentation.
2026-05-27 15:17:43 -06:00
Jordan Ramos
04eb21a7d3 Add vendor-specific issue type dropdown for Jira ticket creation
When the Project Key field contains a vendor project key (e.g. AA_VECIMA),
the Issue Type dropdown switches from STEAM types (Story, Epic, Program,
Project, Reservation, Automation Maintenance) to vendor types (Epic, Story,
Task, Defect, Production Defect/Incident Fix, New Feature, Spike, Release
Candidate, Documentation).

- Add VENDOR_PROJECT_KEYS, VENDOR_ISSUE_TYPES, STEAM_ISSUE_TYPES constants
- Add isVendorProject() and getIssueTypesForProject() pure functions
- Update JiraPage modal with context-aware dropdown and reset on switch
- Update Ivanti queue modal with project_key and issue_type fields
- Add property-based tests for determination logic and state transitions
2026-05-27 15:08:08 -06:00
Jordan Ramos
56e3f5f973 Format resolution_date as YYYY-MM-DD in compliance table
Normalize the date in groupByHostname() to handle PostgreSQL Date objects,
and add .slice(0,10) in the frontend render as a safety net. Prevents the
full ISO timestamp (2026-05-15T00:00:00.000Z) from displaying in the table.
2026-05-27 13:06:39 -06:00
Jordan Ramos
ea875e9193 Add collapsible sections to Ivanti Queue side panel
Make the INVENTORY and vendor group headers in the QueuePanel (slide-out
side panel) clickable to collapse/expand their contents. Adds a chevron
indicator showing collapse state. All sections start expanded by default.
2026-05-27 11:18:22 -06:00
Jordan Ramos
fabf98790c Add collapsible sections to Ivanti Queue page
Group queue items into a hybrid layout: Inventory section (CARD/GRANITE/DECOM)
at top, then vendor-grouped sections for FP/Archer items sorted alphabetically.
Each section header is clickable to collapse/expand with chevron indicators.

- Extract grouping logic into reusable utility (queueGrouping.js)
- Add collapse state management (all sections expanded by default)
- Preserve cross-section multi-select, floating action bar, ticket badges
- Add 5 property-based tests covering grouping correctness, ordering,
  empty section omission, count accuracy, and selection independence
2026-05-27 11:07:32 -06:00
Jordan Ramos
dd6fc394ea Show compliant/total counts on metric summary cards
Display raw counts alongside percentages in the compliance metric health
cards, e.g. '67% (4/6)' instead of just '67%'. Data was already available
in the summary JSON (compliant, total fields) — just not rendered.

Closes #16
2026-05-26 11:48:53 -06:00
Jordan Ramos
caf6ca4008 Add per-metric remediation plans and improve CI pipeline
Per-metric remediation plan scoping (GitLab issue #19):
- Add metric_id column to compliance_item_history table (migration)
- Extend PATCH /items/:hostname/metadata to accept metric_id/metric_ids
  for targeting specific metrics instead of all active items
- Add MetricChipSelector UI in detail panel for choosing which metrics
  to apply resolution_date and remediation_plan changes to
- Display per-metric labels (MetricChip or 'All metrics') on history entries
- Backward compatible: omitting metric_ids preserves hostname-level behavior

CI/CD pipeline improvements:
- Add migration idempotency integration test (runs against real Postgres)
- Add post-deploy smoke tests for compliance and VCL endpoints
- Bump lint --max-warnings from 10 to 25
- Configure varsIgnorePattern for _ prefix convention on unused vars

Closes #19
2026-05-26 11:16:28 -06:00