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

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: Adjust font and layout a bit more 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 swarming-app- style">
38 bot-filters {
39 margin-bottom: 5px;
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 margin-left: 5px;
50 }
51 td, th {
52 border: 1px solid #DDD;
53 padding: 5px;
54 }
55
56 .quarantined, .bad-device {
57 background-color: #ffdddd;
58 }
59 .dead {
60 background-color: #cccccc;
61 }
62
63 th {
64 position: relative;
65 }
66 sort-toggle {
67 position: absolute;
68 right: 0;
69 top: 0.4em;
70 }
71 .bot-list th > span {
72 /* Leave space for sort-toggle*/
73 padding-right: 30px;
74 }
75 </style>
76
77 <swarming-app
78 auth_headers="{{auth_headers}}"
79 busy="[[busy]]"
80 name="Swarming Bot List">
81
82 <bot-filters
83 primary_map="[[primary_map]]"
84 primary_arr="[[primary_arr]]"
85
86 columns="{{columns}}"
87 filter="{{filter}}"
88 verbose="{{verbose}}">
89 </bot-filters>
90
91 <bot-list-data
92 auth_headers="[[auth_headers]]"
93
94 bots="{{bots}}"
95 busy="{{busy}}"
96 primary_map="{{primary_map}}"
97 primary_arr="{{primary_arr}}">
98 </bot-list-data>
99
100 <table class="bot-list">
101 <thead on-sort_change="sortChange">
102 <!-- To allow for dynamic columns without having a lot of copy-pasted
103 code, we break columns up into "special" and "plain" columns. Special
104 columns require some sort of HTML output (e.g. anchor tags) and plain
105 columns just output text. The plain columns use Polymer functions to
106 insert their text [_header(), _column(), _deviceColumn()]. Polymer
107 functions do not allow HTML (to avoid XSS), so special columns, like id
108 and task are inserted in a fixed order.
109 -->
110 <th>
111 <span>Bot Id</span>
112 <sort-toggle
113 name="id"
114 current="[[sort]]">
115 </sort-toggle>
116 </th>
117 <!-- This wonky syntax is the proper way to listen to changes on an
118 array (we are listening to all subproperties). The element returned is
119 not of much use, so we'll ignore it in _hide() and use this.columns.
120 -->
121 <th hidden$="[[_hide('task', columns.*)]]">
122 <span>Current Task</span>
123 <sort-toggle
124 name="task"
125 current="[[sort]]">
126 </sort-toggle>
127 </th>
128
129 <template is="dom-repeat"
130 items="[[plain_columns]]"
131 as="c">
132 <th hidden$="[[_hide(c)]]">
133 <span>[[_header(c)]]</span>
134 <sort-toggle
135 name="[[c]]"
136 current="[[sort]]">
137 </sort-toggle>
138 </th>
139 </template>
140 </thead>
141 <tbody>
142 <template id="bot_table" is="dom-repeat"
143 items="[[bots]]"
144 as="bot"
145 initial-count=50
146 filter="_filterBotTable">
147
148 <tr class$="[[_botClass(bot)]]">
149 <td>
150 <a class="center"
151 href$="[[_botLink(bot.bot_id)]]"
152 target="_blank">
153 [[bot.bot_id]]
154 </a>
155 </td>
156 <td hidden$="[[_hide('task', columns.*)]]">
157 <a href$="[[_taskLink(bot)]]">[[_taskId(bot)]]</a>
158 </td>
159
160 <template is="dom-repeat"
161 items="[[plain_columns]]"
162 as="c">
163 <td hidden$="[[_hide(c)]]">
164 [[_column(c, bot, verbose)]]
165 </td>
166 </template>
167
168 </tr>
169 <template is="dom-repeat"
170 items="[[_devices(bot)]]"
171 as="device">
172 <tr hidden$="[[_hide('devices', columns.*)]]"
173 class$="[[_deviceClass(device)]]">
174 <td></td>
175 <td hidden$="[[_hide('task', columns.*)]]"></td>
176 <template is="dom-repeat"
177 items="[[plain_columns]]"
178 as="c">
179 <td hidden$="[[_hide(c)]]">
180 [[_deviceColumn(c, device, verbose)]]
181 </td>
182 </template>
183 </tr>
184 </template> <!--devices repeat-->
185 </template> <!--bot-table repeat-->
186 </tbody>
187 </table>
188
189 </swarming-app>
190
191 </template>
192 <script>
193 (function(){
194 var special_columns = ["id", "task"];
195
196 var headerMap = {
197 // "id" and "task" are special, so they don't go here and have their
198 // headers hard-coded below.
199 "cores": "Cores",
200 "cpu": "CPU",
201 "devices": "Devices",
202 "gpu": "GPU",
203 "os": "OS",
204 "pool": "Pool",
205 "status": "Status",
206 };
207
208 // This maps column name to a function that will return the content for a
209 // given bot. These functions are bound to this element, and have access
210 // to all functions defined here and in bot-list-shared.
211 var columnMap = {
212 cores: function(bot){
213 var cores = this._cores(bot);
214 if (this.verbose){
215 return cores.join(" | ");
216 }
217 return cores[0];
218 },
219 cpu: function(bot){
220 var cpus = this._dimension(bot, 'cpu') || ['Unknown'];
221 if (this.verbose){
222 return cpus.join(" | ");
223 }
224 return cpus[0];
225 },
226 devices: function(bot){
227 return this._devices(bot).length + " devices attached";
228 },
229 gpu: function(bot){
230 var gpus = this._dimension(bot, 'gpu')
231 if (!gpus) {
232 return "none";
233 }
234 var verbose = []
235 var named = [];
236 // non-verbose mode has only the top level GPU info "e.g. NVidia"
237 // which is found by looking for gpu ids w/o a colon.
238 gpus.forEach(function(g){
239 var alias = this._gpuAlias(g);
240 if (alias === "UNKNOWN") {
241 verbose.push(g);
242 if (g.indexOf(":") === -1) {
243 named.push(g);
244 }
245 return;
246 }
247 verbose.push(this._applyAlias(g, alias));
248 if (g.indexOf(":") === -1) {
249 named.push(this._applyAlias(g, alias));
250 }
251 }.bind(this))
252 if (this.verbose) {
253 return verbose.join(" | ");
254 }
255 return named.join(" | ");
256 },
257 id: function(bot) {
258 return bot.bot_id;
259 },
260 os: function(bot) {
261 var os = this._dimension(bot, 'os') || ['Unknown'];
262 if (this.verbose){
263 return os.join(" | ");
264 }
265 return os[0];
266 },
267 pool: function(bot) {
268 var pool = this._dimension(bot, 'pool') || ['Unknown'];
269 return pool.join(" | ");
270 },
271 status: function(bot) {
272 // If a bot is both dead and quarantined, show the deadness over the
273 // quarentinedness.
274 if (bot.is_dead) {
275 return "Dead: " + bot.is_dead;
276 }
277 if (bot.quarantined) {
278 return "Quarantined: " + bot.quarantined;
279 }
280 return "Alive";
281 },
282 task: function(bot){
283 return this._taskId(bot);
284 },
285 };
286
287 Polymer({
288 is: 'bot-list',
289 behaviors: [SwarmingBehaviors.BotListBehavior],
290
291 properties: {
292
293 columns: {
294 type: Array,
295 },
296 // Should have a property "filter" which is a function.
297 filter: {
298 type: Object,
299 },
300
301 plain_columns: {
302 type: Array,
303 computed: "_stripSpecial(columns.*)",
304 },
305
306 // sort is an Object {name:String, direction:String}.
307 sort: {
308 type: Object,
309 },
310
311 verbose: {
312 type: Boolean,
313 }
314 },
315
316 observers: [
317 '_reRender(filter.*)',
318 '_checkSorts(columns.*)'
319 ],
320
321 _botClass: function(bot) {
322 if (bot.is_dead) {
323 return "dead";
324 }
325 if (bot.quarantined) {
326 return "quarantined";
327 }
328 return "";
329 },
330
331 _botLink: function(id) {
332 // TODO(kjlubick) Make this point to /newui/ when appropriate.
333 return "/restricted/bot/"+id;
334 },
335
336 // _checkSorts makes sure that if a column has been removed, the related
337 // sort is also removed.
338 _checkSorts: function() {
339 if (!this.sort) {
340 return;
341 }
342 this._reRender();
343 },
344
345 _column: function(col, bot) {
346 return columnMap[col].bind(this)(bot);
347 },
348
349 _deviceColumn: function(col, device) {
350 if (col === "devices") {
351 var str = this._androidAlias(device);
352 if (device.okay) {
353 str = this._applyAlias(this._deviceType(device), str);
354 }
355 str += " S/N:";
356 str += device.serial;
357 return str;
358 }
359 if (col === "status") {
360 return device.state;
361 }
362 return "";
363 },
364
365 _deviceClass: function(device) {
366 if (!device.okay) {
367 return "bad-device";
368 }
369 return "";
370 },
371
372 _filterBotTable: function(bot) {
373 if (!this.filter || !this.filter.filter) {
374 return true;
375 }
376 return this.filter.filter.bind(this)(bot);
377 },
378
379 _header: function(col){
380 return headerMap[col];
381 },
382
383 _hide: function(col) {
384 return this.columns.indexOf(col) === -1;
385 },
386
387 _reRender: function(filter, sort) {
388 this.$.bot_table.render();
389 },
390
391 _sortBotTable: function(botA, botB) {
392 if (!this.sort) {
393 return 0;
394 }
395 var dir = 1;
396 if (this.sort.direction === "desc") {
397 dir = -1;
398 }
399 var botACol = this._column(this.sort.name, botA);
400 var botBCol = this._column(this.sort.name, botB);
401
402 return dir * swarming.naturalCompare(botACol, botBCol);
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 this.set("sort", e.detail);
412 swarming.stableSort(this.bots, this._sortBotTable.bind(this));
413 this._reRender();
414 },
415
416 // _stripSpecial removes the special columns and sorts the remaining
417 // columns so they always appear in the same order, regardless of
418 // the order they are added.
419 _stripSpecial: function(){
420 return this.columns.filter(function(c){
421 return special_columns.indexOf(c) === -1;
422 }).sort();
423 },
424
425 _taskLink: function(data) {
426 if (data && data.task_id) {
427 return "/user/task/" + data.task_id;
428 }
429 return undefined;
430 }
431
432 });
433 })();
434 </script>
435 </dom-module>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698