mirror of https://github.com/linkerd/linkerd2.git
Re-implement sidebar resource selectors (#1810)
Signed-off-by: Andrew Seigner <siggy@buoyant.io>
This commit is contained in:
parent
6cffad277b
commit
c661b00f8e
|
@ -24,6 +24,7 @@ const (
|
|||
|
||||
// resources to query in StatSummary when Resource.Type is "all"
|
||||
var StatAllResourceTypes = []string{
|
||||
// TODO: add Namespace here to decrease queries from the web process
|
||||
Deployment,
|
||||
ReplicationController,
|
||||
Pod,
|
||||
|
|
|
@ -62,6 +62,14 @@ class Namespaces extends React.Component {
|
|||
this.timerId = window.setInterval(this.loadFromServer, this.state.pollingInterval);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!_.isEqual(prevProps.match.params.namespace, this.props.match.params.namespace)) {
|
||||
// React won't unmount this component when switching resource pages so we need to clear state
|
||||
this.api.cancelCurrentRequests();
|
||||
this.setState(this.getInitialState(this.props.match.params));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearInterval(this.timerId);
|
||||
this.api.cancelCurrentRequests();
|
||||
|
|
|
@ -70,6 +70,7 @@ class NamespaceLanding extends React.Component {
|
|||
}
|
||||
this.setState({ pendingRequests: true });
|
||||
|
||||
// TODO: make this one request
|
||||
let apiRequests = [
|
||||
this.api.fetchMetrics(this.api.urlsForResource("namespace"))
|
||||
];
|
||||
|
|
|
@ -25,13 +25,12 @@ import HomeIcon from '@material-ui/icons/Home';
|
|||
import LibraryBooksIcon from '@material-ui/icons/LibraryBooks';
|
||||
import { Link } from 'react-router-dom';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import NavigateNextIcon from '@material-ui/icons/NavigateNext';
|
||||
import NavigationResources from './NavigationResources.jsx';
|
||||
import NetworkCheckIcon from '@material-ui/icons/NetworkCheck';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ReactRouterPropTypes from 'react-router-prop-types';
|
||||
import Version from './Version.jsx';
|
||||
import ViewListIcon from '@material-ui/icons/ViewList';
|
||||
import VisibilityIcon from '@material-ui/icons/Visibility';
|
||||
import classNames from 'classnames';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
|
@ -128,7 +127,6 @@ class NavigationBase extends React.Component {
|
|||
getInitialState() {
|
||||
return {
|
||||
drawerOpen: true,
|
||||
resourceMenuOpen: false,
|
||||
helpMenuOpen: false,
|
||||
latestVersion: '',
|
||||
isLatest: true,
|
||||
|
@ -171,10 +169,6 @@ class NavigationBase extends React.Component {
|
|||
this.setState({ drawerOpen: false });
|
||||
};
|
||||
|
||||
handleResourceMenuClick = () => {
|
||||
this.setState(state => ({ resourceMenuOpen: !state.resourceMenuOpen }));
|
||||
};
|
||||
|
||||
handleHelpMenuClick = () => {
|
||||
this.setState(state => ({ helpMenuOpen: !state.helpMenuOpen }));
|
||||
}
|
||||
|
@ -240,24 +234,7 @@ class NavigationBase extends React.Component {
|
|||
{ this.menuItem("/tap", "Tap", <VisibilityIcon />) }
|
||||
{ this.menuItem("/top", "Top", <NetworkCheckIcon />) }
|
||||
{ this.menuItem("/servicemesh", "Service Mesh", <CloudQueueIcon />) }
|
||||
<MenuItem
|
||||
className={classes.navMenuItem}
|
||||
button
|
||||
onClick={this.handleResourceMenuClick}>
|
||||
<ListItemIcon><ViewListIcon /></ListItemIcon>
|
||||
<ListItemText inset primary="Resources" />
|
||||
{this.state.resourceMenuOpen ? <ExpandLess /> : <ExpandMore />}
|
||||
</MenuItem>
|
||||
|
||||
<Collapse in={this.state.resourceMenuOpen} timeout="auto" unmountOnExit>
|
||||
<MenuList dense component="div" disablePadding>
|
||||
{ this.menuItem("/authorities", "Authorities", <NavigateNextIcon />) }
|
||||
{ this.menuItem("/deployments", "Deployments", <NavigateNextIcon />) }
|
||||
{ this.menuItem("/namespaces", "Namespaces", <NavigateNextIcon />) }
|
||||
{ this.menuItem("/pods", "Pods", <NavigateNextIcon />) }
|
||||
{ this.menuItem("/replicationcontrollers", "Replication Controllers", <NavigateNextIcon />) }
|
||||
</MenuList>
|
||||
</Collapse>
|
||||
<NavigationResources />
|
||||
</MenuList>
|
||||
|
||||
<Divider />
|
||||
|
@ -315,9 +292,7 @@ class NavigationBase extends React.Component {
|
|||
}
|
||||
|
||||
NavigationBase.propTypes = {
|
||||
api: PropTypes.shape({
|
||||
PrefixedLink: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
api: PropTypes.shape({}).isRequired,
|
||||
ChildComponent: PropTypes.func.isRequired,
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
location: ReactRouterPropTypes.location.isRequired,
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
import {
|
||||
Collapse,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||
import { Link } from 'react-router-dom';
|
||||
import NavigateNextIcon from '@material-ui/icons/NavigateNext';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import SuccessRateDot from "./util/SuccessRateDot.jsx";
|
||||
import _ from 'lodash';
|
||||
import { friendlyTitle } from "./util/Utils.js";
|
||||
import { processedMetricsPropType } from './util/MetricUtils.jsx';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = () => ({
|
||||
navMenuItem: {
|
||||
paddingLeft: "24px",
|
||||
paddingRight: "12px",
|
||||
},
|
||||
navResourceItem: {
|
||||
marginRight: "0",
|
||||
paddingLeft: "38px",
|
||||
},
|
||||
navResourceText: {
|
||||
overflow: "hidden",
|
||||
padding: "0px 0px 0px 10px",
|
||||
textOverflow: "ellipsis",
|
||||
}
|
||||
});
|
||||
|
||||
class NavigationResource extends React.Component {
|
||||
static defaultProps = {
|
||||
metrics: [],
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
api: PropTypes.shape({
|
||||
prefixLink: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
metrics: PropTypes.arrayOf(processedMetricsPropType),
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.resources = _(props.metrics)
|
||||
.filter(m => m.pods.meshedPods !== "0")
|
||||
.map(m =>
|
||||
_.merge(m, {
|
||||
menuName: props.type === "namespaces" ? m.name : `${m.namespace}/${m.name}`
|
||||
})
|
||||
)
|
||||
.sortBy("menuName")
|
||||
.value();
|
||||
this.to = props.api.prefixLink("/" + props.type);
|
||||
|
||||
this.state = {open: false};
|
||||
}
|
||||
|
||||
handleOnClick = () => {
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
listItemIcon() {
|
||||
let icon = <NavigateNextIcon />;
|
||||
if (this.state.open) {
|
||||
icon = <ExpandMore />;
|
||||
}
|
||||
return (<ListItemIcon>{icon}</ListItemIcon>);
|
||||
}
|
||||
|
||||
menu() {
|
||||
const { classes, type } = this.props;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={classes.navMenuItem}
|
||||
onClick={this.handleOnClick}>
|
||||
{this.listItemIcon(this.resources)}
|
||||
<ListItemText primary={friendlyTitle(type).plural} />
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
subMenu() {
|
||||
const { api, classes } = this.props;
|
||||
|
||||
return (
|
||||
<MenuList dense component="div" disablePadding>
|
||||
<MenuItem
|
||||
component={Link}
|
||||
to={this.to}
|
||||
className={classes.navMenuItem}
|
||||
selected={this.to === window.location.pathname}>
|
||||
<ListItemIcon className={classes.navResourceItem}>
|
||||
<SuccessRateDot />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary="All"
|
||||
className={classes.navResourceText} />
|
||||
</MenuItem>
|
||||
{
|
||||
_.map(this.resources, r => {
|
||||
let url = api.prefixLink(api.generateResourceURL(r));
|
||||
return (
|
||||
<MenuItem
|
||||
component={Link}
|
||||
to={url}
|
||||
key={url}
|
||||
className={classes.navMenuItem}
|
||||
selected={url === window.location.pathname}>
|
||||
<ListItemIcon className={classes.navResourceItem}>
|
||||
<SuccessRateDot sr={r.successRate} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
disableTypography
|
||||
primary={r.menuName}
|
||||
className={classes.navResourceText} />
|
||||
</MenuItem>
|
||||
);
|
||||
})
|
||||
}
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.menu()}
|
||||
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
|
||||
{this.subMenu()}
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withContext(withStyles(styles, { withTheme: true })(NavigationResource));
|
|
@ -0,0 +1,105 @@
|
|||
import {
|
||||
Collapse,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@material-ui/core';
|
||||
import { metricsPropType, processMultiResourceRollup } from './util/MetricUtils.jsx';
|
||||
|
||||
import ExpandLess from '@material-ui/icons/ExpandLess';
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore';
|
||||
import NavigationResource from './NavigationResource.jsx';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ViewListIcon from '@material-ui/icons/ViewList';
|
||||
import _ from 'lodash';
|
||||
import { withContext } from './util/AppContext.jsx';
|
||||
import withREST from './util/withREST.jsx';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
|
||||
const styles = () => ({
|
||||
navMenuItem: {
|
||||
paddingLeft: "24px",
|
||||
paddingRight: "24px",
|
||||
},
|
||||
});
|
||||
|
||||
class NavigationResourcesBase extends React.Component {
|
||||
static propTypes = {
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
data: PropTypes.arrayOf(metricsPropType.isRequired).isRequired,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {open: false};
|
||||
}
|
||||
|
||||
handleOnClick = () => {
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
menu() {
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
className={classes.navMenuItem}
|
||||
button
|
||||
onClick={this.handleOnClick}>
|
||||
<ListItemIcon><ViewListIcon /></ListItemIcon>
|
||||
<ListItemText inset primary="Resources" />
|
||||
{this.state.open ? <ExpandLess /> : <ExpandMore />}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
subMenu() {
|
||||
const { data } = this.props;
|
||||
|
||||
let allMetrics = {};
|
||||
let nsMetrics = {};
|
||||
if (_.has(data, '[0]')) {
|
||||
allMetrics = processMultiResourceRollup(data[0]);
|
||||
|
||||
if (_.has(data, '[1]')) {
|
||||
nsMetrics = processMultiResourceRollup(data[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<MenuList dense component="div" disablePadding>
|
||||
<NavigationResource type="authorities" />
|
||||
<NavigationResource type="deployments" metrics={allMetrics.deployment} />
|
||||
<NavigationResource type="namespaces" metrics={nsMetrics.namespace} />
|
||||
<NavigationResource type="pods" metrics={allMetrics.pod} />
|
||||
<NavigationResource type="replicationcontrollers" metrics={allMetrics.replicationcontroller} />
|
||||
</MenuList>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.menu()}
|
||||
<Collapse in={this.state.open} timeout="auto" unmountOnExit>
|
||||
{this.subMenu()}
|
||||
</Collapse>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withREST(
|
||||
withContext(withStyles(styles, { withTheme: true })(NavigationResourcesBase)),
|
||||
({api}) => [
|
||||
// TODO: modify "all" to also retrieve namespaces, also share fetch with parent component
|
||||
api.fetchMetrics(api.urlsForResource("all")),
|
||||
api.fetchMetrics(api.urlsForResource("namespace")),
|
||||
],
|
||||
{
|
||||
resetProps: ['resource'],
|
||||
},
|
||||
);
|
|
@ -0,0 +1,29 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { getSuccessRateClassification } from './MetricUtils.jsx';
|
||||
import { statusClassNames } from './theme.js';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = theme => statusClassNames(theme);
|
||||
|
||||
class SuccessRateDot extends React.Component {
|
||||
render() {
|
||||
const { sr, classes } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classNames("success-rate-dot", classes[getSuccessRateClassification(sr)])} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SuccessRateDot.propTypes = {
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
sr: PropTypes.number,
|
||||
};
|
||||
|
||||
SuccessRateDot.defaultProps = {
|
||||
sr: null
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(SuccessRateDot);
|
|
@ -1,24 +1,20 @@
|
|||
import Grid from '@material-ui/core/Grid';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import SuccessRateDot from "./SuccessRateDot.jsx";
|
||||
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;
|
||||
const { sr } = this.props;
|
||||
|
||||
return (
|
||||
<Grid container alignItems="center" spacing={8}>
|
||||
<Grid item>{metricToFormatter["SUCCESS_RATE"](sr)}</Grid>
|
||||
<Grid item>{_.isNil(sr) ? null :
|
||||
<div className={classNames("success-rate-dot", classes[getSuccessRateClassification(sr)])} />}
|
||||
<SuccessRateDot sr={sr} />
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
@ -26,7 +22,6 @@ class SuccessRateMiniChart extends React.Component {
|
|||
}
|
||||
|
||||
SuccessRateMiniChart.propTypes = {
|
||||
classes: PropTypes.shape({}).isRequired,
|
||||
sr: PropTypes.number,
|
||||
};
|
||||
|
||||
|
@ -34,4 +29,4 @@ SuccessRateMiniChart.defaultProps = {
|
|||
sr: null
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(SuccessRateMiniChart);
|
||||
export default SuccessRateMiniChart;
|
||||
|
|
Loading…
Reference in New Issue