prometheus

Форк
0
/
ScrapePoolList.tsx 
262 строки · 9.3 Кб
1
import { KVSearch } from '@nexucis/kvsearch';
2
import { usePathPrefix } from '../../contexts/PathPrefixContext';
3
import { useFetch } from '../../hooks/useFetch';
4
import { API_PATH } from '../../constants/constants';
5
import { filterTargetsByHealth, groupTargets, ScrapePool, ScrapePools, Target } from './target';
6
import { withStatusIndicator } from '../../components/withStatusIndicator';
7
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
8
import { Badge, Col, Collapse, Dropdown, DropdownItem, DropdownMenu, DropdownToggle, Input, Row } from 'reactstrap';
9
import { ScrapePoolContent } from './ScrapePoolContent';
10
import Filter, { Expanded, FilterData } from './Filter';
11
import { useLocalStorage } from '../../hooks/useLocalStorage';
12
import styles from './ScrapePoolPanel.module.css';
13
import { ToggleMoreLess } from '../../components/ToggleMoreLess';
14
import SearchBar from '../../components/SearchBar';
15
import { setQuerySearchFilter, getQuerySearchFilter } from '../../utils/index';
16
import Checkbox from '../../components/Checkbox';
17

18
export interface ScrapePoolNamesListProps {
19
  scrapePools: string[];
20
}
21

22
interface ScrapePoolDropDownProps {
23
  selectedPool: string | null;
24
  scrapePools: string[];
25
  onScrapePoolChange: (name: string) => void;
26
}
27

28
const ScrapePoolDropDown: FC<ScrapePoolDropDownProps> = ({ selectedPool, scrapePools, onScrapePoolChange }) => {
29
  const [dropdownOpen, setDropdownOpen] = useState(false);
30
  const toggle = () => setDropdownOpen((prevState) => !prevState);
31

32
  const [filter, setFilter] = useState<string>('');
33

34
  const filteredPools = scrapePools.filter((pool) => pool.toLowerCase().includes(filter.toLowerCase()));
35

36
  return (
37
    <Dropdown isOpen={dropdownOpen} toggle={toggle}>
38
      <DropdownToggle caret className="mw-100 text-truncate">
39
        {selectedPool === null || !scrapePools.includes(selectedPool) ? 'All scrape pools' : selectedPool}
40
      </DropdownToggle>
41
      <DropdownMenu style={{ maxHeight: 400, overflowY: 'auto' }}>
42
        {selectedPool ? (
43
          <>
44
            <DropdownItem key="__all__" value={null} onClick={() => onScrapePoolChange('')}>
45
              Clear selection
46
            </DropdownItem>
47
            <DropdownItem divider />
48
          </>
49
        ) : null}
50
        <DropdownItem key="__header" header toggle={false}>
51
          <Input autoFocus placeholder="Filter" value={filter} onChange={(event) => setFilter(event.target.value.trim())} />
52
        </DropdownItem>
53
        {scrapePools.length === 0 ? (
54
          <DropdownItem disabled>No scrape pools configured</DropdownItem>
55
        ) : (
56
          filteredPools.map((name) => (
57
            <DropdownItem key={name} value={name} onClick={() => onScrapePoolChange(name)} active={name === selectedPool}>
58
              {name}
59
            </DropdownItem>
60
          ))
61
        )}
62
      </DropdownMenu>
63
    </Dropdown>
64
  );
65
};
66

67
interface ScrapePoolListProps {
68
  scrapePools: string[];
69
  selectedPool: string | null;
70
  onPoolSelect: (name: string) => void;
71
}
72

73
interface ScrapePoolListContentProps extends ScrapePoolListProps {
74
  activeTargets: Target[];
75
}
76

77
const kvSearch = new KVSearch<Target>({
78
  shouldSort: true,
79
  indexedKeys: ['labels', 'scrapePool', ['labels', /.*/]],
80
});
81

82
interface PanelProps {
83
  scrapePool: string;
84
  targetGroup: ScrapePool;
85
  expanded: boolean;
86
  toggleExpanded: () => void;
87
}
88

89
export const ScrapePoolPanel: FC<PanelProps> = (props: PanelProps) => {
90
  const modifier = props.targetGroup.upCount < props.targetGroup.targets.length ? 'danger' : 'normal';
91
  const id = `pool-${props.scrapePool}`;
92
  const anchorProps = {
93
    href: `#${id}`,
94
    id,
95
  };
96
  return (
97
    <div>
98
      <ToggleMoreLess event={props.toggleExpanded} showMore={props.expanded}>
99
        <a className={styles[modifier]} {...anchorProps}>
100
          {`${props.scrapePool} (${props.targetGroup.upCount}/${props.targetGroup.targets.length} up)`}
101
        </a>
102
      </ToggleMoreLess>
103
      <Collapse isOpen={props.expanded}>
104
        <ScrapePoolContent targets={props.targetGroup.targets} />
105
      </Collapse>
106
    </div>
107
  );
108
};
109

110
type targetHealth = 'healthy' | 'unhealthy' | 'unknown';
111

112
const healthColorTuples: Array<[targetHealth, string]> = [
113
  ['healthy', 'success'],
114
  ['unhealthy', 'danger'],
115
  ['unknown', 'warning'],
116
];
117

