mirror of https://github.com/linkerd/linkerd2.git
211 lines
6.2 KiB
JavaScript
211 lines
6.2 KiB
JavaScript
import _ from 'lodash';
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import { withContext } from './util/AppContext.jsx';
|
|
import { Col, Icon, Row, Table } from 'antd';
|
|
import { directionColumn, srcDstColumn } from './util/TapUtils.jsx';
|
|
import { formatLatencySec, formatWithComma } from './util/Utils.js';
|
|
|
|
// https://godoc.org/google.golang.org/grpc/codes#Code
|
|
const grpcStatusCodes = {
|
|
0: "OK",
|
|
1: "Canceled",
|
|
2: "Unknown",
|
|
3: "InvalidArgument",
|
|
4: "DeadlineExceeded",
|
|
5: "NotFound",
|
|
6: "AlreadyExists",
|
|
7: "PermissionDenied",
|
|
8: "ResourceExhausted",
|
|
9: "FailedPrecondition",
|
|
10: "Aborted",
|
|
11: "OutOfRange",
|
|
12: "Unimplemented",
|
|
13: "Internal",
|
|
14: "Unavailable",
|
|
15: "DataLoss",
|
|
16: "Unauthenticated"
|
|
};
|
|
|
|
const smallMetricColWidth = "120px";
|
|
|
|
const httpStatusCol = {
|
|
title: "HTTP status",
|
|
key: "http-status",
|
|
width: smallMetricColWidth,
|
|
dataIndex: "responseInit.http.responseInit",
|
|
render: d => !d ? <Icon type="loading" /> : d.httpStatus
|
|
};
|
|
|
|
const responseInitLatencyCol = {
|
|
title: "Latency",
|
|
key: "rsp-latency",
|
|
width: smallMetricColWidth,
|
|
dataIndex: "responseInit.http.responseInit",
|
|
render: d => !d ? <Icon type="loading" /> : formatTapLatency(d.sinceRequestInit)
|
|
};
|
|
|
|
const grpcStatusCol = {
|
|
title: "GRPC status",
|
|
key: "grpc-status",
|
|
width: smallMetricColWidth,
|
|
dataIndex: "responseEnd.http.responseEnd",
|
|
render: d => !d ? <Icon type="loading" /> :
|
|
_.isNull(d.eos) ? "---" : grpcStatusCodes[_.get(d, "eos.grpcStatusCode")]
|
|
};
|
|
|
|
const pathCol = {
|
|
title: "Path",
|
|
key: "path",
|
|
dataIndex: "requestInit.http.requestInit",
|
|
render: d => !d ? <Icon type="loading" /> : d.path
|
|
};
|
|
|
|
const methodCol = {
|
|
title: "Method",
|
|
key: "method",
|
|
dataIndex: "requestInit.http.requestInit",
|
|
render: d => !d ? <Icon type="loading" /> : _.get(d, "method.registered")
|
|
};
|
|
|
|
const topLevelColumns = (resourceType, ResourceLink) => [
|
|
{
|
|
title: "Direction",
|
|
key: "direction",
|
|
dataIndex: "base.proxyDirection",
|
|
width: "98px",
|
|
filters: [
|
|
{ text: "FROM", value: "INBOUND" },
|
|
{ text: "TO", value: "OUTBOUND" }
|
|
],
|
|
render: directionColumn,
|
|
onFilter: (value, row) => _.get(row, "base.proxyDirection").includes(value)
|
|
},
|
|
{
|
|
title: "Name",
|
|
key: "src-dst",
|
|
render: d => {
|
|
let datum = {
|
|
direction: _.get(d, "base.proxyDirection"),
|
|
source: _.get(d, "base.source"),
|
|
destination: _.get(d, "base.destination"),
|
|
sourceLabels: _.get(d, "base.sourceMeta.labels", {}),
|
|
destinationLabels: _.get(d, "base.destinationMeta.labels", {})
|
|
};
|
|
return srcDstColumn(datum, resourceType, ResourceLink);
|
|
}
|
|
}
|
|
];
|
|
|
|
const tapColumns = (resourceType, ResourceLink) => {
|
|
return _.concat(
|
|
topLevelColumns(resourceType, ResourceLink),
|
|
[ methodCol, pathCol, responseInitLatencyCol, httpStatusCol, grpcStatusCol ]
|
|
);
|
|
};
|
|
|
|
const formatTapLatency = str => {
|
|
return formatLatencySec(str.replace("s", ""));
|
|
};
|
|
|
|
const requestInitSection = d => (
|
|
<React.Fragment>
|
|
<Row gutter={8} className="tap-info-section">
|
|
<h3>Request Init</h3>
|
|
<Row gutter={8} className="expand-section-header">
|
|
<Col span={8}>Authority</Col>
|
|
<Col span={9}>Path</Col>
|
|
<Col span={2}>Scheme</Col>
|
|
<Col span={2}>Method</Col>
|
|
<Col span={3}>TLS</Col>
|
|
</Row>
|
|
<Row gutter={8}>
|
|
<Col span={8}>{_.get(d, "requestInit.http.requestInit.authority")}</Col>
|
|
<Col span={9}>{_.get(d, "requestInit.http.requestInit.path")}</Col>
|
|
<Col span={2}>{_.get(d, "requestInit.http.requestInit.scheme.registered")}</Col>
|
|
<Col span={2}>{_.get(d, "requestInit.http.requestInit.method.registered")}</Col>
|
|
<Col span={3}>{_.get(d, "base.tls")}</Col>
|
|
</Row>
|
|
</Row>
|
|
</React.Fragment>
|
|
);
|
|
|
|
const responseInitSection = d => _.isEmpty(d.responseInit) ? null : (
|
|
<React.Fragment>
|
|
<hr />
|
|
<Row gutter={8} className="tap-info-section">
|
|
<h3>Response Init</h3>
|
|
<Row gutter={8} className="expand-section-header">
|
|
<Col span={4}>HTTP Status</Col>
|
|
<Col span={4}>Latency</Col>
|
|
</Row>
|
|
<Row gutter={8}>
|
|
<Col span={4}>{_.get(d, "responseInit.http.responseInit.httpStatus")}</Col>
|
|
<Col span={4}>{formatTapLatency(_.get(d, "responseInit.http.responseInit.sinceRequestInit"))}</Col>
|
|
</Row>
|
|
</Row>
|
|
</React.Fragment>
|
|
);
|
|
|
|
const responseEndSection = d => _.isEmpty(d.responseEnd) ? null : (
|
|
<React.Fragment>
|
|
<hr />
|
|
<Row gutter={8} className="tap-info-section">
|
|
<h3>Response End</h3>
|
|
<Row gutter={8} className="expand-section-header">
|
|
<Col span={4}>GRPC Status</Col>
|
|
<Col span={4}>Latency</Col>
|
|
<Col span={4}>Response Length (B)</Col>
|
|
</Row>
|
|
<Row gutter={8}>
|
|
<Col span={4}>{_.isNull(_.get(d, "responseEnd.http.responseEnd.eos")) ? "N/A" : grpcStatusCodes[_.get(d, "responseEnd.http.responseEnd.eos.grpcStatusCode")]}</Col>
|
|
<Col span={4}>{formatTapLatency(_.get(d, "responseEnd.http.responseEnd.sinceResponseInit"))}</Col>
|
|
<Col span={4}>{formatWithComma(_.get(d, "responseEnd.http.responseEnd.responseBytes"))}</Col>
|
|
</Row>
|
|
</Row>
|
|
</React.Fragment>
|
|
);
|
|
|
|
// hide verbose information
|
|
const expandedRowRender = d => {
|
|
return (
|
|
<div className="tap-more-info">
|
|
{requestInitSection(d)}
|
|
{responseInitSection(d)}
|
|
{responseEndSection(d)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
class TapEventTable extends React.Component {
|
|
static propTypes = {
|
|
api: PropTypes.shape({
|
|
ResourceLink: PropTypes.func.isRequired,
|
|
}).isRequired,
|
|
resource: PropTypes.string,
|
|
tableRows: PropTypes.arrayOf(PropTypes.shape({})),
|
|
}
|
|
|
|
static defaultProps = {
|
|
resource: "",
|
|
tableRows: []
|
|
}
|
|
|
|
render() {
|
|
let resourceType = this.props.resource.split("/")[0];
|
|
return (
|
|
<Table
|
|
dataSource={this.props.tableRows}
|
|
columns={tapColumns(resourceType, this.props.api.ResourceLink)}
|
|
expandedRowRender={expandedRowRender} // hide extra info in expandable row
|
|
expandRowByClick={true} // click anywhere on row to expand
|
|
rowKey={r => r.base.id}
|
|
pagination={false}
|
|
className="tap-event-table"
|
|
size="middle" />
|
|
);
|
|
}
|
|
}
|
|
|
|
export default withContext(TapEventTable);
|