github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ui/ccl/src/views/clusterviz/containers/map/nodeCanvas.tsx (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 import _ from "lodash"; 10 import React from "react"; 11 import { Link } from "react-router-dom"; 12 13 import { CircleLayout } from "./circleLayout"; 14 import { renderAsMap } from "./layout"; 15 import { MapLayout } from "./mapLayout"; 16 17 import { LivenessStatus } from "src/redux/nodes"; 18 import { LocalityTier, LocalityTree } from "src/redux/localities"; 19 import { LocationTree } from "src/redux/locations"; 20 import { CLUSTERVIZ_ROOT } from "src/routes/visualization"; 21 import { generateLocalityRoute, getLocalityLabel } from "src/util/localities"; 22 import arrowUpIcon from "!!raw-loader!assets/arrowUp.svg"; 23 import { trustIcon } from "src/util/trust"; 24 import { cockroach } from "src/js/protos"; 25 import InstructionsBox, { showInstructionsBox } from "src/views/clusterviz/components/instructionsBox"; 26 27 type Liveness = cockroach.kv.kvserver.storagepb.ILiveness; 28 29 const BACK_BUTTON_OFFSET = 26; 30 31 interface NodeCanvasProps { 32 localityTree: LocalityTree; 33 locationTree: LocationTree; 34 livenessStatuses: { [id: string]: LivenessStatus }; 35 livenesses: { [id: string]: Liveness }; 36 tiers: LocalityTier[]; 37 } 38 39 interface NodeCanvasState { 40 viewportSize: [number, number]; 41 } 42 43 export class NodeCanvas extends React.Component<NodeCanvasProps, NodeCanvasState> { 44 graphEl: React.RefObject<SVGSVGElement> = React.createRef(); 45 debouncedOnResize: () => void; 46 47 constructor(props: any) { 48 super(props); 49 50 // Add debounced resize listener. 51 this.debouncedOnResize = _.debounce(this.onResize, 200); 52 } 53 54 updateViewport = () => { 55 const rect = this.graphEl.current.getBoundingClientRect(); 56 this.setState({ 57 viewportSize: [rect.width, rect.height], 58 }); 59 } 60 61 onResize = () => { 62 this.updateViewport(); 63 } 64 65 componentDidMount() { 66 window.addEventListener("resize", this.debouncedOnResize); 67 68 this.updateViewport(); 69 } 70 71 componentWillUnmount() { 72 window.removeEventListener("resize", this.debouncedOnResize); 73 } 74 75 renderContent(asMap: boolean) { 76 if (!this.state) { 77 return null; 78 } 79 80 const { localityTree, locationTree, livenessStatuses, livenesses } = this.props; 81 const { viewportSize } = this.state; 82 83 if (asMap) { 84 return <MapLayout 85 localityTree={localityTree} 86 locationTree={locationTree} 87 livenessStatuses={livenessStatuses} 88 viewportSize={viewportSize} 89 />; 90 } 91 92 return <CircleLayout 93 viewportSize={viewportSize} 94 localityTree={localityTree} 95 livenessStatuses={livenessStatuses} 96 livenesses={livenesses} 97 />; 98 } 99 100 renderBackButton() { 101 const { tiers } = this.props; 102 103 if (!this.state || _.isEmpty(tiers)) { 104 return null; 105 } 106 107 const parentLocality = tiers.slice(0, tiers.length - 1); 108 109 return ( 110 <Link 111 to={ CLUSTERVIZ_ROOT + generateLocalityRoute(parentLocality) } 112 style={{ textDecoration: "none", color: "#595f6c" }} 113 > 114 <div 115 style={{ 116 position: "absolute", 117 left: BACK_BUTTON_OFFSET, 118 bottom: BACK_BUTTON_OFFSET, 119 backgroundColor: "white", 120 border: "1px solid #EDEDED", 121 borderRadius: 3, 122 padding: 12, 123 boxShadow: "0px 0px 4px 0px rgba(0, 0, 0, 0.2)", 124 letterSpacing: 0.5, 125 }} 126 > 127 <span 128 dangerouslySetInnerHTML={trustIcon(arrowUpIcon)} 129 style={{ position: "relative", top: 1 }} 130 /> 131 Up to{" "} 132 <span style={{ textTransform: "uppercase" }}> 133 { getLocalityLabel(parentLocality) } 134 </span> 135 </div> 136 </Link> 137 ); 138 } 139 140 render() { 141 const showMap = renderAsMap(this.props.locationTree, this.props.localityTree); 142 143 // We must render the SVG even before initializing the state, because we 144 // need to read its dimensions from the DOM in order to initialize the 145 // state. 146 return ( 147 <div style={{ flexGrow: 1, position: "relative" }}> 148 <div style={{ width: "100%", height: "100%", position: "absolute" }}> 149 <svg 150 style={{ 151 width: "100%", 152 height: "100%", 153 marginBottom: -3, // WHYYYYYYYYY?!?!?!?!? 154 position: "absolute", 155 }} 156 className="cluster-viz" 157 ref={this.graphEl} 158 > 159 { this.renderContent(showMap) } 160 </svg> 161 </div> 162 { this.renderBackButton() } 163 { showInstructionsBox(showMap, this.props.tiers) 164 ? <InstructionsBox /> 165 : null 166 } 167 </div> 168 ); 169 } 170 }