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
75 lines
2.1 KiB
JavaScript
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>
|
|
);
|
|
}
|