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

209 lines
5.9 KiB
JavaScript

import 'whatwg-fetch';
import { handlePageVisibility, withPageVisibility } from './util/PageVisibility.jsx';
import ErrorBanner from './ErrorBanner.jsx';
import MetricsTable from './MetricsTable.jsx';
import NetworkGraph from './NetworkGraph.jsx';
import PropTypes from 'prop-types';
import React from 'react';
import Spinner from './util/Spinner.jsx';
import _filter from 'lodash/filter';
import _get from 'lodash/get';
import _isEmpty from 'lodash/isEmpty';
import _isEqual from 'lodash/isEqual';
import { friendlyTitle } from './util/Utils.js';
import { processMultiResourceRollup } from './util/MetricUtils.jsx';
import { withContext } from './util/AppContext.jsx';
class Namespaces extends React.Component {
constructor(props) {
super(props);
this.api = props.api;
this.handleApiError = this.handleApiError.bind(this);
this.loadFromServer = this.loadFromServer.bind(this);
this.state = this.getInitialState(props.match.params);
}
getInitialState(params) {
const ns = _get(params, 'namespace', 'default');
return {
ns,
pollingInterval: 2000,
metrics: {},
pendingRequests: false,
loaded: false,
error: null,
};
}
componentDidMount() {
this.startServerPolling();
this.checkNamespaceMatch();
}
componentDidUpdate(prevProps) {
const { match, isPageVisible } = this.props;
const { params } = match;
if (!_isEqual(prevProps.match.params.namespace, params.namespace)) {
// React won't unmount this component when switching resource pages so we need to clear state
this.api.cancelCurrentRequests();
this.resetState(params);
}
handlePageVisibility({
prevVisibilityState: prevProps.isPageVisible,
currentVisibilityState: isPageVisible,
onVisible: () => this.startServerPolling(),
onHidden: () => this.stopServerPolling(),
});
}
resetState(params) {
this.setState(this.getInitialState(params));
}
componentWillUnmount() {
this.stopServerPolling();
}
startServerPolling() {
const { pollingInterval } = this.state;
this.loadFromServer();
this.timerId = window.setInterval(this.loadFromServer, pollingInterval);
}
stopServerPolling() {
window.clearInterval(this.timerId);
this.api.cancelCurrentRequests();
this.setState({ pendingRequests: false });
}
loadFromServer() {
const { pendingRequests, ns } = this.state;
if (pendingRequests) {
return; // don't make more requests if the ones we sent haven't completed
}
this.setState({ pendingRequests: true });
this.api.setCurrentRequests([this.api.fetchMetrics(this.api.urlsForResource('all', ns, true))]);
Promise.all(this.api.getCurrentPromises())
.then(([allRollup]) => {
const metrics = processMultiResourceRollup(allRollup, 'all');
this.setState({
metrics,
loaded: true,
pendingRequests: false,
error: null,
});
})
.catch(this.handleApiError);
}
handleApiError = e => {
if (e.isCanceled) {
return;
}
this.setState({
pendingRequests: false,
error: e,
});
}
checkNamespaceMatch = () => {
const { ns } = this.state;
const { selectedNamespace, updateNamespaceInContext } = this.props;
if (ns !== selectedNamespace) {
updateNamespaceInContext(ns);
}
}
renderResourceSection = (resource, metrics) => {
if (_isEmpty(metrics)) {
return null;
}
return (
<div className="page-section">
<MetricsTable
title={friendlyTitle(resource).plural}
resource={resource}
metrics={metrics}
showNamespaceColumn={false} />
</div>
);
}
render() {
const { metrics, ns, loaded, error } = this.state;
const noMetrics = _isEmpty(metrics.pod);
const deploymentsWithMetrics = _filter(metrics.deployment, d => d.requestRate > 0);
return (
<div className="page-content">
{!error ? null : <ErrorBanner message={error} />}
{!loaded ? <Spinner /> : (
<div>
{noMetrics ? <div>No resources detected.</div> : null}
{
_isEmpty(deploymentsWithMetrics) ? null :
<NetworkGraph namespace={ns} deployments={metrics.deployment} />
}
{this.renderResourceSection('deployment', metrics.deployment)}
{this.renderResourceSection('daemonset', metrics.daemonset)}
{this.renderResourceSection('pod', metrics.pod)}
{this.renderResourceSection('replicationcontroller', metrics.replicationcontroller)}
{this.renderResourceSection('statefulset', metrics.statefulset)}
{this.renderResourceSection('job', metrics.job)}
{this.renderResourceSection('trafficsplit', metrics.trafficsplit)}
{this.renderResourceSection('cronjob', metrics.cronjob)}
{this.renderResourceSection('replicaset', metrics.replicaset)}
{
noMetrics ? null :
<div className="page-section">
<MetricsTable
title="TCP"
resource="pod"
metrics={metrics.pod}
isTcpTable />
</div>
}
</div>
)}
</div>
);
}
}
Namespaces.propTypes = {
api: PropTypes.shape({
cancelCurrentRequests: PropTypes.func.isRequired,
fetchMetrics: PropTypes.func.isRequired,
getCurrentPromises: PropTypes.func.isRequired,
setCurrentRequests: PropTypes.func.isRequired,
urlsForResource: PropTypes.func.isRequired,
}).isRequired,
isPageVisible: PropTypes.bool.isRequired,
match: PropTypes.shape({
params: PropTypes.shape({
namespace: PropTypes.string,
}),
}),
selectedNamespace: PropTypes.string.isRequired,
updateNamespaceInContext: PropTypes.func.isRequired,
};
Namespaces.defaultProps = {
match: {
params: {
namespace: 'default',
},
},
};
export default withPageVisibility(withContext(Namespaces));