feat: Vorbereitung-Module auf 100% — Persistenz, Backend-Services, UCCA Frontend
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 37s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 18s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 37s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 18s
Phase A: PostgreSQL State Store (sdk_states Tabelle, InMemory-Fallback) Phase B: Modules dynamisch vom Backend, Scope DB-Persistenz, Source Policy State Phase C: UCCA Frontend (3 Seiten, Wizard, RiskScoreGauge), Obligations Live-Daten Phase D: Document Import (PDF/LLM/Gap-Analyse), System Screening (SBOM/OSV.dev) Phase E: Company Profile CRUD mit Audit-Logging Phase F: Tests (Python + TypeScript), flow-data.ts DB-Tabellen aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,85 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import React, { useState, useRef } from 'react'
|
||||
import { useSDK, ScreeningResult, SecurityIssue, SBOMComponent } from '@/lib/sdk'
|
||||
|
||||
// =============================================================================
|
||||
// MOCK DATA
|
||||
// =============================================================================
|
||||
|
||||
const mockSBOMComponents: SBOMComponent[] = [
|
||||
{
|
||||
name: 'react',
|
||||
version: '18.3.0',
|
||||
type: 'library',
|
||||
purl: 'pkg:npm/react@18.3.0',
|
||||
licenses: ['MIT'],
|
||||
vulnerabilities: [],
|
||||
},
|
||||
{
|
||||
name: 'next',
|
||||
version: '15.1.0',
|
||||
type: 'framework',
|
||||
purl: 'pkg:npm/next@15.1.0',
|
||||
licenses: ['MIT'],
|
||||
vulnerabilities: [],
|
||||
},
|
||||
{
|
||||
name: 'lodash',
|
||||
version: '4.17.21',
|
||||
type: 'library',
|
||||
purl: 'pkg:npm/lodash@4.17.21',
|
||||
licenses: ['MIT'],
|
||||
vulnerabilities: [
|
||||
{
|
||||
id: 'CVE-2021-23337',
|
||||
cve: 'CVE-2021-23337',
|
||||
severity: 'HIGH',
|
||||
title: 'Prototype Pollution',
|
||||
description: 'Lodash versions prior to 4.17.21 are vulnerable to Command Injection via the template function.',
|
||||
cvss: 7.2,
|
||||
fixedIn: '4.17.21',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const mockSecurityIssues: SecurityIssue[] = [
|
||||
{
|
||||
id: 'issue-1',
|
||||
severity: 'CRITICAL',
|
||||
title: 'SQL Injection Vulnerability',
|
||||
description: 'Unvalidated user input in database queries',
|
||||
cve: 'CVE-2024-12345',
|
||||
cvss: 9.8,
|
||||
affectedComponent: 'database-connector',
|
||||
remediation: 'Use parameterized queries',
|
||||
status: 'OPEN',
|
||||
},
|
||||
{
|
||||
id: 'issue-2',
|
||||
severity: 'HIGH',
|
||||
title: 'Cross-Site Scripting (XSS)',
|
||||
description: 'Reflected XSS in search functionality',
|
||||
cve: 'CVE-2024-12346',
|
||||
cvss: 7.5,
|
||||
affectedComponent: 'search-module',
|
||||
remediation: 'Sanitize and encode user input',
|
||||
status: 'IN_PROGRESS',
|
||||
},
|
||||
{
|
||||
id: 'issue-3',
|
||||
severity: 'MEDIUM',
|
||||
title: 'Insecure Cookie Configuration',
|
||||
description: 'Session cookies missing Secure and HttpOnly flags',
|
||||
cve: null,
|
||||
cvss: 5.3,
|
||||
affectedComponent: 'auth-service',
|
||||
remediation: 'Set Secure and HttpOnly flags on cookies',
|
||||
status: 'OPEN',
|
||||
},
|
||||
]
|
||||
|
||||
// =============================================================================
|
||||
// COMPONENTS
|
||||
// =============================================================================
|
||||
@@ -243,62 +166,120 @@ export default function ScreeningPage() {
|
||||
const [isScanning, setIsScanning] = useState(false)
|
||||
const [scanProgress, setScanProgress] = useState(0)
|
||||
const [scanStatus, setScanStatus] = useState('')
|
||||
const [repositoryUrl, setRepositoryUrl] = useState('')
|
||||
|
||||
const startScan = async () => {
|
||||
if (!repositoryUrl) return
|
||||
const [scanError, setScanError] = useState<string | null>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const startScan = async (file: File) => {
|
||||
setIsScanning(true)
|
||||
setScanProgress(0)
|
||||
setScanStatus('Initialisierung...')
|
||||
setScanError(null)
|
||||
|
||||
// Simulate scan progress
|
||||
const steps = [
|
||||
{ progress: 10, status: 'Repository wird geklont...' },
|
||||
{ progress: 25, status: 'Abhängigkeiten werden analysiert...' },
|
||||
{ progress: 40, status: 'SBOM wird generiert...' },
|
||||
{ progress: 60, status: 'Schwachstellenscan läuft...' },
|
||||
{ progress: 80, status: 'Lizenzprüfung...' },
|
||||
{ progress: 95, status: 'Bericht wird erstellt...' },
|
||||
{ progress: 100, status: 'Abgeschlossen!' },
|
||||
]
|
||||
// Show progress steps while API processes
|
||||
const progressInterval = setInterval(() => {
|
||||
setScanProgress(prev => {
|
||||
if (prev >= 90) return prev
|
||||
const step = Math.random() * 15 + 5
|
||||
const next = Math.min(prev + step, 90)
|
||||
const statuses = [
|
||||
'Abhaengigkeiten werden analysiert...',
|
||||
'SBOM wird generiert...',
|
||||
'Schwachstellenscan laeuft...',
|
||||
'OSV.dev Datenbank wird abgefragt...',
|
||||
'Lizenzpruefung...',
|
||||
]
|
||||
setScanStatus(statuses[Math.min(Math.floor(next / 20), statuses.length - 1)])
|
||||
return next
|
||||
})
|
||||
}, 600)
|
||||
|
||||
for (const step of steps) {
|
||||
await new Promise(r => setTimeout(r, 800))
|
||||
setScanProgress(step.progress)
|
||||
setScanStatus(step.status)
|
||||
try {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('tenant_id', 'default')
|
||||
|
||||
const response = await fetch('/api/sdk/v1/screening/scan', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
clearInterval(progressInterval)
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.json().catch(() => ({ error: 'Unknown error' }))
|
||||
throw new Error(err.details || err.error || `HTTP ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
setScanProgress(100)
|
||||
setScanStatus('Abgeschlossen!')
|
||||
|
||||
// Map backend response to ScreeningResult
|
||||
const issues: SecurityIssue[] = (data.issues || []).map((i: any) => ({
|
||||
id: i.id,
|
||||
severity: i.severity,
|
||||
title: i.title,
|
||||
description: i.description,
|
||||
cve: i.cve || null,
|
||||
cvss: i.cvss || null,
|
||||
affectedComponent: i.affected_component,
|
||||
remediation: i.remediation,
|
||||
status: i.status || 'OPEN',
|
||||
}))
|
||||
|
||||
const components: SBOMComponent[] = (data.components || []).map((c: any) => ({
|
||||
name: c.name,
|
||||
version: c.version,
|
||||
type: c.type,
|
||||
purl: c.purl,
|
||||
licenses: c.licenses || [],
|
||||
vulnerabilities: c.vulnerabilities || [],
|
||||
}))
|
||||
|
||||
const result: ScreeningResult = {
|
||||
id: data.id,
|
||||
status: 'COMPLETED',
|
||||
startedAt: data.started_at ? new Date(data.started_at) : new Date(),
|
||||
completedAt: data.completed_at ? new Date(data.completed_at) : new Date(),
|
||||
sbom: {
|
||||
format: data.sbom_format || 'CycloneDX',
|
||||
version: data.sbom_version || '1.5',
|
||||
components,
|
||||
dependencies: [],
|
||||
generatedAt: new Date(),
|
||||
},
|
||||
securityScan: {
|
||||
totalIssues: data.total_issues || issues.length,
|
||||
critical: data.critical_issues || 0,
|
||||
high: data.high_issues || 0,
|
||||
medium: data.medium_issues || 0,
|
||||
low: data.low_issues || 0,
|
||||
issues,
|
||||
},
|
||||
error: null,
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_SCREENING', payload: result })
|
||||
issues.forEach(issue => {
|
||||
dispatch({ type: 'ADD_SECURITY_ISSUE', payload: issue })
|
||||
})
|
||||
} catch (error: any) {
|
||||
clearInterval(progressInterval)
|
||||
console.error('Screening scan failed:', error)
|
||||
setScanError(error.message || 'Scan fehlgeschlagen')
|
||||
setScanProgress(0)
|
||||
setScanStatus('')
|
||||
} finally {
|
||||
setIsScanning(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Set mock results
|
||||
const result: ScreeningResult = {
|
||||
id: `scan-${Date.now()}`,
|
||||
status: 'COMPLETED',
|
||||
startedAt: new Date(Date.now() - 30000),
|
||||
completedAt: new Date(),
|
||||
sbom: {
|
||||
format: 'CycloneDX',
|
||||
version: '1.5',
|
||||
components: mockSBOMComponents,
|
||||
dependencies: [],
|
||||
generatedAt: new Date(),
|
||||
},
|
||||
securityScan: {
|
||||
totalIssues: mockSecurityIssues.length,
|
||||
critical: mockSecurityIssues.filter(i => i.severity === 'CRITICAL').length,
|
||||
high: mockSecurityIssues.filter(i => i.severity === 'HIGH').length,
|
||||
medium: mockSecurityIssues.filter(i => i.severity === 'MEDIUM').length,
|
||||
low: mockSecurityIssues.filter(i => i.severity === 'LOW').length,
|
||||
issues: mockSecurityIssues,
|
||||
},
|
||||
error: null,
|
||||
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (file) {
|
||||
startScan(file)
|
||||
}
|
||||
|
||||
dispatch({ type: 'SET_SCREENING', payload: result })
|
||||
mockSecurityIssues.forEach(issue => {
|
||||
dispatch({ type: 'ADD_SECURITY_ISSUE', payload: issue })
|
||||
})
|
||||
|
||||
setIsScanning(false)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -314,30 +295,33 @@ export default function ScreeningPage() {
|
||||
{/* Scan Input */}
|
||||
{!state.screening && !isScanning && (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Repository scannen</h3>
|
||||
<h3 className="font-semibold text-gray-900 mb-4">Abhaengigkeiten scannen</h3>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
Laden Sie eine Abhaengigkeitsdatei hoch, um ein SBOM zu generieren und Schwachstellen zu erkennen.
|
||||
</p>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json,.txt,.lock"
|
||||
onChange={handleFileSelect}
|
||||
className="hidden"
|
||||
/>
|
||||
<div className="flex gap-4">
|
||||
<input
|
||||
type="text"
|
||||
value={repositoryUrl}
|
||||
onChange={e => setRepositoryUrl(e.target.value)}
|
||||
placeholder="https://github.com/organization/repository"
|
||||
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
onClick={startScan}
|
||||
disabled={!repositoryUrl}
|
||||
className={`px-6 py-2 rounded-lg font-medium transition-colors ${
|
||||
repositoryUrl
|
||||
? 'bg-purple-600 text-white hover:bg-purple-700'
|
||||
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
|
||||
}`}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="px-6 py-2 bg-purple-600 text-white rounded-lg font-medium hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
Scan starten
|
||||
Datei auswaehlen & scannen
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-gray-500">
|
||||
Unterstützte Formate: Git URL, GitHub, GitLab, Bitbucket
|
||||
Unterstuetzte Formate: package-lock.json, requirements.txt, yarn.lock
|
||||
</p>
|
||||
{scanError && (
|
||||
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-700">
|
||||
{scanError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user