github.com/aitjcize/Overlord@v0.0.0-20240314041920-104a804cf5e8/overlord/app/factoryinstall/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 // View for Factory Install App 6 // 7 // Requires: 8 // NavBar.jsx :: NavBar 9 // TerminalWindow.jsx :: TerminalWindow 10 // 11 // - App 12 // - NavBar 13 // - ClientInfo 14 // - TerminalWindow 15 16 17 var App = React.createClass({ 18 loadCookies: function (key, defaultValue) { 19 return reactCookie.load(key) || defaultValue; 20 }, 21 saveCookies: function (key, value) { 22 // Set cookies expire 10 year later 23 reactCookie.save(key, value, {maxAge: 10 * 365 * 86400}); 24 }, 25 loadClientsFromServer: function () { 26 if (this.state.locked) { 27 return; 28 } 29 $.ajax({ 30 url: this.props.url, 31 dataType: "json", 32 success: function (data) { 33 if (!this.state.locked) { 34 this.setState({clients: data}); 35 } 36 }.bind(this), 37 error: function (xhr, status, err) { 38 console.error(this.props.url, status, err.toString()); 39 }.bind(this) 40 }); 41 }, 42 removeClientFromList: function (target_list, obj) { 43 for (var i = 0; i < target_list.length; ++i) { 44 if (target_list[i].mid == obj.mid) { 45 index = target_list[i].sids.indexOf(obj.sid); 46 if (index != -1) { 47 target_list[i].sids.splice(index, 1); 48 if (!this.state.locked) { 49 if (target_list[i].sids.length == 0) { 50 target_list.splice(i, 1); 51 } 52 } 53 } 54 break; 55 } 56 } 57 }, 58 onLockClicked: function (event) { 59 this.setState(function (state, props) { 60 return {locked: !state.locked}; 61 }); 62 this.saveCookies("locked", this.state.locked); 63 if (this.state.locked) { 64 var locked_mids = this.state.clients.map(function (event) {return event["mid"];}); 65 this.saveCookies("locked_mids", locked_mids); 66 } 67 }, 68 onTimeoutClicked: function (event) { 69 $("#timeout-dialog").modal(); 70 }, 71 getTimeout: function (event) { 72 return this.state.boot_timeout_secs; 73 }, 74 onTimeoutDialogSaveClicked: function (event) { 75 var new_timeout = Math.max(1, $("#boot_timeout_secs").val()); 76 this.setState({boot_timeout_secs: new_timeout}); 77 this.saveCookies("boot_timeout_secs", this.state.boot_timeout_secs); 78 }, 79 onLayoutClicked: function (event) { 80 $("#layout-dialog").modal(); 81 }, 82 onLayoutDialogSaveClicked: function (event) { 83 var nrow = $("#nrow").val(); 84 if (nrow < 1) { 85 nrow = 1; 86 } 87 var width = ($("#client-box-body").width() - 15) / nrow - 10; 88 89 // Hack: the last stylesheet is the oldest one 90 var st = document.styleSheets[document.styleSheets.length - 1]; 91 if (st.rules[0].selectorText == ".client-info") { 92 st.removeRule(0); 93 } 94 st.insertRule(".client-info { width: " + width + " !important }", 0); 95 }, 96 getInitialState: function () { 97 var locked = this.loadCookies("locked", false); 98 var clients = []; 99 if (locked) { 100 clients = this.loadCookies("locked_mids", []).map( 101 function (mid) {return {mid: mid, sids: [], status: "disconnected"}}); 102 } 103 return { 104 locked: locked, 105 clients: clients, 106 boot_timeout_secs: this.loadCookies("boot_timeout_secs", 60)}; 107 }, 108 componentDidMount: function () { 109 this.onLayoutDialogSaveClicked(); 110 this.loadClientsFromServer(); 111 setInterval(this.loadClientsFromServer, this.props.pollInterval); 112 113 var socket = io(window.location.protocol + "//" + window.location.host, 114 {path: "/api/socket.io/"}); 115 socket.on("logcat joined", function (msg) { 116 var obj = JSON.parse(msg); 117 118 if (typeof(this.refs["client-" + obj.mid]) != "undefined") { 119 this.refs["client-" + obj.mid].updateStatus("in-progress"); 120 } 121 122 this.setState(function (state, props) { 123 var client = state.clients.find(function (event, index, arr) { 124 return event.mid == obj.mid; 125 }); 126 if (typeof(client) == "undefined") { 127 if (!state.locked) { 128 state.clients.push({mid: obj.mid, sids: [obj.sid]}); 129 } 130 } else { 131 if (client.sids.indexOf(obj.sid) === -1) { 132 client.sids.push(obj.sid); 133 } 134 } 135 }); 136 }.bind(this)); 137 socket.on("logcat left", function (msg) { 138 var obj = JSON.parse(msg); 139 140 if (this.state.locked) { 141 this.refs["client-" + obj.mid].updateStatus("disconnected"); 142 } 143 this.setState(function (state, props) { 144 this.removeClientFromList(state.clients, obj); 145 }); 146 }.bind(this)); 147 }, 148 render: function() { 149 var lock_btn_class = this.state.locked ? "btn-danger" : "btn-primary"; 150 var lock_btn_text = this.state.locked ? "Unlock" : "Lock"; 151 return ( 152 <div id="main"> 153 <NavBar name="Factory Install Dashboard" url="/api/apps/list" /> 154 <div className="client-box panel panel-info"> 155 <div className="panel-heading"> 156 Clients 157 <div className="ctrl-btn-group"> 158 <button type="button" className="ctrl-btn btn btn-info" 159 onClick={this.onLayoutClicked}>Layout</button> 160 <button type="button" className="ctrl-btn btn btn-info" 161 onClick={this.onTimeoutClicked}>Timeout</button> 162 <button type="button" className={"ctrl-btn btn " + lock_btn_class} 163 onClick={this.onLockClicked}>{lock_btn_text}</button> 164 </div> 165 </div> 166 <div id="client-box-body" className="panel-body"> 167 { 168 this.state.clients.map(function (item) { 169 return ( 170 <ClientInfo key={item.mid} ref={"client-" + item.mid} data={item} root={this} getTimeout={this.getTimeout}> 171 {item.mid} 172 </ClientInfo> 173 ); 174 }.bind(this)) 175 } 176 </div> 177 </div> 178 179 <div id="timeout-dialog" className="modal fade"> 180 <div className="modal-dialog"> 181 <div className="modal-content"> 182 <div className="modal-header"> 183 <button type="button" className="close" data-dismiss="modal" aria-label="Close"> 184 <span aria-hidden="true">×</span></button> 185 <h4 className="modal-title">Timeout Settings</h4> 186 </div> 187 <div className="modal-body"> 188 Timeout seconds of the boot up: 189 <input id="boot_timeout_secs" type="number" className="form-control" defaultValue={this.state.boot_timeout_secs} min="1"></input> 190 </div> 191 <div className="modal-footer"> 192 <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> 193 <button type="button" className="btn btn-primary" data-dismiss="modal" 194 onClick={this.onTimeoutDialogSaveClicked}>Save changes</button> 195 </div> 196 </div> 197 </div> 198 </div> 199 <div id="layout-dialog" className="modal fade"> 200 <div className="modal-dialog"> 201 <div className="modal-content"> 202 <div className="modal-header"> 203 <button type="button" className="close" data-dismiss="modal" aria-label="Close"> 204 <span aria-hidden="true">×</span></button> 205 <h4 className="modal-title">Layout Settings</h4> 206 </div> 207 <div className="modal-body"> 208 Number of device in a row: 209 <input id="nrow" type="number" className="form-control" defaultValue="8" min="1"></input> 210 </div> 211 <div className="modal-footer"> 212 <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> 213 <button type="button" className="btn btn-primary" data-dismiss="modal" 214 onClick={this.onLayoutDialogSaveClicked}>Save changes</button> 215 </div> 216 </div> 217 </div> 218 </div> 219 </div> 220 ); 221 } 222 }); 223 224 var ClientInfo = React.createClass({ 225 getInitialState: function (event) { 226 if (typeof(this.props.data.status) != "undefined") { 227 return {status: this.props.data.status}; 228 } 229 return {status: "in-progress"}; 230 }, 231 updateStatus: function (status) { 232 this.setState({status: status}); 233 }, 234 onTagClick: function (event) { 235 var sid = $(event.target).data("sid"); 236 $(this.refs["term-" + sid].getDOMNode()).css("display", "block"); 237 }, 238 onPanelClick: function (event) { 239 // Workaround: crosbug.com/p/39839#11 240 // It take too long from DUT power up to enter kernel. The operator clicks the panel after 241 // power up the DUT, and shows error if the overlord doesn't connect the DUT in {boot_timeout_secs}. 242 if (this.state.status == "disconnected" || this.state.status == "no-connection") { 243 this.updateStatus("wait-connection"); 244 setTimeout(function () { 245 if (this.state.status == "wait-connection") { 246 this.updateStatus("no-connection"); 247 } 248 }.bind(this), this.props.getTimeout() * 1000); 249 } 250 }, 251 render: function () { 252 var statusClass = "panel-warning"; 253 if (this.state.status == "done") { 254 statusClass = "panel-success"; 255 } else if (this.state.status == "error") { 256 statusClass = "panel-danger"; 257 } else if (this.state.status == "disconnected") { 258 statusClass = "panel-default"; 259 } else if (this.state.status == "wait-connection") { 260 statusClass = "panel-warning"; 261 } else if (this.state.status == "no-connection") { 262 statusClass = "panel-danger"; 263 } 264 265 var message = ""; 266 if (this.state.status == "disconnected") { 267 message = "Click after power up"; 268 } else if (this.state.status == "wait-connection") { 269 message = "Waiting for connection"; 270 } else if (this.state.status == "no-connection") { 271 message = "Failed. Power-cycle DUT and click again."; 272 } 273 274 var onError = function (event) { 275 this.props.client.updateStatus("error"); 276 }; 277 278 var onMessage = function (data) { 279 if (data.indexOf("Factory Installer Complete") != -1) { 280 this.props.client.updateStatus("done"); 281 } else if (data.indexOf("\033[1;31m") != -1) { 282 this.props.client.updateStatus("error"); 283 } 284 }; 285 286 var onCloseClicked = function (event) { 287 var el = document.getElementById(this.props.id); 288 $(el).css("display", "none"); 289 }; 290 291 var mid = this.props.data.mid; 292 return ( 293 <div className={"client-info panel " + statusClass} onClick={this.onPanelClick}> 294 <div className="panel-heading">{this.props.children}</div> 295 <div className="panel-body"> 296 { 297 this.props.data.sids.map(function (sid) { 298 return ( 299 <div className="client-info-tag-container"> 300 <div className="client-info-tag"> 301 <span className="label label-warning client-info-terminal" 302 data-sid={sid} onClick={this.onTagClick}> 303 {sid} 304 </span> 305 </div> 306 <TerminalWindow key={sid} id={"terminal-" + mid + "-" + sid} 307 title={mid + " / " + sid} 308 path={"/api/log/" + mid + "/" + sid} 309 enableCopy={true} 310 onError={onError} onMessage={onMessage} 311 onCloseClicked={onCloseClicked} client={this} 312 ref={"term-" + sid} /> 313 </div> 314 ); 315 }.bind(this)) 316 } 317 {message} 318 </div> 319 </div> 320 ); 321 } 322 }); 323 324 ReactDOM.render( 325 <App url="/api/logcats/list" pollInterval={60000} />, 326 document.getElementById("body") 327 );