import { useEffect, useState } from 'react' import { useParams, Link } from 'react-router-dom' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { apiClient, Image, Vulnerability } from '@/lib/api' import { ArrowLeft, Container, Shield, AlertTriangle, RefreshCw, Calendar, ExternalLink } from 'lucide-react' const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, unspecified: 4 } export function ImageDetail() { const { id } = useParams<{ id: string }>() const [image, setImage] = useState(null) const [vulnerabilities, setVulnerabilities] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const fetchImageData = async () => { if (!id) return try { setIsLoading(true) setError(null) // Fetch image details and vulnerabilities in parallel const [imageData, vulnerabilitiesData] = await Promise.all([ apiClient.getImage(parseInt(id)), apiClient.getImageVulnerabilities(parseInt(id)) ]) setImage(imageData) // Sort vulnerabilities by severity const sortedVulnerabilities = vulnerabilitiesData.sort((a, b) => { const severityA = severityOrder[a.severity as keyof typeof severityOrder] ?? 5 const severityB = severityOrder[b.severity as keyof typeof severityOrder] ?? 5 return severityA - severityB }) setVulnerabilities(sortedVulnerabilities) } catch (error) { console.error('Failed to fetch image data:', error) setError('Failed to load image information') } finally { setIsLoading(false) } } useEffect(() => { fetchImageData() }, [id]) if (isLoading) { return (
) } if (error || !image) { return (

{error || 'Image not found'}

) } const vulnerabilityCounts = vulnerabilities.reduce((acc, vuln) => { acc[vuln.severity] = (acc[vuln.severity] || 0) + 1 return acc }, {} as Record) const formatCVSSScore = (score: string | null): string => { if (!score) return 'N/A' const numScore = parseFloat(score) if (isNaN(numScore)) return score return numScore.toFixed(1) } const getCVSSColor = (score: string | null): string => { if (!score) return 'text-gray-500' const numScore = parseFloat(score) if (isNaN(numScore)) return 'text-gray-500' if (numScore >= 9.0) return 'text-red-600 font-bold' if (numScore >= 7.0) return 'text-orange-600 font-semibold' if (numScore >= 4.0) return 'text-yellow-600' if (numScore > 0) return 'text-blue-600' return 'text-gray-500' } return (
{/* Header */}

{image.image_name}

{image.full_image_name}

{image.is_active ? "Active" : "Inactive"}
{/* Image Information */}
Tag
{image.tag || 'latest'}
Registry
{image.registry || 'Docker Hub'}
Usage Count
{image.usage_count || 0}

Files using this image

Last Seen
{new Date(image.last_seen).toLocaleString()}
{/* Vulnerability Summary */} Vulnerability Summary Security vulnerabilities found in this Docker image {vulnerabilities.length > 0 ? (
{vulnerabilityCounts.critical || 0}
Critical
{vulnerabilityCounts.high || 0}
High
{vulnerabilityCounts.medium || 0}
Medium
{vulnerabilityCounts.low || 0}
Low
{vulnerabilityCounts.unspecified || 0}
Unspecified
) : (
No vulnerabilities found
)}
{/* Vulnerabilities List */} Vulnerability Details Detailed list of all vulnerabilities found in this image {vulnerabilities.length > 0 ? ( CVE ID Severity CVSS Score Title Fixed Version Published Scan Job {vulnerabilities.map((vulnerability) => ( {vulnerability.vulnerability_id} {vulnerability.severity.charAt(0).toUpperCase() + vulnerability.severity.slice(1)} {formatCVSSScore(vulnerability.cvss_score)}
{vulnerability.title || 'No title available'}
{vulnerability.description && (
{vulnerability.description}
)}
{vulnerability.fixed_version ? ( {vulnerability.fixed_version} ) : ( Not available )} {vulnerability.published_date ? new Date(vulnerability.published_date).toLocaleDateString() : 'Unknown' } {vulnerability.scan_job_id ? ( #{vulnerability.scan_job_id} ) : ( - )}
))}
) : (
No vulnerabilities found for this image
)}
) }