main
vue 222 lines 6.33 KB
Raw
1 <template>
2 <div>
3 <CardEntity hoverable clickable @click.stop="showDetails = true">
4 <template #headerMain>#{{ asset.asset_id }} - {{ asset.asset_uuid }}</template>
5 <template #default>
6 <div class="flex flex-col gap-1">
7 {{ asset.asset_name }}
8
9 <p v-if="asset.asset_description" class="text-sm">
10 <template v-if="isUrlLike(asset.asset_description)">
11 <a
12 :href="asset.asset_description"
13 class="asset-url"
14 target="_blank"
15 alt="asset url"
16 rel="nofollow noopener noreferrer"
17 >
18 <code class="text-primary">
19 <span>
20 {{ asset.asset_description }}
21 </span>
22 <Icon :name="LinkIcon" :size="14" class="relative top-0.5" />
23 </code>
24 </a>
25 </template>
26 <template v-else>
27 {{ excerpt }}
28 </template>
29 </p>
30 </div>
31 </template>
32 <template #mainExtra>
33 <div class="flex flex-wrap items-center gap-3">
34 <Badge v-if="asset.date_added" type="splitted" color="primary">
35 <template #iconLeft>
36 <Icon :name="ClockIcon" :size="14" />
37 </template>
38 <template #label>Added</template>
39 <template #value>
40 {{ formatDate(asset.date_added) }}
41 </template>
42 </Badge>
43 <Badge v-if="asset.date_update" type="splitted" color="primary">
44 <template #iconLeft>
45 <Icon :name="ClockIcon" :size="14" />
46 </template>
47 <template #label>Updated</template>
48 <template #value>
49 {{ formatDate(asset.date_update) }}
50 </template>
51 </Badge>
52 <Badge type="active" class="cursor-pointer" @click.stop="routeAgent(asset.asset_tags).navigate()">
53 <template #iconRight>
54 <Icon :name="LinkIcon" :size="14" />
55 </template>
56 <template #label>Agent: {{ asset.asset_tags }}</template>
57 </Badge>
58 </div>
59 </template>
60 </CardEntity>
61
62 <n-modal
63 v-model:show="showDetails"
64 preset="card"
65 content-class="p-0!"
66 :style="{ maxWidth: 'min(800px, 90vw)', minHeight: 'min(600px, 90vh)', overflow: 'hidden' }"
67 :title="`Assets #${asset.asset_id} - ${asset.asset_uuid}`"
68 :bordered="false"
69 segmented
70 >
71 <n-tabs type="line" animated :tabs-padding="24">
72 <n-tab-pane name="Info" tab="Info" display-directive="show">
73 <div v-if="properties" class="grid-auto-fit-200 grid gap-2 p-6 pt-3">
74 <CardKV v-for="(value, key) of properties" :key>
75 <template #key>
76 {{ key }}
77 </template>
78 <template #value>
79 <template v-if="key === 'asset_tags'">
80 <code
81 v-if="value && value !== '-'"
82 class="text-primary cursor-pointer"
83 @click.stop="routeAgent(value).navigate()"
84 >
85 {{ value }}
86 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
87 </code>
88 <span v-else>-</span>
89 </template>
90 <template v-else>
91 {{ value ?? "-" }}
92 </template>
93 </template>
94 </CardKV>
95 </div>
96 </n-tab-pane>
97 <n-tab-pane name="Type" tab="Type" display-directive="show">
98 <div v-if="assetType" class="grid-auto-fit-250 grid gap-2 p-6 pt-3">
99 <CardKV v-for="(value, key) of assetType" :key>
100 <template #key>
101 {{ key }}
102 </template>
103 <template #value>
104 {{ value || "-" }}
105 </template>
106 </CardKV>
107 </div>
108 </n-tab-pane>
109 <n-tab-pane name="Description" tab="Description" display-directive="show">
110 <div class="p-6 pt-3">
111 <template v-if="isUrlLike(asset.asset_description)">
112 <a
113 :href="asset.asset_description"
114 class="asset-url"
115 target="_blank"
116 alt="asset url"
117 rel="nofollow noopener noreferrer"
118 >
119 {{ asset.asset_description }}
120 </a>
121 </template>
122 <template v-else>
123 <div v-html="descriptionFull"></div>
124 </template>
125 </div>
126 </n-tab-pane>
127 <n-tab-pane name="Artifact Collection" tab="Artifact Collection" display-directive="show:lazy">
128 <div class="p-7 pt-2">
129 <ArtifactsCollect
130 :hostname="asset.asset_name"
131 :artifacts-filter="{ hostname: asset.asset_name }"
132 hide-hostname-field
133 velociraptor-id="string"
134 hide-velociraptor-id-field
135 />
136 </div>
137 </n-tab-pane>
138 </n-tabs>
139 </n-modal>
140 </div>
141 </template>
142
143 <script setup lang="ts">
144 import type { SocAlertAsset } from "@/types/soc/asset.d"
145 import _omit from "lodash/omit"
146 import { NModal, NTabPane, NTabs } from "naive-ui"
147 import { computed, defineAsyncComponent, ref } from "vue"
148 import Badge from "@/components/common/Badge.vue"
149 import CardEntity from "@/components/common/cards/CardEntity.vue"
150 import CardKV from "@/components/common/cards/CardKV.vue"
151 import Icon from "@/components/common/Icon.vue"
152 import { useNavigation } from "@/composables/useNavigation"
153 import { useSettingsStore } from "@/stores/settings"
154 import { isUrlLike } from "@/utils"
155 import dayjs from "@/utils/dayjs"
156
157 const { asset } = defineProps<{ asset: SocAlertAsset }>()
158
159 const ArtifactsCollect = defineAsyncComponent(() => import("@/components/artifacts/ArtifactsCollect.vue"))
160
161 const ClockIcon = "carbon:time"
162 const LinkIcon = "carbon:launch"
163 const { routeAgent } = useNavigation()
164 const showDetails = ref(false)
165
166 const dFormats = useSettingsStore().dateFormat
167 const ASSET_NEWLINE_REGEX = /\n/g
168
169 const excerpt = computed(() => {
170 const text = asset.asset_description
171 const truncated = text.split(" ").slice(0, 30).join(" ")
172
173 return truncated + (truncated !== text ? "..." : "")
174 })
175
176 const descriptionFull = computed(() => {
177 const text = asset.asset_description
178
179 return text.replace(ASSET_NEWLINE_REGEX, "<br>") || "Empty"
180 })
181
182 const properties = computed(() => {
183 const props = _omit(asset, ["asset_description", "asset_type"])
184 for (const key in props) {
185 const prop = props[key as keyof typeof props]
186 if (prop && (key === "date_added" || key === "date_update")) {
187 props[key] = formatDate(prop.toString(), true)
188 }
189 }
190 return props
191 })
192
193 const assetType = computed(() => {
194 return asset.asset_type || {}
195 })
196
197 function formatDate(date: string, useSec = false) {
198 const datejs = dayjs(date)
199 if (!datejs.isValid()) return date
200
201 return datejs.format(useSec ? dFormats.datetimesec : dFormats.datetime)
202 }
203 </script>
204
205 <style lang="scss" scoped>
206 .asset-url {
207 display: block;
208
209 code {
210 padding: 8px 12px;
211 display: flex;
212 gap: 10px;
213
214 span {
215 white-space: nowrap;
216 flex-grow: 1;
217 overflow: hidden;
218 text-overflow: ellipsis;
219 }
220 }
221 }
222 </style>