github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/src/views/reports/containers/enqueueRange/index.tsx (about) 1 // Copyright 2020 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 React, { Fragment } from "react"; 12 import Helmet from "react-helmet"; 13 import { RouteComponentProps, withRouter } from "react-router-dom"; 14 import moment from "moment"; 15 16 import { enqueueRange } from "src/util/api"; 17 import { cockroach } from "src/js/protos"; 18 import Print from "src/views/reports/containers/range/print"; 19 import "./index.styl"; 20 21 import EnqueueRangeRequest = cockroach.server.serverpb.EnqueueRangeRequest; 22 import EnqueueRangeResponse = cockroach.server.serverpb.EnqueueRangeResponse; 23 24 const QUEUES = [ 25 "replicate", 26 "gc", 27 "merge", 28 "split", 29 "replicaGC", 30 "raftlog", 31 "raftsnapshot", 32 "consistencyChecker", 33 "timeSeriesMaintenance", 34 ]; 35 36 interface EnqueueRangeProps { 37 handleEnqueueRange: (queue: string, rangeID: number, nodeID: number, skipShouldQueue: boolean) => Promise<EnqueueRangeResponse>; 38 } 39 40 interface EnqueueRangeState { 41 queue: string; 42 rangeID: string; 43 nodeID: string; 44 skipShouldQueue: boolean; 45 response: EnqueueRangeResponse; 46 error: Error; 47 } 48 49 export class EnqueueRange extends React.Component<EnqueueRangeProps & RouteComponentProps, EnqueueRangeState> { 50 state: EnqueueRangeState = { 51 queue: QUEUES[0], 52 rangeID: "", 53 nodeID: "", 54 skipShouldQueue: false, 55 response: null, 56 error: null, 57 }; 58 59 handleUpdateQueue = (evt: React.FormEvent<{ value: string }>) => { 60 this.setState({ 61 queue: evt.currentTarget.value, 62 }); 63 } 64 65 handleUpdateRangeID = (evt: React.FormEvent<{ value: string }>) => { 66 this.setState({ 67 rangeID: evt.currentTarget.value, 68 }); 69 } 70 71 handleUpdateNodeID = (evt: React.FormEvent<{ value: string }>) => { 72 this.setState({ 73 nodeID: evt.currentTarget.value, 74 }); 75 } 76 77 handleEnqueueRange = (queue: string, rangeID: number, nodeID: number, skipShouldQueue: boolean) => { 78 const req = new EnqueueRangeRequest({ 79 queue: queue, 80 range_id: rangeID, 81 node_id: nodeID, 82 skip_should_queue: skipShouldQueue, 83 }); 84 return enqueueRange(req, moment.duration({ hours: 1 })); 85 } 86 87 handleSubmit = (evt: React.FormEvent<any>) => { 88 evt.preventDefault(); 89 90 this.handleEnqueueRange( 91 this.state.queue, 92 // These parseInts should succeed because <input type="number" /> 93 // enforces numeric input. Otherwise they're NaN. 94 parseInt(this.state.rangeID, 10), 95 parseInt(this.state.nodeID, 10), 96 this.state.skipShouldQueue, 97 ).then( 98 response => { 99 this.setState({ response, error: null }); 100 }, 101 error => { 102 this.setState({ response: null, error }); 103 }, 104 ); 105 } 106 107 renderNodeResponse(details: EnqueueRangeResponse.IDetails) { 108 return ( 109 <Fragment> 110 <p> 111 {details.error 112 ? <Fragment><b>Error:</b> {details.error}</Fragment> 113 : "Call succeeded"} 114 </p> 115 <table className="enqueue-range-table"> 116 <thead> 117 <tr className="enqueue-range-table__row enqueue-range-table__row--header"> 118 <th className="enqueue-range-table__cell enqueue-range-table__cell--header">Timestamp</th> 119 <th className="enqueue-range-table__cell enqueue-range-table__cell--header">Message</th> 120 </tr> 121 </thead> 122 <tbody> 123 {details.events.map((event) => ( 124 <tr className="enqueue-range-table__row--body"> 125 <td className="enqueue-range-table__cell enqueue-range-table__cell--date"> 126 {Print.Timestamp(event.time)} 127 </td> 128 <td className="enqueue-range-table__cell"> 129 <pre>{event.message}</pre> 130 </td> 131 </tr> 132 ))} 133 </tbody> 134 </table> 135 </Fragment> 136 ); 137 } 138 139 renderResponse() { 140 const { response } = this.state; 141 142 if (!response) { 143 return null; 144 } 145 146 return ( 147 <Fragment> 148 <h2 className="base-heading">Enqueue Range Output</h2> 149 {response.details.map((details) => ( 150 <div> 151 <h3>Node n{details.node_id}</h3> 152 153 {this.renderNodeResponse(details)} 154 </div> 155 ))} 156 </Fragment> 157 ); 158 } 159 160 renderError() { 161 const { error } = this.state; 162 163 if (!error) { 164 return null; 165 } 166 167 return ( 168 <Fragment>Error running EnqueueRange: {error.message}</Fragment> 169 ); 170 } 171 172 render() { 173 return ( 174 <Fragment> 175 <Helmet title="Enqueue Range" /> 176 <div className="content"> 177 <section className="section"> 178 <div className="form-container"> 179 <h1 className="base-heading heading">Manually enqueue range in a replica queue</h1> 180 <br /> 181 <form onSubmit={this.handleSubmit} className="form-internal" method="post"> 182 <label> 183 Queue:{" "} 184 <select onChange={this.handleUpdateQueue}> 185 {QUEUES.map((queue) => ( 186 <option key={queue} value={queue}>{queue}</option> 187 ))} 188 </select> 189 </label> 190 <br /> 191 <label> 192 RangeID:{" "} 193 <input 194 type="number" 195 name="rangeID" 196 className="input-text" 197 onChange={this.handleUpdateRangeID} 198 value={this.state.rangeID} 199 placeholder="RangeID" 200 /> 201 </label> 202 <br /> 203 <label> 204 NodeID:{" "} 205 <input 206 type="number" 207 name="nodeID" 208 className="input-text" 209 onChange={this.handleUpdateNodeID} 210 value={this.state.nodeID} 211 placeholder="NodeID (optional)" 212 /> 213 If not specified, we'll attempt to enqueue on all the nodes. 214 </label> 215 <br /> 216 <label> 217 SkipShouldQueue:{" "} 218 <input 219 type="checkbox" 220 checked={this.state.skipShouldQueue} 221 name="skipShouldQueue" 222 onChange={() => this.setState({ skipShouldQueue: !this.state.skipShouldQueue })} 223 /> 224 </label> 225 <br /> 226 <input 227 type="submit" 228 className="submit-button" 229 value="Submit" 230 /> 231 </form> 232 </div> 233 </section> 234 </div> 235 <section className="section"> 236 {this.renderResponse()} 237 {this.renderError()} 238 </section> 239 </Fragment> 240 ); 241 } 242 } 243 244 const EnqueueRangeConnected = withRouter(EnqueueRange); 245 246 export default EnqueueRangeConnected;