mirror of https://github.com/linkerd/linkerd2.git
Dashboard Table cleanups (#1793)
This branch includes some small appearance tweaks for tables in the app. - Removes the restrictions on the MetricsTables for Authorities Grafana links (Authorities Grafana dashboards were added in #1772) - Fixes the tables overflowing their containers on the Overview page - Allows tables to be denser, allowing for more data on screen - Fixes the colour of the meshed status bar in the ServiceMesh page - Rixes the ErrorModal icon alignment and colour - Small appearance tweaks to the things in the table e.g. icons
This commit is contained in:
parent
1922dc0a0b
commit
98ee36344e
|
@ -20,11 +20,11 @@ const styles = theme => ({
|
|||
});
|
||||
|
||||
function BaseTable(props) {
|
||||
const { classes, tableRows, tableColumns, tableClassName, rowKey} = props;
|
||||
const { classes, tableRows, tableColumns, tableClassName, rowKey, padding} = props;
|
||||
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<Table className={`${classes.table} ${tableClassName}`}>
|
||||
<Table className={`${classes.table} ${tableClassName}`} padding={padding}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{ _.map(tableColumns, c => (
|
||||
|
@ -60,6 +60,7 @@ function BaseTable(props) {
|
|||
|
||||
BaseTable.propTypes = {
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
padding: PropTypes.string,
|
||||
rowKey: PropTypes.func,
|
||||
tableClassName: PropTypes.string,
|
||||
tableColumns: PropTypes.arrayOf(PropTypes.shape({
|
||||
|
@ -71,9 +72,10 @@ BaseTable.propTypes = {
|
|||
};
|
||||
|
||||
BaseTable.defaultProps = {
|
||||
padding: "default",
|
||||
rowKey: null,
|
||||
tableClassName: "",
|
||||
tableRows: []
|
||||
tableRows: [],
|
||||
};
|
||||
|
||||
export default withStyles(styles)(BaseTable);
|
||||
|
|
|
@ -79,7 +79,7 @@ class ErrorModal extends React.Component {
|
|||
}
|
||||
|
||||
return _.map(errorsByContainer, (errors, container) => (
|
||||
<div key={`error-${container}`} className="container-error">
|
||||
<div key={`error-${container}`}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
|
@ -95,7 +95,7 @@ class ErrorModal extends React.Component {
|
|||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<div className="error-text">
|
||||
<div>
|
||||
{
|
||||
_.map(errors, (er, i) => {
|
||||
if (_.size(er.message) === 0) {
|
||||
|
@ -121,8 +121,8 @@ class ErrorModal extends React.Component {
|
|||
renderPodErrors = errors => {
|
||||
return _.map(errors, err => {
|
||||
return (
|
||||
<div className="controller-pod-error" key={err.pod}>
|
||||
<Typography variant="title" gutterBottom>{err.pod}</Typography>
|
||||
<div key={err.pod}>
|
||||
<Typography variant="h6" gutterBottom>{err.pod}</Typography>
|
||||
{this.renderContainerErrors(err.pod, err.byContainer)}
|
||||
</div>
|
||||
);
|
||||
|
@ -145,7 +145,9 @@ class ErrorModal extends React.Component {
|
|||
<Tooltip title="Pods are initializing"><CircularProgress size={20} thickness={4} /></Tooltip>
|
||||
);
|
||||
} else {
|
||||
return <ErrorIcon onClick={this.handleClickOpen} />;
|
||||
return (
|
||||
<ErrorIcon color="error" fontSize="small" onClick={this.handleClickOpen} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,7 +155,7 @@ class ErrorModal extends React.Component {
|
|||
let errors = this.processErrorData(this.props.errors);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<React.Fragment>
|
||||
{this.renderStatusIcon(errors)}
|
||||
<Dialog
|
||||
open={this.state.open}
|
||||
|
@ -183,7 +185,7 @@ class ErrorModal extends React.Component {
|
|||
<Button onClick={this.handleClose} color="primary">Close</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import BaseTable from './BaseTable.jsx';
|
||||
import ErrorModal from './ErrorModal.jsx';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { StyledProgress } from './util/Progress.jsx';
|
||||
|
@ -24,10 +25,12 @@ const namespacesColumns = PrefixedLink => [
|
|||
render: d => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PrefixedLink to={"/namespaces/" + d.namespace}>{d.namespace}</PrefixedLink>
|
||||
{ _.isEmpty(d.errors) ? null :
|
||||
<ErrorModal errors={d.errors} resourceName={d.namespace} resourceType="namespace" />
|
||||
<Grid container alignItems="center" spacing={8}>
|
||||
<Grid item><PrefixedLink to={"/namespaces/" + d.namespace}>{d.namespace}</PrefixedLink></Grid>
|
||||
{ _.isEmpty(d.errors) ? null :
|
||||
<Grid item><ErrorModal errors={d.errors} resourceName={d.namespace} resourceType="namespace" /></Grid>
|
||||
}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,21 +21,6 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
|
|||
}
|
||||
];
|
||||
|
||||
let grafanaLinkColumn = [
|
||||
{
|
||||
title: "Grafana Dashboard",
|
||||
key: "grafanaDashboard",
|
||||
isNumeric: true,
|
||||
render: row => !row.added || _.get(row, "pods.totalPods") === "0" ? null : (
|
||||
<GrafanaLink
|
||||
name={row.name}
|
||||
namespace={row.namespace}
|
||||
resource={resource}
|
||||
PrefixedLink={PrefixedLink} />
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
let meshedColumn = {
|
||||
title: "Meshed",
|
||||
key: "meshed",
|
||||
|
@ -63,7 +48,7 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
|
|||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
{nameContents}
|
||||
{nameContents}
|
||||
{ _.isEmpty(d.errors) ? null : <ErrorModal errors={d.errors} resourceName={d.name} resourceType={resource} /> }
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -104,13 +89,30 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
|
|||
key: "has_tls",
|
||||
isNumeric: true,
|
||||
render: d => _.isNil(d.tlsRequestPercent) || d.tlsRequestPercent.get() === -1 ? "---" : d.tlsRequestPercent.prettyRate()
|
||||
},
|
||||
{
|
||||
title: "Grafana Dashboard",
|
||||
key: "grafanaDashboard",
|
||||
isNumeric: true,
|
||||
render: row => {
|
||||
if (!isAuthorityTable && (!row.added || _.get(row, "pods.totalPods") === "0") ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<GrafanaLink
|
||||
name={row.name}
|
||||
namespace={row.namespace}
|
||||
resource={resource}
|
||||
PrefixedLink={PrefixedLink} />
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
// don't add the meshed column on a Authority MetricsTable
|
||||
// don't add the meshed column on a Authority MetricsTable
|
||||
if (!isAuthorityTable) {
|
||||
columns.splice(1, 0, meshedColumn);
|
||||
columns = _.concat(columns, grafanaLinkColumn);
|
||||
}
|
||||
|
||||
if (!showNamespaceColumn) {
|
||||
|
@ -159,7 +161,8 @@ class MetricsTable extends React.Component {
|
|||
<BaseTable
|
||||
tableRows={rows}
|
||||
tableColumns={columns}
|
||||
tableClassName="metric-table" />
|
||||
tableClassName="metric-table"
|
||||
padding="dense" />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,13 +52,13 @@ describe('Tests for <MetricsTable>', () => {
|
|||
expect(table.props().tableColumns).toHaveLength(9);
|
||||
});
|
||||
|
||||
it('omits meshed column and grafana column for authority resource', () => {
|
||||
it('omits meshed column for an authority resource', () => {
|
||||
let extraProps = _.merge({}, defaultProps, { metrics: [], resource: "authority"});
|
||||
const component = mount(routerWrap(MetricsTable, extraProps));
|
||||
|
||||
const table = component.find("BaseTable");
|
||||
|
||||
expect(table).toBeDefined();
|
||||
expect(table.props().tableColumns).toHaveLength(8);
|
||||
expect(table.props().tableColumns).toHaveLength(9);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@ import { processMultiResourceRollup, processSingleResourceRollup } from './util/
|
|||
import Accordion from './util/Accordion.jsx';
|
||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||
import ErrorBanner from './ErrorBanner.jsx';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import MetricsTable from './MetricsTable.jsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
@ -17,7 +18,7 @@ import { withContext } from './util/AppContext.jsx';
|
|||
|
||||
const isMeshedTooltip = (
|
||||
<Tooltip title="Namespace is meshed" placement="right-start">
|
||||
<CheckCircleIcon />
|
||||
<CheckCircleIcon color="primary" />
|
||||
</Tooltip>
|
||||
);
|
||||
class NamespaceLanding extends React.Component {
|
||||
|
@ -124,14 +125,18 @@ class NamespaceLanding extends React.Component {
|
|||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="page-section">
|
||||
<br />
|
||||
<Typography variant="h5">{friendlyTitle(resource).plural}</Typography>
|
||||
<MetricsTable
|
||||
resource={resource}
|
||||
metrics={metrics}
|
||||
showNamespaceColumn={false} />
|
||||
</div>
|
||||
<Grid container direction="column" justify="center">
|
||||
<Grid item>
|
||||
<Typography variant="h5">{friendlyTitle(resource).plural}</Typography>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<MetricsTable
|
||||
resource={resource}
|
||||
metrics={metrics}
|
||||
showNamespaceColumn={false} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -144,14 +149,15 @@ class NamespaceLanding extends React.Component {
|
|||
let noMetrics = _.isEmpty(metrics.pod);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h4">Namespace: {namespace}</Typography>
|
||||
{ noMetrics ? <div>No resources detected.</div> : null}
|
||||
<Grid container direction="column">
|
||||
<Grid item><Typography variant="h4">Namespace: {namespace}</Typography></Grid>
|
||||
<Grid item>{ noMetrics ? <div>No resources detected.</div> : null}</Grid>
|
||||
|
||||
{this.renderResourceSection("deployment", metrics.deployment)}
|
||||
{this.renderResourceSection("replicationcontroller", metrics.replicationcontroller)}
|
||||
{this.renderResourceSection("pod", metrics.pod)}
|
||||
{this.renderResourceSection("authority", metrics.authority)}
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -159,19 +165,20 @@ class NamespaceLanding extends React.Component {
|
|||
let panelData = _.map(this.state.namespaces, ns => {
|
||||
return {
|
||||
id: ns.name,
|
||||
header: <React.Fragment>{ns.name} {!ns.added ? null : isMeshedTooltip}</React.Fragment>,
|
||||
header: <React.Fragment><Typography variant="subtitle1">{ns.name}</Typography> {!ns.added ? null : isMeshedTooltip}</React.Fragment>,
|
||||
body: ns.name === this.state.selectedNs || ns.name === this.state.defaultOpenNs.name ?
|
||||
this.renderNamespaceSection(ns.name) : null
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container>
|
||||
|
||||
<Accordion
|
||||
onChange={this.onNamespaceChange}
|
||||
panels={panelData}
|
||||
defaultOpenPanel={_.get(this.state.defaultOpenNs, 'name', null)} />
|
||||
</React.Fragment>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import LinearProgress from '@material-ui/core/LinearProgress';
|
||||
import grey from '@material-ui/core/colors/grey';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const colorLookup = {
|
||||
|
@ -6,10 +7,14 @@ const colorLookup = {
|
|||
colorPrimary: '#c8e6c9', // background bar color (lighter)
|
||||
barColorPrimary: '#388e3c', // inner bar color (darker)
|
||||
},
|
||||
neutral: {
|
||||
warning: {
|
||||
colorPrimary: '#ffcc80',
|
||||
barColorPrimary: '#ef6c00',
|
||||
},
|
||||
neutral: {
|
||||
colorPrimary: grey[200],
|
||||
barColorPrimary: grey[500],
|
||||
},
|
||||
poor: {
|
||||
colorPrimary: '#ffebee',
|
||||
barColorPrimary: '#d32f2f',
|
||||
|
|
Loading…
Reference in New Issue