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,68 @@
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
23
+ class="button is-success is-small ml-1"
24
+ v-if="config.isConfigured"
25
+ >
26
+ <span class="icon is-small">
27
+ <i class="fas fa-check"></i>
28
+ </span>
29
+ <span>Yes</span>
30
+ </button>
31
+ <button class="button is-warning is-small ml-1" v-else>
32
+ <span class="icon is-small">
33
+ <i class="fas fa-exclamation"></i>
34
+ </span>
35
+ <span>No</span>
36
+ </button>
37
+ </td>
38
+ <td>
39
+ <ul>
40
+ <li v-for="(kv, index) in config.values" :key="index">
41
+ <strong> {{ kv.key }} </strong>:
42
+ <code v-if="kv.value">{{ kv.value }}</code>
43
+ <span v-else>N/A</span>
44
+ </li>
45
+ </ul>
46
+ </td>
47
+ </tr>
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
+ <script lang="ts">
55
+ import { defineComponent, PropType } from "vue";
56
+
57
+ import { Config } from "@/types";
58
+
59
+ export default defineComponent({
60
+ name: "ConfigsItem",
61
+ props: {
62
+ configs: {
63
+ type: Array as PropType<Config[]>,
64
+ required: true,
65
+ },
66
+ },
67
+ });
68
+ </script>
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <Loading v-if="getConfigsTask.isRunning"></Loading>
3
+
4
+ <ErrorMessage
5
+ v-if="getConfigsTask.isError"
6
+ :error="getConfigsTask.last?.error"
7
+ ></ErrorMessage>
8
+
9
+ <Configs
10
+ :configs="getConfigsTask.last.value"
11
+ v-if="getConfigsTask.last?.value"
12
+ ></Configs>
13
+ </template>
14
+
15
+ <script lang="ts">
16
+ import { defineComponent, onMounted } from "vue";
17
+
18
+ import { generateGetConfigsTask } from "@/api-helper";
19
+ import Configs from "@/components/config/Configs.vue";
20
+ import ErrorMessage from "@/components/ErrorMessage.vue";
21
+ import Loading from "@/components/Loading.vue";
22
+
23
+ export default defineComponent({
24
+ name: "ConfigsWrapper",
25
+ components: {
26
+ Configs,
27
+ Loading,
28
+ ErrorMessage,
29
+ },
30
+ setup() {
31
+ const getConfigsTask = generateGetConfigsTask();
32
+
33
+ onMounted(async () => {
34
+ await getConfigsTask.perform();
35
+ });
36
+
37
+ return { getConfigsTask };
38
+ },
39
+ });
40
+ </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, PropType } from "vue";
10
+
11
+ import { 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,47 @@
1
+ <template>
2
+ <div class="tags are-medium">
3
+ <LinkComponent
4
+ :data="data"
5
+ :link="link"
6
+ v-for="link in selectedLinks"
7
+ :key="link.name"
8
+ />
9
+ </div>
10
+ </template>
11
+
12
+ <script lang="ts">
13
+ import { computed, defineComponent } from "vue";
14
+
15
+ import LinkComponent from "@/components/link/Link.vue";
16
+ import { Links } from "@/links";
17
+ import { Link } from "@/types";
18
+
19
+ export default defineComponent({
20
+ name: "LinksItem",
21
+ components: {
22
+ LinkComponent,
23
+ },
24
+ props: {
25
+ data: {
26
+ type: String,
27
+ required: true,
28
+ },
29
+ type: {
30
+ type: String,
31
+ required: true,
32
+ },
33
+ },
34
+ setup(props) {
35
+ const links = Links;
36
+ const selectedLinks = computed((): Link[] => {
37
+ if (props.type === undefined) {
38
+ return links;
39
+ }
40
+
41
+ return links.filter((link) => link.type === props.type);
42
+ });
43
+
44
+ return { selectedLinks };
45
+ },
46
+ });
47
+ </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, 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 { 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,56 @@
1
+ <template>
2
+ <Loading v-if="getRuleTask.isRunning"></Loading>
3
+
4
+ <ErrorMessage
5
+ v-if="getRuleTask.isError"
6
+ :error="getRuleTask.last?.error"
7
+ ></ErrorMessage>
8
+
9
+ <EditRule
10
+ :rule="getRuleTask.last.value"
11
+ v-if="getRuleTask.last?.value"
12
+ ></EditRule>
13
+ </template>
14
+
15
+ <script lang="ts">
16
+ import { defineComponent, onMounted, watch } from "vue";
17
+
18
+ import { generateGetRuleTask } from "@/api-helper";
19
+ import ErrorMessage from "@/components/ErrorMessage.vue";
20
+ import Loading from "@/components/Loading.vue";
21
+ import EditRule from "@/components/rule/EditRule.vue";
22
+
23
+ export default defineComponent({
24
+ name: "EditRuleWrapper",
25
+ components: {
26
+ EditRule,
27
+ Loading,
28
+ ErrorMessage,
29
+ },
30
+ props: {
31
+ id: {
32
+ type: String,
33
+ required: true,
34
+ },
35
+ },
36
+ setup(props) {
37
+ const getRuleTask = generateGetRuleTask();
38
+
39
+ const getRule = async () => {
40
+ await getRuleTask.perform(props.id);
41
+ };
42
+
43
+ onMounted(async () => {
44
+ await getRule();
45
+ });
46
+
47
+ watch(props, async () => {
48
+ await getRule();
49
+ });
50
+
51
+ return {
52
+ getRuleTask,
53
+ };
54
+ },
55
+ });
56
+ </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, PropType, ref, watch } from "vue";
92
+ import { useRoute } from "vue-router";
93
+
94
+ import { 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,80 @@
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: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace;
71
+ font-size: 1em;
72
+ line-height: 1.5;
73
+ padding: 5px;
74
+ }
75
+
76
+ .my-editor-wrapper {
77
+ background: hsl(0, 0%, 8%);
78
+ padding: 10px;
79
+ }
80
+ </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>