github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/pages/targets/ScrapePoolList.tsx (about) 1 import React, { ChangeEvent, FC, useEffect, useState } from 'react'; 2 import Filter, { Expanded } from './Filter'; 3 import { useFetch } from '../../hooks/useFetch'; 4 import { groupTargets, ScrapePool, ScrapePools, Target } from './target'; 5 import PathPrefixProps from '../../types/PathPrefixProps'; 6 import { withStatusIndicator } from '../../components/withStatusIndicator'; 7 import { useLocalStorage } from '../../hooks/useLocalStorage'; 8 import { ToggleMoreLess } from '../../components/ToggleMoreLess'; 9 import { KVSearch } from '@nexucis/kvsearch'; 10 import styles from './ScrapePoolPanel.module.css'; 11 import { Col, Collapse, Row } from 'reactstrap'; 12 import SearchBar from '../../components/SearchBar'; 13 import { ScrapePoolContent } from './ScrapePoolContent'; 14 15 interface ScrapePoolListProps { 16 activeTargets: Target[]; 17 } 18 19 const kvSearch = new KVSearch<Target>({ 20 shouldSort: true, 21 indexedKeys: ['labels', 'scrapePool', ['labels', /.*/]], 22 }); 23 24 interface PanelProps { 25 scrapePool: string; 26 targetGroup: ScrapePool; 27 expanded: boolean; 28 toggleExpanded: () => void; 29 } 30 31 export const ScrapePoolPanel: FC<PanelProps> = (props: PanelProps) => { 32 const modifier = props.targetGroup.upCount < props.targetGroup.targets.length ? 'danger' : 'normal'; 33 const id = `pool-${props.scrapePool}`; 34 const anchorProps = { 35 href: `#${id}`, 36 id, 37 }; 38 return ( 39 <div> 40 <ToggleMoreLess event={props.toggleExpanded} showMore={props.expanded}> 41 <a className={styles[modifier]} {...anchorProps}> 42 {`${props.scrapePool} (${props.targetGroup.upCount}/${props.targetGroup.targets.length} up)`} 43 </a> 44 </ToggleMoreLess> 45 <Collapse isOpen={props.expanded}> 46 <ScrapePoolContent targets={props.targetGroup.targets} /> 47 </Collapse> 48 </div> 49 ); 50 }; 51 52 export const ScrapePoolListContent: FC<ScrapePoolListProps> = ({ activeTargets }) => { 53 const initialPoolList = groupTargets(activeTargets); 54 const [poolList, setPoolList] = useState<ScrapePools>(initialPoolList); 55 const [targetList, setTargetList] = useState(activeTargets); 56 const [filter, setFilter] = useLocalStorage('targets-page-filter', { showHealthy: true, showUnhealthy: true }); 57 58 const initialExpanded: Expanded = Object.keys(initialPoolList).reduce( 59 (acc: { [scrapePool: string]: boolean }, scrapePool: string) => ({ 60 ...acc, 61 [scrapePool]: true, 62 }), 63 {} 64 ); 65 const [expanded, setExpanded] = useLocalStorage('targets-page-expansion-state', initialExpanded); 66 const { showHealthy, showUnhealthy } = filter; 67 68 const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { 69 if (e.target.value !== '') { 70 const result = kvSearch.filter(e.target.value.trim(), activeTargets); 71 setTargetList(result.map((value) => value.original)); 72 } else { 73 setTargetList(activeTargets); 74 } 75 }; 76 77 useEffect(() => { 78 const list = targetList.filter((t) => showHealthy || t.health.toLowerCase() !== 'up'); 79 setPoolList(groupTargets(list)); 80 }, [showHealthy, targetList]); 81 82 return ( 83 <> 84 <Row xs="4" className="align-items-center"> 85 <Col> 86 <Filter filter={filter} setFilter={setFilter} expanded={expanded} setExpanded={setExpanded} /> 87 </Col> 88 <Col xs="6"> 89 <SearchBar handleChange={handleSearchChange} placeholder="Filter by endpoint or labels" /> 90 </Col> 91 </Row> 92 {Object.keys(poolList) 93 .filter((scrapePool) => { 94 const targetGroup = poolList[scrapePool]; 95 const isHealthy = targetGroup.upCount === targetGroup.targets.length; 96 return (isHealthy && showHealthy) || (!isHealthy && showUnhealthy); 97 }) 98 .map<JSX.Element>((scrapePool) => ( 99 <ScrapePoolPanel 100 key={scrapePool} 101 scrapePool={scrapePool} 102 targetGroup={poolList[scrapePool]} 103 expanded={expanded[scrapePool]} 104 toggleExpanded={(): void => setExpanded({ ...expanded, [scrapePool]: !expanded[scrapePool] })} 105 /> 106 ))} 107 </> 108 ); 109 }; 110 ScrapePoolListContent.displayName = 'ScrapePoolListContent'; 111 112 const ScrapePoolListWithStatusIndicator = withStatusIndicator(ScrapePoolListContent); 113 114 const ScrapePoolList: FC<PathPrefixProps> = ({ pathPrefix }) => { 115 const { response, error, isLoading } = useFetch<ScrapePoolListProps>(`${pathPrefix}/api/v1/targets?state=active`); 116 const { status: responseStatus } = response; 117 const badResponse = responseStatus !== 'success' && responseStatus !== 'start fetching'; 118 return ( 119 <ScrapePoolListWithStatusIndicator 120 {...response.data} 121 error={badResponse ? new Error(responseStatus) : error} 122 isLoading={isLoading} 123 componentTitle="Targets information" 124 /> 125 ); 126 }; 127 128 export default ScrapePoolList;