mirror of https://github.com/linkerd/linkerd2.git
228 lines
6.6 KiB
JavaScript
228 lines
6.6 KiB
JavaScript
import { directionColumn, srcDstColumn } from './util/TapUtils.jsx';
|
|
import { formatLatencySec, formatWithComma } from './util/Utils.js';
|
|
|
|
import Card from '@material-ui/core/Card';
|
|
import CardContent from '@material-ui/core/CardContent';
|
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
|
import ExpandableTable from './ExpandableTable.jsx';
|
|
import Grid from '@material-ui/core/Grid';
|
|
import List from '@material-ui/core/List';
|
|
import ListItem from '@material-ui/core/ListItem';
|
|
import ListItemText from '@material-ui/core/ListItemText';
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import Typography from '@material-ui/core/Typography';
|
|
import _get from 'lodash/get';
|
|
import _isEmpty from 'lodash/isEmpty';
|
|
import _isNull from 'lodash/isNull';
|
|
import { headersDisplay } from './TapEventHeadersTable.jsx';
|
|
import { withContext } from './util/AppContext.jsx';
|
|
import { withStyles } from '@material-ui/core/styles';
|
|
|
|
// 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 spinnerStyles = theme => ({
|
|
progress: {
|
|
margin: theme.spacing(2),
|
|
},
|
|
});
|
|
const SpinnerBase = () => <CircularProgress size={20} />;
|
|
const Spinner = withStyles(spinnerStyles)(SpinnerBase);
|
|
|
|
const formatTapLatency = str => {
|
|
return formatLatencySec(str.replace('s', ''));
|
|
};
|
|
|
|
const httpStatusCol = {
|
|
title: 'HTTP status',
|
|
key: 'http-status',
|
|
render: datum => {
|
|
const d = _get(datum, 'responseInit.http.responseInit');
|
|
return !d ? <Spinner /> : d.httpStatus;
|
|
},
|
|
};
|
|
|
|
const responseInitLatencyCol = {
|
|
title: 'Latency',
|
|
key: 'rsp-latency',
|
|
isNumeric: true,
|
|
render: datum => {
|
|
const d = _get(datum, 'responseInit.http.responseInit');
|
|
return !d ? <Spinner /> : formatTapLatency(d.sinceRequestInit);
|
|
},
|
|
};
|
|
|
|
const grpcStatusCol = {
|
|
title: 'GRPC status',
|
|
key: 'grpc-status',
|
|
render: datum => {
|
|
const d = _get(datum, 'responseEnd.http.responseEnd');
|
|
return !d ? <Spinner /> :
|
|
_isNull(d.eos) ? '---' : grpcStatusCodes[_get(d, 'eos.grpcStatusCode')];
|
|
},
|
|
};
|
|
|
|
const pathCol = {
|
|
title: 'Path',
|
|
key: 'path',
|
|
render: datum => {
|
|
const d = _get(datum, 'requestInit.http.requestInit');
|
|
return !d ? <Spinner /> : d.path;
|
|
},
|
|
};
|
|
|
|
const methodCol = {
|
|
title: 'Method',
|
|
key: 'method',
|
|
render: datum => {
|
|
const d = _get(datum, 'requestInit.http.requestInit');
|
|
return !d ? <Spinner /> : _get(d, 'method.registered');
|
|
},
|
|
};
|
|
|
|
const topLevelColumns = (resourceType, ResourceLink) => [
|
|
{
|
|
title: 'Direction',
|
|
key: 'direction',
|
|
render: d => directionColumn(d.base.proxyDirection),
|
|
},
|
|
{
|
|
title: 'Name',
|
|
key: 'src-dst',
|
|
render: d => {
|
|
const 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 topLevelColumns(resourceType, ResourceLink).concat(
|
|
[methodCol, pathCol, responseInitLatencyCol, httpStatusCol, grpcStatusCol],
|
|
);
|
|
};
|
|
|
|
const itemDisplay = (title, value) => {
|
|
return (
|
|
<ListItem disableGutters>
|
|
<ListItemText primary={title} secondary={value} />
|
|
</ListItem>
|
|
);
|
|
};
|
|
|
|
const requestInitSection = d => (
|
|
<React.Fragment>
|
|
<Typography variant="subtitle2">Request Init</Typography>
|
|
<br />
|
|
<List dense>
|
|
{itemDisplay('Authority', _get(d, 'requestInit.http.requestInit.authority'))}
|
|
{itemDisplay('Path', _get(d, 'requestInit.http.requestInit.path'))}
|
|
{itemDisplay('Scheme', _get(d, 'requestInit.http.requestInit.scheme.registered'))}
|
|
{itemDisplay('Method', _get(d, 'requestInit.http.requestInit.method.registered'))}
|
|
{headersDisplay('Headers', _get(d, 'requestInit.http.requestInit.headers'))}
|
|
</List>
|
|
</React.Fragment>
|
|
);
|
|
|
|
const responseInitSection = d => _isEmpty(d.responseInit) ? null : (
|
|
<React.Fragment>
|
|
<Typography variant="subtitle2">Response Init</Typography>
|
|
<br />
|
|
<List dense>
|
|
{itemDisplay('HTTP Status', _get(d, 'responseInit.http.responseInit.httpStatus'))}
|
|
{itemDisplay('Latency', formatTapLatency(_get(d, 'responseInit.http.responseInit.sinceRequestInit')))}
|
|
{headersDisplay('Headers', _get(d, 'responseInit.http.responseInit.headers'))}
|
|
</List>
|
|
</React.Fragment>
|
|
);
|
|
|
|
const responseEndSection = d => _isEmpty(d.responseEnd) ? null : (
|
|
<React.Fragment>
|
|
<Typography variant="subtitle2">Response End</Typography>
|
|
<br />
|
|
|
|
<List dense>
|
|
{itemDisplay('GRPC Status', _isNull(_get(d, 'responseEnd.http.responseEnd.eos')) ? 'N/A' : grpcStatusCodes[_get(d, 'responseEnd.http.responseEnd.eos.grpcStatusCode')])}
|
|
{itemDisplay('Latency', formatTapLatency(_get(d, 'responseEnd.http.responseEnd.sinceResponseInit')))}
|
|
{itemDisplay('Response Length (B)', formatWithComma(_get(d, 'responseEnd.http.responseEnd.responseBytes')))}
|
|
</List>
|
|
</React.Fragment>
|
|
);
|
|
|
|
|
|
// hide verbose information
|
|
const expandedRowRender = (d, expandedWrapStyle) => {
|
|
return (
|
|
<Grid container spacing={2} className={expandedWrapStyle}>
|
|
<Grid item xs={4}>
|
|
<Card elevation={3}>
|
|
<CardContent>{requestInitSection(d)}</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
<Grid item xs={4}>
|
|
<Card elevation={3}>
|
|
<CardContent>{responseInitSection(d)}</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
<Grid item xs={4}>
|
|
<Card elevation={3}>
|
|
<CardContent>{responseEndSection(d)}</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
const TapEventTable = ({ tableRows, resource, api }) => {
|
|
const resourceType = resource.split('/')[0];
|
|
const columns = tapColumns(resourceType, api.ResourceLink);
|
|
|
|
return (
|
|
<ExpandableTable
|
|
tableRows={tableRows}
|
|
tableColumns={columns}
|
|
expandedRowRender={expandedRowRender}
|
|
tableClassName="metric-table" />
|
|
);
|
|
};
|
|
|
|
TapEventTable.propTypes = {
|
|
api: PropTypes.shape({
|
|
ResourceLink: PropTypes.func.isRequired,
|
|
}).isRequired,
|
|
resource: PropTypes.string,
|
|
tableRows: PropTypes.arrayOf(PropTypes.shape({})),
|
|
};
|
|
|
|
TapEventTable.defaultProps = {
|
|
resource: '',
|
|
tableRows: [],
|
|
};
|
|
|
|
export default withContext(TapEventTable);
|