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>
This commit is contained in:
Harkishen Singh 2019-12-31 23:11:50 +05:30 committed by Julius Volz
parent ae93bae88f
commit edf8f135bc
8 changed files with 251 additions and 37 deletions

View file

@ -208,6 +208,11 @@ button.execute-btn {
margin-bottom: 20px;
}
.target-head {
font-weight: 700;
font-size: large;
}
.status-badges {
display: flex;
justify-content: space-between;

View file

@ -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}
</>
);
};

View file

@ -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;

View file

@ -20,10 +20,6 @@ describe('ScrapePoolPanel', () => {
});
describe('Header', () => {
it('renders an h3', () => {
expect(scrapePoolPanel.find('h3')).toHaveLength(1);
});
it('renders an anchor with up count and danger color if upCount < targetsCount', () => {
const anchor = scrapePoolPanel.find('a');
expect(anchor).toHaveLength(1);
@ -47,14 +43,6 @@ describe('ScrapePoolPanel', () => {
expect(anchor.prop('className')).toEqual('normal');
});
it('renders a show less btn if expanded', () => {
const btn = scrapePoolPanel.find(Button);
expect(btn).toHaveLength(1);
expect(btn.prop('color')).toEqual('primary');
expect(btn.prop('size')).toEqual('xs');
expect(btn.render().text()).toEqual('show less');
});
it('renders a show more btn if collapsed', () => {
const props = {
scrapePool: 'prometheus',
@ -67,7 +55,6 @@ describe('ScrapePoolPanel', () => {
const btn = scrapePoolPanel.find(Button);
btn.simulate('click');
expect(btn.render().text()).toEqual('show more');
const collapse = scrapePoolPanel.find(Collapse);
expect(collapse.prop('isOpen')).toBe(false);
});

View file

@ -1,6 +1,6 @@
import React, { FC } from 'react';
import { ScrapePool, getColor } from './target';
import { Button, Collapse, Table, Badge } from 'reactstrap';
import { Collapse, Table, Badge } from 'reactstrap';
import styles from './ScrapePoolPanel.module.css';
import { Target } from './target';
import EndpointLink from './EndpointLink';
@ -8,6 +8,7 @@ import TargetLabels from './TargetLabels';
import { formatRelative, humanizeDuration } from '../../utils/timeFormat';
import { now } from 'moment';
import { useLocalStorage } from '../../hooks/useLocalStorage';
import { ToggleMoreLess } from './ToggleMoreLess';
interface PanelProps {
scrapePool: string;
@ -24,27 +25,14 @@ const ScrapePoolPanel: FC<PanelProps> = ({ scrapePool, targetGroup }) => {
href: `#${id}`,
id,
};
const btnProps = {
children: `show ${expanded ? 'less' : 'more'}`,
color: 'primary',
onClick: (): void => setOptions({ expanded: !expanded }),
size: 'xs',
style: {
padding: '0.3em 0.3em 0.25em 0.3em',
fontSize: '0.375em',
marginLeft: '1em',
verticalAlign: 'baseline',
},
};
return (
<div className={styles.container}>
<h3>
<ToggleMoreLess event={(): void => setOptions({ expanded: !expanded })} showMore={expanded}>
<a className={styles[modifier]} {...anchorProps}>
{`${scrapePool} (${targetGroup.upCount}/${targetGroup.targets.length} up)`}
</a>
<Button {...btnProps} />
</h3>
</ToggleMoreLess>
<Collapse isOpen={expanded}>
<Table className={styles.table} size="sm" bordered hover striped>
<thead>

View file

@ -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');
});
});

View file

@ -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>
);
};

View file

@ -13,6 +13,10 @@ export interface Target {
health: string;
}
export interface DroppedTarget {
discoveredLabels: Labels;
}
export interface ScrapePool {
upCount: number;
targets: Target[];