import _ from 'lodash'; import CallToAction from './CallToAction.jsx'; import ConduitSpinner from "./ConduitSpinner.jsx"; import DeploymentSummary from './DeploymentSummary.jsx'; import ErrorBanner from './ErrorBanner.jsx'; import { incompleteMeshMessage } from './util/CopyUtils.jsx'; import Metric from './Metric.jsx'; import PageHeader from './PageHeader.jsx'; import React from 'react'; import { rowGutter } from './util/Utils.js'; import StatusTable from './StatusTable.jsx'; import { Col, Row, Table } from 'antd'; import { getComponentPods, getPodsByDeployment, processRollupMetrics, processTimeseriesMetrics } from './util/MetricUtils.js'; import './../../css/service-mesh.css'; const serviceMeshDetailsColumns = [ { title: "Name", dataIndex: "name", key: "name" }, { title: "Value", dataIndex: "value", key: "value", className: "numeric" } ]; const componentNames = { "prometheus": "Prometheus", "destination": "Destination", "proxy-api": "Proxy API", "public-api": "Public API", "tap": "Tap", "telemetry": "Telemetry", "web": "Web UI" }; const componentGraphTitles = { "telemetry": "Telemetry requests" }; const componentDeploys = { "prometheus": "prometheus", "destination": "controller", "proxy-api": "controller", "public-api": "controller", "tap": "controller", "telemetry": "controller", "web": "web" }; const componentsToGraph = ["proxy-api", "telemetry", "public-api"]; const noData = { timeseries: { requestRate: [], successRate: [] } }; export default class ServiceMesh extends React.Component { 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, metrics: [], deploys: [], components: [], lastUpdated: 0, pendingRequests: false, loaded: false, error: '' }; } componentDidMount() { this.loadFromServer(); this.timerId = window.setInterval(this.loadFromServer, this.state.pollingInterval); } componentWillUnmount() { window.clearInterval(this.timerId); this.api.cancelCurrentRequests(); } loadFromServer() { if (this.state.pendingRequests) { return; // don't make more requests if the ones we sent haven't completed } this.setState({ pendingRequests: true }); let rollupPath = `/api/metrics?aggregation=mesh`; let timeseriesPath = `${rollupPath}×eries=true`; this.api.setCurrentRequests([ this.api.fetchMetrics(rollupPath), this.api.fetchMetrics(timeseriesPath), this.api.fetchPods() ]); this.serverPromise = Promise.all(this.api.getCurrentPromises()) .then(([metrics, ts, pods]) => { let m = processRollupMetrics(metrics.metrics, "component"); let tsByComponent = processTimeseriesMetrics(ts.metrics, "component"); let podsByDeploy = getPodsByDeployment(pods.pods); let controlPlanePods = this.processComponents(pods.pods); this.setState({ metrics: m, timeseriesByComponent: tsByComponent, deploys: podsByDeploy, components: controlPlanePods, lastUpdated: Date.now(), pendingRequests: false, loaded: true, error: '' }); }) .catch(this.handleApiError); } handleApiError(e) { if (e.isCanceled) { return; } this.setState({ pendingRequests: false, error: `Error getting data from server: ${e.message}` }); } addedDeploymentCount() { return _.size(_.filter(this.state.deploys, d => { return _.every(d.pods, ["added", true]); })); } unaddedDeploymentCount() { return this.deployCount() - this.addedDeploymentCount(); } proxyCount() { return _.sum(_.map(this.state.deploys, d => { return _.size(_.filter(d.pods, ["value", "good"])); })); } componentCount() { return _.size(this.state.components); } deployCount() { return _.size(this.state.deploys); } getServiceMeshDetails() { return [ { key: 1, name: "Conduit version", value: this.props.releaseVersion }, { key: 2, name: "Conduit namespace", value: this.props.controllerNamespace }, { key: 3, name: "Control plane components", value: this.componentCount() }, { key: 4, name: "Added deployments", value: this.addedDeploymentCount() }, { key: 5, name: "Unadded deployments", value: this.unaddedDeploymentCount() }, { key: 6, name: "Data plane proxies", value: this.proxyCount() } ]; } processComponents(pods) { let podIndex = _(pods) .filter(p => p.controlPlane) .groupBy(p => _.last(_.split(p.deployment, "/"))) .value(); return _(componentNames) .map((name, id) => { let componentPods = _.get(podIndex, _.get(componentDeploys, id), []); return { name: name, pods: getComponentPods(componentPods) }; }) .sortBy("name") .value(); } renderControllerHealth() { return (
Control plane status
{ _.map(componentsToGraph, meshComponent => { let data = _.cloneDeep(_.find(this.state.metrics, ["name", meshComponent]) || noData); data.id = meshComponent; data.name = componentGraphTitles[meshComponent] || componentNames[meshComponent]; return ( ); }) }
); } renderControlPlaneDetails() { return (
Control plane
); } renderDataPlaneDetails() { return (
Data plane
); } renderServiceMeshDetails() { return (
Service mesh details
); } renderAddDeploymentsMessage() { if (this.deployCount() === 0) { return (
No deployments detected. {incompleteMeshMessage()}
); } else { switch (this.unaddedDeploymentCount()) { case 0: return (
All deployments have been added to the service mesh.
); case 1: return (
1 deployment has not been added to the service mesh. {incompleteMeshMessage()}
); default: return (
{this.unaddedDeploymentCount()} deployments have not been added to the service mesh. {incompleteMeshMessage()}
); } } } renderControlPlane() { return ( {this.renderControlPlaneDetails()}{this.renderServiceMeshDetails()} ); } renderDataPlane() { return ( {this.renderDataPlaneDetails()}{this.renderAddDeploymentsMessage()} ); } renderOverview() { if (this.proxyCount() === 0) { return ; } else { return this.renderControllerHealth(); } } render() { return (
{ !this.state.error ? null : } { !this.state.loaded ? :
{this.renderOverview()} {this.renderControlPlane()} {this.renderDataPlane()}
}
); } }