| OLD | NEW |
| (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-data> | |
| 10 | |
| 11 This makes calls authenticated with Oauth 2 to the swarming apis. It parses | |
| 12 that data into usable data structures. | |
| 13 | |
| 14 Usage: | |
| 15 | |
| 16 <bot-list-data></bot-list-data> | |
| 17 | |
| 18 Properties: | |
| 19 // inputs | |
| 20 auth_headers: Object, the OAuth2 header to include in the request. This | |
| 21 should come from swarming-app. | |
| 22 | |
| 23 // outputs | |
| 24 bots: Array<Object>, all bots returned by the server. This is an Object | |
| 25 with at least the following structure: | |
| 26 dimensions: Array<Object>: Has key:String and value:Array<String> | |
| 27 task_id: String | |
| 28 external_ip: String | |
| 29 is_dead: Object: Is usually Boolean, but could be message string | |
| 30 quarantined: Object: Is usually Boolean, but could be message string | |
| 31 bot_id: String | |
| 32 state: String, Stringified JSON that has many pieces of information, like | |
| 33 devices, disk space, temperature, etc. | |
| 34 busy: Boolean, if any ajax requests are in flight. | |
| 35 dimensions: Array<String>, of all valid dimensions. | |
| 36 fleet: Object, counts of all bots in the fleet. Contains "alive", "busy", | |
| 37 "idle", "dead", and "quarantined". | |
| 38 primary_map: Object, a mapping of primary keys to secondary items. | |
| 39 The primary keys are things that can be columns or sorted by. The | |
| 40 primary values (aka the secondary items) are things that can be filtered | |
| 41 on. Primary consists of dimensions and state. Secondary contains the | |
| 42 values primary things can be. | |
| 43 primary_arr: Array<String>, the display order of the primary keys. | |
| 44 This is dimensions, then bot properties, then elements from bot.state. | |
| 45 | |
| 46 Methods: | |
| 47 signIn(): Force a signin of the user using OAuth. This happens | |
| 48 automatically when auth_headers is set. | |
| 49 | |
| 50 Events: | |
| 51 None. | |
| 52 --> | |
| 53 | |
| 54 <link rel="import" href="bot-list-shared-behavior.html"> | |
| 55 | |
| 56 <dom-module id="bot-list-data"> | |
| 57 | |
| 58 <script> | |
| 59 (function(){ | |
| 60 var AVAILABLE = "available"; | |
| 61 var BLACKLIST_DIMENSIONS = ["quarantined", "error"]; | |
| 62 | |
| 63 function aggregateTemps(temps) { | |
| 64 if (!temps) { | |
| 65 return {}; | |
| 66 } | |
| 67 var zones = []; | |
| 68 var avg = 0; | |
| 69 for (k in temps) { | |
| 70 zones.push(k +": "+temps[k]); | |
| 71 avg += temps[k]; | |
| 72 } | |
| 73 avg = avg / zones.length | |
| 74 if (avg) { | |
| 75 avg = avg.toFixed(1); | |
| 76 } else { | |
| 77 avg = "unknown"; | |
| 78 } | |
| 79 return { | |
| 80 average: avg, | |
| 81 zones: zones.join(" | ") || "unknown", | |
| 82 } | |
| 83 } | |
| 84 | |
| 85 Polymer({ | |
| 86 is: 'bot-list-data', | |
| 87 | |
| 88 behaviors: [ | |
| 89 SwarmingBehaviors.BotListBehavior, | |
| 90 ], | |
| 91 | |
| 92 properties: { | |
| 93 // inputs | |
| 94 auth_headers: { | |
| 95 type: Object, | |
| 96 observer: "signIn", | |
| 97 }, | |
| 98 | |
| 99 //outputs | |
| 100 bots: { | |
| 101 type: Array, | |
| 102 computed: "parseBots(_list)", | |
| 103 notify: true, | |
| 104 }, | |
| 105 busy: { | |
| 106 type: Boolean, | |
| 107 computed: "_or(_busy2,_busy1)", | |
| 108 notify: true, | |
| 109 }, | |
| 110 dimensions: { | |
| 111 type: Array, | |
| 112 computed: "_makeArray(_dimensions)", | |
| 113 notify: true, | |
| 114 }, | |
| 115 fleet: { | |
| 116 type: Object, | |
| 117 computed: "_fleet(_count)", | |
| 118 notify: true, | |
| 119 }, | |
| 120 primary_map: { | |
| 121 type: Object, | |
| 122 computed: "_primaryMap(_dimensions)", | |
| 123 notify: true, | |
| 124 }, | |
| 125 primary_arr: { | |
| 126 type: Array, | |
| 127 //BOT_PROPERTIES is inherited from BotListBehavior | |
| 128 computed: "_primaryArr(dimensions, BOT_PROPERTIES)", | |
| 129 notify: true, | |
| 130 }, | |
| 131 | |
| 132 // private | |
| 133 _busy1: { | |
| 134 type: Boolean, | |
| 135 value: false | |
| 136 }, | |
| 137 _busy2: { | |
| 138 type: Boolean, | |
| 139 value: false | |
| 140 }, | |
| 141 _count: { | |
| 142 type: Object, | |
| 143 }, | |
| 144 _dimensions: { | |
| 145 type: Object, | |
| 146 }, | |
| 147 _list: { | |
| 148 type: Object, | |
| 149 }, | |
| 150 }, | |
| 151 | |
| 152 signIn: function(){ | |
| 153 this._getJsonAsync("_count", "/_ah/api/swarming/v1/bots/count", | |
| 154 "_busy2", this.auth_headers); | |
| 155 this._getJsonAsync("_dimensions","/_ah/api/swarming/v1/bots/dimensions", | |
| 156 "_busy1", this.auth_headers); | |
| 157 }, | |
| 158 | |
| 159 parseBots: function(json){ | |
| 160 if (!json || !json.items) { | |
| 161 return []; | |
| 162 } | |
| 163 // Do any preprocessing here | |
| 164 json.items.forEach(function(bot){ | |
| 165 // Parse the state, which is a JSON string. This contains a lot of | |
| 166 // interesting information like details about the devices attached. | |
| 167 bot.state = bot.state || "{}"; | |
| 168 bot.state = JSON.parse(bot.state) || {}; | |
| 169 // get the disks in an easier to deal with format, sorted by size. | |
| 170 var disks = bot.state.disks || {}; | |
| 171 var keys = Object.keys(disks); | |
| 172 if (!keys.length) { | |
| 173 bot.disks = [{"id": "unknown", "mb": 0}]; | |
| 174 } else { | |
| 175 bot.disks = []; | |
| 176 for (var i = 0; i < keys.length; i++) { | |
| 177 bot.disks.push({"id":keys[i], "mb":disks[keys[i]].free_mb}); | |
| 178 } | |
| 179 // Sort these so the biggest disk comes first. | |
| 180 bot.disks.sort(function(a, b) { | |
| 181 return b.mb - a.mb; | |
| 182 }); | |
| 183 } | |
| 184 | |
| 185 // Make sure every bot has a state.temp object and precompute | |
| 186 // average and list of temps by zone if applicable. | |
| 187 bot.state.temp = aggregateTemps(bot.state.temp); | |
| 188 | |
| 189 var devices = []; | |
| 190 var d = (bot && bot.state && bot.state.devices) || {}; | |
| 191 // state.devices is like {Serial:Object}, so we need to keep the seria
l | |
| 192 for (key in d) { | |
| 193 var o = d[key]; | |
| 194 o.serial = key; | |
| 195 o.okay = (o.state === AVAILABLE); | |
| 196 // It is easier to assume all devices on a bot are of the same type | |
| 197 // than to pick through the (incomplete) device state and find it. | |
| 198 // Bots that are quarentined because they have no devices | |
| 199 // still have devices in their state (the last known device attached
) | |
| 200 // but don't have the device_type dimension. In that case, we punt | |
| 201 // on device type. | |
| 202 var types = this._dimension(bot, "device_type") || ["unknown"]; | |
| 203 o.device_type = types[0]; | |
| 204 o.temp = aggregateTemps(o.temp); | |
| 205 devices.push(o); | |
| 206 } | |
| 207 bot.state.devices = devices; | |
| 208 | |
| 209 if (bot.last_seen_ts) { | |
| 210 bot.last_seen_ts = new Date(bot.last_seen_ts); | |
| 211 } | |
| 212 if (bot.first_seen_ts) { | |
| 213 bot.first_seen_ts = new Date(bot.first_seen_ts); | |
| 214 } | |
| 215 if (bot.lease_expiration_ts) { | |
| 216 bot.lease_expiration_ts = new Date(bot.lease_expiration_ts); | |
| 217 } | |
| 218 | |
| 219 }.bind(this)); | |
| 220 return json.items; | |
| 221 }, | |
| 222 | |
| 223 _fleet: function() { | |
| 224 if (!this._count) { | |
| 225 return {}; | |
| 226 } | |
| 227 return { | |
| 228 all: this._count.count || -1, | |
| 229 alive: (this._count.count - this._count.dead) || -1, | |
| 230 busy: this._count.busy || -1, | |
| 231 idle: (this._count.count - this._count.busy) || -1, | |
| 232 dead: this._count.dead || -1, | |
| 233 quarantined: this._count.quarantined || -1, | |
| 234 } | |
| 235 }, | |
| 236 | |
| 237 _makeArray: function(dimObj) { | |
| 238 if (!dimObj || !dimObj.bots_dimensions) { | |
| 239 return []; | |
| 240 } | |
| 241 var dims = []; | |
| 242 dimObj.bots_dimensions.forEach(function(d){ | |
| 243 if (BLACKLIST_DIMENSIONS.indexOf(d.key) === -1) { | |
| 244 dims.push(d.key); | |
| 245 } | |
| 246 }); | |
| 247 dims.push("id"); | |
| 248 dims.sort(); | |
| 249 return dims; | |
| 250 }, | |
| 251 | |
| 252 _primaryArr: function(dimensions, properties) { | |
| 253 return dimensions.concat(properties); | |
| 254 }, | |
| 255 | |
| 256 _primaryMap: function(dimensions){ | |
| 257 // pMap will have a list of columns to available values (primary key | |
| 258 // to secondary values). This includes bot dimensions, but also | |
| 259 // includes state like disk_space, quarantined, busy, etc. | |
| 260 dimensions = dimensions.bots_dimensions; | |
| 261 | |
| 262 var pMap = {}; | |
| 263 dimensions.forEach(function(d){ | |
| 264 if (swarming.alias.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) { | |
| 265 // value is an array of all seen values for the dimension d.key | |
| 266 pMap[d.key] = d.value; | |
| 267 } else { | |
| 268 var aliased = []; | |
| 269 d.value.forEach(function(value){ | |
| 270 aliased.push(swarming.alias.apply(value, d.key)); | |
| 271 }); | |
| 272 pMap[d.key] = aliased; | |
| 273 } | |
| 274 }); | |
| 275 | |
| 276 // Add some options that might not show up. | |
| 277 pMap["android_devices"].push("0"); | |
| 278 pMap["device_os"].push("none"); | |
| 279 pMap["device_type"].push("none"); | |
| 280 | |
| 281 pMap["id"] = []; | |
| 282 | |
| 283 // Create custom filter options | |
| 284 pMap["disk_space"] = []; | |
| 285 pMap["task"] = ["busy", "idle"]; | |
| 286 pMap["status"] = ["alive", "dead", "quarantined"]; | |
| 287 | |
| 288 // No need to sort any of this, bot-filters sorts secondary items | |
| 289 // automatically, especially when the user types a query. | |
| 290 return pMap; | |
| 291 }, | |
| 292 | |
| 293 }); | |
| 294 })(); | |
| 295 </script> | |
| 296 </dom-module> | |
| OLD | NEW |