mirror of https://github.com/linkerd/linkerd2.git
Add display chips to the resource detail page to provide more detail (#2063)
* Simplify syntax for state variable access * Add a warning chip if a resource isn't receiving traffic Also add a meshed chip to the resource detail pages
This commit is contained in:
parent
53f8bb5a15
commit
2adcdfbca8
|
@ -167,7 +167,7 @@ class NamespaceLanding extends React.Component {
|
|||
let hr = (
|
||||
<Grid container justify="space-between" alignItems="center">
|
||||
<Grid item><Typography variant="subtitle1">{ns.name}</Typography></Grid>
|
||||
{!ns.added ? null : <Grid item><SimpleChip /></Grid> }
|
||||
{!ns.added ? null : <Grid item><SimpleChip label="meshed" type="good" /></Grid> }
|
||||
</Grid>
|
||||
);
|
||||
return {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import 'whatwg-fetch';
|
||||
|
||||
import { emptyMetric, processSingleResourceRollup } from './util/MetricUtils.jsx';
|
||||
import { resourceTypeToCamelCase, singularResource } from './util/Utils.js';
|
||||
|
||||
import AddResources from './AddResources.jsx';
|
||||
import ErrorBanner from './ErrorBanner.jsx';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import MetricsTable from './MetricsTable.jsx';
|
||||
import Octopus from './Octopus.jsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import SimpleChip from './util/Chip.jsx';
|
||||
import Spinner from './util/Spinner.jsx';
|
||||
import TopRoutesTabs from './TopRoutesTabs.jsx';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
@ -21,6 +21,9 @@ import _reduce from 'lodash/reduce';
|
|||
import { processNeighborData } from './util/TapUtils.jsx';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
||||
// if there has been no traffic for some time, show a warning
|
||||
const showNoTrafficMsgDelayMs = 6000;
|
||||
|
||||
const getResourceFromUrl = (match, pathPrefix) => {
|
||||
let resource = {
|
||||
namespace: match.params.namespace
|
||||
|
@ -64,12 +67,13 @@ export class ResourceDetailBase extends React.Component {
|
|||
resourceName: resource.name,
|
||||
resourceType: resource.type,
|
||||
resource,
|
||||
lastMetricReceivedTime: Date.now(),
|
||||
pollingInterval: 2000,
|
||||
resourceMetrics: [],
|
||||
podMetrics: [], // metrics for all pods whose owner is this resource
|
||||
neighborMetrics: {
|
||||
upstream: {},
|
||||
downstream: {}
|
||||
upstream: [],
|
||||
downstream: []
|
||||
},
|
||||
unmeshedSources: {},
|
||||
resourceIsMeshed: true,
|
||||
|
@ -152,6 +156,11 @@ export class ResourceDetailBase extends React.Component {
|
|||
resourceIsMeshed = _get(this.state.resourceMetrics, '[0].pods.meshedPods') > 0;
|
||||
}
|
||||
|
||||
let lastMetricReceivedTime = this.state.lastMetricReceivedTime;
|
||||
if (!_isEmpty(resourceMetrics[0]) && resourceMetrics[0].totalRequests !== 0) {
|
||||
lastMetricReceivedTime = Date.now();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
resourceMetrics,
|
||||
resourceIsMeshed,
|
||||
|
@ -160,6 +169,7 @@ export class ResourceDetailBase extends React.Component {
|
|||
upstream: upstreamMetrics,
|
||||
downstream: downstreamMetrics
|
||||
},
|
||||
lastMetricReceivedTime,
|
||||
loaded: true,
|
||||
pendingRequests: false,
|
||||
error: null,
|
||||
|
@ -200,14 +210,24 @@ export class ResourceDetailBase extends React.Component {
|
|||
return <Spinner />;
|
||||
}
|
||||
|
||||
let { resourceName, resourceType, namespace } = this.state;
|
||||
let {
|
||||
resourceName,
|
||||
resourceType,
|
||||
namespace,
|
||||
resourceMetrics,
|
||||
unmeshedSources,
|
||||
resourceIsMeshed,
|
||||
neighborMetrics,
|
||||
lastMetricReceivedTime
|
||||
} = this.state;
|
||||
|
||||
let query = {
|
||||
resourceName,
|
||||
resourceType,
|
||||
namespace
|
||||
};
|
||||
|
||||
let unmeshed = _filter(this.state.unmeshedSources, d => d.type === this.state.resourceType)
|
||||
let unmeshed = _filter(unmeshedSources, d => d.type === resourceType)
|
||||
.map(d => _merge({}, emptyMetric, d, {
|
||||
unmeshed: true,
|
||||
pods: {
|
||||
|
@ -216,31 +236,47 @@ export class ResourceDetailBase extends React.Component {
|
|||
}
|
||||
}));
|
||||
|
||||
let upstreams = this.state.neighborMetrics.upstream.concat(unmeshed);
|
||||
let upstreams = neighborMetrics.upstream.concat(unmeshed);
|
||||
|
||||
let showNoTrafficMsg = resourceIsMeshed && (Date.now() - lastMetricReceivedTime > showNoTrafficMsgDelayMs);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Grid container justify="space-between" alignItems="center">
|
||||
<Grid item><Typography variant="h5">{resourceType}/{resourceName}</Typography></Grid>
|
||||
<Grid item>
|
||||
<Grid container spacing={8}>
|
||||
{ showNoTrafficMsg ? <Grid item><SimpleChip label="no traffic" type="warning" /></Grid> : null }
|
||||
<Grid item>
|
||||
{ resourceIsMeshed ?
|
||||
<SimpleChip label="meshed" type="good" /> :
|
||||
<SimpleChip label="unmeshed" type="bad" />
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{
|
||||
this.state.resourceIsMeshed ? null :
|
||||
resourceIsMeshed ? null :
|
||||
<React.Fragment>
|
||||
<AddResources
|
||||
resourceName={this.state.resourceName}
|
||||
resourceType={this.state.resourceType} />
|
||||
resourceName={resourceName}
|
||||
resourceType={resourceType} />
|
||||
</React.Fragment>
|
||||
}
|
||||
|
||||
<Octopus
|
||||
resource={this.state.resourceMetrics[0]}
|
||||
neighbors={this.state.neighborMetrics}
|
||||
unmeshedSources={Object.values(this.state.unmeshedSources)}
|
||||
resource={resourceMetrics[0]}
|
||||
neighbors={neighborMetrics}
|
||||
unmeshedSources={Object.values(unmeshedSources)}
|
||||
api={this.api} />
|
||||
|
||||
<TopRoutesTabs
|
||||
query={query}
|
||||
pathPrefix={this.props.pathPrefix}
|
||||
updateNeighborsFromTapData={this.updateNeighborsFromTapData}
|
||||
disableTop={!this.state.resourceIsMeshed} />
|
||||
|
||||
disableTop={!resourceIsMeshed} />
|
||||
|
||||
{ _isEmpty(upstreams) ? null : (
|
||||
<React.Fragment>
|
||||
|
|
|
@ -3,25 +3,36 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = () => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
const styles = theme => ({
|
||||
good: {
|
||||
color: theme.status.dark.good,
|
||||
border: '1px solid ' + theme.status.dark.good
|
||||
},
|
||||
warning: {
|
||||
color: theme.status.dark.warning,
|
||||
border: '1px solid ' + theme.status.dark.warning,
|
||||
},
|
||||
bad: {
|
||||
color: theme.status.dark.danger,
|
||||
border: '1px solid ' + theme.status.dark.danger,
|
||||
}
|
||||
});
|
||||
|
||||
function SimpleChip(props) {
|
||||
const { classes } = props;
|
||||
const { classes, label, type } = props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Chip className={classes.chip} color="primary" label="meshed" variant="outlined" />
|
||||
</div>
|
||||
<Chip
|
||||
className={classes[type]}
|
||||
label={label}
|
||||
variant="outlined" />
|
||||
);
|
||||
}
|
||||
|
||||
SimpleChip.propTypes = {
|
||||
classes: PropTypes.shape({}).isRequired
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles)(SimpleChip);
|
||||
export default withStyles(styles, { withTheme: true })(SimpleChip);
|
||||
|
|
Loading…
Reference in New Issue