Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 <!-- | 1 <!-- |
| 2 Copyright 2016 The LUCI Authors. All rights reserved. | 2 Copyright 2016 The LUCI Authors. All rights reserved. |
| 3 Use of this source code is governed under the Apache License, Version 2.0 | 3 Use of this source code is governed under the Apache License, Version 2.0 |
| 4 that can be found in the LICENSE file. | 4 that can be found in the LICENSE file. |
| 5 | 5 |
| 6 This in an HTML Import-able file that contains the definition | 6 This in an HTML Import-able file that contains the definition |
| 7 of the following elements: | 7 of the following elements: |
| 8 | 8 |
| 9 <bot-list> | 9 <bot-list> |
| 10 | 10 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 23 | 23 |
| 24 <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-la yout-classes.html"> | 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"> | 25 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> |
| 26 | 26 |
| 27 <link rel="import" href="/res/imp/common/sort-toggle.html"> | 27 <link rel="import" href="/res/imp/common/sort-toggle.html"> |
| 28 <link rel="import" href="/res/imp/common/swarming-app.html"> | 28 <link rel="import" href="/res/imp/common/swarming-app.html"> |
| 29 | 29 |
| 30 <link rel="import" href="bot-filters.html"> | 30 <link rel="import" href="bot-filters.html"> |
| 31 <link rel="import" href="bot-list-data.html"> | 31 <link rel="import" href="bot-list-data.html"> |
| 32 <link rel="import" href="bot-list-shared.html"> | 32 <link rel="import" href="bot-list-shared.html"> |
| 33 | 33 <link rel="import" href="bot-list-summary.html"> |
| 34 | 34 |
| 35 <dom-module id="bot-list"> | 35 <dom-module id="bot-list"> |
| 36 <template> | 36 <template> |
| 37 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style"> | 37 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style"> |
| 38 bot-filters { | 38 bot-filters, bot-list-summary { |
| 39 margin-bottom: 5px; | 39 margin-bottom: 5px; |
| 40 margin-right: 5px; | |
| 40 } | 41 } |
| 41 .bot { | 42 .bot { |
| 42 margin:5px; | 43 margin:5px; |
| 43 max-width:400px; | 44 max-width:400px; |
| 44 min-height:100px; | 45 min-height:100px; |
| 45 min-width:300px; | 46 min-width:300px; |
| 46 } | 47 } |
| 47 table { | 48 table { |
| 48 border-collapse: collapse; | 49 border-collapse: collapse; |
| 49 margin-left: 5px; | 50 margin-left: 5px; |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 69 top: 0.4em; | 70 top: 0.4em; |
| 70 } | 71 } |
| 71 .bot-list th > span { | 72 .bot-list th > span { |
| 72 /* Leave space for sort-toggle*/ | 73 /* Leave space for sort-toggle*/ |
| 73 padding-right: 30px; | 74 padding-right: 30px; |
| 74 } | 75 } |
| 75 </style> | 76 </style> |
| 76 | 77 |
| 77 <swarming-app | 78 <swarming-app |
| 78 auth_headers="{{auth_headers}}" | 79 auth_headers="{{auth_headers}}" |
| 80 signed_in="{{signed_in}}" | |
| 81 | |
| 79 busy="[[busy]]" | 82 busy="[[busy]]" |
| 80 name="Swarming Bot List"> | 83 name="Swarming Bot List"> |
| 81 | 84 |
| 82 <bot-filters | 85 <h2 hidden$="[[signed_in]]">You must sign in to see anything useful.</h2> |
| 83 primary_map="[[primary_map]]" | |
| 84 primary_arr="[[primary_arr]]" | |
| 85 | 86 |
| 86 columns="{{columns}}" | 87 <div hidden$="[[_not(signed_in)]]"> |
| 87 filter="{{filter}}" | |
| 88 verbose="{{verbose}}"> | |
| 89 </bot-filters> | |
| 90 | 88 |
| 91 <bot-list-data | 89 <div class="horizontal layout"> |
| 92 auth_headers="[[auth_headers]]" | |
| 93 | 90 |
| 94 bots="{{bots}}" | 91 <bot-filters |
| 95 busy="{{busy}}" | 92 primary_map="[[primary_map]]" |
| 96 primary_map="{{primary_map}}" | 93 primary_arr="[[primary_arr]]" |
| 97 primary_arr="{{primary_arr}}"> | |
| 98 </bot-list-data> | |
| 99 | 94 |
| 100 <table class="bot-list"> | 95 columns="{{columns}}" |
| 101 <thead on-sort_change="sortChange"> | 96 filter="{{filter}}" |
| 102 <!-- To allow for dynamic columns without having a lot of copy-pasted | 97 verbose="{{verbose}}"> |
| 103 code, we break columns up into "special" and "plain" columns. Special | 98 </bot-filters> |
| 104 columns require some sort of HTML output (e.g. anchor tags) and plain | 99 |
| 105 columns just output text. The plain columns use Polymer functions to | 100 <bot-list-summary |
| 106 insert their text [_header(), _column(), _deviceColumn()]. Polymer | 101 fleet="[[fleet]]" |
| 107 functions do not allow HTML (to avoid XSS), so special columns, like id | 102 filtered_bots="[[filteredSortedBots]]"> |
| 108 and task are inserted in a fixed order. | 103 </bot-list-summary> |
| 109 --> | 104 |
| 110 <th> | 105 </div> |
| 111 <span>Bot Id</span> | 106 |
| 112 <sort-toggle | 107 <bot-list-data |
| 113 name="id" | 108 auth_headers="[[auth_headers]]" |
| 114 current="[[sort]]"> | 109 |
| 115 </sort-toggle> | 110 bots="{{bots}}" |
| 116 </th> | 111 busy="{{busy}}" |
| 117 <!-- This wonky syntax is the proper way to listen to changes on an | 112 fleet="{{fleet}}" |
| 118 array (we are listening to all subproperties). The element returned is | 113 primary_map="{{primary_map}}" |
| 119 not of much use, so we'll ignore it in _hide() and use this.columns. | 114 primary_arr="{{primary_arr}}"> |
| 115 </bot-list-data> | |
| 116 | |
| 117 <table class="bot-list"> | |
| 118 <thead on-sort_change="sortChange"> | |
|
stephana
2016/08/02 14:00:29
There should be a <tr> element wrapping the <th> e
kjlubick
2016/08/03 12:52:30
Done.
| |
| 119 <!-- To allow for dynamic columns without having a lot of copy-pasted | |
| 120 code, we break columns up into "special" and "plain" columns. Special | |
| 121 columns require some sort of HTML output (e.g. anchor tags) and plain | |
| 122 columns just output text. The plain columns use Polymer functions to | |
| 123 insert their text [_header(), _column(), _deviceColumn()]. Polymer | |
| 124 functions do not allow HTML (to avoid XSS), so special columns, like i d | |
| 125 and task are inserted in a fixed order. | |
| 120 --> | 126 --> |
| 121 <th hidden$="[[_hide('task', columns.*)]]"> | 127 <th> |
| 122 <span>Current Task</span> | 128 <span>Bot Id</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 | 129 <sort-toggle |
| 135 name="[[c]]" | 130 name="id" |
| 136 current="[[sort]]"> | 131 current="[[sort]]"> |
| 137 </sort-toggle> | 132 </sort-toggle> |
| 138 </th> | 133 </th> |
| 139 </template> | 134 <!-- This wonky syntax is the proper way to listen to changes on an |
| 140 </thead> | 135 array (we are listening to all subproperties). The element returned is |
| 141 <tbody> | 136 not of much use, so we'll ignore it in _hide() and use this.columns. |
| 142 <template id="bot_table" is="dom-repeat" | 137 --> |
| 143 items="[[bots]]" | 138 <th hidden$="[[_hide('task', columns.*)]]"> |
| 144 as="bot" | 139 <span>Current Task</span> |
| 145 initial-count=50 | 140 <sort-toggle |
| 146 filter="_filterBotTable"> | 141 name="task" |
| 142 current="[[sort]]"> | |
| 143 </sort-toggle> | |
| 144 </th> | |
| 147 | 145 |
| 148 <tr class$="[[_botClass(bot)]]"> | 146 <template is="dom-repeat" |
| 149 <td> | 147 items="[[plain_columns]]" |
| 150 <a class="center" | 148 as="c"> |
| 151 href$="[[_botLink(bot.bot_id)]]" | 149 <th hidden$="[[_hide(c)]]"> |
| 152 target="_blank"> | 150 <span>[[_header(c)]]</span> |
| 153 [[bot.bot_id]] | 151 <sort-toggle |
| 154 </a> | 152 name="[[c]]" |
| 155 </td> | 153 current="[[sort]]"> |
| 156 <td hidden$="[[_hide('task', columns.*)]]"> | 154 </sort-toggle> |
| 157 <a href$="[[_taskLink(bot)]]">[[_taskId(bot)]]</a> | 155 </th> |
| 158 </td> | 156 </template> |
| 157 </thead> | |
| 158 <tbody> | |
| 159 <template id="bot_table" is="dom-repeat" | |
| 160 items="[[filteredSortedBots]]" | |
| 161 as="bot" | |
| 162 initial-count=50> | |
| 159 | 163 |
| 160 <template is="dom-repeat" | 164 <tr class$="[[_botClass(bot)]]"> |
| 161 items="[[plain_columns]]" | 165 <td> |
| 162 as="c"> | 166 <a class="center" |
| 163 <td hidden$="[[_hide(c)]]"> | 167 href$="[[_botLink(bot.bot_id)]]" |
| 164 [[_column(c, bot, verbose)]] | 168 target="_blank"> |
| 169 [[bot.bot_id]] | |
| 170 </a> | |
| 165 </td> | 171 </td> |
| 166 </template> | 172 <td hidden$="[[_hide('task', columns.*)]]"> |
| 173 <a href$="[[_taskLink(bot)]]">[[_taskId(bot)]]</a> | |
| 174 </td> | |
| 167 | 175 |
| 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" | 176 <template is="dom-repeat" |
| 177 items="[[plain_columns]]" | 177 items="[[plain_columns]]" |
| 178 as="c"> | 178 as="c"> |
| 179 <td hidden$="[[_hide(c)]]"> | 179 <td hidden$="[[_hide(c)]]"> |
| 180 [[_deviceColumn(c, device, verbose)]] | 180 [[_column(c, bot, verbose)]] |
| 181 </td> | 181 </td> |
| 182 </template> | 182 </template> |
| 183 | |
| 183 </tr> | 184 </tr> |
| 184 </template> <!--devices repeat--> | 185 <template is="dom-repeat" |
| 185 </template> <!--bot-table repeat--> | 186 items="[[_devices(bot)]]" |
| 186 </tbody> | 187 as="device"> |
| 187 </table> | 188 <tr hidden$="[[_hide('devices', columns.*)]]" |
| 189 class$="[[_deviceClass(device)]]"> | |
| 190 <td></td> | |
| 191 <td hidden$="[[_hide('task', columns.*)]]"></td> | |
| 192 <template is="dom-repeat" | |
| 193 items="[[plain_columns]]" | |
| 194 as="c"> | |
| 195 <td hidden$="[[_hide(c)]]"> | |
| 196 [[_deviceColumn(c, device, verbose)]] | |
| 197 </td> | |
| 198 </template> | |
| 199 </tr> | |
| 200 </template> <!--devices repeat--> | |
| 201 </template> <!--bot-table repeat--> | |
| 202 </tbody> | |
| 203 </table> | |
| 204 </div> | |
| 188 | 205 |
| 189 </swarming-app> | 206 </swarming-app> |
| 190 | 207 |
| 191 </template> | 208 </template> |
| 192 <script> | 209 <script> |
| 193 (function(){ | 210 (function(){ |
| 194 var special_columns = ["id", "task"]; | 211 var special_columns = ["id", "task"]; |
| 195 | 212 |
| 196 var headerMap = { | 213 var headerMap = { |
| 197 // "id" and "task" are special, so they don't go here and have their | 214 // "id" and "task" are special, so they don't go here and have their |
| (...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 283 return this._taskId(bot); | 300 return this._taskId(bot); |
| 284 }, | 301 }, |
| 285 }; | 302 }; |
| 286 | 303 |
| 287 Polymer({ | 304 Polymer({ |
| 288 is: 'bot-list', | 305 is: 'bot-list', |
| 289 behaviors: [SwarmingBehaviors.BotListBehavior], | 306 behaviors: [SwarmingBehaviors.BotListBehavior], |
| 290 | 307 |
| 291 properties: { | 308 properties: { |
| 292 | 309 |
| 310 bots: { | |
| 311 type: Array, | |
| 312 }, | |
| 313 | |
| 293 columns: { | 314 columns: { |
| 294 type: Array, | 315 type: Array, |
| 295 }, | 316 }, |
| 296 // Should have a property "filter" which is a function. | 317 |
| 297 filter: { | 318 filter: { |
|
stephana
2016/08/02 14:00:29
If these are exposed properties then they should b
kjlubick
2016/08/03 12:52:30
Done.
| |
| 298 type: Object, | 319 type: Function, |
| 320 value: function() { | |
| 321 return true; | |
| 322 }, | |
| 323 }, | |
| 324 | |
| 325 filteredSortedBots: { | |
| 326 type: Array, | |
| 327 computed: "_filterAndSort(bots,filter.*,sort.*)" | |
| 299 }, | 328 }, |
| 300 | 329 |
| 301 plain_columns: { | 330 plain_columns: { |
| 302 type: Array, | 331 type: Array, |
| 303 computed: "_stripSpecial(columns.*)", | 332 computed: "_stripSpecial(columns.*)", |
| 304 }, | 333 }, |
| 305 | 334 |
| 306 // sort is an Object {name:String, direction:String}. | 335 // sort is an Object {name:String, direction:String}. |
| 307 sort: { | 336 sort: { |
| 308 type: Object, | 337 type: Object, |
| 338 value: function() { | |
| 339 return { | |
| 340 name: "id", | |
| 341 direction: "asc", | |
| 342 }; | |
| 343 } | |
| 309 }, | 344 }, |
| 310 | 345 |
| 311 verbose: { | 346 verbose: { |
| 312 type: Boolean, | 347 type: Boolean, |
| 313 } | 348 } |
| 314 }, | 349 }, |
| 315 | 350 |
| 316 observers: [ | |
| 317 '_reRender(filter.*)', | |
| 318 '_checkSorts(columns.*)' | |
| 319 ], | |
| 320 | |
| 321 _botClass: function(bot) { | 351 _botClass: function(bot) { |
| 322 if (bot.is_dead) { | 352 if (bot.is_dead) { |
| 323 return "dead"; | 353 return "dead"; |
| 324 } | 354 } |
| 325 if (bot.quarantined) { | 355 if (bot.quarantined) { |
| 326 return "quarantined"; | 356 return "quarantined"; |
| 327 } | 357 } |
| 328 return ""; | 358 return ""; |
| 329 }, | 359 }, |
| 330 | 360 |
| 331 _botLink: function(id) { | 361 _botLink: function(id) { |
| 332 // TODO(kjlubick) Make this point to /newui/ when appropriate. | 362 // TODO(kjlubick) Make this point to /newui/ when appropriate. |
| 333 return "/restricted/bot/"+id; | 363 return "/restricted/bot/"+id; |
| 334 }, | 364 }, |
| 335 | 365 |
| 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 | 366 |
| 345 _column: function(col, bot) { | 367 _column: function(col, bot) { |
| 346 return columnMap[col].bind(this)(bot); | 368 return columnMap[col].bind(this)(bot); |
| 347 }, | 369 }, |
| 348 | 370 |
| 349 _deviceColumn: function(col, device) { | 371 _deviceColumn: function(col, device) { |
| 350 if (col === "devices") { | 372 if (col === "devices") { |
| 351 var str = this._androidAlias(device); | 373 var str = this._androidAlias(device); |
| 352 if (device.okay) { | 374 if (device.okay) { |
| 353 str = this._applyAlias(this._deviceType(device), str); | 375 str = this._applyAlias(this._deviceType(device), str); |
| 354 } | 376 } |
| 355 str += " S/N:"; | 377 str += " S/N:"; |
| 356 str += device.serial; | 378 str += device.serial; |
| 357 return str; | 379 return str; |
| 358 } | 380 } |
| 359 if (col === "status") { | 381 if (col === "status") { |
| 360 return device.state; | 382 return device.state; |
| 361 } | 383 } |
| 362 return ""; | 384 return ""; |
| 363 }, | 385 }, |
| 364 | 386 |
| 365 _deviceClass: function(device) { | 387 _deviceClass: function(device) { |
| 366 if (!device.okay) { | 388 if (!device.okay) { |
| 367 return "bad-device"; | 389 return "bad-device"; |
| 368 } | 390 } |
| 369 return ""; | 391 return ""; |
| 370 }, | 392 }, |
| 371 | 393 |
| 372 _filterBotTable: function(bot) { | 394 _filterAndSort: function(a,b,c) { |
| 373 if (!this.filter || !this.filter.filter) { | 395 // We intentionally sort this.bots (and not a copy) to allow users to |
| 374 return true; | 396 // "chain" sorts, that is, sort by one thing and then another, and |
| 397 // have both orderings properly impact the list. | |
| 398 swarming.stableSort(this.bots, this._sortBotTable.bind(this)); | |
| 399 var bots = this.bots; | |
| 400 if (this.filter) { | |
| 401 bots = bots.filter(this.filter.bind(this)); | |
| 375 } | 402 } |
| 376 return this.filter.filter.bind(this)(bot); | 403 |
| 404 return bots; | |
| 377 }, | 405 }, |
| 378 | 406 |
| 379 _header: function(col){ | 407 _header: function(col){ |
| 380 return headerMap[col]; | 408 return headerMap[col]; |
| 381 }, | 409 }, |
| 382 | 410 |
| 383 _hide: function(col) { | 411 _hide: function(col) { |
| 384 return this.columns.indexOf(col) === -1; | 412 return this.columns.indexOf(col) === -1; |
| 385 }, | 413 }, |
| 386 | 414 |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 401 | 429 |
| 402 return dir * swarming.naturalCompare(botACol, botBCol); | 430 return dir * swarming.naturalCompare(botACol, botBCol); |
| 403 }, | 431 }, |
| 404 | 432 |
| 405 sortChange: function(e) { | 433 sortChange: function(e) { |
| 406 // The event we get from sort-toggle tells us the name of what needs | 434 // The event we get from sort-toggle tells us the name of what needs |
| 407 // to be sorting and how to sort it. | 435 // to be sorting and how to sort it. |
| 408 if (!(e && e.detail && e.detail.name)) { | 436 if (!(e && e.detail && e.detail.name)) { |
| 409 return; | 437 return; |
| 410 } | 438 } |
| 439 // should trigger __filterAndSort | |
| 411 this.set("sort", e.detail); | 440 this.set("sort", e.detail); |
| 412 swarming.stableSort(this.bots, this._sortBotTable.bind(this)); | |
| 413 this._reRender(); | |
| 414 }, | 441 }, |
| 415 | 442 |
| 416 // _stripSpecial removes the special columns and sorts the remaining | 443 // _stripSpecial removes the special columns and sorts the remaining |
| 417 // columns so they always appear in the same order, regardless of | 444 // columns so they always appear in the same order, regardless of |
| 418 // the order they are added. | 445 // the order they are added. |
| 419 _stripSpecial: function(){ | 446 _stripSpecial: function(){ |
| 420 return this.columns.filter(function(c){ | 447 return this.columns.filter(function(c){ |
| 421 return special_columns.indexOf(c) === -1; | 448 return special_columns.indexOf(c) === -1; |
| 422 }).sort(); | 449 }).sort(); |
| 423 }, | 450 }, |
| 424 | 451 |
| 425 _taskLink: function(data) { | 452 _taskLink: function(data) { |
| 426 if (data && data.task_id) { | 453 if (data && data.task_id) { |
| 427 return "/user/task/" + data.task_id; | 454 return "/user/task/" + data.task_id; |
| 428 } | 455 } |
| 429 return undefined; | 456 return undefined; |
| 430 } | 457 } |
| 431 | 458 |
| 432 }); | 459 }); |
| 433 })(); | 460 })(); |
| 434 </script> | 461 </script> |
| 435 </dom-module> | 462 </dom-module> |
| OLD | NEW |