| 1 | <template> |
| 2 | <n-spin :show="submitting" class="flex min-h-48 grow flex-col" content-class="flex grow flex-col gap-4"> |
| 3 | <n-form ref="formRef" :label-width="80" :model="form" :rules> |
| 4 | <div class="flex flex-col gap-3"> |
| 5 | <div> |
| 6 | <n-form-item label="Description" path="ioc_description"> |
| 7 | <n-input |
| 8 | v-model:value.trim="form.ioc_description" |
| 9 | placeholder="Input the description..." |
| 10 | clearable |
| 11 | type="textarea" |
| 12 | :autosize="{ |
| 13 | minRows: 5, |
| 14 | maxRows: 15 |
| 15 | }" |
| 16 | /> |
| 17 | </n-form-item> |
| 18 | </div> |
| 19 | <div class="flex flex-col gap-3 sm:flex-row"> |
| 20 | <n-form-item label="Type" path="ioc_type" class="basis-1/2"> |
| 21 | <n-select |
| 22 | v-model:value="form.ioc_type" |
| 23 | :options="iocTypeOptions" |
| 24 | placeholder="Select the Type..." |
| 25 | clearable |
| 26 | to="body" |
| 27 | @update:value="form.ioc_value && validate()" |
| 28 | /> |
| 29 | </n-form-item> |
| 30 | <n-form-item label="Value" path="ioc_value" class="basis-1/2"> |
| 31 | <n-input |
| 32 | v-model:value.trim="form.ioc_value" |
| 33 | :disabled="!form.ioc_type" |
| 34 | placeholder="Input the value..." |
| 35 | clearable |
| 36 | /> |
| 37 | </n-form-item> |
| 38 | </div> |
| 39 | |
| 40 | <div class="flex justify-between gap-4"> |
| 41 | <div class="flex gap-4"> |
| 42 | <slot name="additionalActions"></slot> |
| 43 | </div> |
| 44 | <div class="flex gap-4"> |
| 45 | <n-button :disabled="submitting" @click="reset()">Reset</n-button> |
| 46 | <n-button :disabled="!isValid" :loading="submitting" type="primary" @click="validate(submit)"> |
| 47 | Submit |
| 48 | </n-button> |
| 49 | </div> |
| 50 | </div> |
| 51 | </div> |
| 52 | </n-form> |
| 53 | </n-spin> |
| 54 | </template> |
| 55 | |
| 56 | <script setup lang="ts"> |
| 57 | import type { FormInst, FormItemRule, FormRules, FormValidationError } from "naive-ui" |
| 58 | import type { AlertIocPayload } from "@/api/endpoints/incidentManagement/alerts" |
| 59 | import type { DeepNullable } from "@/types/common" |
| 60 | import type { AlertIOC } from "@/types/incidentManagement/alerts.d" |
| 61 | import _get from "lodash/get" |
| 62 | import _trim from "lodash/trim" |
| 63 | import { NButton, NForm, NFormItem, NInput, NSelect, NSpin, useMessage } from "naive-ui" |
| 64 | import isIP from "validator/es/lib/isIP" |
| 65 | import isURL from "validator/es/lib/isURL" |
| 66 | import { computed, onMounted, ref, toRefs, watch } from "vue" |
| 67 | import Api from "@/api" |
| 68 | |
| 69 | const props = defineProps<{ alertId: number }>() |
| 70 | const emit = defineEmits<{ |
| 71 | (e: "update:loading", value: boolean): void |
| 72 | (e: "submitted", value: AlertIOC): void |
| 73 | ( |
| 74 | e: "mounted", |
| 75 | value: { |
| 76 | reset: () => void |
| 77 | } |
| 78 | ): void |
| 79 | }>() |
| 80 | |
| 81 | const { alertId } = toRefs(props) |
| 82 | const submitting = ref(false) |
| 83 | const message = useMessage() |
| 84 | const form = ref<DeepNullable<AlertIocPayload>>(getForm()) |
| 85 | const formRef = ref<FormInst | null>(null) |
| 86 | |
| 87 | const rules: FormRules = { |
| 88 | ioc_value: { |
| 89 | validator: validateValue, |
| 90 | required: true, |
| 91 | trigger: ["input", "blur"] |
| 92 | }, |
| 93 | ioc_type: { |
| 94 | message: "Please input the Type", |
| 95 | required: true, |
| 96 | trigger: ["input", "blur"] |
| 97 | }, |
| 98 | ioc_description: { |
| 99 | message: "Please input the Description", |
| 100 | required: true, |
| 101 | trigger: ["input", "blur"] |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | const iocTypeOptions: { label: string; value: string }[] = [ |
| 106 | { label: "IP", value: "IP" }, |
| 107 | { label: "DOMAIN", value: "DOMAIN" }, |
| 108 | { label: "HASH", value: "HASH" }, |
| 109 | { label: "URL", value: "URL" } |
| 110 | ] |
| 111 | |
| 112 | const isValid = computed(() => { |
| 113 | let valid = true |
| 114 | |
| 115 | for (const key in rules) { |
| 116 | const rule = rules[key] as FormRules |
| 117 | |
| 118 | if (rule.required && !_trim(_get(form.value, key))) { |
| 119 | valid = false |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | return valid |
| 124 | }) |
| 125 | |
| 126 | function validateValue(_rule: FormItemRule, value: string) { |
| 127 | if (!value) { |
| 128 | return new Error("Please input a valid Value") |
| 129 | } |
| 130 | |
| 131 | switch (form.value.ioc_type) { |
| 132 | case "URL": |
| 133 | if (!isURL(value, { require_tld: false })) { |
| 134 | return new Error("Please input a valid URL") |
| 135 | } |
| 136 | break |
| 137 | case "DOMAIN": |
| 138 | if (!isURL(value, { require_tld: false })) { |
| 139 | return new Error("Please input a valid DOMAIN") |
| 140 | } |
| 141 | break |
| 142 | case "IP": |
| 143 | if (!isIP(value)) { |
| 144 | return new Error("Please input a valid IP Address") |
| 145 | } |
| 146 | break |
| 147 | } |
| 148 | |
| 149 | return true |
| 150 | } |
| 151 | |
| 152 | function validate(cb?: () => void) { |
| 153 | if (!formRef.value) return |
| 154 | |
| 155 | formRef.value.validate((errors?: Array<FormValidationError>) => { |
| 156 | if (!errors) { |
| 157 | if (cb && typeof cb === "function") { |
| 158 | cb() |
| 159 | } |
| 160 | } else { |
| 161 | message.warning("You must fill in the required fields correctly.") |
| 162 | return false |
| 163 | } |
| 164 | }) |
| 165 | } |
| 166 | |
| 167 | function getForm() { |
| 168 | const payload = { |
| 169 | alert_id: alertId.value, |
| 170 | ioc_value: null, |
| 171 | ioc_type: null, |
| 172 | ioc_description: null |
| 173 | } |
| 174 | return payload |
| 175 | } |
| 176 | |
| 177 | function reset(force?: boolean) { |
| 178 | if (!submitting.value || force) { |
| 179 | resetForm() |
| 180 | formRef.value?.restoreValidation() |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | function resetForm() { |
| 185 | form.value = getForm() |
| 186 | } |
| 187 | |
| 188 | function submit() { |
| 189 | submitting.value = true |
| 190 | |
| 191 | Api.incidentManagement.alerts |
| 192 | .createAlertIoc(form.value as AlertIocPayload) |
| 193 | .then(res => { |
| 194 | if (res.data.success) { |
| 195 | emit("submitted", { |
| 196 | id: res.data.alert_ioc.ioc_id, |
| 197 | description: form.value.ioc_description || "", |
| 198 | type: form.value.ioc_type || "", |
| 199 | value: form.value.ioc_value || "" |
| 200 | }) |
| 201 | reset() |
| 202 | } else { |
| 203 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 204 | } |
| 205 | }) |
| 206 | .catch(err => { |
| 207 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 208 | }) |
| 209 | .finally(() => { |
| 210 | submitting.value = false |
| 211 | }) |
| 212 | } |
| 213 | |
| 214 | watch(submitting, val => { |
| 215 | emit("update:loading", val) |
| 216 | }) |
| 217 | |
| 218 | onMounted(() => { |
| 219 | emit("mounted", { |
| 220 | reset |
| 221 | }) |
| 222 | }) |
| 223 | </script> |