Extract components and hooks into _components/ and _hooks/ subdirectories to reduce each page.tsx to under 500 LOC (was 1545/1383/1316). Final line counts: evidence=213, process-tasks=304, hazards=157. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
5.4 KiB
TypeScript
116 lines
5.4 KiB
TypeScript
'use client'
|
|
|
|
import type { EvidenceCheck, CheckResult, CHECK_TYPE_LABELS, RUN_STATUS_LABELS } from './EvidenceTypes'
|
|
import { CHECK_TYPE_LABELS as checkTypeLabels, RUN_STATUS_LABELS as runStatusLabels } from './EvidenceTypes'
|
|
|
|
export function ChecksTab({
|
|
checks,
|
|
checksLoading,
|
|
checkResults,
|
|
runningCheckId,
|
|
seedingChecks,
|
|
onRun,
|
|
onLoadResults,
|
|
onSeed,
|
|
}: {
|
|
checks: EvidenceCheck[]
|
|
checksLoading: boolean
|
|
checkResults: Record<string, CheckResult[]>
|
|
runningCheckId: string | null
|
|
seedingChecks: boolean
|
|
onRun: (id: string) => void
|
|
onLoadResults: (id: string) => void
|
|
onSeed: () => void
|
|
}) {
|
|
return (
|
|
<>
|
|
{!checksLoading && checks.length === 0 && (
|
|
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg flex items-center justify-between">
|
|
<div>
|
|
<p className="font-medium text-yellow-800">Keine automatischen Checks vorhanden</p>
|
|
<p className="text-sm text-yellow-700">Laden Sie ca. 15 Standard-Checks (TLS, Header, Zertifikate, etc.).</p>
|
|
</div>
|
|
<button onClick={onSeed} disabled={seedingChecks}
|
|
className="px-4 py-2 bg-yellow-600 text-white rounded-lg hover:bg-yellow-700 disabled:opacity-50">
|
|
{seedingChecks ? 'Lade...' : 'Standard-Checks laden'}
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{checksLoading ? (
|
|
<div className="flex justify-center py-12">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
|
|
</div>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{checks.map(check => {
|
|
const typeMeta = checkTypeLabels[check.check_type] || { label: check.check_type, color: 'bg-gray-100 text-gray-700' }
|
|
const results = checkResults[check.id] || []
|
|
const lastResult = results[0]
|
|
const isRunning = runningCheckId === check.id
|
|
|
|
return (
|
|
<div key={check.id} className="bg-white rounded-xl border border-gray-200 p-5">
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<div className="flex items-center gap-2">
|
|
<h4 className="font-medium text-gray-900">{check.title}</h4>
|
|
<span className={`px-2 py-0.5 text-xs rounded ${typeMeta.color}`}>{typeMeta.label}</span>
|
|
{!check.is_active && (
|
|
<span className="px-2 py-0.5 text-xs rounded bg-gray-100 text-gray-500">Deaktiviert</span>
|
|
)}
|
|
</div>
|
|
{check.description && <p className="text-sm text-gray-500 mt-1">{check.description}</p>}
|
|
<div className="flex items-center gap-4 mt-2 text-xs text-gray-400">
|
|
<span>Code: {check.check_code}</span>
|
|
{check.target_url && <span>Ziel: {check.target_url}</span>}
|
|
<span>Frequenz: {check.frequency}</span>
|
|
{check.last_run_at && <span>Letzter Lauf: {new Date(check.last_run_at).toLocaleDateString('de-DE')}</span>}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 ml-4">
|
|
{lastResult && (
|
|
<span className={`px-2 py-0.5 text-xs rounded ${runStatusLabels[lastResult.run_status]?.color || ''}`}>
|
|
{runStatusLabels[lastResult.run_status]?.label || lastResult.run_status}
|
|
</span>
|
|
)}
|
|
<button onClick={() => { onRun(check.id); onLoadResults(check.id) }} disabled={isRunning}
|
|
className="px-3 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50">
|
|
{isRunning ? 'Laeuft...' : 'Ausfuehren'}
|
|
</button>
|
|
<button onClick={() => onLoadResults(check.id)}
|
|
className="px-3 py-1.5 text-xs border rounded-lg hover:bg-gray-50">
|
|
Historie
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{results.length > 0 && (
|
|
<div className="mt-3 border-t pt-3">
|
|
<p className="text-xs font-medium text-gray-500 mb-2">Letzte Ergebnisse</p>
|
|
<div className="space-y-1">
|
|
{results.slice(0, 3).map(r => (
|
|
<div key={r.id} className="flex items-center gap-3 text-xs">
|
|
<span className={`px-1.5 py-0.5 rounded ${runStatusLabels[r.run_status]?.color || 'bg-gray-100'}`}>
|
|
{runStatusLabels[r.run_status]?.label || r.run_status}
|
|
</span>
|
|
<span className="text-gray-500">{new Date(r.run_at).toLocaleString('de-DE')}</span>
|
|
<span className="text-gray-400">{r.duration_ms}ms</span>
|
|
{r.findings_count > 0 && (
|
|
<span className="text-orange-600">{r.findings_count} Findings ({r.critical_findings} krit.)</span>
|
|
)}
|
|
{r.summary && <span className="text-gray-600 truncate">{r.summary}</span>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|