prometheus

Форк
0
/
AlertContents.tsx 
199 строк · 5.9 Кб
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';
11

12
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13
export type RuleState = keyof RuleStatus<any>;
14

15
export interface RuleStatus<T> {
16
  firing: T;
17
  pending: T;
18
  inactive: T;
19
}
20

21
export interface AlertsProps {
22
  groups?: RuleGroup[];
23
  statsCount: RuleStatus<number>;
24
}
25

26
export interface Alert {
27
  labels: Record<string, string>;
28
  state: RuleState;
29
  value: string;
30
  annotations: Record<string, string>;
31
  activeAt: string;
32
  keepFiringSince: string;
33
}
34

35
interface RuleGroup {
36
  name: string;
37
  file: string;
38
  rules: Rule[];
39
  interval: number;
40
}
41

42
const kvSearchRule = new KVSearch<Rule>({
43
  shouldSort: true,
44
  indexedKeys: ['name', 'labels', ['labels', /.*/]],
45
});
46

47
const stateColorTuples: Array<[RuleState, 'success' | 'warning' | 'danger']> = [
48
  ['inactive', 'success'],
49
  ['pending', 'warning'],
50
  ['firing', 'danger'],
51
];
52

53
function GroupContent(showAnnotations: boolean) {
54
  const Content: FC<InfiniteScrollItemsProps<Rule>> = ({ items }) => {
55
    return (
56
      <>
57
        {items.map((rule, j) => (
58
          <CollapsibleAlertPanel key={rule.name + j} showAnnotations={showAnnotations} rule={rule} />
59
        ))}
60
      </>
61
    );
62
  };
63
  return Content;
64
}
65

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', {
70
    firing: true,
71
    pending: true,
72
    inactive: true,
73
  });
74
  const [showAnnotations, setShowAnnotations] = useLocalStorage('alerts-annotations-status', { checked: false });
75
  const toggleFilter = (ruleState: RuleState) => () => {
76
    setFilter({
77
      ...filter,
78
      [ruleState]: !filter[ruleState],
79
    });
80
  };
81

82
  const handleSearchChange = useCallback(
83
    (value: string) => {
84
      setQuerySearchFilter(value);
85
      if (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) {
91
            result.push({
92
              file: group.file,
93
              name: group.name,
94
              interval: group.interval,
95
              rules: ruleFilterList.map((value) => value.original),
96
            });
97
          }
98
        }
99
        setGroupList(result);
100
      } else {
101
        setGroupList(groups);
102
      }
103
    },
104
    [groups]
105
  );
106

107
  const defaultValue = useMemo(getQuerySearchFilter, []);
108

109
  useEffect(() => {
110
    const result: RuleGroup[] = [];
111
    for (const group of groupList) {
112
      const newGroup = {
113
        file: group.file,
114
        name: group.name,
115
        interval: group.interval,
116
        rules: group.rules.filter((value) => filter[value.state]),
117
      };
118
      if (newGroup.rules.length > 0) {
119
        result.push(newGroup);
120
      }
121
    }
122
    setFilteredList(result);
123
  }, [groupList, filter]);
124

125
  return (
126
    <>
127
      <Row className="align-items-center">
128
        <Col className="d-flex" lg="4" md="5">
129
          {stateColorTuples.map(([state, color]) => {
130
            return (
131
              <Checkbox key={state} checked={filter[state]} id={`${state}-toggler`} onChange={toggleFilter(state)}>
132
                <Badge color={color} className="text-capitalize">
133
                  {state} ({statsCount[state]})
134
                </Badge>
135
              </Checkbox>
136
            );
137
          })}
138
        </Col>
139
        <Col lg="5" md="4">
140
          <SearchBar defaultValue={defaultValue} handleChange={handleSearchChange} placeholder="Filter by name or labels" />
141
        </Col>
142
        <Col className="d-flex flex-row-reverse" md="3">
143
          <Checkbox
144
            checked={showAnnotations.checked}
145
            id="show-annotations-toggler"
146
            onChange={({ target }) => setShowAnnotations({ checked: target.checked })}
147
          >
148
            <span style={{ fontSize: '0.9rem', lineHeight: 1.9, display: 'inline-block', whiteSpace: 'nowrap' }}>
149
              Show annotations
150
            </span>
151
          </Checkbox>
152
        </Col>
153
      </Row>
154
      {filteredList.map((group, i) => (
155
        <Fragment key={i}>
156
          <GroupInfo rules={group.rules}>
157
            {group.file} &gt; {group.name}
158
          </GroupInfo>
159
          <CustomInfiniteScroll allItems={group.rules} child={GroupContent(showAnnotations.checked)} />
160
        </Fragment>
161
      ))}
162
    </>
163
  );
164
};
165

166
interface GroupInfoProps {
167
  rules: Rule[];
168
}
169

170
export const GroupInfo: FC<GroupInfoProps> = ({ rules, children }) => {
171
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
  const statesCounter = rules.reduce<any>(
173
    (acc, r) => {
174
      return {
175
        ...acc,
176
        [r.state]: acc[r.state] + r.alerts.length,
177
      };
178
    },
179
    {
180
      firing: 0,
181
      pending: 0,
182
    }
183
  );
184

185
  return (
186
    <div className="group-info border rounded-sm" style={{ lineHeight: 1.1 }}>
187
      {children}
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>}
192
      </div>
193
    </div>
194
  );
195
};
196

197
AlertsContent.displayName = 'Alerts';
198

199
export default AlertsContent;
200

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.