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>