github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/stores/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 import { createSelector } from "reselect"; 17 18 import * as protos from "src/js/protos"; 19 import { storesRequestKey, refreshStores } from "src/redux/apiReducers"; 20 import { AdminUIState } from "src/redux/state"; 21 import { nodeIDAttr } from "src/util/constants"; 22 import EncryptionStatus from "src/views/reports/containers/stores/encryption"; 23 import Loading from "src/views/shared/components/loading"; 24 import { getMatchParamByName } from "src/util/query"; 25 26 interface StoresOwnProps { 27 stores: protos.cockroach.server.serverpb.IStoreDetails[]; 28 loading: boolean; 29 lastError: Error; 30 refreshStores: typeof refreshStores; 31 } 32 33 type StoresProps = StoresOwnProps & RouteComponentProps; 34 35 function storesRequestFromProps(props: StoresProps) { 36 const nodeId = getMatchParamByName(props.match, nodeIDAttr); 37 return new protos.cockroach.server.serverpb.StoresRequest({ 38 node_id: nodeId, 39 }); 40 } 41 42 /** 43 * Renders the Stores Report page. 44 */ 45 export class Stores extends React.Component<StoresProps, {}> { 46 refresh(props = this.props) { 47 props.refreshStores(storesRequestFromProps(props)); 48 } 49 50 componentDidMount() { 51 // Refresh nodes status query when mounting. 52 this.refresh(); 53 } 54 55 componentDidUpdate(prevProps: StoresProps) { 56 if (!_.isEqual(this.props.location, prevProps.location)) { 57 this.refresh(this.props); 58 } 59 } 60 61 renderSimpleRow(header: string, value: string, title: string = "") { 62 let realTitle = title; 63 if (_.isEmpty(realTitle)) { 64 realTitle = value; 65 } 66 return ( 67 <tr className="stores-table__row"> 68 <th className="stores-table__cell stores-table__cell--header">{header}</th> 69 <td className="stores-table__cell" title={realTitle}>{value}</td> 70 </tr> 71 ); 72 } 73 74 renderStore = (store: protos.cockroach.server.serverpb.IStoreDetails) => { 75 return ( 76 <table key={store.store_id} className="stores-table"> 77 <tbody> 78 { this.renderSimpleRow("Store ID", store.store_id.toString()) } 79 { new EncryptionStatus({store: store}).getEncryptionRows() } 80 </tbody> 81 </table> 82 ); 83 } 84 85 renderContent = () => { 86 const { stores, match } = this.props; 87 88 const nodeID = getMatchParamByName(match, nodeIDAttr); 89 if (_.isEmpty(stores)) { 90 return ( 91 <h2 className="base-heading">No stores were found on node {nodeID}.</h2> 92 ); 93 } 94 95 return _.map(this.props.stores, this.renderStore); 96 } 97 98 render() { 99 const nodeID = getMatchParamByName(this.props.match, nodeIDAttr); 100 let header: string = null; 101 if (_.isNaN(parseInt(nodeID, 10))) { 102 header = "Local Node"; 103 } else { 104 header = `Node ${nodeID}`; 105 } 106 107 return ( 108 <div className="section"> 109 <Helmet title="Stores | Debug" /> 110 <h1 className="base-heading">Stores</h1> 111 <h2 className="base-heading">{header} stores</h2> 112 <Loading 113 loading={this.props.loading} 114 error={this.props.lastError} 115 render={this.renderContent} 116 /> 117 </div> 118 ); 119 } 120 } 121 122 function selectStoresState(state: AdminUIState, props: StoresProps) { 123 const nodeIDKey = storesRequestKey(storesRequestFromProps(props)); 124 return state.cachedData.stores[nodeIDKey]; 125 } 126 127 const selectStoresLoading = createSelector( 128 selectStoresState, 129 (stores) => _.isEmpty(stores) || _.isEmpty(stores.data), 130 ); 131 132 const selectSortedStores = createSelector( 133 selectStoresLoading, 134 selectStoresState, 135 (loading, stores) => { 136 if (loading) { 137 return null; 138 } 139 return _.sortBy(stores.data.stores, (store) => store.store_id); 140 }, 141 ); 142 143 const selectStoresLastError = createSelector( 144 selectStoresLoading, 145 selectStoresState, 146 (loading, stores) => { 147 if (loading) { 148 return null; 149 } 150 return stores.lastError; 151 }, 152 ); 153 154 const mapStateToProps = (state: AdminUIState, props: StoresProps) => ({ 155 stores: selectSortedStores(state, props), 156 loading: selectStoresLoading(state, props), 157 lastError: selectStoresLastError(state, props), 158 }); 159 160 const mapDispatchToProps = { 161 refreshStores, 162 }; 163 164 export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Stores));