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">&times;</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">&times;</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  );