github.com/alexdevranger/node-1.8.27@v0.0.0-20221128213301-aa5841e41d2d/dashboard/assets/components/Dashboard.jsx (about)

     1  // @flow
     2  
     3  // Copyright 2017 The go-ethereum Authors
     4  // This file is part of the go-dubxcoin library.
     5  //
     6  // The go-dubxcoin library is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU Lesser General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // (at your option) any later version.
    10  //
    11  // The go-dubxcoin library is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    14  // GNU Lesser General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU Lesser General Public License
    17  // along with the go-dubxcoin library. If not, see <http://www.gnu.org/licenses/>.
    18  
    19  import React, { Component } from "react";
    20  
    21  import withStyles from "material-ui/styles/withStyles";
    22  
    23  import Header from "./Header";
    24  import Body from "./Body";
    25  import { MENU } from "../common";
    26  import type { Content } from "../types/content";
    27  import { inserter as logInserter } from "./Logs";
    28  
    29  // deepUpdate updates an object corresponding to the given update data, which has
    30  // the shape of the same structure as the original object. updater also has the same
    31  // structure, except that it contains functions where the original data needs to be
    32  // updated. These functions are used to handle the update.
    33  //
    34  // Since the messages have the same shape as the state content, this approach allows
    35  // the generalization of the message handling. The only necessary thing is to set a
    36  // handler function for every path of the state in order to maximize the flexibility
    37  // of the update.
    38  const deepUpdate = (
    39    updater: Object,
    40    update: Object,
    41    prev: Object
    42  ): $Shape<Content> => {
    43    if (typeof update === "undefined") {
    44      // TODO (kurkomisi): originally this was deep copy, investigate it.
    45      return prev;
    46    }
    47    if (typeof updater === "function") {
    48      return updater(update, prev);
    49    }
    50    const updated = {};
    51    Object.keys(prev).forEach((key) => {
    52      updated[key] = deepUpdate(updater[key], update[key], prev[key]);
    53    });
    54  
    55    return updated;
    56  };
    57  
    58  // shouldUpdate returns the structure of a message. It is used to prevent unnecessary render
    59  // method triggerings. In the affected component's shouldComponentUpdate method it can be checked
    60  // whether the involved data was changed or not by checking the message structure.
    61  //
    62  // We could return the message itself too, but it's safer not to give access to it.
    63  const shouldUpdate = (updater: Object, msg: Object) => {
    64    const su = {};
    65    Object.keys(msg).forEach((key) => {
    66      su[key] =
    67        typeof updater[key] !== "function"
    68          ? shouldUpdate(updater[key], msg[key])
    69          : true;
    70    });
    71  
    72    return su;
    73  };
    74  
    75  // replacer is a state updater function, which replaces the original data.
    76  const replacer = <T>(update: T) => update;
    77  
    78  // appender is a state updater function, which appends the update data to the
    79  // existing data. limit defines the maximum allowed size of the created array,
    80  // mapper maps the update data.
    81  const appender =
    82    <T>(limit: number, mapper = replacer) =>
    83    (update: Array<T>, prev: Array<T>) =>
    84      [...prev, ...update.map((sample) => mapper(sample))].slice(-limit);
    85  
    86  // defaultContent returns the initial value of the state content. Needs to be a function in order to
    87  // instantiate the object again, because it is used by the state, and isn't automatically cleaned
    88  // when a new connection is established. The state is mutated during the update in order to avoid
    89  // the execution of unnecessary operations (e.g. copy of the log array).
    90  const defaultContent: () => Content = () => ({
    91    general: {
    92      version: null,
    93      commit: null,
    94    },
    95    home: {},
    96    chain: {},
    97    txpool: {},
    98    network: {},
    99    system: {
   100      activeMemory: [],
   101      virtualMemory: [],
   102      networkIngress: [],
   103      networkEgress: [],
   104      processCPU: [],
   105      systemCPU: [],
   106      diskRead: [],
   107      diskWrite: [],
   108    },
   109    logs: {
   110      chunks: [],
   111      endTop: false,
   112      endBottom: true,
   113      topChanged: 0,
   114      bottomChanged: 0,
   115    },
   116  });
   117  
   118  // updaters contains the state updater functions for each path of the state.
   119  //
   120  // TODO (kurkomisi): Define a tricky type which embraces the content and the updaters.
   121  const updaters = {
   122    general: {
   123      version: replacer,
   124      commit: replacer,
   125    },
   126    home: null,
   127    chain: null,
   128    txpool: null,
   129    network: null,
   130    system: {
   131      activeMemory: appender(200),
   132      virtualMemory: appender(200),
   133      networkIngress: appender(200),
   134      networkEgress: appender(200),
   135      processCPU: appender(200),
   136      systemCPU: appender(200),
   137      diskRead: appender(200),
   138      diskWrite: appender(200),
   139    },
   140    logs: logInserter(5),
   141  };
   142  
   143  // styles contains the constant styles of the component.
   144  const styles = {
   145    dashboard: {
   146      display: "flex",
   147      flexFlow: "column",
   148      width: "100%",
   149      height: "100%",
   150      zIndex: 1,
   151      overflow: "hidden",
   152    },
   153  };
   154  
   155  // themeStyles returns the styles generated from the theme for the component.
   156  const themeStyles: Object = (theme: Object) => ({
   157    dashboard: {
   158      background: theme.palette.background.default,
   159    },
   160  });
   161  
   162  export type Props = {
   163    classes: Object, // injected by withStyles()
   164  };
   165  
   166  type State = {
   167    active: string, // active menu
   168    sideBar: boolean, // true if the sidebar is opened
   169    content: Content, // the visualized data
   170    shouldUpdate: Object, // labels for the components, which need to re-render based on the incoming message
   171    server: ?WebSocket,
   172  };
   173  
   174  // Dashboard is the main component, which renders the whole page, makes connection with the server and
   175  // listens for messages. When there is an incoming message, updates the page's content correspondingly.
   176  class Dashboard extends Component<Props, State> {
   177    constructor(props: Props) {
   178      super(props);
   179      this.state = {
   180        active: MENU.get("home").id,
   181        sideBar: true,
   182        content: defaultContent(),
   183        shouldUpdate: {},
   184        server: null,
   185      };
   186    }
   187  
   188    // componentDidMount initiates the establishment of the first websocket connection after the component is rendered.
   189    componentDidMount() {
   190      this.reconnect();
   191    }
   192  
   193    // reconnect establishes a websocket connection with the server, listens for incoming messages
   194    // and tries to reconnect on connection loss.
   195    reconnect = () => {
   196      // PROD is defined by webpack.
   197      const server = new WebSocket(
   198        `${window.location.protocol === "https:" ? "wss://" : "ws://"}${
   199          PROD ? window.location.host : "localhost:8080"
   200        }/api`
   201      );
   202      server.onopen = () => {
   203        this.setState({ content: defaultContent(), shouldUpdate: {}, server });
   204      };
   205      server.onmessage = (event) => {
   206        const msg: $Shape<Content> = JSON.parse(event.data);
   207        if (!msg) {
   208          console.error(`Incoming message is ${msg}`);
   209          return;
   210        }
   211        this.update(msg);
   212      };
   213      server.onclose = () => {
   214        this.setState({ server: null });
   215        setTimeout(this.reconnect, 3000);
   216      };
   217    };
   218  
   219    // send sends a message to the server, which can be accessed only through this function for safety reasons.
   220    send = (msg: string) => {
   221      if (this.state.server != null) {
   222        this.state.server.send(msg);
   223      }
   224    };
   225  
   226    // update updates the content corresponding to the incoming message.
   227    update = (msg: $Shape<Content>) => {
   228      this.setState((prevState) => ({
   229        content: deepUpdate(updaters, msg, prevState.content),
   230        shouldUpdate: shouldUpdate(updaters, msg),
   231      }));
   232    };
   233  
   234    // changeContent sets the active label, which is used at the content rendering.
   235    changeContent = (newActive: string) => {
   236      this.setState((prevState) =>
   237        prevState.active !== newActive ? { active: newActive } : {}
   238      );
   239    };
   240  
   241    // switchSideBar opens or closes the sidebar's state.
   242    switchSideBar = () => {
   243      this.setState((prevState) => ({ sideBar: !prevState.sideBar }));
   244    };
   245  
   246    render() {
   247      return (
   248        <div className={this.props.classes.dashboard} style={styles.dashboard}>
   249          <Header switchSideBar={this.switchSideBar} />
   250          <Body
   251            opened={this.state.sideBar}
   252            changeContent={this.changeContent}
   253            active={this.state.active}
   254            content={this.state.content}
   255            shouldUpdate={this.state.shouldUpdate}
   256            send={this.send}
   257          />
   258        </div>
   259      );
   260    }
   261  }
   262  
   263  export default withStyles(themeStyles)(Dashboard);