Adds support service discovery page in react ui (#6394)
* active targets component completed Signed-off-by: Harkishen-Singh <harkishensingh@hotmail.com> * support for service-discovery in react ui Signed-off-by: Harkishen-Singh <harkishensingh@hotmail.com> * restored prev files Signed-off-by: Harkishen-Singh <harkishensingh@hotmail.com> * used fc Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * removed trivial keys Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * FC based labels Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implmented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * minor word change Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * before dropped addressed Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * linted Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions. removed false styles Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions. Unified buttons with targets screen. Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * component for ToggleButton Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * removed false Button Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * implemented suggestions. Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * tests for ToggleMoreLess component Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * linted Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * fixed nested h3. implemented suggestions Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com> * linted Signed-off-by: Harkishen Singh <harkishensingh@hotmail.com>pull/6539/head
parent
ae93bae88f
commit
edf8f135bc
@ -0,0 +1,65 @@ |
||||
import React, { FC, useState } from 'react'; |
||||
import { RouteComponentProps } from '@reach/router'; |
||||
import { Badge, Table } from 'reactstrap'; |
||||
import { TargetLabels } from './Services'; |
||||
import { ToggleMoreLess } from './targets/ToggleMoreLess'; |
||||
|
||||
interface LabelProps { |
||||
value: TargetLabels[]; |
||||
name: string; |
||||
} |
||||
|
||||
const formatLabels = (labels: Record<string, string> | string) => { |
||||
return Object.entries(labels).map(([key, value]) => { |
||||
return ( |
||||
<div key={key}> |
||||
<Badge color="primary" className="mr-1"> |
||||
{`${key}="${value}"`} |
||||
</Badge> |
||||
</div> |
||||
); |
||||
}); |
||||
}; |
||||
|
||||
export const LabelsTable: FC<RouteComponentProps & LabelProps> = ({ value, name }) => { |
||||
const [showMore, setShowMore] = useState(false); |
||||
|
||||
return ( |
||||
<> |
||||
<div> |
||||
<ToggleMoreLess |
||||
event={(): void => { |
||||
setShowMore(!showMore); |
||||
}} |
||||
showMore={showMore} |
||||
> |
||||
<span className="target-head">{name}</span> |
||||
</ToggleMoreLess> |
||||
</div> |
||||
{showMore ? ( |
||||
<Table size="sm" bordered hover striped> |
||||
<thead> |
||||
<tr> |
||||
<th>Discovered Labels</th> |
||||
<th>Target Labels</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
{value.map((_, i) => { |
||||
return ( |
||||
<tr key={i}> |
||||
<td>{formatLabels(value[i].discoveredLabels)}</td> |
||||
{value[i].isDropped ? ( |
||||
<td style={{ fontWeight: 'bold' }}>Dropped</td> |
||||
) : ( |
||||
<td>{formatLabels(value[i].labels)}</td> |
||||
)} |
||||
</tr> |
||||
); |
||||
})} |
||||
</tbody> |
||||
</Table> |
||||
) : null} |
||||
</> |
||||
); |
||||
}; |
||||
@ -1,15 +1,119 @@ |
||||
import React, { FC } from 'react'; |
||||
import { RouteComponentProps } from '@reach/router'; |
||||
import PathPrefixProps from '../PathPrefixProps'; |
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'; |
||||
import { Alert } from 'reactstrap'; |
||||
import { useFetch } from '../utils/useFetch'; |
||||
import { LabelsTable } from './LabelsTable'; |
||||
import { Target, Labels, DroppedTarget } from './targets/target'; |
||||
|
||||
const Services: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => ( |
||||
<> |
||||
<h2>Service Discovery</h2> |
||||
<Alert color="warning"> |
||||
This page is still under construction. Please try it in the <a href={`${pathPrefix}/service-discovery`}>Classic UI</a>. |
||||
</Alert> |
||||
</> |
||||
); |
||||
// TODO: Deduplicate with https://github.com/prometheus/prometheus/blob/213a8fe89a7308e73f22888a963cbf9375217cd6/web/ui/react-app/src/pages/targets/ScrapePoolList.tsx#L11-L14
|
||||
interface ServiceMap { |
||||
activeTargets: Target[]; |
||||
droppedTargets: DroppedTarget[]; |
||||
} |
||||
|
||||
export interface TargetLabels { |
||||
discoveredLabels: Labels; |
||||
labels: Labels; |
||||
isDropped: boolean; |
||||
} |
||||
|
||||
const Services: FC<RouteComponentProps & PathPrefixProps> = ({ pathPrefix }) => { |
||||
const { response, error } = useFetch<ServiceMap>(`${pathPrefix}/api/v1/targets`); |
||||
|
||||
const processSummary = (response: ServiceMap) => { |
||||
const targets: any = {}; |
||||
|
||||
// Get targets of each type along with the total and active end points
|
||||
for (const target of response.activeTargets) { |
||||
const { scrapePool: name } = target; |
||||
if (!targets[name]) { |
||||
targets[name] = { |
||||
total: 0, |
||||
active: 0, |
||||
}; |
||||
} |
||||
targets[name].total++; |
||||
targets[name].active++; |
||||
} |
||||
for (const target of response.droppedTargets) { |
||||
const { job: name } = target.discoveredLabels; |
||||
if (!targets[name]) { |
||||
targets[name] = { |
||||
total: 0, |
||||
active: 0, |
||||
}; |
||||
} |
||||
targets[name].total++; |
||||
} |
||||
|
||||
return targets; |
||||
}; |
||||
|
||||
const processTargets = (response: Target[], dropped: DroppedTarget[]) => { |
||||
const labels: Record<string, TargetLabels[]> = {}; |
||||
|
||||
for (const target of response) { |
||||
const name = target.scrapePool; |
||||
if (!labels[name]) { |
||||
labels[name] = []; |
||||
} |
||||
labels[name].push({ |
||||
discoveredLabels: target.discoveredLabels, |
||||
labels: target.labels, |
||||
isDropped: false, |
||||
}); |
||||
} |
||||
|
||||
for (const target of dropped) { |
||||
const { job: name } = target.discoveredLabels; |
||||
if (!labels[name]) { |
||||
labels[name] = []; |
||||
} |
||||
labels[name].push({ |
||||
discoveredLabels: target.discoveredLabels, |
||||
isDropped: true, |
||||
labels: {}, |
||||
}); |
||||
} |
||||
|
||||
return labels; |
||||
}; |
||||
|
||||
if (error) { |
||||
return ( |
||||
<Alert color="danger"> |
||||
<strong>Error:</strong> Error fetching Service-Discovery: {error.message} |
||||
</Alert> |
||||
); |
||||
} else if (response.data) { |
||||
const targets = processSummary(response.data); |
||||
const labels = processTargets(response.data.activeTargets, response.data.droppedTargets); |
||||
|
||||
return ( |
||||
<> |
||||
<h2>Service Discovery</h2> |
||||
<ul> |
||||
{Object.keys(targets).map((val, i) => ( |
||||
<li key={i}> |
||||
<a href={'#' + val}> |
||||
{' '} |
||||
{val} ({targets[val].active} / {targets[val].total} active targets){' '} |
||||
</a> |
||||
</li> |
||||
))} |
||||
</ul> |
||||
<hr /> |
||||
{Object.keys(labels).map((val: any, i) => { |
||||
const value = labels[val]; |
||||
return <LabelsTable value={value} name={val} key={Object.keys(labels)[i]} />; |
||||
})} |
||||
</> |
||||
); |
||||
} |
||||
return <FontAwesomeIcon icon={faSpinner} spin />; |
||||
}; |
||||
|
||||
export default Services; |
||||
|
||||
@ -0,0 +1,33 @@ |
||||
import React from 'react'; |
||||
import { shallow } from 'enzyme'; |
||||
import { Button } from 'reactstrap'; |
||||
import { ToggleMoreLess } from './ToggleMoreLess'; |
||||
|
||||
describe('ToggleMoreLess', () => { |
||||
const showMoreValue = false; |
||||
const defaultProps = { |
||||
event: (): void => { |
||||
tggleBtn.setProps({ showMore: !showMoreValue }); |
||||
}, |
||||
showMore: showMoreValue, |
||||
}; |
||||
const tggleBtn = shallow(<ToggleMoreLess {...defaultProps} />); |
||||
|
||||
it('renders a show more btn at start', () => { |
||||
const btn = tggleBtn.find(Button); |
||||
expect(btn).toHaveLength(1); |
||||
expect(btn.prop('color')).toEqual('primary'); |
||||
expect(btn.prop('size')).toEqual('xs'); |
||||
expect(btn.render().text()).toEqual('show more'); |
||||
}); |
||||
|
||||
it('renders a show less btn if clicked', () => { |
||||
tggleBtn.find(Button).simulate('click'); |
||||
expect( |
||||
tggleBtn |
||||
.find(Button) |
||||
.render() |
||||
.text() |
||||
).toEqual('show less'); |
||||
}); |
||||
}); |
||||
@ -0,0 +1,28 @@ |
||||
import React, { FC } from 'react'; |
||||
import { Button } from 'reactstrap'; |
||||
|
||||
interface ToggleMoreLessProps { |
||||
event(): void; |
||||
showMore: boolean; |
||||
} |
||||
|
||||
export const ToggleMoreLess: FC<ToggleMoreLessProps> = ({ children, event, showMore }) => { |
||||
return ( |
||||
<h3> |
||||
{children} |
||||
<Button |
||||
size="xs" |
||||
onClick={event} |
||||
style={{ |
||||
padding: '0.3em 0.3em 0.25em 0.3em', |
||||
fontSize: '0.375em', |
||||
marginLeft: '1em', |
||||
verticalAlign: 'baseline', |
||||
}} |
||||
color="primary" |
||||
> |
||||
show {showMore ? 'less' : 'more'} |
||||
</Button> |
||||
</h3> |
||||
); |
||||
}; |
||||
Loading…
Reference in new issue