github.com/aitjcize/Overlord@v0.0.0-20240314041920-104a804cf5e8/overlord/app/fixture/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 // 11 // View for Fixture Dashboard App 12 // - App 13 // - NavBar 14 // - UploadProgressWidget 15 // - Paginator 16 // - [FixtureWidget ...] 17 18 // Identifier for selecting all clients 19 ALL = "All"; 20 21 var App = React.createClass({ 22 mixins: [BaseApp], 23 onNewClient: function (client) { 24 var prop = client.properties; 25 26 if (typeof(prop) == "undefined" || 27 typeof(prop.context) == "undefined" || 28 prop.context.indexOf("ui") === -1) { 29 return false; 30 } 31 32 if (typeof(prop.ui) != "undefined" && 33 typeof(prop.ui.group) != "undefined") { 34 if (this.state.groups.indexOf(prop.ui.group) == -1) { 35 this.setState(function (state, props) { 36 state.groups.push(prop.ui.group); 37 }); 38 } 39 } 40 41 return true; 42 }, 43 addTerminal: function (id, term) { 44 this.setState(function (state, props) { 45 state.terminals[id] = term; 46 }); 47 }, 48 removeTerminal: function (id) { 49 this.setState(function (state, props) { 50 if (typeof(state.terminals[id]) != "undefined") { 51 delete state.terminals[id]; 52 } 53 }); 54 }, 55 getInitialState: function () { 56 return {terminals: {}, groups: [ALL]}; 57 }, 58 componentWillMount: function () { 59 this.addOnNewClientHandler(this.onNewClient); 60 }, 61 componentDidMount: function () { 62 var socket = io(window.location.protocol + "//" + window.location.host, 63 {path: "/api/socket.io/"}); 64 socket.on("agent joined", function (msg) { 65 var obj = JSON.parse(msg); 66 this.addClient(obj); 67 }.bind(this)); 68 69 socket.on("agent left", function (msg) { 70 var obj = JSON.parse(msg); 71 this.removeClient(obj); 72 }.bind(this)); 73 74 // Initiate a file download 75 socket.on("file download", function (sid) { 76 var url = window.location.protocol + "//" + window.location.host + 77 "/api/file/download/" + sid; 78 $("<iframe id='" + sid + "' src='" + url + "' style='display:none'>" + 79 "</iframe>").appendTo('body'); 80 }); 81 this.socket = socket; 82 }, 83 computePageSize: function () { 84 // compute how many clients we can put in the screen 85 var screen = { 86 width: window.innerWidth, 87 }; 88 89 var nFixturePerRow = Math.floor( 90 screen.width / (FIXTURE_WINDOW_WIDTH + FIXTURE_WINDOW_MARGIN * 2)); 91 nFixturePerRow = Math.max(1, nFixturePerRow); 92 var nTotalFixture = Math.min(2 * nFixturePerRow, 8); 93 return nTotalFixture; 94 }, 95 render: function () { 96 var onControl = function (control) { 97 if (control.type == "sid") { 98 this.terminal_sid = control.data; 99 this.props.app.socket.emit("subscribe", control.data); 100 } 101 }; 102 var onCloseClicked = function (event) { 103 this.props.app.removeTerminal(this.props.id); 104 this.props.app.socket.emit("unsubscribe", this.terminal_sid); 105 }; 106 return ( 107 <div id="main"> 108 <NavBar name="Fixture Dashboard" url="/api/apps/list" /> 109 <div className="terminals"> 110 { 111 Object.keys(this.state.terminals).map(function (id) { 112 var term = this.state.terminals[id]; 113 var extra = ""; 114 if (typeof(term.path) != "undefined") { 115 extra = "?tty_device=" + term.path; 116 } 117 return ( 118 <TerminalWindow key={id} mid={term.mid} id={id} title={id} 119 path={"/api/agent/tty/" + term.mid + extra} 120 uploadRequestPath={"/api/agent/upload/" + term.mid} 121 enableMaximize={true} 122 app={this} progressBars={this.refs.uploadProgress} 123 onControl={onControl} onCloseClicked={onCloseClicked} /> 124 ); 125 }.bind(this)) 126 } 127 </div> 128 <div className="upload-progress"> 129 <UploadProgressWidget ref="uploadProgress"/> 130 </div> 131 <Paginator header="Clients" app={this} 132 pageSize={this.computePageSize()}> 133 { 134 this.getFilteredClientList().map(function (data) { 135 return ( 136 <FixtureWidget key={data.mid} client={data} app={this} 137 progressBars={this.refs.uploadProgress}/> 138 ); 139 }.bind(this)) 140 } 141 </Paginator> 142 </div> 143 ); 144 } 145 }); 146 147 Paginator = React.createClass({ 148 onKeyUp: function (event) { 149 this.props.app.setMidFilterPattern(this.refs.filter.value); 150 }, 151 changePage: function (i) { 152 this.setState(function (state, props) { 153 state.pageNumber = i; 154 }); 155 }, 156 getInitialState: function () { 157 return {pageNumber: 0, selectedGroup: ALL}; 158 }, 159 filterBySelectedGroup: function (client) { 160 var prop = client.properties; 161 if (this.state.selectedGroup == ALL) 162 return true; 163 164 if (typeof(prop.ui) == "undefined") 165 return false; 166 return prop.ui.group == this.state.selectedGroup; 167 }, 168 componentWillMount: function () { 169 this.props.app.addClientFilter(this.filterBySelectedGroup); 170 }, 171 onGroupSelected: function (e) { 172 this.setState(function (state, props) { 173 state.selectedGroup = e.target.hash.substring(1); 174 this.props.app.forceUpdate(); 175 }); 176 }, 177 render: function () { 178 var nPage = Math.ceil(this.props.children.length / this.props.pageSize); 179 var pageNumber = Math.max(Math.min(this.state.pageNumber, nPage - 1), 0); 180 var start = pageNumber * this.props.pageSize; 181 var end = start + this.props.pageSize; 182 var children = this.props.children.slice(start, end); 183 var pages = Array.apply(null, {length: nPage}).map(Number.call, Number); 184 return ( 185 <div className="app-box panel panel-info"> 186 <div className="panel-heading"> 187 <div className="container-fluid panel-container"> 188 <div className="col-xs-3 text-left"> 189 <h3 className="panel-title">{this.props.header}</h3> 190 <div className="dropdown group-dropdown"> 191 <button className="btn btn-default dropdown-toggle" 192 type="button" data-toggle="dropdown" 193 aria-haspopup="true" aria-expanded="true"> 194 {this.state.selectedGroup} 195 <span className="caret"></span> 196 </button> 197 <ul className="dropdown-menu"> 198 { 199 this.props.app.state.groups.map(function (i) { 200 return <li><a href={"#" + i} 201 onClick={this.onGroupSelected}>{i}</a></li> 202 }.bind(this)) 203 } 204 </ul> 205 </div> 206 </div> 207 <div className="col-xs-6 text-center"> 208 <ul className="pagination panel-pagination"> 209 <li> 210 <a href="#" aria-label="Previous" 211 onClick={this.changePage.bind(this, pageNumber - 1)}> 212 <span aria-hidden="true">«</span> 213 </a> 214 </li> 215 { 216 pages.map(function (i) { 217 var extra = {}; 218 if (i == pageNumber) { 219 extra.className = "active"; 220 } 221 return ( 222 <li key={i} {...extra}> 223 <a onClick={this.changePage.bind(this, i)} href="#"> 224 {i + 1} 225 </a> 226 </li> 227 ) 228 }.bind(this)) 229 } 230 <li> 231 <a href="#" aria-label="Next" 232 onClick={this.changePage.bind(this, pageNumber + 1)}> 233 <span aria-hidden="true">»</span> 234 </a> 235 </li> 236 </ul> 237 </div> 238 <div className="col-xs-3"> 239 <div className="col-xs-6 pull-right"> 240 <input type="text" ref="filter" placeholder="keyword" 241 className="filter-input form-control" 242 onKeyUp={this.onKeyUp} /> 243 </div> 244 </div> 245 </div> 246 </div> 247 <div className="panel-body"> 248 { 249 children.map(function (child) { 250 return child; 251 }.bind(this)) 252 } 253 </div> 254 </div> 255 ); 256 } 257 }); 258 259 ReactDOM.render( 260 <App url="/api/agents/list"/>, 261 document.getElementById("body") 262 );