import BaseTable from './BaseTable.jsx'; import CallToAction from './CallToAction.jsx'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import ErrorBanner from './ErrorBanner.jsx'; import Grid from '@material-ui/core/Grid'; import MeshedStatusTable from './MeshedStatusTable.jsx'; import Percentage from './util/Percentage.js'; import PropTypes from 'prop-types'; import React from 'react'; import Spinner from './util/Spinner.jsx'; import StatusTable from './StatusTable.jsx'; import Typography from '@material-ui/core/Typography'; import _ from 'lodash'; import { incompleteMeshMessage } from './util/CopyUtils.jsx'; import moment from 'moment'; import { withContext } from './util/AppContext.jsx'; const serviceMeshDetailsColumns = [ { title: "Name", key: "name", render: d => d.name }, { title: "Value", key: "value", isNumeric: true, render: d => d.value } ]; const getPodClassification = pod => { if (pod.status === "Running") { return "good"; } else if (pod.status === "Waiting") { return "default"; } else { return "poor"; } }; const componentsToDeployNames = { "Destination": "controller", "Grafana" : "grafana", "Prometheus": "prometheus", "Proxy API": "controller", "Public API": "controller", "Tap": "controller", "Web UI": "web" }; class ServiceMesh extends React.Component { static defaultProps = { productName: 'controller' } static propTypes = { api: PropTypes.shape({ cancelCurrentRequests: PropTypes.func.isRequired, PrefixedLink: PropTypes.func.isRequired, fetchMetrics: PropTypes.func.isRequired, getCurrentPromises: PropTypes.func.isRequired, setCurrentRequests: PropTypes.func.isRequired, urlsForResource: PropTypes.func.isRequired, }).isRequired, controllerNamespace: PropTypes.string.isRequired, productName: PropTypes.string, releaseVersion: PropTypes.string.isRequired, } constructor(props) { super(props); this.loadFromServer = this.loadFromServer.bind(this); this.handleApiError = this.handleApiError.bind(this); this.api = this.props.api; this.state = { pollingInterval: 2000, components: [], 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(); } getServiceMeshDetails() { return [ { key: 1, name: this.props.productName + " version", value: this.props.releaseVersion }, { key: 2, name: this.props.productName + " namespace", value: this.props.controllerNamespace }, { key: 3, name: "Control plane components", value: this.componentCount() }, { key: 4, name: "Data plane proxies", value: this.proxyCount() } ]; } getControllerComponentData(podData) { let podDataByDeploy = _.chain(podData.pods) .filter( "controlPlane") .groupBy("deployment") .mapKeys((pods, dep) => { return dep.split("/")[1]; }) .value(); return _.map(componentsToDeployNames, (deployName, component) => { return { name: component, pods: _.map(podDataByDeploy[deployName], p => { let uptimeSec = !p.uptime ? 0 : p.uptime.split(".")[0]; let uptime = moment.duration(parseInt(uptimeSec, 10) * 1000); return { name: p.name, value: getPodClassification(p), uptime: uptime.humanize(), uptimeSec }; }) }; }); } extractNsStatuses(nsData) { let podsByNs = _.get(nsData, ["ok", "statTables", 0, "podGroup", "rows"], []); let dataPlaneNamepaces = _.map(podsByNs, ns => { let meshedPods = parseInt(ns.meshedPodCount, 10); let totalPods = parseInt(ns.runningPodCount, 10); let failedPods = parseInt(ns.failedPodCount, 10); return { namespace: ns.resource.name, meshedPodsStr: ns.meshedPodCount + "/" + ns.runningPodCount, meshedPercent: new Percentage(meshedPods, totalPods), meshedPods, totalPods, failedPods, errors: ns.errorsByPod }; }); return _.compact(dataPlaneNamepaces); } loadFromServer() { if (this.state.pendingRequests) { return; // don't make more requests if the ones we sent haven't completed } this.setState({ pendingRequests: true }); this.api.setCurrentRequests([ this.api.fetchPods(this.props.controllerNamespace), this.api.fetchMetrics(this.api.urlsForResource("namespace")) ]); this.serverPromise = Promise.all(this.api.getCurrentPromises()) .then(([pods, nsStats]) => { this.setState({ components: this.getControllerComponentData(pods), nsStatuses: this.extractNsStatuses(nsStats), pendingRequests: false, loaded: true, error: null }); }) .catch(this.handleApiError); } handleApiError(e) { if (e.isCanceled) { return; } this.setState({ pendingRequests: false, loaded: true, error: e }); } componentCount() { return _.size(this.state.components); } proxyCount() { return _.sumBy(this.state.nsStatuses, d => { return d.namespace === this.props.controllerNamespace ? 0 : d.meshedPods; }); } renderControlPlaneDetails() { return ( Control plane Components {this.componentCount()} ); } renderServiceMeshDetails() { return ( Service mesh details d.key} /> ); } renderAddResourcesMessage() { let message = ""; let numUnadded = 0; if (_.isEmpty(this.state.nsStatuses)) { message = "No resources detected."; } else { let meshedCount = _.countBy(this.state.nsStatuses, pod => { return pod.meshedPercent.get() > 0; }); numUnadded = meshedCount["false"] || 0; message = numUnadded === 0 ? `All namespaces have a ${this.props.productName} install.` : `${numUnadded} ${numUnadded === 1 ? "namespace has" : "namespaces have"} no meshed resources.`; } return ( {message} { numUnadded > 0 ? incompleteMeshMessage() : null } ); } render() { return (
{ !this.state.error ? null : } { !this.state.loaded ? : (
{this.proxyCount() === 0 ? : null} {this.renderControlPlaneDetails()} {this.renderServiceMeshDetails()} {this.renderAddResourcesMessage()}
)}
); } } export default withContext(ServiceMesh);