github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/pages/alerts/AlertContents.tsx (about)

     1  import React, { FC, useState, Fragment, ChangeEvent, useEffect } from 'react';
     2  import { Badge, Col, Row } from 'reactstrap';
     3  import CollapsibleAlertPanel from './CollapsibleAlertPanel';
     4  import Checkbox from '../../components/Checkbox';
     5  import { isPresent } from '../../utils';
     6  import { Rule } from '../../types/types';
     7  import { KVSearch } from '@nexucis/kvsearch';
     8  import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll';
     9  import SearchBar from '../../components/SearchBar';
    10  
    11  export type RuleState = keyof RuleStatus<any>;
    12  
    13  export interface RuleStatus<T> {
    14    firing: T;
    15    pending: T;
    16    inactive: T;
    17  }
    18  
    19  export interface AlertsProps {
    20    groups?: RuleGroup[];
    21    statsCount: RuleStatus<number>;
    22  }
    23  
    24  export interface Alert {
    25    labels: Record<string, string>;
    26    state: RuleState;
    27    value: string;
    28    annotations: Record<string, string>;
    29    activeAt: string;
    30  }
    31  
    32  interface RuleGroup {
    33    name: string;
    34    file: string;
    35    rules: Rule[];
    36    interval: number;
    37  }
    38  
    39  const kvSearchRule = new KVSearch<Rule>({
    40    shouldSort: true,
    41    indexedKeys: ['name', 'labels', ['labels', /.*/]],
    42  });
    43  
    44  const stateColorTuples: Array<[RuleState, 'success' | 'warning' | 'danger']> = [
    45    ['inactive', 'success'],
    46    ['pending', 'warning'],
    47    ['firing', 'danger'],
    48  ];
    49  
    50  function GroupContent(showAnnotations: boolean) {
    51    const Content: FC<InfiniteScrollItemsProps<Rule>> = ({ items }) => {
    52      return (
    53        <>
    54          {items.map((rule, j) => (
    55            <CollapsibleAlertPanel key={rule.name + j} showAnnotations={showAnnotations} rule={rule} />
    56          ))}
    57        </>
    58      );
    59    };
    60    return Content;
    61  }
    62  
    63  const AlertsContent: FC<AlertsProps> = ({ groups = [], statsCount }) => {
    64    const [groupList, setGroupList] = useState(groups);
    65    const [filteredList, setFilteredList] = useState(groups);
    66    const [filter, setFilter] = useState<RuleStatus<boolean>>({
    67      firing: true,
    68      pending: true,
    69      inactive: true,
    70    });
    71    const [showAnnotations, setShowAnnotations] = useState(false);
    72  
    73    const toggleFilter = (ruleState: RuleState) => () => {
    74      setFilter({
    75        ...filter,
    76        [ruleState]: !filter[ruleState],
    77      });
    78    };
    79  
    80    const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
    81      if (e.target.value !== '') {
    82        const pattern = e.target.value.trim();
    83        const result: RuleGroup[] = [];
    84        for (const group of groups) {
    85          const ruleFilterList = kvSearchRule.filter(pattern, group.rules);
    86          if (ruleFilterList.length > 0) {
    87            result.push({
    88              file: group.file,
    89              name: group.name,
    90              interval: group.interval,
    91              rules: ruleFilterList.map((value) => value.original),
    92            });
    93          }
    94        }
    95        setGroupList(result);
    96      } else {
    97        setGroupList(groups);
    98      }
    99    };
   100  
   101    useEffect(() => {
   102      const result: RuleGroup[] = [];
   103      for (const group of groupList) {
   104        const newGroup = {
   105          file: group.file,
   106          name: group.name,
   107          interval: group.interval,
   108          rules: group.rules.filter((value) => filter[value.state]),
   109        };
   110        if (newGroup.rules.length > 0) {
   111          result.push(newGroup);
   112        }
   113      }
   114      setFilteredList(result);
   115    }, [groupList, filter]);
   116  
   117    return (
   118      <>
   119        <Row className="align-items-center">
   120          <Col className="d-flex" lg="4" md="5">
   121            {stateColorTuples.map(([state, color]) => {
   122              return (
   123                <Checkbox key={state} checked={filter[state]} id={`${state}-toggler`} onChange={toggleFilter(state)}>
   124                  <Badge color={color} className="text-capitalize">
   125                    {state} ({statsCount[state]})
   126                  </Badge>
   127                </Checkbox>
   128              );
   129            })}
   130          </Col>
   131          <Col lg="5" md="4">
   132            <SearchBar handleChange={handleSearchChange} placeholder="Filter by name or labels" />
   133          </Col>
   134          <Col className="d-flex flex-row-reverse" md="3">
   135            <Checkbox
   136              checked={showAnnotations}
   137              id="show-annotations-toggler"
   138              onChange={({ target }) => setShowAnnotations(target.checked)}
   139            >
   140              <span style={{ fontSize: '0.9rem', lineHeight: 1.9, display: 'inline-block', whiteSpace: 'nowrap' }}>
   141                Show annotations
   142              </span>
   143            </Checkbox>
   144          </Col>
   145        </Row>
   146        {filteredList.map((group, i) => (
   147          <Fragment key={i}>
   148            <GroupInfo rules={group.rules}>
   149              {group.file} &gt; {group.name}
   150            </GroupInfo>
   151            <CustomInfiniteScroll allItems={group.rules} child={GroupContent(showAnnotations)} />
   152          </Fragment>
   153        ))}
   154      </>
   155    );
   156  };
   157  
   158  interface GroupInfoProps {
   159    rules: Rule[];
   160  }
   161  
   162  export const GroupInfo: FC<GroupInfoProps> = ({ rules, children }) => {
   163    const statesCounter = rules.reduce<any>(
   164      (acc, r) => {
   165        return {
   166          ...acc,
   167          [r.state]: acc[r.state] + r.alerts.length,
   168        };
   169      },
   170      {
   171        firing: 0,
   172        pending: 0,
   173      }
   174    );
   175  
   176    return (
   177      <div className="group-info border rounded-sm" style={{ lineHeight: 1.1 }}>
   178        {children}
   179        <div className="badges-wrapper">
   180          {isPresent(statesCounter.inactive) && <Badge color="success">inactive</Badge>}
   181          {statesCounter.pending > 0 && <Badge color="warning">pending ({statesCounter.pending})</Badge>}
   182          {statesCounter.firing > 0 && <Badge color="danger">firing ({statesCounter.firing})</Badge>}
   183        </div>
   184      </div>
   185    );
   186  };
   187  
   188  AlertsContent.displayName = 'Alerts';
   189  
   190  export default AlertsContent;