Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(85)

Side by Side Diff: appengine/swarming/elements/res/imp/botpage/bot-page.html

Issue 2408743002: Move elements/ to ui/ (Closed)
Patch Set: rebase again Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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-page>
10
11 bot-page shows the tasks, events, and dimensions of a bot.
12
13 This is a top-level element.
14
15 Properties:
16 bot_id: String, Used in testing to specify a bot_id
17 client_id: String, Oauth 2.0 client id. It will be set by server-side
18 template evaluation.
19
20 Methods:
21 None.
22
23 Events:
24 None.
25 -->
26
27 <link rel="import" href="/res/imp/bower_components/iron-collapse/iron-collapse.h tml">
28 <link rel="import" href="/res/imp/bower_components/iron-icon/iron-icon.html">
29 <link rel="import" href="/res/imp/bower_components/iron-icons/iron-icons.html">
30 <link rel="import" href="/res/imp/bower_components/paper-button/paper-button.htm l">
31 <link rel="import" href="/res/imp/bower_components/paper-checkbox/paper-checkbox .html">
32 <link rel="import" href="/res/imp/bower_components/paper-dialog/paper-dialog.htm l">
33 <link rel="import" href="/res/imp/bower_components/paper-input/paper-input.html" >
34 <link rel="import" href="/res/imp/bower_components/paper-tabs/paper-tabs.html">
35 <link rel="import" href="/res/imp/bower_components/polymer/polymer.html">
36
37 <link rel="import" href="/res/imp/common/error-toast.html">
38 <link rel="import" href="/res/imp/common/pageable-data.html">
39 <link rel="import" href="/res/imp/common/single-page-style.html">
40 <link rel="import" href="/res/imp/common/swarming-app.html">
41 <link rel="import" href="/res/imp/common/task-behavior.html">
42 <link rel="import" href="/res/imp/common/url-param.html">
43
44 <link rel="import" href="bot-page-data.html">
45 <link rel="import" href="bot-page-shared-behavior.html">
46 <link rel="import" href="bot-page-summary.html">
47
48
49 <dom-module id="bot-page">
50 <template>
51 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style single-page-style task-style">
52 .message {
53 white-space: pre-line;
54 font-family: monospace;
55 }
56
57 .bot_state {
58 white-space: pre;
59 font-family: monospace;
60 margin-bottom: 10px;
61 }
62
63 .tasks_table,
64 .events_table {
65 border: 3px solid #1F78B4;
66 }
67
68 .old_version {
69 background-color: #ffffdd;
70 }
71
72 .stats {
73 min-width: 700px;
74 flex-grow: 2;
75 }
76
77 #collapse {
78 max-width: 700px;
79 }
80
81 .cloud {
82 white-space: nowrap;
83 margin-bottom: 5px;
84 margin-top: auto;
85 }
86
87 paper-checkbox {
88 --paper-checkbox-label-color: #fff;
89 --paper-checkbox-checked-color: #fff;
90 --paper-checkbox-checkmark-color: #000;
91 --paper-checkbox-unchecked-color: #fff;
92 padding: 3px;
93 }
94
95 paper-dialog {
96 border-radius: 6px;
97 }
98 </style>
99
100 <url-param name="id"
101 value="{{bot_id}}">
102 </url-param>
103 <url-param name="show_all_events"
104 value="{{_show_all}}">
105 </url-param>
106 <url-param name="selected"
107 value="{{_selected}}">
108 </url-param>
109 <url-param name="show_state"
110 value="{{_show_state}}">
111 </url-param>
112
113 <swarming-app
114 client_id="[[client_id]]"
115 auth_headers="{{_auth_headers}}"
116 permissions="{{_permissions}}"
117 server_details="{{_server_details}}"
118 signed_in="{{_signed_in}}"
119
120 busy="[[_or(_busy1,_busy2,_busy3)]]"
121 name="Swarming Bot Page">
122
123 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
124
125 <div hidden$="[[_not(_signed_in)]]">
126
127 <bot-page-data
128 id="data"
129 auth_headers="[[_auth_headers]]"
130 bot_id="[[bot_id]]"
131
132 bot="{{_bot}}"
133 busy="{{_busy1}}"
134 events="{{_events}}"
135 tasks="{{_tasks}}"
136 on-reload="_clearAndReload">
137 </bot-page-data>
138
139 <div class="header horizontal layout">
140 <paper-input class="id_input" label="Bot id" value="{{bot_id}}"></pape r-input>
141 <template is="dom-if" if="[[_ccLink(_bot)]]">
142 <div class="vertical layout">
143 <a href$="[[_ccLink(_bot)]]" class="cloud">Cloud Console</a>
144 </div>
145 </template>
146 <button on-click="_refresh">
147 <iron-icon class="refresh" icon="icons:refresh"></iron-icon>
148 </button>
149 </div>
150
151 <div class="horizontal wrap layout">
152 <div class="flex">
153 <table>
154 <tr class$="[[_isDead(_bot)]]" title="Last time the bot contacted the server.">
155 <td>Last Seen</td>
156 <td title="[[_bot.human_last_seen_ts]]">
157 [[_timeDiffExact(_bot.last_seen_ts)]] ago</td>
158 <td>
159 <!-- dom-ifs are slightly less performant than hidden$=, but
160 prevent things from first drawing and then hiding. We prefer to
161 not flash buttons or quarantined messages -->
162 <template is="dom-if" if="[[_canShutdown(_bot,_permissions)]]" >
163 <button class="raised" on-click="_promptShutdown">
164 Shut Down Gracefully
165 </button>
166 </template>
167 <template is="dom-if" if="[[_canDelete(_bot,_permissions)]]">
168 <button class="raised" on-click="_promptDelete">
169 Delete
170 </button>
171 </template>
172 </td>
173 </tr>
174 <template is="dom-if" if="[[_bot.quarantined]]">
175 <tr class="quarantined">
176 <td>Quarantined</td>
177 <td colspan="2" class="message">[[_quarantineMessage(_bot)]]</ td>
178 </tr>
179 </template>
180 <tr>
181 <td>Current Task</td>
182 <td>
183 <a target="_blank" rel="noopener"
184 href$="[[_taskLink(_bot.task_id)]]">
185 [[_task(_bot)]]
186 </a>
187 </td>
188 <td>
189 <!-- TODO(kjlubick) add the cancel button when swarming can
190 cancel running tasks -->
191 </td>
192 </tr>
193 <tr>
194 <td rowspan$="[[_numRows(_bot.dimensions)]]">Dimensions</td>
195 </tr>
196 <template
197 is="dom-repeat"
198 items="[[_bot.dimensions]]"
199 as="dim">
200 <tr>
201 <td>[[dim.key]]</td>
202 <td>[[_concat(dim.value)]]</td>
203 </tr>
204 </template>
205
206 <tr title="IP address that the server saw the connection from.">
207 <td>External IP</td>
208 <td><a href$="[[_bot.external_ip]]">[[_bot.external_ip]]</a></td >
209 <td></td>
210 </tr>
211 <tr
212 class$="[[_classVersion(_server_details.bot_version,_bot.versi on)]]"
213 title="Version is based on the content of swarming_bot.zip whi ch is the swarming bot code. The bot won't update if quarantined, dead, or busy. ">
214 <td>Bot Version</td>
215 <td>[[_shorten(_bot.version,'8')]]</td>
216 <td></td>
217 </tr>
218 <tr title="The version the server expects the bot to be using.">
219 <td>Expected Bot Version</td>
220 <td>[[_shorten(_server_details.bot_version,'8')]]</td>
221 <td></td>
222 </tr>
223 <tr title="First time ever a bot with this id contacted the server .">
224 <td>First seen</td>
225 <td title="[[_bot.human_first_seen_ts]]">
226 [[_timeDiffApprox(_bot.first_seen_ts)]] ago
227 </td>
228 <td></td>
229 </tr>
230 <tr title="How the bot is authenticated by the server.">
231 <td>Authenticated as</td>
232 <td colspan=2>[[_bot.authenticated_as]]</td>
233 </tr>
234 <template is="dom-if" if="[[_bot.lease_id]]">
235 <tr>
236 <td>Machine Provider Lease ID</td>
237 <td colspan=2>
238 <a href$="[[_mpLink(_bot,_server_details.machine_provider_te mplate)]]">
239 [[_bot.lease_id]]
240 </a>
241 </td>
242 </tr>
243 <tr>
244 <td>Machine Provider Lease Expires</td>
245 <td colspan=2>[[_bot.human_lease_expiration_ts]]</td>
246 </tr>
247 </template>
248 </table>
249
250 <span class="title">State</span>
251
252 <template is="dom-if" if="[[_not(_show_state)]]">
253 <button on-click="_toggleState">
254 <iron-icon icon="icons:add-circle-outline"></iron-icon>
255 </button>
256 </template>
257
258 <template is="dom-if" if="[[_show_state]]">
259 <button on-click="_toggleState">
260 <iron-icon icon="icons:remove-circle-outline"></iron-icon>
261 </button>
262 </template>
263
264 <iron-collapse id="collapse" opened="[[_show_state]]">
265 <div class="bot_state">[[_prettyPrint(_bot.state)]]</div>
266 </iron-collapse>
267 </div>
268
269 <div class="stats flex">
270 <bot-page-summary
271 tasks="[[_tasks]]">
272 </bot-page-summary>
273 </div>
274 </div>
275
276 <div class="tabs">
277 <paper-tabs selected="{{_selected}}" no-bar>
278 <paper-tab>Tasks</paper-tab>
279 <paper-tab>Events</paper-tab>
280 </paper-tabs>
281
282 <template is="dom-if" if="[[_showEvents]]">
283 <paper-checkbox checked="{{_show_all}}">
284 Show all events
285 </paper-checkbox>
286 </template>
287 </div>
288
289 <template is="dom-if" if="[[_not(_showEvents)]]">
290 <table class="tasks_table">
291 <thead>
292 <tr>
293 <th>Task</th>
294 <th>Started</th>
295 <th>Duration</th>
296 <th>Result</th>
297 </tr>
298 </thead>
299 <tbody>
300 <template is="dom-repeat" items="{{_tasks}}" as="task">
301 <tr class$="[[_taskClass(task)]]">
302 <td>
303 <a target="_blank" rel="noopener"
304 href$="[[_taskLink(task.task_id)]]">
305 [[task.name]]
306 </a>
307 </td>
308 <td>[[task.human_started_ts]]</td>
309 <td title="[[task.human_completed_ts]]">[[task.human_duration] ]</td>
310 <td>[[task.state]]</td>
311 </tr>
312 </template>
313 </tbody>
314 </table>
315 </template>
316
317 <template is="dom-if" if="[[_showEvents]]">
318 <table class="events_table">
319 <thead>
320 <tr>
321 <th>Message</th>
322 <th>Type</th>
323 <th>Timestamp</th>
324 <th>Task ID</th>
325 <th>Version</th>
326 </tr>
327 </thead>
328 <tbody>
329 <template is="dom-repeat" items="{{_eventList(_show_all,_events.*) }}" as="event">
330 <tr>
331 <td class="message">[[event.message]]</td>
332 <td>[[event.event_type]]</td>
333 <td>[[event.human_ts]]</td>
334 <td>
335 <a target="_blank" rel="noopener"
336 href$="[[_taskLink(event.task_id)]]">
337 [[event.task_id]]
338 </a>
339 </td>
340 <td class$="[[_classVersion(_server_details.bot_version,event. version)]]">
341 [[_shorten(event.version,'8')]]
342 </td>
343 </tr>
344 </template>
345 </tbody>
346 </table>
347 </template>
348 <!-- https://github.com/Polymer/polymer/issues/3669 hidden$ doesn't
349 respect truthiness, only booleanness, so we have _showEvents
350 instead of using _selected directly.-->
351 <pageable-data
352 id="page_tasks"
353 hidden$="[[_showEvents]]"
354 busy="{{_busy2}}"
355 label="Show more tasks"
356 output="{{_tasks}}"
357 parse="[[_parseTasks]]">
358 </pageable-data>
359 <pageable-data
360 id="page_events"
361 hidden$="[[_not(_showEvents)]]"
362 busy="{{_busy3}}"
363 label="Show more events"
364 output="{{_events}}"
365 parse="[[_parseEvents]]">
366 </pageable-data>
367 </div> <!-- hidden when not signed in-->
368 </swarming-app>
369
370 <paper-dialog id="prompt" modal on-iron-overlay-closed="_promptClosed">
371 <h2>Are you sure?</h2>
372 <div>Are you sure you want to [[_dialogPrompt]]?</div>
373 <div class="buttons">
374 <paper-button dialog-dismiss autofocus>No</paper-button>
375 <paper-button dialog-confirm>Yes</paper-button>
376 </div>
377 </paper-dialog>
378
379 <error-toast></error-toast>
380
381 </template>
382 <script>
383 (function(){
384
385 Polymer({
386 is: 'bot-page',
387
388 behaviors: [
389 SwarmingBehaviors.BotPageBehavior,
390 ],
391
392 properties: {
393 bot_id: {
394 type: String,
395 },
396 client_id: {
397 type: String,
398 },
399
400 _auth_headers: {
401 type: Object,
402 observer: "_reload",
403 },
404 _bot: {
405 type: Object,
406 },
407 _dialogPrompt: {
408 type: String,
409 value: "",
410 },
411 _selected: {
412 type: Number,
413 },
414 _show_all: {
415 type: Boolean,
416 },
417 _showEvents: {
418 type: Boolean,
419 computed: "_truthy(_selected)"
420 },
421 _show_state: {
422 type: Boolean,
423 },
424
425 _parseEvents: {
426 type: Function,
427 value: function() {
428 return this.$.data.parseEvents.bind(this);
429 }
430 },
431 _parseTasks: {
432 type: Function,
433 value: function() {
434 return this.$.data.parseTasks.bind(this);
435 }
436 }
437 },
438
439 _canCancel: function(bot, permissions) {
440 return bot && bot.task_id && permissions.cancel_task;
441 },
442
443 _canDelete: function(bot, permissions) {
444 return bot && bot.is_dead && permissions.delete_bot;
445 },
446
447 _canShutdown: function(bot, permissions){
448 return bot && !bot.is_dead && permissions.terminate_bot;
449 },
450
451 _classVersion: function(serverVersion, otherVersion) {
452 if (serverVersion !== otherVersion) {
453 return "old_version";
454 }
455 return "";
456 },
457
458 _clearAndReload: function(botID) {
459 this.$.page_tasks.clear();
460 this.$.page_events.clear();
461 this._reload();
462 },
463
464 _ccLink: function(bot) {
465 // We create a link to this bot in cloud console if we detect it is
466 // on GCE. We look for the zone dimension and create the link using
467 // that.
468 if (!bot || !bot.dimensions) {
469 return false;
470 }
471 var link;
472 bot.dimensions.forEach(function(d){
473 if (d.key === "zone") {
474 link = this._cloudConsoleLink(d.value[0], bot.bot_id);
475 }
476 }.bind(this))
477 return link;
478 },
479
480 _concat: function(arr) {
481 if (!arr) {
482 return "";
483 }
484 return arr.join(" | ");
485 },
486
487 _deleteBot: function() {
488 swarming.postWithToast("/_ah/api/swarming/v1/bot/"+this.bot_id+"/delete" ,
489 "Deleting "+this.bot_id, this._auth_headers);
490 },
491
492 _eventList(showAll) {
493 if (!this._events) {
494 return [];
495 }
496 return this._events.filter(function(e){
497 return showAll || e.message;
498 });
499 },
500
501 _isDead(bot){
502 if (bot && bot.is_dead) {
503 return "dead";
504 }
505 return "";
506 },
507
508 _luciLink: function(revision) {
509 if (!revision) {
510 return undefined;
511 }
512 return "https://github.com/luci/luci-py/commit/" + revision;
513 },
514
515 _mpLink: function(bot, template) {
516 if (!bot || !bot.lease_id || !template) {
517 return false;
518 }
519 return template.replace("%s", bot.lease_id);
520 },
521
522 _numRows: function(arr) {
523 if (!arr || !arr.length) {
524 return 1;
525 }
526 return 1 + arr.length;
527 },
528
529 _prettyPrint: function(obj) {
530 obj = obj || {};
531 return JSON.stringify(obj, null, 2);
532 },
533
534 _promptClosed: function(e) {
535 if (e.detail.confirmed) {
536 if (this._dialogPrompt.startsWith("shut down")) {
537 this._shutdownBot();
538 } else {
539 this._deleteBot();
540 }
541 }
542 },
543
544 _promptDelete: function() {
545 this.set("_dialogPrompt", "delete "+this.bot_id);
546 this.$.prompt.open();
547 },
548
549 _promptShutdown: function() {
550 this.set("_dialogPrompt", "shut down "+this.bot_id);
551 this.$.prompt.open();
552 },
553
554 _quarantineMessage: function(bot) {
555 if (bot && bot.quarantined) {
556 var msg = bot.state.quarantined;
557 // Sometimes, the quarantined message is actually in "error". This
558 // happens when the bot code has thrown an exception.
559 if (msg === undefined || msg === "true" || msg === true) {
560 msg = bot.state && bot.state.error;
561 }
562 return msg || "True";
563 }
564 return "";
565 },
566
567 _refresh: function() {
568 this.$.data.request();
569 },
570
571 _reload: function() {
572 if (!this._auth_headers) {
573 return;
574 }
575 var baseUrl = "/_ah/api/swarming/v1/bot/"+this.bot_id;
576 // We limit the fields on these two queries to make them faster.
577 this.$.page_tasks.load(baseUrl + "/tasks?fields=cursor%2Citems(abandoned _ts%2Cbot_version%2Ccompleted_ts%2Cduration%2Cexit_code%2Cfailure%2Cinternal_fai lure%2Cmodified_ts%2Cname%2Cstarted_ts%2Cstate%2Ctask_id%2Ctry_number)",
578 this._auth_headers, 30);
579 this.$.page_events.load(baseUrl + "/events?fields=cursor%2Citems(event_t ype%2Cmessage%2Cquarantined%2Ctask_id%2Cts%2Cversion)",
580 this._auth_headers, 50);
581 },
582
583 _shorten: function(str, length) {
584 if (!str || ! length) {
585 return "";
586 }
587 return str.substring(0, length);
588 },
589
590 _shutdownBot: function() {
591 swarming.postWithToast("/_ah/api/swarming/v1/bot/"+this.bot_id+"/termina te",
592 "Shutting down "+this.bot_id, this._auth_headers);
593 },
594
595 _task: function(bot) {
596 return (bot && bot.task_id) || "idle";
597 },
598
599 _taskClass: function(task) {
600 if (task && task.internal_failure) {
601 return "bot_died";
602 }
603 if (task && task.failure) {
604 return "failed_task";
605 }
606 return "";
607 },
608
609 _toggleState: function() {
610 this.set("_show_state", !this._show_state);
611 }
612
613 });
614 })();
615 </script>
616 </dom-module>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698