mihari 5.2.3 → 5.2.4

Sign up to get free protection for your applications and to get access to all the features.
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>