feat(training+controls): interactive video pipeline, training blocks, control generator, CE libraries
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 37s
CI/CD / test-python-backend-compliance (push) Successful in 39s
CI/CD / test-python-document-crawler (push) Successful in 26s
CI/CD / test-python-dsms-gateway (push) Successful in 23s
CI/CD / validate-canonical-controls (push) Successful in 12s
CI/CD / Deploy (push) Has been skipped

Interactive Training Videos (CP-TRAIN):
- DB migration 022: training_checkpoints + checkpoint_progress tables
- NarratorScript generation via Anthropic (AI Teacher persona, German)
- TTS batch synthesis + interactive video pipeline (slides + checkpoint slides + FFmpeg)
- 4 new API endpoints: generate-interactive, interactive-manifest, checkpoint submit, checkpoint progress
- InteractiveVideoPlayer component (HTML5 Video, quiz overlay, seek protection, progress tracking)
- Learner portal integration with automatic completion on all checkpoints passed
- 30 new tests (handler validation + grading logic + manifest/progress + seek protection)

Training Blocks:
- Block generator, block store, block config CRUD + preview/generate endpoints
- Migration 021: training_blocks schema

Control Generator + Canonical Library:
- Control generator routes + service enhancements
- Canonical control library helpers, sidebar entry
- Citation backfill service + tests
- CE libraries data (hazard, protection, evidence, lifecycle, components)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-16 21:41:48 +01:00
parent d2133dbfa2
commit 4f6bc8f6f6
50 changed files with 17299 additions and 198 deletions

View File

@@ -3,7 +3,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import {
Shield, Search, ChevronRight, ChevronLeft, Filter, Lock,
BookOpen, Plus, Zap, BarChart3, ListChecks,
BookOpen, Plus, Zap, BarChart3, ListChecks, Trash2,
ChevronsLeft, ChevronsRight, ArrowUpDown, Clock, RefreshCw,
} from 'lucide-react'
import {
@@ -263,6 +263,33 @@ export default function ControlLibraryPage() {
} catch { /* ignore */ }
}
const [bulkProcessing, setBulkProcessing] = useState(false)
const handleBulkReject = async (sourceState: string) => {
const count = stateFilter === sourceState ? totalCount : reviewCount
if (!confirm(`Alle ${count} Controls mit Status "${sourceState}" auf "deprecated" setzen? Diese Aktion kann nicht rueckgaengig gemacht werden.`)) return
setBulkProcessing(true)
try {
const res = await fetch(`${BACKEND_URL}?endpoint=bulk-review`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ release_state: sourceState, action: 'reject' }),
})
if (res.ok) {
const data = await res.json()
alert(`${data.affected_count} Controls auf "deprecated" gesetzt.`)
await fullReload()
} else {
const err = await res.json()
alert(`Fehler: ${err.error || err.details || 'Unbekannt'}`)
}
} catch {
alert('Netzwerkfehler')
} finally {
setBulkProcessing(false)
}
}
const loadProcessedStats = async () => {
try {
const res = await fetch(`${BACKEND_URL}?endpoint=processed-stats`)
@@ -381,13 +408,23 @@ export default function ControlLibraryPage() {
</div>
<div className="flex items-center gap-2">
{reviewCount > 0 && (
<button
onClick={enterReviewMode}
className="flex items-center gap-1.5 px-3 py-2 text-sm text-white bg-yellow-600 rounded-lg hover:bg-yellow-700"
>
<ListChecks className="w-4 h-4" />
Review ({reviewCount})
</button>
<>
<button
onClick={enterReviewMode}
className="flex items-center gap-1.5 px-3 py-2 text-sm text-white bg-yellow-600 rounded-lg hover:bg-yellow-700"
>
<ListChecks className="w-4 h-4" />
Review ({reviewCount})
</button>
<button
onClick={() => handleBulkReject('needs_review')}
disabled={bulkProcessing}
className="flex items-center gap-1.5 px-3 py-2 text-sm text-white bg-red-600 rounded-lg hover:bg-red-700 disabled:opacity-50"
>
<Trash2 className="w-4 h-4" />
{bulkProcessing ? 'Wird verarbeitet...' : `Alle ${reviewCount} ablehnen`}
</button>
</>
)}
<button
onClick={() => { setShowStats(!showStats); if (!showStats) loadProcessedStats() }}
@@ -480,6 +517,7 @@ export default function ControlLibraryPage() {
<option value="needs_review">Review noetig</option>
<option value="too_close">Zu aehnlich</option>
<option value="duplicate">Duplikat</option>
<option value="deprecated">Deprecated</option>
</select>
<select
value={verificationFilter}
@@ -507,9 +545,21 @@ export default function ControlLibraryPage() {
className="text-sm border border-gray-300 rounded-lg px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Zielgruppe</option>
{Object.entries(TARGET_AUDIENCE_OPTIONS).map(([k, v]) => (
<option key={k} value={k}>{v.label}</option>
))}
<option value="unternehmen">Unternehmen</option>
<option value="behoerden">Behoerden</option>
<option value="entwickler">Entwickler</option>
<option value="datenschutzbeauftragte">DSB</option>
<option value="geschaeftsfuehrung">Geschaeftsfuehrung</option>
<option value="it-abteilung">IT-Abteilung</option>
<option value="rechtsabteilung">Rechtsabteilung</option>
<option value="compliance-officer">Compliance Officer</option>
<option value="personalwesen">Personalwesen</option>
<option value="einkauf">Einkauf</option>
<option value="produktion">Produktion</option>
<option value="vertrieb">Vertrieb</option>
<option value="gesundheitswesen">Gesundheitswesen</option>
<option value="finanzwesen">Finanzwesen</option>
<option value="oeffentlicher_dienst">Oeffentl. Dienst</option>
</select>
<select
value={sourceFilter}
@@ -567,11 +617,23 @@ export default function ControlLibraryPage() {
{/* Pagination Header */}
<div className="px-6 py-2 bg-gray-50 border-b border-gray-200 flex items-center justify-between text-xs text-gray-500">
<span>
{totalCount} Controls gefunden
{totalCount !== (meta?.total ?? totalCount) && ` (von ${meta?.total} gesamt)`}
{loading && <span className="ml-2 text-purple-500">Lade...</span>}
</span>
<div className="flex items-center gap-3">
<span>
{totalCount} Controls gefunden
{totalCount !== (meta?.total ?? totalCount) && ` (von ${meta?.total} gesamt)`}
{loading && <span className="ml-2 text-purple-500">Lade...</span>}
</span>
{stateFilter && ['needs_review', 'too_close', 'duplicate'].includes(stateFilter) && totalCount > 0 && (
<button
onClick={() => handleBulkReject(stateFilter)}
disabled={bulkProcessing}
className="flex items-center gap-1 px-2 py-1 text-xs text-white bg-red-600 rounded hover:bg-red-700 disabled:opacity-50"
>
<Trash2 className="w-3 h-3" />
{bulkProcessing ? '...' : `Alle ${totalCount} ablehnen`}
</button>
)}
</div>
<span>Seite {currentPage} von {totalPages}</span>
</div>