feat(compliance): add 'View in Reporting' link for 2.3.x Ivanti metrics
In ComplianceDetailPanel, active metrics with a metric_id starting with '2.3' and an Ivanti_Vulnerability_ID in extra_json now surface the ID prominently alongside a 'View in Reporting →' button. Clicking navigates directly to the Reporting page. onNavigate prop threaded through App → CompliancePage → ComplianceDetailPanel → MetricRow. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -37,7 +37,7 @@ function MetricChip({ metricId, category, status }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded }) {
|
||||
export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded, onNavigate }) {
|
||||
const [detail, setDetail] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
@@ -158,7 +158,7 @@ export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded }
|
||||
{activeMetrics.length > 0 && (
|
||||
<Section title="Failing Metrics" icon={<Shield style={{ width: '14px', height: '14px' }} />}>
|
||||
{activeMetrics.map(m => (
|
||||
<MetricRow key={m.metric_id} metric={m} />
|
||||
<MetricRow key={m.metric_id} metric={m} onNavigate={onNavigate} />
|
||||
))}
|
||||
</Section>
|
||||
)}
|
||||
@@ -288,10 +288,14 @@ function Section({ title, icon, children, muted, grow }) {
|
||||
);
|
||||
}
|
||||
|
||||
function MetricRow({ metric, resolved }) {
|
||||
function MetricRow({ metric, resolved, onNavigate }) {
|
||||
const color = resolved ? '#475569' : categoryColor(metric.category);
|
||||
const extra = metric.extra || {};
|
||||
|
||||
const ivantiId = (!resolved && metric.metric_id?.startsWith('2.3'))
|
||||
? (extra['Ivanti_Vulnerability_ID'] || null)
|
||||
: null;
|
||||
|
||||
// Surface the most useful extra fields per metric type
|
||||
const highlights = [];
|
||||
if (extra['CVEs_Associated']) highlights.push({ label: 'CVEs', value: extra['CVEs_Associated'] });
|
||||
@@ -317,10 +321,38 @@ function MetricRow({ metric, resolved }) {
|
||||
{resolved && <span style={{ fontSize: '0.68rem', color: '#334155', fontFamily: 'monospace' }}>resolved {metric.resolved_on || ''}</span>}
|
||||
</div>
|
||||
{metric.metric_desc && (
|
||||
<div style={{ fontSize: '0.72rem', color: '#475569', marginBottom: highlights.length ? '0.4rem' : 0, lineHeight: 1.4 }}>
|
||||
<div style={{ fontSize: '0.72rem', color: '#475569', marginBottom: (highlights.length || ivantiId) ? '0.4rem' : 0, lineHeight: 1.4 }}>
|
||||
{metric.metric_desc.length > 100 ? metric.metric_desc.slice(0, 100) + '…' : metric.metric_desc}
|
||||
</div>
|
||||
)}
|
||||
{ivantiId && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: highlights.length ? '0.25rem' : 0 }}>
|
||||
<div style={{ display: 'flex', gap: '0.4rem', alignItems: 'center', minWidth: 0 }}>
|
||||
<span style={{ fontSize: '0.68rem', color: '#475569', fontFamily: 'monospace', flexShrink: 0 }}>Ivanti ID</span>
|
||||
<span style={{ fontSize: '0.68rem', color: '#94A3B8', fontFamily: 'monospace', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{ivantiId}</span>
|
||||
</div>
|
||||
{onNavigate && (
|
||||
<button
|
||||
onClick={e => { e.stopPropagation(); onNavigate('reporting'); }}
|
||||
style={{
|
||||
flexShrink: 0, marginLeft: '0.5rem',
|
||||
background: 'rgba(14,165,233,0.1)',
|
||||
border: '1px solid rgba(14,165,233,0.4)',
|
||||
borderRadius: '0.25rem',
|
||||
color: '#0EA5E9',
|
||||
fontSize: '0.65rem', fontFamily: 'monospace',
|
||||
padding: '0.2rem 0.5rem',
|
||||
cursor: 'pointer', whiteSpace: 'nowrap',
|
||||
transition: 'all 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { e.currentTarget.style.background = 'rgba(14,165,233,0.18)'; e.currentTarget.style.borderColor = 'rgba(14,165,233,0.7)'; }}
|
||||
onMouseLeave={e => { e.currentTarget.style.background = 'rgba(14,165,233,0.1)'; e.currentTarget.style.borderColor = 'rgba(14,165,233,0.4)'; }}
|
||||
>
|
||||
View in Reporting →
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{highlights.map(h => (
|
||||
<div key={h.label} style={{ display: 'flex', gap: '0.4rem', marginTop: '0.25rem' }}>
|
||||
<span style={{ fontSize: '0.68rem', color: '#475569', fontFamily: 'monospace', minWidth: '48px' }}>{h.label}</span>
|
||||
|
||||
@@ -141,7 +141,7 @@ function SeenBadge({ count }) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main Page
|
||||
// ---------------------------------------------------------------------------
|
||||
export default function CompliancePage() {
|
||||
export default function CompliancePage({ onNavigate }) {
|
||||
const { canWrite } = useAuth();
|
||||
|
||||
const [activeTeam, setActiveTeam] = useState('STEAM');
|
||||
@@ -424,6 +424,7 @@ export default function CompliancePage() {
|
||||
hostname={selectedHost}
|
||||
onClose={() => setSelectedHost(null)}
|
||||
onNoteAdded={refresh}
|
||||
onNavigate={onNavigate}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user