Files
cve-dashboard/frontend/src/components/AtlasBadge.js
root 4c04c9870a Add Atlas InfoSec action plans integration
Integrate Atlas InfoSec API to manage compliance action plans directly from
the ReportingPage. Users can view, create, and update action plans for host
findings without switching to the Atlas web tool.

Backend:
- Add atlasApi.js helper with Basic Auth, TLS skip, GET/PUT/PATCH/POST
- Add atlas_action_plans_cache migration for SQLite cache table
- Add atlas.js router with sync, status, and proxy CRUD endpoints
- Mount Atlas router at /api/atlas in server.js
- Extract hostId from Ivanti host findings during sync

Frontend:
- Add AtlasBadge component (amber=needs plan, green=has plan)
- Add AtlasSlideOutPanel with plan list, create form, edit capability
- Separate active plans from inactive history in collapsible section
- Custom dark-themed plan type dropdown
- Optimistic local state shows pending plans immediately after creation
- Atlas sync button on ReportingPage toolbar
- Prepopulate finding ID in create form from clicked row

Environment:
- Add ATLAS_API_URL, ATLAS_API_USER, ATLAS_API_PASS, ATLAS_SKIP_TLS to .env.example
2026-04-23 21:52:53 +00:00

75 lines
2.1 KiB
JavaScript

import React from 'react';
import { Shield } from 'lucide-react';
// ---------------------------------------------------------------------------
// AtlasBadge — small inline pill badge for the Host column on ReportingPage.
// Shows Atlas action plan coverage status for a given host.
//
// Props:
// hostId — numeric host identifier
// atlasStatus — { host_id, has_action_plan, plan_count, synced_at } or undefined
// onClick — callback when badge is clicked (opens slide-out panel)
// ---------------------------------------------------------------------------
const warningStyle = {
display: 'inline-flex',
alignItems: 'center',
gap: '3px',
borderRadius: '9999px',
padding: '1px 6px',
fontFamily: "'JetBrains Mono', monospace",
fontSize: '0.58rem',
fontWeight: 700,
lineHeight: 1,
cursor: 'pointer',
marginLeft: '6px',
background: 'rgba(245,158,11,0.12)',
border: '1px solid rgba(245,158,11,0.4)',
color: '#F59E0B',
};
const successStyle = {
display: 'inline-flex',
alignItems: 'center',
gap: '3px',
borderRadius: '9999px',
padding: '1px 6px',
fontFamily: "'JetBrains Mono', monospace",
fontSize: '0.58rem',
fontWeight: 700,
lineHeight: 1,
cursor: 'pointer',
marginLeft: '6px',
background: 'rgba(16,185,129,0.12)',
border: '1px solid rgba(16,185,129,0.4)',
color: '#10B981',
};
export default function AtlasBadge({ hostId, atlasStatus, onClick }) {
// No status data — render nothing
if (!atlasStatus) return null;
const hasPlan = atlasStatus.plan_count > 0;
const style = hasPlan ? successStyle : warningStyle;
const label = hasPlan ? String(atlasStatus.plan_count) : '0';
return (
<span
style={style}
title={
hasPlan
? `${atlasStatus.plan_count} Atlas action plan${atlasStatus.plan_count !== 1 ? 's' : ''}`
: 'No Atlas action plans — needs attention'
}
onClick={(e) => {
e.stopPropagation();
if (onClick) onClick(hostId);
}}
data-testid="atlas-badge"
>
<Shield style={{ width: 12, height: 12, flexShrink: 0 }} />
{label}
</span>
);
}