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