- 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.
181 lines
6.0 KiB
TypeScript
181 lines
6.0 KiB
TypeScript
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>
|
|
)
|
|
} |