| 1 | <template> |
| 2 | <n-spin :show="loading" class="source-configuration-wizard"> |
| 3 | <div class="flex min-h-48 flex-col"> |
| 4 | <div class="flex grow flex-col"> |
| 5 | <n-scrollbar x-scrollable trigger="none"> |
| 6 | <div class="p-6 pt-3"> |
| 7 | <n-steps :current size="small" :status="currentStatus"> |
| 8 | <n-step title="Source" /> |
| 9 | <n-step title="Configuration" /> |
| 10 | </n-steps> |
| 11 | </div> |
| 12 | </n-scrollbar> |
| 13 | |
| 14 | <div class="mt-4 flex grow flex-col"> |
| 15 | <Transition :name="`slide-form-${slideFormDirection}`"> |
| 16 | <div v-if="current === 1" class="flex flex-col gap-4 px-7"> |
| 17 | <n-select |
| 18 | v-model:value="selectedIndex" |
| 19 | placeholder="Indices list" |
| 20 | clearable |
| 21 | filterable |
| 22 | to="body" |
| 23 | :loading="loadingIndices" |
| 24 | :options="indexNamesOptions" |
| 25 | @update:value="setSourceConfiguration(selectedIndex)" |
| 26 | ></n-select> |
| 27 | |
| 28 | <div class="flex justify-end gap-4"> |
| 29 | <n-button @click="overrideMode()">Manual override</n-button> |
| 30 | <n-button |
| 31 | v-if="isNextStepEnabled" |
| 32 | icon-placement="right" |
| 33 | :disabled="!selectedIndex" |
| 34 | @click="standardMode()" |
| 35 | > |
| 36 | <template #icon> |
| 37 | <Icon :name="ArrowRightIcon" /> |
| 38 | </template> |
| 39 | Next |
| 40 | </n-button> |
| 41 | </div> |
| 42 | </div> |
| 43 | |
| 44 | <div v-else-if="current === 2" class="flex min-h-[401px] grow flex-col px-7 pb-7"> |
| 45 | <SourceConfigurationForm |
| 46 | v-if="sourceConfigurationModel" |
| 47 | :source-configuration-model |
| 48 | show-source-field |
| 49 | disable-source-field |
| 50 | :show-index-name-field |
| 51 | :arbitrary-source-field |
| 52 | disable-index-name-field |
| 53 | :disabled-sources |
| 54 | @mounted="formCTX = $event" |
| 55 | @submitted="createSourceConfiguration($event)" |
| 56 | > |
| 57 | <template #additionalActions> |
| 58 | <n-button v-if="isPrevStepEnabled" :disabled="submitting" @click="prev()"> |
| 59 | <template #icon> |
| 60 | <Icon :name="ArrowLeftIcon" /> |
| 61 | </template> |
| 62 | Prev |
| 63 | </n-button> |
| 64 | </template> |
| 65 | </SourceConfigurationForm> |
| 66 | </div> |
| 67 | </Transition> |
| 68 | </div> |
| 69 | </div> |
| 70 | </div> |
| 71 | </n-spin> |
| 72 | </template> |
| 73 | |
| 74 | <script setup lang="ts"> |
| 75 | import type { StepsProps } from "naive-ui" |
| 76 | import type { ApiError } from "@/types/common.d" |
| 77 | import type { SourceConfiguration, SourceConfigurationModel, SourceName } from "@/types/incidentManagement/sources.d" |
| 78 | import { NButton, NScrollbar, NSelect, NSpin, NStep, NSteps, useMessage } from "naive-ui" |
| 79 | import { computed, onBeforeMount, onMounted, ref, toRefs, watch } from "vue" |
| 80 | import Api from "@/api" |
| 81 | import Icon from "@/components/common/Icon.vue" |
| 82 | import SourceConfigurationForm from "./SourceConfigurationForm.vue" |
| 83 | |
| 84 | const props = defineProps<{ disabledSources?: SourceName[] }>() |
| 85 | |
| 86 | const emit = defineEmits<{ |
| 87 | (e: "update:loading", value: boolean): void |
| 88 | (e: "submitted"): void |
| 89 | ( |
| 90 | e: "mounted", |
| 91 | value: { |
| 92 | reset: () => void |
| 93 | } |
| 94 | ): void |
| 95 | }>() |
| 96 | |
| 97 | const { disabledSources } = toRefs(props) |
| 98 | |
| 99 | const ArrowLeftIcon = "carbon:arrow-left" |
| 100 | const ArrowRightIcon = "carbon:arrow-right" |
| 101 | const loadingIndices = ref(false) |
| 102 | const submitting = ref(false) |
| 103 | const loading = computed(() => submitting.value) |
| 104 | const slideFormDirection = ref<"right" | "left">("right") |
| 105 | const message = useMessage() |
| 106 | const current = ref<number>(1) |
| 107 | const currentStatus = ref<StepsProps["status"]>("process") |
| 108 | const formCTX = ref<{ reset: () => void; toggleSubmittingFlag: () => boolean } | null>(null) |
| 109 | const selectedIndex = ref<string | null>(null) |
| 110 | const sourceConfigurationModel = ref<SourceConfigurationModel | null>(null) |
| 111 | const indexNamesOptions = ref<{ label: string; value: string }[]>([]) |
| 112 | |
| 113 | watch(loading, val => { |
| 114 | emit("update:loading", val) |
| 115 | }) |
| 116 | |
| 117 | const isPrevStepEnabled = computed(() => current.value > 1) |
| 118 | const isNextStepEnabled = computed(() => current.value < 2) |
| 119 | const showIndexNameField = ref(true) |
| 120 | const arbitrarySourceField = ref(false) |
| 121 | |
| 122 | function overrideMode() { |
| 123 | showIndexNameField.value = false |
| 124 | arbitrarySourceField.value = true |
| 125 | setSourceConfiguration(null) |
| 126 | next() |
| 127 | } |
| 128 | |
| 129 | function standardMode() { |
| 130 | showIndexNameField.value = true |
| 131 | arbitrarySourceField.value = false |
| 132 | next() |
| 133 | } |
| 134 | |
| 135 | function next() { |
| 136 | currentStatus.value = "process" |
| 137 | slideFormDirection.value = "right" |
| 138 | current.value++ |
| 139 | } |
| 140 | |
| 141 | function prev() { |
| 142 | currentStatus.value = "process" |
| 143 | slideFormDirection.value = "left" |
| 144 | current.value-- |
| 145 | formCTX.value?.reset() |
| 146 | } |
| 147 | |
| 148 | function reset(force?: boolean) { |
| 149 | if (!submitting.value || force) { |
| 150 | currentStatus.value = "process" |
| 151 | slideFormDirection.value = "right" |
| 152 | current.value = 1 |
| 153 | selectedIndex.value = null |
| 154 | sourceConfigurationModel.value = null |
| 155 | formCTX.value?.reset() |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | function setSourceConfiguration(indexName: string | null) { |
| 160 | if (indexName) { |
| 161 | sourceConfigurationModel.value = { |
| 162 | field_names: [], |
| 163 | ioc_field_names: [], |
| 164 | asset_name: null, |
| 165 | timefield_name: null, |
| 166 | alert_title_name: null, |
| 167 | source: "", |
| 168 | index_name: indexName |
| 169 | } |
| 170 | next() |
| 171 | } else { |
| 172 | sourceConfigurationModel.value = { |
| 173 | field_names: [], |
| 174 | ioc_field_names: [], |
| 175 | asset_name: null, |
| 176 | timefield_name: null, |
| 177 | alert_title_name: null, |
| 178 | source: "", |
| 179 | index_name: "" |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | function getIndices() { |
| 185 | loadingIndices.value = true |
| 186 | |
| 187 | Api.graylog |
| 188 | .getIndices() |
| 189 | .then(res => { |
| 190 | if (res.data.success) { |
| 191 | indexNamesOptions.value = (res.data?.indices || []).map(o => ({ |
| 192 | label: o.index_name, |
| 193 | value: o.index_name |
| 194 | })) |
| 195 | } else { |
| 196 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 197 | } |
| 198 | }) |
| 199 | .catch(err => { |
| 200 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 201 | }) |
| 202 | .finally(() => { |
| 203 | loadingIndices.value = false |
| 204 | }) |
| 205 | } |
| 206 | |
| 207 | function createSourceConfiguration(payload: SourceConfiguration) { |
| 208 | submitting.value = formCTX.value?.toggleSubmittingFlag() || true |
| 209 | |
| 210 | Api.incidentManagement.sources |
| 211 | .createSourceConfiguration(payload) |
| 212 | .then(res => { |
| 213 | if (res.data.success) { |
| 214 | message.success(res.data?.message || `Source Configuration sent successfully`) |
| 215 | reset(true) |
| 216 | emit("submitted") |
| 217 | } else { |
| 218 | message.warning(res.data?.message || "An error occurred. Please try again later.") |
| 219 | } |
| 220 | }) |
| 221 | .catch((err: ApiError) => { |
| 222 | message.error(err.response?.data?.message || "An error occurred. Please try again later.") |
| 223 | }) |
| 224 | .finally(() => { |
| 225 | submitting.value = formCTX.value?.toggleSubmittingFlag() || false |
| 226 | }) |
| 227 | } |
| 228 | |
| 229 | onBeforeMount(() => { |
| 230 | getIndices() |
| 231 | }) |
| 232 | |
| 233 | onMounted(() => { |
| 234 | emit("mounted", { |
| 235 | reset |
| 236 | }) |
| 237 | }) |
| 238 | </script> |
| 239 | |
| 240 | <style lang="scss" scoped> |
| 241 | .source-configuration-wizard { |
| 242 | .slide-form-right-enter-active, |
| 243 | .slide-form-right-leave-active, |
| 244 | .slide-form-left-enter-active, |
| 245 | .slide-form-left-leave-active { |
| 246 | transition: all 0.2s ease-out; |
| 247 | position: absolute; |
| 248 | width: 100%; |
| 249 | } |
| 250 | |
| 251 | .slide-form-left-enter-from { |
| 252 | transform: translateX(-100%); |
| 253 | } |
| 254 | |
| 255 | .slide-form-left-leave-to { |
| 256 | transform: translateX(100%); |
| 257 | } |
| 258 | |
| 259 | .slide-form-right-enter-from { |
| 260 | transform: translateX(100%); |
| 261 | } |
| 262 | |
| 263 | .slide-form-right-leave-to { |
| 264 | transform: translateX(-100%); |
| 265 | } |
| 266 | } |
| 267 | </style> |