github.com/thanos-io/thanos@v0.32.5/pkg/ui/react-app/src/pages/serviceDiscovery/Services.tsx (about) 1 import React, { ChangeEvent, FC, useEffect, useState } from 'react'; 2 import { RouteComponentProps } from '@reach/router'; 3 import PathPrefixProps from '../../types/PathPrefixProps'; 4 import { useFetch } from '../../hooks/useFetch'; 5 import { LabelsTable } from './LabelsTable'; 6 import { Target, Labels, DroppedTarget } from '../targets/target'; 7 8 import { withStatusIndicator } from '../../components/withStatusIndicator'; 9 import { mapObjEntries } from '../../utils'; 10 import { KVSearch } from '@nexucis/kvsearch'; 11 import { Container } from 'reactstrap'; 12 import SearchBar from '../../components/SearchBar'; 13 14 interface ServiceMap { 15 activeTargets: Target[]; 16 droppedTargets: DroppedTarget[]; 17 } 18 19 export interface TargetLabels { 20 discoveredLabels: Labels; 21 labels: Labels; 22 isDropped: boolean; 23 } 24 25 const kvSearch = new KVSearch<Target>({ 26 shouldSort: true, 27 indexedKeys: ['labels', 'discoveredLabels', ['discoveredLabels', /.*/], ['labels', /.*/]], 28 }); 29 30 export const processSummary = (activeTargets: Target[], droppedTargets: DroppedTarget[]) => { 31 const targets: Record<string, { active: number; total: number }> = {}; 32 33 // Get targets of each type along with the total and active end points 34 for (const target of activeTargets) { 35 const { scrapePool: name } = target; 36 if (!targets[name]) { 37 targets[name] = { 38 total: 0, 39 active: 0, 40 }; 41 } 42 targets[name].total++; 43 targets[name].active++; 44 } 45 for (const target of droppedTargets) { 46 const { job: name } = target.discoveredLabels; 47 if (!targets[name]) { 48 targets[name] = { 49 total: 0, 50 active: 0, 51 }; 52 } 53 targets[name].total++; 54 } 55 56 return targets; 57 }; 58 59 export const processTargets = (activeTargets: Target[], droppedTargets: DroppedTarget[]) => { 60 const labels: Record<string, TargetLabels[]> = {}; 61 62 for (const target of activeTargets) { 63 const name = target.scrapePool; 64 if (!labels[name]) { 65 labels[name] = []; 66 } 67 labels[name].push({ 68 discoveredLabels: target.discoveredLabels, 69 labels: target.labels, 70 isDropped: false, 71 }); 72 } 73 74 for (const target of droppedTargets) { 75 const { job: name } = target.discoveredLabels; 76 if (!labels[name]) { 77 labels[name] = []; 78 } 79 labels[name].push({ 80 discoveredLabels: target.discoveredLabels, 81 isDropped: true, 82 labels: {}, 83 }); 84 } 85 86 return labels; 87 }; 88 89 export const ServiceDiscoveryContent: FC<ServiceMap> = ({ activeTargets, droppedTargets }) => { 90 const [activeTargetList, setActiveTargetList] = useState(activeTargets); 91 const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets)); 92 const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets)); 93 94 const handleSearchChange = (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { 95 if (e.target.value !== '') { 96 const result = kvSearch.filter(e.target.value.trim(), activeTargets); 97 setActiveTargetList(result.map((value) => value.original)); 98 } else { 99 setActiveTargetList(activeTargets); 100 } 101 }; 102 103 useEffect(() => { 104 setTargetList(processSummary(activeTargetList, droppedTargets)); 105 setLabelList(processTargets(activeTargetList, droppedTargets)); 106 }, [activeTargetList, droppedTargets]); 107 108 return ( 109 <> 110 <h2>Service Discovery</h2> 111 <Container> 112 <SearchBar handleChange={handleSearchChange} placeholder="Filter by labels" /> 113 </Container> 114 <ul> 115 {mapObjEntries(targetList, ([k, v]) => ( 116 <li key={k}> 117 <a href={'#' + k}> 118 {k} ({v.active} / {v.total} active targets) 119 </a> 120 </li> 121 ))} 122 </ul> 123 <hr /> 124 {mapObjEntries(labelList, ([k, v]) => { 125 return <LabelsTable value={v} name={k} key={k} />; 126 })} 127 </> 128 ); 129 }; 130 ServiceDiscoveryContent.displayName = 'ServiceDiscoveryContent'; 131 132 const ServicesWithStatusIndicator = withStatusIndicator(ServiceDiscoveryContent); 133 134 const ServiceDiscovery: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => { 135 const { response, error, isLoading } = useFetch<ServiceMap>(`${pathPrefix}/api/v1/targets`); 136 return ( 137 <ServicesWithStatusIndicator 138 {...response.data} 139 error={error} 140 isLoading={isLoading} 141 componentTitle="Service Discovery information" 142 /> 143 ); 144 }; 145 146 export default ServiceDiscovery;