linkerd2/web/app/js/components/Navigation.jsx

331 lines
11 KiB
JavaScript

import {
AppBar,
Collapse,
Divider,
Drawer,
IconButton,
ListItem,
ListItemIcon,
ListItemText,
MenuItem,
MenuList,
Toolbar,
Typography
} from '@material-ui/core';
import { githubIcon, linkerdWordLogo, slackIcon } from './util/SvgWrappers.jsx';
import BreadcrumbHeader from './BreadcrumbHeader.jsx';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import CloudQueueIcon from '@material-ui/icons/CloudQueue';
import EmailIcon from '@material-ui/icons/Email';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import HelpIcon from '@material-ui/icons/HelpOutline';
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 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';
import { withStyles } from '@material-ui/core/styles';
const drawerWidth = 250;
const styles = theme => ({
root: {
flexGrow: 1,
height: "100vh",
width: "100%",
zIndex: 1,
overflowX: 'scroll',
position: 'relative',
display: 'flex',
},
appBar: {
color: 'white',
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(['width', 'margin'], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
menuButton: {
marginLeft: 12,
marginRight: 36,
},
hide: {
display: 'none',
},
drawerPaper: {
position: 'relative',
whiteSpace: 'nowrap',
width: drawerWidth,
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen,
}),
},
drawerPaperClose: {
overflowX: 'hidden',
transition: theme.transitions.create('width', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
width: theme.spacing.unit * 7,
[theme.breakpoints.up('sm')]: {
width: theme.spacing.unit * 9,
},
},
toolbar: {
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-end',
padding: '0 8px',
...theme.mixins.toolbar,
},
content: {
flexGrow: 1,
width: `calc(100% - ${drawerWidth}px)`,
backgroundColor: theme.palette.background.default,
padding: theme.spacing.unit * 3,
},
linkerdLogoContainer: {
backgroundColor: theme.palette.primary.main,
},
linkerdNavLogo: {
minWidth: "180px",
},
navMenuItem: {
paddingLeft: "24px",
paddingRight: "24px",
},
});
class NavigationBase extends React.Component {
constructor(props) {
super(props);
this.api = this.props.api;
this.handleApiError = this.handleApiError.bind(this);
this.state = this.getInitialState();
}
getInitialState() {
return {
drawerOpen: true,
resourceMenuOpen: false,
helpMenuOpen: false,
latestVersion: '',
isLatest: true,
namespaceFilter: "all"
};
}
componentDidMount() {
this.fetchVersion();
}
fetchVersion() {
let versionUrl = `https://versioncheck.linkerd.io/version.json?version=${this.props.releaseVersion}&uuid=${this.props.uuid}&source=web`;
this.versionPromise = fetch(versionUrl, { credentials: 'include' })
.then(rsp => rsp.json())
.then(versionRsp => {
let latestVersion;
let parts = this.props.releaseVersion.split("-", 2);
if (parts.length === 2) {
latestVersion = versionRsp[parts[0]];
}
this.setState({
latestVersion,
isLatest: latestVersion === this.props.releaseVersion
});
}).catch(this.handleApiError);
}
handleApiError(e) {
this.setState({
error: e
});
}
handleDrawerOpen = () => {
this.setState({ drawerOpen: true });
};
handleDrawerClose = () => {
this.setState({ drawerOpen: false });
};
handleResourceMenuClick = () => {
this.setState(state => ({ resourceMenuOpen: !state.resourceMenuOpen }));
};
handleHelpMenuClick = () => {
this.setState(state => ({ helpMenuOpen: !state.helpMenuOpen }));
}
menuItem(path, title, icon) {
const { classes, api } = this.props;
let normalizedPath = this.props.location.pathname.replace(this.props.pathPrefix, "");
let isCurrentPage = path => path === normalizedPath;
return (
<MenuItem
component={Link}
to={api.prefixLink(path)}
className={classes.navMenuItem}
selected={isCurrentPage(path)}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={title} />
</MenuItem>
);
}
render() {
const { classes, ChildComponent } = this.props;
return (
<div className={classes.root}>
<AppBar
position="absolute"
className={classNames(classes.appBar, {[classes.appBarShift]: this.state.drawerOpen} )}>
<Toolbar disableGutters={!this.state.drawerOpen}>
<IconButton
color="inherit"
aria-label="Open drawer"
onClick={this.handleDrawerOpen}
className={classNames(classes.menuButton, {[classes.hide]: this.state.drawerOpen} )}>
<MenuIcon />
</IconButton>
<Typography variant="h6" color="inherit" noWrap>
<BreadcrumbHeader {...this.props} />
</Typography>
</Toolbar>
</AppBar>
<Drawer
variant="permanent"
classes={{
paper: classNames(classes.drawerPaper, {[classes.drawerPaperClose]: !this.state.drawerOpen} ),
}}
open={this.state.drawerOpen}>
<div className={classNames(classes.linkerdLogoContainer, classes.toolbar)}>
<div className={classes.linkerdNavLogo}>
{linkerdWordLogo}
</div>
<IconButton className="drawer-toggle-btn" onClick={this.handleDrawerClose}>
<ChevronLeftIcon />
</IconButton>
</div>
<Divider />
<MenuList>
{ this.menuItem("/overview", "Overview", <HomeIcon />) }
{ 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>
</MenuList>
<Divider />
<MenuList>
<ListItem component="a" href="https://linkerd.io/2/overview/" target="_blank">
<ListItemIcon><LibraryBooksIcon /></ListItemIcon>
<ListItemText primary="Documentation" />
</ListItem>
<MenuItem
className={classes.navMenuItem}
button
onClick={this.handleHelpMenuClick}>
<ListItemIcon><HelpIcon /></ListItemIcon>
<ListItemText inset primary="Help" />
{this.state.helpMenuOpen ? <ExpandLess /> : <ExpandMore />}
</MenuItem>
<Collapse in={this.state.helpMenuOpen} timeout="auto" unmountOnExit>
<MenuList dense component="div" disablePadding>
<ListItem component="a" href="https://lists.cncf.io/g/cncf-linkerd-users" target="_blank">
<ListItemIcon><EmailIcon /></ListItemIcon>
<ListItemText primary="Join the mailing list" />
</ListItem>
<ListItem component="a" href="https://slack.linkerd.io" target="_blank">
<ListItemIcon>{slackIcon}</ListItemIcon>
<ListItemText primary="Join us on slack" />
</ListItem>
<ListItem component="a" href="https://github.com/linkerd/linkerd2/issues/new/choose" target="_blank">
<ListItemIcon>{githubIcon}</ListItemIcon>
<ListItemText primary="File an issue" />
</ListItem>
</MenuList>
</Collapse>
</MenuList>
{
!this.state.drawerOpen ? null : <Version
isLatest={this.state.isLatest}
latestVersion={this.state.latestVersion}
releaseVersion={this.props.releaseVersion}
error={this.state.error}
uuid={this.props.uuid} />
}
</Drawer>
<main className={classes.content}>
<div className={classes.toolbar} />
<div className="main-content"><ChildComponent {...this.props} /></div>
</main>
</div>
);
}
}
NavigationBase.propTypes = {
api: PropTypes.shape({
PrefixedLink: PropTypes.func.isRequired,
}).isRequired,
ChildComponent: PropTypes.func.isRequired,
classes: PropTypes.shape({}).isRequired,
location: ReactRouterPropTypes.location.isRequired,
pathPrefix: PropTypes.string.isRequired,
releaseVersion: PropTypes.string.isRequired,
theme: PropTypes.shape({}).isRequired,
uuid: PropTypes.string.isRequired,
};
export default withContext(withStyles(styles, { withTheme: true })(NavigationBase));