feat: Implement API client and dashboard functionality

- Added api.ts to handle API requests and define data models for Project, Image, Vulnerability, IgnoreRule, ScanJob, and DashboardStats.
- Created Dashboard component to display statistics and initiate scans for projects and vulnerabilities.
- Developed IgnoreRules component for managing ignore rules with filtering options.
- Implemented Images component to list discovered Docker images.
- Added Projects component to display monitored GitLab projects.
- Created ScanJobs component to show history and status of scanning operations.
- Developed Vulnerabilities component to report security vulnerabilities found in Docker images.
- Removed BrowserRouter from main.tsx as routing is not currently implemented.
This commit is contained in:
JSC
2025-07-10 22:57:22 +02:00
parent 8fe6ee937b
commit 181b3e2878
17 changed files with 1931 additions and 8 deletions

181
src/pages/IgnoreRules.tsx Normal file
View File

@@ -0,0 +1,181 @@
import { useEffect, useState } from 'react'
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, IgnoreRule } from '@/lib/api'
import { Settings, RefreshCw, Plus, Trash2 } from 'lucide-react'
const ignoreTypeColors = {
image: 'default',
file: 'secondary',
project: 'outline',
} as const
export function IgnoreRules() {
const [ignoreRules, setIgnoreRules] = useState<IgnoreRule[]>([])
const [isLoading, setIsLoading] = useState(true)
const [selectedType, setSelectedType] = useState<string>('')
const fetchIgnoreRules = async (ignoreType?: string) => {
try {
const data = await apiClient.getIgnoreRules({
ignore_type: ignoreType || undefined
})
setIgnoreRules(data)
} catch (error) {
console.error('Failed to fetch ignore rules:', error)
} finally {
setIsLoading(false)
}
}
const handleDeleteRule = async (id: number) => {
try {
await apiClient.deleteIgnoreRule(id)
await fetchIgnoreRules(selectedType)
} catch (error) {
console.error('Failed to delete ignore rule:', error)
}
}
useEffect(() => {
fetchIgnoreRules(selectedType)
}, [selectedType])
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<RefreshCw className="h-8 w-8 animate-spin" />
</div>
)
}
return (
<div className="space-y-6">
<div className="flex justify-between items-center">
<div>
<h1 className="text-3xl font-bold tracking-tight">Ignore Rules</h1>
<p className="text-gray-600">
Manage rules to exclude projects, files, or images from scanning
</p>
</div>
<Button>
<Plus className="h-4 w-4 mr-2" />
Add Rule
</Button>
</div>
{/* Type Filter */}
<Card>
<CardHeader>
<CardTitle>Filter by Type</CardTitle>
</CardHeader>
<CardContent>
<div className="flex space-x-2">
<button
onClick={() => setSelectedType('')}
className={`px-3 py-1 rounded text-sm ${
selectedType === ''
? 'bg-blue-100 text-blue-800'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
All
</button>
{Object.keys(ignoreTypeColors).map((type) => (
<button
key={type}
onClick={() => setSelectedType(type)}
className={`px-3 py-1 rounded text-sm ${
selectedType === type
? 'bg-blue-100 text-blue-800'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
<Badge variant={ignoreTypeColors[type as keyof typeof ignoreTypeColors]}>
{type.charAt(0).toUpperCase() + type.slice(1)}
</Badge>
</button>
))}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center">
<Settings className="h-5 w-5 mr-2" />
Active Ignore Rules
</CardTitle>
<CardDescription>
Rules that exclude items from scanning and vulnerability checking
</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>Type</TableHead>
<TableHead>Target</TableHead>
<TableHead>Reason</TableHead>
<TableHead>Created By</TableHead>
<TableHead>Created</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{ignoreRules.map((rule) => (
<TableRow key={rule.id}>
<TableCell>
<Badge variant={ignoreTypeColors[rule.ignore_type as keyof typeof ignoreTypeColors]}>
{rule.ignore_type.charAt(0).toUpperCase() + rule.ignore_type.slice(1)}
</Badge>
</TableCell>
<TableCell>
<div className="font-mono text-sm max-w-md break-words">
{rule.target}
</div>
</TableCell>
<TableCell>
<div className="max-w-md">
{rule.reason || (
<span className="text-gray-400 italic">No reason provided</span>
)}
</div>
</TableCell>
<TableCell>
{rule.created_by || (
<span className="text-gray-400">Unknown</span>
)}
</TableCell>
<TableCell className="text-sm text-gray-600">
{new Date(rule.created_at).toLocaleDateString()}
</TableCell>
<TableCell>
<Button
variant="outline"
size="sm"
onClick={() => handleDeleteRule(rule.id)}
className="text-red-600 hover:text-red-800"
>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{ignoreRules.length === 0 && (
<div className="text-center py-8 text-gray-500">
{selectedType
? `No ${selectedType} ignore rules found.`
: 'No ignore rules configured. Add rules to exclude items from scanning.'
}
</div>
)}
</CardContent>
</Card>
</div>
)
}