1
-<template>
2
- <el-scrollbar class="page page-indices">
3
- bkp
4
- <div class="card-base mb-30">
5
- <IndicesMarquee :indices="indices" @click="setIndex" />
6
- </div>
7
-
8
- <div class="index-details-box">
9
- <div class="flex center demo-box bg-orange">
10
- <el-select v-model="currentIndex" placeholder="Select Your Index" clearable :value-key="'index'">
11
- <el-option v-for="index in indices" :key="index.index" :label="index.index" :value="index"></el-option>
12
- </el-select>
13
- </div>
14
- </div>
15
-
16
- <!--BEGIN TEST-->
17
- <div class="box center left">
18
- <div class="page-header header-primary card-base card-shadow--small">
19
- <h1 class="title">Index Stats</h1>
20
-
21
- <div class="flex justify-center align-center bg-orange" v-if="currentIndex">
22
- <div class="widget-icon-box mr-20 animate__animated animate__fadeInRight">
23
- <span class="badge">
24
- <i v-if="currentIndex.health === 'green'" class="mdi mdi-shield-check"></i>
25
- <i v-else-if="currentIndex.health === 'yellow'" class="mdi mdi-alert"></i>
26
- <i v-else-if="currentIndex.health === 'red'" class="mdi mdi-alert-decagram"></i>
27
- <strong class="accent-text font-size-20">Index:</strong>
28
- </span>
29
- <span class="highlight font-size-20">{{ currentIndex.index }}</span>
30
- </div>
31
- <div class="widget-icon-box mr-20 animate__animated animate__fadeInRight">
32
- <span class="accent-text font-size-20">Health:</span>
33
- <span class="highlight font-size-20">{{ currentIndex.health }}</span>
34
- </div>
35
- </div>
36
- </div>
37
-
38
- <div class="spacer"></div>
39
-
40
- <div class="card-base card-shadow--medium scrollable only-x bg-black">
41
- <el-row class="mt-0" :gutter="30">
42
- <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
43
- <div class="page-table scrollable only-y" id="affix-container">
44
- <div class="page-header">
45
- <h1 class="warning-text">Index Data</h1>
46
- </div>
47
-
48
- <div class="table-box card-base card-shadow--medium scrollable only-x">
49
- <table class="styled striped">
50
- <thead>
51
- <tr>
52
- <th scope="col">Index Name</th>
53
- <th scope="col">Index Health</th>
54
- <th scope="col">Index Document Size</th>
55
- <th scope="col">Storage Size</th>
56
- <th scope="col">Replica Count</th>
57
- </tr>
58
- </thead>
59
- <tr v-for="index in filteredIndices" :key="index.id" :class="{ health: index.health }">
60
- <!-- Display the connector details in the table -->
61
- <td>{{ index.index }}</td>
62
- <td>{{ index.health }}</td>
63
- <td>{{ index.docs_count }}</td>
64
- <td>{{ index.store_size }}</td>
65
- <td>{{ index.replica_count }}</td>
66
-
67
- <!-- Add a buttton to Rotate an Index -->
68
- <td>
69
- <div class="btn-group" role="group" aria-label="Basic example">
70
- <button type="button" class="btn btn-info btn-sm">Rotate Index</button>
71
- </div>
72
- </td>
73
- <!-- Add a buttton to Delete an Index -->
74
- <td>
75
- <div class="btn-group" role="group" aria-label="Basic example">
76
- <button type="button" class="btn btn-info btn-sm" @click="deleteIndex(index.index)">
77
- Delete Index
78
- </button>
79
- </div>
80
- </td>
81
- </tr>
82
- </table>
83
- </div>
84
- </div>
85
- </el-col>
86
- </el-row>
87
-
88
- <!-- New table to display shards -->
89
- <el-row class="mt-0" :gutter="30">
90
- <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
91
- <div class="page-table scrollable only-y" id="affix-container">
92
- <div class="page-header">
93
- <h1 class="warning-text">Index Shards</h1>
94
- </div>
95
-
96
- <div class="table-box card-base card-shadow--medium scrollable only-x">
97
- <table class="styled striped">
98
- <thead>
99
- <tr>
100
- <th scope="col">Shard Index</th>
101
- <th scope="col">Shard ID</th>
102
- <th scope="col">Shard State</th>
103
- <th scope="col">Shard Size</th>
104
- <th scope="col">Shard Node</th>
105
- </tr>
106
- </thead>
107
- <tr v-for="shard in filteredShards" :key="shard.id" class="bg-accent">
108
- <!-- Display the shard details in the table -->
109
- <td>{{ shard.index }}</td>
110
- <td>{{ shard.shard }}</td>
111
- <td>{{ shard.state }}</td>
112
- <td>{{ shard.size }}</td>
113
- <td>{{ shard.node }}</td>
114
- </tr>
115
- </table>
116
- </div>
117
- </div>
118
- </el-col>
119
- </el-row>
120
- </div>
121
-
122
- <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12 el-col-md-12 el-col-lg-16 el-col-xl-16">
123
- <el-row class="chart-row">
124
- <el-col :xs="24" :sm="12" :md="12" :lg="16" :xl="16" class="chart-col">
125
- <div class="chart-container">
126
- <div id="chart" class="chart" :style="{ height: '500px', width: '120%' }"></div>
127
- </div>
128
- </el-col>
129
- <el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="8" class="chart-col">
130
- <div class="chart-container">
131
- <div id="pie" class="chart pie-chart" :style="{ height: '500px', width: '200%' }"></div>
132
- </div>
133
- </el-col>
134
- </el-row>
135
- </div>
136
-
137
- <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12 el-col-md-12 el-col-lg-8 el-col-xl-8 flex box grow">
138
- <!-- New table to display shards -->
139
- <el-row class="mt-0" :gutter="30">
140
- <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
141
- <ClusterHealth />
142
- </el-col>
143
-
144
- <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
145
- <div class="page-table scrollable only-y" id="affix-container">
146
- <div class="page-header">
147
- <h1 class="warning-text">Unhealthy Indices</h1>
148
- </div>
149
-
150
- <div class="table-box card-base card-shadow--medium scrollable only-x">
151
- <table class="styled striped hover">
152
- <thead>
153
- <tr>
154
- <th scope="col">Index Name</th>
155
- <th scope="col">Index Health</th>
156
- </tr>
157
- </thead>
158
- <tbody>
159
- <tr
160
- v-for="index in unhealthyIndices"
161
- :key="index.id"
162
- :class="{
163
- 'bg-orange': index.health === 'yellow',
164
- 'bg-red': index.health === 'red'
165
- }"
166
- >
167
- <!-- Display the shard details in the table -->
168
- <td>{{ index.index }}</td>
169
-
170
- <td>{{ index.health }}</td>
171
- </tr>
172
- </tbody>
173
- </table>
174
- </div>
175
- </div>
176
- </el-col>
177
- </el-row>
178
- </div>
179
- </div>
180
- <!--END TEST-->
181
- </el-scrollbar>
182
-</template>
183
-
184
-<script lang="ts">
185
-import * as echarts from "echarts"
186
-import { Index, IndexAllocation, IndexHealth, IndexShard } from "@/types/indices.d"
187
-import Api from "@/api"
188
-import { ElMessage } from "element-plus"
189
-import { defineComponent } from "vue"
190
-import IndicesMarquee from "@/components/indices/Marquee.vue"
191
-import ClusterHealth from "@/components/indices/ClusterHealth.vue"
192
-
193
-export default defineComponent({
194
- data() {
195
- return {
196
- indices: [] as Index[],
197
- shards: [] as IndexShard[],
198
- indicesAllocation: [] as IndexAllocation[],
199
- loadingIndex: false,
200
- loadingShards: false,
201
- loadingAllocation: false,
202
- loadingDeleteIndex: false,
203
- currentIndex: null as Index | null,
204
- selectedValue: "",
205
- selectedHealth: ""
206
- }
207
- },
208
- computed: {
209
- filteredIndices() {
210
- return this.indices.filter((index: Index) => index.index === this.currentIndex?.index)
211
- },
212
- filteredShards() {
213
- return this.shards.filter((shard: IndexShard) => shard.index === this.currentIndex?.index)
214
- },
215
- unhealthyIndices() {
216
- return this.indices.filter((index: Index) => index.health === IndexHealth.YELLOW || index.health === IndexHealth.RED)
217
- },
218
- loading() {
219
- return this.loadingIndex || this.loadingShards || this.loadingAllocation
220
- }
221
- },
222
- methods: {
223
- setIndex(index: Index) {
224
- this.currentIndex = index
225
- },
226
- initChart() {
227
- // Store the indicesAllocation
228
- const indicesAllocation = this.indicesAllocation
229
- // For the indicesAllocation, get the disk_used, disk_total, and timestamp for all items in the array
230
- let data = indicesAllocation.map(item => {
231
- const date = new Date(item.timestamp)
232
- const hours = date.getHours()
233
- const minutes = date.getMinutes()
234
- return {
235
- diskUsed: item.disk_used,
236
- diskTotal: item.disk_total,
237
- timestamp: `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`
238
- }
239
- })
240
-
241
- // Sort data array by timestamp
242
- data.sort((a, b) => {
243
- const aParts = a.timestamp.split(":").map(Number)
244
- const bParts = b.timestamp.split(":").map(Number)
245
- const aDate = new Date(1970, 0, 1, aParts[0], aParts[1])
246
- const bDate = new Date(1970, 0, 1, bParts[0], bParts[1])
247
- return aDate - bDate
248
- })
249
-
250
- // Get the last 12 data points
251
- data = data.slice(Math.max(data.length - 12, 0))
252
-
253
- // Separate the data array into individual arrays for diskUsed, diskTotal, and timestamp
254
- const diskUsed = data.map(item => item.diskUsed)
255
- const diskTotal = data.map(item => item.diskTotal)
256
- const timestamp = data.map(item => item.timestamp)
257
-
258
- console.log("diskUsed: ", diskUsed)
259
- console.log("diskTotal: ", diskTotal)
260
- console.log("timestamp: ", timestamp)
261
-
262
- // Initialize the chart
263
- this.chart = echarts.init(document.getElementById("chart"))
264
- this.chart.setOption({
265
- //backgroundColor: '#394056',
266
- title: {
267
- top: 20,
268
- text: "Wazuh-Indexer Disk Usage",
269
- textStyle: { fontWeight: "normal", fontSize: 16, fontFamily: "Nunito Sans" /*color: '#F1F1F3'*/ },
270
- left: "1%"
271
- },
272
- tooltip: {
273
- trigger: "axis",
274
- axisPointer: {
275
- lineStyle: {
276
- /*color: '#57617B'*/
277
- }
278
- }
279
- },
280
- legend: {
281
- top: 40,
282
- icon: "rect",
283
- itemWidth: 14,
284
- itemHeight: 5,
285
- itemGap: 13,
286
- data: ["Product-A", "Product-B"],
287
- right: "4%",
288
- textStyle: { fontSize: 12, fontFamily: "Nunito Sans" /*color: '#F1F1F3'*/ }
289
- },
290
- grid: {
291
- top: 100,
292
- left: "-5px",
293
- right: "30px",
294
- bottom: "2%",
295
- containLabel: true
296
- },
297
- xAxis: [
298
- {
299
- type: "category",
300
- boundaryGap: false,
301
- axisLine: {
302
- lineStyle: {
303
- /*color: '#57617B'*/
304
- }
305
- },
306
- data: timestamp //timestamp is the x-axis
307
- }
308
- ],
309
- yAxis: [
310
- {
311
- show: false,
312
- type: "value",
313
- name: "(%)",
314
- axisTick: { show: false },
315
- axisLine: {
316
- lineStyle: {
317
- /*color: '#57617B'*/
318
- }
319
- },
320
- axisLabel: {
321
- margin: 10,
322
- fontSize: 14
323
- },
324
- splitLine: { lineStyle: { color: "#eee" /*color: '#57617B'*/ } }
325
- }
326
- ],
327
- series: [
328
- {
329
- name: "Disk Used",
330
- type: "line",
331
- smooth: true,
332
- symbol: "circle",
333
- symbolSize: 5,
334
- showSymbol: false,
335
- lineStyle: { width: 1 },
336
- areaStyle: {
337
- color: new echarts.graphic.LinearGradient(
338
- 0,
339
- 0,
340
- 0,
341
- 1,
342
- [
343
- {
344
- offset: 0,
345
- color: "rgba(19, 206, 102, 0.3)"
346
- },
347
- {
348
- offset: 0.8,
349
- color: "rgba(19, 206, 102, 0)"
350
- }
351
- ],
352
- false
353
- ),
354
- shadowColor: "rgba(0, 0, 0, 0.1)",
355
- shadowBlur: 10
356
- },
357
- itemStyle: {
358
- color: "rgb(19, 206, 102)",
359
- borderColor: "rgba(19, 206, 102, 0.27)",
360
- borderWidth: 12
361
- },
362
- data: diskUsed
363
- },
364
- {
365
- name: "Disk Total",
366
- type: "line",
367
- smooth: true,
368
- symbol: "circle",
369
- symbolSize: 5,
370
- showSymbol: false,
371
- lineStyle: { width: 1 },
372
- areaStyle: {
373
- color: new echarts.graphic.LinearGradient(
374
- 0,
375
- 0,
376
- 0,
377
- 1,
378
- [
379
- {
380
- offset: 0,
381
- color: "rgba(95, 143, 223, 0.3)"
382
- },
383
- {
384
- offset: 0.8,
385
- color: "rgba(95, 143, 223, 0)"
386
- }
387
- ],
388
- false
389
- ),
390
- shadowColor: "rgba(0, 0, 0, 0.1)",
391
- shadowBlur: 10
392
- },
393
- itemStyle: {
394
- color: "rgb(95, 143, 223)",
395
- borderColor: "rgba(95, 143, 223, 0.2)",
396
- borderWidth: 12
397
- },
398
- data: diskTotal
399
- } /*{
400
- name: 'Product-C',
401
- type: 'line',
402
- smooth: true,
403
- symbol: 'circle',
404
- symbolSize: 5,
405
- showSymbol: false,
406
- lineStyle: { width: 1 },
407
- areaStyle: {
408
-
409
- color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
410
- offset: 0,
411
- color: 'rgba(236, 32, 95, 0.3)'
412
- }, {
413
- offset: 0.8,
414
- color: 'rgba(236, 32, 95, 0)'
415
- }], false),
416
- shadowColor: 'rgba(0, 0, 0, 0.1)',
417
- shadowBlur: 10
418
-
419
- },
420
- itemStyle: {
421
-
422
- color: 'rgb(236, 32, 95)',
423
- borderColor: 'rgba(236, 32, 95, 0.2)',
424
- borderWidth: 12
425
-
426
- },
427
- data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
428
- }*/
429
- ]
430
- })
431
- },
432
- initPie() {
433
- // const topIndexes = this.indices.sort((a, b) => b.store_size - a.store_size).slice(0, 5)
434
- const topIndexes = this.indices.sort((a, b) => b.store_size - a.store_size).slice(0, 8)
435
-
436
- const size = topIndexes.map(index => {
437
- const value = parseFloat(index.store_size) / 1024 // Convert MB to GB
438
- return {
439
- value: value,
440
- name: index.index,
441
- health: index.health,
442
- itemStyle: {
443
- color: "#3f84f6" // Custom color for each index
444
- }
445
- }
446
- })
447
- console.log("Size", size)
448
-
449
- const data = topIndexes.map(index => {
450
- // const value = parseFloat(index.store_size) // Parse the value as a float and remvoe the decimal
451
- const value = parseFloat(index.store_size) / 1000000000 // Parse the value as a float and convert to GB
452
- return {
453
- value: value,
454
- name: index.index,
455
- health: index.health,
456
- itemStyle: {
457
- color: "#3f84f6" // Custom color for each index
458
- }
459
- }
460
- })
461
- console.log(data)
462
-
463
- const redIndices = data.filter(index => index.health === "red")
464
- const yellowIndices = data.filter(index => index.health === "yellow")
465
- const greenIndices = data.filter(index => index.health === "green")
466
-
467
- // Get the percentage of green indices
468
- const greenPercentage = (greenIndices.length / data.length) * 100
469
- const yellowPercentage = (yellowIndices.length / data.length) * 100
470
- const redPercentage = (redIndices.length / data.length) * 100
471
- console.log("Red Indices:", redIndices)
472
- console.log("Yellow Indices:", yellowIndices)
473
- console.log("Green Indices:", greenIndices)
474
- console.log("Green Indices Percentage:", greenPercentage)
475
-
476
- // const ordersValue = data.length > 0 ? data[0].value.toFixed(2) : 0 // Get the rounded value of the first item in the data array
477
-
478
- this.pie = echarts.init(document.getElementById("pie"))
479
- this.pie.setOption({
480
- title: {
481
- top: 20,
482
- text: "Index Status and Top 8 Index Sizes By GB",
483
- textStyle: { fontWeight: "normal", fontSize: 16, fontFamily: "Nunito Sans" /*color: '#F1F1F3'*/ },
484
- left: "1%"
485
- },
486
- tooltip: {
487
- trigger: "item",
488
- formatter: "{a} <br/>{b}: {c} ({d}%)"
489
- },
490
- series: [
491
- {
492
- name: "Index",
493
- type: "pie",
494
- selectedMode: "single",
495
- radius: [0, "35%"],
496
-
497
- label: {
498
- position: "inner"
499
- },
500
- labelLine: {
501
- show: false
502
- },
503
- data: [
504
- {
505
- // set the value as the index size
506
- value: greenPercentage.toFixed(2),
507
- name: "Green Indices",
508
- selected: true,
509
- itemStyle: { color: "rgb(19, 206, 102)" }
510
- },
511
- {
512
- value: yellowPercentage.toFixed(2),
513
- name: "Yellow Indices",
514
- itemStyle: { color: "rgb(255, 255, 0)" }
515
- },
516
- {
517
- value: redPercentage.toFixed(2),
518
- name: "Red Indices",
519
- itemStyle: { color: "rgb(255, 0, 0)" }
520
- }
521
- ]
522
- },
523
- {
524
- name: "Index",
525
- type: "pie",
526
- radius: ["45%", "60%"],
527
- data: size.map((item, index) => ({
528
- value: item.value.toFixed(2),
529
- name: item.name,
530
- itemStyle: {
531
- color: item.itemStyle.color
532
- }
533
- })),
534
-
535
- itemStyle: {
536
- color: "rgb(19, 206, 102)"
537
- }
538
- }
539
- ]
540
- })
541
- },
542
- deleteIndex(index) {
543
- this.loadingDeleteIndex = true
544
-
545
- Api.indices
546
- .deleteIndex(index)
547
- .then(res => {
548
- ElMessage({
549
- message: "Index was successfully deleted.",
550
- type: "success"
551
- })
552
-
553
- this.getIndices()
554
- })
555
- .catch(err => {
556
- if (err.response.status === 401) {
557
- ElMessage({
558
- message: "Wazuh-Indexer returned Unauthorized. Please check your connector credentials.",
559
- type: "error"
560
- })
561
- } else if (err.response.status === 404) {
562
- ElMessage({
563
- message: err.response?.data?.message || "An error occurred. Please try again later.",
564
- type: "error"
565
- })
566
- } else {
567
- ElMessage({
568
- message: "An error occurred. Please try again later.",
569
- type: "error"
570
- })
571
- }
572
- })
573
- .finally(() => {
574
- this.loadingDeleteIndex = false
575
- })
576
- },
577
- getIndicesAllocation() {
578
- this.loadingAllocation = true
579
- Api.indices
580
- .getAllocation()
581
- .then(res => {
582
- this.indicesAllocation = res.data.node_allocation
583
- this.initChart()
584
- })
585
- .catch(err => {
586
- if (err.response.status === 401) {
587
- ElMessage({
588
- message: "Wazuh-Indexer returned Unauthorized. Please check your connector credentials.",
589
- type: "error"
590
- })
591
- } else if (err.response.status === 404) {
592
- ElMessage({
593
- message: "No alerts were found.",
594
- type: "error"
595
- })
596
- } else {
597
- ElMessage({
598
- message: "An error occurred. Please try again later.",
599
- type: "error"
600
- })
601
- }
602
- })
603
- .finally(() => {
604
- this.loadingAllocation = false
605
- })
606
- },
607
- getIndices() {
608
- this.loadingIndex = true
609
- Api.indices
610
- .getIndices()
611
- .then(res => {
612
- this.indices = res.data.indices
613
- this.initPie()
614
- })
615
- .catch(err => {
616
- if (err.response.status === 401) {
617
- ElMessage({
618
- message: "Wazuh-Indexer returned Unauthorized. Please check your connector credentials.",
619
- type: "error"
620
- })
621
- } else if (err.response.status === 404) {
622
- ElMessage({
623
- message: "No alerts were found.",
624
- type: "error"
625
- })
626
- } else {
627
- ElMessage({
628
- message: "An error occurred. Please try again later.",
629
- type: "error"
630
- })
631
- }
632
- })
633
- .finally(() => {
634
- this.loadingIndex = false
635
- })
636
- },
637
- getShards() {
638
- this.loadingShards = true
639
- Api.indices
640
- .getShards()
641
- .then(res => {
642
- this.shards = res.data.shards
643
- })
644
- .catch(err => {
645
- if (err.response.status === 401) {
646
- ElMessage({
647
- message: "Wazuh-Indexer returned Unauthorized. Please check your connector credentials.",
648
- type: "error"
649
- })
650
- } else if (err.response.status === 404) {
651
- ElMessage({
652
- message: "No alerts were found.",
653
- type: "error"
654
- })
655
- } else {
656
- ElMessage({
657
- message: "An error occurred. Please try again later.",
658
- type: "error"
659
- })
660
- }
661
- })
662
- .finally(() => {
663
- this.loadingShards = false
664
- })
665
- },
666
-
667
- getPieChartData() {
668
- const topIndexes = this.indices.sort((a, b) => b.store_size - a.store_size).slice(0, 8)
669
-
670
- return topIndexes.map((index, i) => ({
671
- value: index.store_size,
672
- name: `p${i + 1}`,
673
- itemStyle: {
674
- color: "#3f84f6" // Custom color for each index
675
- }
676
- }))
677
- }
678
- },
679
- beforeUnmount() {
680
- this.pie?.dispose()
681
- this.chart?.dispose()
682
- },
683
- created() {
684
- this.getIndices()
685
- this.getShards()
686
- this.getIndicesAllocation()
687
- },
688
- components: { IndicesMarquee, ClusterHealth }
689
-})
690
-</script>
691
-
692
-<style lang="scss" scoped>
693
-@import "../../../assets/scss/_variables";
694
-</style>