linkerd2/web/app/js/components/TapEventTable.jsx

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