| 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 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 86 | 86 |
| 87 <div hidden$="[[_not(_signed_in)]]"> | 87 <div hidden$="[[_not(_signed_in)]]"> |
| 88 | 88 |
| 89 <div class="horizontal layout"> | 89 <div class="horizontal layout"> |
| 90 | 90 |
| 91 <bot-filters | 91 <bot-filters |
| 92 primary_map="[[_primary_map]]" | 92 primary_map="[[_primary_map]]" |
| 93 primary_arr="[[_primary_arr]]" | 93 primary_arr="[[_primary_arr]]" |
| 94 | 94 |
| 95 columns="{{_columns}}" | 95 columns="{{_columns}}" |
| 96 dimensions="{{_dimensions}}" |
| 96 filter="{{_filter}}" | 97 filter="{{_filter}}" |
| 97 verbose="{{_verbose}}"> | 98 verbose="{{_verbose}}"> |
| 98 </bot-filters> | 99 </bot-filters> |
| 99 | 100 |
| 100 <bot-list-summary | 101 <bot-list-summary |
| 101 fleet="[[_fleet]]" | 102 fleet="[[_fleet]]" |
| 102 filtered_bots="[[_filteredSortedBots]]"> | 103 filtered_bots="[[_filteredSortedBots]]"> |
| 103 </bot-list-summary> | 104 </bot-list-summary> |
| 104 | 105 |
| 105 </div> | 106 </div> |
| 106 | 107 |
| 107 <bot-list-data | 108 <bot-list-data |
| 108 auth_headers="[[_auth_headers]]" | 109 auth_headers="[[_auth_headers]]" |
| 110 dimensions="[[_dimensions]]" |
| 109 | 111 |
| 110 bots="{{_bots}}" | 112 bots="{{_bots}}" |
| 111 busy="{{_busy}}" | 113 busy="{{_busy}}" |
| 112 fleet="{{_fleet}}" | 114 fleet="{{_fleet}}" |
| 113 primary_map="{{_primary_map}}" | 115 primary_map="{{_primary_map}}" |
| 114 primary_arr="{{_primary_arr}}"> | 116 primary_arr="{{_primary_arr}}"> |
| 115 </bot-list-data> | 117 </bot-list-data> |
| 116 | 118 |
| 117 <table class="bot-list"> | 119 <table class="bot-list"> |
| 118 <thead on-sort_change="_sortChange"> | 120 <thead on-sort_change="_sortChange"> |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 180 as="c"> | 182 as="c"> |
| 181 <td hidden$="[[_hide(c)]]"> | 183 <td hidden$="[[_hide(c)]]"> |
| 182 [[_column(c, bot, _verbose)]] | 184 [[_column(c, bot, _verbose)]] |
| 183 </td> | 185 </td> |
| 184 </template> | 186 </template> |
| 185 | 187 |
| 186 </tr> | 188 </tr> |
| 187 <template is="dom-repeat" | 189 <template is="dom-repeat" |
| 188 items="[[_devices(bot)]]" | 190 items="[[_devices(bot)]]" |
| 189 as="device"> | 191 as="device"> |
| 190 <tr hidden$="[[_hide('devices', _columns.*)]]" | 192 <tr hidden$="[[_hide('android_devices', _columns.*)]]" |
| 191 class$="[[_deviceClass(device)]]"> | 193 class$="[[_deviceClass(device)]]"> |
| 192 <td></td> | 194 <td></td> |
| 193 <td hidden$="[[_hide('task', _columns.*)]]"></td> | 195 <td hidden$="[[_hide('task', _columns.*)]]"></td> |
| 194 <template is="dom-repeat" | 196 <template is="dom-repeat" |
| 195 items="[[_plain_columns]]" | 197 items="[[_plain_columns]]" |
| 196 as="c"> | 198 as="c"> |
| 197 <td hidden$="[[_hide(c)]]"> | 199 <td hidden$="[[_hide(c)]]"> |
| 198 [[_deviceColumn(c, device, _verbose)]] | 200 [[_deviceColumn(c, device, _verbose)]] |
| 199 </td> | 201 </td> |
| 200 </template> | 202 </template> |
| 201 </tr> | 203 </tr> |
| 202 </template> <!--devices repeat--> | 204 </template> <!--devices repeat--> |
| 203 </template> <!--bot-table repeat--> | 205 </template> <!--bot-table repeat--> |
| 204 </tbody> | 206 </tbody> |
| 205 </table> | 207 </table> |
| 206 </div> | 208 </div> |
| 207 | 209 |
| 208 </swarming-app> | 210 </swarming-app> |
| 209 | 211 |
| 210 </template> | 212 </template> |
| 211 <script> | 213 <script> |
| 212 (function(){ | 214 (function(){ |
| 213 var special_columns = ["id", "task"]; | 215 var special_columns = ["id", "task"]; |
| 214 | 216 |
| 215 var headerMap = { | 217 var headerMap = { |
| 216 // "id" and "task" are special, so they don't go here and have their | 218 // "id" and "task" are special, so they don't go here and have their |
| 217 // headers hard-coded below. | 219 // headers hard-coded below. |
| 220 "android_devices": "Android Devices", |
| 218 "cores": "Cores", | 221 "cores": "Cores", |
| 219 "cpu": "CPU", | 222 "cpu": "CPU", |
| 220 "devices": "Devices", | 223 "device_os": "Device OS", |
| 224 "device_type": "Device Type", |
| 225 "disk_space": "Free Space (MB)", |
| 221 "gpu": "GPU", | 226 "gpu": "GPU", |
| 222 "os": "OS", | 227 "os": "OS", |
| 223 "pool": "Pool", | 228 "pool": "Pool", |
| 224 "status": "Status", | 229 "status": "Status", |
| 225 }; | 230 }; |
| 226 | 231 |
| 227 // This maps column name to a function that will return the content for a | 232 // This maps column name to a function that will return the content for a |
| 228 // given bot. These functions are bound to this element, and have access | 233 // given bot. These functions are bound to this element, and have access |
| 229 // to all functions defined here and in bot-list-shared. | 234 // to all functions defined here and in bot-list-shared. |
| 230 var columnMap = { | 235 var columnMap = { |
| 236 android_devices: function(bot) { |
| 237 var devs = this._attribute(bot, "android_devices", "0"); |
| 238 if (this._verbose) { |
| 239 return devs.join(" | ") + " devices available"; |
| 240 } |
| 241 // max() works on strings as long as they can be coerced to Number. |
| 242 return Math.max(...devs) + " devices available"; |
| 243 }, |
| 231 cores: function(bot){ | 244 cores: function(bot){ |
| 232 var cores = this._cores(bot); | 245 var cores = this._attribute(bot, "cores"); |
| 233 if (this._verbose){ | 246 if (this._verbose){ |
| 234 return cores.join(" | "); | 247 return cores.join(" | "); |
| 235 } | 248 } |
| 236 return cores[0]; | 249 return cores[0]; |
| 237 }, | 250 }, |
| 238 cpu: function(bot){ | 251 cpu: function(bot){ |
| 239 var cpus = this._dimension(bot, 'cpu') || ['Unknown']; | 252 var cpus = this._attribute(bot, "cpu"); |
| 240 if (this._verbose){ | 253 if (this._verbose){ |
| 241 return cpus.join(" | "); | 254 return cpus.join(" | "); |
| 242 } | 255 } |
| 243 return cpus[0]; | 256 return cpus[0]; |
| 244 }, | 257 }, |
| 245 devices: function(bot){ | 258 device_os: function(bot){ |
| 246 return this._devices(bot).length + " devices attached"; | 259 var os = this._attribute(bot, "device_os", "none"); |
| 260 if (this._verbose) { |
| 261 return os.join(" | "); |
| 262 } |
| 263 return os[0]; |
| 264 }, |
| 265 device_type: function(bot){ |
| 266 var dt = this._attribute(bot, "device_type", "none"); |
| 267 if (this._verbose) { |
| 268 return dt.join(" | "); |
| 269 } |
| 270 return dt[0]; |
| 271 }, |
| 272 disk_space: function(bot) { |
| 273 var aliased = []; |
| 274 bot.disks.forEach(function(disk){ |
| 275 var alias = swarming.humanBytes(disk.mb, swarming.MB); |
| 276 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias)); |
| 277 }.bind(this)); |
| 278 if (this._verbose) { |
| 279 return aliased.join(" | "); |
| 280 } |
| 281 return aliased[0]; |
| 247 }, | 282 }, |
| 248 gpu: function(bot){ | 283 gpu: function(bot){ |
| 249 var gpus = this._dimension(bot, 'gpu') | 284 var gpus = this._attribute(bot, "gpu", "none") |
| 250 if (!gpus) { | |
| 251 return "none"; | |
| 252 } | |
| 253 var verbose = [] | 285 var verbose = [] |
| 254 var named = []; | 286 var named = []; |
| 255 // non-verbose mode has only the top level GPU info "e.g. NVidia" | 287 // non-verbose mode has only the top level GPU info "e.g. NVidia" |
| 256 // which is found by looking for gpu ids w/o a colon. | 288 // which is found by looking for gpu ids w/o a colon. |
| 257 gpus.forEach(function(g){ | 289 gpus.forEach(function(g){ |
| 258 var alias = this._gpuAlias(g); | 290 var alias = this._gpuAlias(g); |
| 259 if (alias === "UNKNOWN") { | 291 if (alias === "unknown") { |
| 260 verbose.push(g); | 292 verbose.push(g); |
| 261 if (g.indexOf(":") === -1) { | 293 if (g.indexOf(":") === -1) { |
| 262 named.push(g); | 294 named.push(g); |
| 263 } | 295 } |
| 264 return; | 296 return; |
| 265 } | 297 } |
| 266 verbose.push(this._applyAlias(g, alias)); | 298 verbose.push(this._applyAlias(g, alias)); |
| 267 if (g.indexOf(":") === -1) { | 299 if (g.indexOf(":") === -1) { |
| 268 named.push(this._applyAlias(g, alias)); | 300 named.push(this._applyAlias(g, alias)); |
| 269 } | 301 } |
| 270 }.bind(this)) | 302 }.bind(this)) |
| 271 if (this._verbose) { | 303 if (this._verbose) { |
| 272 return verbose.join(" | "); | 304 return verbose.join(" | "); |
| 273 } | 305 } |
| 274 return named.join(" | "); | 306 return named.join(" | "); |
| 275 }, | 307 }, |
| 276 id: function(bot) { | 308 id: function(bot) { |
| 277 return bot.bot_id; | 309 return bot.bot_id; |
| 278 }, | 310 }, |
| 279 os: function(bot) { | 311 os: function(bot) { |
| 280 var os = this._dimension(bot, 'os') || ['Unknown']; | 312 var os = this._attribute(bot, "os"); |
| 281 if (this._verbose){ | 313 if (this._verbose){ |
| 282 return os.join(" | "); | 314 return os.join(" | "); |
| 283 } | 315 } |
| 284 return os[0]; | 316 return os[0]; |
| 285 }, | 317 }, |
| 286 pool: function(bot) { | 318 pool: function(bot) { |
| 287 var pool = this._dimension(bot, 'pool') || ['Unknown']; | 319 var pool = this._attribute(bot, "pool"); |
| 288 return pool.join(" | "); | 320 return pool.join(" | "); |
| 289 }, | 321 }, |
| 290 status: function(bot) { | 322 status: function(bot) { |
| 291 // If a bot is both dead and quarantined, show the deadness over the | 323 // If a bot is both dead and quarantined, show the deadness over the |
| 292 // quarentinedness. | 324 // quarentinedness. |
| 293 if (bot.is_dead) { | 325 if (bot.is_dead) { |
| 294 return "Dead: " + bot.is_dead; | 326 return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) + |
| 327 " ago"; |
| 295 } | 328 } |
| 296 if (bot.quarantined) { | 329 if (bot.quarantined) { |
| 297 return "Quarantined: " + bot.quarantined; | 330 return "Quarantined: " + this._attribute(bot, "quarantined"); |
| 298 } | 331 } |
| 299 return "Alive"; | 332 return "Alive"; |
| 300 }, | 333 }, |
| 301 task: function(bot){ | 334 task: function(bot){ |
| 302 return this._taskId(bot); | 335 return this._taskId(bot); |
| 303 }, | 336 }, |
| 304 }; | 337 }; |
| 305 | 338 |
| 339 var deviceColumnMap = { |
| 340 android_devices: function(device) { |
| 341 var str = this._androidAliasDevice(device); |
| 342 if (device.okay) { |
| 343 str = this._applyAlias(this._deviceType(device), str); |
| 344 } |
| 345 str += " S/N:"; |
| 346 str += device.serial; |
| 347 return str; |
| 348 }, |
| 349 device_os: function(device) { |
| 350 if (device.build) { |
| 351 return device.build["build.id"]; |
| 352 } |
| 353 return "unknown"; |
| 354 }, |
| 355 status: function(device) { |
| 356 return device.state; |
| 357 } |
| 358 } |
| 359 |
| 360 // specialSort defines any custom sorting rules. By default, a |
| 361 // naturalCompare of the column content is done. |
| 362 var specialSort = { |
| 363 device_type: function(dir, botA, botB) { |
| 364 // We sort on the number of attached devices. Note that this |
| 365 // may not be the same as android_devices, because _devices().length |
| 366 // counts all devices plugged into the bot, whereas android_devices |
| 367 // counts just devices ready for work. |
| 368 var botACol = this._devices(botA).length; |
| 369 var botBCol = this._devices(botB).length; |
| 370 return dir * swarming.naturalCompare(botACol, botBCol); |
| 371 }, |
| 372 disk_space: function(dir, botA, botB) { |
| 373 // We sort based on the raw number of MB of the first disk. |
| 374 var botACol = botA.disks[0].mb; |
| 375 var botBCol = botB.disks[0].mb;; |
| 376 return dir * swarming.naturalCompare(botACol, botBCol); |
| 377 }, |
| 378 }; |
| 379 |
| 306 Polymer({ | 380 Polymer({ |
| 307 is: 'bot-list', | 381 is: 'bot-list', |
| 308 behaviors: [SwarmingBehaviors.BotListBehavior], | 382 behaviors: [SwarmingBehaviors.BotListBehavior], |
| 309 | 383 |
| 310 properties: { | 384 properties: { |
| 311 | 385 |
| 312 _bots: { | 386 _bots: { |
| 313 type: Array, | 387 type: Array, |
| 314 }, | 388 }, |
| 315 | 389 |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 363 _botLink: function(id) { | 437 _botLink: function(id) { |
| 364 // TODO(kjlubick) Make this point to /newui/ when appropriate. | 438 // TODO(kjlubick) Make this point to /newui/ when appropriate. |
| 365 return "/restricted/bot/"+id; | 439 return "/restricted/bot/"+id; |
| 366 }, | 440 }, |
| 367 | 441 |
| 368 | 442 |
| 369 _column: function(col, bot) { | 443 _column: function(col, bot) { |
| 370 return columnMap[col].bind(this)(bot); | 444 return columnMap[col].bind(this)(bot); |
| 371 }, | 445 }, |
| 372 | 446 |
| 447 _androidAliasDevice: function(device) { |
| 448 if (device.notReady) { |
| 449 return UNAUTHENTICATED.toUpperCase(); |
| 450 } |
| 451 return this._androidAlias(this._deviceType(device)); |
| 452 }, |
| 453 |
| 373 _deviceColumn: function(col, device) { | 454 _deviceColumn: function(col, device) { |
| 374 if (col === "devices") { | 455 var f = deviceColumnMap[col]; |
| 375 var str = this._androidAlias(device); | 456 if (!f || !device) { |
| 376 if (device.okay) { | 457 return ""; |
| 377 str = this._applyAlias(this._deviceType(device), str); | |
| 378 } | |
| 379 str += " S/N:"; | |
| 380 str += device.serial; | |
| 381 return str; | |
| 382 } | 458 } |
| 383 if (col === "status") { | 459 return f.bind(this)(device); |
| 384 return device.state; | |
| 385 } | |
| 386 return ""; | |
| 387 }, | 460 }, |
| 388 | 461 |
| 389 _deviceClass: function(device) { | 462 _deviceClass: function(device) { |
| 390 if (!device.okay) { | 463 if (!device.okay) { |
| 391 return "bad-device"; | 464 return "bad-device"; |
| 392 } | 465 } |
| 393 return ""; | 466 return ""; |
| 394 }, | 467 }, |
| 395 | 468 |
| 396 _filterAndSort: function(a,b,c) { | 469 _filterAndSort: function(a,b,c) { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 419 }, | 492 }, |
| 420 | 493 |
| 421 _sortBotTable: function(botA, botB) { | 494 _sortBotTable: function(botA, botB) { |
| 422 if (!this._sort) { | 495 if (!this._sort) { |
| 423 return 0; | 496 return 0; |
| 424 } | 497 } |
| 425 var dir = 1; | 498 var dir = 1; |
| 426 if (this._sort.direction === "desc") { | 499 if (this._sort.direction === "desc") { |
| 427 dir = -1; | 500 dir = -1; |
| 428 } | 501 } |
| 502 var sort = specialSort[this._sort.name]; |
| 503 if (sort) { |
| 504 return sort.bind(this)(dir, botA, botB); |
| 505 } |
| 506 // Default to a natural compare of the columns. |
| 429 var botACol = this._column(this._sort.name, botA); | 507 var botACol = this._column(this._sort.name, botA); |
| 430 var botBCol = this._column(this._sort.name, botB); | 508 var botBCol = this._column(this._sort.name, botB); |
| 431 | 509 |
| 432 return dir * swarming.naturalCompare(botACol, botBCol); | 510 return dir * swarming.naturalCompare(botACol, botBCol); |
| 433 }, | 511 }, |
| 434 | 512 |
| 435 _sortChange: function(e) { | 513 _sortChange: function(e) { |
| 436 // The event we get from sort-toggle tells us the name of what needs | 514 // The event we get from sort-toggle tells us the name of what needs |
| 437 // to be sorting and how to sort it. | 515 // to be sorting and how to sort it. |
| 438 if (!(e && e.detail && e.detail.name)) { | 516 if (!(e && e.detail && e.detail.name)) { |
| (...skipping 16 matching lines...) Expand all Loading... |
| 455 if (data && data.task_id) { | 533 if (data && data.task_id) { |
| 456 return "/user/task/" + data.task_id; | 534 return "/user/task/" + data.task_id; |
| 457 } | 535 } |
| 458 return undefined; | 536 return undefined; |
| 459 } | 537 } |
| 460 | 538 |
| 461 }); | 539 }); |
| 462 })(); | 540 })(); |
| 463 </script> | 541 </script> |
| 464 </dom-module> | 542 </dom-module> |
| OLD | NEW |