github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/cluster/containers/nodeLogs/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 { RouteComponentProps, withRouter } from "react-router-dom";
    16  
    17  import * as protos from "src/js/protos";
    18  import { INodeStatus } from "src/util/proto";
    19  import { nodeIDAttr, REMOTE_DEBUGGING_ERROR_TEXT } from "src/util/constants";
    20  import { LogEntriesResponseMessage } from "src/util/api";
    21  import { LongToMoment } from "src/util/convert";
    22  import { SortableTable } from "src/views/shared/components/sortabletable";
    23  import { AdminUIState } from "src/redux/state";
    24  import { refreshLogs, refreshNodes } from "src/redux/apiReducers";
    25  import { currentNode } from "src/views/cluster/containers/nodeOverview";
    26  import { CachedDataReducerState } from "src/redux/cachedDataReducer";
    27  import { getDisplayName } from "src/redux/nodes";
    28  import Loading from "src/views/shared/components/loading";
    29  import { getMatchParamByName } from "src/util/query";
    30  import "./logs.styl";
    31  
    32  interface LogProps {
    33    logs: CachedDataReducerState<LogEntriesResponseMessage>;
    34    currentNode: INodeStatus;
    35    refreshLogs: typeof refreshLogs;
    36    refreshNodes: typeof refreshNodes;
    37  }
    38  
    39  /**
    40   * Renders the main content of the logs page.
    41   */
    42  export class Logs extends React.Component<LogProps & RouteComponentProps, {}> {
    43    componentDidMount() {
    44      const nodeId = getMatchParamByName(this.props.match, nodeIDAttr);
    45      this.props.refreshNodes();
    46      this.props.refreshLogs(new protos.cockroach.server.serverpb.LogsRequest({ node_id: nodeId }));
    47    }
    48  
    49    renderContent = () => {
    50      const logEntries = _.sortBy(this.props.logs.data.entries, (e) => e.time);
    51      const columns = [
    52        {
    53          title: "Time",
    54          cell: (index: number) => LongToMoment(logEntries[index].time).format("YYYY-MM-DD HH:mm:ss"),
    55        },
    56        {
    57          title: "Severity",
    58          cell: (index: number) => protos.cockroach.util.log.Severity[logEntries[index].severity],
    59        },
    60        {
    61          title: "Message",
    62          cell: (index: number) => (
    63            <pre className="sort-table__unbounded-column logs-table__message">
    64                { logEntries[index].message }
    65              </pre>
    66          ),
    67        },
    68        {
    69          title: "File:Line",
    70          cell: (index: number) => `${logEntries[index].file}:${logEntries[index].line}`,
    71        },
    72      ];
    73      return (
    74        <SortableTable
    75          count={logEntries.length}
    76          columns={columns}
    77          className="logs-table"
    78        />
    79      );
    80    }
    81  
    82    render() {
    83      const nodeAddress = this.props.currentNode
    84        ? this.props.currentNode.desc.address.address_field
    85        : null;
    86      const nodeId = getMatchParamByName(this.props.match, nodeIDAttr);
    87      const title = this.props.currentNode
    88        ? `Logs | ${getDisplayName(this.props.currentNode)} | Nodes`
    89        : `Logs | Node ${nodeId} | Nodes`;
    90  
    91      // TODO(couchand): This is a really myopic way to check for this particular
    92      // case, but making major changes to the CachedDataReducer or util.api seems
    93      // fraught at this point.  We should revisit this soon.
    94      if (this.props.logs.lastError && this.props.logs.lastError.message === "Forbidden") {
    95        return (
    96          <div>
    97            <Helmet title={ title } />
    98            <div className="section section--heading">
    99              <h2 className="base-heading">Logs Node { nodeId } / { nodeAddress }</h2>
   100            </div>
   101            <section className="section">
   102              { REMOTE_DEBUGGING_ERROR_TEXT }
   103            </section>
   104          </div>
   105        );
   106      }
   107  
   108      return (
   109        <div>
   110          <Helmet title={ title } />
   111          <div className="section section--heading">
   112            <h2 className="base-heading">Logs Node { nodeId } / { nodeAddress }</h2>
   113          </div>
   114          <section className="section">
   115            <Loading
   116              loading={ !this.props.logs.data }
   117              error={ this.props.logs.lastError }
   118              render={ this.renderContent }
   119            />
   120          </section>
   121        </div>
   122      );
   123    }
   124  }
   125  
   126  // Connect the EventsList class with our redux store.
   127  const logsConnected = withRouter(connect(
   128    (state: AdminUIState, ownProps: RouteComponentProps) => {
   129      return {
   130        logs: state.cachedData.logs,
   131        currentNode: currentNode(state, ownProps),
   132      };
   133    },
   134    {
   135      refreshLogs,
   136      refreshNodes,
   137    },
   138  )(Logs));
   139  
   140  export default logsConnected;