'use client' /** * SBOM (Software Bill of Materials) Admin Page */ import { useState, useEffect } from 'react' import AdminLayout from '@/components/admin/AdminLayout' import { Component, SBOMData, CategoryType, INFRASTRUCTURE_COMPONENTS, SECURITY_TOOLS, PYTHON_PACKAGES, GO_MODULES, NODE_PACKAGES, } from './_components/sbom-data' import { SBOMTable } from './_components/SBOMTable' export default function SBOMPage() { const [sbomData, setSbomData] = useState(null) const [loading, setLoading] = useState(true) const [activeCategory, setActiveCategory] = useState('all') const [searchTerm, setSearchTerm] = useState('') const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000' useEffect(() => { const loadSBOM = async () => { setLoading(true) try { const res = await fetch(`${BACKEND_URL}/api/v1/security/sbom`); if (res.ok) setSbomData(await res.json()) } catch (error) { console.error('Failed to load SBOM:', error) } finally { setLoading(false) } } loadSBOM() }, []) const getFilteredComponents = () => { let components: Component[] 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 else { components = [ ...INFRASTRUCTURE_COMPONENTS.map(c => ({ ...c, category: c.category || 'infrastructure' })), ...SECURITY_TOOLS.map(c => ({ ...c, category: c.category || 'security-tool' })), ...PYTHON_PACKAGES.map(c => ({ ...c, category: 'python' })), ...GO_MODULES.map(c => ({ ...c, category: 'go' })), ...NODE_PACKAGES.map(c => ({ ...c, category: 'nodejs' })), ...(sbomData?.components || []).map(c => ({ ...c, category: 'python' })), ] } 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 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, } 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 }, ] return ( {/* Stats Cards */}
{[ { v: stats.totalAll, l: 'Komponenten Total', c: 'text-slate-800' }, { v: stats.totalInfra, l: 'Docker Services', c: 'text-purple-600' }, { v: stats.totalSecurityTools, l: 'Security Tools', c: 'text-red-600' }, { v: stats.totalPython, l: 'Python', c: 'text-emerald-600' }, { v: stats.totalGo, l: 'Go', c: 'text-sky-600' }, { v: stats.totalNode, l: 'Node.js', c: 'text-lime-600' }, { v: stats.databases, l: 'Datenbanken', c: 'text-blue-600' }, { v: stats.services, l: 'App Services', c: 'text-green-600' }, ].map((s, i) => (
{s.v}
{s.l}
))}
{/* Filters */}
{categories.map((cat) => ( ))}
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" />
{/* SBOM Metadata */} {sbomData?.metadata && (
Format:{sbomData.bomFormat} {sbomData.specVersion}
Generiert:{sbomData.metadata.timestamp ? new Date(sbomData.metadata.timestamp).toLocaleString('de-DE') : '-'}
Anwendung:{sbomData.metadata.component?.name} v{sbomData.metadata.component?.version}
)} {/* Export Button */}
) }