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);