github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/certificates/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, { Fragment } 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 { certificatesRequestKey, refreshCertificates } from "src/redux/apiReducers"; 19 import { AdminUIState } from "src/redux/state"; 20 import { nodeIDAttr } from "src/util/constants"; 21 import { LongToMoment } from "src/util/convert"; 22 import Loading from "src/views/shared/components/loading"; 23 import { getMatchParamByName } from "src/util/query"; 24 25 interface CertificatesOwnProps { 26 certificates: protos.cockroach.server.serverpb.CertificatesResponse; 27 lastError: Error; 28 refreshCertificates: typeof refreshCertificates; 29 } 30 31 const dateFormat = "Y-MM-DD HH:mm:ss"; 32 33 type CertificatesProps = CertificatesOwnProps & RouteComponentProps; 34 35 const emptyRow = ( 36 <tr className="certs-table__row"> 37 <th className="certs-table__cell certs-table__cell--header" /> 38 <td className="certs-table__cell" /> 39 </tr> 40 ); 41 42 function certificatesRequestFromProps(props: CertificatesProps) { 43 return new protos.cockroach.server.serverpb.CertificatesRequest({ 44 node_id: getMatchParamByName(props.match, nodeIDAttr), 45 }); 46 } 47 48 /** 49 * Renders the Certificate Report page. 50 */ 51 export class Certificates extends React.Component<CertificatesProps, {}> { 52 refresh(props = this.props) { 53 props.refreshCertificates(certificatesRequestFromProps(props)); 54 } 55 56 componentDidMount() { 57 // Refresh nodes status query when mounting. 58 this.refresh(); 59 } 60 61 componentDidUpdate(prevProps: CertificatesProps) { 62 if (!_.isEqual(this.props.location, prevProps.location)) { 63 this.refresh(this.props); 64 } 65 } 66 67 renderSimpleRow(header: string, value: string, title: string = "") { 68 let realTitle = title; 69 if (_.isEmpty(realTitle)) { 70 realTitle = value; 71 } 72 return ( 73 <tr className="certs-table__row"> 74 <th className="certs-table__cell certs-table__cell--header">{header}</th> 75 <td className="certs-table__cell" title={realTitle}>{value}</td> 76 </tr> 77 ); 78 } 79 80 renderMultilineRow(header: string, values: string[]) { 81 return ( 82 <tr className="certs-table__row"> 83 <th className="certs-table__cell certs-table__cell--header">{header}</th> 84 <td className="certs-table__cell" title={_.join(values, "\n")}> 85 <ul className="certs-entries-list"> 86 { 87 _.chain(values) 88 .sort() 89 .map((value, key) => ( 90 <li key={key}> 91 {value} 92 </li> 93 )) 94 .value() 95 } 96 </ul> 97 </td> 98 </tr> 99 ); 100 } 101 102 renderTimestampRow(header: string, value: Long) { 103 const timestamp = LongToMoment(value).format(dateFormat); 104 const title = value + "\n" + timestamp; 105 return this.renderSimpleRow(header, timestamp, title); 106 } 107 108 renderFields(fields: protos.cockroach.server.serverpb.CertificateDetails.IFields, id: number) { 109 return [ 110 this.renderSimpleRow("Cert ID", id.toString()), 111 this.renderSimpleRow("Issuer", fields.issuer), 112 this.renderSimpleRow("Subject", fields.subject), 113 this.renderTimestampRow("Valid From", fields.valid_from), 114 this.renderTimestampRow("Valid Until", fields.valid_until), 115 this.renderMultilineRow("Addresses", fields.addresses), 116 this.renderSimpleRow("Signature Algorithm", fields.signature_algorithm), 117 this.renderSimpleRow("Public Key", fields.public_key), 118 this.renderMultilineRow("Key Usage", fields.key_usage), 119 this.renderMultilineRow("Extended Key Usage", fields.extended_key_usage), 120 ]; 121 } 122 123 renderCert(cert: protos.cockroach.server.serverpb.ICertificateDetails, key: number) { 124 let certType: string; 125 switch (cert.type) { 126 case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.CA: 127 certType = "Certificate Authority"; 128 break; 129 case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.NODE: 130 certType = "Node Certificate"; 131 break; 132 case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.CLIENT_CA: 133 certType = "Client Certificate Authority"; 134 break; 135 case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.CLIENT: 136 certType = "Client Certificate"; 137 break; 138 case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.UI_CA: 139 certType = "UI Certificate Authority"; 140 break; 141 case protos.cockroach.server.serverpb.CertificateDetails.CertificateType.UI: 142 certType = "UI Certificate"; 143 break; 144 default: 145 certType = "Unknown"; 146 } 147 return ( 148 <table key={key} className="certs-table"> 149 <tbody> 150 {this.renderSimpleRow("Type", certType)} 151 { 152 _.map(cert.fields, (fields, id) => { 153 const result = this.renderFields(fields, id); 154 if (id > 0) { 155 result.unshift(emptyRow); 156 } 157 return result; 158 }) 159 } 160 </tbody> 161 </table> 162 ); 163 } 164 165 renderContent = () => { 166 const { certificates, match } = this.props; 167 const nodeId = getMatchParamByName(match, nodeIDAttr); 168 169 if (_.isEmpty(certificates.certificates)) { 170 return <h2 className="base-heading">No certificates were found on node {nodeId}.</h2>; 171 } 172 173 let header: string = null; 174 if (_.isNaN(parseInt(nodeId, 10))) { 175 header = "Local Node"; 176 } else { 177 header = `Node ${nodeId}`; 178 } 179 180 return ( 181 <Fragment> 182 <h2 className="base-heading">{header} certificates</h2> 183 { 184 _.map(certificates.certificates, (cert, key) => ( 185 this.renderCert(cert, key) 186 )) 187 } 188 </Fragment> 189 ); 190 } 191 192 render() { 193 return ( 194 <div className="section"> 195 <Helmet title="Certificates | Debug" /> 196 <h1 className="base-heading">Certificates</h1> 197 198 <section className="section"> 199 <Loading 200 loading={!this.props.certificates} 201 error={this.props.lastError} 202 render={this.renderContent} 203 /> 204 </section> 205 </div> 206 ); 207 } 208 } 209 210 const mapStateToProps = (state: AdminUIState, props: CertificatesProps) => { 211 const nodeIDKey = certificatesRequestKey(certificatesRequestFromProps(props)); 212 return { 213 certificates: state.cachedData.certificates[nodeIDKey] && state.cachedData.certificates[nodeIDKey].data, 214 lastError: state.cachedData.certificates[nodeIDKey] && state.cachedData.certificates[nodeIDKey].lastError, 215 }; 216 }; 217 218 const mapDispatchToProps = { 219 refreshCertificates, 220 }; 221 222 export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Certificates));