main
vue 156 lines 4.08 KB
Raw
1 <template>
2 <div class="flex flex-col gap-4">
3 <!-- Search Filters -->
4 <div class="flex items-center gap-2">
5 <n-input v-model:value="searchQuery" size="small" placeholder="Search rules..." class="grow" clearable>
6 <template #prefix>
7 <Icon :name="SearchIcon" />
8 </template>
9 </n-input>
10
11 <n-select
12 v-model:value="selectedPlatform"
13 :options="platformOptions"
14 size="small"
15 placeholder="All Platforms"
16 class="max-w-35"
17 clearable
18 :consistent-menu-width="false"
19 />
20 </div>
21
22 <n-card content-class="p-0!">
23 <!-- Rules List -->
24 <n-spin :show="loadingRules" class="min-h-50">
25 <n-scrollbar
26 v-if="filteredRules.length"
27 class="max-h-90 p-3!"
28 trigger="none"
29 :theme-overrides="{
30 railInsetVerticalRight: `4px 4px 4px auto`
31 }"
32 >
33 <div class="flex flex-col gap-3">
34 <CardEntity
35 v-for="rule in filteredRules"
36 :key="rule.id"
37 size="small"
38 hoverable
39 embedded
40 clickable
41 :highlighted="selectedRule?.id === rule.id"
42 @click="selectRule(rule)"
43 >
44 <template #headerMain>
45 <PlatformBadge :platform="rule.platform" class="text-default" />
46 </template>
47 <template #headerExtra>
48 <SeverityBadge :severity="rule.severity" />
49 </template>
50 <template #mainExtra>
51 <div class="flex flex-col gap-1">
52 <span class="font-medium">{{ rule.name }}</span>
53 <span class="line-clamp-2 text-sm opacity-70">{{ rule.description }}</span>
54 </div>
55 </template>
56 </CardEntity>
57 </div>
58 </n-scrollbar>
59
60 <n-empty v-else-if="!loadingRules" description="No rules found" class="py-20" />
61 </n-spin>
62 </n-card>
63
64 <n-modal
65 :show="!!selectedRule"
66 :mask-closable="false"
67 :title="selectedRule?.name"
68 segmented
69 preset="card"
70 :style="{ maxWidth: 'min(550px, 90vw)', minHeight: 'min(300px, 90vh)', overflow: 'hidden' }"
71 @close="selectedRule = null"
72 >
73 <ExecuteSearchForm v-if="selectedRule" :rule-summary="selectedRule" :asset />
74 </n-modal>
75 </div>
76 </template>
77
78 <script setup lang="ts">
79 import type { PlatformFilter, RuleSummary } from "@/types/copilotSearches.d"
80 import type { AlertAsset } from "@/types/incidentManagement/alerts.d"
81 import { NCard, NEmpty, NInput, NModal, NScrollbar, NSelect, NSpin, useMessage } from "naive-ui"
82 import { computed, onBeforeMount, ref, watch } from "vue"
83 import Api from "@/api"
84 import CardEntity from "@/components/common/cards/CardEntity.vue"
85 import Icon from "@/components/common/Icon.vue"
86 import PlatformBadge from "@/components/common/PlatformBadge.vue"
87 import ExecuteSearchForm from "./ExecuteSearchForm.vue"
88 import SeverityBadge from "./SeverityBadge.vue"
89
90 const { asset } = defineProps<{
91 asset: AlertAsset
92 }>()
93
94 const message = useMessage()
95 const SearchIcon = "carbon:search"
96
97 // State
98 const loadingRules = ref(false)
99 const rules = ref<RuleSummary[]>([])
100 const selectedRule = ref<RuleSummary | null>(null)
101
102 // Filters
103 const selectedPlatform = ref<PlatformFilter | null>(null)
104 const searchQuery = ref<string | null>(null)
105
106 const platformOptions = [
107 { label: "Linux", value: "linux" },
108 { label: "Windows", value: "windows" }
109 ]
110
111 // Computed
112 const filteredRules = computed(() => {
113 let result = rules.value
114
115 if (selectedPlatform.value) {
116 result = result.filter(r => r.platform === selectedPlatform.value)
117 }
118
119 if (searchQuery.value) {
120 const query = searchQuery.value.toLowerCase()
121 result = result.filter(r => r.name.toLowerCase().includes(query) || r.description.toLowerCase().includes(query))
122 }
123
124 return result
125 })
126
127 // Methods
128 async function loadRules() {
129 loadingRules.value = true
130
131 try {
132 const res = await Api.copilotSearches.getRules({ limit: 100 })
133 if (res.data.success) {
134 rules.value = res.data.rules
135 }
136 } catch (err: any) {
137 message.error(err.response?.data?.message || "Failed to load rules")
138 } finally {
139 loadingRules.value = false
140 }
141 }
142
143 function selectRule(rule: RuleSummary) {
144 selectedRule.value = rule
145 }
146
147 // Lifecycle
148 onBeforeMount(() => {
149 loadRules()
150 })
151
152 // Watch for platform changes to reload
153 watch(selectedPlatform, () => {
154 selectedRule.value = null
155 })
156 </script>