Files
breakpilot-compliance/admin-compliance/app/sdk/control-provenance/page.tsx
Sharang Parnerkar 0125199c76 refactor(admin): split controls, training, control-provenance, iace/verification pages
Each page.tsx exceeded the 500-LOC hard cap. Extracted components and hooks into
colocated _components/ and _hooks/ directories; page.tsx is now a thin orchestrator.

- controls/page.tsx: 944 → 180 LOC; extracted ControlCard, AddControlForm,
  LoadingSkeleton, TransitionErrorBanner, StatsCards, FilterBar, RAGPanel into
  _components/ and useControlsData, useRAGSuggestions into _hooks/; types into _types.ts
- training/page.tsx: 780 → 288 LOC; extracted ContentTab (inline content generator tab)
  into _components/ContentTab.tsx
- control-provenance/page.tsx: 739 → 122 LOC; extracted MarkdownRenderer, UsageBadge,
  PermBadge, LicenseMatrix, SourceRegistry into _components/; PROVENANCE_SECTIONS
  static data into _data/provenance-sections.ts
- iace/[projectId]/verification/page.tsx: 673 → 196 LOC; extracted StatusBadge,
  VerificationForm, CompleteModal, SuggestEvidenceModal, VerificationTable into _components/

Zero behavior changes; logic relocated verbatim.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 22:50:15 +02:00

123 lines
5.0 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Shield, FileText } from 'lucide-react'
import Link from 'next/link'
import { PROVENANCE_SECTIONS } from './_data/provenance-sections'
import { MarkdownRenderer } from './_components/MarkdownRenderer'
import { LicenseMatrix } from './_components/LicenseMatrix'
import { SourceRegistry } from './_components/SourceRegistry'
interface LicenseInfo {
license_id: string; name: string; terms_url: string | null; commercial_use: string
ai_training_restriction: string | null; tdm_allowed_under_44b: string | null
deletion_required: boolean; notes: string | null
}
interface SourceInfo {
source_id: string; title: string; publisher: string; url: string | null
version_label: string | null; language: string; license_id: string; license_name: string
commercial_use: string; allowed_analysis: boolean; allowed_store_excerpt: boolean
allowed_ship_embeddings: boolean; allowed_ship_in_product: boolean
vault_retention_days: number; vault_access_tier: string
}
export default function ControlProvenancePage() {
const [licenses, setLicenses] = useState<LicenseInfo[]>([])
const [sources, setSources] = useState<SourceInfo[]>([])
const [activeSection, setActiveSection] = useState('methodology')
const [loading, setLoading] = useState(true)
useEffect(() => {
async function load() {
try {
const [licRes, srcRes] = await Promise.all([
fetch('/api/sdk/v1/canonical?endpoint=licenses'),
fetch('/api/sdk/v1/canonical?endpoint=sources'),
])
if (licRes.ok) setLicenses(await licRes.json())
if (srcRes.ok) setSources(await srcRes.json())
} catch {
// silently continue — static content still shown
} finally {
setLoading(false)
}
}
load()
}, [])
const currentSection = PROVENANCE_SECTIONS.find(s => s.id === activeSection)
return (
<div className="flex flex-col h-full">
<div className="border-b border-gray-200 bg-white px-6 py-4">
<div className="flex items-center gap-3">
<FileText className="w-6 h-6 text-green-600" />
<div>
<h1 className="text-lg font-semibold text-gray-900">Control Provenance Wiki</h1>
<p className="text-xs text-gray-500">
Dokumentation der unabhaengigen Herkunft aller Security Controls rechtssicherer Nachweis
</p>
</div>
<Link href="/sdk/control-library" className="ml-auto flex items-center gap-1 text-sm text-purple-600 hover:text-purple-800">
<Shield className="w-4 h-4" />
Zur Control Library
</Link>
</div>
</div>
<div className="flex flex-1 overflow-hidden">
{/* Left: Navigation */}
<div className="w-72 border-r border-gray-200 bg-gray-50 overflow-y-auto flex-shrink-0">
<div className="p-3 space-y-1">
<p className="text-xs font-semibold text-gray-400 uppercase px-3 mb-2">Dokumentation</p>
{PROVENANCE_SECTIONS.map(section => (
<button
key={section.id}
onClick={() => setActiveSection(section.id)}
className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${
activeSection === section.id
? 'bg-green-100 text-green-900 font-medium'
: 'text-gray-700 hover:bg-gray-100'
}`}
>
{section.title}
</button>
))}
<div className="border-t border-gray-200 mt-3 pt-3">
<p className="text-xs font-semibold text-gray-400 uppercase px-3 mb-2">Live-Daten</p>
{['license-matrix', 'source-registry'].map(id => (
<button
key={id}
onClick={() => setActiveSection(id)}
className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${
activeSection === id ? 'bg-green-100 text-green-900 font-medium' : 'text-gray-700 hover:bg-gray-100'
}`}
>
{id === 'license-matrix' ? 'Lizenz-Matrix' : 'Quellenregister'}
</button>
))}
</div>
</div>
</div>
{/* Right: Content */}
<div className="flex-1 overflow-y-auto p-6">
<div className="max-w-3xl mx-auto">
{currentSection && (
<div>
<h2 className="text-xl font-bold text-gray-900 mb-4">{currentSection.title}</h2>
<div className="prose prose-sm max-w-none">
<MarkdownRenderer content={currentSection.content} />
</div>
</div>
)}
{activeSection === 'license-matrix' && <LicenseMatrix licenses={licenses} loading={loading} />}
{activeSection === 'source-registry' && <SourceRegistry sources={sources} loading={loading} />}
</div>
</div>
</div>
</div>
)
}