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;