Commit Graph

401 Commits

Author SHA1 Message Date
Jordan Ramos
c1a266f4f7 Skip integration tests in CI — no Postgres service available
The migrations-idempotency.integration.test.js requires a reachable
Postgres instance. The CI Docker container can't resolve the DATABASE_URL
hostname. Skip files matching 'integration' in the test-backend job.
2026-06-16 15:09:10 -06:00
Jordan Ramos
95aac03769 Release v2.3.0 — Atlas scoping, BU detail view, drift checker fix
See CHANGELOG.md for full details.
v2.3.0
2026-06-16 14:45:43 -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
93efb70d1c Fix KB content/download failing for relative file paths
res.sendFile requires an absolute path. Article #7 was stored with a
relative path which caused the TypeError. Now both the content and
download endpoints resolve relative paths against the backend directory
before calling existsSync and sendFile.
2026-06-16 13:23:20 -06:00
Jordan Ramos
a8877728e0 Fix drift checker re-classifying same archived findings every sync
Root cause: archived findings were never removed from ivanti_findings
(state='open'), so they appeared in previousFindings every sync, got
flagged as 'disappeared' every time, and were re-classified by the
drift checker — inflating the BU reassignment count to ~220/sync
when only a handful of genuinely new reassignments existed.

Fixes:
1. Filter out already-archived findings (archived >2h ago) before
   passing to the drift checker — only genuinely new archives get
   classified.
2. Delete disappeared findings from ivanti_findings after archive
   detection so they don't pollute future syncs.
3. Cleaned up 536 stale findings that were accumulated in the table.

The archive activity bar chart should drop from ~500 to near-zero
on the next sync, with only genuinely new disappearances showing.
2026-06-16 13:13:15 -06:00
Jordan Ramos
b0cb67b975 Update full reference manual to v2.2.0
Brings the reference manual in line with the current codebase:
- Add CCP Metrics (VCL multi-vertical) feature section and full API reference
- Add CARD Asset Ownership feature section with tooltip, direct actions, queue actions
- Add Granite Loader Sheet feature section with CARD enrichment details
- Add Atlas Action Plans feature section with cache and badge rendering
- Add Finding Archive Tracking feature section with lifecycle states and anomaly detection
- Add Archer Template Library feature section with hierarchy and clone
- Add In-App Notifications feature section
- Add Feedback (GitLab integration) feature section with webhook lifecycle
- Add FP Workflow Submission feature section with lifecycle tracking
- Update Ivanti Queue to document all 6 workflow types (FP, Archer, CARD, GRANITE, DECOM, Remediate)
- Update Reporting section with Group by Host, CARD tooltip, multi-BU scope
- Update Jira section with flexible creation, consolidation modal, raw status
- Update Configuration with CARD, Atlas, GitLab, IVANTI_BU_FILTER, IVANTI_MANAGED_BUS vars
- Update Architecture tree with all routes, helpers, and components
- Update Database Schema with 15+ new tables
- Update Migrations section with all 46 migration files
- Update API Reference with Archer Templates, CARD, Atlas, VCL, Notifications, Feedback, Webhooks
- Add per-user settings (bu_teams, ivanti_identity) to auth section
2026-06-15 13:55:22 -06:00
Jordan Ramos
c0b9e8a6fc Update README to v2.2.0 — document all features since initial release
Rewrites the README from v1.0.0 to reflect current codebase state:
- Add TOC, full feature descriptions for 20+ features
- Add complete Configuration table (30+ env vars)
- Add Database Schema table (26 tables)
- Add Migrations table (46 migration files)
- Add API Reference with all route groups
- Add CI/CD Pipeline section
- Add Troubleshooting section (Symptom/Cause/Fix format)
- Update Project Structure with all routes, components, pages
- Update Tech Stack and Documentation links
2026-06-15 12:50:48 -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
032a8df403 Merge update_token from getOwner when asset-search omits it
When the hostId fast path resolves via asset-search but the response lacks
an update_token, do a follow-up getOwner() call using the resolved _id to
fetch the token. Returns the rich owner data from asset-search merged with
the update_token from the owner endpoint.
2026-06-09 14:23:56 -06:00
Jordan Ramos
32ed65eb79 Fix owner-lookup hostId fast path — use asset-search owner data directly
The asset-search response wraps in { assets: [...] } and includes the full
owner record. Previously we tried to extract just an _id from the top level
(which didn't exist) and then made a separate getOwner() call that returned
empty data for IPv6 assets.

Now when hostId resolves via asset-search, we return the owner data directly
from the search response — no second API call needed. This fixes the tooltip
showing empty confirmed/unconfirmed for IPv6-only findings.
2026-06-09 14:03:31 -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
54d6e49cb1 Use asset-search fast path in enrich-batch for Granite lookups
The CARD asset-search endpoint returns the full enriched record (card_flags,
ivanti_assets, ncim_discovery, etc.) — same shape as team-assets. Before
falling back to the slow paginated team-assets loop, try each IP's host_id
via asset-search for direct single-call resolution.

Also registers the notifications table migration in run-all.js.
2026-06-09 12:48:08 -06:00
Jordan Ramos
29d8ecb9dd Add notifications table migration to run-all.js
The migration file existed but was never registered in the POSTGRES_MIGRATIONS
array, so it never ran on production. The missing table caused 500 errors on
GET /api/notifications/count.
2026-06-09 12:43:27 -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
4d8a6b9c6e Fix add_decom_workflow_type migration to include Remediate in constraint
The older migration drops and re-adds the workflow_type CHECK constraint
but only included FP, Archer, CARD, GRANITE, DECOM. Once Remediate data
exists in the table, re-adding the old constraint fails. Added Remediate
to the constraint set so migrations can run idempotently in order.
2026-06-08 16:20:17 -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
e8aa7038ad Release v2.2.0 v2.2.0 2026-06-04 11:16:45 -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
4f40850fd2 ci: force pipeline refresh for v2.1.0 deploy 2026-06-03 07:47:30 -06:00
Jordan Ramos
e4abf8dc9b Update CHANGELOG for v2.1.0 release
Add Archer Template Library to the feature list.
v2.1.0
2026-06-02 16:09:28 -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