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  }