mirror of https://github.com/linkerd/linkerd2.git
Improve the top routes request form and code structure. (#1886)
Separates out the querying and table display of route data, so that this module can be easily placed in other places in the UI. Adds usability improvements to the routes query form at /routes: - displays CLI equivalent - adds dropdown with populated options for service / namespace As part of this work, made TapQueryCliCmd more generic, so it can work for other CLI commands besides tap/top.
This commit is contained in:
parent
e9aa9114e1
commit
7a2689ce4a
|
@ -0,0 +1,58 @@
|
|||
import {
|
||||
CardContent,
|
||||
Typography
|
||||
} from '@material-ui/core';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
const toCliParam = {
|
||||
"namespace": "--namespace",
|
||||
"toResource": "--to",
|
||||
"toNamespace": "--to-namespace",
|
||||
"method": "--method",
|
||||
"path": "--path",
|
||||
"scheme": "--scheme",
|
||||
"authority": "--authority",
|
||||
"maxRps": "--max-rps",
|
||||
"from": "--from",
|
||||
"from_namespace": "--from-namespace"
|
||||
};
|
||||
|
||||
/*
|
||||
prints a given linkerd api query in an equivalent CLI format, such that it
|
||||
could be pasted into a terminal
|
||||
*/
|
||||
export default class QueryToCliCmd extends React.Component {
|
||||
static propTypes = {
|
||||
cmdName: PropTypes.string.isRequired,
|
||||
displayOrder: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
query: PropTypes.shape({}).isRequired,
|
||||
resource: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
renderCliItem = (queryLabel, queryVal) => {
|
||||
return _.isEmpty(queryVal) ? null : ` ${queryLabel} ${queryVal}`;
|
||||
}
|
||||
|
||||
render = () => {
|
||||
let { cmdName, query, resource, displayOrder } = this.props;
|
||||
|
||||
return (
|
||||
_.isEmpty(resource) ? null :
|
||||
<CardContent>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
Current {_.startCase(cmdName)} query
|
||||
</Typography>
|
||||
|
||||
<code>
|
||||
linkerd {this.props.cmdName} {resource}
|
||||
{ _.map(displayOrder, item => {
|
||||
return !toCliParam[item] ? null : this.renderCliItem(toCliParam[item], query[item]);
|
||||
})}
|
||||
</code>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import QueryToCliCmd from './QueryToCliCmd.jsx';
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
|
||||
describe('QueryToCliCmd', () => {
|
||||
it('renders a query as a linkerd CLI command', () => {
|
||||
let query = {
|
||||
"resource": "deploy/controller",
|
||||
"namespace": "linkerd",
|
||||
"scheme": ""
|
||||
}
|
||||
let cliQueryDisplayOrder = [
|
||||
"namespace",
|
||||
"scheme"
|
||||
]
|
||||
|
||||
let component = mount(
|
||||
<QueryToCliCmd
|
||||
cmdName="routes"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
displayOrder={cliQueryDisplayOrder} />
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Routes query");
|
||||
expect(component).toIncludeText("linkerd routes deploy/controller --namespace linkerd");
|
||||
});
|
||||
|
||||
it('does not render flags for items that are not populated in the query', () => {
|
||||
let query = {
|
||||
"resource": "deploy/controller",
|
||||
"namespace": "linkerd",
|
||||
"scheme": "",
|
||||
"maxRps": "",
|
||||
"authority": "foo.bar:8080"
|
||||
}
|
||||
let cliQueryDisplayOrder = [
|
||||
"namespace",
|
||||
"scheme",
|
||||
"maxRps",
|
||||
"authority"
|
||||
]
|
||||
|
||||
let component = mount(
|
||||
<QueryToCliCmd
|
||||
cmdName="tap"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
displayOrder={cliQueryDisplayOrder} />
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Tap query");
|
||||
expect(component).toIncludeText("linkerd tap deploy/controller --namespace linkerd --authority foo.bar:8080");
|
||||
});
|
||||
|
||||
it('displays the flags in the specified displayOrder', () => {
|
||||
let query = {
|
||||
"resource_name": "deploy/controller",
|
||||
"namespace": "linkerd",
|
||||
"scheme": "HTTPS",
|
||||
"maxRps": "",
|
||||
"toResource": "deploy/prometheus",
|
||||
"authority": "foo.bar:8080"
|
||||
}
|
||||
let cliQueryDisplayOrder = [
|
||||
"namespace",
|
||||
"toResource",
|
||||
"scheme",
|
||||
"maxRps",
|
||||
"authority"
|
||||
]
|
||||
|
||||
let component = mount(
|
||||
<QueryToCliCmd
|
||||
cmdName="tap"
|
||||
query={query}
|
||||
resource={query.resource_name}
|
||||
displayOrder={cliQueryDisplayOrder} />
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Tap query");
|
||||
expect(component).toIncludeText("linkerd tap deploy/controller --namespace linkerd --to deploy/prometheus --scheme HTTPS --authority foo.bar:8080");
|
||||
});
|
||||
|
||||
it("doesn't render commands for which a flag is not specified", () => {
|
||||
let query = {
|
||||
"resource": "deploy/controller",
|
||||
"namespace": "linkerd",
|
||||
"scheme": "HTTPS"
|
||||
}
|
||||
let cliQueryDisplayOrder = [
|
||||
"namespace",
|
||||
"theLimitDoesNotExist",
|
||||
"scheme"
|
||||
]
|
||||
|
||||
let component = mount(
|
||||
<QueryToCliCmd
|
||||
cmdName="routes"
|
||||
query={query}
|
||||
resource={query.resource}
|
||||
displayOrder={cliQueryDisplayOrder} />
|
||||
);
|
||||
|
||||
expect(component).toIncludeText("Current Routes query");
|
||||
expect(component).toIncludeText("linkerd routes deploy/controller --namespace linkerd --scheme HTTPS");
|
||||
});
|
||||
});
|
|
@ -1,76 +0,0 @@
|
|||
import {
|
||||
CardContent,
|
||||
Typography
|
||||
} from '@material-ui/core';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { tapQueryPropType } from './util/TapUtils.jsx';
|
||||
|
||||
/*
|
||||
prints a given tap query in an equivalent CLI format, such that it
|
||||
could be pasted into a terminal
|
||||
*/
|
||||
export default class TapQueryCliCmd extends React.Component {
|
||||
static propTypes = {
|
||||
cmdName: PropTypes.string.isRequired,
|
||||
query: tapQueryPropType
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
query: {
|
||||
resource: "",
|
||||
namespace: "",
|
||||
toResource: "",
|
||||
toNamespace: "",
|
||||
method: "",
|
||||
path: "",
|
||||
scheme: "",
|
||||
authority: "",
|
||||
maxRps: ""
|
||||
}
|
||||
}
|
||||
|
||||
renderCliItem = (queryLabel, queryVal) => {
|
||||
return _.isEmpty(queryVal) ? null : ` ${queryLabel} ${queryVal}`;
|
||||
}
|
||||
|
||||
render = () => {
|
||||
let {
|
||||
resource,
|
||||
namespace,
|
||||
toResource,
|
||||
toNamespace,
|
||||
method,
|
||||
path,
|
||||
scheme,
|
||||
authority,
|
||||
maxRps
|
||||
} = this.props.query;
|
||||
|
||||
return (
|
||||
_.isEmpty(resource) ? null :
|
||||
<CardContent className="tap-query">
|
||||
<Typography variant="caption" gutterBottom>
|
||||
Current {_.startCase(this.props.cmdName)} query
|
||||
</Typography>
|
||||
|
||||
<code>
|
||||
linkerd {this.props.cmdName} {resource}
|
||||
{ resource.indexOf("namespace") === 0 ? null : this.renderCliItem("--namespace", namespace) }
|
||||
{ this.renderCliItem("--to", toResource) }
|
||||
{
|
||||
_.isEmpty(toResource) || toResource.indexOf("namespace") === 0 ? null :
|
||||
this.renderCliItem("--to-namespace", toNamespace)
|
||||
}
|
||||
{ this.renderCliItem("--method", method) }
|
||||
{ this.renderCliItem("--scheme", scheme) }
|
||||
{ this.renderCliItem("--authority", authority) }
|
||||
{ this.renderCliItem("--path", path) }
|
||||
{ this.renderCliItem("--max-rps", maxRps) }
|
||||
</code>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,8 +25,8 @@ import {
|
|||
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import PropTypes from 'prop-types';
|
||||
import QueryToCliCmd from './QueryToCliCmd.jsx';
|
||||
import React from 'react';
|
||||
import TapQueryCliCmd from './TapQueryCliCmd.jsx';
|
||||
import _ from 'lodash';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
|
@ -396,7 +396,16 @@ class TapQueryForm extends React.Component {
|
|||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
let cliQueryDisplayOrder = _.compact([
|
||||
this.state.query.resource.indexOf("namespace") === 0 ? null : "namespace",
|
||||
"toResource",
|
||||
this.state.query.toResource.indexOf("namespace") === 0 ? null : "toNamespace",
|
||||
"method",
|
||||
"path",
|
||||
"scheme",
|
||||
"authority",
|
||||
"maxRps"
|
||||
]);
|
||||
return (
|
||||
<Card className={classes.card}>
|
||||
<CardContent>
|
||||
|
@ -423,9 +432,9 @@ class TapQueryForm extends React.Component {
|
|||
</Grid>
|
||||
</CardContent>
|
||||
|
||||
<TapQueryCliCmd cmdName="tap" query={this.state.query} />
|
||||
<QueryToCliCmd cmdName="tap" query={this.state.query} resource={this.state.query.resource} displayOrder={cliQueryDisplayOrder} />
|
||||
|
||||
{ !this.props.enableAdvancedForm ? null : this.renderAdvancedTapForm() }
|
||||
{ !this.props.enableAdvancedForm ? null : this.renderAdvancedTapForm() }
|
||||
|
||||
</Card>
|
||||
);
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import Button from '@material-ui/core/Button';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import ErrorBanner from './ErrorBanner.jsx';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormHelperText from '@material-ui/core/FormHelperText';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import PropTypes from 'prop-types';
|
||||
import QueryToCliCmd from './QueryToCliCmd.jsx';
|
||||
import React from 'react';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import TopRoutesTable from './TopRoutesTable.jsx';
|
||||
import TopRoutesModule from './TopRoutesModule.jsx';
|
||||
import _ from 'lodash';
|
||||
import { processTopRoutesResults } from './util/MetricUtils.jsx';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
||||
class TopRoutes extends React.Component {
|
||||
|
@ -29,46 +36,38 @@ class TopRoutes extends React.Component {
|
|||
from_namespace: ''
|
||||
},
|
||||
error: null,
|
||||
metrics: [],
|
||||
services: [],
|
||||
namespaces: [],
|
||||
pollingInterval: 5000,
|
||||
pendingRequests: false,
|
||||
pollingInProgress: false
|
||||
requestInProgress: false
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.startServerPolling();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.stopServerPolling();
|
||||
}
|
||||
|
||||
getQueryParams() {
|
||||
// TODO: form validation
|
||||
return _.compact(_.map(this.state.query, (val, name) => {
|
||||
if (_.isEmpty(val)) {
|
||||
return null;
|
||||
} else {
|
||||
return `${name}=${val}`;
|
||||
}
|
||||
})).join("&");
|
||||
}
|
||||
|
||||
loadFromServer = () => {
|
||||
if (this.state.pendingRequests) {
|
||||
return; // don't make more requests if the ones we sent haven't completed
|
||||
}
|
||||
this.setState({ pendingRequests: true });
|
||||
|
||||
let queryParams = this.getQueryParams();
|
||||
|
||||
this.api.setCurrentRequests([
|
||||
this.api.fetchMetrics(`/api/routes?${queryParams}`)
|
||||
]);
|
||||
this.api.setCurrentRequests([this.api.fetchServices()]);
|
||||
|
||||
this.serverPromise = Promise.all(this.api.getCurrentPromises())
|
||||
.then(([routeStats]) => {
|
||||
let metrics = processTopRoutesResults(_.get(routeStats, 'routes.rows', []));
|
||||
.then(([svcList]) => {
|
||||
let services = _.get(svcList, 'services', []);
|
||||
let namespaces = _.uniq(_.map(services, 'namespace'));
|
||||
|
||||
this.setState({
|
||||
metrics,
|
||||
services,
|
||||
namespaces,
|
||||
pendingRequests: false,
|
||||
error: null
|
||||
});
|
||||
|
@ -88,9 +87,6 @@ class TopRoutes extends React.Component {
|
|||
}
|
||||
|
||||
startServerPolling = () => {
|
||||
this.setState({
|
||||
pollingInProgress: true
|
||||
});
|
||||
this.loadFromServer();
|
||||
this.timerId = window.setInterval(this.loadFromServer, this.state.pollingInterval);
|
||||
}
|
||||
|
@ -98,8 +94,11 @@ class TopRoutes extends React.Component {
|
|||
stopServerPolling = () => {
|
||||
window.clearInterval(this.timerId);
|
||||
this.api.cancelCurrentRequests();
|
||||
}
|
||||
|
||||
handleBtnClick = inProgress => () => {
|
||||
this.setState({
|
||||
pollingInProgress: false
|
||||
requestInProgress: inProgress
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -113,49 +112,102 @@ class TopRoutes extends React.Component {
|
|||
|
||||
renderRoutesQueryForm = () => {
|
||||
return (
|
||||
<Grid container direction="column">
|
||||
<Grid item container spacing={8} alignItems="center">
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("Service", "resource_name", "Name of the configured service") }
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("Namespace", "namespace", "Namespace of the configured service") }
|
||||
</Grid>
|
||||
</Grid>
|
||||
<CardContent>
|
||||
<Grid container direction="column">
|
||||
<Grid item container spacing={8} alignItems="center">
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderNamespaceDropdown("Namespace", "namespace", "Namespace of the configured service") }
|
||||
</Grid>
|
||||
|
||||
<Grid item container spacing={8} alignItems="center">
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("From", "from_name", "Resource name") }
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderServiceDropdown() }
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("From type", "from_type", "Resource type") }
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("From namespace", "from_namespace", "Resource namespace") }
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item container spacing={8} alignItems="center">
|
||||
<Grid item>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
disabled={this.state.pollingInProgress}
|
||||
onClick={this.startServerPolling}>
|
||||
<Grid item container spacing={8} alignItems="center">
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("From", "from_name", "Resource name") }
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("From type", "from_type", "Resource type") }
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
{ this.renderTextInput("From namespace", "from_namespace", "Resource namespace") }
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid item container spacing={8} alignItems="center">
|
||||
<Grid item>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
disabled={this.state.requestInProgress}
|
||||
onClick={this.handleBtnClick(true)}>
|
||||
Start
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
color="default"
|
||||
variant="outlined"
|
||||
disabled={!this.state.pollingInProgress}
|
||||
onClick={this.stopServerPolling}>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
color="default"
|
||||
variant="outlined"
|
||||
disabled={!this.state.requestInProgress}
|
||||
onClick={this.handleBtnClick(false)}>
|
||||
Stop
|
||||
</Button>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
);
|
||||
}
|
||||
|
||||
renderNamespaceDropdown = (title, key, helperText) => {
|
||||
return (
|
||||
<FormControl>
|
||||
<InputLabel htmlFor={`${key}-dropdown`}>{title}</InputLabel>
|
||||
<Select
|
||||
value={this.state.query[key]}
|
||||
onChange={this.handleFormEvent(key)}
|
||||
inputProps={{
|
||||
name: key,
|
||||
id: `${key}-dropdown`,
|
||||
}}
|
||||
name={key}>
|
||||
{
|
||||
_.map(_.sortBy(this.state.namespaces), ns =>
|
||||
<MenuItem key={`namespace-${ns}`} value={ns}>{ns}</MenuItem>)
|
||||
}
|
||||
</Select>
|
||||
<FormHelperText>{helperText}</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
renderServiceDropdown = () => {
|
||||
let key = "resource_name";
|
||||
let services = _.chain(this.state.services)
|
||||
.filter(['namespace', this.state.query.namespace])
|
||||
.map('name').sortBy().value();
|
||||
|
||||
return (
|
||||
<FormControl>
|
||||
<InputLabel htmlFor={`${key}-dropdown`}>Service</InputLabel>
|
||||
<Select
|
||||
value={this.state.query[key]}
|
||||
onChange={this.handleFormEvent(key)}
|
||||
disabled={_.isEmpty(this.state.query.namespace)}
|
||||
autoWidth
|
||||
inputProps={{
|
||||
name: key,
|
||||
id: `${key}-dropdown`,
|
||||
}}
|
||||
name={key}>
|
||||
{
|
||||
_.map(services, svc => <MenuItem key={`service-${svc}`} value={svc}>{svc}</MenuItem>)
|
||||
}
|
||||
</Select>
|
||||
<FormHelperText>The configured service</FormHelperText>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -172,14 +224,31 @@ class TopRoutes extends React.Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let cliQueryDisplayOrder = [
|
||||
"namespace",
|
||||
"from",
|
||||
"from_namespace"
|
||||
];
|
||||
let query = this.state.query;
|
||||
let from = '';
|
||||
if (_.isEmpty(query.from_type)) {
|
||||
from = query.from_name;
|
||||
} else {
|
||||
from = `${query.from_type}${_.isEmpty(query.from_name) ? "" : "/"}${query.from_name}`;
|
||||
}
|
||||
query.from = from;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
!this.state.error ? null :
|
||||
<ErrorBanner message={this.state.error} onHideMessage={() => this.setState({ error: null })} />
|
||||
}
|
||||
{this.renderRoutesQueryForm()}
|
||||
<TopRoutesTable rows={this.state.metrics} />
|
||||
<Card>
|
||||
{ this.renderRoutesQueryForm() }
|
||||
<QueryToCliCmd cmdName="routes" query={query} resource={this.state.query.resource_name} displayOrder={cliQueryDisplayOrder} />
|
||||
</Card>
|
||||
{ !this.state.requestInProgress ? null : <TopRoutesModule query={this.state.query} /> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import ErrorBanner from './ErrorBanner.jsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import TopRoutesTable from './TopRoutesTable.jsx';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import _ from 'lodash';
|
||||
import { apiErrorPropType } from './util/ApiHelpers.jsx';
|
||||
import { processTopRoutesResults } from './util/MetricUtils.jsx';
|
||||
import withREST from './util/withREST.jsx';
|
||||
|
||||
class TopRoutesBase extends React.Component {
|
||||
static defaultProps = {
|
||||
error: null
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
|
||||
error: apiErrorPropType,
|
||||
query: PropTypes.shape({}).isRequired,
|
||||
}
|
||||
|
||||
banner = () => {
|
||||
const {error} = this.props;
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
return <ErrorBanner message={error} />;
|
||||
}
|
||||
|
||||
configureProfileMsg() {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography>
|
||||
No traffic found. Does the service have a service profile? You can create one with the `linkerd profile` command.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {data} = this.props;
|
||||
let metrics = processTopRoutesResults(_.get(data, '[0].routes.rows', []));
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.banner()}
|
||||
{_.isEmpty(metrics) ? this.configureProfileMsg() : null}
|
||||
<TopRoutesTable rows={metrics} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withREST(
|
||||
TopRoutesBase,
|
||||
({api, query}) => {
|
||||
let queryParams = new URLSearchParams(query).toString();
|
||||
return [api.fetchMetrics(`/api/routes?${queryParams}`)];
|
||||
}
|
||||
);
|
|
@ -55,6 +55,7 @@ export const apiErrorPropType = PropTypes.shape({
|
|||
const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
||||
let metricsWindow = defaultMetricsWindow;
|
||||
const podsPath = `/api/pods`;
|
||||
const servicesPath = `/api/services`;
|
||||
|
||||
const validMetricsWindows = {
|
||||
"10s": "10 minutes",
|
||||
|
@ -89,6 +90,13 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
|||
return apiFetch(podsPath);
|
||||
};
|
||||
|
||||
const fetchServices = namespace => {
|
||||
if (!_.isNil(namespace)) {
|
||||
return apiFetch(servicesPath + "?namespace=" + namespace);
|
||||
}
|
||||
return apiFetch(servicesPath);
|
||||
};
|
||||
|
||||
const getMetricsWindow = () => metricsWindow;
|
||||
const getMetricsWindowDisplayText = () => validMetricsWindows[metricsWindow];
|
||||
|
||||
|
@ -187,6 +195,7 @@ const ApiHelpers = (pathPrefix, defaultMetricsWindow = '1m') => {
|
|||
fetch: apiFetch,
|
||||
fetchMetrics,
|
||||
fetchPods,
|
||||
fetchServices,
|
||||
getMetricsWindow,
|
||||
setMetricsWindow,
|
||||
getValidMetricsWindows: () => _.keys(validMetricsWindows),
|
||||
|
|
Loading…
Reference in New Issue