mihari 5.2.3 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -1
  3. data/README.md +0 -10
  4. data/Rakefile +7 -1
  5. data/build_frontend.sh +2 -10
  6. data/frontend/.eslintrc.cjs +22 -0
  7. data/frontend/.gitignore +31 -0
  8. data/frontend/.prettierrc.json +8 -0
  9. data/frontend/README.md +3 -0
  10. data/frontend/env.d.ts +5 -0
  11. data/frontend/index.html +21 -0
  12. data/frontend/package-lock.json +8650 -0
  13. data/frontend/package.json +64 -0
  14. data/frontend/public/favicon.ico +0 -0
  15. data/frontend/scripts/swagger_doc_to_yaml.rb +23 -0
  16. data/frontend/src/App.vue +27 -0
  17. data/frontend/src/api-helper.ts +111 -0
  18. data/frontend/src/api.ts +105 -0
  19. data/frontend/src/components/ErrorMessage.vue +32 -0
  20. data/frontend/src/components/Loading.vue +15 -0
  21. data/frontend/src/components/Navbar.vue +42 -0
  22. data/frontend/src/components/Pagination.vue +119 -0
  23. data/frontend/src/components/alert/Alert.vue +87 -0
  24. data/frontend/src/components/alert/Alerts.vue +64 -0
  25. data/frontend/src/components/alert/AlertsWithPagination.vue +91 -0
  26. data/frontend/src/components/alert/AlertsWrapper.vue +134 -0
  27. data/frontend/src/components/alert/Form.vue +184 -0
  28. data/frontend/src/components/artifact/AS.vue +29 -0
  29. data/frontend/src/components/artifact/Artifact.vue +304 -0
  30. data/frontend/src/components/artifact/ArtifactTag.vue +64 -0
  31. data/frontend/src/components/artifact/ArtifactTags.vue +29 -0
  32. data/frontend/src/components/artifact/ArtifactWrapper.vue +59 -0
  33. data/frontend/src/components/artifact/CPEs.vue +23 -0
  34. data/frontend/src/components/artifact/DnsRecords.vue +38 -0
  35. data/frontend/src/components/artifact/Ports.vue +23 -0
  36. data/frontend/src/components/artifact/ReverseDnsNames.vue +31 -0
  37. data/frontend/src/components/artifact/Tags.vue +29 -0
  38. data/frontend/src/components/artifact/WhoisRecord.vue +47 -0
  39. data/frontend/src/components/config/Configs.vue +65 -0
  40. data/frontend/src/components/config/ConfigsWrapper.vue +34 -0
  41. data/frontend/src/components/link/Link.vue +32 -0
  42. data/frontend/src/components/link/Links.vue +42 -0
  43. data/frontend/src/components/rule/EditRule.vue +74 -0
  44. data/frontend/src/components/rule/EditRuleWrapper.vue +50 -0
  45. data/frontend/src/components/rule/Form.vue +160 -0
  46. data/frontend/src/components/rule/InputForm.vue +86 -0
  47. data/frontend/src/components/rule/NewRule.vue +60 -0
  48. data/frontend/src/components/rule/Rule.vue +106 -0
  49. data/frontend/src/components/rule/RuleWrapper.vue +55 -0
  50. data/frontend/src/components/rule/Rules.vue +84 -0
  51. data/frontend/src/components/rule/RulesWrapper.vue +127 -0
  52. data/frontend/src/components/rule/YAML.vue +44 -0
  53. data/frontend/src/components/tag/Tag.vue +65 -0
  54. data/frontend/src/components/tag/Tags.vue +37 -0
  55. data/frontend/src/countries.ts +350 -0
  56. data/frontend/src/index.ts +20 -0
  57. data/frontend/src/links/anyrun.ts +19 -0
  58. data/frontend/src/links/base.ts +14 -0
  59. data/frontend/src/links/censys.ts +20 -0
  60. data/frontend/src/links/crtsh.ts +20 -0
  61. data/frontend/src/links/dnslytics.ts +38 -0
  62. data/frontend/src/links/greynoise.ts +20 -0
  63. data/frontend/src/links/index.ts +40 -0
  64. data/frontend/src/links/intezer.ts +20 -0
  65. data/frontend/src/links/otx.ts +33 -0
  66. data/frontend/src/links/securitytrails.ts +38 -0
  67. data/frontend/src/links/shodan.ts +20 -0
  68. data/frontend/src/links/urlscan.ts +50 -0
  69. data/frontend/src/links/virustotal.ts +72 -0
  70. data/frontend/src/main.ts +11 -0
  71. data/frontend/src/router/index.ts +57 -0
  72. data/frontend/src/rule.ts +14 -0
  73. data/frontend/src/shims-vue.d.ts +6 -0
  74. data/frontend/src/swagger.yaml +737 -0
  75. data/frontend/src/types.ts +188 -0
  76. data/frontend/src/utils.ts +54 -0
  77. data/frontend/src/views/Alerts.vue +20 -0
  78. data/frontend/src/views/Artifact.vue +44 -0
  79. data/frontend/src/views/Configs.vue +20 -0
  80. data/frontend/src/views/EditRule.vue +44 -0
  81. data/frontend/src/views/NewRule.vue +26 -0
  82. data/frontend/src/views/Rule.vue +44 -0
  83. data/frontend/src/views/Rules.vue +20 -0
  84. data/frontend/tests/utils.spec.ts +9 -0
  85. data/frontend/tsconfig.app.json +21 -0
  86. data/frontend/tsconfig.json +14 -0
  87. data/frontend/tsconfig.node.json +13 -0
  88. data/frontend/tsconfig.vitest.json +12 -0
  89. data/frontend/vite.config.ts +24 -0
  90. data/frontend/vitest.config.ts +21 -0
  91. data/lefthook.yml +12 -0
  92. data/lib/mihari/analyzers/base.rb +63 -12
  93. data/lib/mihari/analyzers/binaryedge.rb +10 -15
  94. data/lib/mihari/analyzers/censys.rb +12 -15
  95. data/lib/mihari/analyzers/circl.rb +10 -10
  96. data/lib/mihari/analyzers/crtsh.rb +10 -6
  97. data/lib/mihari/analyzers/dnstwister.rb +6 -8
  98. data/lib/mihari/analyzers/feed.rb +21 -10
  99. data/lib/mihari/analyzers/greynoise.rb +10 -20
  100. data/lib/mihari/analyzers/onyphe.rb +9 -14
  101. data/lib/mihari/analyzers/otx.rb +8 -9
  102. data/lib/mihari/analyzers/passivetotal.rb +10 -10
  103. data/lib/mihari/analyzers/pulsedive.rb +21 -31
  104. data/lib/mihari/analyzers/rule.rb +8 -29
  105. data/lib/mihari/analyzers/securitytrails.rb +8 -6
  106. data/lib/mihari/analyzers/shodan.rb +8 -13
  107. data/lib/mihari/analyzers/urlscan.rb +15 -20
  108. data/lib/mihari/analyzers/virustotal.rb +16 -26
  109. data/lib/mihari/analyzers/virustotal_intelligence.rb +11 -17
  110. data/lib/mihari/analyzers/zoomeye.rb +12 -17
  111. data/lib/mihari/commands/search.rb +16 -7
  112. data/lib/mihari/config.rb +133 -0
  113. data/lib/mihari/constants.rb +3 -0
  114. data/lib/mihari/emitters/slack.rb +13 -3
  115. data/lib/mihari/entities/rule.rb +1 -1
  116. data/lib/mihari/entities/tag.rb +1 -1
  117. data/lib/mihari/errors.rb +1 -1
  118. data/lib/mihari/http.rb +2 -3
  119. data/lib/mihari/schemas/analyzer.rb +4 -7
  120. data/lib/mihari/schemas/rule.rb +1 -1
  121. data/lib/mihari/structs/config.rb +39 -16
  122. data/lib/mihari/structs/rule.rb +1 -1
  123. data/lib/mihari/type_checker.rb +6 -6
  124. data/lib/mihari/version.rb +1 -1
  125. data/lib/mihari/web/endpoints/configs.rb +5 -1
  126. data/lib/mihari/web/public/assets/{index-eed1bcd8.css → index-2ba8f0a6.css} +1 -1
  127. data/lib/mihari/web/public/assets/index-71285b15.js +50 -0
  128. data/lib/mihari/web/public/index.html +2 -2
  129. data/lib/mihari/web/public/redoc-static.html +388 -2193
  130. data/lib/mihari.rb +9 -59
  131. data/mihari.gemspec +13 -13
  132. metadata +112 -69
  133. data/.gitmodules +0 -0
  134. data/.overcommit.yml +0 -12
  135. data/lib/mihari/web/public/assets/index-cbe1734c.js +0 -50
