mihari 5.2.3 → 5.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/README.md +0 -10
- data/Rakefile +7 -1
- data/build_frontend.sh +2 -10
- data/frontend/.eslintrc.cjs +22 -0
- data/frontend/.gitignore +31 -0
- data/frontend/.prettierrc.json +8 -0
- data/frontend/README.md +3 -0
- data/frontend/env.d.ts +5 -0
- data/frontend/index.html +21 -0
- data/frontend/package-lock.json +8650 -0
- data/frontend/package.json +64 -0
- data/frontend/public/favicon.ico +0 -0
- data/frontend/scripts/swagger_doc_to_yaml.rb +23 -0
- data/frontend/src/App.vue +27 -0
- data/frontend/src/api-helper.ts +111 -0
- data/frontend/src/api.ts +105 -0
- data/frontend/src/components/ErrorMessage.vue +32 -0
- data/frontend/src/components/Loading.vue +15 -0
- data/frontend/src/components/Navbar.vue +42 -0
- data/frontend/src/components/Pagination.vue +119 -0
- data/frontend/src/components/alert/Alert.vue +87 -0
- data/frontend/src/components/alert/Alerts.vue +64 -0
- data/frontend/src/components/alert/AlertsWithPagination.vue +91 -0
- data/frontend/src/components/alert/AlertsWrapper.vue +134 -0
- data/frontend/src/components/alert/Form.vue +184 -0
- data/frontend/src/components/artifact/AS.vue +29 -0
- data/frontend/src/components/artifact/Artifact.vue +304 -0
- data/frontend/src/components/artifact/ArtifactTag.vue +64 -0
- data/frontend/src/components/artifact/ArtifactTags.vue +29 -0
- data/frontend/src/components/artifact/ArtifactWrapper.vue +59 -0
- data/frontend/src/components/artifact/CPEs.vue +23 -0
- data/frontend/src/components/artifact/DnsRecords.vue +38 -0
- data/frontend/src/components/artifact/Ports.vue +23 -0
- data/frontend/src/components/artifact/ReverseDnsNames.vue +31 -0
- data/frontend/src/components/artifact/Tags.vue +29 -0
- data/frontend/src/components/artifact/WhoisRecord.vue +47 -0
- data/frontend/src/components/config/Configs.vue +65 -0
- data/frontend/src/components/config/ConfigsWrapper.vue +34 -0
- data/frontend/src/components/link/Link.vue +32 -0
- data/frontend/src/components/link/Links.vue +42 -0
- data/frontend/src/components/rule/EditRule.vue +74 -0
- data/frontend/src/components/rule/EditRuleWrapper.vue +50 -0
- data/frontend/src/components/rule/Form.vue +160 -0
- data/frontend/src/components/rule/InputForm.vue +86 -0
- data/frontend/src/components/rule/NewRule.vue +60 -0
- data/frontend/src/components/rule/Rule.vue +106 -0
- data/frontend/src/components/rule/RuleWrapper.vue +55 -0
- data/frontend/src/components/rule/Rules.vue +84 -0
- data/frontend/src/components/rule/RulesWrapper.vue +127 -0
- data/frontend/src/components/rule/YAML.vue +44 -0
- data/frontend/src/components/tag/Tag.vue +65 -0
- data/frontend/src/components/tag/Tags.vue +37 -0
- data/frontend/src/countries.ts +350 -0
- data/frontend/src/index.ts +20 -0
- data/frontend/src/links/anyrun.ts +19 -0
- data/frontend/src/links/base.ts +14 -0
- data/frontend/src/links/censys.ts +20 -0
- data/frontend/src/links/crtsh.ts +20 -0
- data/frontend/src/links/dnslytics.ts +38 -0
- data/frontend/src/links/greynoise.ts +20 -0
- data/frontend/src/links/index.ts +40 -0
- data/frontend/src/links/intezer.ts +20 -0
- data/frontend/src/links/otx.ts +33 -0
- data/frontend/src/links/securitytrails.ts +38 -0
- data/frontend/src/links/shodan.ts +20 -0
- data/frontend/src/links/urlscan.ts +50 -0
- data/frontend/src/links/virustotal.ts +72 -0
- data/frontend/src/main.ts +11 -0
- data/frontend/src/router/index.ts +57 -0
- data/frontend/src/rule.ts +14 -0
- data/frontend/src/shims-vue.d.ts +6 -0
- data/frontend/src/swagger.yaml +737 -0
- data/frontend/src/types.ts +188 -0
- data/frontend/src/utils.ts +54 -0
- data/frontend/src/views/Alerts.vue +20 -0
- data/frontend/src/views/Artifact.vue +44 -0
- data/frontend/src/views/Configs.vue +20 -0
- data/frontend/src/views/EditRule.vue +44 -0
- data/frontend/src/views/NewRule.vue +26 -0
- data/frontend/src/views/Rule.vue +44 -0
- data/frontend/src/views/Rules.vue +20 -0
- data/frontend/tests/utils.spec.ts +9 -0
- data/frontend/tsconfig.app.json +21 -0
- data/frontend/tsconfig.json +14 -0
- data/frontend/tsconfig.node.json +13 -0
- data/frontend/tsconfig.vitest.json +12 -0
- data/frontend/vite.config.ts +24 -0
- data/frontend/vitest.config.ts +21 -0
- data/lefthook.yml +12 -0
- data/lib/mihari/analyzers/base.rb +63 -12
- data/lib/mihari/analyzers/binaryedge.rb +10 -15
- data/lib/mihari/analyzers/censys.rb +12 -15
- data/lib/mihari/analyzers/circl.rb +10 -10
- data/lib/mihari/analyzers/crtsh.rb +10 -6
- data/lib/mihari/analyzers/dnstwister.rb +6 -8
- data/lib/mihari/analyzers/feed.rb +21 -10
- data/lib/mihari/analyzers/greynoise.rb +10 -20
- data/lib/mihari/analyzers/onyphe.rb +9 -14
- data/lib/mihari/analyzers/otx.rb +8 -9
- data/lib/mihari/analyzers/passivetotal.rb +10 -10
- data/lib/mihari/analyzers/pulsedive.rb +21 -31
- data/lib/mihari/analyzers/rule.rb +8 -29
- data/lib/mihari/analyzers/securitytrails.rb +8 -6
- data/lib/mihari/analyzers/shodan.rb +8 -13
- data/lib/mihari/analyzers/urlscan.rb +15 -20
- data/lib/mihari/analyzers/virustotal.rb +16 -26
- data/lib/mihari/analyzers/virustotal_intelligence.rb +11 -17
- data/lib/mihari/analyzers/zoomeye.rb +12 -17
- data/lib/mihari/commands/search.rb +16 -7
- data/lib/mihari/config.rb +133 -0
- data/lib/mihari/constants.rb +3 -0
- data/lib/mihari/emitters/slack.rb +13 -3
- data/lib/mihari/entities/rule.rb +1 -1
- data/lib/mihari/entities/tag.rb +1 -1
- data/lib/mihari/errors.rb +1 -1
- data/lib/mihari/http.rb +2 -3
- data/lib/mihari/schemas/analyzer.rb +4 -7
- data/lib/mihari/schemas/rule.rb +1 -1
- data/lib/mihari/structs/config.rb +39 -16
- data/lib/mihari/structs/rule.rb +1 -1
- data/lib/mihari/type_checker.rb +6 -6
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/endpoints/configs.rb +5 -1
- data/lib/mihari/web/public/assets/{index-eed1bcd8.css → index-2ba8f0a6.css} +1 -1
- data/lib/mihari/web/public/assets/index-71285b15.js +50 -0
- data/lib/mihari/web/public/index.html +2 -2
- data/lib/mihari/web/public/redoc-static.html +388 -2193
- data/lib/mihari.rb +9 -59
- data/mihari.gemspec +13 -13
- metadata +112 -69
- data/.gitmodules +0 -0
- data/.overcommit.yml +0 -12
- data/lib/mihari/web/public/assets/index-cbe1734c.js +0 -50
@@ -0,0 +1,188 @@
|
|
1
|
+
export interface Pagination {
|
2
|
+
total: number
|
3
|
+
currentPage: number
|
4
|
+
pageSize: number
|
5
|
+
}
|
6
|
+
|
7
|
+
export interface ConfigValue {
|
8
|
+
key: string
|
9
|
+
value: string | null
|
10
|
+
}
|
11
|
+
|
12
|
+
export interface Config {
|
13
|
+
name: string
|
14
|
+
isConfigured: boolean
|
15
|
+
values: ConfigValue[]
|
16
|
+
type: string
|
17
|
+
}
|
18
|
+
|
19
|
+
export interface Tag {
|
20
|
+
name: string
|
21
|
+
}
|
22
|
+
|
23
|
+
export interface Tags {
|
24
|
+
tags: string[]
|
25
|
+
}
|
26
|
+
|
27
|
+
export interface RuleSet {
|
28
|
+
ruleIds: string[]
|
29
|
+
}
|
30
|
+
|
31
|
+
export interface DnsRecord {
|
32
|
+
resource: string
|
33
|
+
value: string
|
34
|
+
}
|
35
|
+
|
36
|
+
export interface Contact {
|
37
|
+
name: string | null
|
38
|
+
organization: string | null
|
39
|
+
}
|
40
|
+
|
41
|
+
export interface Registrar {
|
42
|
+
name: string | null
|
43
|
+
organization: string | null
|
44
|
+
}
|
45
|
+
|
46
|
+
export interface WhoisRecord {
|
47
|
+
createdOn: Date | null
|
48
|
+
updatedOn: Date | null
|
49
|
+
expiresOn: Date | null
|
50
|
+
registrar: Registrar | null
|
51
|
+
contacts: Contact[]
|
52
|
+
}
|
53
|
+
|
54
|
+
export interface AutonomousSystem {
|
55
|
+
asn: number
|
56
|
+
}
|
57
|
+
|
58
|
+
export interface Geolocation {
|
59
|
+
country: string
|
60
|
+
countryCode: string
|
61
|
+
}
|
62
|
+
|
63
|
+
export interface ReverseDnsName {
|
64
|
+
name: string
|
65
|
+
}
|
66
|
+
|
67
|
+
export interface CPE {
|
68
|
+
cpe: string
|
69
|
+
}
|
70
|
+
|
71
|
+
export interface Port {
|
72
|
+
port: string
|
73
|
+
}
|
74
|
+
|
75
|
+
export interface Artifact {
|
76
|
+
id: string
|
77
|
+
data: string
|
78
|
+
dataType: string
|
79
|
+
source: string
|
80
|
+
metadata: unknown | null
|
81
|
+
createdAt: string
|
82
|
+
|
83
|
+
autonomousSystem: AutonomousSystem | null
|
84
|
+
whoisRecord: WhoisRecord | null
|
85
|
+
geolocation: Geolocation | null
|
86
|
+
|
87
|
+
dnsRecords: DnsRecord[] | null
|
88
|
+
reverseDnsNames: ReverseDnsName[] | null
|
89
|
+
cpes: CPE[] | null
|
90
|
+
ports: Port[] | null
|
91
|
+
}
|
92
|
+
|
93
|
+
export interface ArtifactWithTags extends Artifact {
|
94
|
+
tags: string[]
|
95
|
+
}
|
96
|
+
|
97
|
+
export interface Alert {
|
98
|
+
id: string
|
99
|
+
ruleId: string
|
100
|
+
createdAt: string
|
101
|
+
|
102
|
+
tags: Tag[]
|
103
|
+
artifacts: Artifact[]
|
104
|
+
}
|
105
|
+
|
106
|
+
export interface Alerts extends Pagination {
|
107
|
+
alerts: Alert[]
|
108
|
+
}
|
109
|
+
|
110
|
+
export interface PaginationParams {
|
111
|
+
page: number | undefined
|
112
|
+
}
|
113
|
+
|
114
|
+
export interface AlertSearchParams extends PaginationParams {
|
115
|
+
artifact: string | undefined
|
116
|
+
ruleId: string | undefined
|
117
|
+
tag: string | undefined
|
118
|
+
fromAt: string | undefined
|
119
|
+
toAt: string | undefined
|
120
|
+
}
|
121
|
+
|
122
|
+
export interface IPInfo {
|
123
|
+
ip: string
|
124
|
+
hostname: string | null
|
125
|
+
loc: string
|
126
|
+
countryCode: string
|
127
|
+
asn: string
|
128
|
+
}
|
129
|
+
|
130
|
+
export interface GCS {
|
131
|
+
lat: number
|
132
|
+
long: number
|
133
|
+
}
|
134
|
+
|
135
|
+
export interface Country {
|
136
|
+
name: string
|
137
|
+
code: string
|
138
|
+
lat: number
|
139
|
+
long: number
|
140
|
+
}
|
141
|
+
|
142
|
+
export type LinkType = "ip" | "domain" | "url" | "hash"
|
143
|
+
|
144
|
+
export interface Link {
|
145
|
+
name: string
|
146
|
+
type: string
|
147
|
+
baseURL: string
|
148
|
+
// eslint-disable-next-line no-unused-vars
|
149
|
+
href(data: string): string
|
150
|
+
favicon(): string
|
151
|
+
}
|
152
|
+
|
153
|
+
export interface Rule {
|
154
|
+
id: string
|
155
|
+
title: string
|
156
|
+
description: string
|
157
|
+
yaml: string
|
158
|
+
createdAt: string
|
159
|
+
updatedAt: string
|
160
|
+
tags: Tag[]
|
161
|
+
}
|
162
|
+
|
163
|
+
export interface CreateRule {
|
164
|
+
yaml: string
|
165
|
+
}
|
166
|
+
|
167
|
+
export interface UpdateRule {
|
168
|
+
id: string
|
169
|
+
yaml: string
|
170
|
+
}
|
171
|
+
|
172
|
+
export interface Query {
|
173
|
+
analyzer: string
|
174
|
+
query: string
|
175
|
+
interval: null
|
176
|
+
}
|
177
|
+
|
178
|
+
export interface Rules extends Pagination {
|
179
|
+
rules: Rule[]
|
180
|
+
}
|
181
|
+
|
182
|
+
export interface RuleSearchParams extends PaginationParams {
|
183
|
+
description: string | undefined
|
184
|
+
tag: string | undefined
|
185
|
+
title: string | undefined
|
186
|
+
fromAt: string | undefined
|
187
|
+
toAt: string | undefined
|
188
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import dayjs from "dayjs"
|
2
|
+
import relativeTime from "dayjs/plugin/relativeTime"
|
3
|
+
import timezone from "dayjs/plugin/timezone"
|
4
|
+
import utc from "dayjs/plugin/utc"
|
5
|
+
import type { LocationQueryValue } from "vue-router"
|
6
|
+
|
7
|
+
import { getCountryByCode } from "@/countries"
|
8
|
+
import type { GCS, IPInfo } from "@/types"
|
9
|
+
|
10
|
+
dayjs.extend(relativeTime)
|
11
|
+
dayjs.extend(timezone)
|
12
|
+
dayjs.extend(utc)
|
13
|
+
|
14
|
+
export function getLocalDatetime(datetime: string): string {
|
15
|
+
return dayjs(datetime).local().format("YYYY-MM-DD HH:mm:ss")
|
16
|
+
}
|
17
|
+
|
18
|
+
export function getHumanizedRelativeTime(datetime: string): string {
|
19
|
+
return dayjs(datetime).local().fromNow()
|
20
|
+
}
|
21
|
+
|
22
|
+
export function getGCSByCountryCode(countryCode: string): GCS | undefined {
|
23
|
+
const country = getCountryByCode(countryCode)
|
24
|
+
if (country !== undefined) {
|
25
|
+
return { lat: country.lat, long: country.long }
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
export function getGCSByIPInfo(ipinfo: IPInfo): GCS | undefined {
|
30
|
+
if (ipinfo.loc !== undefined) {
|
31
|
+
const numbers = ipinfo.loc.split(",")
|
32
|
+
if (numbers.length === 2) {
|
33
|
+
const lat = numbers[0]
|
34
|
+
const long = numbers[1]
|
35
|
+
|
36
|
+
return { lat: parseFloat(lat), long: parseFloat(long) }
|
37
|
+
}
|
38
|
+
}
|
39
|
+
return getGCSByCountryCode(ipinfo.countryCode)
|
40
|
+
}
|
41
|
+
|
42
|
+
export function normalizeQueryParam(
|
43
|
+
param: undefined | null | string | string[] | LocationQueryValue | LocationQueryValue[]
|
44
|
+
): string | undefined {
|
45
|
+
if (param === undefined || param === null) {
|
46
|
+
return undefined
|
47
|
+
}
|
48
|
+
|
49
|
+
if (typeof param === "string") {
|
50
|
+
return param
|
51
|
+
}
|
52
|
+
|
53
|
+
return param.toString()
|
54
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<template>
|
2
|
+
<Alerts></Alerts>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent } from "vue"
|
8
|
+
|
9
|
+
import Alerts from "@/components/alert/AlertsWrapper.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "AlertsView",
|
13
|
+
components: {
|
14
|
+
Alerts
|
15
|
+
},
|
16
|
+
setup() {
|
17
|
+
useTitle("Alerts - Mihari")
|
18
|
+
}
|
19
|
+
})
|
20
|
+
</script>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<template>
|
2
|
+
<Artifact :id="artifactId"></Artifact>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent, onMounted, ref, watch } from "vue"
|
8
|
+
|
9
|
+
import Artifact from "@/components/artifact/ArtifactWrapper.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "ArtifactView",
|
13
|
+
components: {
|
14
|
+
Artifact
|
15
|
+
},
|
16
|
+
props: {
|
17
|
+
id: {
|
18
|
+
type: String,
|
19
|
+
required: true
|
20
|
+
}
|
21
|
+
},
|
22
|
+
setup(props) {
|
23
|
+
const artifactId = ref<string>(props.id)
|
24
|
+
|
25
|
+
const updateTitle = () => {
|
26
|
+
useTitle(`Artifact:${artifactId.value} - Mihari`)
|
27
|
+
}
|
28
|
+
|
29
|
+
onMounted(() => {
|
30
|
+
updateTitle()
|
31
|
+
})
|
32
|
+
|
33
|
+
watch(
|
34
|
+
() => props.id,
|
35
|
+
() => {
|
36
|
+
artifactId.value = props.id
|
37
|
+
updateTitle()
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
return { artifactId }
|
42
|
+
}
|
43
|
+
})
|
44
|
+
</script>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<template>
|
2
|
+
<Configs></Configs>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent } from "vue"
|
8
|
+
|
9
|
+
import Configs from "@/components/config/ConfigsWrapper.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "ConfigView",
|
13
|
+
components: {
|
14
|
+
Configs
|
15
|
+
},
|
16
|
+
setup() {
|
17
|
+
useTitle("Config - Mihari")
|
18
|
+
}
|
19
|
+
})
|
20
|
+
</script>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<template>
|
2
|
+
<EditRule :id="id"></EditRule>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent, onMounted, ref, watch } from "vue"
|
8
|
+
|
9
|
+
import EditRule from "@/components/rule/EditRuleWrapper.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "EditRuleView",
|
13
|
+
components: {
|
14
|
+
EditRule
|
15
|
+
},
|
16
|
+
props: {
|
17
|
+
id: {
|
18
|
+
type: String,
|
19
|
+
required: true
|
20
|
+
}
|
21
|
+
},
|
22
|
+
setup(props) {
|
23
|
+
const ruleId = ref<string>(props.id)
|
24
|
+
|
25
|
+
const updateTitle = () => {
|
26
|
+
useTitle(`Edit rule:${ruleId.value} - Mihari`)
|
27
|
+
}
|
28
|
+
|
29
|
+
onMounted(() => {
|
30
|
+
updateTitle()
|
31
|
+
})
|
32
|
+
|
33
|
+
watch(
|
34
|
+
() => props.id,
|
35
|
+
() => {
|
36
|
+
ruleId.value = props.id
|
37
|
+
updateTitle()
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
return { ruleId }
|
42
|
+
}
|
43
|
+
})
|
44
|
+
</script>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<template>
|
2
|
+
<NewRule></NewRule>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent, onMounted } from "vue"
|
8
|
+
|
9
|
+
import NewRule from "@/components/rule/NewRule.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "NewRuleView",
|
13
|
+
components: {
|
14
|
+
NewRule
|
15
|
+
},
|
16
|
+
setup() {
|
17
|
+
const updateTitle = () => {
|
18
|
+
useTitle(`New rule - Mihari`)
|
19
|
+
}
|
20
|
+
|
21
|
+
onMounted(() => {
|
22
|
+
updateTitle()
|
23
|
+
})
|
24
|
+
}
|
25
|
+
})
|
26
|
+
</script>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<template>
|
2
|
+
<Rule :id="ruleId"></Rule>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent, onMounted, ref, watch } from "vue"
|
8
|
+
|
9
|
+
import Rule from "@/components/rule/RuleWrapper.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "RuleView",
|
13
|
+
components: {
|
14
|
+
Rule
|
15
|
+
},
|
16
|
+
props: {
|
17
|
+
id: {
|
18
|
+
type: String,
|
19
|
+
required: true
|
20
|
+
}
|
21
|
+
},
|
22
|
+
setup(props) {
|
23
|
+
const ruleId = ref<string>(props.id)
|
24
|
+
|
25
|
+
const updateTitle = () => {
|
26
|
+
useTitle(`Rule:${ruleId.value} - Mihari`)
|
27
|
+
}
|
28
|
+
|
29
|
+
onMounted(() => {
|
30
|
+
updateTitle()
|
31
|
+
})
|
32
|
+
|
33
|
+
watch(
|
34
|
+
() => props.id,
|
35
|
+
() => {
|
36
|
+
ruleId.value = props.id
|
37
|
+
updateTitle()
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
return { ruleId }
|
42
|
+
}
|
43
|
+
})
|
44
|
+
</script>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<template>
|
2
|
+
<Rules></Rules>
|
3
|
+
</template>
|
4
|
+
|
5
|
+
<script lang="ts">
|
6
|
+
import { useTitle } from "@vueuse/core"
|
7
|
+
import { defineComponent } from "vue"
|
8
|
+
|
9
|
+
import Rules from "@/components/rule/RulesWrapper.vue"
|
10
|
+
|
11
|
+
export default defineComponent({
|
12
|
+
name: "RulesView",
|
13
|
+
components: {
|
14
|
+
Rules
|
15
|
+
},
|
16
|
+
setup() {
|
17
|
+
useTitle("Rules - Mihari")
|
18
|
+
}
|
19
|
+
})
|
20
|
+
</script>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
2
|
+
|
3
|
+
import { getHumanizedRelativeTime } from '@/utils'
|
4
|
+
|
5
|
+
describe('getHumanizedRelativeTime', () => {
|
6
|
+
it('returns a relative time in humanized format', () => {
|
7
|
+
expect(getHumanizedRelativeTime('1970-01-01 00:00:00')).toContain('years')
|
8
|
+
})
|
9
|
+
})
|
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
3
|
+
"include": [
|
4
|
+
"env.d.ts",
|
5
|
+
"src/**/*",
|
6
|
+
"src/**/*.vue",
|
7
|
+
"tests/**/*"
|
8
|
+
],
|
9
|
+
"exclude": [
|
10
|
+
"src/**/__tests__/*"
|
11
|
+
],
|
12
|
+
"compilerOptions": {
|
13
|
+
"composite": true,
|
14
|
+
"baseUrl": ".",
|
15
|
+
"paths": {
|
16
|
+
"@/*": [
|
17
|
+
"./src/*"
|
18
|
+
]
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { fileURLToPath, URL } from "node:url"
|
2
|
+
|
3
|
+
import vue from "@vitejs/plugin-vue"
|
4
|
+
import { defineConfig } from "vite"
|
5
|
+
|
6
|
+
const env = process.env
|
7
|
+
const target = env.BACKEND_URL || "http://localhost:9292/"
|
8
|
+
const port = parseInt(env.port || "8080")
|
9
|
+
|
10
|
+
// https://vitejs.dev/config/
|
11
|
+
export default defineConfig({
|
12
|
+
plugins: [vue()],
|
13
|
+
server: {
|
14
|
+
port,
|
15
|
+
proxy: {
|
16
|
+
"/api": target
|
17
|
+
}
|
18
|
+
},
|
19
|
+
resolve: {
|
20
|
+
alias: {
|
21
|
+
"@": fileURLToPath(new URL("./src", import.meta.url))
|
22
|
+
}
|
23
|
+
}
|
24
|
+
})
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { fileURLToPath } from "node:url"
|
2
|
+
|
3
|
+
import { mergeConfig } from "vite"
|
4
|
+
import { configDefaults, defineConfig } from "vitest/config"
|
5
|
+
|
6
|
+
import viteConfig from "./vite.config"
|
7
|
+
|
8
|
+
export default mergeConfig(
|
9
|
+
viteConfig,
|
10
|
+
defineConfig({
|
11
|
+
test: {
|
12
|
+
environment: "jsdom",
|
13
|
+
exclude: [...configDefaults.exclude, "e2e/*"],
|
14
|
+
root: fileURLToPath(new URL("./", import.meta.url)),
|
15
|
+
transformMode: {
|
16
|
+
web: [/\.[jt]sx$/]
|
17
|
+
},
|
18
|
+
include: ["**/__tests__/**/*.?(c|m)[jt]s?(x)", "**/?(*.){test,spec}.?(c|m)[jt]s?(x)"]
|
19
|
+
}
|
20
|
+
})
|
21
|
+
)
|
data/lefthook.yml
ADDED