import 'whatwg-fetch'; import { processMultiResourceRollup, processSingleResourceRollup } from './util/MetricUtils.jsx'; import Accordion from './util/Accordion.jsx'; import Divider from '@material-ui/core/Divider'; import ErrorBanner from './ErrorBanner.jsx'; import Grid from '@material-ui/core/Grid'; import Hidden from '@material-ui/core/Hidden'; import MetricsTable from './MetricsTable.jsx'; import PropTypes from 'prop-types'; import React from 'react'; import SimpleChip from './util/Chip.jsx'; import Spinner from './util/Spinner.jsx'; import Typography from '@material-ui/core/Typography'; import _find from 'lodash/find'; import _get from 'lodash/get'; import _has from 'lodash/has'; import _isEmpty from 'lodash/isEmpty'; import _isNil from 'lodash/isNil'; import { friendlyTitle } from './util/Utils.js'; import { withContext } from './util/AppContext.jsx'; import { withStyles } from '@material-ui/core/styles'; const styles = () => { return { grid: { width: '100%' } }; }; class NamespaceLanding extends React.Component { static propTypes = { api: PropTypes.shape({ cancelCurrentRequests: PropTypes.func.isRequired, fetchMetrics: PropTypes.func.isRequired, getCurrentPromises: PropTypes.func.isRequired, setCurrentRequests: PropTypes.func.isRequired, urlsForResource: PropTypes.func.isRequired, urlsForResourceNoStats: PropTypes.func.isRequired, }).isRequired, classes: PropTypes.shape({}).isRequired, controllerNamespace: PropTypes.string.isRequired } constructor(props) { super(props); this.api = this.props.api; this.handleApiError = this.handleApiError.bind(this); this.loadFromServer = this.loadFromServer.bind(this); this.state = this.getInitialState(); } getInitialState() { return { selectedNs: null, metricsByNs: {}, namespaces: [], pollingInterval: 2000, pendingRequests: false, loaded: false, error: null }; } componentDidMount() { this.loadFromServer(); this.timerId = window.setInterval(this.loadFromServer, this.state.pollingInterval); } componentWillUnmount() { window.clearInterval(this.timerId); this.api.cancelCurrentRequests(); } onNamespaceChange = ns => { this.setState({ selectedNs: ns }); } loadFromServer() { if (this.state.pendingRequests) { return; // don't make more requests if the ones we sent haven't completed } this.setState({ pendingRequests: true }); // TODO: make this one request let apiRequests = [ this.api.fetchMetrics(this.api.urlsForResourceNoStats("namespace")) ]; if (!_isEmpty(this.state.selectedNs)) { apiRequests = apiRequests.concat([ this.api.fetchMetrics(this.api.urlsForResource("all", this.state.selectedNs, true)) ]); } this.api.setCurrentRequests(apiRequests); Promise.all(this.api.getCurrentPromises()) .then(([allNs, metricsForNs]) => { let namespaces = processSingleResourceRollup(allNs); let metricsByNs = this.state.metricsByNs; if (!_isNil(this.state.selectedNs) && !_isNil(metricsForNs)) { metricsByNs[this.state.selectedNs] = processMultiResourceRollup(metricsForNs); } // by default, show the first non-linkerd meshed namesapce // if no other meshed namespaces are found, show the linkerd namespace let defaultOpenNs = _find(namespaces, ns => ns.added && ns.name !== this.props.controllerNamespace); defaultOpenNs = defaultOpenNs || _find(namespaces, ns => ns.name === this.props.controllerNamespace); this.setState({ namespaces, metricsByNs, defaultOpenNs, selectedNs: this.state.selectedNs || defaultOpenNs.name, pendingRequests: false, loaded: true, error: null }); }) .catch(this.handleApiError); } handleApiError = e => { if (e.isCanceled) { return; } this.setState({ pendingRequests: false, error: e }); } renderResourceSection(resource, metrics) { const classes = this.props.classes; if (_isEmpty(metrics)) { return null; } return ( ); } renderNamespaceSection(namespace) { const classes = this.props.classes; if (!_has(this.state.metricsByNs, namespace)) { return ; } let metrics = this.state.metricsByNs[namespace] || {}; let noMetrics = _isEmpty(metrics.pod); return ( Namespace: {namespace} {noMetrics ?
No resources detected.
: null}
{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)} { noMetrics ? null : }
); } renderAccordion() { let panelData = this.state.namespaces.map(ns => { let hr = ( {ns.name} {!ns.added ? null : } ); return { id: ns.name, header: hr, body: ns.name === this.state.selectedNs || ns.name === this.state.defaultOpenNs.name ? this.renderNamespaceSection(ns.name) : null }; }); return ( ); } render() { return (
{!this.state.error ? null : } {!this.state.loaded ? : this.renderAccordion()}
); } } export default withContext(withStyles(styles)(NamespaceLanding));