mirror of https://github.com/linkerd/linkerd2.git
Start tweaking the look and feel of the Octopus graph (#1501)
Do a little more work to get the octopus graph closer to the mocks. This version gives you a slightly better navigational sense of where you are in the app, and gives you a clearer view of the neighbouring stats
This commit is contained in:
parent
cc98b5e784
commit
062d35db7d
|
@ -1,40 +1,51 @@
|
||||||
@import 'styles.css';
|
@import 'styles.css';
|
||||||
|
|
||||||
.octopus-container {
|
.octopus-container {
|
||||||
padding: 80px;
|
padding: calc(var(--base-width) * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.octopus-graph {
|
.octopus-graph {
|
||||||
background-color: #ffffff;
|
& .octopus-body {
|
||||||
height: 100%;
|
background-color: white;
|
||||||
width: 600px;
|
margin-bottom: calc(var(--base-width) * 3);
|
||||||
margin-left: auto;
|
box-shadow: 2px 2px 2px var(--neutralgrey);
|
||||||
margin-right: auto;
|
|
||||||
padding: 24px;
|
|
||||||
box-shadow: 2px 2px 2px var(--neutralgrey);
|
|
||||||
|
|
||||||
& .octopus-title, & .octopus-metric {
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& .octopus-upstreams .neighbor, & .octopus-downstreams .neighbor {
|
& .octopus-col {
|
||||||
clear: both;
|
text-align: center;
|
||||||
|
}
|
||||||
|
& .resource-col {
|
||||||
|
background-color: #FAFAFA;
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
& .status-dot {
|
& .octopus-title {
|
||||||
margin: 4px 4px 0 4px;
|
padding: 8px 0;
|
||||||
|
|
||||||
|
&.main {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: var(--font-weight-extra-bold);
|
||||||
|
}
|
||||||
|
&.neighbor {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .octopus-upstreams .neighbor > div div {
|
& .octopus-metric {
|
||||||
float: left;
|
line-height: 32px;
|
||||||
}
|
|
||||||
|
|
||||||
& .octopus-downstreams .neighbor > div div {
|
&.status-good {
|
||||||
float: right;
|
color: var(--green);
|
||||||
|
}
|
||||||
|
&.status-poor {
|
||||||
|
color: var(--siennared);
|
||||||
|
}
|
||||||
|
&.status-neutral {
|
||||||
|
color: #E0E0E0;
|
||||||
|
}
|
||||||
|
&.status-ok {
|
||||||
|
color: #FF8C00;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.octopus-metric-lg {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,95 +1,89 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Col, Popover, Row } from 'antd';
|
import { Col, Icon, Row } from 'antd';
|
||||||
import { metricToFormatter, toShortResourceName } from './util/Utils.js';
|
import { metricToFormatter, toShortResourceName } from './util/Utils.js';
|
||||||
import './../../css/octopus.css';
|
import './../../css/octopus.css';
|
||||||
|
|
||||||
const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`;
|
const displayName = resource => `${toShortResourceName(resource.type)}/${resource.name}`;
|
||||||
|
|
||||||
const getDotClassification = sr => {
|
const getSrClassification = sr => {
|
||||||
if (sr < 0.9) {
|
if (sr < 0.9) {
|
||||||
return "status-dot-poor";
|
return "status-poor";
|
||||||
} else if (sr < 0.95) {
|
} else if (sr < 0.95) {
|
||||||
return "status-dot-ok";
|
return "status-ok";
|
||||||
} else {return "status-dot-good";}
|
} else {return "status-good";}
|
||||||
};
|
};
|
||||||
|
|
||||||
const Neighbor = ({neighbor, direction}) => {
|
const Metric = ({title, value, className}) => {
|
||||||
return (
|
return (
|
||||||
<div className="neighbor">
|
<Row type="flex" justify="center" className={`octopus-metric ${className}`}>
|
||||||
<Popover
|
|
||||||
title={displayName(neighbor)}
|
|
||||||
content={<MetricSummaryRow resource={neighbor} metricClass="metric-sm" />}
|
|
||||||
placement={direction ==="in" ? "left" : "right"}>
|
|
||||||
<div className="neighbor-row">
|
|
||||||
<div>{direction === "in" ? "<" : ">"}</div>
|
|
||||||
<div className={`status-dot ${getDotClassification(neighbor.successRate)}`} />
|
|
||||||
<div>{displayName(neighbor)}</div>
|
|
||||||
</div>
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Neighbor.propTypes = {
|
|
||||||
direction: PropTypes.string.isRequired,
|
|
||||||
neighbor: PropTypes.shape({}).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
const Metric = ({title, value, metricClass}) => {
|
|
||||||
return (
|
|
||||||
<Row type="flex" justify="center" className={`octopus-${metricClass}`}>
|
|
||||||
<Col span={12} className="octopus-metric-title"><div>{title}</div></Col>
|
<Col span={12} className="octopus-metric-title"><div>{title}</div></Col>
|
||||||
<Col span={12} className="octopus-metric-value"><div>{value}</div></Col>
|
<Col span={12} className="octopus-metric-value"><div>{value}</div></Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Metric.defaultProps = { className: "" };
|
||||||
Metric.propTypes = {
|
Metric.propTypes = {
|
||||||
metricClass: PropTypes.string.isRequired,
|
className: PropTypes.string,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
value: PropTypes.string.isRequired
|
value: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const MetricSummaryRow = ({resource, metricClass}) => {
|
const ArrowCol = ({showArrow}) => <Col span={2} className="octopus-col">{!showArrow ? " " : <Icon type="arrow-right" />}</Col>;
|
||||||
return (
|
ArrowCol.propTypes = PropTypes.bool.isRequired;
|
||||||
<React.Fragment>
|
|
||||||
<Metric title="Success Rate" value={metricToFormatter["SUCCESS_RATE"](resource.successRate)} metricClass={metricClass} />
|
|
||||||
<Metric title="Request Rate" value={metricToFormatter["REQUEST_RATE"](resource.requestRate)} metricClass={metricClass} />
|
|
||||||
<Metric title="P99 Latency" value={metricToFormatter["LATENCY"](_.get(resource, "latency.P99"))} metricClass={metricClass} />
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
MetricSummaryRow.propTypes = {
|
|
||||||
metricClass: PropTypes.string.isRequired,
|
|
||||||
resource: PropTypes.shape({}).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class Octopus extends React.Component {
|
export default class Octopus extends React.Component {
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
metrics: {},
|
neighbors: {},
|
||||||
neighbors: {}
|
resource: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
metrics: PropTypes.shape({}),
|
|
||||||
neighbors: PropTypes.shape({}),
|
neighbors: PropTypes.shape({}),
|
||||||
resource: PropTypes.shape({}).isRequired
|
resource: PropTypes.shape({})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResourceSummary(resource, cn) {
|
||||||
|
return (
|
||||||
|
<div key={resource.name} className={`octopus-body ${cn}`}>
|
||||||
|
<div className={`octopus-title ${cn} ${getSrClassification(resource.successRate)}`}>
|
||||||
|
<this.props.api.PrefixedLink to={`/namespaces/${resource.namespace}/${resource.type}s/${resource.name}`}>
|
||||||
|
{displayName(resource)}
|
||||||
|
</this.props.api.PrefixedLink>
|
||||||
|
</div>
|
||||||
|
<Metric
|
||||||
|
title="SR"
|
||||||
|
className={`${getSrClassification(resource.successRate)}`}
|
||||||
|
value={metricToFormatter["SUCCESS_RATE"](resource.successRate)} />
|
||||||
|
<Metric title="RPS" value={metricToFormatter["REQUEST_RATE"](resource.requestRate)} />
|
||||||
|
<Metric title="P99" value={metricToFormatter["LATENCY"](_.get(resource, "latency.P99"))} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { resource, metrics, neighbors } = this.props;
|
let { resource, neighbors } = this.props;
|
||||||
|
let hasUpstreams = _.size(neighbors.upstream) > 0;
|
||||||
|
let hasDownstreams = _.size(neighbors.downstream) > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="octopus-container">
|
<div className="octopus-container">
|
||||||
<div className="octopus-graph">
|
<div className="octopus-graph">
|
||||||
<h1 className="octopus-title">{displayName(resource)}</h1>
|
<Row type="flex" justify="center" gutter={32} align="middle">
|
||||||
<MetricSummaryRow resource={metrics} metricClass="metric-lg" />
|
<Col span={6} className={`octopus-col ${hasUpstreams ? "resource-col" : ""}`}>
|
||||||
<hr />
|
{_.map(neighbors.upstream, n => this.renderResourceSummary(n, "neighbor"))}
|
||||||
<Row type="flex" justify="center">
|
|
||||||
<Col span={12} className="octopus-upstreams">
|
|
||||||
{_.map(neighbors.upstream, n => <Neighbor neighbor={n} direction="in" key={n.namespace + "-" + n.name} />)}
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12} className="octopus-downstreams">
|
|
||||||
{_.map(neighbors.downstream, n => <Neighbor neighbor={n} direction="out" key={n.namespace + "-" + n.name} />)}
|
<ArrowCol showArrow={hasUpstreams} />
|
||||||
|
|
||||||
|
<Col span={8} className="octopus-col resource-col">
|
||||||
|
{this.renderResourceSummary(resource, "main")}
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<ArrowCol showArrow={hasDownstreams} />
|
||||||
|
|
||||||
|
<Col span={6} className={`octopus-col ${hasDownstreams ? "resource-col" : ""}`}>
|
||||||
|
{_.map(neighbors.downstream, n => this.renderResourceSummary(n, "neighbor"))}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -54,6 +54,10 @@ export class ResourceDetailBase extends React.Component {
|
||||||
pollingInterval: 2000,
|
pollingInterval: 2000,
|
||||||
resourceMetrics: [],
|
resourceMetrics: [],
|
||||||
podMetrics: [], // metrics for all pods whose owner is this resource
|
podMetrics: [], // metrics for all pods whose owner is this resource
|
||||||
|
neighborMetrics: {
|
||||||
|
upstream: {},
|
||||||
|
downstream: {}
|
||||||
|
},
|
||||||
pendingRequests: false,
|
pendingRequests: false,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
error: null
|
error: null
|
||||||
|
@ -170,9 +174,9 @@ export class ResourceDetailBase extends React.Component {
|
||||||
<div>
|
<div>
|
||||||
<div className="page-section">
|
<div className="page-section">
|
||||||
<Octopus
|
<Octopus
|
||||||
resource={this.state.resource}
|
resource={this.state.resourceMetrics[0]}
|
||||||
metrics={this.state.resourceMetrics[0]}
|
neighbors={this.state.neighborMetrics}
|
||||||
neighbors={this.state.neighborMetrics} />
|
api={this.api} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ _.isEmpty(this.state.neighborMetrics.upstream) ? null : (
|
{ _.isEmpty(this.state.neighborMetrics.upstream) ? null : (
|
||||||
|
|
Loading…
Reference in New Issue