WP-20/21: vulnerability management with Trivy and mail notifications
Trivy scanner adapter (rootfs + image JSON, parsed and deduplicated), findings repository, scan diff that keeps first_seen, marks disappeared findings fixed and skips failed targets, vulnerability_scan job (daily by default), digest mail for new findings at or above a configurable severity, /api/vulnerabilities routes, Vulnerabilities page with severity tiles, filters, details and acknowledge, notification threshold in settings. Deploy script installs Trivy from the Aqua apt repository. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
69
frontend/src/components/FindingTable.vue
Normal file
69
frontend/src/components/FindingTable.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import type { Finding } from '../api/types'
|
||||
|
||||
defineProps<{ findings: Finding[]; canAct: boolean }>()
|
||||
defineEmits<{ status: [f: Finding, status: 'open' | 'acknowledged']; select: [f: Finding] }>()
|
||||
|
||||
const sev: Record<string, string> = {
|
||||
critical: 'bg-red-600 text-white',
|
||||
high: 'bg-orange-500 text-white',
|
||||
medium: 'bg-amber-300 text-amber-900',
|
||||
low: 'bg-gray-200 text-gray-700',
|
||||
unknown: 'bg-gray-100 text-gray-500',
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="border-b border-gray-200 text-gray-500">
|
||||
<tr>
|
||||
<th class="py-2">Severity</th>
|
||||
<th>CVE</th>
|
||||
<th>Package</th>
|
||||
<th>Installed</th>
|
||||
<th>Fixed in</th>
|
||||
<th>Target</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="f in findings"
|
||||
:key="f.id"
|
||||
class="border-b border-gray-100"
|
||||
:class="f.status === 'acknowledged' ? 'text-gray-500' : ''"
|
||||
>
|
||||
<td class="py-1.5">
|
||||
<span class="rounded-full px-2 py-0.5 text-xs font-medium" :class="sev[f.severity]">{{
|
||||
f.severity
|
||||
}}</span>
|
||||
</td>
|
||||
<td class="font-mono">{{ f.cve_id }}</td>
|
||||
<td class="font-mono">{{ f.package }}</td>
|
||||
<td class="font-mono text-xs">{{ f.installed_version }}</td>
|
||||
<td class="font-mono text-xs" :class="f.fixed_version ? 'text-green-700' : 'text-gray-400'">
|
||||
{{ f.fixed_version ?? '–' }}
|
||||
</td>
|
||||
<td class="max-w-xs truncate font-mono text-xs" :title="f.target">{{ f.target }}</td>
|
||||
<td>{{ f.status }}</td>
|
||||
<td class="space-x-3 whitespace-nowrap text-right">
|
||||
<button name="details" class="text-blue-600 hover:underline" @click="$emit('select', f)">
|
||||
Details
|
||||
</button>
|
||||
<button
|
||||
v-if="canAct"
|
||||
name="ack"
|
||||
class="text-blue-600 hover:underline"
|
||||
@click="$emit('status', f, f.status === 'acknowledged' ? 'open' : 'acknowledged')"
|
||||
>
|
||||
{{ f.status === 'acknowledged' ? 'Reopen' : 'Acknowledge' }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="findings.length === 0">
|
||||
<td colspan="8" class="py-6 text-center text-gray-500">No findings.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
Reference in New Issue
Block a user