118
// ScrapePoolListContent is taking care of every possible filter
119
const ScrapePoolListContent: FC<ScrapePoolListContentProps> = ({
120
  activeTargets,
121
  scrapePools,
122
  selectedPool,
123
  onPoolSelect,
124
}) => {
125
  const initialPoolList = groupTargets(activeTargets);
126
  const [poolList, setPoolList] = useState<ScrapePools>(initialPoolList);
127
  const [targetList, setTargetList] = useState(activeTargets);
128

129
  const initialFilter: FilterData = {
130
    showHealthy: true,
131
    showUnhealthy: true,
132
  };
133
  const [filter, setFilter] = useLocalStorage('targets-page-filter', initialFilter);
134

135
  const [healthFilters, setHealthFilters] = useLocalStorage('target-health-filter', {
136
    healthy: true,
137
    unhealthy: true,
138
    unknown: true,
139
  });
140
  const toggleHealthFilter = (val: targetHealth) => () => {
141
    setHealthFilters({
142
      ...healthFilters,
143
      [val]: !healthFilters[val],
144
    });
145
  };
146

147
  const initialExpanded: Expanded = Object.keys(initialPoolList).reduce(
148
    (acc: { [scrapePool: string]: boolean }, scrapePool: string) => ({
149
      ...acc,
150
      [scrapePool]: true,
151
    }),
152
    {}
153
  );
154
  const [expanded, setExpanded] = useLocalStorage('targets-page-expansion-state', initialExpanded);
155
  const { showHealthy, showUnhealthy } = filter;
156

157
  const handleSearchChange = useCallback(
158
    (value: string) => {
159
      setQuerySearchFilter(value);
160
      if (value !== '') {
161
        const result = kvSearch.filter(value.trim(), activeTargets);
162
        setTargetList(result.map((value) => value.original));
163
      } else {
164
        setTargetList(activeTargets);
165
      }
166
    },
167
    [activeTargets]
168
  );
169

170
  const defaultValue = useMemo(getQuerySearchFilter, []);
171

172
  useEffect(() => {
173
    const list = targetList.filter((t) => showHealthy || t.health.toLowerCase() !== 'up');
174
    setPoolList(groupTargets(list));
175
  }, [showHealthy, targetList]);
176

177
  return (
178
    <>
179
      <Row className="align-items-center">
180
        <Col className="flex-grow-0 py-1">
181
          <ScrapePoolDropDown selectedPool={selectedPool} scrapePools={scrapePools} onScrapePoolChange={onPoolSelect} />
182
        </Col>
183
        <Col className="flex-grow-0 py-1">
184
          <Filter filter={filter} setFilter={setFilter} expanded={expanded} setExpanded={setExpanded} />
185
        </Col>
186
        <Col className="flex-grow-1 py-1">
187
          <SearchBar
188
            defaultValue={defaultValue}
189
            handleChange={handleSearchChange}
190
            placeholder="Filter by endpoint or labels"
191
          />
192
        </Col>
193
        <Col className="flex-grow-0 py-1">
194
          <div className="d-flex flex-row-reverse">
195
            {healthColorTuples.map(([val, color]) => (
196
              <Checkbox
197
                wrapperStyles={{ marginBottom: 0 }}
198
                key={val}
199
                checked={healthFilters[val]}
200
                id={`${val}-toggler`}
201
                onChange={toggleHealthFilter(val)}
202
              >
203
                <Badge color={color} className="text-capitalize">
204
                  {val}
205
                </Badge>
206
              </Checkbox>
207
            ))}
208
          </div>
209
        </Col>
210
      </Row>
211
      {Object.keys(poolList)
212
        .filter((scrapePool) => {
213
          const targetGroup = poolList[scrapePool];
214
          const isHealthy = targetGroup.upCount === targetGroup.targets.length;
215
          return (isHealthy && showHealthy) || (!isHealthy && showUnhealthy);
216
        })
217
        .map<JSX.Element>((scrapePool) => (
218
          <ScrapePoolPanel
219
            key={scrapePool}
220
            scrapePool={scrapePool}
221
            targetGroup={{
222
              upCount: poolList[scrapePool].upCount,
223
              targets: poolList[scrapePool].targets.filter((target) => filterTargetsByHealth(target.health, healthFilters)),
224
            }}
225
            expanded={expanded[scrapePool]}
226
            toggleExpanded={(): void => setExpanded({ ...expanded, [scrapePool]: !expanded[scrapePool] })}
227
          />
228
        ))}
229
    </>
230
  );
231
};
232

233
const ScrapePoolListWithStatusIndicator = withStatusIndicator(ScrapePoolListContent);
234

235
export const ScrapePoolList: FC<ScrapePoolListProps> = ({ selectedPool, scrapePools, ...props }) => {
236
  // If we have more than 20 scrape pools AND there's no pool selected then select first pool
237
  // by default. This is to avoid loading a huge list of targets when we have many pools configured.
238
  // If we have up to 20 scrape pools then pass whatever is the value of selectedPool, it can
239
  // be a pool name or a null (if all pools should be shown).
240
  const poolToShow = selectedPool === null && scrapePools.length > 20 ? scrapePools[0] : selectedPool;
241

242
  const pathPrefix = usePathPrefix();
243
  const { response, error, isLoading } = useFetch<ScrapePoolListContentProps>(
244
    `${pathPrefix}/${API_PATH}/targets?state=active${poolToShow === null ? '' : `&scrapePool=${poolToShow}`}`
245
  );
246
  const { status: responseStatus } = response;
247
  const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching';
248

249
  return (
250
    <ScrapePoolListWithStatusIndicator
251
      {...props}
252
      {...response.data}
253
      selectedPool={poolToShow}
254
      scrapePools={scrapePools}
255
      error={badResponse ? new Error(responseStatus) : error}
256
      isLoading={isLoading}
257
      componentTitle="Targets information"
258
    />
259
  );
260
};
261

262
export default ScrapePoolList;
263

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

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

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

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