Add VCL compliance reporting: exec report page, device metadata fields, bulk upload

This commit is contained in:
Jordan Ramos
2026-05-11 15:48:10 -06:00
parent 955036145d
commit d093a3d113
10 changed files with 2626 additions and 9 deletions

View File

@@ -5,6 +5,7 @@ import ComplianceUploadModal from './ComplianceUploadModal';
import ComplianceDetailPanel from './ComplianceDetailPanel';
import ComplianceChartsPanel from './ComplianceChartsPanel';
import MetricInfoPanel from './MetricInfoPanel';
import VCLReportPage from './VCLReportPage';
import metricDefinitionsRaw from '../../data/metricDefinitions.json';
const API_BASE = process.env.REACT_APP_API_BASE || 'http://localhost:3001/api';
@@ -250,6 +251,7 @@ export default function CompliancePage({ onNavigate }) {
const availableTeams = getAvailableTeams();
const [activeTeam, setActiveTeam] = useState(() => availableTeams[0] || 'STEAM');
const [activeTab, setActiveTab] = useState('active');
const [vclView, setVclView] = useState(false);
const [metricFilter, setMetricFilter] = useState(null);
const [hostSearch, setHostSearch] = useState('');
const [summary, setSummary] = useState({ entries: [], overall_scores: {}, upload: null });
@@ -408,6 +410,23 @@ export default function CompliancePage({ onNavigate }) {
onMouseLeave={e => { e.currentTarget.style.color = '#475569'; e.currentTarget.style.borderColor = 'rgba(20,184,166,0.25)'; }}>
<RefreshCw style={{ width: '16px', height: '16px' }} />
</button>
<button
onClick={() => setVclView(!vclView)}
style={{
background: vclView ? `${TEAL}18` : 'transparent',
border: `1px solid ${vclView ? TEAL : 'rgba(20,184,166,0.25)'}`,
color: vclView ? TEAL : '#475569',
padding: '0.5rem 1rem',
display: 'flex', alignItems: 'center', gap: '0.4rem',
fontFamily: 'monospace', fontSize: '0.75rem', fontWeight: '600',
textTransform: 'uppercase', letterSpacing: '0.05em', cursor: 'pointer',
borderRadius: '0.375rem', transition: 'all 0.15s',
}}
onMouseEnter={e => { if (!vclView) { e.currentTarget.style.color = TEAL; e.currentTarget.style.borderColor = `${TEAL}60`; }}}
onMouseLeave={e => { if (!vclView) { e.currentTarget.style.color = '#475569'; e.currentTarget.style.borderColor = 'rgba(20,184,166,0.25)'; }}}
>
VCL Report
</button>
{canWrite() && (
<button onClick={() => setShowUpload(true)}
className="intel-button"
@@ -426,8 +445,13 @@ export default function CompliancePage({ onNavigate }) {
</div>
</div>
{/* ── VCL Report View ─────────────────────────────────────── */}
{vclView && (
<VCLReportPage />
)}
{/* ── Team tabs ────────────────────────────────────────────── */}
{availableTeams.length === 0 && !isAdmin() ? (
{!vclView && availableTeams.length === 0 && !isAdmin() ? (
<div style={{
padding: '1.5rem', marginBottom: '1.5rem',
borderRadius: '0.5rem', border: '1px solid rgba(245, 158, 11, 0.3)',
@@ -437,7 +461,7 @@ export default function CompliancePage({ onNavigate }) {
}}>
No BU teams assigned to your account. Contact an admin to configure your team access.
</div>
) : (
) : !vclView && (
<div style={{ display: 'flex', gap: '0.375rem', marginBottom: '1.5rem' }}>
{availableTeams.map(team => {
const isActive = activeTeam === team;
@@ -463,7 +487,7 @@ export default function CompliancePage({ onNavigate }) {
)}
{/* ── Metric health cards ──────────────────────────────────── */}
{families.length > 0 ? (
{!vclView && families.length > 0 ? (
<div style={{ marginBottom: '1.5rem' }}>
<div style={{ fontSize: '0.65rem', color: '#334155', fontFamily: 'monospace', textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: '0.625rem' }}>
Metric Health click to filter
@@ -564,10 +588,10 @@ export default function CompliancePage({ onNavigate }) {
) : null}
{/* ── Historical trend charts ──────────────────────────────── */}
<ComplianceChartsPanel />
{!vclView && <ComplianceChartsPanel />}
{/* ── Device table ─────────────────────────────────────────── */}
<div style={{
{!vclView && <div style={{
background: 'linear-gradient(135deg,rgba(30,41,59,0.95) 0%,rgba(15,23,42,0.98) 100%)',
border: '1px solid rgba(20,184,166,0.15)', borderRadius: '0.5rem',
overflow: 'hidden',
@@ -622,7 +646,7 @@ export default function CompliancePage({ onNavigate }) {
{/* Column headers */}
<div style={{
display: 'grid',
gridTemplateColumns: '2.5fr 1.1fr 1fr 2fr 0.5fr 0.4fr',
gridTemplateColumns: '2fr 1fr 0.8fr 1.8fr 1fr 1.2fr 0.5fr 0.4fr',
padding: '0.5rem 1rem',
borderBottom: '1px solid rgba(255,255,255,0.05)',
fontSize: '0.62rem', color: '#334155',
@@ -632,6 +656,8 @@ export default function CompliancePage({ onNavigate }) {
<span>IP Address</span>
<span>Type</span>
<span>Failing Metrics</span>
<span>Resolution Date</span>
<span>Remediation Plan</span>
<span>Seen</span>
<span></span>
</div>
@@ -659,7 +685,7 @@ export default function CompliancePage({ onNavigate }) {
/>
))
)}
</div>
</div>}
{/* ── Detail panel ─────────────────────────────────────────── */}
{selectedHost && (
@@ -805,12 +831,17 @@ export default function CompliancePage({ onNavigate }) {
}
function DeviceRow({ device, selected, onClick }) {
const truncateText = (text, maxLen = 80) => {
if (!text) return '—';
return text.length > maxLen ? text.slice(0, maxLen) + '…' : text;
};
return (
<div
onClick={onClick}
style={{
display: 'grid',
gridTemplateColumns: '2.5fr 1.1fr 1fr 2fr 0.5fr 0.4fr',
gridTemplateColumns: '2fr 1fr 0.8fr 1.8fr 1fr 1.2fr 0.5fr 0.4fr',
padding: '0.625rem 1rem',
borderBottom: '1px solid rgba(255,255,255,0.04)',
cursor: 'pointer',
@@ -844,6 +875,16 @@ function DeviceRow({ device, selected, onClick }) {
))}
</div>
{/* Resolution Date */}
<div style={{ fontFamily: 'monospace', fontSize: '0.72rem', color: '#94A3B8' }}>
{device.resolution_date || '—'}
</div>
{/* Remediation Plan */}
<div style={{ fontSize: '0.7rem', color: '#94A3B8', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={device.remediation_plan || ''}>
{truncateText(device.remediation_plan)}
</div>
{/* Seen count */}
<div>
<SeenBadge count={device.seen_count} />