Apply global theming to the dashboard using material-ui themes (#1799)

Try to standardize theming and colours throughout the app:

- Move Material UI theme definition into its own file
- Use theme colours in success rate charts
- Remove all colour definitions from styles.css
- Remove unused styles in styles.css
- Audit bare h tag usage throughout the app; replace with Typography
- Standardize the colours to the theme for Progress.jsx
- Use theme colour in Spinner
- Default to warning in meshed status table bar chart
This commit is contained in:
Risha Mars 2018-10-24 17:13:39 -07:00 committed by GitHub
parent 0e91dbb18d
commit 715e8ff2dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 144 additions and 99 deletions

View File

@ -3,25 +3,7 @@
@import url("https://fonts.googleapis.com/css?family=Montserrat:400,700|Roboto:100,300,400");
:root {
--white: #fff;
--header-black: #1e2322;
--sider-black: #343838;
--royalblue: #2F80ED;
--linkblue: #2196f3;
--darkblue: #071E3C;
--curiousblue: #2D9CDB;
--pictonblue: #56CCF2;
--warmgrey: #f6f6f6;
--coldgrey: #c9c9c9;
--neutralgrey: #828282;
--graphgrey: #E0E0E0;
--silver: #BDBDBD;
--green: #26E99D;
--red: #FF4D2B;
--orange: #ffae4b;
--latency-p99: var(--royalblue);
--latency-p95: var(--curiousblue);
--latency-p50: var(--pictonblue);
--font-stack: 'Roboto', 'Lato', helvetica, arial, sans-serif;
--base-width: 8px;
}
@ -42,12 +24,6 @@ a {
}
}
.metric-table {
& .metric-table-sr {
padding-right: 6px;
}
}
/* Colored dot for indicating statuses */
div.status-table-dot {
width: calc(2 * var(--base-width));
@ -63,21 +39,6 @@ div.success-rate-dot {
border-radius: 50%;
}
.status-dot {
&.status-dot-good {
background-color: var(--green);
}
&.status-dot-poor {
background-color: var(--red);
}
&.status-dot-neutral {
background-color: var(--orange);
}
&.status-dot-default {
background-color: #E0E0E0;
}
}
.breadcrumb-link a {
color: white;
}

View File

@ -12,7 +12,7 @@ const getClassification = (meshedPodCount, failedPodCount) => {
if (failedPodCount > 0) {
return "poor";
} else if (meshedPodCount === 0) {
return "neutral";
return "default";
} else {
return "good";
}
@ -47,7 +47,7 @@ const namespacesColumns = PrefixedLink => [
render: row => {
let percent = row.meshedPercent.get();
let barType = _.isEmpty(row.errors) ?
getClassification(row.meshedPods, row.failedPods) : "poor";
getClassification(row.meshedPods, row.failedPods) : "warning";
let Progress = StyledProgress(barType);
let percentMeshedMsg = "";

View File

@ -1,12 +1,13 @@
import { friendlyTitle, metricToFormatter } from './util/Utils.js';
import { processedMetricsPropType, successRateWithMiniChart } from './util/MetricUtils.jsx';
import BaseTable from './BaseTable.jsx';
import ErrorModal from './ErrorModal.jsx';
import GrafanaLink from './GrafanaLink.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import SuccessRateMiniChart from './util/SuccessRateMiniChart.jsx';
import _ from 'lodash';
import { processedMetricsPropType } from './util/MetricUtils.jsx';
import { withContext } from './util/AppContext.jsx';
const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
@ -58,7 +59,7 @@ const columnDefinitions = (resource, showNamespaceColumn, PrefixedLink) => {
title: "Success Rate",
key: "success-rate",
isNumeric: true,
render: d => successRateWithMiniChart(d.successRate)
render: d => <SuccessRateMiniChart sr={d.successRate} />
},
{
title: "Request Rate",

View File

@ -6,6 +6,7 @@ import NetworkGraph from './NetworkGraph.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import Spinner from './util/Spinner.jsx';
import Typography from '@material-ui/core/Typography';
import _ from 'lodash';
import { friendlyTitle } from './util/Utils.js';
import { processMultiResourceRollup } from './util/MetricUtils.jsx';
@ -105,7 +106,7 @@ class Namespaces extends React.Component {
}
return (
<div className="page-section">
<h1>{friendlyTitle(resource).plural}</h1>
<Typography variant="h5">{friendlyTitle(resource).plural}</Typography>
<MetricsTable
resource={resource}
metrics={metrics}

View File

@ -104,7 +104,7 @@ const styles = theme => ({
padding: theme.spacing.unit * 3,
},
linkerdLogoContainer: {
backgroundColor: theme.palette.primary.dark,
backgroundColor: theme.palette.primary.main,
},
linkerdNavLogo: {
minWidth: "180px",

View File

@ -1,5 +1,4 @@
import { displayName, metricToFormatter } from './util/Utils.js';
import { getSuccessRateClassification, srArcClassLabels } from './util/MetricUtils.jsx' ;
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
@ -14,6 +13,7 @@ import TableCell from '@material-ui/core/TableCell';
import TableRow from '@material-ui/core/TableRow';
import Typography from '@material-ui/core/Typography';
import _ from 'lodash';
import { getSuccessRateClassification } from './util/MetricUtils.jsx' ;
const maxNumNeighbors = 6; // max number of neighbor nodes to show in the octopus graph
@ -70,7 +70,7 @@ export default class Octopus extends React.Component {
renderResourceCard(resource, type) {
let display = displayName(resource);
let classification = getSuccessRateClassification(resource.successRate, srArcClassLabels);
let classification = getSuccessRateClassification(resource.successRate);
let Progress = StyledProgress(classification);
return (

View File

@ -11,6 +11,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import Spinner from './util/Spinner.jsx';
import TopModule from './TopModule.jsx';
import Typography from '@material-ui/core/Typography';
import _ from 'lodash';
import { processNeighborData } from './util/TapUtils.jsx';
import { withContext } from './util/AppContext.jsx';
@ -245,7 +246,7 @@ export class ResourceDetailBase extends React.Component {
{ _.isEmpty(upstreams) ? null : (
<div className="page-section">
<h2 className="subsection-header">Inbound</h2>
<Typography variant="h5">Inbound</Typography>
<MetricsTable
resource={this.state.resource.type}
metrics={upstreams} />
@ -255,7 +256,7 @@ export class ResourceDetailBase extends React.Component {
{ _.isEmpty(this.state.neighborMetrics.downstream) ? null : (
<div className="page-section">
<h2 className="subsection-header">Outbound</h2>
<Typography variant="h5">Outbound</Typography>
<MetricsTable
resource={this.state.resource.type}
metrics={this.state.neighborMetrics.downstream} />
@ -266,7 +267,7 @@ export class ResourceDetailBase extends React.Component {
{
this.state.resource.type === "pod" ? null : (
<div className="page-section">
<h2 className="subsection-header">Pods</h2>
<Typography variant="h5">Pods</Typography>
<MetricsTable
resource="pod"
metrics={this.state.podMetrics} />

View File

@ -34,7 +34,7 @@ const getPodClassification = pod => {
if (pod.status === "Running") {
return "good";
} else if (pod.status === "Waiting") {
return "neutral";
return "default";
} else {
return "poor";
}

View File

@ -3,6 +3,11 @@ import PropTypes from 'prop-types';
import React from 'react';
import Tooltip from '@material-ui/core/Tooltip';
import _ from 'lodash';
import classNames from 'classnames';
import { statusClassNames } from './util/theme.js';
import { withStyles } from '@material-ui/core/styles';
const styles = theme => statusClassNames(theme);
const columnConfig = {
"Pod Status": {
@ -28,7 +33,7 @@ const columnConfig = {
}
};
const StatusDot = ({status, multilineDots, columnName}) => (
const StatusDot = ({status, multilineDots, columnName, classes}) => (
<Tooltip
placement="top"
title={(
@ -39,13 +44,18 @@ const StatusDot = ({status, multilineDots, columnName}) => (
</div>
)}>
<div
className={`status-table-dot status-dot status-dot-${status.value} ${multilineDots ? 'dot-multiline': ''}`}
className={classNames(
"status-table-dot",
classes[status.value],
{ "dot-multiline": multilineDots }
)}
key={status.name}>&nbsp;
</div>
</Tooltip>
);
StatusDot.propTypes = {
classes: PropTypes.shape({}).isRequired,
columnName: PropTypes.string.isRequired,
multilineDots: PropTypes.bool.isRequired,
status: PropTypes.shape({
@ -66,7 +76,7 @@ const columns = {
isNumeric: true,
render: d => d.numEntities
},
status: name => {
status: (name, classes) => {
return {
title: name,
key: "status",
@ -79,6 +89,7 @@ const columns = {
status={status}
multilineDots={multilineDots}
columnName={name}
classes={classes}
key={`${name}-pod-status-${i}`} />
);
});
@ -89,6 +100,7 @@ const columns = {
class StatusTable extends React.Component {
static propTypes = {
classes: PropTypes.shape({}).isRequired,
data: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
pods: PropTypes.arrayOf(PropTypes.object).isRequired, // TODO: What's the real shape here.
@ -107,10 +119,11 @@ class StatusTable extends React.Component {
}
render() {
const { classes } = this.props;
let tableCols = [
columns.resourceName,
columns.pods,
columns.status(this.props.statusColumnTitle)
columns.status(this.props.statusColumnTitle, classes)
];
let tableData = this.getTableData();
@ -124,4 +137,4 @@ class StatusTable extends React.Component {
}
}
export default StatusTable;
export default withStyles(styles, { withTheme: true })(StatusTable);

View File

@ -1,9 +1,6 @@
import Grid from '@material-ui/core/Grid';
import Percentage from './Percentage.js';
import PropTypes from 'prop-types';
import React from 'react';
import _ from 'lodash';
import { metricToFormatter } from './Utils.js';
const getPodCategorization = pod => {
if (pod.added && pod.status === "Running") {
@ -16,7 +13,7 @@ const getPodCategorization = pod => {
return ""; // Terminating | Succeeded | Unknown
};
export const getSuccessRateClassification = (rate, successRateLabels) => {
export const getSuccessRateClassification = (rate, successRateLabels = srArcClassLabels) => {
if (_.isNull(rate)) {
return successRateLabels.default;
}
@ -24,26 +21,19 @@ export const getSuccessRateClassification = (rate, successRateLabels) => {
if (rate < 0.9) {
return successRateLabels.poor;
} else if (rate < 0.95) {
return successRateLabels.neutral;
return successRateLabels.warning;
} else {
return successRateLabels.good;
}
};
export const srArcClassLabels = {
const srArcClassLabels = {
good: "good",
neutral: "neutral",
warning: "warning",
poor: "poor",
default: "default"
};
export const successRateWithMiniChart = sr => (
<Grid container spacing={8}>
<Grid item>{metricToFormatter["SUCCESS_RATE"](sr)}</Grid>
<Grid item>{_.isNil(sr) ? null : <div className={`success-rate-dot status-dot status-dot-${getSuccessRateClassification(sr, srArcClassLabels)}`} />}</Grid>
</Grid>
);
const getTotalRequests = row => {
let success = parseInt(_.get(row, ["stats", "successCount"], 0), 10);
let failure = parseInt(_.get(row, ["stats", "failureCount"], 0), 10);

View File

@ -1,27 +1,23 @@
import LinearProgress from '@material-ui/core/LinearProgress';
import grey from '@material-ui/core/colors/grey';
import { dashboardTheme } from './theme.js';
import { withStyles } from '@material-ui/core/styles';
const colorLookup = {
good: {
colorPrimary: '#c8e6c9', // background bar color (lighter)
barColorPrimary: '#388e3c', // inner bar color (darker)
colorPrimary: dashboardTheme.status.light.good, // background bar color (lighter)
barColorPrimary: dashboardTheme.status.dark.good, // inner bar color (darker)
},
warning: {
colorPrimary: '#ffcc80',
barColorPrimary: '#ef6c00',
},
neutral: {
colorPrimary: grey[200],
barColorPrimary: grey[500],
colorPrimary: dashboardTheme.status.light.warning,
barColorPrimary: dashboardTheme.status.dark.warning,
},
poor: {
colorPrimary: '#ffebee',
barColorPrimary: '#d32f2f',
colorPrimary: dashboardTheme.status.light.danger,
barColorPrimary: dashboardTheme.status.dark.danger,
},
default: {
colorPrimary: '#e8eaf6',
barColorPrimary: '#3f51b5',
colorPrimary: dashboardTheme.status.light.default,
barColorPrimary: dashboardTheme.status.dark.default,
}
};

View File

@ -4,9 +4,10 @@ import PropTypes from 'prop-types';
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
const styles = () => ({
const styles = theme => ({
progress: {
"margin": "auto",
margin: "auto",
color: theme.palette.primary.main,
},
});
@ -14,9 +15,7 @@ function CircularIndeterminate(props) {
const { classes } = props;
return (
<Grid container justify="center">
<div >
<CircularProgress className={classes.progress} style={{ color: "#26E99D" }} />
</div>
<CircularProgress className={classes.progress} />
</Grid>
);
}
@ -25,4 +24,4 @@ CircularIndeterminate.propTypes = {
classes: PropTypes.shape({}).isRequired,
};
export default withStyles(styles)(CircularIndeterminate);
export default withStyles(styles, { withTheme: true })(CircularIndeterminate);

View File

@ -0,0 +1,37 @@
import Grid from '@material-ui/core/Grid';
import PropTypes from 'prop-types';
import React from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import { getSuccessRateClassification } from './MetricUtils.jsx';
import { metricToFormatter } from './Utils.js';
import { statusClassNames } from './theme.js';
import { withStyles } from '@material-ui/core/styles';
const styles = theme => statusClassNames(theme);
class SuccessRateMiniChart extends React.Component {
render() {
const { sr, classes } = this.props;
return (
<Grid container spacing={8}>
<Grid item>{metricToFormatter["SUCCESS_RATE"](sr)}</Grid>
<Grid item>{_.isNil(sr) ? null :
<div className={classNames("success-rate-dot", classes[getSuccessRateClassification(sr)])} />}
</Grid>
</Grid>
);
}
}
SuccessRateMiniChart.propTypes = {
classes: PropTypes.shape({}).isRequired,
sr: PropTypes.number,
};
SuccessRateMiniChart.defaultProps = {
sr: null
};
export default withStyles(styles, { withTheme: true })(SuccessRateMiniChart);

View File

@ -0,0 +1,54 @@
import green from '@material-ui/core/colors/green';
import grey from '@material-ui/core/colors/grey';
import orange from '@material-ui/core/colors/orange';
import red from '@material-ui/core/colors/red';
const status = {
// custom variables for success rate indicators
dark: {
danger: red[500],
warning: orange[500],
good: green[500],
default: grey[500],
},
// custom variables for progress bars, which need both the normal colors
// as well as a lighter version of them for the bar background
light: {
danger: red[200],
warning: orange[200],
good: green[200],
default: grey[200],
}
};
export const dashboardTheme = {
palette: {
primary: green
},
typography: {
useNextVariants: true,
suppressDeprecationWarnings: true // https://github.com/mui-org/material-ui/issues/13175
},
status
};
export const statusClassNames = theme => {
theme.status = theme.status || status; // tests don't inject custom variables
return {
poor: {
backgroundColor: theme.status.dark.danger,
},
warning: {
backgroundColor: theme.status.dark.warning,
},
good: {
backgroundColor: theme.status.dark.good,
},
default: {
backgroundColor: theme.status.dark.default,
},
};
};

View File

@ -19,7 +19,7 @@ import { RouterToUrlQuery } from 'react-url-query';
import ServiceMesh from './components/ServiceMesh.jsx';
import Tap from './components/Tap.jsx';
import Top from './components/Top.jsx';
import green from '@material-ui/core/colors/green';
import { dashboardTheme } from './components/util/theme.js';
let appMain = document.getElementById('main');
let appData = !appMain ? {} : appMain.dataset;
@ -37,15 +37,7 @@ const context = {
productName: "Linkerd"
};
const theme = createMuiTheme({
palette: {
primary: green
},
typography: {
useNextVariants: true,
suppressDeprecationWarnings: true // https://github.com/mui-org/material-ui/issues/13175
}
});
const theme = createMuiTheme(dashboardTheme);
let applicationHtml = (
<React.Fragment>