main
vue 225 lines 6.61 KB
Raw
1 <template>
2 <n-tabs type="line" animated :tabs-padding="24">
3 <n-tab-pane name="Overview" tab="Overview" display-directive="show">
4 <div class="flex flex-col gap-6 p-7 pt-2">
5 <div class="grid-auto-fit-200 grid gap-2">
6 <CardKV>
7 <template #key>Policy ID</template>
8 <template #value>
9 {{ sca.policy_id }}
10 </template>
11 </CardKV>
12
13 <CardKV>
14 <template #key>Agent</template>
15 <template #value>
16 {{ sca.agent_name }}
17 </template>
18 </CardKV>
19
20 <CardKV v-if="sca.customer_code">
21 <template #key>Customer</template>
22 <template #value>
23 <code
24 class="text-primary cursor-pointer"
25 @click.stop="routeCustomer({ code: sca.customer_code }).navigate()"
26 >
27 #{{ sca.customer_code }}
28 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
29 </code>
30 </template>
31 </CardKV>
32
33 <CardKV>
34 <template #key>Compliance Level</template>
35 <template #value>
36 {{ getComplianceLevel(sca.score) }}
37 </template>
38 </CardKV>
39
40 <CardKV>
41 <template #key>Score</template>
42 <template #value>{{ sca.score }}%</template>
43 </CardKV>
44
45 <CardKV>
46 <template #key>Scan Date</template>
47 <template #value>
48 {{ formatDate(sca.end_scan, dFormats.datetime) }}
49 </template>
50 </CardKV>
51 </div>
52
53 <CardKV>
54 <template #key>Description</template>
55 <template #value>
56 {{ sca.description }}
57 </template>
58 </CardKV>
59
60 <CardKV v-if="sca.references">
61 <template #key>References</template>
62 <template #value>
63 <a :href="sca.references" target="_blank" tabindex="-1" class="outline-none">
64 {{ sca.references }}
65 </a>
66 </template>
67 </CardKV>
68 </div>
69 </n-tab-pane>
70 <n-tab-pane name="Statistics & Progress" tab="Statistics & Progress" display-directive="show">
71 <div class="flex flex-col gap-4 p-7 pt-2">
72 <div class="flex items-center gap-2 text-sm">
73 <Icon :name="InfoIcon" :size="14" />
74 Total:
75 <code>{{ sca.total_checks }}</code>
76 </div>
77
78 <div class="grid-auto-fit-200 grid gap-2">
79 <n-card content-class="flex flex-col gap-2" size="small">
80 <div class="flex items-center justify-between gap-2 whitespace-nowrap">
81 <div class="flex items-center gap-2">
82 <Icon :name="PassIcon" :size="20" class="text-success" />
83 <span class="text-lg">Passed</span>
84 </div>
85 <div class="font-mono text-lg font-bold">{{ sca.pass }}</div>
86 </div>
87 <n-progress
88 type="line"
89 indicator-placement="inside"
90 :percentage="getPercentage(sca.pass)"
91 color="var(--success-color)"
92 class="custom-progress"
93 />
94 </n-card>
95
96 <n-card content-class="flex flex-col gap-2" size="small">
97 <div class="flex items-center justify-between gap-2 whitespace-nowrap">
98 <div class="flex items-center gap-2">
99 <Icon :name="FailIcon" :size="20" class="text-error" />
100 <span class="text-lg">Failed</span>
101 </div>
102 <div class="font-mono text-lg font-bold">{{ sca.fail }}</div>
103 </div>
104 <n-progress
105 type="line"
106 indicator-placement="inside"
107 :percentage="getPercentage(sca.fail)"
108 color="var(--error-color)"
109 class="custom-progress"
110 />
111 </n-card>
112
113 <n-card v-if="sca.invalid > 0" content-class="flex flex-col gap-2" size="small">
114 <div class="flex items-center justify-between gap-2 whitespace-nowrap">
115 <div class="flex items-center gap-2">
116 <Icon :name="InvalidIcon" :size="20" class="text-warning" />
117 <span class="text-lg">Invalid</span>
118 </div>
119 <div class="font-mono text-lg font-bold">{{ sca.invalid }}</div>
120 </div>
121 <n-progress
122 type="line"
123 indicator-placement="inside"
124 :percentage="getPercentage(sca.invalid)"
125 color="var(--warning-color)"
126 class="custom-progress"
127 />
128 </n-card>
129 </div>
130
131 <n-card class="bg-secondary! overflow-hidden" title="Progress">
132 <n-progress
133 type="line"
134 indicator-placement="inside"
135 :percentage="sca.score"
136 color="var(--success-color)"
137 class="custom-progress"
138 />
139 </n-card>
140 </div>
141 </n-tab-pane>
142 <n-tab-pane name="Scan Information" tab="Scan Information" display-directive="show">
143 <div class="flex flex-col gap-4 p-7 pt-2">
144 <n-card class="bg-secondary! overflow-hidden">
145 <div class="flex flex-wrap justify-between gap-8">
146 <n-statistic
147 label="Scan Started"
148 :value="`${formatDate(sca.start_scan, dFormats.datetime)}`"
149 tabular-nums
150 />
151 <n-statistic
152 label="Scan Completed"
153 :value="`${formatDate(sca.end_scan, dFormats.datetime)}`"
154 tabular-nums
155 />
156 <n-statistic label="Scan Duration" :value="`${getScanDuration()}`" tabular-nums />
157 </div>
158 </n-card>
159
160 <CardKV v-if="sca.hash_file">
161 <template #key>Hash File</template>
162 <template #value>
163 {{ sca.hash_file }}
164 </template>
165 </CardKV>
166 </div>
167 </n-tab-pane>
168 </n-tabs>
169 </template>
170
171 <script setup lang="ts">
172 import type { AgentScaOverviewItem } from "@/types/sca.d"
173 import _toNumber from "lodash/toNumber"
174 import { NCard, NProgress, NStatistic, NTabPane, NTabs } from "naive-ui"
175 import CardKV from "@/components/common/cards/CardKV.vue"
176 import Icon from "@/components/common/Icon.vue"
177 import { useNavigation } from "@/composables/useNavigation"
178 import { useSettingsStore } from "@/stores/settings"
179 import { formatDate } from "@/utils/format"
180 import { getComplianceLevel } from "./utils"
181
182 const { sca } = defineProps<{ sca: AgentScaOverviewItem }>()
183
184 const dFormats = useSettingsStore().dateFormat
185 const { routeCustomer } = useNavigation()
186
187 const InfoIcon = "carbon:information"
188 const LinkIcon = "carbon:launch"
189 const PassIcon = "carbon:checkmark-filled"
190 const FailIcon = "carbon:close-filled"
191 const InvalidIcon = "carbon:warning-alt"
192
193 function getPercentage(count: number): number {
194 if (sca.total_checks === 0) return 0
195 return _toNumber(((count / sca.total_checks) * 100).toFixed(1))
196 }
197
198 function getScanDuration(): string {
199 const start = new Date(sca.start_scan)
200 const end = new Date(sca.end_scan)
201 const durationMs = end.getTime() - start.getTime()
202 const durationMinutes = Math.floor(durationMs / 60000)
203 const durationSeconds = Math.floor((durationMs % 60000) / 1000)
204
205 if (durationMinutes > 0) {
206 return `${durationMinutes}m ${durationSeconds}s`
207 }
208 return `${durationSeconds}s`
209 }
210 </script>
211
212 <style lang="scss" scoped>
213 .custom-progress {
214 :deep() {
215 .n-progress-graph-line-indicator {
216 text-shadow:
217 0px 0px 1px black,
218 0px 0px 2px black,
219 0px 0px 3px black;
220 font-weight: bold;
221 color: white !important;
222 }
223 }
224 }
225 </style>