1
import React, { FC, Fragment, useCallback, useEffect, useMemo, useState } from 'react';
2
import { Badge, Col, Row } from 'reactstrap';
3
import CollapsibleAlertPanel from './CollapsibleAlertPanel';
4
import Checkbox from '../../components/Checkbox';
5
import { getQuerySearchFilter, isPresent, setQuerySearchFilter } from '../../utils';
6
import { Rule } from '../../types/types';
7
import { useLocalStorage } from '../../hooks/useLocalStorage';
8
import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
9
import { KVSearch } from '@nexucis/kvsearch';
10
import SearchBar from '../../components/SearchBar';
12
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13
export type RuleState = keyof RuleStatus<any>;
15
export interface RuleStatus<T> {
21
export interface AlertsProps {
23
statsCount: RuleStatus<number>;
26
export interface Alert {
27
labels: Record<string, string>;
30
annotations: Record<string, string>;
32
keepFiringSince: string;
42
const kvSearchRule = new KVSearch<Rule>({
44
indexedKeys: ['name', 'labels', ['labels', /.*/]],
47
const stateColorTuples: Array<[RuleState, 'success' | 'warning' | 'danger']> = [
48
['inactive', 'success'],
49
['pending', 'warning'],
53
function GroupContent(showAnnotations: boolean) {
54
const Content: FC<InfiniteScrollItemsProps<Rule>> = ({ items }) => {
57
{items.map((rule, j) => (
58
<CollapsibleAlertPanel key={rule.name + j} showAnnotations={showAnnotations} rule={rule} />
66
const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
67
const [groupList, setGroupList] = useState(groups);
68
const [filteredList, setFilteredList] = useState(groups);
69
const [filter, setFilter] = useLocalStorage('alerts-status-filter', {
74
const [showAnnotations, setShowAnnotations] = useLocalStorage('alerts-annotations-status', { checked: false });
75
const toggleFilter = (ruleState: RuleState) => () => {
78
[ruleState]: !filter[ruleState],
82
const handleSearchChange = useCallback(
84
setQuerySearchFilter(value);
86
const pattern = value.trim();
87
const result: RuleGroup[] = [];
88
for (const group of groups) {
89
const ruleFilterList = kvSearchRule.filter(pattern, group.rules);
90
if (ruleFilterList.length > 0) {
94
interval: group.interval,
95
rules: ruleFilterList.map((value) => value.original),
101
setGroupList(groups);
107
const defaultValue = useMemo(getQuerySearchFilter, []);
110
const result: RuleGroup[] = [];
111
for (const group of groupList) {
115
interval: group.interval,
116
rules: group.rules.filter((value) => filter[value.state]),
118
if (newGroup.rules.length > 0) {
119
result.push(newGroup);
122
setFilteredList(result);
123
}, [groupList, filter]);
127
<Row className="align-items-center">
128
<Col className="d-flex" lg="4" md="5">
129
{stateColorTuples.map(([state, color]) => {
131
<Checkbox key={state} checked={filter[state]} id={`${state}-toggler`} onChange={toggleFilter(state)}>
132
<Badge color={color} className="text-capitalize">
133
{state} ({statsCount[state]})
140
<SearchBar defaultValue={defaultValue} handleChange={handleSearchChange} placeholder="Filter by name or labels" />
142
<Col className="d-flex flex-row-reverse" md="3">
144
checked={showAnnotations.checked}
145
id="show-annotations-toggler"
146
onChange={({ target }) => setShowAnnotations({ checked: target.checked })}
148
<span style={{ fontSize: '0.9rem', lineHeight: 1.9, display: 'inline-block', whiteSpace: 'nowrap' }}>
154
{filteredList.map((group, i) => (
156
<GroupInfo rules={group.rules}>
157
{group.file} > {group.name}
159
<CustomInfiniteScroll allItems={group.rules} child={GroupContent(showAnnotations.checked)} />
166
interface GroupInfoProps {
170
export const GroupInfo: FC<GroupInfoProps> = ({ rules, children }) => {
171
// eslint-disable-next-line @typescript-eslint/no-explicit-any
172
const statesCounter = rules.reduce<any>(
176
[r.state]: acc[r.state] + r.alerts.length,
186
<div className="group-info border rounded-sm" style={{ lineHeight: 1.1 }}>
188
<div className="badges-wrapper">
189
{isPresent(statesCounter.inactive) && <Badge color="success">inactive</Badge>}
190
{statesCounter.pending > 0 && <Badge color="warning">pending ({statesCounter.pending})</Badge>}
191
{statesCounter.firing > 0 && <Badge color="danger">firing ({statesCounter.firing})</Badge>}
197
AlertsContent.displayName = 'Alerts';
199
export default AlertsContent;