@@ -0,0 +1,304 @@
1
+ <template>
2
+ <div class="column">
3
+ <div v-if="enrichArtifactTask.isRunning">
4
+ <Loading></Loading>
5
+ <hr />
6
+ </div>
7
+
8
+ <h2 class="is-size-2 mb-4">Artifact</h2>
9
+
10
+ <div class="columns">
11
+ <div
12
+ class="column is-half"
13
+ v-if="googleMapSrc !== undefined || urlscanLiveshotSrc !== undefined"
14
+ >
15
+ <div v-if="googleMapSrc">
16
+ <h4 class="is-size-4 mb-2">
17
+ Geolocation
18
+ <span class="has-text-grey">{{
19
+ countryCode || artifact.geolocation?.countryCode
20
+ }}</span>
21
+ </h4>
22
+ <iframe class="mb-4" :src="googleMapSrc" width="100%" height="240px"></iframe>
23
+ </div>
24
+
25
+ <div v-if="urlscanLiveshotSrc">
26
+ <h4 class="is-size-4 mb-2">
27
+ Live screenshot
28
+ <span class="has-text-grey">Hover to expand</span>
29
+ </h4>
30
+ <img :src="urlscanLiveshotSrc" class="liveshot" alt="liveshot" />
31
+ </div>
32
+ </div>
33
+
34
+ <div class="column">
35
+ <div class="block">
36
+ <h4 class="is-size-4 mb-2">Information</h4>
37
+
38
+ <table class="table is-fullwidth is-completely-borderless">
39
+ <tr>
40
+ <th>ID</th>
41
+ <td>
42
+ {{ artifact.id }}
43
+ <span class="buttons is-pulled-right">
44
+ <button class="button is-primary is-light is-small" @click="enrichArtifact">
45
+ <span>Enrich</span>
46
+ <span class="icon is-small">
47
+ <i class="fas fa-lightbulb"></i>
48
+ </span>
49
+ </button>
50
+
51
+ <button
52
+ class="button is-info is-light is-small"
53
+ @click="flipShowMetadata"
54
+ v-if="artifact.metadata"
55
+ >
56
+ <span>Metadata</span>
57
+ <span class="icon is-small">
58
+ <i class="fas fa-info-circle"></i>
59
+ </span>
60
+ </button>
61
+
62
+ <button class="button is-light is-small" @click="deleteArtifact">
63
+ <span>Delete</span>
64
+ <span class="icon is-small">
65
+ <i class="fas fa-times"></i>
66
+ </span>
67
+ </button>
68
+ </span>
69
+ </td>
70
+ </tr>
71
+ <tr>
72
+ <th>Data type</th>
73
+ <td>{{ artifact.dataType }}</td>
74
+ </tr>
75
+ <tr>
76
+ <th>Data</th>
77
+ <td>{{ artifact.data }}</td>
78
+ </tr>
79
+ <tr>
80
+ <th>Source</th>
81
+ <td>{{ artifact.source }}</td>
82
+ </tr>
83
+ <tr v-if="artifact.tags.length > 0">
84
+ <th>Tags</th>
85
+ <td><Tags :tags="artifact.tags"></Tags></td>
86
+ </tr>
87
+ </table>
88
+ </div>
89
+
90
+ <div v-if="artifact.metadata && showMetadata">
91
+ <div class="modal is-active">
92
+ <div class="modal-background" @click="flipShowMetadata"></div>
93
+ <div class="modal-card">
94
+ <header class="modal-card-head">
95
+ <p class="modal-card-title">Metadata</p>
96
+ <button class="delete" aria-label="close" @click="flipShowMetadata"></button>
97
+ </header>
98
+ <section class="modal-card-body">
99
+ <VueJsonPretty :data="artifact.metadata as any"></VueJsonPretty>
100
+ </section>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="block" v-if="artifact.autonomousSystem">
108
+ <h4 class="is-size-4 mb-2">AS</h4>
109
+ <AS :autonomousSystem="artifact.autonomousSystem"></AS>
110
+ </div>
111
+
112
+ <div class="block" v-if="artifact.reverseDnsNames">
113
+ <h4 class="is-size-4 mb-2">Reverse DNS</h4>
114
+ <ReverseDnsNames :reverseDnsNames="artifact.reverseDnsNames"></ReverseDnsNames>
115
+ </div>
116
+
117
+ <div class="block" v-if="artifact.dnsRecords">
118
+ <h4 class="is-size-4 mb-2">DNS records</h4>
119
+ <DnsRecords :dnsRecords="artifact.dnsRecords"></DnsRecords>
120
+ </div>
121
+
122
+ <div class="block" v-if="artifact.cpes">
123
+ <h4 class="is-size-4 mb-2">CPEs</h4>
124
+ <CPEs :cpes="artifact.cpes"></CPEs>
125
+ </div>
126
+
127
+ <div class="block" v-if="artifact.ports">
128
+ <h4 class="is-size-4 mb-2">Ports</h4>
129
+ <Ports :ports="artifact.ports"></Ports>
130
+ </div>
131
+
132
+ <div class="block" v-if="artifact.whoisRecord">
133
+ <h4 class="is-size-4 mb-2">Whois record</h4>
134
+ <WhoisRecord :whoisRecord="artifact.whoisRecord"></WhoisRecord>
135
+ </div>
136
+
137
+ <div class="block">
138
+ <h4 class="is-size-4 mb-2">Links</h4>
139
+ <Links :data="artifact.data" :type="artifact.dataType"></Links>
140
+ </div>
141
+ </div>
142
+
143
+ <hr />
144
+
145
+ <div class="column">
146
+ <h2 class="is-size-2 mb-4">Related alerts</h2>
147
+ <Alerts :artifact="artifact.data"></Alerts>
148
+ </div>
149
+ </template>
150
+
151
+ <script lang="ts">
152
+ import "vue-json-pretty/lib/styles.css"
153
+
154
+ import { computed, defineComponent, onMounted, type PropType, ref } from "vue"
155
+ import VueJsonPretty from "vue-json-pretty"
156
+ import { useRouter } from "vue-router"
157
+
158
+ import {
159
+ generateDeleteArtifactTask,
160
+ generateEnrichArtifactTask,
161
+ generateGetAlertsTask,
162
+ generateGetIPTask
163
+ } from "@/api-helper"
164
+ import Alerts from "@/components/alert/AlertsWithPagination.vue"
165
+ import AS from "@/components/artifact/AS.vue"
166
+ import CPEs from "@/components/artifact/CPEs.vue"
167
+ import DnsRecords from "@/components/artifact/DnsRecords.vue"
168
+ import Ports from "@/components/artifact/Ports.vue"
169
+ import ReverseDnsNames from "@/components/artifact/ReverseDnsNames.vue"
170
+ import Tags from "@/components/artifact/Tags.vue"
171
+ import WhoisRecord from "@/components/artifact/WhoisRecord.vue"
172
+ import Links from "@/components/link/Links.vue"
173
+ import Loading from "@/components/Loading.vue"
174
+ import type { ArtifactWithTags, GCS } from "@/types"
175
+ import { getGCSByCountryCode, getGCSByIPInfo } from "@/utils"
176
+
177
+ export default defineComponent({
178
+ name: "ArtifactItem",
179
+ props: {
180
+ artifact: {
181
+ type: Object as PropType<ArtifactWithTags>,
182
+ required: true
183
+ }
184
+ },
185
+ components: {
186
+ Alerts,
187
+ AS,
188
+ DnsRecords,
189
+ Links,
190
+ Loading,
191
+ ReverseDnsNames,
192
+ Tags,
193
+ VueJsonPretty,
194
+ WhoisRecord,
195
+ CPEs,
196
+ Ports
197
+ },
198
+ emits: ["refresh"],
199
+ setup(props, context) {
200
+ const googleMapSrc = ref<string | undefined>(undefined)
201
+ const countryCode = ref<string | undefined>(undefined)
202
+ const showMetadata = ref(false)
203
+
204
+ const router = useRouter()
205
+
206
+ const urlscanLiveshotSrc = computed<string | undefined>(() => {
207
+ if (props.artifact.dataType === "domain") {
208
+ const url = `http://${props.artifact.data}`
209
+ return `https://urlscan.io/liveshot/?url=${url}`
210
+ }
211
+
212
+ if (props.artifact.dataType === "url") {
213
+ return `https://urlscan.io/liveshot/?url=${props.artifact.data}`
214
+ }
215
+
216
+ return undefined
217
+ })
218
+
219
+ const getGoogleMapSrc = (gcs: GCS | undefined): string | undefined => {
220
+ if (gcs !== undefined) {
221
+ return `https://maps.google.co.jp/maps?output=embed&q=${gcs.lat},${gcs.long}&z=3`
222
+ }
223
+
224
+ return undefined
225
+ }
226
+
227
+ const getIPInfoTask = generateGetIPTask()
228
+ const getAlertsTask = generateGetAlertsTask()
229
+ const deleteArtifactTask = generateDeleteArtifactTask()
230
+ const enrichArtifactTask = generateEnrichArtifactTask()
231
+
232
+ const deleteArtifact = async () => {
233
+ const result = window.confirm(`Are you sure you want to delete ${props.artifact.data}?`)
234
+
235
+ if (result) {
236
+ await deleteArtifactTask.perform(props.artifact.id)
237
+ router.push("/")
238
+ }
239
+ }
240
+
241
+ const enrichArtifact = async () => {
242
+ await enrichArtifactTask.perform(props.artifact.id)
243
+ context.emit("refresh")
244
+ }
245
+
246
+ onMounted(async () => {
247
+ if (props.artifact.dataType === "ip") {
248
+ let gcs: GCS | undefined = undefined
249
+
250
+ if (props.artifact.geolocation === null) {
251
+ // Use IPInfo if an artifact does not have geolocation
252
+ const ipinfo = await getIPInfoTask.perform(props.artifact.data)
253
+ gcs = getGCSByIPInfo(ipinfo)
254
+ countryCode.value = ipinfo.countryCode
255
+ } else {
256
+ gcs = getGCSByCountryCode(props.artifact.geolocation.countryCode)
257
+ }
258
+
259
+ googleMapSrc.value = getGoogleMapSrc(gcs)
260
+ }
261
+ })
262
+
263
+ const flipShowMetadata = () => {
264
+ showMetadata.value = !showMetadata.value
265
+ }
266
+
267
+ return {
268
+ countryCode,
269
+ enrichArtifactTask,
270
+ getAlertsTask,
271
+ googleMapSrc,
272
+ showMetadata,
273
+ urlscanLiveshotSrc,
274
+ deleteArtifact,
275
+ enrichArtifact,
276
+ flipShowMetadata
277
+ }
278
+ }
279
+ })
280
+ </script>
281
+
282
+ <style scoped>
283
+ img.liveshot {
284
+ border: 1px solid #aaa;
285
+ border-radius: 5px;
286
+ width: 100%;
287
+ max-height: 250px;
288
+ object-fit: cover;
289
+ object-position: top;
290
+ display: block;
291
+ overflow: hidden;
292
+ transition:
293
+ max-height 1s,
294
+ height 1s;
295
+ }
296
+
297
+ img.liveshot:hover {
298
+ max-height: none;
299
+ }
300
+
301
+ .modal-card {
302
+ width: 960px;
303
+ }
304
+ </style>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <div class="control" v-if="!isDeleted">
3
+ <div
4
+ class="tags has-addons are-medium"
5
+ v-on:mouseover="showDeleteButton"
6
+ v-on:mouseleave="hideDeleteButton"
7
+ >
8
+ <router-link
9
+ class="tag is-link is-light"
10
+ :to="{ name: 'Artifact', params: { id: artifact.id } }"
11
+ >{{ artifact.data }}</router-link
12
+ >
13
+ <span class="tag is-delete" v-if="isDeleteButtonEnabled" @click="deleteArtifact"></span>
14
+ </div>
15
+ </div>
16
+ </template>
17
+
18
+ <script lang="ts">
19
+ import { defineComponent, type PropType, ref } from "vue"
20
+
21
+ import { generateDeleteArtifactTask } from "@/api-helper"
22
+ import type { Artifact } from "@/types"
23
+
24
+ export default defineComponent({
25
+ name: "ArtifactTag",
26
+ props: {
27
+ artifact: {
28
+ type: Object as PropType<Artifact>,
29
+ required: true
30
+ }
31
+ },
32
+ setup(props) {
33
+ const isDeleted = ref(false)
34
+ const isDeleteButtonEnabled = ref(false)
35
+
36
+ const deleteArtifactTask = generateDeleteArtifactTask()
37
+
38
+ const deleteArtifact = async () => {
39
+ const result = window.confirm(`Are you sure you want to delete ${props.artifact.data}?`)
40
+
41
+ if (result) {
42
+ await deleteArtifactTask.perform(props.artifact.id)
43
+ isDeleted.value = true
44
+ }
45
+ }
46
+
47
+ const showDeleteButton = () => {
48
+ isDeleteButtonEnabled.value = true
49
+ }
50
+
51
+ const hideDeleteButton = () => {
52
+ isDeleteButtonEnabled.value = false
53
+ }
54
+
55
+ return {
56
+ isDeleted,
57
+ deleteArtifact,
58
+ showDeleteButton,
59
+ hideDeleteButton,
60
+ isDeleteButtonEnabled
61
+ }
62
+ }
63
+ })
64
+ </script>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div class="field is-grouped is-grouped-multiline">
3
+ <ArtifactComponent
4
+ v-for="artifact in artifacts"
5
+ :key="artifact.id"
6
+ :artifact="artifact"
7
+ ></ArtifactComponent>
8
+ </div>
9
+ </template>
10
+
11
+ <script lang="ts">
12
+ import { defineComponent, type PropType } from "vue"
13
+
14
+ import ArtifactComponent from "@/components/artifact/ArtifactTag.vue"
15
+ import type { Artifact } from "@/types"
16
+
17
+ export default defineComponent({
18
+ name: "ArtifactTags",
19
+ components: {
20
+ ArtifactComponent
21
+ },
22
+ props: {
23
+ artifacts: {
24
+ type: Array as PropType<Artifact[]>,
25
+ required: true
26
+ }
27
+ }
28
+ })
29
+ </script>
@@ -0,0 +1,59 @@
1
+ <template>
2
+ <Loading v-if="getArtifactTask.isRunning"></Loading>
3
+
4
+ <ErrorMessage v-if="getArtifactTask.isError" :error="getArtifactTask.last?.error"></ErrorMessage>
5
+
6
+ <ArtifactComponent
7
+ :artifact="getArtifactTask.last.value"
8
+ @refresh="refresh"
9
+ v-if="getArtifactTask.last?.value"
10
+ ></ArtifactComponent>
11
+ </template>
12
+
13
+ <script lang="ts">
14
+ import { defineComponent, onMounted, watch } from "vue"
15
+
16
+ import { generateGetArtifactTask } from "@/api-helper"
17
+ import ArtifactComponent from "@/components/artifact/Artifact.vue"
18
+ import ErrorMessage from "@/components/ErrorMessage.vue"
19
+ import Loading from "@/components/Loading.vue"
20
+
21
+ export default defineComponent({
22
+ name: "ArtifactWrapper",
23
+ components: {
24
+ ArtifactComponent,
25
+ Loading,
26
+ ErrorMessage
27
+ },
28
+ props: {
29
+ id: {
30
+ type: String,
31
+ required: true
32
+ }
33
+ },
34
+ setup(props) {
35
+ const getArtifactTask = generateGetArtifactTask()
36
+
37
+ const getArtifact = async () => {
38
+ await getArtifactTask.perform(props.id)
39
+ }
40
+
41
+ const refresh = async () => {
42
+ await getArtifact()
43
+ }
44
+
45
+ onMounted(async () => {
46
+ await getArtifact()
47
+ })
48
+
49
+ watch(props, async () => {
50
+ await getArtifact()
51
+ })
52
+
53
+ return {
54
+ getArtifactTask,
55
+ refresh
56
+ }
57
+ }
58
+ })
59
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div class="tags are-medium">
3
+ <span class="tag" v-for="cpe in cpes" :key="cpe.cpe">
4
+ {{ cpe.cpe }}
5
+ </span>
6
+ </div>
7
+ </template>
8
+
9
+ <script lang="ts">
10
+ import { defineComponent, type PropType } from "vue"
11
+
12
+ import type { CPE } from "@/types"
13
+
14
+ export default defineComponent({
15
+ name: "CPEsItem",
16
+ props: {
17
+ cpes: {
18
+ type: Array as PropType<CPE[]>,
19
+ required: true
20
+ }
21
+ }
22
+ })
23
+ </script>
@@ -0,0 +1,38 @@
1
+ <template>
2
+ <div class="field is-grouped is-grouped-multiline">
3
+ <div class="control" v-for="(dnsRecord, index) in dnsRecords" :key="index">
4
+ <div class="tags has-addons are-medium">
5
+ <span class="tag is-dark"> {{ dnsRecord.resource }}</span>
6
+ <router-link
7
+ class="tag"
8
+ :to="{
9
+ name: 'Alerts',
10
+ query: { dnsRecord: dnsRecord.value }
11
+ }"
12
+ >
13
+ {{ truncate(dnsRecord.value, 50) }}
14
+ </router-link>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </template>
19
+
20
+ <script lang="ts">
21
+ import truncate from "truncate"
22
+ import { defineComponent, type PropType } from "vue"
23
+
24
+ import type { DnsRecord } from "@/types"
25
+
26
+ export default defineComponent({
27
+ name: "DnsRecords",
28
+ props: {
29
+ dnsRecords: {
30
+ type: Array as PropType<DnsRecord[]>,
31
+ required: true
32
+ }
33
+ },
34
+ setup() {
35
+ return { truncate }
36
+ }
37
+ })
38
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div class="tags are-medium">
3
+ <span class="tag" v-for="port in ports" :key="port.port">
4
+ {{ port.port }}
5
+ </span>
6
+ </div>
7
+ </template>
8
+
9
+ <script lang="ts">
10
+ import { defineComponent, type PropType } from "vue"
11
+
12
+ import type { Port } from "@/types"
13
+
14
+ export default defineComponent({
15
+ name: "PortsItem",
16
+ props: {
17
+ ports: {
18
+ type: Array as PropType<Port[]>,
19
+ required: true
20
+ }
21
+ }
22
+ })
23
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div class="tags are-medium">
3
+ <router-link
4
+ class="tag"
5
+ v-for="reverseDnsName in reverseDnsNames"
6
+ :key="reverseDnsName.name"
7
+ :to="{
8
+ name: 'Alerts',
9
+ query: { reverseDnsName: reverseDnsName.name }
10
+ }"
11
+ >
12
+ {{ reverseDnsName.name }}
13
+ </router-link>
14
+ </div>
15
+ </template>
16
+
17
+ <script lang="ts">
18
+ import { defineComponent, type PropType } from "vue"
19
+
20
+ import type { ReverseDnsName } from "@/types"
21
+
22
+ export default defineComponent({
23
+ name: "ReverseDnsNames",
24
+ props: {
25
+ reverseDnsNames: {
26
+ type: Array as PropType<ReverseDnsName[]>,
27
+ required: true
28
+ }
29
+ }
30
+ })
31
+ </script>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div class="tags are-medium">
3
+ <router-link
4
+ class="tag is-info is-light"
5
+ v-for="tag in tags"
6
+ :key="tag"
7
+ :to="{
8
+ name: 'Alerts',
9
+ query: { tag: tag }
10
+ }"
11
+ >
12
+ {{ tag }}
13
+ </router-link>
14
+ </div>
15
+ </template>
16
+
17
+ <script lang="ts">
18
+ import { defineComponent, type PropType } from "vue"
19
+
20
+ export default defineComponent({
21
+ name: "TagsItem",
22
+ props: {
23
+ tags: {
24
+ type: Array as PropType<string[]>,
25
+ required: true
26
+ }
27
+ }
28
+ })
29
+ </script>
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <div class="field is-grouped is-grouped-multiline">
3
+ <div class="control">
4
+ <div class="tags has-addons are-medium">
5
+ <span class="tag is-dark">Registrar</span>
6
+ <span class="tag is-light">{{ whoisRecord.registrar?.name || "N/A" }}</span>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="control">
11
+ <div class="tags has-addons are-medium">
12
+ <span class="tag is-dark">Created on</span>
13
+ <span class="tag is-light">{{ whoisRecord.createdOn || "N/A" }}</span>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="control">
18
+ <div class="tags has-addons are-medium">
19
+ <span class="tag is-dark">Updated on</span>
20
+ <span class="tag is-light">{{ whoisRecord.updatedOn || "N/A" }}</span>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="control">
25
+ <div class="tags has-addons are-medium">
26
+ <span class="tag is-dark">Exipres on</span>
27
+ <span class="tag is-light">{{ whoisRecord.expiresOn || "N/A" }}</span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script lang="ts">
34
+ import { defineComponent, type PropType } from "vue"
35
+
36
+ import type { WhoisRecord } from "@/types"
37
+
38
+ export default defineComponent({
39
+ name: "WhoisRecord",
40
+ props: {
41
+ whoisRecord: {
42
+ type: Object as PropType<WhoisRecord>,
43
+ required: true
44
+ }
45
+ }
46
+ })
47
+ </script>