Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website, Klausur-Service, School-Service, Voice-Service, Geo-Service, BreakPilot Drive, Agent-Core Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
525 lines
32 KiB
TypeScript
525 lines
32 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* SBOM (Software Bill of Materials) Admin Page
|
|
*
|
|
* Displays:
|
|
* - All infrastructure components (Docker services)
|
|
* - Python/Go dependencies
|
|
* - Node.js packages
|
|
* - License information
|
|
* - Version tracking
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react'
|
|
import AdminLayout from '@/components/admin/AdminLayout'
|
|
|
|
interface Component {
|
|
type: string
|
|
name: string
|
|
version: string
|
|
purl?: string
|
|
licenses?: { license: { id: string } }[]
|
|
category?: string
|
|
port?: string
|
|
description?: string
|
|
license?: string
|
|
sourceUrl?: string
|
|
}
|
|
|
|
interface SBOMData {
|
|
bomFormat?: string
|
|
specVersion?: string
|
|
version?: number
|
|
metadata?: {
|
|
timestamp?: string
|
|
tools?: { vendor: string; name: string; version: string }[]
|
|
component?: { type: string; name: string; version: string }
|
|
}
|
|
components?: Component[]
|
|
}
|
|
|
|
type CategoryType = 'all' | 'infrastructure' | 'security-tools' | 'python' | 'go' | 'nodejs'
|
|
|
|
// Infrastructure components from docker-compose.yml and project analysis
|
|
const INFRASTRUCTURE_COMPONENTS: Component[] = [
|
|
// ===== DATABASES =====
|
|
{ type: 'service', name: 'PostgreSQL', version: '16-alpine', category: 'database', port: '5432', description: 'Hauptdatenbank', license: 'PostgreSQL', sourceUrl: 'https://github.com/postgres/postgres' },
|
|
{ type: 'service', name: 'Synapse PostgreSQL', version: '16-alpine', category: 'database', port: '-', description: 'Matrix Datenbank', license: 'PostgreSQL', sourceUrl: 'https://github.com/postgres/postgres' },
|
|
{ type: 'service', name: 'ERPNext MariaDB', version: '10.6', category: 'database', port: '-', description: 'ERPNext Datenbank', license: 'GPL-2.0', sourceUrl: 'https://github.com/MariaDB/server' },
|
|
{ type: 'service', name: 'MongoDB', version: '7.0', category: 'database', port: '27017', description: 'LibreChat Datenbank', license: 'SSPL-1.0', sourceUrl: 'https://github.com/mongodb/mongo' },
|
|
|
|
// ===== CACHE & QUEUE =====
|
|
{ type: 'service', name: 'Redis', version: 'alpine', category: 'cache', port: '6379', description: 'In-Memory Cache & Sessions', license: 'BSD-3-Clause', sourceUrl: 'https://github.com/redis/redis' },
|
|
{ type: 'service', name: 'ERPNext Redis Queue', version: 'alpine', category: 'cache', port: '-', description: 'Job Queue', license: 'BSD-3-Clause', sourceUrl: 'https://github.com/redis/redis' },
|
|
{ type: 'service', name: 'ERPNext Redis Cache', version: 'alpine', category: 'cache', port: '-', description: 'Cache Layer', license: 'BSD-3-Clause', sourceUrl: 'https://github.com/redis/redis' },
|
|
|
|
// ===== SEARCH ENGINES =====
|
|
{ type: 'service', name: 'Qdrant', version: '1.7.4', category: 'search', port: '6333', description: 'Vector Database (RAG/Embeddings)', license: 'Apache-2.0', sourceUrl: 'https://github.com/qdrant/qdrant' },
|
|
{ type: 'service', name: 'OpenSearch', version: '2.x', category: 'search', port: '9200', description: 'Volltext-Suche (Elasticsearch Fork)', license: 'Apache-2.0', sourceUrl: 'https://github.com/opensearch-project/OpenSearch' },
|
|
{ type: 'service', name: 'Meilisearch', version: 'latest', category: 'search', port: '7700', description: 'Instant Search Engine', license: 'MIT', sourceUrl: 'https://github.com/meilisearch/meilisearch' },
|
|
|
|
// ===== OBJECT STORAGE =====
|
|
{ type: 'service', name: 'MinIO', version: 'latest', category: 'storage', port: '9000/9001', description: 'S3-kompatibel Object Storage', license: 'AGPL-3.0', sourceUrl: 'https://github.com/minio/minio' },
|
|
{ type: 'service', name: 'IPFS (Kubo)', version: '0.24', category: 'storage', port: '5001', description: 'Dezentrales Speichersystem', license: 'MIT/Apache-2.0', sourceUrl: 'https://github.com/ipfs/kubo' },
|
|
{ type: 'service', name: 'DSMS Gateway', version: '1.0', category: 'storage', port: '8082', description: 'IPFS REST API', license: 'Proprietary', sourceUrl: '-' },
|
|
|
|
// ===== SECURITY =====
|
|
{ type: 'service', name: 'HashiCorp Vault', version: '1.15', category: 'security', port: '8200', description: 'Secrets Management', license: 'BUSL-1.1', sourceUrl: 'https://github.com/hashicorp/vault' },
|
|
{ type: 'service', name: 'Keycloak', version: '23.0', category: 'security', port: '8180', description: 'Identity Provider (SSO/OIDC)', license: 'Apache-2.0', sourceUrl: 'https://github.com/keycloak/keycloak' },
|
|
|
|
// ===== COMMUNICATION =====
|
|
{ type: 'service', name: 'Matrix Synapse', version: 'latest', category: 'communication', port: '8008', description: 'E2EE Messenger Server', license: 'AGPL-3.0', sourceUrl: 'https://github.com/element-hq/synapse' },
|
|
{ type: 'service', name: 'Jitsi Web', version: 'stable-9823', category: 'communication', port: '8443', description: 'Videokonferenz UI', license: 'Apache-2.0', sourceUrl: 'https://github.com/jitsi/jitsi-meet' },
|
|
{ type: 'service', name: 'Jitsi Prosody (XMPP)', version: 'stable-9823', category: 'communication', port: '-', description: 'XMPP Server', license: 'MIT', sourceUrl: 'https://github.com/bjc/prosody' },
|
|
{ type: 'service', name: 'Jitsi Jicofo', version: 'stable-9823', category: 'communication', port: '-', description: 'Conference Focus Component', license: 'Apache-2.0', sourceUrl: 'https://github.com/jitsi/jicofo' },
|
|
{ type: 'service', name: 'Jitsi JVB', version: 'stable-9823', category: 'communication', port: '10000/udp', description: 'Videobridge (WebRTC SFU)', license: 'Apache-2.0', sourceUrl: 'https://github.com/jitsi/jitsi-videobridge' },
|
|
|
|
// ===== APPLICATION SERVICES (Python) =====
|
|
{ type: 'service', name: 'Python Backend (FastAPI)', version: '3.12', category: 'application', port: '8000', description: 'Haupt-Backend API & Studio', license: 'Proprietary', sourceUrl: '-' },
|
|
{ type: 'service', name: 'Klausur Service', version: '1.0', category: 'application', port: '8086', description: 'Abitur-Klausurkorrektur (BYOEH)', license: 'Proprietary', sourceUrl: '-' },
|
|
|
|
// ===== APPLICATION SERVICES (Go) =====
|
|
{ type: 'service', name: 'Go Consent Service', version: '1.21', category: 'application', port: '8081', description: 'DSGVO Consent Management', license: 'Proprietary', sourceUrl: '-' },
|
|
{ type: 'service', name: 'Go School Service', version: '1.21', category: 'application', port: '8084', description: 'Klausuren, Noten, Zeugnisse', license: 'Proprietary', sourceUrl: '-' },
|
|
{ type: 'service', name: 'Go Billing Service', version: '1.21', category: 'application', port: '8083', description: 'Stripe Billing Integration', license: 'Proprietary', sourceUrl: '-' },
|
|
|
|
// ===== APPLICATION SERVICES (Node.js) =====
|
|
{ type: 'service', name: 'Next.js Admin Frontend', version: '15.1', category: 'application', port: '3000', description: 'Admin Dashboard (React)', license: 'Proprietary', sourceUrl: '-' },
|
|
{ type: 'service', name: 'H5P Content Service', version: 'latest', category: 'application', port: '8085', description: 'Interaktive Inhalte', license: 'MIT', sourceUrl: 'https://github.com/h5p/h5p-server' },
|
|
{ type: 'service', name: 'Policy Vault (NestJS)', version: '1.0', category: 'application', port: '3001', description: 'Richtlinien-Verwaltung API', license: 'Proprietary', sourceUrl: '-' },
|
|
{ type: 'service', name: 'Policy Vault (Angular)', version: '17', category: 'application', port: '4200', description: 'Richtlinien-Verwaltung UI', license: 'Proprietary', sourceUrl: '-' },
|
|
|
|
// ===== APPLICATION SERVICES (Vue) =====
|
|
{ type: 'service', name: 'Creator Studio (Vue 3)', version: '3.4', category: 'application', port: '-', description: 'Content Creation UI', license: 'Proprietary', sourceUrl: '-' },
|
|
|
|
// ===== AI/LLM SERVICES =====
|
|
{ type: 'service', name: 'LibreChat', version: 'latest', category: 'ai', port: '3080', description: 'Multi-LLM Chat Interface', license: 'MIT', sourceUrl: 'https://github.com/danny-avila/LibreChat' },
|
|
{ type: 'service', name: 'RAGFlow', version: 'latest', category: 'ai', port: '9380', description: 'RAG Pipeline Service', license: 'Apache-2.0', sourceUrl: 'https://github.com/infiniflow/ragflow' },
|
|
|
|
// ===== ERP =====
|
|
{ type: 'service', name: 'ERPNext', version: 'v15', category: 'erp', port: '8090', description: 'Open Source ERP System', license: 'GPL-3.0', sourceUrl: 'https://github.com/frappe/erpnext' },
|
|
|
|
// ===== DEVELOPMENT =====
|
|
{ type: 'service', name: 'Mailpit', version: 'latest', category: 'development', port: '8025/1025', description: 'E-Mail Testing (SMTP Catch-All)', license: 'MIT', sourceUrl: 'https://github.com/axllent/mailpit' },
|
|
]
|
|
|
|
// Security Tools discovered in project
|
|
const SECURITY_TOOLS: Component[] = [
|
|
{ type: 'tool', name: 'Trivy', version: 'latest', category: 'security-tool', description: 'Container Vulnerability Scanner', license: 'Apache-2.0', sourceUrl: 'https://github.com/aquasecurity/trivy' },
|
|
{ type: 'tool', name: 'Grype', version: 'latest', category: 'security-tool', description: 'SBOM Vulnerability Scanner', license: 'Apache-2.0', sourceUrl: 'https://github.com/anchore/grype' },
|
|
{ type: 'tool', name: 'Syft', version: 'latest', category: 'security-tool', description: 'SBOM Generator', license: 'Apache-2.0', sourceUrl: 'https://github.com/anchore/syft' },
|
|
{ type: 'tool', name: 'Gitleaks', version: 'latest', category: 'security-tool', description: 'Secrets Detection in Git', license: 'MIT', sourceUrl: 'https://github.com/gitleaks/gitleaks' },
|
|
{ type: 'tool', name: 'TruffleHog', version: '3.x', category: 'security-tool', description: 'Secrets Scanner (Regex/Entropy)', license: 'AGPL-3.0', sourceUrl: 'https://github.com/trufflesecurity/trufflehog' },
|
|
{ type: 'tool', name: 'Semgrep', version: 'latest', category: 'security-tool', description: 'SAST - Static Analysis', license: 'LGPL-2.1', sourceUrl: 'https://github.com/semgrep/semgrep' },
|
|
{ type: 'tool', name: 'Bandit', version: 'latest', category: 'security-tool', description: 'Python Security Linter', license: 'Apache-2.0', sourceUrl: 'https://github.com/PyCQA/bandit' },
|
|
{ type: 'tool', name: 'Gosec', version: 'latest', category: 'security-tool', description: 'Go Security Scanner', license: 'Apache-2.0', sourceUrl: 'https://github.com/securego/gosec' },
|
|
{ type: 'tool', name: 'govulncheck', version: 'latest', category: 'security-tool', description: 'Go Vulnerability Check', license: 'BSD-3-Clause', sourceUrl: 'https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck' },
|
|
{ type: 'tool', name: 'golangci-lint', version: 'latest', category: 'security-tool', description: 'Go Linter (Security Rules)', license: 'GPL-3.0', sourceUrl: 'https://github.com/golangci/golangci-lint' },
|
|
{ type: 'tool', name: 'npm audit', version: 'built-in', category: 'security-tool', description: 'Node.js Vulnerability Check', license: 'Artistic-2.0', sourceUrl: 'https://docs.npmjs.com/cli/commands/npm-audit' },
|
|
{ type: 'tool', name: 'pip-audit', version: 'latest', category: 'security-tool', description: 'Python Dependency Audit', license: 'Apache-2.0', sourceUrl: 'https://github.com/pypa/pip-audit' },
|
|
{ type: 'tool', name: 'safety', version: 'latest', category: 'security-tool', description: 'Python Safety Check', license: 'MIT', sourceUrl: 'https://github.com/pyupio/safety' },
|
|
{ type: 'tool', name: 'CodeQL', version: 'latest', category: 'security-tool', description: 'GitHub Security Analysis', license: 'MIT', sourceUrl: 'https://github.com/github/codeql' },
|
|
]
|
|
|
|
// Key Python packages (from requirements.txt)
|
|
const PYTHON_PACKAGES: Component[] = [
|
|
{ type: 'library', name: 'FastAPI', version: '0.109+', category: 'python', description: 'Web Framework', license: 'MIT', sourceUrl: 'https://github.com/tiangolo/fastapi' },
|
|
{ type: 'library', name: 'Pydantic', version: '2.x', category: 'python', description: 'Data Validation', license: 'MIT', sourceUrl: 'https://github.com/pydantic/pydantic' },
|
|
{ type: 'library', name: 'SQLAlchemy', version: '2.x', category: 'python', description: 'ORM', license: 'MIT', sourceUrl: 'https://github.com/sqlalchemy/sqlalchemy' },
|
|
{ type: 'library', name: 'httpx', version: 'latest', category: 'python', description: 'Async HTTP Client', license: 'BSD-3-Clause', sourceUrl: 'https://github.com/encode/httpx' },
|
|
{ type: 'library', name: 'PyJWT', version: 'latest', category: 'python', description: 'JWT Handling', license: 'MIT', sourceUrl: 'https://github.com/jpadilla/pyjwt' },
|
|
{ type: 'library', name: 'hvac', version: 'latest', category: 'python', description: 'Vault Client', license: 'Apache-2.0', sourceUrl: 'https://github.com/hvac/hvac' },
|
|
{ type: 'library', name: 'python-multipart', version: 'latest', category: 'python', description: 'File Uploads', license: 'Apache-2.0', sourceUrl: 'https://github.com/andrew-d/python-multipart' },
|
|
{ type: 'library', name: 'aiofiles', version: 'latest', category: 'python', description: 'Async File I/O', license: 'Apache-2.0', sourceUrl: 'https://github.com/Tinche/aiofiles' },
|
|
{ type: 'library', name: 'openai', version: 'latest', category: 'python', description: 'OpenAI SDK', license: 'MIT', sourceUrl: 'https://github.com/openai/openai-python' },
|
|
{ type: 'library', name: 'anthropic', version: 'latest', category: 'python', description: 'Anthropic Claude SDK', license: 'MIT', sourceUrl: 'https://github.com/anthropics/anthropic-sdk-python' },
|
|
{ type: 'library', name: 'langchain', version: 'latest', category: 'python', description: 'LLM Framework', license: 'MIT', sourceUrl: 'https://github.com/langchain-ai/langchain' },
|
|
// Mail Module Dependencies
|
|
{ type: 'library', name: 'aioimaplib', version: 'latest', category: 'python', description: 'Async IMAP Client (Unified Inbox)', license: 'MIT', sourceUrl: 'https://github.com/bamthomas/aioimaplib' },
|
|
{ type: 'library', name: 'aiosmtplib', version: 'latest', category: 'python', description: 'Async SMTP Client (Mail Sending)', license: 'MIT', sourceUrl: 'https://github.com/cole/aiosmtplib' },
|
|
{ type: 'library', name: 'email-validator', version: 'latest', category: 'python', description: 'Email Validation', license: 'CC0-1.0', sourceUrl: 'https://github.com/JoshData/python-email-validator' },
|
|
{ type: 'library', name: 'cryptography', version: 'latest', category: 'python', description: 'Encryption (Mail Credentials)', license: 'BSD-3-Clause', sourceUrl: 'https://github.com/pyca/cryptography' },
|
|
{ type: 'library', name: 'asyncpg', version: 'latest', category: 'python', description: 'Async PostgreSQL Driver', license: 'Apache-2.0', sourceUrl: 'https://github.com/MagicStack/asyncpg' },
|
|
{ type: 'library', name: 'python-dateutil', version: 'latest', category: 'python', description: 'Date Parsing (Deadline Extraction)', license: 'Apache-2.0', sourceUrl: 'https://github.com/dateutil/dateutil' },
|
|
]
|
|
|
|
// Key Go modules (from go.mod files)
|
|
const GO_MODULES: Component[] = [
|
|
{ type: 'library', name: 'gin-gonic/gin', version: '1.9+', category: 'go', description: 'Web Framework', license: 'MIT', sourceUrl: 'https://github.com/gin-gonic/gin' },
|
|
{ type: 'library', name: 'gorm.io/gorm', version: '1.25+', category: 'go', description: 'ORM', license: 'MIT', sourceUrl: 'https://github.com/go-gorm/gorm' },
|
|
{ type: 'library', name: 'golang-jwt/jwt', version: 'v5', category: 'go', description: 'JWT Library', license: 'MIT', sourceUrl: 'https://github.com/golang-jwt/jwt' },
|
|
{ type: 'library', name: 'stripe/stripe-go', version: 'v76', category: 'go', description: 'Stripe SDK', license: 'MIT', sourceUrl: 'https://github.com/stripe/stripe-go' },
|
|
{ type: 'library', name: 'spf13/viper', version: 'latest', category: 'go', description: 'Configuration', license: 'MIT', sourceUrl: 'https://github.com/spf13/viper' },
|
|
{ type: 'library', name: 'uber-go/zap', version: 'latest', category: 'go', description: 'Structured Logging', license: 'MIT', sourceUrl: 'https://github.com/uber-go/zap' },
|
|
{ type: 'library', name: 'swaggo/swag', version: 'latest', category: 'go', description: 'Swagger Docs', license: 'MIT', sourceUrl: 'https://github.com/swaggo/swag' },
|
|
]
|
|
|
|
// Key Node.js packages (from package.json files)
|
|
const NODE_PACKAGES: Component[] = [
|
|
{ type: 'library', name: 'Next.js', version: '15.1', category: 'nodejs', description: 'React Framework', license: 'MIT', sourceUrl: 'https://github.com/vercel/next.js' },
|
|
{ type: 'library', name: 'React', version: '19', category: 'nodejs', description: 'UI Library', license: 'MIT', sourceUrl: 'https://github.com/facebook/react' },
|
|
{ type: 'library', name: 'Vue.js', version: '3.4', category: 'nodejs', description: 'UI Framework (Creator Studio)', license: 'MIT', sourceUrl: 'https://github.com/vuejs/core' },
|
|
{ type: 'library', name: 'Angular', version: '17', category: 'nodejs', description: 'UI Framework (Policy Vault)', license: 'MIT', sourceUrl: 'https://github.com/angular/angular' },
|
|
{ type: 'library', name: 'NestJS', version: '10', category: 'nodejs', description: 'Node.js Framework', license: 'MIT', sourceUrl: 'https://github.com/nestjs/nest' },
|
|
{ type: 'library', name: 'TypeScript', version: '5.x', category: 'nodejs', description: 'Type System', license: 'Apache-2.0', sourceUrl: 'https://github.com/microsoft/TypeScript' },
|
|
{ type: 'library', name: 'Tailwind CSS', version: '3.4', category: 'nodejs', description: 'Utility CSS', license: 'MIT', sourceUrl: 'https://github.com/tailwindlabs/tailwindcss' },
|
|
{ type: 'library', name: 'Prisma', version: '5.x', category: 'nodejs', description: 'ORM (Policy Vault)', license: 'Apache-2.0', sourceUrl: 'https://github.com/prisma/prisma' },
|
|
]
|
|
|
|
export default function SBOMPage() {
|
|
const [sbomData, setSbomData] = useState<SBOMData | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [activeCategory, setActiveCategory] = useState<CategoryType>('all')
|
|
const [searchTerm, setSearchTerm] = useState('')
|
|
|
|
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
|
|
|
|
useEffect(() => {
|
|
loadSBOM()
|
|
}, [])
|
|
|
|
const loadSBOM = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const res = await fetch(`${BACKEND_URL}/api/v1/security/sbom`)
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
setSbomData(data)
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load SBOM:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const getAllComponents = (): Component[] => {
|
|
const infraComponents = INFRASTRUCTURE_COMPONENTS.map(c => ({
|
|
...c,
|
|
category: c.category || 'infrastructure'
|
|
}))
|
|
|
|
const securityToolsComponents = SECURITY_TOOLS.map(c => ({
|
|
...c,
|
|
category: c.category || 'security-tool'
|
|
}))
|
|
|
|
const pythonComponents = PYTHON_PACKAGES.map(c => ({
|
|
...c,
|
|
category: 'python'
|
|
}))
|
|
|
|
const goComponents = GO_MODULES.map(c => ({
|
|
...c,
|
|
category: 'go'
|
|
}))
|
|
|
|
const nodeComponents = NODE_PACKAGES.map(c => ({
|
|
...c,
|
|
category: 'nodejs'
|
|
}))
|
|
|
|
// Add dynamic SBOM data from backend if available
|
|
const dynamicPython = (sbomData?.components || []).map(c => ({
|
|
...c,
|
|
category: 'python'
|
|
}))
|
|
|
|
return [...infraComponents, ...securityToolsComponents, ...pythonComponents, ...goComponents, ...nodeComponents, ...dynamicPython]
|
|
}
|
|
|
|
const getFilteredComponents = () => {
|
|
let components = getAllComponents()
|
|
|
|
if (activeCategory !== 'all') {
|
|
if (activeCategory === 'infrastructure') {
|
|
components = INFRASTRUCTURE_COMPONENTS
|
|
} else if (activeCategory === 'security-tools') {
|
|
components = SECURITY_TOOLS
|
|
} else if (activeCategory === 'python') {
|
|
components = [...PYTHON_PACKAGES, ...(sbomData?.components || [])]
|
|
} else if (activeCategory === 'go') {
|
|
components = GO_MODULES
|
|
} else if (activeCategory === 'nodejs') {
|
|
components = NODE_PACKAGES
|
|
}
|
|
}
|
|
|
|
if (searchTerm) {
|
|
components = components.filter(c =>
|
|
c.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
c.version.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
(c.description?.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
)
|
|
}
|
|
|
|
return components
|
|
}
|
|
|
|
const getCategoryColor = (category?: string) => {
|
|
switch (category) {
|
|
case 'database': return 'bg-blue-100 text-blue-800'
|
|
case 'security': return 'bg-purple-100 text-purple-800'
|
|
case 'security-tool': return 'bg-red-100 text-red-800'
|
|
case 'application': return 'bg-green-100 text-green-800'
|
|
case 'communication': return 'bg-yellow-100 text-yellow-800'
|
|
case 'storage': return 'bg-orange-100 text-orange-800'
|
|
case 'search': return 'bg-pink-100 text-pink-800'
|
|
case 'erp': return 'bg-indigo-100 text-indigo-800'
|
|
case 'cache': return 'bg-cyan-100 text-cyan-800'
|
|
case 'ai': return 'bg-violet-100 text-violet-800'
|
|
case 'development': return 'bg-gray-100 text-gray-800'
|
|
case 'python': return 'bg-emerald-100 text-emerald-800'
|
|
case 'go': return 'bg-sky-100 text-sky-800'
|
|
case 'nodejs': return 'bg-lime-100 text-lime-800'
|
|
default: return 'bg-slate-100 text-slate-800'
|
|
}
|
|
}
|
|
|
|
const getLicenseColor = (license?: string) => {
|
|
if (!license) return 'bg-gray-100 text-gray-600'
|
|
if (license.includes('MIT')) return 'bg-green-100 text-green-700'
|
|
if (license.includes('Apache')) return 'bg-blue-100 text-blue-700'
|
|
if (license.includes('BSD')) return 'bg-cyan-100 text-cyan-700'
|
|
if (license.includes('GPL') || license.includes('LGPL')) return 'bg-orange-100 text-orange-700'
|
|
return 'bg-gray-100 text-gray-600'
|
|
}
|
|
|
|
const stats = {
|
|
totalInfra: INFRASTRUCTURE_COMPONENTS.length,
|
|
totalSecurityTools: SECURITY_TOOLS.length,
|
|
totalPython: PYTHON_PACKAGES.length + (sbomData?.components?.length || 0),
|
|
totalGo: GO_MODULES.length,
|
|
totalNode: NODE_PACKAGES.length,
|
|
totalAll: INFRASTRUCTURE_COMPONENTS.length + SECURITY_TOOLS.length + PYTHON_PACKAGES.length + GO_MODULES.length + NODE_PACKAGES.length + (sbomData?.components?.length || 0),
|
|
databases: INFRASTRUCTURE_COMPONENTS.filter(c => c.category === 'database').length,
|
|
services: INFRASTRUCTURE_COMPONENTS.filter(c => c.category === 'application').length,
|
|
communication: INFRASTRUCTURE_COMPONENTS.filter(c => c.category === 'communication').length,
|
|
}
|
|
|
|
const categories = [
|
|
{ id: 'all', name: 'Alle', count: stats.totalAll },
|
|
{ id: 'infrastructure', name: 'Infrastruktur', count: stats.totalInfra },
|
|
{ id: 'security-tools', name: 'Security Tools', count: stats.totalSecurityTools },
|
|
{ id: 'python', name: 'Python', count: stats.totalPython },
|
|
{ id: 'go', name: 'Go', count: stats.totalGo },
|
|
{ id: 'nodejs', name: 'Node.js', count: stats.totalNode },
|
|
]
|
|
|
|
const filteredComponents = getFilteredComponents()
|
|
|
|
return (
|
|
<AdminLayout title="SBOM" description="Software Bill of Materials - Alle Komponenten & Abhängigkeiten">
|
|
{/* Stats Cards */}
|
|
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4 mb-6">
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-slate-800">{stats.totalAll}</div>
|
|
<div className="text-sm text-slate-500">Komponenten Total</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-purple-600">{stats.totalInfra}</div>
|
|
<div className="text-sm text-slate-500">Docker Services</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-red-600">{stats.totalSecurityTools}</div>
|
|
<div className="text-sm text-slate-500">Security Tools</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-emerald-600">{stats.totalPython}</div>
|
|
<div className="text-sm text-slate-500">Python</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-sky-600">{stats.totalGo}</div>
|
|
<div className="text-sm text-slate-500">Go</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-lime-600">{stats.totalNode}</div>
|
|
<div className="text-sm text-slate-500">Node.js</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-blue-600">{stats.databases}</div>
|
|
<div className="text-sm text-slate-500">Datenbanken</div>
|
|
</div>
|
|
<div className="bg-white rounded-lg shadow p-4">
|
|
<div className="text-3xl font-bold text-green-600">{stats.services}</div>
|
|
<div className="text-sm text-slate-500">App Services</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<div className="bg-white rounded-lg shadow p-4 mb-6">
|
|
<div className="flex flex-col md:flex-row gap-4 items-center justify-between">
|
|
{/* Category Tabs */}
|
|
<div className="flex gap-2">
|
|
{categories.map((cat) => (
|
|
<button
|
|
key={cat.id}
|
|
onClick={() => setActiveCategory(cat.id as CategoryType)}
|
|
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
|
|
activeCategory === cat.id
|
|
? 'bg-primary-600 text-white'
|
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
|
}`}
|
|
>
|
|
{cat.name} ({cat.count})
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Search */}
|
|
<div className="relative w-full md:w-64">
|
|
<input
|
|
type="text"
|
|
placeholder="Suchen..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent"
|
|
/>
|
|
<svg className="absolute left-3 top-2.5 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* SBOM Metadata */}
|
|
{sbomData?.metadata && (
|
|
<div className="bg-slate-50 rounded-lg p-4 mb-6 text-sm">
|
|
<div className="flex flex-wrap gap-6">
|
|
<div>
|
|
<span className="text-slate-500">Format:</span>
|
|
<span className="ml-2 font-medium">{sbomData.bomFormat} {sbomData.specVersion}</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-slate-500">Generiert:</span>
|
|
<span className="ml-2 font-medium">
|
|
{sbomData.metadata.timestamp ? new Date(sbomData.metadata.timestamp).toLocaleString('de-DE') : '-'}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<span className="text-slate-500">Anwendung:</span>
|
|
<span className="ml-2 font-medium">
|
|
{sbomData.metadata.component?.name} v{sbomData.metadata.component?.version}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Components Table */}
|
|
{loading ? (
|
|
<div className="flex items-center justify-center py-12">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
|
<span className="ml-3 text-gray-600">Lade SBOM...</span>
|
|
</div>
|
|
) : (
|
|
<div className="bg-white rounded-lg shadow overflow-hidden">
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Komponente</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Version</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kategorie</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Port</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Lizenz</th>
|
|
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Source</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{filteredComponents.map((component, idx) => {
|
|
// Get license from either the new license field or the old licenses array
|
|
const licenseId = component.license || component.licenses?.[0]?.license?.id
|
|
|
|
return (
|
|
<tr key={idx} className="hover:bg-gray-50">
|
|
<td className="px-4 py-4">
|
|
<div>
|
|
<div className="text-sm font-medium text-gray-900">{component.name}</div>
|
|
{component.description && (
|
|
<div className="text-xs text-gray-500 max-w-xs truncate">{component.description}</div>
|
|
)}
|
|
</div>
|
|
</td>
|
|
<td className="px-4 py-4 whitespace-nowrap">
|
|
<span className="text-sm font-mono text-gray-900">{component.version}</span>
|
|
</td>
|
|
<td className="px-4 py-4 whitespace-nowrap">
|
|
<span className={`px-2 py-1 text-xs font-medium rounded ${getCategoryColor(component.category)}`}>
|
|
{component.category || component.type}
|
|
</span>
|
|
</td>
|
|
<td className="px-4 py-4 whitespace-nowrap">
|
|
{component.port ? (
|
|
<span className="text-sm font-mono text-gray-600">{component.port}</span>
|
|
) : (
|
|
<span className="text-sm text-gray-400">-</span>
|
|
)}
|
|
</td>
|
|
<td className="px-4 py-4 whitespace-nowrap">
|
|
{licenseId ? (
|
|
<span className={`px-2 py-1 text-xs font-medium rounded ${getLicenseColor(licenseId)}`}>
|
|
{licenseId}
|
|
</span>
|
|
) : (
|
|
<span className="text-sm text-gray-400">-</span>
|
|
)}
|
|
</td>
|
|
<td className="px-4 py-4 whitespace-nowrap">
|
|
{component.sourceUrl && component.sourceUrl !== '-' ? (
|
|
<a
|
|
href={component.sourceUrl}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-primary-600 hover:text-primary-800 text-sm flex items-center gap-1"
|
|
>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
GitHub
|
|
</a>
|
|
) : (
|
|
<span className="text-sm text-gray-400">-</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
)
|
|
})}
|
|
</tbody>
|
|
</table>
|
|
|
|
{filteredComponents.length === 0 && (
|
|
<div className="p-8 text-center text-gray-500">
|
|
Keine Komponenten gefunden.
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Export Button */}
|
|
<div className="mt-6 flex justify-end">
|
|
<button
|
|
onClick={() => {
|
|
const data = JSON.stringify({
|
|
...sbomData,
|
|
infrastructure: INFRASTRUCTURE_COMPONENTS
|
|
}, null, 2)
|
|
const blob = new Blob([data], { type: 'application/json' })
|
|
const url = URL.createObjectURL(blob)
|
|
const a = document.createElement('a')
|
|
a.href = url
|
|
a.download = `breakpilot-sbom-${new Date().toISOString().split('T')[0]}.json`
|
|
a.click()
|
|
}}
|
|
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors flex items-center gap-2"
|
|
>
|
|
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
|
</svg>
|
|
SBOM exportieren (JSON)
|
|
</button>
|
|
</div>
|
|
</AdminLayout>
|
|
)
|
|
}
|