@cryptotaxi247 / CoPilot / commits / d8c53b2e

Monitoring alerts (#202)

* Update content pack names in graylog/schema/provision.py * Update wazuh.py to remove commented out code * added monitoring alerts api * added goto composable * added Soc-PendingAlerts page * added monitoring alert type * refactor goto functions * refactor goto agent func * refactor goto index func * refactor goto func * refactor goto func * updated monitoring api/types * improved style * added monitoring alerts components * updated todo * updated monitoring alert components * uncomment non alert removal --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed Apr 27, 2024 at 17:24 UTC d8c53b2e8170a7837c42d018f34cbbec265cd54f
37 files changed +631 -181
backend/app/stack_provisioning/graylog/schema/provision.py
+4 -4
@@ -14,10 +14,10 @@ class AvailableContentPacks(str, Enum):
14 " Pipelines, and Lookup Tables for Wazuh logs and the SOCFortress SIEM stack."
15 )
16 # ! COMMENTING OUT UNTIL READY ! #
17 - # SOCFORTRESS_FORTINET_INPUT_SYSLOG_TCP = "The Fortinet Input Syslog TCP content pack"
18 - # SOCFORTRESS_FORTINET_INPUT_SYSLOG_UDP = "The Fortinet Input Syslog UDP content pack"
19 - # SOCFORTRESS_FORTINET_PROCESSING_PIPELINE = "The Fortinet Processing Pipeline content pack"
20 - # SOCFORTRESS_FORTINET_STREAM = "The Fortinet Stream content pack"
17 + SOCFORTRESS_FORTINET_INPUT_SYSLOG_TCP = "The Fortinet Input Syslog TCP content pack"
18 + SOCFORTRESS_FORTINET_INPUT_SYSLOG_UDP = "The Fortinet Input Syslog UDP content pack"
19 + SOCFORTRESS_FORTINET_PROCESSING_PIPELINE = "The Fortinet Processing Pipeline content pack"
20 + SOCFORTRESS_FORTINET_STREAM = "The Fortinet Stream content pack"
21
22
23 class ContentPackKeywords(BaseModel):
frontend/src/api/monitoringAlerts.ts
+13 -1
@@ -1,6 +1,6 @@
1 import { type FlaskBaseResponse } from "@/types/flask.d"
2 import { HttpClient } from "./httpClient"
3 -import type { AvailableMonitoringAlert } from "@/types/monitoringAlerts.d"
3 +import type { AvailableMonitoringAlert, MonitoringAlert } from "@/types/monitoringAlerts.d"
4
5 export interface ProvisionsMonitoringAlertParams {
6 searchWithinLast: number
@@ -42,5 +42,17 @@ export default {
42 },
43 customProvision(payload: CustomProvisionPayload) {
44 return HttpClient.post<FlaskBaseResponse>(`/monitoring_alert/provision/custom`, payload)
45 + },
46 + listAll(signal?: AbortSignal) {
47 + return HttpClient.get<FlaskBaseResponse & { monitoring_alerts: MonitoringAlert[] }>(
48 + `/monitoring_alert/list`,
49 + signal ? { signal } : {}
50 + )
51 + },
52 + invoke(alertId: number) {
53 + return HttpClient.post<FlaskBaseResponse>(`/monitoring_alert/invoke/${alertId}`)
54 + },
55 + delete(alertId: number) {
56 + return HttpClient.delete<FlaskBaseResponse>(`/monitoring_alert/${alertId}`)
57 }
58 }
frontend/src/assets/scss/helpers.scss
+1 -1
@@ -69,7 +69,7 @@
69 }
70 }
71
72 -.overlay {
72 +.over-layer {
73 background-color: rgba(var(--bg-body-rgb), 0.8);
74 position: absolute;
75 top: 0;
frontend/src/components/agents/OverviewSection.vue
+3 -7
@@ -7,7 +7,7 @@
7 <template v-if="item.key === 'customer_code'">
8 <code
9 class="cursor-pointer text-primary-color"
10 - @click="gotoCustomer(item.val)"
10 + @click="gotoCustomer({ code: item.val })"
11 v-if="item.val && item.val !== '-'"
12 >
13 {{ item.val }}
@@ -31,7 +31,7 @@ import { type Agent } from "@/types/agents.d"
31 import { useSettingsStore } from "@/stores/settings"
32 import KVCard from "@/components/common/KVCard.vue"
33 import Icon from "@/components/common/Icon.vue"
34 -import { useRouter } from "vue-router"
34 +import { useGoto } from "@/composables/useGoto"
35
36 const props = defineProps<{
37 agent: Agent
@@ -39,8 +39,8 @@ const props = defineProps<{
39 const { agent } = toRefs(props)
40
41 const LinkIcon = "carbon:launch"
42 -const router = useRouter()
42 const dFormats = useSettingsStore().dateFormat
43 +const { gotoCustomer } = useGoto()
44
45 const propsSanitized = computed(() => {
46 const obj = []
@@ -56,10 +56,6 @@ const propsSanitized = computed(() => {
56
57 return obj
58 })
59 -
60 -function gotoCustomer(code: string | number) {
61 - router.push({ name: "Customers", query: { code } })
62 -}
59 </script>
60
61 <style lang="scss" scoped>
frontend/src/components/alerts/Alert.vue
+9 -14
@@ -47,7 +47,7 @@
47 agent_id:
48 <code
49 class="cursor-pointer text-primary-color"
50 - @click="gotoAgentPage(alert._source.agent_id)"
50 + @click="gotoAgent(alert._source.agent_id)"
51 >
52 {{ alert._source.agent_id }}
53 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -65,7 +65,7 @@
65 agent_labels_customer:
66 <code
67 class="cursor-pointer text-primary-color"
68 - @click="gotoCustomer(alert._source.agent_labels_customer)"
68 + @click="gotoCustomer({ code: alert._source.agent_labels_customer })"
69 >
70 {{ alert._source.agent_labels_customer }}
71 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -131,13 +131,16 @@
131 <template #key>{{ key }}</template>
132 <template #value>
133 <template v-if="key === 'agent_id'">
134 - <code class="cursor-pointer text-primary-color" @click="gotoAgentPage(value + '')">
134 + <code class="cursor-pointer text-primary-color" @click="gotoAgent(value + '')">
135 {{ value }}
136 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
137 </code>
138 </template>
139 <template v-else-if="key === 'agent_labels_customer'">
140 - <code class="cursor-pointer text-primary-color" @click="gotoCustomer(value + '')">
140 + <code
141 + class="cursor-pointer text-primary-color"
142 + @click="gotoCustomer(value ? { code: value.toString() } : undefined)"
143 + >
144 {{ value }}
145 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
146 </code>
@@ -226,9 +229,9 @@ const AlertActions = defineAsyncComponent(() => import("./AlertActions.vue"))
229 import type { Alert } from "@/types/alerts.d"
230 import { SimpleJsonViewer } from "vue-sjv"
231 import "@/assets/scss/vuesjv-override.scss"
229 -import { useRouter } from "vue-router"
232 import _pick from "lodash/pick"
233 import KVCard from "@/components/common/KVCard.vue"
234 +import { useGoto } from "@/composables/useGoto"
235
236 const props = defineProps<{ alert: Alert; hideActions?: boolean }>()
237 const { alert, hideActions } = toRefs(props)
@@ -240,7 +243,7 @@ const MailIcon = "carbon:email"
243 const AgentIcon = "carbon:police"
244 const LinkIcon = "carbon:launch"
245
243 -const router = useRouter()
246 +const { gotoCustomer, gotoAgent } = useGoto()
247 const loading = ref(false)
248 const showDetails = ref(false)
249 const dFormats = useSettingsStore().dateFormat
@@ -257,14 +260,6 @@ const agentProperties = computed(() => {
260 "agent_name"
261 ])
262 })
260 -
261 -function gotoAgentPage(agentId: string) {
262 - router.push({ name: "Agent", params: { id: agentId } })
263 -}
264 -
265 -function gotoCustomer(code: string | number) {
266 - router.push({ name: "Customers", query: { code } })
267 -}
263 </script>
264
265 <style lang="scss" scoped>
frontend/src/components/alerts/AlertsSummary.vue
+3 -7
@@ -1,7 +1,7 @@
1 <template>
2 <div class="alert-summary flex flex-col">
3 <div class="header-box flex justify-between gap-4">
4 - <div class="id flex items-center gap-2" @click="gotoIndicesPage(alertsSummary.index_name)">
4 + <div class="id flex items-center gap-2" @click="gotoIndex(alertsSummary.index_name)">
5 <IndexIcon :health="alertsSummary.indexStats?.health" color v-if="alertsSummary.indexStats?.health" />
6 <Icon :name="PlaceholderIcon" v-else :size="18" />
7
@@ -50,8 +50,8 @@ import Alert from "./Alert.vue"
50 import IndexIcon from "@/components/indices/IndexIcon.vue"
51 import Icon from "@/components/common/Icon.vue"
52 import type { IndexStats } from "@/types/indices.d"
53 -import { useRouter } from "vue-router"
53 import { ref } from "vue"
54 +import { useGoto } from "@/composables/useGoto"
55
56 export interface AlertsSummaryExt extends AlertsSummary {
57 indexStats?: IndexStats
@@ -63,12 +63,8 @@ const ExpandIcon = "carbon:chevron-down"
63 const PlaceholderIcon = "ph:question"
64 const LinkIcon = "carbon:launch"
65
66 -const router = useRouter()
66 const showAllAlerts = ref(false)
68 -
69 -function gotoIndicesPage(index: string) {
70 - router.push({ name: "Indices", query: { index_name: index } })
71 -}
67 +const { gotoIndex } = useGoto()
68 </script>
69
70 <style lang="scss" scoped>
frontend/src/components/common/Badge.vue
+1
@@ -17,6 +17,7 @@
17 </template>
18
19 <script setup lang="ts">
20 +// TODO: refactor
21 const { type, hintCursor, pointCursor, color, href, fluid } = defineProps<{
22 type?: "splitted" | "muted" | "active" | "cursor"
23 hintCursor?: boolean
frontend/src/components/common/SearchDialog.vue
+6 -7
@@ -75,13 +75,13 @@ import { computed, onMounted, ref } from "vue"
75 import { NText, NModal, NCard, NDivider, NAvatar, NScrollbar, type ScrollbarInst } from "naive-ui"
76 import { useMagicKeys, whenever } from "@vueuse/core"
77 import Highlighter from "vue-highlight-words"
78 -import { useRouter } from "vue-router"
78 import { useThemeSwitch } from "@/composables/useThemeSwitch"
79 import { useFullscreenSwitch } from "@/composables/useFullscreenSwitch"
80 import { useSearchDialog } from "@/composables/useSearchDialog"
81 import { getOS } from "@/utils"
82 import Icon from "@/components/common/Icon.vue"
83 import { emitter } from "@/emitter"
84 +import { useGoto } from "@/composables/useGoto"
85
86 const SearchIcon = "ion:search-outline"
87 const ArrowEnterIcon = "fluent:arrow-enter-left-24-regular"
@@ -111,13 +111,12 @@ interface Group {
111 }
112 type Groups = Group[]
113
114 -const router = useRouter()
115 -
114 const showSearchBox = ref(false)
115 const search = ref("")
116 const activeItem = ref<null | string | number>(null)
117 const commandIcon = ref("⌘")
118 const scrollContent = ref<(ScrollbarInst & { $el: any }) | null>(null)
119 +const { gotoCustomer, gotoSocAlerts, gotoAlerts, gotoConnectors } = useGoto()
120
121 const groups = ref<Groups>([
122 {
@@ -130,7 +129,7 @@ const groups = ref<Groups>([
129 title: "Add a Customer",
130 label: "Shortcut",
131 action() {
133 - router.push({ name: "Customers", query: { action: "add-customer" } })
132 + gotoCustomer({ action: "add-customer" })
133 emitter.emit("action:add-customer")
134 }
135 },
@@ -141,7 +140,7 @@ const groups = ref<Groups>([
140 title: "Configure a connector",
141 label: "Shortcut",
142 action() {
144 - router.push({ name: "Connectors" })
143 + gotoConnectors()
144 }
145 },
146 {
@@ -151,7 +150,7 @@ const groups = ref<Groups>([
150 title: "View Escalated Alerts",
151 label: "Shortcut",
152 action() {
154 - router.push({ name: "Soc-Alerts" })
153 + gotoSocAlerts()
154 }
155 },
156 {
@@ -161,7 +160,7 @@ const groups = ref<Groups>([
160 title: "View Identified Alerts",
161 label: "Shortcut",
162 action() {
164 - router.push({ name: "Alerts" })
163 + gotoAlerts()
164 }
165 }
166 ]
frontend/src/components/customers/CustomerAgents.vue
+3 -7
@@ -8,7 +8,7 @@
8 bg-secondary
9 show-actions
10 @delete="getAgents()"
11 - @click="gotoAgentPage(agent)"
11 + @click="gotoAgent(agent.agent_id)"
12 class="item-appear item-appear-bottom item-appear-005"
13 />
14 <n-empty v-if="!list.length" description="No Agents found" class="justify-center h-48" />
@@ -22,8 +22,8 @@ import AgentCard from "@/components/agents/AgentCard.vue"
22 import Api from "@/api"
23 import { useMessage, NSpin, NEmpty } from "naive-ui"
24 import type { Customer } from "@/types/customers.d"
25 -import { useRouter } from "vue-router"
25 import type { Agent } from "@/types/agents.d"
26 +import { useGoto } from "@/composables/useGoto"
27
28 const props = defineProps<{
29 customer: Customer
@@ -31,14 +31,10 @@ const props = defineProps<{
31 const { customer } = toRefs(props)
32
33 const loading = ref(false)
34 -const router = useRouter()
34 +const { gotoAgent } = useGoto()
35 const message = useMessage()
36 const list = ref<Agent[] | []>([])
37
38 -function gotoAgentPage(agent: Agent) {
39 - router.push({ name: "Agent", params: { id: agent.agent_id } })
40 -}
41 -
38 function getAgents() {
39 loading.value = true
40
frontend/src/components/customers/healthcheck/CustomerHealthcheckItem.vue
+3 -7
@@ -49,7 +49,7 @@
49 <div class="flex flex-col gap-1">
50 <div class="box">
51 agent_id:
52 - <code class="cursor-pointer text-primary-color" @click="gotoAgentPage(healthData.agent_id)">
52 + <code class="cursor-pointer text-primary-color" @click="gotoAgent(healthData.agent_id)">
53 {{ healthData.agent_id }}
54 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
55 </code>
@@ -92,7 +92,7 @@ import type { CustomerAgentHealth, CustomerHealthcheckSource } from "@/types/cus
92 import dayjs from "@/utils/dayjs"
93 import { iconFromOs } from "@/utils"
94 import { useSettingsStore } from "@/stores/settings"
95 -import { useRouter } from "vue-router"
95 +import { useGoto } from "@/composables/useGoto"
96
97 const { healthData, source, bgSecondary, type } = defineProps<{
98 healthData: CustomerAgentHealth
@@ -106,7 +106,7 @@ const AgentIcon = "carbon:police"
106 const LinkIcon = "carbon:launch"
107
108 const showDetails = ref(false)
109 -const router = useRouter()
109 +const { gotoAgent } = useGoto()
110
111 const agentVersion = computed(() => {
112 let agent = ""
@@ -142,10 +142,6 @@ const dFormats = useSettingsStore().dateFormat
142 function formatDate(timestamp: string | number, utc: boolean = true): string {
143 return dayjs(timestamp).utc(utc).format(dFormats.datetimesec)
144 }
145 -
146 -function gotoAgentPage(agentId: string) {
147 - router.push({ name: "Agent", params: { id: agentId } })
148 -}
145 </script>
146
147 <style lang="scss" scoped>
frontend/src/components/graylog/Alerts/Item.vue
+3 -10
@@ -30,10 +30,7 @@
30 </div>
31 <div class="box">
32 index_name:
33 - <code
34 - class="cursor-pointer text-primary-color"
35 - @click="gotoIndicesPage(alertsEvent.index_name)"
36 - >
33 + <code class="cursor-pointer text-primary-color" @click="gotoIndex(alertsEvent.index_name)">
34 {{ alertsEvent.index_name }}
35 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
36 </code>
@@ -95,7 +92,7 @@ import { NPopover, NTimeline, NTimelineItem } from "naive-ui"
92 import { useSettingsStore } from "@/stores/settings"
93 import { formatDate } from "@/utils"
94 import Icon from "@/components/common/Icon.vue"
98 -import { useRouter } from "vue-router"
95 +import { useGoto } from "@/composables/useGoto"
96
97 const { alertsEvent } = defineProps<{ alertsEvent: AlertsEventElement }>()
98
@@ -107,17 +104,13 @@ const InfoIcon = "carbon:information"
104 const TimeIcon = "carbon:time"
105 const LinkIcon = "carbon:launch"
106
110 -const router = useRouter()
107 const dFormats = useSettingsStore().dateFormat
108 +const { gotoIndex } = useGoto()
109
110 function formatDateTime(timestamp: string): string {
111 return formatDate(timestamp, dFormats.datetimesec).toString()
112 }
113
117 -function gotoIndicesPage(index: string) {
118 - router.push({ name: "Indices", query: { index_name: index } })
119 -}
120 -
114 function gotoEventsPage(event_definition_id: string) {
115 emit("clickEvent", event_definition_id)
116 }
frontend/src/components/graylog/Metrics/UncommittedEntries.vue
+3 -7
@@ -13,7 +13,7 @@
13 </div>
14 </div>
15 <div class="footer">
16 - <n-button type="primary" ghost icon-placement="right" @click="gotoMessages()">
16 + <n-button type="primary" ghost icon-placement="right" @click="gotoGraylogManagement('messages')">
17 <template #icon>
18 <Icon :name="LinkIcon" :size="14"></Icon>
19 </template>
@@ -27,20 +27,19 @@
27 import Icon from "@/components/common/Icon.vue"
28 import { NButton } from "naive-ui"
29 import { computed, ref, toRefs } from "vue"
30 -import { useRouter } from "vue-router"
30 import { useThemeStore } from "@/stores/theme"
31 import dayjs from "@/utils/dayjs"
32 import "@/assets/scss/apexchart-override.scss"
33 import { watch } from "vue"
34 import { usHealthcheckStore } from "@/stores/healthcheck"
35 import apexchart from "vue3-apexcharts"
36 +import { useGoto } from "@/composables/useGoto"
37
38 const UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD = usHealthcheckStore().uncommittedJournalEntriesThreshold
39
40 const props = defineProps<{
41 value: number
42 }>()
43 -const router = useRouter()
43 const { value } = toRefs(props)
44
45 const LinkIcon = "carbon:launch"
@@ -48,15 +47,12 @@ const DangerIcon = "majesticons:exclamation-line"
47
48 const style = computed<{ [key: string]: any }>(() => useThemeStore().style)
49 const isThemeDark = computed(() => useThemeStore().isThemeDark)
50 +const { gotoGraylogManagement } = useGoto()
51
52 const isWarning = computed<boolean>(() => {
53 return value.value > UNCOMMITTED_JOURNAL_ENTRIES_THRESHOLD
54 })
55
56 -function gotoMessages() {
57 - router.push({ name: "Graylog-Management" })
58 -}
59 -
56 const series = ref<{ name: string; data: any }[]>([
57 {
58 name: "Entries",
frontend/src/components/license/LicenseCheckoutResponse.vue
+2 -6
@@ -35,7 +35,7 @@ import Api from "@/api"
35 import { NButton, NCard, NSpin, useMessage } from "naive-ui"
36 import Icon from "@/components/common/Icon.vue"
37 import type { LicenseKey } from "@/types/license"
38 -import { useRouter } from "vue-router"
38 +import { useGoto } from "@/composables/useGoto"
39
40 const props = defineProps<{ type: "success" | "error"; data?: { email?: string } }>()
41 const { type, data } = toRefs(props)
@@ -43,7 +43,7 @@ const { type, data } = toRefs(props)
43 const ErrorIcon = "majesticons:exclamation-line"
44 const LicenseIcon = "carbon:license"
45 const CheckIcon = "carbon:checkmark-outline"
46 -const router = useRouter()
46 +const { gotoLicense } = useGoto()
47 const message = useMessage()
48 const loadingLicense = ref(false)
49 const license = ref<LicenseKey | null>(null)
@@ -70,10 +70,6 @@ function getLicense(email: string) {
70 })
71 }
72
73 -function gotoLicense() {
74 - router.push({ name: "License" })
75 -}
76 -
73 onBeforeMount(() => {
74 if (data.value?.email) {
75 getLicense(data.value.email)
frontend/src/components/license/LicenseFeatureOverlay.vue
+3 -7
@@ -1,5 +1,5 @@
1 <template>
2 - <div class="overlay feature-overlay" v-if="showBanner">
2 + <div class="over-layer feature-layer" v-if="showBanner">
3 <n-alert title="Feature required">
4 <template #icon>
5 <Icon :name="AlertIcon" :size="18"></Icon>
@@ -26,23 +26,19 @@
26 import { ref } from "vue"
27 import { NAlert, NButton } from "naive-ui"
28 import Icon from "@/components/common/Icon.vue"
29 -import { useRouter } from "vue-router"
29 import Api from "@/api"
30 import { onBeforeMount } from "vue"
31 import type { LicenseFeatures } from "@/types/license"
32 +import { useGoto } from "@/composables/useGoto"
33
34 const { feature } = defineProps<{ feature: LicenseFeatures }>()
35
36 const LicenseIcon = "carbon:license"
37 const AlertIcon = "mdi:alert-outline"
38
39 -const router = useRouter()
39 +const { gotoLicense } = useGoto()
40 const showBanner = ref(false)
41
42 -function gotoLicense() {
43 - router.push({ name: "License" })
44 -}
45 -
42 function checkFeature(feature: LicenseFeatures) {
43 Api.license
44 .isFeatureEnabled(feature)
frontend/src/components/license/bkp/LicenseCheckout.vue
+1 -1
@@ -35,7 +35,7 @@
35 </template>
36
37 <script setup lang="ts">
38 -import { NButton, NSpin, NModal, useMessage } from "naive-ui"
38 +import { NButton, NModal, useMessage } from "naive-ui"
39 import Icon from "@/components/common/Icon.vue"
40 import Api from "@/api"
41 import { onBeforeMount, ref } from "vue"
frontend/src/components/monitoringAlerts/Item.vue new
+142
@@ -0,0 +1,142 @@
1 +<template>
2 + <n-spin :show="loading" :description="loadingDelete ? 'Deleting' : 'Invoking'">
3 + <div class="monitoring-alerts-item flex flex-col gap-2 px-5 py-3" :class="{ embedded }">
4 + <div class="header-box flex justify-between">
5 + <div class="id flex items-center">#{{ alert.id }}</div>
6 + </div>
7 + <div class="main-box flex items-center justify-between gap-4">
8 + <div class="content">
9 + <div class="title">{{ alert.alert_id }}</div>
10 +
11 + <div class="badges-box flex flex-wrap items-center gap-3 mt-4">
12 + <Badge type="active" class="cursor-pointer" @click="gotoIndex(alert.alert_index)">
13 + <template #iconRight>
14 + <Icon :name="LinkIcon" :size="14"></Icon>
15 + </template>
16 + <template #label>Index / {{ alert.alert_index }}</template>
17 + </Badge>
18 +
19 + <Badge
20 + type="active"
21 + class="cursor-pointer"
22 + @click="gotoCustomer({ code: alert.customer_code })"
23 + >
24 + <template #iconRight>
25 + <Icon :name="LinkIcon" :size="14"></Icon>
26 + </template>
27 + <template #label>Customer #{{ alert.customer_code }}</template>
28 + </Badge>
29 +
30 + <Badge type="splitted">
31 + <template #label>Source</template>
32 + <template #value>{{ alert.alert_source }}</template>
33 + </Badge>
34 + </div>
35 + </div>
36 +
37 + <AlertActions
38 + class="actions-box"
39 + :alert="alert"
40 + @deleted="emit('deleted')"
41 + @invoked="emit('invoked')"
42 + @startDeleting="loadingDelete = true"
43 + @stopDeleting="loadingDelete = false"
44 + @startInvoking="loadingInvoke = true"
45 + @stopInvoking="loadingInvoke = false"
46 + />
47 + </div>
48 + <div class="footer-box flex justify-between items-center gap-3">
49 + <AlertActions
50 + class="actions-box !flex-row"
51 + :alert="alert"
52 + size="small"
53 + inline
54 + @deleted="emit('deleted')"
55 + @invoked="emit('invoked')"
56 + @startDeleting="loadingDelete = true"
57 + @stopDeleting="loadingDelete = false"
58 + @startInvoking="loadingInvoke = true"
59 + @stopInvoking="loadingInvoke = false"
60 + />
61 + </div>
62 + </div>
63 + </n-spin>
64 +</template>
65 +
66 +<script setup lang="ts">
67 +import Icon from "@/components/common/Icon.vue"
68 +import Badge from "@/components/common/Badge.vue"
69 +import { computed, ref } from "vue"
70 +import AlertActions from "./ItemActions.vue"
71 +import { NSpin } from "naive-ui"
72 +import { useGoto } from "@/composables/useGoto"
73 +import type { MonitoringAlert } from "@/types/monitoringAlerts"
74 +
75 +const { alert, embedded } = defineProps<{
76 + alert: MonitoringAlert
77 + embedded?: boolean
78 +}>()
79 +
80 +const emit = defineEmits<{
81 + (e: "deleted"): void
82 + (e: "invoked"): void
83 +}>()
84 +
85 +const LinkIcon = "carbon:launch"
86 +
87 +const { gotoCustomer, gotoIndex } = useGoto()
88 +const loadingDelete = ref(false)
89 +const loadingInvoke = ref(false)
90 +const loading = computed(() => loadingDelete.value || loadingInvoke.value)
91 +</script>
92 +
93 +<style lang="scss" scoped>
94 +.monitoring-alerts-item {
95 + &:not(.embedded) {
96 + border-radius: var(--border-radius);
97 + background-color: var(--bg-color);
98 + border: var(--border-small-050);
99 + }
100 + border-top: var(--border-small-050);
101 + transition: all 0.2s var(--bezier-ease);
102 + min-height: 100px;
103 +
104 + .header-box {
105 + font-family: var(--font-family-mono);
106 + font-size: 13px;
107 + .id {
108 + word-break: break-word;
109 + color: var(--fg-secondary-color);
110 + line-height: 1.2;
111 + }
112 + }
113 +
114 + .main-box {
115 + word-break: break-word;
116 + }
117 +
118 + .footer-box {
119 + font-size: 13px;
120 + margin-top: 10px;
121 + display: none;
122 + }
123 +
124 + &:not(.embedded) {
125 + &:hover {
126 + border-color: var(--primary-color);
127 + }
128 + }
129 +
130 + @container (max-width: 550px) {
131 + .main-box {
132 + .actions-box {
133 + display: none;
134 + }
135 + }
136 +
137 + .footer-box {
138 + display: flex;
139 + }
140 + }
141 +}
142 +</style>
frontend/src/components/monitoringAlerts/ItemActions.vue new
+128
@@ -0,0 +1,128 @@
1 +<template>
2 + <div class="alert-actions flex flex-col gap-3 justify-center" :class="{ '!flex-row': inline }">
3 + <n-button :loading="loadingInvoke" type="success" secondary :size="size" @click="invoke()">
4 + <template #icon><Icon :name="InvokeIcon"></Icon></template>
5 + Invoke
6 + </n-button>
7 + <n-button :loading="loadingDelete" :size="size" type="error" secondary @click="handleDelete()">
8 + <template #icon><Icon :name="DeleteIcon"></Icon></template>
9 + Delete
10 + </n-button>
11 + </div>
12 +</template>
13 +
14 +<script setup lang="ts">
15 +import { NButton, useDialog, useMessage } from "naive-ui"
16 +import Icon from "@/components/common/Icon.vue"
17 +import Api from "@/api"
18 +import { computed, watch, ref } from "vue"
19 +import type { Size } from "naive-ui/es/button/src/interface"
20 +import type { MonitoringAlert } from "@/types/monitoringAlerts"
21 +
22 +const emit = defineEmits<{
23 + (e: "startInvoking"): void
24 + (e: "stopInvoking"): void
25 + (e: "startDeleting"): void
26 + (e: "stopDeleting"): void
27 + (e: "startLoading"): void
28 + (e: "stopLoading"): void
29 + (e: "invoked"): void
30 + (e: "deleted"): void
31 +}>()
32 +
33 +const { alert, size, inline } = defineProps<{
34 + alert: MonitoringAlert
35 + size?: Size
36 + inline?: boolean
37 +}>()
38 +
39 +const DeleteIcon = "ph:trash"
40 +const InvokeIcon = "carbon:play"
41 +
42 +const dialog = useDialog()
43 +const message = useMessage()
44 +const loadingInvoke = ref(false)
45 +const loadingDelete = ref(false)
46 +const loading = computed(() => loadingInvoke.value || loadingDelete.value)
47 +
48 +watch(loading, val => {
49 + if (val) {
50 + emit("startLoading")
51 + } else {
52 + emit("stopLoading")
53 + }
54 +})
55 +
56 +watch(loadingInvoke, val => {
57 + if (val) {
58 + emit("startInvoking")
59 + } else {
60 + emit("stopInvoking")
61 + }
62 +})
63 +
64 +watch(loadingDelete, val => {
65 + if (val) {
66 + emit("startDeleting")
67 + } else {
68 + emit("stopDeleting")
69 + }
70 +})
71 +
72 +function invoke() {
73 + loadingInvoke.value = true
74 +
75 + Api.monitoringAlerts
76 + .invoke(alert.id)
77 + .then(res => {
78 + if (res.data.success) {
79 + emit("invoked")
80 + message.success(res.data?.message || "Alert invoked successfully")
81 + } else {
82 + message.warning(res.data?.message || "An error occurred. Please try again later.")
83 + }
84 + })
85 + .catch(err => {
86 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
87 + })
88 + .finally(() => {
89 + loadingInvoke.value = false
90 + })
91 +}
92 +
93 +function handleDelete() {
94 + dialog.warning({
95 + title: "Confirm",
96 + content: "This will delete the alert, are you sure you want to proceed?",
97 + positiveText: "Yes I'm sure",
98 + negativeText: "Cancel",
99 + onPositiveClick: () => {
100 + deleteAlert()
101 + },
102 + onNegativeClick: () => {
103 + message.info("Delete canceled")
104 + }
105 + })
106 +}
107 +
108 +function deleteAlert() {
109 + loadingDelete.value = true
110 +
111 + Api.monitoringAlerts
112 + .delete(alert.id)
113 + .then(res => {
114 + if (res.data.success) {
115 + emit("deleted")
116 + message.success(res.data?.message || "Alert deleted successfully.")
117 + } else {
118 + message.warning(res.data?.message || "An error occurred. Please try again later.")
119 + }
120 + })
121 + .catch(err => {
122 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
123 + })
124 + .finally(() => {
125 + loadingDelete.value = false
126 + })
127 +}
128 +</script>
frontend/src/components/monitoringAlerts/List.vue new
+138
@@ -0,0 +1,138 @@
1 +<template>
2 + <div class="monitoring-alerts-list">
3 + <div class="header flex items-center justify-end gap-2" ref="header">
4 + <div class="info grow flex gap-2">
5 + <n-popover overlap placement="bottom-start">
6 + <template #trigger>
7 + <div class="bg-color border-radius">
8 + <n-button size="small" class="!cursor-help">
9 + <template #icon>
10 + <Icon :name="InfoIcon"></Icon>
11 + </template>
12 + </n-button>
13 + </div>
14 + </template>
15 + <div class="flex flex-col gap-2">
16 + <div class="box">
17 + Total :
18 + <code>{{ total }}</code>
19 + </div>
20 + </div>
21 + </n-popover>
22 + </div>
23 + <n-pagination
24 + v-model:page="currentPage"
25 + v-model:page-size="pageSize"
26 + :page-slot="pageSlot"
27 + :show-size-picker="showSizePicker"
28 + :page-sizes="pageSizes"
29 + :item-count="total"
30 + :simple="simpleMode"
31 + />
32 + </div>
33 + <n-spin :show="loading">
34 + <div class="list my-3">
35 + <template v-if="monitoringAlerts.length">
36 + <Alert
37 + v-for="alert of itemsPaginated"
38 + :key="alert.id"
39 + :alert="alert"
40 + @deleted="getData()"
41 + @invoked="getData()"
42 + class="item-appear item-appear-bottom item-appear-005 mb-2"
43 + />
44 + </template>
45 + <template v-else>
46 + <n-empty description="No items found" class="justify-center h-48" v-if="!loading" />
47 + </template>
48 + </div>
49 + </n-spin>
50 + <div class="footer flex justify-end">
51 + <n-pagination
52 + v-model:page="currentPage"
53 + :page-size="pageSize"
54 + :item-count="total"
55 + :page-slot="6"
56 + v-if="itemsPaginated.length > 3"
57 + />
58 + </div>
59 + </div>
60 +</template>
61 +
62 +<script setup lang="ts">
63 +import { ref, onBeforeMount, computed } from "vue"
64 +import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination } from "naive-ui"
65 +import Api from "@/api"
66 +import Icon from "@/components/common/Icon.vue"
67 +import { useResizeObserver } from "@vueuse/core"
68 +import Alert from "./Item.vue"
69 +import type { MonitoringAlert } from "@/types/monitoringAlerts.d"
70 +
71 +const message = useMessage()
72 +const loading = ref(false)
73 +const monitoringAlerts = ref<MonitoringAlert[]>([])
74 +
75 +const pageSize = ref(25)
76 +const currentPage = ref(1)
77 +const simpleMode = ref(false)
78 +const showSizePicker = ref(true)
79 +const pageSizes = [10, 25, 50, 100]
80 +const header = ref()
81 +const pageSlot = ref(8)
82 +
83 +const itemsPaginated = computed(() => {
84 + const from = (currentPage.value - 1) * pageSize.value
85 + const to = currentPage.value * pageSize.value
86 +
87 + return monitoringAlerts.value.slice(from, to)
88 +})
89 +
90 +const InfoIcon = "carbon:information"
91 +
92 +const total = computed<number>(() => {
93 + return monitoringAlerts.value.length || 0
94 +})
95 +
96 +function getData() {
97 + loading.value = true
98 +
99 + Api.monitoringAlerts
100 + .listAll()
101 + .then(res => {
102 + if (res.data.success) {
103 + monitoringAlerts.value = res.data?.monitoring_alerts || []
104 + } else {
105 + message.warning(res.data?.message || "An error occurred. Please try again later.")
106 + }
107 + })
108 + .catch(err => {
109 + monitoringAlerts.value = []
110 +
111 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
112 + })
113 + .finally(() => {
114 + loading.value = false
115 + })
116 +}
117 +
118 +useResizeObserver(header, entries => {
119 + const entry = entries[0]
120 + const { width } = entry.contentRect
121 +
122 + pageSlot.value = width < 700 ? 5 : 8
123 + simpleMode.value = width < 550
124 +})
125 +
126 +onBeforeMount(() => {
127 + getData()
128 +})
129 +</script>
130 +
131 +<style lang="scss" scoped>
132 +.monitoring-alerts-list {
133 + .list {
134 + container-type: inline-size;
135 + min-height: 200px;
136 + }
137 +}
138 +</style>
frontend/src/components/overview/AgentsCard.vue
+3 -7
@@ -5,7 +5,7 @@
5 firstLabel="Total"
6 hovered
7 class="cursor-pointer"
8 - @click="gotoAgentsPage()"
8 + @click="gotoAgent()"
9 :value="total"
10 secondLabel="Online"
11 :subValue="onlineTotal"
@@ -25,10 +25,10 @@ import CardStatsDouble from "@/components/common/CardStatsDouble.vue"
25 import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
26 import Api from "@/api"
27 import { useMessage, NSpin } from "naive-ui"
28 -import { useRouter } from "vue-router"
28 +import { useGoto } from "@/composables/useGoto"
29
30 const AgentsIcon = "carbon:network-3"
31 -const router = useRouter()
31 +const { gotoAgent } = useGoto()
32 const message = useMessage()
33 const loading = ref(false)
34 const agents = ref<Agent[]>([])
@@ -61,10 +61,6 @@ function getData() {
61 })
62 }
63
64 -function gotoAgentsPage() {
65 - router.push({ name: "Agents" })
66 -}
67 -
64 onBeforeMount(() => {
65 getData()
66 })
frontend/src/components/overview/CustomersCard.vue
+3 -7
@@ -6,7 +6,7 @@
6 :vertical="vertical"
7 hovered
8 class="cursor-pointer h-full"
9 - @click="gotoCustomersPage()"
9 + @click="gotoCustomer()"
10 >
11 <template #icon>
12 <CardStatsIcon :iconName="CustomersIcon" boxed :boxSize="40"></CardStatsIcon>
@@ -22,7 +22,7 @@ import CardStats from "@/components/common/CardStats.vue"
22 import Api from "@/api"
23 import { useMessage, NSpin } from "naive-ui"
24 import type { Customer } from "@/types/customers.d"
25 -import { useRouter } from "vue-router"
25 +import { useGoto } from "@/composables/useGoto"
26
27 const props = defineProps<{
28 vertical?: boolean
@@ -30,7 +30,7 @@ const props = defineProps<{
30 const { vertical } = toRefs(props)
31
32 const CustomersIcon = "carbon:user-multiple"
33 -const router = useRouter()
33 +const { gotoCustomer } = useGoto()
34 const message = useMessage()
35 const loading = ref(false)
36 const customers = ref<Customer[]>([])
@@ -59,10 +59,6 @@ function getData() {
59 })
60 }
61
62 -function gotoCustomersPage() {
63 - router.push({ name: "Customers" })
64 -}
65 -
62 onBeforeMount(() => {
63 getData()
64 })
frontend/src/components/overview/HealthcheckCard.vue
+3 -7
@@ -6,7 +6,7 @@
6 :value="total"
7 hovered
8 class="cursor-pointer"
9 - @click="gotoHealthcheckPage()"
9 + @click="gotoHealthcheck()"
10 secondLabel="Critical"
11 :subValue="criticalTotal"
12 :secondStatus="criticalTotal ? 'warning' : undefined"
@@ -30,11 +30,11 @@ import CardStatsIcon from "@/components/common/CardStatsIcon.vue"
30 import Api from "@/api"
31 import { useMessage, NSpin } from "naive-ui"
32 import { InfluxDBAlertLevel, type InfluxDBAlert } from "@/types/healthchecks.d"
33 -import { useRouter } from "vue-router"
33 import { useThemeStore } from "@/stores/theme"
34 +import { useGoto } from "@/composables/useGoto"
35
36 const HealthcheckIcon = "ph:heartbeat"
37 -const router = useRouter()
37 +const { gotoHealthcheck } = useGoto()
38 const message = useMessage()
39 const loading = ref(false)
40 const healthcheck = ref<InfluxDBAlert[]>([])
@@ -71,10 +71,6 @@ function getData() {
71 })
72 }
73
74 -function gotoHealthcheckPage() {
75 - router.push({ name: "Healthcheck" })
76 -}
77 -
74 onBeforeMount(() => {
75 getData()
76 })
frontend/src/components/overview/SocAlertsCard.vue
+3 -7
@@ -6,7 +6,7 @@
6 :vertical="vertical"
7 hovered
8 class="cursor-pointer h-full"
9 - @click="gotoSocAlertsPage()"
9 + @click="gotoSocAlerts()"
10 >
11 <template #icon>
12 <CardStatsIcon :iconName="SOCIcon" boxed :boxSize="40"></CardStatsIcon>
@@ -22,7 +22,7 @@ import CardStats from "@/components/common/CardStats.vue"
22 import Api from "@/api"
23 import { useMessage, NSpin } from "naive-ui"
24 import type { SocAlert } from "@/types/soc/alert.d"
25 -import { useRouter } from "vue-router"
25 +import { useGoto } from "@/composables/useGoto"
26
27 const props = defineProps<{
28 vertical?: boolean
@@ -30,7 +30,7 @@ const props = defineProps<{
30 const { vertical } = toRefs(props)
31
32 const SOCIcon = "carbon:security"
33 -const router = useRouter()
33 +const { gotoSocAlerts } = useGoto()
34 const message = useMessage()
35 const loading = ref(false)
36 const alerts = ref<SocAlert[]>([])
@@ -59,10 +59,6 @@ function getData() {
59 })
60 }
61
62 -function gotoSocAlertsPage() {
63 - router.push({ name: "Soc-Alerts" })
64 -}
65 -
62 onBeforeMount(() => {
63 getData()
64 })
frontend/src/components/scheduler/JobActions.vue
+1 -1
@@ -38,7 +38,7 @@
38 v-model:show="showForm"
39 display-directive="show"
40 preset="card"
41 - :style="{ maxWidth: 'min(450px, 90vw)', minHeight: 'min(300px, 90vh)', overflow: 'hidden' }"
41 + :style="{ maxWidth: 'min(450px, 90vw)', overflow: 'hidden' }"
42 :title="`Update ${job.name}`"
43 :bordered="false"
44 segmented
frontend/src/components/soc/SocAlerts/SocAlertAssetsItem.vue
+4 -8
@@ -47,7 +47,7 @@
47 <template #label>Updated</template>
48 <template #value>{{ formatDate(asset.date_update) }}</template>
49 </Badge>
50 - <Badge type="active" class="cursor-pointer" @click="gotoAgentPage(asset.asset_tags)">
50 + <Badge type="active" class="cursor-pointer" @click="gotoAgent(asset.asset_tags)">
51 <template #iconRight>
52 <Icon :name="LinkIcon" :size="14"></Icon>
53 </template>
@@ -76,7 +76,7 @@
76 <template v-if="key === 'asset_tags'">
77 <code
78 class="cursor-pointer text-primary-color"
79 - @click="gotoAgentPage(value)"
79 + @click="gotoAgent(value)"
80 v-if="value && value !== '-'"
81 >
82 {{ value }}
@@ -132,15 +132,15 @@ import _omit from "lodash/omit"
132 import dayjs from "@/utils/dayjs"
133 import { isUrlLike } from "@/utils"
134 import type { SocAlertAsset } from "@/types/soc/asset.d"
135 -import { useRouter } from "vue-router"
135 import { useSettingsStore } from "@/stores/settings"
136 +import { useGoto } from "@/composables/useGoto"
137
138 const { asset } = defineProps<{ asset: SocAlertAsset }>()
139
140 const InfoIcon = "carbon:information"
141 const ClockIcon = "carbon:time"
142 const LinkIcon = "carbon:launch"
143 -const router = useRouter()
143 +const { gotoAgent } = useGoto()
144 const showDetails = ref(false)
145
146 const dFormats = useSettingsStore().dateFormat
@@ -173,10 +173,6 @@ const assetType = computed(() => {
173 return asset.asset_type || {}
174 })
175
176 -function gotoAgentPage(agentId: string) {
177 - router.push({ name: "Agent", params: { id: agentId } })
178 -}
179 -
176 const formatDate = (date: string, useSec = false) => {
177 const datejs = dayjs(date)
178 if (!datejs.isValid()) return date
frontend/src/components/soc/SocAlerts/SocAlertItem.vue
+9 -14
@@ -2,7 +2,7 @@
2 <n-spin
3 :show="loading"
4 :description="loadingDelete ? 'Deleting Soc Alert' : 'Loading Soc Alert'"
5 - class="soc-alert-item flex flex-col gap-0"
5 + class="soc-alert-item flex flex-col gap-0 min-h-36 pb-2"
6 :class="{ bookmarked: isBookmark, highlight, embedded }"
7 :id="'alert-' + alert?.alert_id"
8 >
@@ -115,7 +115,7 @@
115 >
116 <code
117 class="cursor-pointer text-primary-color"
118 - @click="gotoCustomer(alert.customer.customer_code)"
118 + @click="gotoCustomer({ code: alert.customer.customer_code })"
119 >
120 {{ alert.customer?.customer_name || alert.customer.customer_code || "-" }}
121 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -214,7 +214,10 @@
214 <template #key>{{ key }}</template>
215 <template #value>
216 <template v-if="key === 'customer_code' && value && value !== 'Customer Not Found'">
217 - <code class="cursor-pointer text-primary-color" @click="gotoCustomer(value)">
217 + <code
218 + class="cursor-pointer text-primary-color"
219 + @click="gotoCustomer({ code: value })"
220 + >
221 #{{ value }}
222 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
223 </code>
@@ -232,7 +235,7 @@
235 type="active"
236 style="max-width: 145px"
237 class="cursor-pointer"
235 - @click="gotoUsersPage(ownerId)"
238 + @click="gotoSocUsers(ownerId)"
239 >
240 <template #iconRight>
241 <Icon :name="LinkIcon" :size="14"></Icon>
@@ -327,7 +330,7 @@ import {
330 import { useSettingsStore } from "@/stores/settings"
331 import dayjs from "@/utils/dayjs"
332 import type { SocUser } from "@/types/soc/user.d"
330 -import { useRouter } from "vue-router"
333 +import { useGoto } from "@/composables/useGoto"
334
335 const checked = defineModel<boolean>("checked", { default: false })
336
@@ -383,8 +386,8 @@ const showBadges = ref(false)
386 const loadingDelete = ref(false)
387 const loadingData = ref(false)
388 const loadingBookmark = ref(false)
386 -const router = useRouter()
389 const message = useMessage()
390 +const { gotoCustomer, gotoSocUsers } = useGoto()
391
392 const alert = ref(alertData.value || null)
393
@@ -448,10 +451,6 @@ function updateAlert(alertUpdated: SocAlert) {
451 }
452 }
453
451 -function gotoUsersPage(userId?: string | number) {
452 - router.push({ name: "Soc-Users", query: userId ? { user_id: userId } : {} })
453 -}
454 -
454 function getAlert(id: string | number, cb?: () => void) {
455 loadingData.value = true
456
@@ -492,10 +491,6 @@ function deleted() {
491 emit("deleted")
492 }
493
495 -function gotoCustomer(code: string | number | { [key: string]: any }) {
496 - router.push({ name: "Customers", query: { code: code.toString() } })
497 -}
498 -
494 watch(checked, val => {
495 emit("check", val)
496 if (val) {
frontend/src/components/soc/SocCases/SocCaseItem.vue
+3 -7
@@ -174,7 +174,7 @@
174 >
175 <code
176 class="cursor-pointer text-primary-color"
177 - @click="gotoCustomer(value)"
177 + @click="gotoCustomer({ code: value })"
178 >
179 #{{ value }}
180 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
@@ -275,7 +275,7 @@ import dayjs from "@/utils/dayjs"
275 import { type SocCase, StateName, type SocCaseExt } from "@/types/soc/case.d"
276 import _omit from "lodash/omit"
277 import _split from "lodash/split"
278 -import { useRouter } from "vue-router"
278 +import { useGoto } from "@/composables/useGoto"
279
280 const { caseData, caseId, embedded, hideSocCaseAction, hideSocAlertLink } = defineProps<{
281 caseData?: SocCase
@@ -297,7 +297,7 @@ const OwnerIcon = "carbon:user-military"
297 const StatusIcon = "fluent:status-20-regular"
298 const AddIcon = "carbon:add-alt"
299
300 -const router = useRouter()
300 +const { gotoCustomer } = useGoto()
301 const showSocAlertDetails = ref(false)
302 const showDetails = ref(false)
303 const loadingDetails = ref(false)
@@ -430,10 +430,6 @@ function getDetails() {
430 }
431 }
432
433 -function gotoCustomer(code: string | number) {
434 - router.push({ name: "Customers", query: { code } })
435 -}
436 -
433 watch(showDetails, val => {
434 if (val && !extendedInfo.value) {
435 getDetails()
frontend/src/components/soc/SocCases/SocCaseItemActions.vue
+1 -1
@@ -114,7 +114,7 @@ function reopenCase() {
114 function handleDelete() {
115 dialog.warning({
116 title: "Confirm",
117 - content: "This will delete the case are you sure you want to proceed?",
117 + content: "This will delete the case, are you sure you want to proceed?",
118 positiveText: "Yes I'm sure",
119 negativeText: "Cancel",
120 onPositiveClick: () => {
frontend/src/composables/useGoto.ts new
+78
@@ -0,0 +1,78 @@
1 +import { useRouter } from "vue-router"
2 +
3 +export function useGoto() {
4 + const router = useRouter()
5 +
6 + function gotoCustomer(params?: { code?: string | number | { [key: string]: any }; action?: "add-customer" }) {
7 + if (params?.code) {
8 + router.push({ name: "Customers", query: { code: params.code.toString() } })
9 + } else if (params?.action) {
10 + router.push({ name: "Customers", query: { action: params.action.toString() } })
11 + } else {
12 + router.push({ name: "Customers" })
13 + }
14 + }
15 +
16 + function gotoAgent(agentId?: string | number) {
17 + if (agentId) {
18 + router.push({ name: "Agent", params: { id: agentId.toString() } })
19 + } else {
20 + router.push({ name: "Agents" })
21 + }
22 + }
23 +
24 + function gotoIndex(indexName?: string) {
25 + router.push({ name: "Indices", query: indexName ? { index_name: indexName } : {} })
26 + }
27 +
28 + function gotoLicense() {
29 + router.push({ name: "License" })
30 + }
31 +
32 + function gotoHealthcheck() {
33 + router.push({ name: "Healthcheck" })
34 + }
35 +
36 + function gotoGraylogMetrics() {
37 + router.push({ name: "Graylog-Metrics" })
38 + }
39 +
40 + function gotoGraylogManagement(tabName?: "messages" | "alerts" | "events" | "streams" | "provisioning" | "inputs") {
41 + router.push({ name: "Graylog-Management", hash: tabName ? `#${tabName}` : undefined })
42 + }
43 +
44 + function gotoSocAlerts() {
45 + router.push({ name: "Soc-Alerts" })
46 + }
47 +
48 + function gotoAlerts() {
49 + router.push({ name: "Alerts" })
50 + }
51 +
52 + function gotoConnectors() {
53 + router.push({ name: "Connectors" })
54 + }
55 +
56 + function gotoGraylogPipelines(rule?: string) {
57 + router.push({ name: "Graylog-Pipelines", query: rule ? { rule } : {} })
58 + }
59 +
60 + function gotoSocUsers(userId?: string | number) {
61 + router.push({ name: "Soc-Users", query: userId ? { user_id: userId } : {} })
62 + }
63 +
64 + return {
65 + gotoCustomer,
66 + gotoAgent,
67 + gotoIndex,
68 + gotoLicense,
69 + gotoHealthcheck,
70 + gotoGraylogMetrics,
71 + gotoGraylogManagement,
72 + gotoSocAlerts,
73 + gotoGraylogPipelines,
74 + gotoSocUsers,
75 + gotoAlerts,
76 + gotoConnectors
77 + }
78 +}
frontend/src/composables/useHealthchecksNotify.ts
+5 -5
@@ -2,13 +2,13 @@ import { computed, watch } from "vue"
2 import { usHealthcheckStore } from "@/stores/healthcheck"
3 import { useNotifications, type Notification } from "./useNotifications"
4 import { IndexHealth } from "@/types/indices.d"
5 -import { useRouter } from "vue-router"
5 import _capitalize from "lodash/capitalize"
6 +import { useGoto } from "./useGoto"
7
8 export function useHealthchecksNotify() {
9 return {
10 init: () => {
11 - const router = useRouter()
11 + const { gotoHealthcheck, gotoIndex, gotoGraylogMetrics } = useGoto()
12
13 const uncommittedJournalEntriesThreshold = usHealthcheckStore().uncommittedJournalEntriesThreshold
14 const uncommittedJournalEntries = computed(() => usHealthcheckStore().uncommittedJournalEntries)
@@ -29,7 +29,7 @@ export function useHealthchecksNotify() {
29 read: false,
30 date: new Date(),
31 action() {
32 - router.push({ name: "Graylog-Metrics" })
32 + gotoGraylogMetrics()
33 },
34 actionTitle: "See Graylog Metrics"
35 }
@@ -55,7 +55,7 @@ export function useHealthchecksNotify() {
55 read: false,
56 date: new Date(),
57 action() {
58 - router.push({ name: "Indices" })
58 + gotoIndex()
59 },
60 actionTitle: "See Cluster"
61 }
@@ -81,7 +81,7 @@ export function useHealthchecksNotify() {
81 read: false,
82 date: new Date(),
83 action() {
84 - router.push({ name: "Healthcheck" })
84 + gotoHealthcheck()
85 },
86 actionTitle: "See Healthcheck"
87 }
frontend/src/layouts/common/Navbar/items.tsx
+13
@@ -194,6 +194,19 @@ export default function getItems(mode: "vertical" | "horizontal", collapsed: boo
194 { default: () => "Users" }
195 ),
196 key: "Soc-Users"
197 + },
198 + {
199 + label: () =>
200 + h(
201 + RouterLink,
202 + {
203 + to: {
204 + name: "Soc-PendingAlerts"
205 + }
206 + },
207 + { default: () => "Pending Alerts" }
208 + ),
209 + key: "Soc-PendingAlerts"
210 }
211 ]
212 },
frontend/src/router/index.ts
+6
@@ -117,6 +117,12 @@ const router = createRouter({
117 name: "Soc-Users",
118 component: () => import("@/views/soc/Users.vue"),
119 meta: { title: "SOC Users" }
120 + },
121 + {
122 + path: "pending-alerts",
123 + name: "Soc-PendingAlerts",
124 + component: () => import("@/views/soc/PendingAlerts.vue"),
125 + meta: { title: "SOC Pending Alerts" }
126 }
127 ]
128 },
frontend/src/types/monitoringAlerts.d.ts
+8
@@ -2,3 +2,11 @@ export interface AvailableMonitoringAlert {
2 name: string
3 value: string
4 }
5 +
6 +export interface MonitoringAlert {
7 + id: number
8 + alert_id: string
9 + alert_index: string
10 + customer_code: string
11 + alert_source: string
12 +}
frontend/src/views/Overview.vue
+4 -13
@@ -36,7 +36,7 @@
36 </div>
37 </div>
38 <div class="section">
39 - <IndicesMarquee @click="gotoIndicesPage" />
39 + <IndicesMarquee @click="gotoIndex($event.index)" />
40 </div>
41 <div class="section">
42 <div class="columns">
@@ -49,7 +49,7 @@
49 </div>
50 </div>
51 <div class="section">
52 - <PipeList minHeight="28px" @open-rule="gotoPipelinesPage($event)" />
52 + <PipeList minHeight="28px" @open-rule="gotoGraylogPipelines($event)" />
53 </div>
54
55 <n-drawer
@@ -73,7 +73,6 @@
73 <script setup lang="ts">
74 import { ref } from "vue"
75 import { NButton, NDrawer, NDrawerContent } from "naive-ui"
76 -import { useRouter } from "vue-router"
76 import ClusterHealth from "@/components/indices/ClusterHealth.vue"
77 import NodeAllocation from "@/components/indices/NodeAllocation.vue"
78 import IndicesMarquee from "@/components/indices/Marquee.vue"
@@ -85,23 +84,15 @@ import HealthcheckCard from "@/components/overview/HealthcheckCard.vue"
84 // import SocAlertsCard from "@/components/overview/SocAlertsCard.vue"
85 import CustomersCard from "@/components/overview/CustomersCard.vue"
86 import PipeList from "@/components/graylog/Pipelines/PipeList.vue"
88 -import type { IndexStats } from "@/types/indices.d"
87 import { useResizeObserver } from "@vueuse/core"
88 import Icon from "@/components/common/Icon.vue"
89 +import { useGoto } from "@/composables/useGoto"
90
91 const QuickActionsIcon = "ant-design:thunderbolt-outlined"
93 -const router = useRouter()
92 const page = ref()
93 const cardDirection = ref<"horizontal" | "vertical">("horizontal")
94 const showQuickActions = ref(false)
97 -
98 -function gotoIndicesPage(index: IndexStats) {
99 - router.push({ name: "Indices", query: { index_name: index.index } })
100 -}
101 -
102 -function gotoPipelinesPage(rule: string) {
103 - router.push({ name: "Graylog-Pipelines", query: { rule } })
104 -}
95 +const { gotoIndex, gotoGraylogPipelines } = useGoto()
96
97 useResizeObserver(page, entries => {
98 const entry = entries[0]
frontend/src/views/ReportCreation.vue
+2 -2
@@ -8,7 +8,7 @@
8 hide-panels-select
9 />
10 <ReportPanels :timerange="timerange" :org="org" :dashboard="dashboard" :panels="panels" />
11 - <div class="overlay mobile-overlay">
11 + <div class="over-layer mobile-layer">
12 <n-alert>
13 <template #icon>
14 <Icon :name="AlertIcon" :size="18"></Icon>
@@ -45,7 +45,7 @@ const panels = ref<Panel[]>([])
45 overflow: hidden;
46 position: relative;
47
48 - .mobile-overlay {
48 + .mobile-layer {
49 display: none;
50 @media (max-width: $sidebar-bp) {
51 display: flex;
frontend/src/views/agents/Agents.vue
+4 -8
@@ -10,7 +10,7 @@
10 :agents-critical="agentsCritical"
11 :agents-online="agentsOnline"
12 @sync="syncAgents()"
13 - @click="gotoAgentPage"
13 + @click="gotoAgent($event.agent_id)"
14 />
15 </div>
16 <div class="main grow flex flex-col overflow-hidden">
@@ -24,7 +24,7 @@
24 :agent="agent"
25 show-actions
26 @delete="syncAgents()"
27 - @click="gotoAgentPage(agent)"
27 + @click="gotoAgent(agent.agent_id)"
28 class="item-appear item-appear-bottom item-appear-005"
29 />
30 </template>
@@ -58,12 +58,12 @@ import { AgentStatus, type Agent } from "@/types/agents.d"
58 import AgentCard from "@/components/agents/AgentCard.vue"
59 import AgentToolbar from "@/components/agents/AgentToolbar.vue"
60 import Api from "@/api"
61 -import { useRouter } from "vue-router"
61 import { useMessage, NSpin, NScrollbar, NEmpty, NPagination } from "naive-ui"
62 import _debounce from "lodash/debounce"
63 +import { useGoto } from "@/composables/useGoto"
64
65 const message = useMessage()
66 -const router = useRouter()
66 +const { gotoAgent } = useGoto()
67 const loadingAgents = ref(false)
68 const loadingSync = ref(false)
69 const agents = ref<Agent[]>([])
@@ -106,10 +106,6 @@ const agentsOnline = computed(() => {
106 return agents.value.filter(({ wazuh_agent_status }) => wazuh_agent_status === AgentStatus.Active)
107 })
108
109 -function gotoAgentPage(agent: Agent) {
110 - router.push({ name: "Agent", params: { id: agent.agent_id } })
111 -}
112 -
109 function getAgents() {
110 loadingAgents.value = true
111
frontend/src/views/agents/Overview.vue
+6 -8
@@ -1,7 +1,7 @@
1 <template>
2 <div class="page">
3 <div class="agent-toolbar">
4 - <div class="back-btn" @click="gotoAgents()">
4 + <div class="back-btn" @click="gotoAgent()">
5 <Icon :name="ArrowIcon" :size="16"></Icon>
6 <span>Agents list</span>
7 </div>
@@ -125,11 +125,13 @@ import ArtifactsCollect from "@/components/artifacts/ArtifactsCollect.vue"
125 import ArtifactsCommand from "@/components/artifacts/ArtifactsCommand.vue"
126 import ArtifactsQuarantine from "@/components/artifacts/ArtifactsQuarantine.vue"
127 import ActiveResponseAgent from "@/components/activeResponse/ActiveResponseAgent.vue"
128 +import { useGoto } from "@/composables/useGoto"
129
130 const StarIcon = "carbon:star"
131 const QuarantinedIcon = "ph:seal-warning-light"
132 const ArrowIcon = "carbon:arrow-left"
133
134 +const { gotoAgent } = useGoto()
135 const message = useMessage()
136 const router = useRouter()
137 const dialog = useDialog()
@@ -159,12 +161,12 @@ function getAgent() {
161 agent.value = res.data.agents[0] || null
162 } else {
163 message.error(res.data?.message || "An error occurred. Please try again later.")
162 - gotoAgents()
164 + gotoAgent()
165 }
166 })
167 .catch(err => {
168 message.error(err.response?.data?.message || "An error occurred. Please try again later.")
167 - gotoAgents()
169 + gotoAgent()
170 })
171 .finally(() => {
172 loadingAgent.value = false
@@ -201,7 +203,7 @@ function handleDelete() {
203 loadingAgent.value = true
204 },
205 cbSuccess: () => {
204 - gotoAgents()
206 + gotoAgent()
207 },
208 cbAfter: () => {
209 loadingAgent.value = false
@@ -210,10 +212,6 @@ function handleDelete() {
212 }
213 }
214
213 -function gotoAgents() {
214 - router.push({ name: "Agents" })
215 -}
216 -
215 onBeforeMount(() => {
216 if (route.params.id) {
217 agentId.value = route.params.id.toString()
frontend/src/views/soc/PendingAlerts.vue new
+9
@@ -0,0 +1,9 @@
1 +<template>
2 + <div class="page">
3 + <MonitoringAlerts />
4 + </div>
5 +</template>
6 +
7 +<script setup lang="ts">
8 +import MonitoringAlerts from "@/components/monitoringAlerts/List.vue"
9 +</script>