| Index: appengine/swarming/elements/res/imp/botlist/bot-list.html
|
| diff --git a/appengine/swarming/elements/res/imp/botlist/bot-list.html b/appengine/swarming/elements/res/imp/botlist/bot-list.html
|
| deleted file mode 100644
|
| index eb583e1c3d3e66a9655cd28daf12b72c509d6005..0000000000000000000000000000000000000000
|
| --- a/appengine/swarming/elements/res/imp/botlist/bot-list.html
|
| +++ /dev/null
|
| @@ -1,644 +0,0 @@
|
| -<!--
|
| - Copyright 2016 The LUCI Authors. All rights reserved.
|
| - Use of this source code is governed under the Apache License, Version 2.0
|
| - that can be found in the LICENSE file.
|
| -
|
| - This in an HTML Import-able file that contains the definition
|
| - of the following elements:
|
| -
|
| - <bot-list>
|
| -
|
| - bot-list creats a dynamic table for viewing swarming bots. Columns can be
|
| - dynamically filtered and it supports client-side filtering.
|
| -
|
| - This is a top-level element.
|
| -
|
| - Properties:
|
| - client_id: String, Oauth 2.0 client id. It will be set by server-side
|
| - template evaluation.
|
| -
|
| - Methods:
|
| - None.
|
| -
|
| - Events:
|
| - None.
|
| --->
|
| -
|
| -<link rel="import" href="/res/imp/bower_components/iron-flex-layout/iron-flex-layout-classes.html">
|
| -<link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
|
| -
|
| -<link rel="import" href="/res/imp/common/dynamic-table-behavior.html">
|
| -<link rel="import" href="/res/imp/common/error-toast.html">
|
| -<link rel="import" href="/res/imp/common/sort-toggle.html">
|
| -<link rel="import" href="/res/imp/common/swarming-app.html">
|
| -<link rel="import" href="/res/imp/common/url-param.html">
|
| -<link rel="import" href="/res/imp/common/pageable-data.html">
|
| -
|
| -<link rel="import" href="bot-filters.html">
|
| -<link rel="import" href="bot-list-data.html">
|
| -<link rel="import" href="bot-list-shared-behavior.html">
|
| -<link rel="import" href="bot-list-summary.html">
|
| -
|
| -<dom-module id="bot-list">
|
| - <template>
|
| - <style include="iron-flex iron-flex-alignment iron-positioning swarming-app-style dynamic-table-style">
|
| - bot-filters, bot-list-summary {
|
| - margin-bottom: 8px;
|
| - margin-right: 10px;
|
| - }
|
| - .quarantined, .bad-device {
|
| - background-color: #ffdddd;
|
| - }
|
| - .dead {
|
| - background-color: #cccccc;
|
| - }
|
| - .bot-list th > span {
|
| - /* Leave space for sort-toggle*/
|
| - padding-right: 30px;
|
| - }
|
| - </style>
|
| -
|
| - <url-param name="s"
|
| - value="{{_sortstr}}"
|
| - default_value="id:asc">
|
| - </url-param>
|
| -
|
| - <swarming-app
|
| - client_id="[[client_id]]"
|
| - auth_headers="{{_auth_headers}}"
|
| - signed_in="{{_signed_in}}"
|
| - server_details="{{_server_details}}"
|
| -
|
| - busy="[[_or(_busy1,_busy2)]]"
|
| - name="Swarming Bot List">
|
| -
|
| - <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
|
| -
|
| - <div hidden$="[[_not(_signed_in)]]">
|
| -
|
| - <div class="horizontal layout">
|
| -
|
| - <bot-filters
|
| - dimensions="[[_dimensions]]"
|
| - primary_map="[[_primary_map]]"
|
| - primary_arr="[[_primary_arr]]"
|
| -
|
| - columns="{{_columns}}"
|
| - query_params="{{_query_params}}"
|
| - filter="{{_filter}}"
|
| - verbose="{{_verbose}}">
|
| - </bot-filters>
|
| -
|
| - <bot-list-summary
|
| - columns="[[_columns]]"
|
| - fleet="[[_fleet]]"
|
| - filtered_bots="[[_filteredSortedItems]]"
|
| - sort="[[_sortstr]]"
|
| - verbose="[[_verbose]]">
|
| - </bot-list-summary>
|
| -
|
| - </div>
|
| -
|
| - <bot-list-data
|
| - id="data"
|
| - auth_headers="[[_auth_headers]]"
|
| - query_params="[[_query_params]]"
|
| -
|
| - busy="{{_busy1}}"
|
| - dimensions="{{_dimensions}}"
|
| - fleet="{{_fleet}}"
|
| - primary_map="{{_primary_map}}"
|
| - primary_arr="{{_primary_arr}}">
|
| - </bot-list-data>
|
| -
|
| - <table class="bot-list">
|
| - <thead on-sort_change="_sortChange">
|
| - <!-- To allow for dynamic columns without having a lot of copy-pasted
|
| - code, we break columns up into "special" and "plain" columns. Special
|
| - columns require some sort of HTML output (e.g. anchor tags) and plain
|
| - columns just output text. The plain columns use Polymer functions to
|
| - insert their text [_header(), _column(), _deviceColumn()]. Polymer
|
| - functions do not allow HTML (to avoid XSS), so special columns, like id
|
| - and task are inserted in a fixed order.
|
| - -->
|
| - <tr>
|
| - <th>
|
| - <span>Bot Id</span>
|
| - <sort-toggle
|
| - name="id"
|
| - current="[[_sort]]">
|
| - </sort-toggle>
|
| - </th>
|
| - <!-- This wonky syntax is the proper way to listen to changes on an
|
| - array (we are listening to all subproperties). The element returned is
|
| - not of much use, so we'll ignore it in _hide() and use this._columns.
|
| - -->
|
| - <th hidden$="[[_hide('cloud_console_link', _columns.*)]]">
|
| - <span>Bot in Cloud Console</span>
|
| - <sort-toggle
|
| - name="cloud_console_link"
|
| - current="[[_sort]]">
|
| - </sort-toggle>
|
| - </th>
|
| - <th hidden$="[[_hide('mp_lease_id', _columns.*)]]">
|
| - <span>Machine Provider Lease Id</span>
|
| - <sort-toggle
|
| - name="mp_lease_id"
|
| - current="[[_sort]]">
|
| - </sort-toggle>
|
| - </th>
|
| - <th hidden$="[[_hide('task', _columns.*)]]">
|
| - <span>Current Task</span>
|
| - <sort-toggle
|
| - name="task"
|
| - current="[[_sort]]">
|
| - </sort-toggle>
|
| - </th>
|
| -
|
| - <template
|
| - is="dom-repeat"
|
| - items="[[_plainColumns]]"
|
| - as="c">
|
| - <th hidden$="[[_hide(c)]]">
|
| - <span>[[_header(c)]]</span>
|
| - <sort-toggle
|
| - name="[[c]]"
|
| - current="[[_sort]]">
|
| - </sort-toggle>
|
| - </th>
|
| - </template>
|
| - </tr>
|
| - </thead>
|
| - <tbody>
|
| - <template
|
| - id="bot_table"
|
| - is="dom-repeat"
|
| - items="[[_filteredSortedItems]]"
|
| - as="bot"
|
| - initial-count=50>
|
| -
|
| - <tr class$="[[_botClass(bot)]]">
|
| - <td>
|
| - <a
|
| - class="center"
|
| - href$="[[_botLink(bot.bot_id)]]"
|
| - target="_blank"
|
| - rel="noopener">
|
| - [[bot.bot_id]]
|
| - </a>
|
| - </td>
|
| - <td hidden$="[[_hide('cloud_console_link', _columns.*)]]">
|
| - <a href$="[[_ccLink(bot)]]">[[_ccText(bot)]]</a>
|
| - </td>
|
| - <td hidden$="[[_hide('mp_lease_id', _columns.*)]]">
|
| - <a href$="[[_mpLink(bot, _server_details.machine_provider_template)]]">
|
| - [[_column('mp_lease_id', bot,_verbose)]]
|
| - </a>
|
| - </td>
|
| - <td hidden$="[[_hide('task', _columns.*)]]">
|
| - <a href$="[[_taskLink(bot.task_id)]]">[[_taskId(bot)]]</a>
|
| - </td>
|
| -
|
| - <template
|
| - is="dom-repeat"
|
| - items="[[_plainColumns]]"
|
| - as="c">
|
| - <td hidden$="[[_hide(c)]]">
|
| - [[_column(c, bot, _verbose)]]
|
| - </td>
|
| - </template>
|
| -
|
| - </tr>
|
| - <template
|
| - is="dom-repeat"
|
| - items="[[_devices(bot)]]"
|
| - as="device">
|
| - <tr
|
| - hidden$="[[_hide('android_devices', _columns.*)]]"
|
| - class$="[[_deviceClass(device)]]">
|
| - <td></td>
|
| - <td hidden$="[[_hide('task', _columns.*)]]"></td>
|
| - <template
|
| - is="dom-repeat"
|
| - items="[[_plainColumns]]"
|
| - as="c">
|
| - <td hidden$="[[_hide(c)]]">
|
| - [[_deviceColumn(c, device, _verbose)]]
|
| - </td>
|
| - </template>
|
| - </tr>
|
| - </template> <!--devices repeat-->
|
| - </template> <!--bot-table repeat-->
|
| - </tbody>
|
| - </table>
|
| - <pageable-data
|
| - id="page_bots"
|
| - busy="{{_busy2}}"
|
| - label="Show more bots"
|
| - output="{{_items}}"
|
| - parse="[[_parseBots]]">
|
| - </pageable-data>
|
| - </div>
|
| -
|
| - <error-toast></error-toast>
|
| - </swarming-app>
|
| -
|
| - </template>
|
| - <script>
|
| - (function(){
|
| - var UNKNOWN = "unknown";
|
| - // see dynamic-table for more information on specialColumns, headerMap,
|
| - // columnMap, and specialSort
|
| - var specialColumns = ["id", "task", "cloud_console_link", "mp_lease_id"];
|
| -
|
| - var headerMap = {
|
| - // "id" and "task" are special, so they don't go here. They have their
|
| - // headers hard-coded above.
|
| - "android_devices": "Android Devices",
|
| - "battery_health": "Battery Health",
|
| - "battery_level": "Battery Level (%)",
|
| - "battery_status": "Battery Status",
|
| - "battery_temperature": "Battery Temp (°C)",
|
| - "battery_voltage": "Battery Voltage (mV)",
|
| - "bot_temperature": "Bot Temp (°C)",
|
| - "cores": "Cores",
|
| - "cpu": "CPU",
|
| - "device": "Non-android Device",
|
| - "device_os": "Device OS",
|
| - "device_temperature": "Device Temp (°C)",
|
| - "device_type": "Device Type",
|
| - "disk_space": "Free Space (MB)",
|
| - "first_seen": "First Seen",
|
| - "gpu": "GPU",
|
| - "last_seen": "Last Seen",
|
| - "mp_lease_expires": "Machine Provider Lease Expires",
|
| - "os": "OS",
|
| - "pool": "Pool",
|
| - "running_time": "Swarming Uptime",
|
| - "status": "Status",
|
| - "uptime": "Bot Uptime",
|
| - "xcode_version": "XCode Version",
|
| - };
|
| -
|
| - var columnMap = {
|
| - android_devices: function(bot) {
|
| - var devs = this._attribute(bot, "android_devices", "0");
|
| - if (this._verbose) {
|
| - return devs.join(" | ") + " devices available";
|
| - }
|
| - // max() works on strings as long as they can be coerced to Number.
|
| - return Math.max(...devs) + " devices available";
|
| - },
|
| - battery_health: function(){
|
| - return "";
|
| - },
|
| - battery_level: function(){
|
| - return "";
|
| - },
|
| - battery_status: function(){
|
| - return "";
|
| - },
|
| - battery_temperature: function(){
|
| - return "";
|
| - },
|
| - battery_voltage: function(){
|
| - return "";
|
| - },
|
| - bot_temperature: function(bot){
|
| - if (this._verbose) {
|
| - return bot.state.temp.zones || UNKNOWN;
|
| - }
|
| - return bot.state.temp.average || UNKNOWN;
|
| - },
|
| - device_temperature: function(){
|
| - return "";
|
| - },
|
| - disk_space: function(bot) {
|
| - var aliased = [];
|
| - bot.disks.forEach(function(disk){
|
| - var alias = sk.human.bytes(disk.mb, sk.MB);
|
| - aliased.push(swarming.alias.apply(disk.mb, disk.id + " "+ alias));
|
| - }.bind(this));
|
| - if (this._verbose) {
|
| - return aliased.join(" | ");
|
| - }
|
| - return aliased[0];
|
| - },
|
| - external_ip: function(bot) {
|
| - return bot.external_ip || "none";
|
| - },
|
| - first_seen: function(bot) {
|
| - return sk.human.localeTime(bot.first_seen_ts)
|
| - },
|
| - id: function(bot) {
|
| - return bot.bot_id;
|
| - },
|
| - last_seen: function(bot) {
|
| - if (this._verbose) {
|
| - return sk.human.localeTime(bot.last_seen_ts);
|
| - }
|
| - return this._timeDiffApprox(bot.last_seen_ts) + " ago";
|
| - },
|
| - mp_lease_id: function(bot) {
|
| - var id = bot.lease_id || "none";
|
| - if (this._verbose) {
|
| - return id;
|
| - }
|
| - return id.substring(0, 10);
|
| - },
|
| - mp_lease_expires: function(bot) {
|
| - if (!bot.lease_expiration_ts) {
|
| - return "N/A";
|
| - }
|
| - if (this._verbose) {
|
| - return sk.human.localeTime(bot.lease_expiration_ts);
|
| - }
|
| - if (bot.lease_expiration_ts < new Date()) {
|
| - return this._timeDiffApprox(bot.lease_expiration_ts) + " ago";
|
| - }
|
| - return "in " + this._timeDiffApprox(bot.lease_expiration_ts);
|
| - },
|
| - running_time: function(bot) {
|
| - var u = this._state(bot, "running_time");
|
| - if (!u) {
|
| - return "unknown";
|
| - }
|
| - return sk.human.strDuration(u);
|
| - },
|
| - status: function(bot) {
|
| - // If a bot is both dead and quarantined, show the deadness over the
|
| - // quarentinedness.
|
| - if (bot.is_dead) {
|
| - return "Dead. Last seen " + sk.human.diffDate(bot.last_seen_ts) +
|
| - " ago";
|
| - }
|
| - if (bot.quarantined) {
|
| - var msg = this._state(bot, "quarantined")[0];
|
| - // Sometimes, the quarantined message is actually in "error". This
|
| - // happens when the bot code has thrown an exception.
|
| - if (msg === UNKNOWN || msg === "true" || msg === true) {
|
| - msg = this._attribute(bot, "error");
|
| - }
|
| - return "Quarantined: " + msg;
|
| - }
|
| - return "Alive";
|
| - },
|
| - task: function(bot) {
|
| - return this._taskId(bot);
|
| - },
|
| - uptime: function(bot) {
|
| - var u = this._state(bot, "uptime");
|
| - if (!u) {
|
| - return "unknown";
|
| - }
|
| - return sk.human.strDuration(u);
|
| - },
|
| - version: function(bot) {
|
| - var v = bot.version || UNKNOWN
|
| - return v.substring(0, 10);
|
| - }
|
| - };
|
| -
|
| - var deviceColumnMap = {
|
| - android_devices: function(device) {
|
| - var str = this._androidAliasDevice(device);
|
| - if (device.okay) {
|
| - str = swarming.alias.apply(this._deviceType(device), str);
|
| - }
|
| - str += " S/N:";
|
| - str += device.serial;
|
| - return str;
|
| - },
|
| - battery_health: function(device){
|
| - var h = (device.battery && device.battery.health) || UNKNOWN;
|
| - return swarming.alias.apply(h, "battery_health");
|
| - },
|
| - battery_level: function(device){
|
| - return (device.battery && device.battery.level) || UNKNOWN;
|
| - },
|
| - battery_status: function(device){
|
| - var s = (device.battery && device.battery.status) || UNKNOWN;
|
| - return swarming.alias.apply(s, "battery_status");
|
| - },
|
| - battery_temperature: function(device){
|
| - // Battery temps are in tenths of degrees C - convert to more human range.
|
| - return (device.battery && device.battery.temperature / 10) || UNKNOWN
|
| - },
|
| - battery_voltage: function(device){
|
| - return (device.battery && device.battery.voltage) || UNKNOWN;
|
| - },
|
| - device_temperature: function(device){
|
| - if (this._verbose) {
|
| - return device.temp.zones || UNKNOWN;
|
| - }
|
| - return device.temp.average || UNKNOWN;
|
| - },
|
| - device_os: function(device) {
|
| - if (device.build) {
|
| - return device.build["build.id"];
|
| - }
|
| - return UNKNOWN;
|
| - },
|
| - status: function(device) {
|
| - return device.state;
|
| - }
|
| - }
|
| -
|
| -
|
| - function deviceAverage(col) {
|
| - return function(dir, botA, botB) {
|
| - // sort by average of all devices or 0 if no devices.
|
| - var avgA = 0;
|
| - var avgB = 0;
|
| - var devsA = this._devices(botA);
|
| - devsA.forEach(function(device) {
|
| - var v = deviceColumnMap[col](device);
|
| - v = parseFloat(swarming.alias.unapply(v)) || 0;
|
| - avgA += v / devsA.length;
|
| - }.bind(this));
|
| - var devsB = this._devices(botB);
|
| - devsB.forEach(function(device) {
|
| - var v = deviceColumnMap[col](device);
|
| - v = parseFloat(swarming.alias.unapply(v)) || 0;
|
| - avgB += v / devsB.length;
|
| - }.bind(this));
|
| - return dir * swarming.naturalCompare(avgA, avgB);
|
| - };
|
| - }
|
| -
|
| - var specialSort = {
|
| - android_devices: function(dir, botA, botB) {
|
| - // We sort on the number of attached devices. Note that this
|
| - // may not be the same as android_devices, because _devices().length
|
| - // counts all devices plugged into the bot, whereas android_devices
|
| - // counts just devices ready for work.
|
| - var botACol = this._devices(botA).length;
|
| - var botBCol = this._devices(botB).length;
|
| - return dir * swarming.naturalCompare(botACol, botBCol);
|
| - },
|
| -
|
| - battery_health: deviceAverage("battery_health"),
|
| - battery_level: deviceAverage("battery_level"),
|
| - battery_status: deviceAverage("battery_status"),
|
| - battery_temperature: deviceAverage("battery_temperature"),
|
| - battery_voltage: deviceAverage("battery_voltage"),
|
| - device_temperature: deviceAverage("device_temperature"),
|
| -
|
| - bot_temperature: function(dir, botA, botB) {
|
| - // Sort by average temperature.
|
| - var botACol = botA.state.temp.average || 0;
|
| - var botBCol = botB.state.temp.average || 0;
|
| - return dir * swarming.naturalCompare(botACol, botBCol);
|
| - },
|
| - disk_space: function(dir, botA, botB) {
|
| - // We sort based on the raw number of MB of the first disk.
|
| - var botACol = botA.disks[0].mb;
|
| - var botBCol = botB.disks[0].mb;
|
| - return dir * swarming.naturalCompare(botACol, botBCol);
|
| - },
|
| - first_seen: function(dir, botA, botB) {
|
| - var botACol = botA.first_seen_ts;
|
| - var botBCol = botB.first_seen_ts;
|
| - return dir * swarming.naturalCompare(botACol, botBCol)
|
| - },
|
| - last_seen: function(dir, botA, botB) {
|
| - var botACol = botA.last_seen_ts;
|
| - var botBCol = botB.last_seen_ts;
|
| - return dir * swarming.naturalCompare(botACol, botBCol)
|
| - },
|
| - running_time: function(dir, botA, botB) {
|
| - var botACol = this._state(botA, "running_time") || 0;
|
| - var botBCol = this._state(botB, "running_time") || 0;
|
| - return dir * swarming.naturalCompare(botACol, botBCol)
|
| - },
|
| - uptime: function(dir, botA, botB) {
|
| - var botACol = this._state(botA, "uptime") || 0;
|
| - var botBCol = this._state(botB, "uptime") || 0;
|
| - return dir * swarming.naturalCompare(botACol, botBCol)
|
| - },
|
| - };
|
| -
|
| - Polymer({
|
| - is: 'bot-list',
|
| -
|
| - // The order behaviors are applied in matters - later ones overwrite
|
| - // attributes of earlier ones
|
| - behaviors: [
|
| - SwarmingBehaviors.BotListBehavior,
|
| - SwarmingBehaviors.DynamicTableBehavior,
|
| - ],
|
| -
|
| - properties: {
|
| - client_id: {
|
| - type: String,
|
| - },
|
| -
|
| - _busy1: {
|
| - type: Boolean,
|
| - value: false
|
| - },
|
| - _busy2: {
|
| - type: Boolean,
|
| - value: false
|
| - },
|
| - _parseBots: {
|
| - type: Function,
|
| - value: function() {
|
| - return this.$.data.parseBots.bind(this);
|
| - }
|
| - },
|
| -
|
| - // For dynamic table.
|
| - _columnMap: {
|
| - type: Object,
|
| - value: function() {
|
| - var base = this._commonColumns();
|
| - for (var attr in columnMap) {
|
| - base[attr] = columnMap[attr];
|
| - }
|
| - return base;
|
| - },
|
| - },
|
| - _headerMap: {
|
| - type: Object,
|
| - value: headerMap,
|
| - },
|
| - _specialColumns: {
|
| - type: Array,
|
| - value: specialColumns,
|
| - },
|
| - _specialSort: {
|
| - type: Object,
|
| - value: specialSort,
|
| - },
|
| - },
|
| -
|
| - observers:["_reload(_query_params,_auth_headers)"],
|
| -
|
| - _androidAliasDevice: function(device) {
|
| - if (device.notReady) {
|
| - return UNAUTHENTICATED.toUpperCase();
|
| - }
|
| - return swarming.alias.android(this._deviceType(device));
|
| - },
|
| -
|
| - _botClass: function(bot) {
|
| - if (bot.is_dead) {
|
| - return "dead";
|
| - }
|
| - if (bot.quarantined) {
|
| - return "quarantined";
|
| - }
|
| - return "";
|
| - },
|
| -
|
| - _ccLink: function(bot){
|
| - var z = this._attribute(bot, "zone")[0];
|
| - if (z === "unknown") {
|
| - return undefined;
|
| - }
|
| - return this._cloudConsoleLink(z, bot.bot_id);
|
| - },
|
| -
|
| - _ccText: function(bot){
|
| - var z = this._attribute(bot, "zone")[0];
|
| - if (z === "unknown") {
|
| - return "Not on GCE";
|
| - }
|
| - return "View Bot";
|
| - },
|
| -
|
| - _deviceColumn: function(col, device) {
|
| - var f = deviceColumnMap[col];
|
| - if (!f || !device) {
|
| - return "";
|
| - }
|
| - return f.bind(this)(device);
|
| - },
|
| -
|
| - _deviceClass: function(device) {
|
| - if (!device.okay) {
|
| - return "bad-device";
|
| - }
|
| - return "";
|
| - },
|
| -
|
| - _mpLink: function(bot, template) {
|
| - if (!bot || !bot.lease_id || !template) {
|
| - return false;
|
| - }
|
| - return template.replace("%s", bot.lease_id);
|
| - },
|
| -
|
| - _reload: function() {
|
| - if (!this._auth_headers || !this._query_params) {
|
| - return;
|
| - }
|
| - var url = "/_ah/api/swarming/v1/bots/list?" + sk.query.fromParamSet(this._query_params);
|
| - this.$.page_bots.load(url,this._auth_headers);
|
| - }
|
| -
|
| - });
|
| - })();
|
| - </script>
|
| -</dom-module>
|
|
|