mirror of https://github.com/linkerd/linkerd2.git
214 lines
5.7 KiB
JavaScript
214 lines
5.7 KiB
JavaScript
import Button from '@material-ui/core/Button';
|
|
import CircularProgress from '@material-ui/core/CircularProgress';
|
|
import Dialog from '@material-ui/core/Dialog';
|
|
import DialogActions from '@material-ui/core/DialogActions';
|
|
import DialogContent from '@material-ui/core/DialogContent';
|
|
import DialogTitle from '@material-ui/core/DialogTitle';
|
|
import ErrorIcon from '@material-ui/icons/Error';
|
|
import Grid from '@material-ui/core/Grid';
|
|
import PropTypes from 'prop-types';
|
|
import React from 'react';
|
|
import Switch from '@material-ui/core/Switch';
|
|
import Tooltip from '@material-ui/core/Tooltip';
|
|
import Typography from '@material-ui/core/Typography';
|
|
import _each from 'lodash/each';
|
|
import _get from 'lodash/get';
|
|
import _isEmpty from 'lodash/isEmpty';
|
|
import _map from 'lodash/map';
|
|
import _reduce from 'lodash/reduce';
|
|
import _take from 'lodash/take';
|
|
import { friendlyTitle } from './util/Utils.js';
|
|
|
|
// max characters we display for error messages before truncating them
|
|
const maxErrorLength = 500;
|
|
|
|
class ErrorModal extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
truncateErrors: true,
|
|
open: false,
|
|
scroll: 'paper',
|
|
};
|
|
}
|
|
|
|
handleClickOpen = () => {
|
|
this.setState({ open: true });
|
|
};
|
|
|
|
handleClose = () => {
|
|
this.setState({ open: false });
|
|
};
|
|
|
|
toggleTruncateErrors = () => {
|
|
const { truncateErrors } = this.state;
|
|
this.setState({
|
|
truncateErrors: !truncateErrors,
|
|
});
|
|
}
|
|
|
|
processErrorData = errorsByPod => {
|
|
let shouldTruncate = false;
|
|
|
|
const byPodAndContainer = _map(errorsByPod, (podErrors, pod) => {
|
|
const byContainer = _reduce(podErrors.errors, (errors, err) => {
|
|
if (!_isEmpty(err.container)) {
|
|
const c = err.container;
|
|
if (_isEmpty(errors[c.container])) {
|
|
errors[c.container] = [];
|
|
}
|
|
|
|
const errMsg = c.message;
|
|
if (errMsg.length > maxErrorLength) {
|
|
shouldTruncate = true;
|
|
c.truncatedMessage = `${_take(errMsg, maxErrorLength).join('')}...`;
|
|
}
|
|
errors[c.container].push(c);
|
|
}
|
|
return errors;
|
|
}, {});
|
|
|
|
return {
|
|
pod,
|
|
byContainer,
|
|
};
|
|
});
|
|
|
|
return {
|
|
byPodAndContainer,
|
|
shouldTruncate,
|
|
};
|
|
}
|
|
|
|
renderContainerErrors = (pod, errorsByContainer) => {
|
|
const { truncateErrors } = this.state;
|
|
|
|
if (_isEmpty(errorsByContainer)) {
|
|
return 'No messages to display';
|
|
}
|
|
|
|
return _map(errorsByContainer, (errors, container) => (
|
|
<div key={`error-${container}`}>
|
|
<Grid
|
|
container
|
|
direction="row"
|
|
justify="space-between"
|
|
alignItems="center">
|
|
<Grid item>
|
|
<Typography variant="subtitle1" gutterBottom>{container}</Typography>
|
|
</Grid>
|
|
<Grid item>
|
|
<Typography variant="subtitle1" gutterBottom align="right">
|
|
{_get(errors, [0, 'image'])}
|
|
</Typography>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<div>
|
|
{
|
|
_map(errors, (er, i) => {
|
|
if (_isEmpty(er.message)) {
|
|
return null;
|
|
}
|
|
|
|
const message = !truncateErrors ? er.message :
|
|
er.truncatedMessage || er.message;
|
|
|
|
return (
|
|
<React.Fragment key={`error-msg-long-${i}`}>
|
|
<code>{!er.reason ? null : `${er.reason}: `} {message}</code><br /><br />
|
|
</React.Fragment>
|
|
);
|
|
})
|
|
}
|
|
</div>
|
|
</div>
|
|
));
|
|
};
|
|
|
|
renderPodErrors = errors => {
|
|
return _map(errors, err => {
|
|
return (
|
|
<div key={err.pod}>
|
|
<Typography variant="h6" gutterBottom>{err.pod}</Typography>
|
|
{this.renderContainerErrors(err.pod, err.byContainer)}
|
|
</div>
|
|
);
|
|
});
|
|
}
|
|
|
|
renderStatusIcon = errors => {
|
|
let showInit = true;
|
|
|
|
_each(errors.byPodAndContainer, container => {
|
|
_each(container.byContainer, con => {
|
|
if (con[0].reason !== 'PodInitializing') {
|
|
showInit = false;
|
|
}
|
|
});
|
|
});
|
|
|
|
if (showInit) {
|
|
return (
|
|
<Tooltip title="Pods are initializing"><CircularProgress size={20} thickness={4} /></Tooltip>
|
|
);
|
|
} else {
|
|
return (
|
|
<ErrorIcon color="error" fontSize="small" onClick={this.handleClickOpen} />
|
|
);
|
|
}
|
|
}
|
|
|
|
render() {
|
|
const { open, scroll, truncateErrors } = this.state;
|
|
const { resourceType, resourceName, errors } = this.props;
|
|
const errorData = this.processErrorData(errors);
|
|
|
|
return (
|
|
<React.Fragment>
|
|
{this.renderStatusIcon(errors)}
|
|
<Dialog
|
|
open={open}
|
|
onClose={this.handleClose}
|
|
scroll={scroll}
|
|
aria-labelledby="scroll-dialog-title">
|
|
|
|
<DialogTitle id="scroll-dialog-title">Errors in {friendlyTitle(resourceType).singular} {resourceName}</DialogTitle>
|
|
|
|
<DialogContent>
|
|
{
|
|
!errorData.shouldTruncate ? null :
|
|
<React.Fragment>
|
|
Some of these error messages are very long. Show full error text?
|
|
<Switch
|
|
checked={!truncateErrors}
|
|
onChange={this.toggleTruncateErrors}
|
|
color="primary" />
|
|
</React.Fragment>
|
|
}
|
|
{this.renderPodErrors(errorData.byPodAndContainer)}
|
|
|
|
|
|
</DialogContent>
|
|
|
|
<DialogActions>
|
|
<Button onClick={this.handleClose} color="primary">Close</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</React.Fragment>
|
|
);
|
|
}
|
|
}
|
|
|
|
ErrorModal.propTypes = {
|
|
errors: PropTypes.shape({}),
|
|
resourceName: PropTypes.string.isRequired,
|
|
resourceType: PropTypes.string.isRequired,
|
|
};
|
|
|
|
ErrorModal.defaultProps = {
|
|
errors: {},
|
|
};
|
|
|
|
export default ErrorModal;
|