3ef8c9b247
CI / detect-changes (push) Successful in 7s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Failing after 4s
CI / validate-canonical-controls (push) Successful in 11s
CI / loc-budget (push) Successful in 14s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m24s
CI / test-go (push) Has been skipped
CI / iace-gt-coverage (push) Has been skipped
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
User-Vorgabe: pro Slot transparent zeigen WAS wir tun:
1. Was wurde geprueft (MC-Coverage, collapsible)
2. Speedometer mit Severity-Verteilung
3. LLM-Eskalation-Log (wenn benutzt)
4. Findings sortiert HIGH->LOW, je Card:
- Methodik-Badge (MC / Regex / KB / LLM / Cross)
- Gesetzliche Basis (Norm-Block, violett)
- Befund (Zitat-Block, amber)
- Empfehlung -> 'Pflicht-Massnahme' bei HIGH,
'Best-Practice' bei MEDIUM/LOW, 'LLM-Vorschlag'
bei LLM-Quelle
5. Maszahmen-Plan (gerollupte Recommendations mit
related_finding_ids + Aufwand)
Refactor: ein File AgentTestTab.tsx (519 LOC) -> 7 Files:
_agentTypes.ts (Types + Methodik-Konstanten)
AgentSpeedometer.tsx
AgentMcCoverage.tsx
AgentFindingCard.tsx
AgentRecommendationCard.tsx
AgentSlotCard.tsx
AgentTestTab.tsx (Top-Level, schlank)
Plus Methodik-Info-Erklaerung am Tab-Anfang + Disclaimer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Strukturierte Finding-Anzeige.
|
|
* Layout:
|
|
* [Severity-Badge] [Methodik-Badge(s)]
|
|
* [Titel]
|
|
* ┌ Gesetzliche Basis / Norm ─────────┐
|
|
* │ § 5 Abs. 1 Nr. 1 TMG │
|
|
* └────────────────────────────────────┘
|
|
* ┌ Befund / Wörtlich ───────────────┐
|
|
* │ "Vorstand: …" │
|
|
* └────────────────────────────────────┘
|
|
* ┌ Empfehlung / Best Practice ──────┐
|
|
* │ → Konkrete Maßnahme │
|
|
* └────────────────────────────────────┘
|
|
*/
|
|
|
|
import React from 'react'
|
|
|
|
import type { Finding, SourceType } from './_agentTypes'
|
|
import {
|
|
METHODIK_COLOR,
|
|
METHODIK_LABEL,
|
|
METHODIK_SHORT,
|
|
SEVERITY_BG,
|
|
SEVERITY_COLOR,
|
|
} from './_agentTypes'
|
|
|
|
export function AgentFindingCard({ f }: { f: Finding }) {
|
|
const sev = f.severity
|
|
const color = SEVERITY_COLOR[sev]
|
|
const bg = SEVERITY_BG[sev]
|
|
const sources = f.sources || []
|
|
return (
|
|
<div
|
|
className="rounded border-l-4 p-3 space-y-2"
|
|
style={{ borderLeftColor: color, background: bg }}
|
|
>
|
|
<div className="flex items-center flex-wrap gap-2">
|
|
<span
|
|
className="text-xs font-bold px-2 py-0.5 rounded text-white"
|
|
style={{ background: color }}
|
|
>
|
|
{sev}
|
|
</span>
|
|
<code className="text-[11px] text-gray-500">{f.check_id}</code>
|
|
{sources.map((s, i) => (
|
|
<MethodikBadge key={i} src={s.source_type} sourceId={s.source_id} />
|
|
))}
|
|
{f.confidence !== undefined && (
|
|
<span className="text-[10px] text-gray-500 ml-auto">
|
|
Konfidenz {(f.confidence * 100).toFixed(0)}%
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div className="text-sm font-medium text-gray-900">{f.title}</div>
|
|
|
|
{f.norm && (
|
|
<Block label="Gesetzliche Basis" tone="purple">
|
|
{f.norm}
|
|
</Block>
|
|
)}
|
|
|
|
{f.evidence && (
|
|
<Block label="Befund" tone="amber">
|
|
<span className="italic">„{f.evidence}"</span>
|
|
</Block>
|
|
)}
|
|
|
|
{f.action && (
|
|
<Block
|
|
label={
|
|
sources.some(s =>
|
|
s.source_type === 'llm_local' ||
|
|
s.source_type === 'llm_local_big' ||
|
|
s.source_type === 'llm_cloud'
|
|
)
|
|
? 'Empfehlung (LLM-Vorschlag)'
|
|
: sev === 'HIGH'
|
|
? 'Pflicht-Maßnahme'
|
|
: 'Best-Practice-Empfehlung'
|
|
}
|
|
tone="green"
|
|
>
|
|
{f.action}
|
|
</Block>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function MethodikBadge({
|
|
src, sourceId,
|
|
}: { src: SourceType; sourceId?: string }) {
|
|
const { bg, fg } = METHODIK_COLOR[src] || { bg: '#e5e7eb', fg: '#374151' }
|
|
const title = `${METHODIK_LABEL[src]}${sourceId ? ` · ${sourceId}` : ''}`
|
|
return (
|
|
<span
|
|
title={title}
|
|
className="text-[10px] px-1.5 py-0.5 rounded font-mono"
|
|
style={{ background: bg, color: fg }}
|
|
>
|
|
{METHODIK_SHORT[src]}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
function Block({
|
|
label, tone, children,
|
|
}: {
|
|
label: string
|
|
tone: 'purple' | 'amber' | 'green'
|
|
children: React.ReactNode
|
|
}) {
|
|
const toneMap = {
|
|
purple: { border: '#a78bfa', bg: '#f5f3ff', label: '#5b21b6' },
|
|
amber: { border: '#fbbf24', bg: '#fffbeb', label: '#92400e' },
|
|
green: { border: '#34d399', bg: '#ecfdf5', label: '#065f46' },
|
|
} as const
|
|
const t = toneMap[tone]
|
|
return (
|
|
<div
|
|
className="rounded px-2 py-1.5 text-xs"
|
|
style={{ background: t.bg, borderLeft: `3px solid ${t.border}` }}
|
|
>
|
|
<div className="font-semibold mb-0.5" style={{ color: t.label }}>
|
|
{label}
|
|
</div>
|
|
<div className="text-gray-800">{children}</div>
|
|
</div>
|
|
)
|
|
}
|