main
vue 130 lines 4.7 KB
Raw
1 <template>
2 <div class="h-full">
3 <CardEntity
4 clickable
5 :embedded
6 class="h-full"
7 main-box-class="grow"
8 card-entity-wrapper-class="h-full"
9 header-box-class="flex-nowrap! items-start"
10 :class="`${getSeverityBorderClass(vulnerability.severity)} transition-all duration-300`"
11 @click.stop="showDetails = true"
12 >
13 <template #headerMain>{{ vulnerability.cve_id }}</template>
14 <template #headerExtra>
15 <VulnerabilitySeverityBadge :severity="vulnerability.severity" :size="14" />
16 </template>
17 <template #default>
18 <div class="flex flex-col gap-4">
19 <div class="leading-snug font-medium">
20 {{ vulnerability.title }}
21 </div>
22 <div class="text-secondary flex flex-col gap-0.5 text-sm">
23 <div class="flex items-center gap-2">
24 <Icon :name="HostIcon" :size="14" />
25 <span>{{ vulnerability.agent_name }}</span>
26 </div>
27 <div v-if="vulnerability.package_name" class="mt-1 flex items-center gap-2">
28 <Icon :name="PackageIcon" :size="14" />
29 <span>
30 {{ vulnerability.package_name
31 }}{{ vulnerability.package_version ? ` (${vulnerability.package_version})` : "" }}
32 </span>
33 </div>
34 <div v-if="vulnerability.package_architecture" class="mt-1 flex items-center gap-2">
35 <Icon :name="ArchitectureIcon" :size="14" />
36 <span>
37 {{ vulnerability.package_architecture }}
38 </span>
39 </div>
40 </div>
41 </div>
42 </template>
43 <template #footerMain>
44 <div class="flex flex-wrap items-center gap-2">
45 <Badge v-if="vulnerability.customer_code" class="text-xs">
46 <template #value>
47 <code
48 class="text-primary cursor-pointer"
49 @click.stop="routeCustomer({ code: vulnerability.customer_code }).navigate()"
50 >
51 customer #{{ vulnerability.customer_code }}
52 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
53 </code>
54 </template>
55 </Badge>
56
57 <Badge v-if="vulnerability.base_score" color="primary" type="splitted" class="text-xs">
58 <template #label>CVSS</template>
59 <template #value>{{ vulnerability.base_score }}</template>
60 </Badge>
61
62 <Badge v-if="vulnerability.epss_score" color="warning" type="splitted" class="text-xs">
63 <template #label>EPSS</template>
64 <template #value>{{ parseFloat(vulnerability.epss_score).toFixed(3) }}</template>
65 </Badge>
66
67 <Badge v-if="vulnerability.epss_percentile" color="warning" type="splitted" class="text-xs">
68 <template #label>EPSS Pct</template>
69 <template #value>{{ parseFloat(vulnerability.epss_percentile).toFixed(1) }}%</template>
70 </Badge>
71 </div>
72 </template>
73 <template #footerExtra>
74 <div class="text-tertiary text-xs">
75 {{ formatDate(vulnerability.detected_at, dFormats.datetime) }}
76 </div>
77 </template>
78 </CardEntity>
79
80 <!-- Vulnerability Details Modal -->
81 <n-modal
82 v-model:show="showDetails"
83 preset="card"
84 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(700px, 90vh)' }"
85 :title="`Vulnerability: ${vulnerability.cve_id}`"
86 :bordered="false"
87 content-class="p-0!"
88 segmented
89 >
90 <VulnerabilityCardContent :vulnerability />
91 </n-modal>
92 </div>
93 </template>
94
95 <script setup lang="ts">
96 // TODO-FE: review VulnerabilityCard (this component)
97 import type { VulnerabilitySearchItem } from "@/types/vulnerabilities.d"
98 import { NModal } from "naive-ui"
99 import { ref } from "vue"
100 import Badge from "@/components/common/Badge.vue"
101 import CardEntity from "@/components/common/cards/CardEntity.vue"
102 import Icon from "@/components/common/Icon.vue"
103 import { useNavigation } from "@/composables/useNavigation"
104 import { useSettingsStore } from "@/stores/settings"
105 import { VulnerabilitySeverity } from "@/types/vulnerabilities.d"
106 import { formatDate } from "@/utils/format"
107 import VulnerabilityCardContent from "./VulnerabilityCardContent.vue"
108 import VulnerabilitySeverityBadge from "./VulnerabilitySeverityBadge.vue"
109
110 const { vulnerability } = defineProps<{ vulnerability: VulnerabilitySearchItem; embedded?: boolean }>()
111
112 const { routeCustomer } = useNavigation()
113 const dFormats = useSettingsStore().dateFormat
114
115 const showDetails = ref(false)
116 const HostIcon = "carbon:bare-metal-server"
117 const LinkIcon = "carbon:launch"
118 const PackageIcon = "carbon:package"
119 const ArchitectureIcon = "carbon:chip"
120
121 function getSeverityBorderClass(severity: string): string {
122 const borderMap: Record<string, string> = {
123 [VulnerabilitySeverity.Critical]: "ring-1 ring-error/30 hover:ring-error/80",
124 [VulnerabilitySeverity.High]: "ring-1 ring-orange-500/30 hover:ring-orange-500/80",
125 [VulnerabilitySeverity.Medium]: "ring-1 ring-warning/30 hover:ring-warning/80",
126 [VulnerabilitySeverity.Low]: "ring-1 ring-info/30 hover:ring-info/80"
127 }
128 return borderMap[severity] || ""
129 }
130 </script>