Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Side by Side Diff: appengine/swarming/elements/res/imp/botlist/bot-list.html

Issue 2182693002: Add new botlist for swarming (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@app-wrapper
Patch Set: Add documentation Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 <!--
2 Copyright 2016 The LUCI Authors. All rights reserved.
3 Use of this source code is governed under the Apache License, Version 2.0
4 that can be found in the LICENSE file.
5
6 This in an HTML Import-able file that contains the definition
7 of the following elements:
8
9 <bot-list>
10
11 bot-list creats a dynamic table for viewing swarming bots. Columns can be
12 dynamically filtered and it supports client-side filtering.
13
14 Properties:
15 None. This is a top-level element.
16
17 Methods:
18 None.
19
20 Events:
21 None.
22 -->
23
24 <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-la yout-classes.html">
25 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
26
27 <link rel="import" href="/res/imp/common/sort-toggle.html">
28 <link rel="import" href="/res/imp/common/swarming-app.html">
29
30 <link rel="import" href="bot-filters.html">
31 <link rel="import" href="bot-list-data.html">
32 <link rel="import" href="bot-list-shared.html">
33
34
35 <dom-module id="bot-list">
36 <template>
37 <style include="iron-flex iron-flex-alignment iron-positioning">
38 bot-filters {
39 margin-bottom:2px;
40 }
41 .bot {
42 margin:5px;
43 max-width:400px;
44 min-height:100px;
45 min-width:300px;
46 }
47 table {
48 border-collapse: collapse;
49 border: 1px solid black;
50 }
51 td, th {
52 border: 1px solid black;
53 padding: 5px;
54 }
55
56 .bot-list th > span {
57 display:inline-block;
58 }
59
60 .quarantined, .bad-device {
61 background-color: #ffdddd;
62 border: 2px solid black;
63 }
64 .dead {
65 background-color: #cccccc;
66 border: 2px solid black;
67 }
68 </style>
69
70 <swarming-app
71 auth_headers="{{auth_headers}}"
72 busy="[[busy]]"
73 name="Swarming Bot List">
74
75 <bot-filters
76 primary_map="[[primary_map]]"
77 primary_arr="[[primary_arr]]"
78
79 columns="{{columns}}"
80 filter="{{filter}}"
81 verbose="{{verbose}}">
82 </bot-filters>
83
84 <bot-list-data
85 auth_headers="[[auth_headers]]"
86
87 bots="{{bots}}"
88 busy="{{busy}}"
89 primary_map="{{primary_map}}"
90 primary_arr="{{primary_arr}}">
91 </bot-list-data>
92
93 <table class="bot-list">
94 <thead on-sort_change="sortChange">
95 <!-- To allow for dynamic columns without having a lot of copy-pasted
96 code, we break columns up into "special" and "plain" columns. Special
97 columns require some sort of HTML output (e.g. anchor tags) and plain
98 columns just output text. The plain columns use Polymer functions to
99 insert their text [_header(), _column(), _deviceColumn()]. Polymer
100 functions do not allow HTML (to avoid XSS), so special columns, like id
101 and task are inserted in a fixed order.
102 -->
103 <th>
104 <span>Bot Id</span>
105 <sort-toggle name="id"></sort-toggle>
106 </th>
107 <!-- This wonky syntax is the proper way to listen to changes on an
108 array (we are listening to all subproperties). The element returned is
109 not of much use, so we'll ignore it in _hide() and use this.columns.
110 -->
111 <th hidden$="[[_hide('task', columns.*)]]">
112 <span>Current Task</span>
113 <sort-toggle name="task"></sort-toggle>
114 </th>
115
116 <template is="dom-repeat"
117 items="[[plain_columns]]"
118 as="c">
119 <th hidden$="[[_hide(c)]]">
120 <span>[[_header(c)]]</span>
121 <sort-toggle name="[[c]]"></sort-toggle>
122 </th>
123 </template>
124 </thead>
125 <tbody>
126 <template id="bot_table" is="dom-repeat"
127 items="[[bots]]"
128 as="bot"
129 initial-count=50
130 sort="_sortBotTable"
131 filter="_filterBotTable">
132
133 <tr class$="[[_botClass(bot)]]">
134 <td>
135 <a class="center"
136 href$="[[_botLink(bot.bot_id)]]"
137 target="_blank">
138 [[bot.bot_id]]
139 </a>
140 </td>
141 <td hidden$="[[_hide('task', columns.*)]]">
142 Current Task:
143 <a href$="[[_taskLink(bot)]]">[[_taskId(bot)]]</a>
144 </td>
145
146 <template is="dom-repeat"
147 items="[[plain_columns]]"
148 as="c">
149 <td hidden$="[[_hide(c)]]">
150 [[_column(c, bot, verbose)]]
151 </td>
152 </template>
153
154 </tr>
155 <template is="dom-repeat"
156 items="[[_devices(bot)]]"
157 as="device">
158 <tr hidden$="[[_hide('devices', columns.*)]]"
159 class$="[[_deviceClass(device)]]">
160 <td></td>
161 <td hidden$="[[_hide('task', columns.*)]]"></td>
162 <template is="dom-repeat"
163 items="[[plain_columns]]"
164 as="c">
165 <td hidden$="[[_hide(c)]]">
166 [[_deviceColumn(c, device, verbose)]]
167 </td>
168 </template>
169 </tr>
170 </template> <!--devices repeat-->
171 </template> <!--bot-table repeat-->
172 </tbody>
173 </table>
174
175 </swarming-app>
176
177 </template>
178 <script>
179 (function(){
180 var special_columns = ["id", "task"];
181
182 var headerMap = {
183 // "id" and "task" are special, so they don't go here and have their
184 // headers hard-coded below.
185 "cores": "Cores",
186 "cpu": "CPU",
187 "devices": "Devices",
188 "gpu": "GPU",
189 "os": "OS",
190 "pool": "Pool",
191 "status": "Status",
192 };
193
194 // This maps column name to a function that will return the content for a
195 // given bot. These functions are bound to this element, and have access
196 // to all functions defined here and in bot-list-shared.
197 var columnMap = {
198 cores: function(bot){
199 var cores = this._cores(bot);
200 if (this.verbose){
201 return cores.join(" | ");
202 }
203 return cores[0];
204 },
205 cpu: function(bot){
206 var cpus = this._dimension(bot, 'cpu') || ['Unknown'];
207 if (this.verbose){
208 return cpus.join(" | ");
209 }
210 return cpus[0];
211 },
212 devices: function(bot){
213 return this._devices(bot).length + " devices attached";
214 },
215 gpu: function(bot){
216 var gpus = this._dimension(bot, 'gpu')
217 if (!gpus) {
218 return "none";
219 }
220 var verbose = []
221 var named = [];
222 // non-verbose mode has only the top level GPU info "e.g. NVidia"
223 // which is found by looking for gpu ids w/o a colon.
224 gpus.forEach(function(g){
225 var alias = this._gpuAlias(g);
226 if (alias === "UNKNOWN") {
227 verbose.push(g);
228 if (g.indexOf(":") === -1) {
229 named.push(g);
230 }
231 return;
232 }
233 verbose.push(this._applyAlias(g, alias));
234 if (g.indexOf(":") === -1) {
235 named.push(this._applyAlias(g, alias));
236 }
237 }.bind(this))
238 if (this.verbose) {
239 return verbose.join(" | ");
240 }
241 return named.join(" | ");
242 },
243 id: function(bot) {
244 return bot.bot_id;
245 },
246 os: function(bot) {
247 var os = this._dimension(bot, 'os') || ['Unknown'];
248 if (this.verbose){
249 return os.join(" | ");
250 }
251 return os[0];
252 },
253 pool: function(bot) {
254 var pool = this._dimension(bot, 'pool') || ['Unknown'];
255 return pool.join(" | ");
256 },
257 status: function(bot) {
258 // If a bot is both dead and quarantined, show the deadness over the
259 // quarentinedness.
260 if (bot.is_dead) {
261 return "Dead: " + bot.is_dead;
262 }
263 if (bot.quarantined) {
264 return "Quarantined: " + bot.quarantined;
265 }
266 return "Alive";
267 },
268 task: function(bot){
269 return this._taskId(bot);
270 },
271 };
272
273 Polymer({
274 is: 'bot-list',
275 behaviors: [SwarmingBehaviors.BotListBehavior],
276
277 properties: {
278
279 columns: {
280 type: Array,
281 },
282 // Should have a property "filter" which is a function.
283 filter: {
284 type: Object,
285 },
286
287 plain_columns: {
288 type: Array,
289 computed: "_stripSpecial(columns.*)",
290 },
291
292 // sorts is an array of objects showing how to sort the table.
293 sorts: {
294 type: Array,
295 },
296
297 verbose: {
298 type: Boolean,
299 }
300 },
301
302 observers: [
303 '_reRender(filter.*)',
304 '_checkSorts(columns.*)'
305 ],
306
307 _botClass: function(bot) {
308 if (bot.is_dead) {
309 return "dead";
310 }
311 if (bot.quarantined) {
312 return "quarantined";
313 }
314 return "";
315 },
316
317 _botLink: function(id) {
318 // TODO(kjlubick) Make this point to /newui/ when appropriate.
319 return "/restricted/bot/"+id;
320 },
321
322 // _checkSorts makes sure that if a column has been removed, the related
323 // sort is also removed.
324 _checkSorts: function() {
325 if (!this.sorts) {
326 return;
327 }
328 this.sorts = this.sorts.filter(function(s){
329 return this.columns.indexOf(s) !== -1;
330 }.bind(this));
331 this._reRender();
332 },
333
334 _column: function(col, bot) {
335 return columnMap[col].bind(this)(bot);
336 },
337
338 _deviceColumn: function(col, device) {
339 if (col === "devices") {
340 var str = this._androidAlias(device);
341 if (device.okay) {
342 str = this._applyAlias(this._deviceType(device), str);
343 }
344 str += " S/N:";
345 str += device.serial;
346 return str;
347 }
348 if (col === "status") {
349 return device.state;
350 }
351 return "";
352 },
353
354 _deviceClass: function(device) {
355 if (!device.okay) {
356 return "bad-device";
357 }
358 return "";
359 },
360
361 _filterBotTable: function(bot) {
362 if (!this.filter || !this.filter.filter) {
363 return true;
364 }
365 return this.filter.filter.bind(this)(bot);
366 },
367
368 _header: function(col){
369 return headerMap[col];
370 },
371
372 _hide: function(col) {
373 return this.columns.indexOf(col) === -1;
374 },
375
376 _reRender: function(filter, sort) {
377 this.$.bot_table.render();
378 },
379
380 _sortBotTable: function(botA, botB) {
381 if (!this.sorts) {
382 return 0;
383 }
384 for (var i = 0; i < this.sorts.length; i++) {
385 var s = this.sorts[i];
386 var botACol = this._column(s.name, botA);
387 var botBCol = this._column(s.name, botB);
388 // s.sort is either -1 or 1 (if it is 0, it should have been removed f rom this list).
389 var sort = s.sort * botACol.localeCompare(botBCol);
390 // Try numeric, aka "natural" sort and use it if ns is not NaN.
391 // Javascript will try to corece these to numbers or return NaN.
392 var ns = botACol - botBCol;
393 if (ns) {
394 sort = s.sort * ns;
395 }
396 // If sort is non-zero, we are done sorting. Otherwise, we'll have to
397 // got to the next sorting critera.
398 if (sort) {
399 return sort;
400 }
401 }
402 return 0;
403 },
404
405 sortChange: function(e) {
406 // The event we get from sort-toggle tells us the name of what needs
407 // to be sorting and how to sort it.
408 if (!(e && e.detail && e.detail.name)) {
409 return;
410 }
411 var sorts = this.sorts || [];
412 var found = false;
413 for (var i = 0; i < sorts.length; i++) {
414 if (sorts[i].name === e.detail.name) {
415 // e.detail.sort is 1, 0, -1 for ascending/nothing/descending.
416 sorts[i].sort = e.detail.sort;
417 found = true;
418 break;
419 }
420 }
421 if (!found) {
422 sorts.push(e.detail);
423 }
424
425 this.sorts = sorts;
426 this._reRender();
427 },
428
429 // _stripSpecial removes the special columns and sorts the remaining
430 // columns so they always appear in the same order, regardless of
431 // the order they are added.
432 _stripSpecial: function(){
433 return this.columns.filter(function(c){
434 return special_columns.indexOf(c) === -1;
435 }).sort();
436 },
437
438 _taskLink: function(data) {
439 if (data && data.task_id) {
440 return "/user/task/" + data.task_id;
441 }
442 return undefined;
443 }
444
445 });
446 })();
447 </script>
448 </dom-module>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698