github.com/aitjcize/Overlord@v0.0.0-20240314041920-104a804cf5e8/overlord/app/dashboard/js/view.jsx (about) 1 // Copyright 2015 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 // 5 // Requires: 6 // NavBar.jsx :: NavBar 7 // UploadProgressWidget.jsx :: UploadProgressWidget 8 // FixtureWidget.jsx :: FixtureWidget 9 // TerminalWindow.jsx :: TerminalWindow 10 // CameraWindow.jsx :: CameraWindow 11 // 12 // View for Dashboard App: 13 // - App 14 // - NavBar 15 // - SideBar 16 // - ClientBox 17 // - FilterInput 18 // - ClientList 19 // - [ClientInfo ...] 20 // - RecentList 21 // - ClientInfo 22 // - Windows 23 // - [TerminalWindow ...] 24 // - [CameraWindow ...] 25 // - UploadProgressWidget 26 // - FixtureGroup 27 // - [FixtureWidget ...] 28 29 var App = React.createClass({ 30 mixins: [BaseApp], 31 addTerminal: function (id, term) { 32 this.setState(function (state, props) { 33 state.terminals[id] = term; 34 }); 35 }, 36 addFixture: function (client) { 37 if (this.isClientInList(this.state.fixtures, client)) { 38 return; 39 } 40 41 // compute how many fixtures we can put in the screen 42 var screen = { 43 width: window.innerWidth, 44 }; 45 46 var sidebar = ReactDOM.findDOMNode(this.refs.sidebar).getBoundingClientRect(); 47 48 screen.width -= sidebar.right; 49 50 var nFixturePerRow = Math.floor( 51 screen.width / (FIXTURE_WINDOW_WIDTH + FIXTURE_WINDOW_MARGIN * 2)); 52 nFixturePerRow = Math.max(1, nFixturePerRow); 53 var nTotalFixture = Math.min(2 * nFixturePerRow, 8); 54 55 // only keep recently opened @nTotalFixture fixtures. 56 this.setState(function (state, props) { 57 state.fixtures.push(client); 58 return {fixtures: state.fixtures.slice(-nTotalFixture)}; 59 }); 60 }, 61 addCamera: function (id, cam) { 62 this.setState(function (state, props) { 63 state.cameras[id] = cam; 64 }); 65 }, 66 toggleFixtureState: function (client) { 67 if (this.isClientInList(this.state.fixtures, client)) { 68 this.removeFixture(client.mid); 69 } else { 70 this.addFixture(client); 71 } 72 }, 73 removeTerminal: function (id) { 74 this.setState(function (state, props) { 75 if (typeof(state.terminals[id]) != "undefined") { 76 delete state.terminals[id]; 77 } 78 }); 79 }, 80 removeFixture: function (id) { 81 this.setState(function (state, props) { 82 this.removeClientFromList(state.fixtures, {mid: id}); 83 }); 84 }, 85 removeCamera: function (id) { 86 this.setState(function (state, props) { 87 if (typeof(state.cameras[id]) != "undefined") { 88 delete state.cameras[id]; 89 } 90 }); 91 }, 92 getInitialState: function () { 93 return {cameras: [], fixtures: [], recentclients: [], terminals: {}}; 94 }, 95 componentDidMount: function () { 96 var socket = io(window.location.protocol + "//" + window.location.host, 97 {path: "/api/socket.io/"}); 98 this.socket = socket; 99 100 socket.on("agent joined", function (msg) { 101 var client = JSON.parse(msg); 102 this.addClient(client); 103 104 this.state.recentclients.splice(0, 0, client); 105 this.state.recentclients = this.state.recentclients.slice(0, 5); 106 }.bind(this)); 107 108 socket.on("agent left", function (msg) { 109 var client = JSON.parse(msg); 110 111 this.removeClientFromList(this.state.clients, client); 112 this.removeClientFromList(this.state.recentclients, client); 113 this.removeFixture(client.mid); 114 }.bind(this)); 115 116 // Initiate a file download 117 socket.on("file download", function (sid) { 118 var url = window.location.protocol + "//" + window.location.host + 119 "/api/file/download/" + sid; 120 $("<iframe src='" + url + "' style='display:none'>" + 121 "</iframe>").appendTo('body'); 122 }); 123 }, 124 render: function () { 125 return ( 126 <div id="main"> 127 <NavBar name="Dashboard" url="/api/apps/list" ref="navbar" /> 128 <div id="container"> 129 <SideBar clients={this.getFilteredClientList()} ref="sidebar" 130 recentclients={this.state.recentclients} app={this} /> 131 <FixtureGroup data={this.state.fixtures} app={this} 132 uploadProgress={this.refs.uploadProgress} /> 133 </div> 134 <div className="windows"> 135 <Windows app={this} terminals={this.state.terminals} 136 uploadProgress={this.refs.uploadProgress} 137 cameras={this.state.cameras} /> 138 </div> 139 <div className="upload-progress"> 140 <UploadProgressWidget ref="uploadProgress" /> 141 </div> 142 </div> 143 ); 144 } 145 }); 146 147 var SideBar = React.createClass({ 148 render: function () { 149 return ( 150 <div className="sidebar"> 151 <ClientBox data={this.props.clients} app={this.props.app} /> 152 <RecentList data={this.props.recentclients} app={this.props.app} /> 153 </div> 154 ); 155 } 156 }); 157 158 var ClientBox = React.createClass({ 159 render: function () { 160 return ( 161 <div className="client-box panel panel-success"> 162 <div className="panel-heading">Clients</div> 163 <div className="panel-body"> 164 <FilterInput app={this.props.app} /> 165 <ClientList data={this.props.data} app={this.props.app} /> 166 </div> 167 </div> 168 ); 169 } 170 }) 171 172 var FilterInput = React.createClass({ 173 onKeyUp: function (event) { 174 this.props.app.setDisplayFilterPattern(this.refs.filter.value); 175 }, 176 render: function () { 177 return ( 178 <div> 179 <input type="text" className="filter-input form-control" ref="filter" 180 placeholder="keyword" onKeyUp={this.onKeyUp}></input> 181 </div> 182 ) 183 } 184 }); 185 186 var ClientList = React.createClass({ 187 render: function () { 188 return ( 189 <div className="list-box client-list"> 190 { 191 this.props.data.map(function (item) { 192 return ( 193 <ClientInfo key={item.mid} data={item} app={this.props.app}> 194 {displayClient(item)} 195 </ClientInfo> 196 ); 197 }.bind(this)) 198 } 199 </div> 200 ); 201 } 202 }); 203 204 var RecentList = React.createClass({ 205 render: function () { 206 return ( 207 <div className="recent-box panel panel-info"> 208 <div className="panel-heading">Recent Connected Clients</div> 209 <div className="panel-body"> 210 <div className="list-box recent-list"> 211 { 212 this.props.data.map(function (item) { 213 return ( 214 <ClientInfo key={item.mid} data={item} app={this.props.app}> 215 {displayClient(item)} 216 </ClientInfo> 217 ); 218 }.bind(this)) 219 } 220 </div> 221 </div> 222 </div> 223 ) 224 } 225 }); 226 227 var ClientInfo = React.createClass({ 228 openTerminal: function (event) { 229 this.props.app.addTerminal(randomID(), this.props.data); 230 }, 231 openCamera: function (event) { 232 this.props.app.addCamera(this.props.data.mid, this.props.data); 233 }, 234 onUIButtonClick: function (event) { 235 this.props.app.toggleFixtureState(this.props.data); 236 }, 237 componentDidMount: function (event) { 238 // Since the button covers the machine ID text, abbrieviate to match the 239 // current visible width. 240 var chPerLine = 50; 241 var pxPerCh = this.refs.mid.clientWidth / chPerLine; 242 this.refs.mid.innerText = 243 abbr(this.refs.mid.innerText, 244 chPerLine - (this.refs["info-buttons"].clientWidth)/ pxPerCh); 245 }, 246 render: function () { 247 var display = "block"; 248 var ui_span = null; 249 var cam_span = null; 250 251 if (typeof(this.props.data.properties) != "undefined" && 252 typeof(this.props.data.properties.context) != "undefined" && 253 this.props.data.properties.context.indexOf("ui") !== -1) { 254 var ui_state = this.props.app.isClientInList( 255 this.props.app.state.fixtures, this.props.data); 256 var ui_light_css = LIGHT_CSS_MAP[ui_state ? "light-toggle-on" 257 : "light-toggle-off"]; 258 ui_span = ( 259 <div className={"label " + ui_light_css + " client-info-button"} 260 data-mid={this.props.data.key} onClick={this.onUIButtonClick}> 261 UI 262 </div> 263 ); 264 } 265 if (typeof(this.props.data.properties) != "undefined" && 266 typeof(this.props.data.properties.context) != "undefined" && 267 this.props.data.properties.context.indexOf("cam") !== -1) { 268 cam_span = ( 269 <div className="label label-success client-info-button" 270 data-mid={this.props.data.key} onClick={this.openCamera}> 271 CAM 272 </div> 273 ); 274 } 275 return ( 276 <div className="client-info"> 277 <div className="client-info-mid" ref="mid"> 278 {this.props.children} 279 </div> 280 <div className="client-info-buttons" ref="info-buttons"> 281 {cam_span} 282 {ui_span} 283 <div className="label label-warning client-info-button" 284 data-mid={this.props.data.key} onClick={this.openTerminal}> 285 Terminal 286 </div> 287 </div> 288 </div> 289 ); 290 } 291 }); 292 293 var Windows = React.createClass({ 294 render: function () { 295 var onTerminalControl = function (control) { 296 if (control.type == "sid") { 297 this.terminal_sid = control.data; 298 this.props.app.socket.emit("subscribe", control.data); 299 } 300 }; 301 var onTerminalCloseClicked = function (event) { 302 this.props.app.removeTerminal(this.props.id); 303 this.props.app.socket.emit("unsubscribe", this.terminal_sid); 304 }; 305 var onCameraCloseClicked = function (event) { 306 this.props.app.removeCamera(this.props.id); 307 } 308 // We need to make TerminalWindow and CameraWindow have the same parent 309 // div so z-index stacking works. 310 return ( 311 <div> 312 <div className="windows"> 313 { 314 Object.keys(this.props.terminals).map(function (id) { 315 var term = this.props.terminals[id]; 316 var extra = ""; 317 if (typeof(term.path) != "undefined") { 318 extra = "?tty_device=" + term.path; 319 } 320 return ( 321 <TerminalWindow key={id} mid={term.mid} id={id} title={term.mid} 322 path={"/api/agent/tty/" + term.mid + extra} 323 uploadRequestPath={"/api/agent/upload/" + term.mid} 324 enableMaximize={true} 325 app={this.props.app} progressBars={this.props.uploadProgress} 326 onControl={onTerminalControl} 327 onCloseClicked={onTerminalCloseClicked} /> 328 ); 329 }.bind(this)) 330 } 331 { 332 Object.keys(this.props.cameras).map(function (id) { 333 var cam = this.props.cameras[id]; 334 var cam_prop = cam.properties.camera; 335 if (typeof(cam_prop) != "undefined") { 336 var command = cam_prop.command; 337 var width = cam_prop.width || 640; 338 var height = cam_prop.height || 640; 339 return ( 340 <CameraWindow key={id} mid={cam.mid} id={id} title={cam.mid} 341 path={"/api/agent/shell/" + cam.mid + "?command=" + 342 encodeURIComponent(command)} 343 width={width} height={height} app={this.props.app} 344 onCloseClicked={onCameraCloseClicked} /> 345 ); 346 } 347 }.bind(this)) 348 } 349 </div> 350 </div> 351 ); 352 } 353 }); 354 355 var FixtureGroup = React.createClass({ 356 render: function () { 357 return ( 358 <div className="fixture-group"> 359 { 360 this.props.data.map(function (item) { 361 return ( 362 <FixtureWidget key={item.mid} client={item} 363 progressBars={this.props.uploadProgress} 364 app={this.props.app} /> 365 ); 366 }.bind(this)) 367 } 368 </div> 369 ); 370 } 371 }); 372 373 ReactDOM.render( 374 <App url="/api/agents/list" />, 375 document.getElementById("body") 376 );