mirror of https://github.com/linkerd/linkerd2.git
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:
parent
0e91dbb18d
commit
715e8ff2dc
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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}>
|
||||
</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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue