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,65 @@
1
+ <template>
2
+ <div class="box">
3
+ <div class="table-container">
4
+ <table class="table is-fullwidth">
5
+ <thead>
6
+ <tr>
7
+ <th>Name</th>
8
+ <th>Type</th>
9
+ <th>Configured</th>
10
+ <th>Key-value(s)</th>
11
+ </tr>
12
+ </thead>
13
+ <tbody>
14
+ <tr v-for="config in configs" :key="config.name">
15
+ <td>
16
+ {{ config.name }}
17
+ </td>
18
+ <td>
19
+ {{ config.type }}
20
+ </td>
21
+ <td>
22
+ <button class="button is-success is-small ml-1" v-if="config.isConfigured">
23
+ <span class="icon is-small">
24
+ <i class="fas fa-check"></i>
25
+ </span>
26
+ <span>Yes</span>
27
+ </button>
28
+ <button class="button is-warning is-small ml-1" v-else>
29
+ <span class="icon is-small">
30
+ <i class="fas fa-exclamation"></i>
31
+ </span>
32
+ <span>No</span>
33
+ </button>
34
+ </td>
35
+ <td>
36
+ <ul>
37
+ <li v-for="(kv, index) in config.values" :key="index">
38
+ <strong> {{ kv.key }} </strong>:
39
+ <code v-if="kv.value">{{ kv.value }}</code>
40
+ <span v-else>N/A</span>
41
+ </li>
42
+ </ul>
43
+ </td>
44
+ </tr>
45
+ </tbody>
46
+ </table>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script lang="ts">
52
+ import { defineComponent, type PropType } from "vue"
53
+
54
+ import type { Config } from "@/types"
55
+
56
+ export default defineComponent({
57
+ name: "ConfigsItem",
58
+ props: {
59
+ configs: {
60
+ type: Array as PropType<Config[]>,
61
+ required: true
62
+ }
63
+ }
64
+ })
65
+ </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <Loading v-if="getConfigsTask.isRunning"></Loading>
3
+
4
+ <ErrorMessage v-if="getConfigsTask.isError" :error="getConfigsTask.last?.error"></ErrorMessage>
5
+
6
+ <Configs :configs="getConfigsTask.last.value" v-if="getConfigsTask.last?.value"></Configs>
7
+ </template>
8
+
9
+ <script lang="ts">
10
+ import { defineComponent, onMounted } from "vue"
11
+
12
+ import { generateGetConfigsTask } from "@/api-helper"
13
+ import Configs from "@/components/config/Configs.vue"
14
+ import ErrorMessage from "@/components/ErrorMessage.vue"
15
+ import Loading from "@/components/Loading.vue"
16
+
17
+ export default defineComponent({
18
+ name: "ConfigsWrapper",
19
+ components: {
20
+ Configs,
21
+ Loading,
22
+ ErrorMessage
23
+ },
24
+ setup() {
25
+ const getConfigsTask = generateGetConfigsTask()
26
+
27
+ onMounted(async () => {
28
+ await getConfigsTask.perform()
29
+ })
30
+
31
+ return { getConfigsTask }
32
+ }
33
+ })
34
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <a :href="link.href(data)" class="tag is-white" target="_blank">
3
+ <img :src="link.favicon()" alt="favicon" />
4
+ <span>{{ link.name }}</span>
5
+ </a>
6
+ </template>
7
+
8
+ <script lang="ts">
9
+ import { defineComponent, type PropType } from "vue"
10
+
11
+ import type { Link } from "@/types"
12
+
13
+ export default defineComponent({
14
+ name: "LinkItem",
15
+ props: {
16
+ data: {
17
+ type: String,
18
+ required: true
19
+ },
20
+ link: {
21
+ type: Object as PropType<Link>,
22
+ required: true
23
+ }
24
+ }
25
+ })
26
+ </script>
27
+
28
+ <style scoped>
29
+ .tag img {
30
+ margin-right: 5px;
31
+ }
32
+ </style>
@@ -0,0 +1,42 @@
1
+ <template>
2
+ <div class="tags are-medium">
3
+ <LinkComponent :data="data" :link="link" v-for="link in selectedLinks" :key="link.name" />
4
+ </div>
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ import { computed, defineComponent } from "vue"
9
+
10
+ import LinkComponent from "@/components/link/Link.vue"
11
+ import { Links } from "@/links"
12
+ import type { Link } from "@/types"
13
+
14
+ export default defineComponent({
15
+ name: "LinksItem",
16
+ components: {
17
+ LinkComponent
18
+ },
19
+ props: {
20
+ data: {
21
+ type: String,
22
+ required: true
23
+ },
24
+ type: {
25
+ type: String,
26
+ required: true
27
+ }
28
+ },
29
+ setup(props) {
30
+ const links = Links
31
+ const selectedLinks = computed((): Link[] => {
32
+ if (props.type === undefined) {
33
+ return links
34
+ }
35
+
36
+ return links.filter((link) => link.type === props.type)
37
+ })
38
+
39
+ return { selectedLinks }
40
+ }
41
+ })
42
+ </script>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <div class="column">
3
+ <h2 class="is-size-2 mb-4">Edit rule: {{ rule.id }}</h2>
4
+
5
+ <InputForm v-model:yaml="yaml" @update-yaml="updateYAML"></InputForm>
6
+
7
+ <div class="field is-grouped is-grouped-centered">
8
+ <p class="control">
9
+ <a class="button is-primary" @click="edit">
10
+ <span class="icon is-small">
11
+ <i class="fas fa-edit"></i>
12
+ </span>
13
+ <span>Edit</span>
14
+ </a>
15
+ </p>
16
+ </div>
17
+
18
+ <div v-if="updateRuleTask.last?.error">
19
+ <hr />
20
+ <ErrorMessage :error="updateRuleTask.last?.error"></ErrorMessage>
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <script lang="ts">
26
+ import { defineComponent, type PropType, ref } from "vue"
27
+ import { useRouter } from "vue-router"
28
+
29
+ import { generateUpdateRuleTask } from "@/api-helper"
30
+ import ErrorMessage from "@/components/ErrorMessage.vue"
31
+ import InputForm from "@/components/rule/InputForm.vue"
32
+ import type { Rule } from "@/types"
33
+
34
+ export default defineComponent({
35
+ name: "EditRule",
36
+ components: {
37
+ InputForm,
38
+ ErrorMessage
39
+ },
40
+ props: {
41
+ rule: {
42
+ type: Object as PropType<Rule>,
43
+ required: true
44
+ }
45
+ },
46
+ setup(props) {
47
+ const router = useRouter()
48
+
49
+ const yaml = ref(props.rule.yaml)
50
+
51
+ const updateRuleTask = generateUpdateRuleTask()
52
+
53
+ const updateYAML = (value: string) => {
54
+ yaml.value = value
55
+ }
56
+
57
+ const edit = async () => {
58
+ const rule = await updateRuleTask.perform({
59
+ id: props.rule.id,
60
+ yaml: yaml.value
61
+ })
62
+
63
+ router.push({ name: "Rule", params: { id: rule.id } })
64
+ }
65
+
66
+ return {
67
+ edit,
68
+ yaml,
69
+ updateYAML,
70
+ updateRuleTask
71
+ }
72
+ }
73
+ })
74
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <Loading v-if="getRuleTask.isRunning"></Loading>
3
+
4
+ <ErrorMessage v-if="getRuleTask.isError" :error="getRuleTask.last?.error"></ErrorMessage>
5
+
6
+ <EditRule :rule="getRuleTask.last.value" v-if="getRuleTask.last?.value"></EditRule>
7
+ </template>
8
+
9
+ <script lang="ts">
10
+ import { defineComponent, onMounted, watch } from "vue"
11
+
12
+ import { generateGetRuleTask } from "@/api-helper"
13
+ import ErrorMessage from "@/components/ErrorMessage.vue"
14
+ import Loading from "@/components/Loading.vue"
15
+ import EditRule from "@/components/rule/EditRule.vue"
16
+
17
+ export default defineComponent({
18
+ name: "EditRuleWrapper",
19
+ components: {
20
+ EditRule,
21
+ Loading,
22
+ ErrorMessage
23
+ },
24
+ props: {
25
+ id: {
26
+ type: String,
27
+ required: true
28
+ }
29
+ },
30
+ setup(props) {
31
+ const getRuleTask = generateGetRuleTask()
32
+
33
+ const getRule = async () => {
34
+ await getRuleTask.perform(props.id)
35
+ }
36
+
37
+ onMounted(async () => {
38
+ await getRule()
39
+ })
40
+
41
+ watch(props, async () => {
42
+ await getRule()
43
+ })
44
+
45
+ return {
46
+ getRuleTask
47
+ }
48
+ }
49
+ })
50
+ </script>
@@ -0,0 +1,160 @@
1
+ <template>
2
+ <div class="columns">
3
+ <div class="column">
4
+ <div class="field is-horizontal">
5
+ <div class="field-label is-normal">
6
+ <label class="label">Title</label>
7
+ </div>
8
+ <div class="field-body">
9
+ <div class="field">
10
+ <p class="control">
11
+ <input class="input" type="text" v-model="title" />
12
+ </p>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="column">
18
+ <div class="field is-horizontal">
19
+ <div class="field-label is-normal">
20
+ <label class="label">Description</label>
21
+ </div>
22
+ <div class="field-body">
23
+ <div class="field">
24
+ <p class="control">
25
+ <input class="input" type="text" v-model="description" />
26
+ </p>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="columns">
34
+ <div class="column">
35
+ <div class="field is-horizontal">
36
+ <div class="field-label is-normal">
37
+ <label class="label">Tag</label>
38
+ </div>
39
+ <div class="field-body">
40
+ <div class="field">
41
+ <div class="control">
42
+ <div class="select">
43
+ <select v-model="tagInput">
44
+ <option></option>
45
+ <option v-for="tag_ in tags" :key="tag_">
46
+ {{ tag_ }}
47
+ </option>
48
+ </select>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+ <div class="column"></div>
56
+ </div>
57
+
58
+ <div class="columns">
59
+ <div class="column">
60
+ <div class="field is-horizontal">
61
+ <div class="field-label is-normal">
62
+ <label class="label">From</label>
63
+ </div>
64
+ <div class="field-body">
65
+ <div class="field">
66
+ <p class="control">
67
+ <input class="input" type="date" v-model="fromAt" />
68
+ </p>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ <div class="column">
74
+ <div class="field is-horizontal">
75
+ <div class="field-label is-normal">
76
+ <label class="label">To</label>
77
+ </div>
78
+ <div class="field-body">
79
+ <div class="field">
80
+ <p class="control">
81
+ <input class="input" type="date" v-model="toAt" />
82
+ </p>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </template>
89
+
90
+ <script lang="ts">
91
+ import { defineComponent, type PropType, ref, watch } from "vue"
92
+ import { useRoute } from "vue-router"
93
+
94
+ import type { RuleSearchParams } from "@/types"
95
+ import { normalizeQueryParam } from "@/utils"
96
+
97
+ export default defineComponent({
98
+ name: "RulesForm",
99
+ props: {
100
+ tags: {
101
+ type: Array as PropType<string[]>,
102
+ required: true
103
+ },
104
+ page: {
105
+ type: Number,
106
+ required: true
107
+ },
108
+ tag: {
109
+ type: String,
110
+ required: false
111
+ }
112
+ },
113
+ setup(props) {
114
+ const route = useRoute()
115
+
116
+ const description = ref<string | undefined>(undefined)
117
+ const fromAt = ref<string | undefined>(undefined)
118
+ const tagInput = ref<string | undefined>(props.tag)
119
+ const title = ref<string | undefined>(undefined)
120
+ const toAt = ref<string | undefined>(undefined)
121
+
122
+ const updateByQueryParams = () => {
123
+ const tag_ = route.query["tag"]
124
+ if (tagInput.value === undefined) {
125
+ tagInput.value = normalizeQueryParam(tag_)
126
+ }
127
+ }
128
+
129
+ const getSearchParams = (): RuleSearchParams => {
130
+ updateByQueryParams()
131
+
132
+ const params: RuleSearchParams = {
133
+ description: description.value === "" ? undefined : description.value,
134
+ page: props.page,
135
+ tag: tagInput.value === "" ? undefined : tagInput.value,
136
+ title: title.value === "" ? undefined : title.value,
137
+ toAt: toAt.value === "" ? undefined : toAt.value,
138
+ fromAt: fromAt.value === "" ? undefined : fromAt.value
139
+ }
140
+ return params
141
+ }
142
+
143
+ watch(
144
+ () => props.tag,
145
+ () => {
146
+ tagInput.value = props.tag
147
+ }
148
+ )
149
+
150
+ return {
151
+ description,
152
+ fromAt,
153
+ getSearchParams,
154
+ title,
155
+ toAt,
156
+ tagInput
157
+ }
158
+ }
159
+ })
160
+ </script>
@@ -0,0 +1,86 @@
1
+ <template>
2
+ <div class="block my-editor-wrapper" ref="wrapper">
3
+ <PrismEditor
4
+ class="my-editor"
5
+ v-model="yamlInput"
6
+ :highlight="highlighter"
7
+ line-numbers
8
+ ></PrismEditor>
9
+ </div>
10
+ </template>
11
+
12
+ <script lang="ts">
13
+ // eslint-disable-next-line simple-import-sort/imports
14
+ import "vue-prism-editor/dist/prismeditor.min.css"
15
+
16
+ import { defineComponent, ref, watchEffect } from "vue"
17
+ import { PrismEditor } from "vue-prism-editor"
18
+
19
+ import Prism from "prismjs"
20
+
21
+ import "prismjs/components/prism-yaml"
22
+ import "prismjs/plugins/custom-class/prism-custom-class"
23
+ import "prismjs/themes/prism-twilight.css"
24
+
25
+ export default defineComponent({
26
+ name: "RuleInputForm",
27
+ components: {
28
+ PrismEditor
29
+ },
30
+ props: {
31
+ yaml: {
32
+ type: String,
33
+ required: true
34
+ }
35
+ },
36
+ emits: ["update-yaml"],
37
+ setup(props, context) {
38
+ const yamlInput = ref(props.yaml)
39
+ const wrapper = ref<HTMLElement | undefined>(undefined)
40
+
41
+ Prism.plugins.customClass.map({
42
+ number: "prism-number",
43
+ tag: "prism-tag"
44
+ })
45
+
46
+ const highlighter = (code: string) => {
47
+ return Prism.highlight(code, Prism.languages.yaml, "yaml")
48
+ }
49
+
50
+ watchEffect(() => {
51
+ context.emit("update-yaml", yamlInput.value)
52
+
53
+ // TODO: a dirty hack to change the default text color
54
+ if (wrapper.value) {
55
+ const strings = wrapper.value.querySelectorAll(":not(span.token)")
56
+ strings.forEach((string) => {
57
+ ;(string as HTMLElement).style.color = "white"
58
+ })
59
+ }
60
+ })
61
+
62
+ return { yamlInput, highlighter, wrapper }
63
+ }
64
+ })
65
+ </script>
66
+
67
+ <style scoped>
68
+ .my-editor {
69
+ background: hsl(0, 0%, 8%);
70
+ font-family:
71
+ Fira code,
72
+ Fira Mono,
73
+ Consolas,
74
+ Menlo,
75
+ Courier,
76
+ monospace;
77
+ font-size: 1em;
78
+ line-height: 1.5;
79
+ padding: 5px;
80
+ }
81
+
82
+ .my-editor-wrapper {
83
+ background: hsl(0, 0%, 8%);
84
+ padding: 10px;
85
+ }
86
+ </style>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <div class="column">
3
+ <h2 class="is-size-2 mb-4">New rule</h2>
4
+
5
+ <InputForm v-model:yaml="yaml" @update-yaml="updateYAML"></InputForm>
6
+
7
+ <div class="field is-grouped is-grouped-centered">
8
+ <p class="control">
9
+ <a class="button is-primary" @click="create">
10
+ <span class="icon is-small">
11
+ <i class="fas fa-plus"></i>
12
+ </span>
13
+ <span>Create</span>
14
+ </a>
15
+ </p>
16
+ </div>
17
+
18
+ <div v-if="createRuleTask.last?.error">
19
+ <hr />
20
+ <ErrorMessage :error="createRuleTask.last?.error"></ErrorMessage>
21
+ </div>
22
+ </div>
23
+ </template>
24
+
25
+ <script lang="ts">
26
+ import { defineComponent, ref } from "vue"
27
+ import { useRouter } from "vue-router"
28
+
29
+ import { generateCreateRuleTask } from "@/api-helper"
30
+ import ErrorMessage from "@/components/ErrorMessage.vue"
31
+ import InputForm from "@/components/rule/InputForm.vue"
32
+ import { getRuleTemplate } from "@/rule"
33
+
34
+ export default defineComponent({
35
+ name: "NewRule",
36
+ components: {
37
+ InputForm,
38
+ ErrorMessage
39
+ },
40
+ setup() {
41
+ const router = useRouter()
42
+
43
+ const yaml = ref(getRuleTemplate())
44
+
45
+ const createRuleTask = generateCreateRuleTask()
46
+
47
+ const updateYAML = (value: string) => {
48
+ yaml.value = value
49
+ }
50
+
51
+ const create = async () => {
52
+ const rule = await createRuleTask.perform({ yaml: yaml.value })
53
+
54
+ router.push({ name: "Rule", params: { id: rule.id } })
55
+ }
56
+
57
+ return { yaml, create, updateYAML, createRuleTask }
58
+ }
59
+ })
60
+ </script>