diff --git a/web/ui/react-app/src/components/CustomInfiniteScroll.tsx b/web/ui/react-app/src/components/CustomInfiniteScroll.tsx new file mode 100644 index 000000000..87f1d6067 --- /dev/null +++ b/web/ui/react-app/src/components/CustomInfiniteScroll.tsx @@ -0,0 +1,50 @@ +import { ComponentType, useEffect, useState } from 'react'; +import InfiniteScroll from 'react-infinite-scroll-component'; + +const initialNumberOfItemsDisplayed = 50; + +export interface InfiniteScrollItemsProps { + items: T[]; +} + +interface CustomInfiniteScrollProps { + allItems: T[]; + child: ComponentType>; +} + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +const CustomInfiniteScroll = ({ allItems, child }: CustomInfiniteScrollProps) => { + const [items, setItems] = useState(allItems.slice(0, 50)); + const [index, setIndex] = useState(initialNumberOfItemsDisplayed); + const [hasMore, setHasMore] = useState(allItems.length > initialNumberOfItemsDisplayed); + const Child = child; + + useEffect(() => { + setItems(allItems.slice(0, initialNumberOfItemsDisplayed)); + setHasMore(allItems.length > initialNumberOfItemsDisplayed); + }, [allItems]); + + const fetchMoreData = () => { + if (items.length === allItems.length) { + setHasMore(false); + } else { + const newIndex = index + initialNumberOfItemsDisplayed; + setIndex(newIndex); + setItems(allItems.slice(0, newIndex)); + } + }; + + return ( + loading...} + dataLength={items.length} + height={items.length > 25 ? '75vh' : ''} + > + + + ); +}; + +export default CustomInfiniteScroll; diff --git a/web/ui/react-app/src/pages/serviceDiscovery/LabelsTable.tsx b/web/ui/react-app/src/pages/serviceDiscovery/LabelsTable.tsx index 01c657c3a..6fb633db3 100644 --- a/web/ui/react-app/src/pages/serviceDiscovery/LabelsTable.tsx +++ b/web/ui/react-app/src/pages/serviceDiscovery/LabelsTable.tsx @@ -2,6 +2,7 @@ import React, { FC, useState } from 'react'; import { Badge, Table } from 'reactstrap'; import { TargetLabels } from './Services'; import { ToggleMoreLess } from '../../components/ToggleMoreLess'; +import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll'; interface LabelProps { value: TargetLabels[]; @@ -20,6 +21,33 @@ const formatLabels = (labels: Record | string) => { }); }; +const LabelsTableContent: FC> = ({ items }) => { + return ( + + + + + + + + + {items.map((_, i) => { + return ( + + + {items[i].isDropped ? ( + + ) : ( + + )} + + ); + })} + +
Discovered LabelsTarget Labels
{formatLabels(items[i].discoveredLabels)}Dropped{formatLabels(items[i].labels)}
+ ); +}; + export const LabelsTable: FC = ({ value, name }) => { const [showMore, setShowMore] = useState(false); @@ -35,30 +63,7 @@ export const LabelsTable: FC = ({ value, name }) => { {name} - {showMore ? ( - - - - - - - - - {value.map((_, i) => { - return ( - - - {value[i].isDropped ? ( - - ) : ( - - )} - - ); - })} - -
Discovered LabelsTarget Labels
{formatLabels(value[i].discoveredLabels)}Dropped{formatLabels(value[i].labels)}
- ) : null} + {showMore ? : null} ); }; diff --git a/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx b/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx index 28d6db6dc..ed90b7e3e 100644 --- a/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx +++ b/web/ui/react-app/src/pages/serviceDiscovery/Services.tsx @@ -1,4 +1,4 @@ -import React, { FC } from 'react'; +import React, { ChangeEvent, FC, useEffect, useState } from 'react'; import { useFetch } from '../../hooks/useFetch'; import { LabelsTable } from './LabelsTable'; import { DroppedTarget, Labels, Target } from '../targets/target'; @@ -7,6 +7,10 @@ import { withStatusIndicator } from '../../components/withStatusIndicator'; import { mapObjEntries } from '../../utils'; import { usePathPrefix } from '../../contexts/PathPrefixContext'; import { API_PATH } from '../../constants/constants'; +import { KVSearch } from '@nexucis/kvsearch'; +import { Container, Input, InputGroup, InputGroupAddon, InputGroupText } from 'reactstrap'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSearch } from '@fortawesome/free-solid-svg-icons'; interface ServiceMap { activeTargets: Target[]; @@ -19,6 +23,11 @@ export interface TargetLabels { isDropped: boolean; } +const kvSearch = new KVSearch({ + shouldSort: true, + indexedKeys: ['labels', 'discoveredLabels', ['discoveredLabels', /.*/], ['labels', /.*/]], +}); + export const processSummary = ( activeTargets: Target[], droppedTargets: DroppedTarget[] @@ -82,14 +91,41 @@ export const processTargets = (activeTargets: Target[], droppedTargets: DroppedT }; export const ServiceDiscoveryContent: FC = ({ activeTargets, droppedTargets }) => { - const targets = processSummary(activeTargets, droppedTargets); - const labels = processTargets(activeTargets, droppedTargets); + const [activeTargetList, setActiveTargetList] = useState(activeTargets); + const [targetList, setTargetList] = useState(processSummary(activeTargets, droppedTargets)); + const [labelList, setLabelList] = useState(processTargets(activeTargets, droppedTargets)); + + const handleSearchChange = (e: ChangeEvent) => { + if (e.target.value !== '') { + const result = kvSearch.filter(e.target.value.trim(), activeTargets); + setActiveTargetList( + result.map((value) => { + return value.original as unknown as Target; + }) + ); + } else { + setActiveTargetList(activeTargets); + } + }; + + useEffect(() => { + setTargetList(processSummary(activeTargetList, droppedTargets)); + setLabelList(processTargets(activeTargetList, droppedTargets)); + }, [activeTargetList, droppedTargets]); return ( <>

Service Discovery

+ + + + {} + + + +
- {mapObjEntries(labels, ([k, v]) => { + {mapObjEntries(labelList, ([k, v]) => { return ; })} diff --git a/web/ui/react-app/src/pages/targets/ScrapePoolContent.tsx b/web/ui/react-app/src/pages/targets/ScrapePoolContent.tsx index 8a55a09b1..e1cbce6fc 100644 --- a/web/ui/react-app/src/pages/targets/ScrapePoolContent.tsx +++ b/web/ui/react-app/src/pages/targets/ScrapePoolContent.tsx @@ -1,6 +1,5 @@ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC } from 'react'; import { getColor, Target } from './target'; -import InfiniteScroll from 'react-infinite-scroll-component'; import { Badge, Table } from 'reactstrap'; import TargetLabels from './TargetLabels'; import styles from './ScrapePoolPanel.module.css'; @@ -8,84 +7,61 @@ import { formatRelative } from '../../utils'; import { now } from 'moment'; import TargetScrapeDuration from './TargetScrapeDuration'; import EndpointLink from './EndpointLink'; +import CustomInfiniteScroll, { InfiniteScrollItemsProps } from '../../components/CustomInfiniteScroll'; const columns = ['Endpoint', 'State', 'Labels', 'Last Scrape', 'Scrape Duration', 'Error']; -const initialNumberOfTargetsDisplayed = 50; interface ScrapePoolContentProps { targets: Target[]; } -export const ScrapePoolContent: FC = ({ targets }) => { - const [items, setItems] = useState(targets.slice(0, 50)); - const [index, setIndex] = useState(initialNumberOfTargetsDisplayed); - const [hasMore, setHasMore] = useState(targets.length > initialNumberOfTargetsDisplayed); - - useEffect(() => { - setItems(targets.slice(0, initialNumberOfTargetsDisplayed)); - setHasMore(targets.length > initialNumberOfTargetsDisplayed); - }, [targets]); - - const fetchMoreData = () => { - if (items.length === targets.length) { - setHasMore(false); - } else { - const newIndex = index + initialNumberOfTargetsDisplayed; - setIndex(newIndex); - setItems(targets.slice(0, newIndex)); - } - }; - +const ScrapePoolContentTable: FC> = ({ items }) => { return ( - loading...} - dataLength={items.length} - height={items.length > 25 ? '75vh' : ''} - > - - - - {columns.map((column) => ( - - ))} - - - - {items.map((target, index) => ( - - - - - - - - +
{column}
- - - {target.health.toUpperCase()} - - - {formatRelative(target.lastScrape, now())} - - - {target.lastError ? {target.lastError} : null} -
+ + + {columns.map((column) => ( + ))} - -
{column}
-
+ + + + {items.map((target, index) => ( + + + + + + {target.health.toUpperCase()} + + + + + {formatRelative(target.lastScrape, now())} + + + + + {target.lastError ? {target.lastError} : null} + + + ))} + + ); }; + +export const ScrapePoolContent: FC = ({ targets }) => { + return ; +};