| 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 10 matching lines...) Expand all Loading... |
| 21 | 21 |
| 22 Events: | 22 Events: |
| 23 None. | 23 None. |
| 24 --> | 24 --> |
| 25 | 25 |
| 26 <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-la
yout-classes.html"> | 26 <link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-la
yout-classes.html"> |
| 27 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> | 27 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html"> |
| 28 | 28 |
| 29 <link rel="import" href="/res/imp/common/sort-toggle.html"> | 29 <link rel="import" href="/res/imp/common/sort-toggle.html"> |
| 30 <link rel="import" href="/res/imp/common/swarming-app.html"> | 30 <link rel="import" href="/res/imp/common/swarming-app.html"> |
| 31 <link rel="import" href="/res/imp/common/url-param.html"> |
| 31 | 32 |
| 32 <link rel="import" href="bot-filters.html"> | 33 <link rel="import" href="bot-filters.html"> |
| 33 <link rel="import" href="bot-list-data.html"> | 34 <link rel="import" href="bot-list-data.html"> |
| 34 <link rel="import" href="bot-list-shared.html"> | 35 <link rel="import" href="bot-list-shared.html"> |
| 35 <link rel="import" href="bot-list-summary.html"> | 36 <link rel="import" href="bot-list-summary.html"> |
| 36 | 37 |
| 37 <dom-module id="bot-list"> | 38 <dom-module id="bot-list"> |
| 38 <template> | 39 <template> |
| 39 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-
style"> | 40 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-
style"> |
| 40 bot-filters, bot-list-summary { | 41 bot-filters, bot-list-summary { |
| (...skipping 29 matching lines...) Expand all Loading... |
| 70 position: absolute; | 71 position: absolute; |
| 71 right: 0; | 72 right: 0; |
| 72 top: 0.4em; | 73 top: 0.4em; |
| 73 } | 74 } |
| 74 .bot-list th > span { | 75 .bot-list th > span { |
| 75 /* Leave space for sort-toggle*/ | 76 /* Leave space for sort-toggle*/ |
| 76 padding-right: 30px; | 77 padding-right: 30px; |
| 77 } | 78 } |
| 78 </style> | 79 </style> |
| 79 | 80 |
| 81 <url-param name="sort" |
| 82 value="{{_sortstr}}" |
| 83 default_value="id:asc"> |
| 84 </url-param> |
| 85 |
| 80 <swarming-app | 86 <swarming-app |
| 81 client_id="[[client_id]]" | 87 client_id="[[client_id]]" |
| 82 auth_headers="{{_auth_headers}}" | 88 auth_headers="{{_auth_headers}}" |
| 83 signed_in="{{_signed_in}}" | 89 signed_in="{{_signed_in}}" |
| 84 | 90 |
| 85 busy="[[_busy]]" | 91 busy="[[_busy]]" |
| 86 name="Swarming Bot List"> | 92 name="Swarming Bot List"> |
| 87 | 93 |
| 88 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> | 94 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> |
| 89 | 95 |
| 90 <div hidden$="[[_not(_signed_in)]]"> | 96 <div hidden$="[[_not(_signed_in)]]"> |
| 91 | 97 |
| 92 <div class="horizontal layout"> | 98 <div class="horizontal layout"> |
| 93 | 99 |
| 94 <bot-filters | 100 <bot-filters |
| 101 dimensions="[[_dimensions]]" |
| 95 primary_map="[[_primary_map]]" | 102 primary_map="[[_primary_map]]" |
| 96 primary_arr="[[_primary_arr]]" | 103 primary_arr="[[_primary_arr]]" |
| 97 | 104 |
| 98 columns="{{_columns}}" | 105 columns="{{_columns}}" |
| 99 dimensions="{{_dimensions}}" | 106 query_params="{{_query_params}}" |
| 100 filter="{{_filter}}" | 107 filter="{{_filter}}" |
| 101 verbose="{{_verbose}}"> | 108 verbose="{{_verbose}}"> |
| 102 </bot-filters> | 109 </bot-filters> |
| 103 | 110 |
| 104 <bot-list-summary | 111 <bot-list-summary |
| 112 columns="[[_columns]]" |
| 105 fleet="[[_fleet]]" | 113 fleet="[[_fleet]]" |
| 106 filtered_bots="[[_filteredSortedBots]]"> | 114 filtered_bots="[[_filteredSortedBots]]" |
| 115 sort="[[_sortstr]]" |
| 116 verbose="[[_verbose]]"> |
| 107 </bot-list-summary> | 117 </bot-list-summary> |
| 108 | 118 |
| 109 </div> | 119 </div> |
| 110 | 120 |
| 111 <bot-list-data | 121 <bot-list-data |
| 112 auth_headers="[[_auth_headers]]" | 122 auth_headers="[[_auth_headers]]" |
| 113 dimensions="[[_dimensions]]" | 123 query_params="[[_query_params]]" |
| 114 | 124 |
| 115 bots="{{_bots}}" | 125 bots="{{_bots}}" |
| 116 busy="{{_busy}}" | 126 busy="{{_busy}}" |
| 127 dimensions="{{_dimensions}}" |
| 117 fleet="{{_fleet}}" | 128 fleet="{{_fleet}}" |
| 118 primary_map="{{_primary_map}}" | 129 primary_map="{{_primary_map}}" |
| 119 primary_arr="{{_primary_arr}}"> | 130 primary_arr="{{_primary_arr}}"> |
| 120 </bot-list-data> | 131 </bot-list-data> |
| 121 | 132 |
| 122 <table class="bot-list"> | 133 <table class="bot-list"> |
| 123 <thead on-sort_change="_sortChange"> | 134 <thead on-sort_change="_sortChange"> |
| 124 <!-- To allow for dynamic columns without having a lot of copy-pasted | 135 <!-- To allow for dynamic columns without having a lot of copy-pasted |
| 125 code, we break columns up into "special" and "plain" columns. Special | 136 code, we break columns up into "special" and "plain" columns. Special |
| 126 columns require some sort of HTML output (e.g. anchor tags) and plain | 137 columns require some sort of HTML output (e.g. anchor tags) and plain |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 216 <script> | 227 <script> |
| 217 (function(){ | 228 (function(){ |
| 218 var special_columns = ["id", "task"]; | 229 var special_columns = ["id", "task"]; |
| 219 | 230 |
| 220 var headerMap = { | 231 var headerMap = { |
| 221 // "id" and "task" are special, so they don't go here and have their | 232 // "id" and "task" are special, so they don't go here and have their |
| 222 // headers hard-coded below. | 233 // headers hard-coded below. |
| 223 "android_devices": "Android Devices", | 234 "android_devices": "Android Devices", |
| 224 "cores": "Cores", | 235 "cores": "Cores", |
| 225 "cpu": "CPU", | 236 "cpu": "CPU", |
| 237 "device": "Non-android Device", |
| 226 "device_os": "Device OS", | 238 "device_os": "Device OS", |
| 227 "device_type": "Device Type", | 239 "device_type": "Device Type", |
| 228 "disk_space": "Free Space (MB)", | 240 "disk_space": "Free Space (MB)", |
| 229 "gpu": "GPU", | 241 "gpu": "GPU", |
| 230 "os": "OS", | 242 "os": "OS", |
| 231 "pool": "Pool", | 243 "pool": "Pool", |
| 232 "status": "Status", | 244 "status": "Status", |
| 245 "xcode_version": "XCode Version", |
| 233 }; | 246 }; |
| 234 | 247 |
| 235 // This maps column name to a function that will return the content for a | 248 // This maps column name to a function that will return the content for a |
| 236 // given bot. These functions are bound to this element, and have access | 249 // given bot. These functions are bound to this element, and have access |
| 237 // to all functions defined here and in bot-list-shared. | 250 // to all functions defined here and in bot-list-shared. If a column |
| 251 // is not listed here, a sane default will be used (see _column()). |
| 238 var columnMap = { | 252 var columnMap = { |
| 239 android_devices: function(bot) { | 253 android_devices: function(bot) { |
| 240 var devs = this._attribute(bot, "android_devices", "0"); | 254 var devs = this._attribute(bot, "android_devices", "0"); |
| 241 if (this._verbose) { | 255 if (this._verbose) { |
| 242 return devs.join(" | ") + " devices available"; | 256 return devs.join(" | ") + " devices available"; |
| 243 } | 257 } |
| 244 // max() works on strings as long as they can be coerced to Number. | 258 // max() works on strings as long as they can be coerced to Number. |
| 245 return Math.max(...devs) + " devices available"; | 259 return Math.max(...devs) + " devices available"; |
| 246 }, | 260 }, |
| 247 cores: function(bot){ | 261 device_type: function(bot) { |
| 248 var cores = this._attribute(bot, "cores"); | 262 var dt = this._attribute(bot, "device_type", "none"); |
| 249 if (this._verbose){ | 263 dt = dt[0]; |
| 250 return cores.join(" | "); | 264 var alias = this._androidAlias(dt); |
| 265 if (alias === "unknown") { |
| 266 return dt; |
| 251 } | 267 } |
| 252 return cores[0]; | 268 return this._applyAlias(dt, alias); |
| 253 }, | |
| 254 cpu: function(bot){ | |
| 255 var cpus = this._attribute(bot, "cpu"); | |
| 256 if (this._verbose){ | |
| 257 return cpus.join(" | "); | |
| 258 } | |
| 259 return cpus[0]; | |
| 260 }, | |
| 261 device_os: function(bot){ | |
| 262 var os = this._attribute(bot, "device_os", "none"); | |
| 263 if (this._verbose) { | |
| 264 return os.join(" | "); | |
| 265 } | |
| 266 return os[0]; | |
| 267 }, | |
| 268 device_type: function(bot){ | |
| 269 var dt = this._attribute(bot, "device_type", "none"); | |
| 270 if (this._verbose) { | |
| 271 return dt.join(" | "); | |
| 272 } | |
| 273 return dt[0]; | |
| 274 }, | 269 }, |
| 275 disk_space: function(bot) { | 270 disk_space: function(bot) { |
| 276 var aliased = []; | 271 var aliased = []; |
| 277 bot.disks.forEach(function(disk){ | 272 bot.disks.forEach(function(disk){ |
| 278 var alias = swarming.humanBytes(disk.mb, swarming.MB); | 273 var alias = sk.human.bytes(disk.mb, swarming.MB); |
| 279 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias)); | 274 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias)); |
| 280 }.bind(this)); | 275 }.bind(this)); |
| 281 if (this._verbose) { | 276 if (this._verbose) { |
| 282 return aliased.join(" | "); | 277 return aliased.join(" | "); |
| 283 } | 278 } |
| 284 return aliased[0]; | 279 return aliased[0]; |
| 285 }, | 280 }, |
| 286 gpu: function(bot){ | 281 gpu: function(bot){ |
| 287 var gpus = this._attribute(bot, "gpu", "none") | 282 var gpus = this._attribute(bot, "gpu", "none") |
| 288 var verbose = [] | 283 var verbose = [] |
| (...skipping 15 matching lines...) Expand all Loading... |
| 304 } | 299 } |
| 305 }.bind(this)) | 300 }.bind(this)) |
| 306 if (this._verbose) { | 301 if (this._verbose) { |
| 307 return verbose.join(" | "); | 302 return verbose.join(" | "); |
| 308 } | 303 } |
| 309 return named.join(" | "); | 304 return named.join(" | "); |
| 310 }, | 305 }, |
| 311 id: function(bot) { | 306 id: function(bot) { |
| 312 return bot.bot_id; | 307 return bot.bot_id; |
| 313 }, | 308 }, |
| 314 os: function(bot) { | |
| 315 var os = this._attribute(bot, "os"); | |
| 316 if (this._verbose){ | |
| 317 return os.join(" | "); | |
| 318 } | |
| 319 return os[0]; | |
| 320 }, | |
| 321 pool: function(bot) { | 309 pool: function(bot) { |
| 322 var pool = this._attribute(bot, "pool"); | 310 var pool = this._attribute(bot, "pool"); |
| 323 return pool.join(" | "); | 311 return pool.join(" | "); |
| 324 }, | 312 }, |
| 325 status: function(bot) { | 313 status: function(bot) { |
| 326 // If a bot is both dead and quarantined, show the deadness over the | 314 // If a bot is both dead and quarantined, show the deadness over the |
| 327 // quarentinedness. | 315 // quarentinedness. |
| 328 if (bot.is_dead) { | 316 if (bot.is_dead) { |
| 329 return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) + | 317 return "Dead. Last seen " + sk.human.diffDate(bot.last_seen_ts) + |
| 330 " ago"; | 318 " ago"; |
| 331 } | 319 } |
| 332 if (bot.quarantined) { | 320 if (bot.quarantined) { |
| 333 return "Quarantined: " + this._attribute(bot, "quarantined"); | 321 return "Quarantined: " + this._attribute(bot, "quarantined"); |
| 334 } | 322 } |
| 335 return "Alive"; | 323 return "Alive"; |
| 336 }, | 324 }, |
| 337 task: function(bot){ | 325 task: function(bot){ |
| 338 return this._taskId(bot); | 326 return this._taskId(bot); |
| 339 }, | 327 }, |
| (...skipping 16 matching lines...) Expand all Loading... |
| 356 return "unknown"; | 344 return "unknown"; |
| 357 }, | 345 }, |
| 358 status: function(device) { | 346 status: function(device) { |
| 359 return device.state; | 347 return device.state; |
| 360 } | 348 } |
| 361 } | 349 } |
| 362 | 350 |
| 363 // specialSort defines any custom sorting rules. By default, a | 351 // specialSort defines any custom sorting rules. By default, a |
| 364 // naturalCompare of the column content is done. | 352 // naturalCompare of the column content is done. |
| 365 var specialSort = { | 353 var specialSort = { |
| 366 device_type: function(dir, botA, botB) { | 354 android_devices: function(dir, botA, botB) { |
| 367 // We sort on the number of attached devices. Note that this | 355 // We sort on the number of attached devices. Note that this |
| 368 // may not be the same as android_devices, because _devices().length | 356 // may not be the same as android_devices, because _devices().length |
| 369 // counts all devices plugged into the bot, whereas android_devices | 357 // counts all devices plugged into the bot, whereas android_devices |
| 370 // counts just devices ready for work. | 358 // counts just devices ready for work. |
| 371 var botACol = this._devices(botA).length; | 359 var botACol = this._devices(botA).length; |
| 372 var botBCol = this._devices(botB).length; | 360 var botBCol = this._devices(botB).length; |
| 373 return dir * swarming.naturalCompare(botACol, botBCol); | 361 return dir * swarming.naturalCompare(botACol, botBCol); |
| 374 }, | 362 }, |
| 375 disk_space: function(dir, botA, botB) { | 363 disk_space: function(dir, botA, botB) { |
| 376 // We sort based on the raw number of MB of the first disk. | 364 // We sort based on the raw number of MB of the first disk. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 411 }, | 399 }, |
| 412 | 400 |
| 413 _plain_columns: { | 401 _plain_columns: { |
| 414 type: Array, | 402 type: Array, |
| 415 computed: "_stripSpecial(_columns.*)", | 403 computed: "_stripSpecial(_columns.*)", |
| 416 }, | 404 }, |
| 417 | 405 |
| 418 // _sort is an Object {name:String, direction:String}. | 406 // _sort is an Object {name:String, direction:String}. |
| 419 _sort: { | 407 _sort: { |
| 420 type: Object, | 408 type: Object, |
| 421 value: function() { | 409 computed: "_makeObject(_sortstr)", |
| 422 return { | |
| 423 name: "id", | |
| 424 direction: "asc", | |
| 425 }; | |
| 426 } | |
| 427 }, | 410 }, |
| 428 | 411 |
| 429 _verbose: { | 412 _verbose: { |
| 430 type: Boolean, | 413 type: Boolean, |
| 431 } | 414 } |
| 432 }, | 415 }, |
| 433 | 416 |
| 434 _botClass: function(bot) { | 417 _botClass: function(bot) { |
| 435 if (bot.is_dead) { | 418 if (bot.is_dead) { |
| 436 return "dead"; | 419 return "dead"; |
| 437 } | 420 } |
| 438 if (bot.quarantined) { | 421 if (bot.quarantined) { |
| 439 return "quarantined"; | 422 return "quarantined"; |
| 440 } | 423 } |
| 441 return ""; | 424 return ""; |
| 442 }, | 425 }, |
| 443 | 426 |
| 444 _botLink: function(id) { | 427 _botLink: function(id) { |
| 445 // TODO(kjlubick) Make this point to /newui/ when appropriate. | 428 // TODO(kjlubick) Make this point to /newui/ when appropriate. |
| 446 return "/restricted/bot/"+id; | 429 return "/restricted/bot/"+id; |
| 447 }, | 430 }, |
| 448 | 431 |
| 449 | 432 |
| 450 _column: function(col, bot) { | 433 _column: function(col, bot) { |
| 451 return columnMap[col].bind(this)(bot); | 434 var f = columnMap[col]; |
| 435 if (!f) { |
| 436 f = function(bot) { |
| 437 var c = this._attribute(bot, col, "none"); |
| 438 if (this._verbose) { |
| 439 return c.join(" | "); |
| 440 } |
| 441 return c[0]; |
| 442 } |
| 443 } |
| 444 return f.bind(this)(bot); |
| 452 }, | 445 }, |
| 453 | 446 |
| 454 _androidAliasDevice: function(device) { | 447 _androidAliasDevice: function(device) { |
| 455 if (device.notReady) { | 448 if (device.notReady) { |
| 456 return UNAUTHENTICATED.toUpperCase(); | 449 return UNAUTHENTICATED.toUpperCase(); |
| 457 } | 450 } |
| 458 return this._androidAlias(this._deviceType(device)); | 451 return this._androidAlias(this._deviceType(device)); |
| 459 }, | 452 }, |
| 460 | 453 |
| 461 _deviceColumn: function(col, device) { | 454 _deviceColumn: function(col, device) { |
| (...skipping 18 matching lines...) Expand all Loading... |
| 480 swarming.stableSort(this._bots, this._sortBotTable.bind(this)); | 473 swarming.stableSort(this._bots, this._sortBotTable.bind(this)); |
| 481 var bots = this._bots; | 474 var bots = this._bots; |
| 482 if (this._filter) { | 475 if (this._filter) { |
| 483 bots = bots.filter(this._filter.bind(this)); | 476 bots = bots.filter(this._filter.bind(this)); |
| 484 } | 477 } |
| 485 | 478 |
| 486 return bots; | 479 return bots; |
| 487 }, | 480 }, |
| 488 | 481 |
| 489 _header: function(col){ | 482 _header: function(col){ |
| 490 return headerMap[col]; | 483 return headerMap[col] || col; |
| 491 }, | 484 }, |
| 492 | 485 |
| 493 _hide: function(col) { | 486 _hide: function(col) { |
| 494 return this._columns.indexOf(col) === -1; | 487 return this._columns.indexOf(col) === -1; |
| 495 }, | 488 }, |
| 496 | 489 |
| 490 _makeObject: function(sortstr){ |
| 491 if (!sortstr) { |
| 492 return undefined; |
| 493 } |
| 494 var pieces = sortstr.split(":"); |
| 495 if (pieces.length != 2) { |
| 496 // fail safe |
| 497 return {name: "id", direction:"desc"}; |
| 498 } |
| 499 return { |
| 500 name: pieces[0], |
| 501 direction: pieces[1], |
| 502 } |
| 503 }, |
| 504 |
| 497 _reRender: function(filter, sort) { | 505 _reRender: function(filter, sort) { |
| 498 this.$.bot_table.render(); | 506 this.$.bot_table.render(); |
| 499 }, | 507 }, |
| 500 | 508 |
| 501 _sortBotTable: function(botA, botB) { | 509 _sortBotTable: function(botA, botB) { |
| 502 if (!this._sort) { | 510 if (!this._sort) { |
| 503 return 0; | 511 return 0; |
| 504 } | 512 } |
| 505 var dir = 1; | 513 var dir = 1; |
| 506 if (this._sort.direction === "desc") { | 514 if (this._sort.direction === "desc") { |
| 507 dir = -1; | 515 dir = -1; |
| 508 } | 516 } |
| 509 var sort = specialSort[this._sort.name]; | 517 var sort = specialSort[this._sort.name]; |
| 510 if (sort) { | 518 if (sort) { |
| 511 return sort.bind(this)(dir, botA, botB); | 519 return sort.bind(this)(dir, botA, botB); |
| 512 } | 520 } |
| 513 // Default to a natural compare of the columns. | 521 // Default to a natural compare of the columns. |
| 514 var botACol = this._column(this._sort.name, botA); | 522 var botACol = this._column(this._sort.name, botA); |
| 515 var botBCol = this._column(this._sort.name, botB); | 523 var botBCol = this._column(this._sort.name, botB); |
| 516 | 524 |
| 517 return dir * swarming.naturalCompare(botACol, botBCol); | 525 return dir * swarming.naturalCompare(botACol, botBCol); |
| 518 }, | 526 }, |
| 519 | 527 |
| 520 _sortChange: function(e) { | 528 _sortChange: function(e) { |
| 521 // The event we get from sort-toggle tells us the name of what needs | 529 // The event we get from sort-toggle tells us the name of what needs |
| 522 // to be sorting and how to sort it. | 530 // to be sorting and how to sort it. |
| 523 if (!(e && e.detail && e.detail.name)) { | 531 if (!(e && e.detail && e.detail.name)) { |
| 524 return; | 532 return; |
| 525 } | 533 } |
| 526 // should trigger __filterAndSort | 534 // should trigger the computation of _sort and __filterAndSort |
| 527 this.set("_sort", e.detail); | 535 this.set("_sortstr", e.detail.name +":"+e.detail.direction); |
| 528 }, | 536 }, |
| 529 | 537 |
| 530 // _stripSpecial removes the special columns and sorts the remaining | 538 // _stripSpecial removes the special columns and sorts the remaining |
| 531 // columns so they always appear in the same order, regardless of | 539 // columns so they always appear in the same order, regardless of |
| 532 // the order they are added. | 540 // the order they are added. |
| 533 _stripSpecial: function(){ | 541 _stripSpecial: function(){ |
| 534 return this._columns.filter(function(c){ | 542 return this._columns.filter(function(c){ |
| 535 return special_columns.indexOf(c) === -1; | 543 return special_columns.indexOf(c) === -1; |
| 536 }).sort(); | 544 }).sort(); |
| 537 }, | 545 }, |
| 538 | 546 |
| 539 _taskLink: function(data) { | 547 _taskLink: function(data) { |
| 540 if (data && data.task_id) { | 548 if (data && data.task_id) { |
| 541 return "/user/task/" + data.task_id; | 549 return "/user/task/" + data.task_id; |
| 542 } | 550 } |
| 543 return undefined; | 551 return undefined; |
| 544 } | 552 } |
| 545 | 553 |
| 546 }); | 554 }); |
| 547 })(); | 555 })(); |
| 548 </script> | 556 </script> |
| 549 </dom-module> | 557 </dom-module> |
| OLD | NEW |