mihari 5.2.3 → 5.2.4

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