github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/cluster/containers/nodeOverview/index.tsx (about)

     1  // Copyright 2018 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  import _ from "lodash";
    12  import React from "react";
    13  import { Helmet } from "react-helmet";
    14  import { connect } from "react-redux";
    15  import { Link, RouteComponentProps, withRouter } from "react-router-dom";
    16  import { createSelector } from "reselect";
    17  import { refreshLiveness, refreshNodes } from "src/redux/apiReducers";
    18  import { livenessNomenclature, LivenessStatus, NodesSummary, nodesSummarySelector, selectNodesSummaryValid } from "src/redux/nodes";
    19  import { AdminUIState } from "src/redux/state";
    20  import { nodeIDAttr } from "src/util/constants";
    21  import { LongToMoment } from "src/util/convert";
    22  import { Bytes, DATE_FORMAT, Percentage } from "src/util/format";
    23  import { INodeStatus, MetricConstants, StatusMetrics } from "src/util/proto";
    24  import { getMatchParamByName } from "src/util/query";
    25  import { SummaryBar, SummaryLabel, SummaryValue } from "src/views/shared/components/summaryBar";
    26  import { Button, BackIcon } from "src/components/button";
    27  import "./nodeOverview.styl";
    28  
    29  /**
    30   * TableRow is a small stateless component that renders a single row in the node
    31   * overview table. Each row renders a store metrics value, comparing the value
    32   * across the different stores on the node (along with a total value for the
    33   * node itself).
    34   */
    35  function TableRow(props: { data: INodeStatus, title: string, valueFn: (s: StatusMetrics) => React.ReactNode }) {
    36    return <tr className="table__row table__row--body">
    37      <td className="table__cell">{ props.title }</td>
    38      <td className="table__cell">{ props.valueFn(props.data.metrics) }</td>
    39      {
    40        _.map(props.data.store_statuses, (ss) => {
    41          return <td key={ss.desc.store_id} className="table__cell">{ props.valueFn(ss.metrics) }</td>;
    42        })
    43      }
    44      <td className="table__cell table__cell--filler" />
    45    </tr>;
    46  }
    47  
    48  interface NodeOverviewProps extends RouteComponentProps {
    49    node: INodeStatus;
    50    nodesSummary: NodesSummary;
    51    refreshNodes: typeof refreshNodes;
    52    refreshLiveness: typeof refreshLiveness;
    53    // True if current status results are still valid. Needed so that this
    54    // component refreshes status query when it becomes invalid.
    55    nodesSummaryValid: boolean;
    56  }
    57  
    58  /**
    59   * Renders the Node Overview page.
    60   */
    61  export class NodeOverview extends React.Component<NodeOverviewProps, {}> {
    62    componentDidMount() {
    63      // Refresh nodes status query when mounting.
    64      this.props.refreshNodes();
    65      this.props.refreshLiveness();
    66    }
    67  
    68    componentDidUpdate() {
    69      // Refresh nodes status query when props are received; this will immediately
    70      // trigger a new request if previous results are invalidated.
    71      this.props.refreshNodes();
    72      this.props.refreshLiveness();
    73    }
    74  
    75    prevPage = () => this.props.history.goBack();
    76  
    77    render() {
    78      const { node, nodesSummary } = this.props;
    79      if (!node) {
    80        return (
    81          <div className="section">
    82            <h1 className="base-heading">Loading cluster status...</h1>
    83          </div>
    84        );
    85      }
    86  
    87      const liveness = nodesSummary.livenessStatusByNodeID[node.desc.node_id] || LivenessStatus.LIVE;
    88      const livenessString = livenessNomenclature(liveness);
    89  
    90      return (
    91        <div>
    92          <Helmet title={`${nodesSummary.nodeDisplayNameByID[node.desc.node_id]} | Nodes`} />
    93          <div className="section section--heading">
    94            <Button
    95              onClick={this.prevPage}
    96              type="flat"
    97              size="small"
    98              className="crl-button--link-to"
    99              icon={BackIcon}
   100              iconPosition="left"
   101            >
   102              Overview
   103            </Button>
   104            <h2 className="base-heading">{`Node ${node.desc.node_id} / ${node.desc.address.address_field}`}</h2>
   105          </div>
   106          <section className="section l-columns">
   107            <div className="l-columns__left">
   108              <table className="table">
   109                <thead>
   110                  <tr className="table__row table__row--header">
   111                    <th className="table__cell" />
   112                    <th className="table__cell">{`Node ${node.desc.node_id}`}</th>
   113                    {
   114                      _.map(node.store_statuses, (ss) => {
   115                        const storeId = ss.desc.store_id;
   116                        return <th key={storeId} className="table__cell">{`Store ${storeId}`}</th>;
   117                      })
   118                    }
   119                    <th className="table__cell table__cell--filler" />
   120                  </tr>
   121                </thead>
   122                <tbody>
   123                  <TableRow data={node}
   124                            title="Live Bytes"
   125                            valueFn={(metrics) => Bytes(metrics[MetricConstants.liveBytes])} />
   126                  <TableRow data={node}
   127                            title="Key Bytes"
   128                            valueFn={(metrics) => Bytes(metrics[MetricConstants.keyBytes])} />
   129                  <TableRow data={node}
   130                            title="Value Bytes"
   131                            valueFn={(metrics) => Bytes(metrics[MetricConstants.valBytes])} />
   132                  <TableRow data={node}
   133                            title="Intent Bytes"
   134                            valueFn={(metrics) => Bytes(metrics[MetricConstants.intentBytes])} />
   135                  <TableRow data={node}
   136                            title="Sys Bytes"
   137                            valueFn={(metrics) => Bytes(metrics[MetricConstants.sysBytes])} />
   138                  <TableRow data={node}
   139                            title="GC Bytes Age"
   140                            valueFn={(metrics) => metrics[MetricConstants.gcBytesAge].toString()} />
   141                  <TableRow data={node}
   142                            title="Total Replicas"
   143                            valueFn={(metrics) => metrics[MetricConstants.replicas].toString()} />
   144                  <TableRow data={node}
   145                            title="Raft Leaders"
   146                            valueFn={(metrics) => metrics[MetricConstants.raftLeaders].toString()} />
   147                  <TableRow data={node}
   148                            title="Total Ranges"
   149                            valueFn={(metrics) => metrics[MetricConstants.ranges]} />
   150                  <TableRow data={node}
   151                            title="Unavailable %"
   152                            valueFn={(metrics) => Percentage(metrics[MetricConstants.unavailableRanges], metrics[MetricConstants.ranges])} />
   153                  <TableRow data={node}
   154                            title="Under Replicated %"
   155                            valueFn={(metrics) => Percentage(metrics[MetricConstants.underReplicatedRanges], metrics[MetricConstants.ranges])} />
   156                  <TableRow data={node}
   157                            title="Used Capacity"
   158                            valueFn={(metrics) => Bytes(metrics[MetricConstants.usedCapacity])} />
   159                  <TableRow data={node}
   160                            title="Available Capacity"
   161                            valueFn={(metrics) => Bytes(metrics[MetricConstants.availableCapacity])} />
   162                  <TableRow data={node}
   163                            title="Total Capacity"
   164                            valueFn={(metrics) => Bytes(metrics[MetricConstants.capacity])} />
   165                </tbody>
   166              </table>
   167            </div>
   168            <div className="l-columns__right">
   169              <SummaryBar>
   170                <SummaryLabel>Node Summary</SummaryLabel>
   171                <SummaryValue
   172                  title="Health"
   173                  value={livenessString}
   174                  classModifier={livenessString}
   175                />
   176                <SummaryValue title="Last Update" value={LongToMoment(node.updated_at).format(DATE_FORMAT)} />
   177                <SummaryValue title="Build" value={node.build_info.tag} />
   178                <SummaryValue
   179                  title="Logs"
   180                  value={<Link to={`/node/${node.desc.node_id}/logs`}>View Logs</Link>}
   181                  classModifier="link"
   182                />
   183              </SummaryBar>
   184            </div>
   185          </section>
   186        </div>
   187      );
   188    }
   189  }
   190  
   191  export const currentNode = createSelector(
   192    (state: AdminUIState, _props: RouteComponentProps): INodeStatus[] => state.cachedData.nodes.data,
   193    (_state: AdminUIState, props: RouteComponentProps): number => parseInt(getMatchParamByName(props.match, nodeIDAttr), 10),
   194    (nodes, id) => {
   195      if (!nodes || !id) {
   196        return undefined;
   197      }
   198      return _.find(nodes, (ns) => ns.desc.node_id === id);
   199    });
   200  
   201  export default withRouter(connect(
   202    (state: AdminUIState, ownProps: RouteComponentProps) => {
   203      return {
   204        node: currentNode(state, ownProps),
   205        nodesSummary: nodesSummarySelector(state),
   206        nodesSummaryValid: selectNodesSummaryValid(state),
   207      };
   208    },
   209    {
   210      refreshNodes,
   211      refreshLiveness,
   212    },
   213  )(NodeOverview));