main
vue 165 lines 4.97 KB
Raw
1 <template>
2 <n-modal
3 v-model:show="show"
4 preset="card"
5 display-directive="show"
6 :title="entry ? `Import — ${entry.name}` : 'Import library entry'"
7 :style="{ maxWidth: 'min(640px, 92vw)' }"
8 segmented
9 >
10 <template v-if="entry">
11 <div class="flex flex-col gap-3">
12 <n-alert v-if="!result" type="info" :show-icon="false">
13 <div class="text-sm">
14 This will create a new
15 <strong>global</strong>
16 case template (no customer or source scope). If you want a customer-specific version, edit the
17 template after import.
18 </div>
19 </n-alert>
20
21 <div v-if="!result" class="flex flex-col gap-3">
22 <div class="grid grid-cols-2 gap-3">
23 <CardKV>
24 <template #key>Key</template>
25 <template #value>
26 {{ entry.key }}
27 </template>
28 </CardKV>
29 <CardKV>
30 <template #key>Source</template>
31 <template #value>
32 <span v-if="entry.source">{{ entry.source }}</span>
33 <span v-else class="text-secondary"></span>
34 </template>
35 </CardKV>
36 </div>
37
38 <CardKV v-if="entry.match_field && entry.match_value">
39 <template #key>Conditional auto-apply</template>
40 <template #value>
41 Only fires when
42 <code>{{ entry.match_field }}</code>
43 ==
44 <code>{{ entry.match_value }}</code>
45 on the originating Wazuh document.
46 </template>
47 </CardKV>
48
49 <CardKV v-if="entry.description">
50 <template #key>Description</template>
51 <template #value>
52 {{ entry.description }}
53 </template>
54 </CardKV>
55
56 <CardKV value-class="flex flex-col divide-y divide-border p-0!">
57 <template #key>Tasks ({{ entry.tasks.length }})</template>
58 <template #value>
59 <div
60 v-for="(t, idx) of entry.tasks"
61 :key="`${entry.key}-${idx}`"
62 class="flex items-center gap-4 px-4 py-2"
63 >
64 <div class="text-secondary font-mono">{{ t.order_index }}</div>
65 <div class="flex min-w-0 flex-col gap-0.5">
66 <div class="flex items-center gap-2">
67 <div class="font-medium">{{ t.title }}</div>
68 <Badge v-if="t.mandatory" color="warning" size="small">
69 <template #value>mandatory</template>
70 </Badge>
71 </div>
72 <div v-if="t.description" class="text-secondary text-xs">
73 {{ t.description }}
74 </div>
75 </div>
76 </div>
77 </template>
78 </CardKV>
79
80 <div class="flex justify-end gap-2">
81 <n-button size="small" quaternary :disabled="submitting" @click="close">Cancel</n-button>
82 <n-button size="small" type="primary" :loading="submitting" @click="submit">
83 Import as global template
84 </n-button>
85 </div>
86 </div>
87
88 <!-- Success view -->
89 <div v-else class="flex flex-col gap-3">
90 <n-alert type="success" :show-icon="false">
91 <template #header>Template imported</template>
92 <div class="text-sm">
93 <strong>{{ result.name }}</strong>
94 is now in your Templates list. You can apply it to any case from the case detail page, or
95 edit/customize it from the Templates tab.
96 </div>
97 </n-alert>
98 <div class="flex justify-end">
99 <n-button size="small" type="primary" @click="close">Done</n-button>
100 </div>
101 </div>
102 </div>
103 </template>
104 </n-modal>
105 </template>
106
107 <script setup lang="ts">
108 import type { CaseTemplate, CaseTemplateLibraryEntry } from "@/types/incidentManagement/caseTemplates.d"
109 import { NAlert, NButton, NModal, useMessage } from "naive-ui"
110 import { ref, watch } from "vue"
111 import Api from "@/api"
112 import Badge from "@/components/common/Badge.vue"
113 import CardKV from "@/components/common/cards/CardKV.vue"
114
115 const props = defineProps<{
116 entry: CaseTemplateLibraryEntry | null
117 }>()
118
119 const emit = defineEmits<{
120 (e: "imported", template: CaseTemplate): void
121 }>()
122
123 const message = useMessage()
124
125 const show = defineModel<boolean>("show", { required: true, default: false })
126
127 const submitting = ref(false)
128 const result = ref<CaseTemplate | null>(null)
129
130 async function submit() {
131 if (!props.entry) return
132 submitting.value = true
133 try {
134 const res = await Api.incidentManagement.caseTemplates.importLibraryEntry(props.entry.key)
135 if (res.data?.success && res.data.template) {
136 result.value = res.data.template
137 emit("imported", res.data.template)
138 message.success(`Imported '${res.data.template.name}'`)
139 } else {
140 message.warning(res.data?.message || "Failed to import library entry")
141 }
142 } catch (err: any) {
143 // Backend returns HTTP 409 on name collision with a useful detail message.
144 const status = err.response?.status
145 const detail =
146 err.response?.data?.detail || err.response?.data?.message || err.message || "Failed to import library entry"
147 if (status === 409) {
148 message.warning(detail)
149 } else {
150 message.error(detail)
151 }
152 } finally {
153 submitting.value = false
154 }
155 }
156
157 function close() {
158 show.value = false
159 }
160
161 // Reset result whenever the modal opens fresh OR the selected entry changes.
162 watch([show.value, () => props.entry?.key], ([open]) => {
163 if (open) result.value = null
164 })
165 </script>