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

Side by Side Diff: appengine/swarming/elements/build/elements.html

Issue 2291323002: Introduce new bot-page UI (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@bot-page
Patch Set: Fix some query glitches Created 4 years, 3 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
1 <!DOCTYPE html><html><head><!-- 1 <!DOCTYPE html><html><head><!--
2 @license 2 @license
3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt 4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
7 Code distributed by Google as part of the polymer project is also 7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
9 --><!-- 9 --><!--
10 @license 10 @license
(...skipping 8099 matching lines...) Expand 10 before | Expand all | Expand 10 after
8110 // because computed values in Polymer don't fire if a property is 8110 // because computed values in Polymer don't fire if a property is
8111 // undefined. Clients should check that bindTo is not falsey. 8111 // undefined. Clients should check that bindTo is not falsey.
8112 _getJsonAsync: function(bindTo, url, busy, headers, params) { 8112 _getJsonAsync: function(bindTo, url, busy, headers, params) {
8113 if (!bindTo || !url) { 8113 if (!bindTo || !url) {
8114 console.log("Need at least a polymer element to bind to and a url"); 8114 console.log("Need at least a polymer element to bind to and a url");
8115 return; 8115 return;
8116 } 8116 }
8117 if (busy) { 8117 if (busy) {
8118 this.set(busy, true); 8118 this.set(busy, true);
8119 } 8119 }
8120 url = url + "?" + sk.query.fromParamSet(params); 8120 if (params) {
8121 url = url + "?" + sk.query.fromParamSet(params);
8122 }
8121 sk.request("GET", url, "", headers).then(JSON.parse).then(function(json) { 8123 sk.request("GET", url, "", headers).then(JSON.parse).then(function(json) {
8122 this.set(bindTo, json); 8124 this.set(bindTo, json);
8123 if (busy) { 8125 if (busy) {
8124 this.set(busy, false); 8126 this.set(busy, false);
8125 } 8127 }
8126 }.bind(this)).catch(function(reason){ 8128 }.bind(this)).catch(function(reason){
8127 console.log("Reason for failure of request to " + url, reason); 8129 console.log("Reason for failure of request to " + url, reason);
8128 this.set(bindTo, false); 8130 this.set(bindTo, false);
8129 if (busy) { 8131 if (busy) {
8130 this.set(busy, false); 8132 this.set(busy, false);
(...skipping 15567 matching lines...) Expand 10 before | Expand all | Expand 10 after
23698 if (bot.is_dead) { 23700 if (bot.is_dead) {
23699 return "dead"; 23701 return "dead";
23700 } 23702 }
23701 if (bot.quarantined) { 23703 if (bot.quarantined) {
23702 return "quarantined"; 23704 return "quarantined";
23703 } 23705 }
23704 return ""; 23706 return "";
23705 }, 23707 },
23706 23708
23707 _botLink: function(id) { 23709 _botLink: function(id) {
23708 // TODO(kjlubick) Make this point to /newui/ when appropriate. 23710 return "/newui/bot?id="+id;
23709 return "/restricted/bot/"+id;
23710 }, 23711 },
23711 23712
23712 23713
23713 _androidAliasDevice: function(device) { 23714 _androidAliasDevice: function(device) {
23714 if (device.notReady) { 23715 if (device.notReady) {
23715 return UNAUTHENTICATED.toUpperCase(); 23716 return UNAUTHENTICATED.toUpperCase();
23716 } 23717 }
23717 return swarming.alias.android(this._deviceType(device)); 23718 return swarming.alias.android(this._deviceType(device));
23718 }, 23719 },
23719 23720
23720 _deviceColumn: function(col, device) { 23721 _deviceColumn: function(col, device) {
23721 var f = deviceColumnMap[col]; 23722 var f = deviceColumnMap[col];
23722 if (!f || !device) { 23723 if (!f || !device) {
23723 return ""; 23724 return "";
23724 } 23725 }
23725 return f.bind(this)(device); 23726 return f.bind(this)(device);
23726 }, 23727 },
23727 23728
23728 _deviceClass: function(device) { 23729 _deviceClass: function(device) {
23729 if (!device.okay) { 23730 if (!device.okay) {
23730 return "bad-device"; 23731 return "bad-device";
23731 } 23732 }
23732 return ""; 23733 return "";
23733 }, 23734 },
23734 23735
23735 _taskLink: function(data) { 23736 _taskLink: function(data) {
23737 // TODO(kjlubick): Migrate this to /newui/ when ready
23736 if (data && data.task_id) { 23738 if (data && data.task_id) {
23737 return "/user/task/" + data.task_id; 23739 return "/user/task/" + data.task_id;
23738 } 23740 }
23739 return undefined; 23741 return undefined;
23740 } 23742 }
23741 23743
23742 }); 23744 });
23743 })(); 23745 })();
23744 </script> 23746 </script>
23745 </dom-module> 23747 </dom-module>
(...skipping 828 matching lines...) Expand 10 before | Expand all | Expand 10 after
24574 } 24576 }
24575 if (state === "RUNNING" || state === "PENDING") { 24577 if (state === "RUNNING" || state === "PENDING") {
24576 return "pending"; 24578 return "pending";
24577 } 24579 }
24578 return ""; 24580 return "";
24579 } 24581 }
24580 24582
24581 }); 24583 });
24582 })(); 24584 })();
24583 </script> 24585 </script>
24584 </dom-module><dom-module id="bot-page-data" assetpath="/res/imp/botpage/"> 24586 </dom-module>
24587
24588 <dom-module id="iron-collapse" assetpath="/res/imp/bower_components/iron-collaps e/">
24589
24590 <template>
24591
24592 <style>
24593 :host {
24594 display: block;
24595 transition-duration: var(--iron-collapse-transition-duration, 300ms);
24596 overflow: visible;
24597 }
24598
24599 :host(.iron-collapse-closed) {
24600 display: none;
24601 }
24602
24603 :host(:not(.iron-collapse-opened)) {
24604 overflow: hidden;
24605 }
24606 </style>
24607
24608 <content></content>
24609
24610 </template>
24611
24612 </dom-module>
24613
24614 <script>
24615
24616 Polymer({
24617
24618 is: 'iron-collapse',
24619
24620 behaviors: [
24621 Polymer.IronResizableBehavior
24622 ],
24623
24624 properties: {
24625
24626 /**
24627 * If true, the orientation is horizontal; otherwise is vertical.
24628 *
24629 * @attribute horizontal
24630 */
24631 horizontal: {
24632 type: Boolean,
24633 value: false,
24634 observer: '_horizontalChanged'
24635 },
24636
24637 /**
24638 * Set opened to true to show the collapse element and to false to hide it .
24639 *
24640 * @attribute opened
24641 */
24642 opened: {
24643 type: Boolean,
24644 value: false,
24645 notify: true,
24646 observer: '_openedChanged'
24647 },
24648
24649 /**
24650 * Set noAnimation to true to disable animations
24651 *
24652 * @attribute noAnimation
24653 */
24654 noAnimation: {
24655 type: Boolean
24656 },
24657
24658 },
24659
24660 get dimension() {
24661 return this.horizontal ? 'width' : 'height';
24662 },
24663
24664 /**
24665 * `maxWidth` or `maxHeight`.
24666 * @private
24667 */
24668 get _dimensionMax() {
24669 return this.horizontal ? 'maxWidth' : 'maxHeight';
24670 },
24671
24672 /**
24673 * `max-width` or `max-height`.
24674 * @private
24675 */
24676 get _dimensionMaxCss() {
24677 return this.horizontal ? 'max-width' : 'max-height';
24678 },
24679
24680 hostAttributes: {
24681 role: 'group',
24682 'aria-hidden': 'true',
24683 'aria-expanded': 'false'
24684 },
24685
24686 listeners: {
24687 transitionend: '_transitionEnd'
24688 },
24689
24690 attached: function() {
24691 // It will take care of setting correct classes and styles.
24692 this._transitionEnd();
24693 },
24694
24695 /**
24696 * Toggle the opened state.
24697 *
24698 * @method toggle
24699 */
24700 toggle: function() {
24701 this.opened = !this.opened;
24702 },
24703
24704 show: function() {
24705 this.opened = true;
24706 },
24707
24708 hide: function() {
24709 this.opened = false;
24710 },
24711
24712 /**
24713 * Updates the size of the element.
24714 * @param {string} size The new value for `maxWidth`/`maxHeight` as css prop erty value, usually `auto` or `0px`.
24715 * @param {boolean=} animated if `true` updates the size with an animation, otherwise without.
24716 */
24717 updateSize: function(size, animated) {
24718 // No change!
24719 var curSize = this.style[this._dimensionMax];
24720 if (curSize === size || (size === 'auto' && !curSize)) {
24721 return;
24722 }
24723
24724 this._updateTransition(false);
24725 // If we can animate, must do some prep work.
24726 if (animated && !this.noAnimation && this._isDisplayed) {
24727 // Animation will start at the current size.
24728 var startSize = this._calcSize();
24729 // For `auto` we must calculate what is the final size for the animation .
24730 // After the transition is done, _transitionEnd will set the size back t o `auto`.
24731 if (size === 'auto') {
24732 this.style[this._dimensionMax] = '';
24733 size = this._calcSize();
24734 }
24735 // Go to startSize without animation.
24736 this.style[this._dimensionMax] = startSize;
24737 // Force layout to ensure transition will go. Set scrollTop to itself
24738 // so that compilers won't remove it.
24739 this.scrollTop = this.scrollTop;
24740 // Enable animation.
24741 this._updateTransition(true);
24742 }
24743 // Set the final size.
24744 if (size === 'auto') {
24745 this.style[this._dimensionMax] = '';
24746 } else {
24747 this.style[this._dimensionMax] = size;
24748 }
24749 },
24750
24751 /**
24752 * enableTransition() is deprecated, but left over so it doesn't break exist ing code.
24753 * Please use `noAnimation` property instead.
24754 *
24755 * @method enableTransition
24756 * @deprecated since version 1.0.4
24757 */
24758 enableTransition: function(enabled) {
24759 Polymer.Base._warn('`enableTransition()` is deprecated, use `noAnimation` instead.');
24760 this.noAnimation = !enabled;
24761 },
24762
24763 _updateTransition: function(enabled) {
24764 this.style.transitionDuration = (enabled && !this.noAnimation) ? '' : '0s' ;
24765 },
24766
24767 _horizontalChanged: function() {
24768 this.style.transitionProperty = this._dimensionMaxCss;
24769 var otherDimension = this._dimensionMax === 'maxWidth' ? 'maxHeight' : 'ma xWidth';
24770 this.style[otherDimension] = '';
24771 this.updateSize(this.opened ? 'auto' : '0px', false);
24772 },
24773
24774 _openedChanged: function() {
24775 this.setAttribute('aria-expanded', this.opened);
24776 this.setAttribute('aria-hidden', !this.opened);
24777
24778 this.toggleClass('iron-collapse-closed', false);
24779 this.toggleClass('iron-collapse-opened', false);
24780 this.updateSize(this.opened ? 'auto' : '0px', true);
24781
24782 // Focus the current collapse.
24783 if (this.opened) {
24784 this.focus();
24785 }
24786 if (this.noAnimation) {
24787 this._transitionEnd();
24788 }
24789 },
24790
24791 _transitionEnd: function() {
24792 if (this.opened) {
24793 this.style[this._dimensionMax] = '';
24794 }
24795 this.toggleClass('iron-collapse-closed', !this.opened);
24796 this.toggleClass('iron-collapse-opened', this.opened);
24797 this._updateTransition(false);
24798 this.notifyResize();
24799 },
24800
24801 /**
24802 * Simplistic heuristic to detect if element has a parent with display: none
24803 *
24804 * @private
24805 */
24806 get _isDisplayed() {
24807 var rect = this.getBoundingClientRect();
24808 for (var prop in rect) {
24809 if (rect[prop] !== 0) return true;
24810 }
24811 return false;
24812 },
24813
24814 _calcSize: function() {
24815 return this.getBoundingClientRect()[this.dimension] + 'px';
24816 }
24817
24818 });
24819
24820 </script>
24821 <script>
24822
24823 /**
24824 * `Polymer.IronMenuBehavior` implements accessible menu behavior.
24825 *
24826 * @demo demo/index.html
24827 * @polymerBehavior Polymer.IronMenuBehavior
24828 */
24829 Polymer.IronMenuBehaviorImpl = {
24830
24831 properties: {
24832
24833 /**
24834 * Returns the currently focused item.
24835 * @type {?Object}
24836 */
24837 focusedItem: {
24838 observer: '_focusedItemChanged',
24839 readOnly: true,
24840 type: Object
24841 },
24842
24843 /**
24844 * The attribute to use on menu items to look up the item title. Typing th e first
24845 * letter of an item when the menu is open focuses that item. If unset, `t extContent`
24846 * will be used.
24847 */
24848 attrForItemTitle: {
24849 type: String
24850 }
24851 },
24852
24853 hostAttributes: {
24854 'role': 'menu',
24855 'tabindex': '0'
24856 },
24857
24858 observers: [
24859 '_updateMultiselectable(multi)'
24860 ],
24861
24862 listeners: {
24863 'focus': '_onFocus',
24864 'keydown': '_onKeydown',
24865 'iron-items-changed': '_onIronItemsChanged'
24866 },
24867
24868 keyBindings: {
24869 'up': '_onUpKey',
24870 'down': '_onDownKey',
24871 'esc': '_onEscKey',
24872 'shift+tab:keydown': '_onShiftTabDown'
24873 },
24874
24875 attached: function() {
24876 this._resetTabindices();
24877 },
24878
24879 /**
24880 * Selects the given value. If the `multi` property is true, then the select ed state of the
24881 * `value` will be toggled; otherwise the `value` will be selected.
24882 *
24883 * @param {string|number} value the value to select.
24884 */
24885 select: function(value) {
24886 // Cancel automatically focusing a default item if the menu received focus
24887 // through a user action selecting a particular item.
24888 if (this._defaultFocusAsync) {
24889 this.cancelAsync(this._defaultFocusAsync);
24890 this._defaultFocusAsync = null;
24891 }
24892 var item = this._valueToItem(value);
24893 if (item && item.hasAttribute('disabled')) return;
24894 this._setFocusedItem(item);
24895 Polymer.IronMultiSelectableBehaviorImpl.select.apply(this, arguments);
24896 },
24897
24898 /**
24899 * Resets all tabindex attributes to the appropriate value based on the
24900 * current selection state. The appropriate value is `0` (focusable) for
24901 * the default selected item, and `-1` (not keyboard focusable) for all
24902 * other items.
24903 */
24904 _resetTabindices: function() {
24905 var selectedItem = this.multi ? (this.selectedItems && this.selectedItems[ 0]) : this.selectedItem;
24906
24907 this.items.forEach(function(item) {
24908 item.setAttribute('tabindex', item === selectedItem ? '0' : '-1');
24909 }, this);
24910 },
24911
24912 /**
24913 * Sets appropriate ARIA based on whether or not the menu is meant to be
24914 * multi-selectable.
24915 *
24916 * @param {boolean} multi True if the menu should be multi-selectable.
24917 */
24918 _updateMultiselectable: function(multi) {
24919 if (multi) {
24920 this.setAttribute('aria-multiselectable', 'true');
24921 } else {
24922 this.removeAttribute('aria-multiselectable');
24923 }
24924 },
24925
24926 /**
24927 * Given a KeyboardEvent, this method will focus the appropriate item in the
24928 * menu (if there is a relevant item, and it is possible to focus it).
24929 *
24930 * @param {KeyboardEvent} event A KeyboardEvent.
24931 */
24932 _focusWithKeyboardEvent: function(event) {
24933 for (var i = 0, item; item = this.items[i]; i++) {
24934 var attr = this.attrForItemTitle || 'textContent';
24935 var title = item[attr] || item.getAttribute(attr);
24936
24937 if (!item.hasAttribute('disabled') && title &&
24938 title.trim().charAt(0).toLowerCase() === String.fromCharCode(event.k eyCode).toLowerCase()) {
24939 this._setFocusedItem(item);
24940 break;
24941 }
24942 }
24943 },
24944
24945 /**
24946 * Focuses the previous item (relative to the currently focused item) in the
24947 * menu, disabled items will be skipped.
24948 * Loop until length + 1 to handle case of single item in menu.
24949 */
24950 _focusPrevious: function() {
24951 var length = this.items.length;
24952 var curFocusIndex = Number(this.indexOf(this.focusedItem));
24953 for (var i = 1; i < length + 1; i++) {
24954 var item = this.items[(curFocusIndex - i + length) % length];
24955 if (!item.hasAttribute('disabled')) {
24956 this._setFocusedItem(item);
24957 return;
24958 }
24959 }
24960 },
24961
24962 /**
24963 * Focuses the next item (relative to the currently focused item) in the
24964 * menu, disabled items will be skipped.
24965 * Loop until length + 1 to handle case of single item in menu.
24966 */
24967 _focusNext: function() {
24968 var length = this.items.length;
24969 var curFocusIndex = Number(this.indexOf(this.focusedItem));
24970 for (var i = 1; i < length + 1; i++) {
24971 var item = this.items[(curFocusIndex + i) % length];
24972 if (!item.hasAttribute('disabled')) {
24973 this._setFocusedItem(item);
24974 return;
24975 }
24976 }
24977 },
24978
24979 /**
24980 * Mutates items in the menu based on provided selection details, so that
24981 * all items correctly reflect selection state.
24982 *
24983 * @param {Element} item An item in the menu.
24984 * @param {boolean} isSelected True if the item should be shown in a
24985 * selected state, otherwise false.
24986 */
24987 _applySelection: function(item, isSelected) {
24988 if (isSelected) {
24989 item.setAttribute('aria-selected', 'true');
24990 } else {
24991 item.removeAttribute('aria-selected');
24992 }
24993 Polymer.IronSelectableBehavior._applySelection.apply(this, arguments);
24994 },
24995
24996 /**
24997 * Discretely updates tabindex values among menu items as the focused item
24998 * changes.
24999 *
25000 * @param {Element} focusedItem The element that is currently focused.
25001 * @param {?Element} old The last element that was considered focused, if
25002 * applicable.
25003 */
25004 _focusedItemChanged: function(focusedItem, old) {
25005 old && old.setAttribute('tabindex', '-1');
25006 if (focusedItem) {
25007 focusedItem.setAttribute('tabindex', '0');
25008 focusedItem.focus();
25009 }
25010 },
25011
25012 /**
25013 * A handler that responds to mutation changes related to the list of items
25014 * in the menu.
25015 *
25016 * @param {CustomEvent} event An event containing mutation records as its
25017 * detail.
25018 */
25019 _onIronItemsChanged: function(event) {
25020 if (event.detail.addedNodes.length) {
25021 this._resetTabindices();
25022 }
25023 },
25024
25025 /**
25026 * Handler that is called when a shift+tab keypress is detected by the menu.
25027 *
25028 * @param {CustomEvent} event A key combination event.
25029 */
25030 _onShiftTabDown: function(event) {
25031 var oldTabIndex = this.getAttribute('tabindex');
25032
25033 Polymer.IronMenuBehaviorImpl._shiftTabPressed = true;
25034
25035 this._setFocusedItem(null);
25036
25037 this.setAttribute('tabindex', '-1');
25038
25039 this.async(function() {
25040 this.setAttribute('tabindex', oldTabIndex);
25041 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
25042 // NOTE(cdata): polymer/polymer#1305
25043 }, 1);
25044 },
25045
25046 /**
25047 * Handler that is called when the menu receives focus.
25048 *
25049 * @param {FocusEvent} event A focus event.
25050 */
25051 _onFocus: function(event) {
25052 if (Polymer.IronMenuBehaviorImpl._shiftTabPressed) {
25053 // do not focus the menu itself
25054 return;
25055 }
25056
25057 // Do not focus the selected tab if the deepest target is part of the
25058 // menu element's local DOM and is focusable.
25059 var rootTarget = /** @type {?HTMLElement} */(
25060 Polymer.dom(event).rootTarget);
25061 if (rootTarget !== this && typeof rootTarget.tabIndex !== "undefined" && ! this.isLightDescendant(rootTarget)) {
25062 return;
25063 }
25064
25065 // clear the cached focus item
25066 this._defaultFocusAsync = this.async(function() {
25067 // focus the selected item when the menu receives focus, or the first it em
25068 // if no item is selected
25069 var selectedItem = this.multi ? (this.selectedItems && this.selectedItem s[0]) : this.selectedItem;
25070
25071 this._setFocusedItem(null);
25072
25073 if (selectedItem) {
25074 this._setFocusedItem(selectedItem);
25075 } else if (this.items[0]) {
25076 // We find the first none-disabled item (if one exists)
25077 this._focusNext();
25078 }
25079 });
25080 },
25081
25082 /**
25083 * Handler that is called when the up key is pressed.
25084 *
25085 * @param {CustomEvent} event A key combination event.
25086 */
25087 _onUpKey: function(event) {
25088 // up and down arrows moves the focus
25089 this._focusPrevious();
25090 event.detail.keyboardEvent.preventDefault();
25091 },
25092
25093 /**
25094 * Handler that is called when the down key is pressed.
25095 *
25096 * @param {CustomEvent} event A key combination event.
25097 */
25098 _onDownKey: function(event) {
25099 this._focusNext();
25100 event.detail.keyboardEvent.preventDefault();
25101 },
25102
25103 /**
25104 * Handler that is called when the esc key is pressed.
25105 *
25106 * @param {CustomEvent} event A key combination event.
25107 */
25108 _onEscKey: function(event) {
25109 // esc blurs the control
25110 this.focusedItem.blur();
25111 },
25112
25113 /**
25114 * Handler that is called when a keydown event is detected.
25115 *
25116 * @param {KeyboardEvent} event A keyboard event.
25117 */
25118 _onKeydown: function(event) {
25119 if (!this.keyboardEventMatchesKeys(event, 'up down esc')) {
25120 // all other keys focus the menu item starting with that character
25121 this._focusWithKeyboardEvent(event);
25122 }
25123 event.stopPropagation();
25124 },
25125
25126 // override _activateHandler
25127 _activateHandler: function(event) {
25128 Polymer.IronSelectableBehavior._activateHandler.call(this, event);
25129 event.stopPropagation();
25130 }
25131 };
25132
25133 Polymer.IronMenuBehaviorImpl._shiftTabPressed = false;
25134
25135 /** @polymerBehavior Polymer.IronMenuBehavior */
25136 Polymer.IronMenuBehavior = [
25137 Polymer.IronMultiSelectableBehavior,
25138 Polymer.IronA11yKeysBehavior,
25139 Polymer.IronMenuBehaviorImpl
25140 ];
25141
25142 </script>
25143 <script>
25144
25145 /**
25146 * `Polymer.IronMenubarBehavior` implements accessible menubar behavior.
25147 *
25148 * @polymerBehavior Polymer.IronMenubarBehavior
25149 */
25150 Polymer.IronMenubarBehaviorImpl = {
25151
25152 hostAttributes: {
25153 'role': 'menubar'
25154 },
25155
25156 keyBindings: {
25157 'left': '_onLeftKey',
25158 'right': '_onRightKey'
25159 },
25160
25161 _onUpKey: function(event) {
25162 this.focusedItem.click();
25163 event.detail.keyboardEvent.preventDefault();
25164 },
25165
25166 _onDownKey: function(event) {
25167 this.focusedItem.click();
25168 event.detail.keyboardEvent.preventDefault();
25169 },
25170
25171 get _isRTL() {
25172 return window.getComputedStyle(this)['direction'] === 'rtl';
25173 },
25174
25175 _onLeftKey: function(event) {
25176 if (this._isRTL) {
25177 this._focusNext();
25178 } else {
25179 this._focusPrevious();
25180 }
25181 event.detail.keyboardEvent.preventDefault();
25182 },
25183
25184 _onRightKey: function(event) {
25185 if (this._isRTL) {
25186 this._focusPrevious();
25187 } else {
25188 this._focusNext();
25189 }
25190 event.detail.keyboardEvent.preventDefault();
25191 },
25192
25193 _onKeydown: function(event) {
25194 if (this.keyboardEventMatchesKeys(event, 'up down left right esc')) {
25195 return;
25196 }
25197
25198 // all other keys focus the menu item starting with that character
25199 this._focusWithKeyboardEvent(event);
25200 }
25201
25202 };
25203
25204 /** @polymerBehavior Polymer.IronMenubarBehavior */
25205 Polymer.IronMenubarBehavior = [
25206 Polymer.IronMenuBehavior,
25207 Polymer.IronMenubarBehaviorImpl
25208 ];
25209
25210 </script>
25211 <iron-iconset-svg name="paper-tabs" size="24">
25212 <svg><defs>
25213 <g id="chevron-left"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"></p ath></g>
25214 <g id="chevron-right"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z">< /path></g>
25215 </defs></svg>
25216 </iron-iconset-svg>
25217
25218
25219 <dom-module id="paper-tab" assetpath="/res/imp/bower_components/paper-tabs/">
25220 <template>
25221 <style>
25222 :host {
25223 @apply(--layout-inline);
25224 @apply(--layout-center);
25225 @apply(--layout-center-justified);
25226 @apply(--layout-flex-auto);
25227
25228 position: relative;
25229 padding: 0 12px;
25230 overflow: hidden;
25231 cursor: pointer;
25232 vertical-align: middle;
25233
25234 @apply(--paper-font-common-base);
25235 @apply(--paper-tab);
25236 }
25237
25238 :host(:focus) {
25239 outline: none;
25240 }
25241
25242 :host([link]) {
25243 padding: 0;
25244 }
25245
25246 .tab-content {
25247 height: 100%;
25248 transform: translateZ(0);
25249 -webkit-transform: translateZ(0);
25250 transition: opacity 0.1s cubic-bezier(0.4, 0.0, 1, 1);
25251 @apply(--layout-horizontal);
25252 @apply(--layout-center-center);
25253 @apply(--layout-flex-auto);
25254 @apply(--paper-tab-content);
25255 }
25256
25257 :host(:not(.iron-selected)) > .tab-content {
25258 opacity: 0.8;
25259
25260 @apply(--paper-tab-content-unselected);
25261 }
25262
25263 :host(:focus) .tab-content {
25264 opacity: 1;
25265 font-weight: 700;
25266 }
25267
25268 paper-ripple {
25269 color: var(--paper-tab-ink, --paper-yellow-a100);
25270 }
25271
25272 .tab-content > ::content > a {
25273 @apply(--layout-flex-auto);
25274
25275 height: 100%;
25276 }
25277 </style>
25278
25279 <div class="tab-content">
25280 <content></content>
25281 </div>
25282 </template>
25283
25284 <script>
25285 Polymer({
25286 is: 'paper-tab',
25287
25288 behaviors: [
25289 Polymer.IronControlState,
25290 Polymer.IronButtonState,
25291 Polymer.PaperRippleBehavior
25292 ],
25293
25294 properties: {
25295
25296 /**
25297 * If true, the tab will forward keyboard clicks (enter/space) to
25298 * the first anchor element found in its descendants
25299 */
25300 link: {
25301 type: Boolean,
25302 value: false,
25303 reflectToAttribute: true
25304 }
25305
25306 },
25307
25308 hostAttributes: {
25309 role: 'tab'
25310 },
25311
25312 listeners: {
25313 down: '_updateNoink',
25314 tap: '_onTap'
25315 },
25316
25317 attached: function() {
25318 this._updateNoink();
25319 },
25320
25321 get _parentNoink () {
25322 var parent = Polymer.dom(this).parentNode;
25323 return !!parent && !!parent.noink;
25324 },
25325
25326 _updateNoink: function() {
25327 this.noink = !!this.noink || !!this._parentNoink;
25328 },
25329
25330 _onTap: function(event) {
25331 if (this.link) {
25332 var anchor = this.queryEffectiveChildren('a');
25333
25334 if (!anchor) {
25335 return;
25336 }
25337
25338 // Don't get stuck in a loop delegating
25339 // the listener from the child anchor
25340 if (event.target === anchor) {
25341 return;
25342 }
25343
25344 anchor.click();
25345 }
25346 }
25347
25348 });
25349 </script>
25350 </dom-module>
25351
25352
25353 <dom-module id="paper-tabs" assetpath="/res/imp/bower_components/paper-tabs/">
25354 <template>
25355 <style>
25356 :host {
25357 @apply(--layout);
25358 @apply(--layout-center);
25359
25360 height: 48px;
25361 font-size: 14px;
25362 font-weight: 500;
25363 overflow: hidden;
25364 -moz-user-select: none;
25365 -ms-user-select: none;
25366 -webkit-user-select: none;
25367 user-select: none;
25368
25369 /* NOTE: Both values are needed, since some phones require the value to be `transparent`. */
25370 -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
25371 -webkit-tap-highlight-color: transparent;
25372
25373 @apply(--paper-tabs);
25374 }
25375
25376 :host-context([dir=rtl]) {
25377 @apply(--layout-horizontal-reverse);
25378 }
25379
25380 #tabsContainer {
25381 position: relative;
25382 height: 100%;
25383 white-space: nowrap;
25384 overflow: hidden;
25385 @apply(--layout-flex-auto);
25386 }
25387
25388 #tabsContent {
25389 height: 100%;
25390 -moz-flex-basis: auto;
25391 -ms-flex-basis: auto;
25392 flex-basis: auto;
25393 }
25394
25395 #tabsContent.scrollable {
25396 position: absolute;
25397 white-space: nowrap;
25398 }
25399
25400 #tabsContent:not(.scrollable),
25401 #tabsContent.scrollable.fit-container {
25402 @apply(--layout-horizontal);
25403 }
25404
25405 #tabsContent.scrollable.fit-container {
25406 min-width: 100%;
25407 }
25408
25409 #tabsContent.scrollable.fit-container > ::content > * {
25410 /* IE - prevent tabs from compressing when they should scroll. */
25411 -ms-flex: 1 0 auto;
25412 -webkit-flex: 1 0 auto;
25413 flex: 1 0 auto;
25414 }
25415
25416 .hidden {
25417 display: none;
25418 }
25419
25420 .not-visible {
25421 opacity: 0;
25422 cursor: default;
25423 }
25424
25425 paper-icon-button {
25426 width: 48px;
25427 height: 48px;
25428 padding: 12px;
25429 margin: 0 4px;
25430 }
25431
25432 #selectionBar {
25433 position: absolute;
25434 height: 2px;
25435 bottom: 0;
25436 left: 0;
25437 right: 0;
25438 background-color: var(--paper-tabs-selection-bar-color, --paper-yellow-a 100);
25439 -webkit-transform: scale(0);
25440 transform: scale(0);
25441 -webkit-transform-origin: left center;
25442 transform-origin: left center;
25443 transition: -webkit-transform;
25444 transition: transform;
25445
25446 @apply(--paper-tabs-selection-bar);
25447 }
25448
25449 #selectionBar.align-bottom {
25450 top: 0;
25451 bottom: auto;
25452 }
25453
25454 #selectionBar.expand {
25455 transition-duration: 0.15s;
25456 transition-timing-function: cubic-bezier(0.4, 0.0, 1, 1);
25457 }
25458
25459 #selectionBar.contract {
25460 transition-duration: 0.18s;
25461 transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
25462 }
25463
25464 #tabsContent > ::content > *:not(#selectionBar) {
25465 height: 100%;
25466 }
25467 </style>
25468
25469 <paper-icon-button icon="paper-tabs:chevron-left" class$="[[_computeScrollBu ttonClass(_leftHidden, scrollable, hideScrollButtons)]]" on-up="_onScrollButtonU p" on-down="_onLeftScrollButtonDown" tabindex="-1"></paper-icon-button>
25470
25471 <div id="tabsContainer" on-track="_scroll" on-down="_down">
25472 <div id="tabsContent" class$="[[_computeTabsContentClass(scrollable, fitCo ntainer)]]">
25473 <div id="selectionBar" class$="[[_computeSelectionBarClass(noBar, alignB ottom)]]" on-transitionend="_onBarTransitionEnd"></div>
25474 <content select="*"></content>
25475 </div>
25476 </div>
25477
25478 <paper-icon-button icon="paper-tabs:chevron-right" class$="[[_computeScrollB uttonClass(_rightHidden, scrollable, hideScrollButtons)]]" on-up="_onScrollButto nUp" on-down="_onRightScrollButtonDown" tabindex="-1"></paper-icon-button>
25479
25480 </template>
25481
25482 <script>
25483 Polymer({
25484 is: 'paper-tabs',
25485
25486 behaviors: [
25487 Polymer.IronResizableBehavior,
25488 Polymer.IronMenubarBehavior
25489 ],
25490
25491 properties: {
25492 /**
25493 * If true, ink ripple effect is disabled. When this property is changed ,
25494 * all descendant `<paper-tab>` elements have their `noink` property
25495 * changed to the new value as well.
25496 */
25497 noink: {
25498 type: Boolean,
25499 value: false,
25500 observer: '_noinkChanged'
25501 },
25502
25503 /**
25504 * If true, the bottom bar to indicate the selected tab will not be show n.
25505 */
25506 noBar: {
25507 type: Boolean,
25508 value: false
25509 },
25510
25511 /**
25512 * If true, the slide effect for the bottom bar is disabled.
25513 */
25514 noSlide: {
25515 type: Boolean,
25516 value: false
25517 },
25518
25519 /**
25520 * If true, tabs are scrollable and the tab width is based on the label width.
25521 */
25522 scrollable: {
25523 type: Boolean,
25524 value: false
25525 },
25526
25527 /**
25528 * If true, tabs expand to fit their container. This currently only appl ies when
25529 * scrollable is true.
25530 */
25531 fitContainer: {
25532 type: Boolean,
25533 value: false
25534 },
25535
25536 /**
25537 * If true, dragging on the tabs to scroll is disabled.
25538 */
25539 disableDrag: {
25540 type: Boolean,
25541 value: false
25542 },
25543
25544 /**
25545 * If true, scroll buttons (left/right arrow) will be hidden for scrolla ble tabs.
25546 */
25547 hideScrollButtons: {
25548 type: Boolean,
25549 value: false
25550 },
25551
25552 /**
25553 * If true, the tabs are aligned to bottom (the selection bar appears at the top).
25554 */
25555 alignBottom: {
25556 type: Boolean,
25557 value: false
25558 },
25559
25560 selectable: {
25561 type: String,
25562 value: 'paper-tab'
25563 },
25564
25565 /**
25566 * If true, tabs are automatically selected when focused using the
25567 * keyboard.
25568 */
25569 autoselect: {
25570 type: Boolean,
25571 value: false
25572 },
25573
25574 /**
25575 * The delay (in milliseconds) between when the user stops interacting
25576 * with the tabs through the keyboard and when the focused item is
25577 * automatically selected (if `autoselect` is true).
25578 */
25579 autoselectDelay: {
25580 type: Number,
25581 value: 0
25582 },
25583
25584 _step: {
25585 type: Number,
25586 value: 10
25587 },
25588
25589 _holdDelay: {
25590 type: Number,
25591 value: 1
25592 },
25593
25594 _leftHidden: {
25595 type: Boolean,
25596 value: false
25597 },
25598
25599 _rightHidden: {
25600 type: Boolean,
25601 value: false
25602 },
25603
25604 _previousTab: {
25605 type: Object
25606 }
25607 },
25608
25609 hostAttributes: {
25610 role: 'tablist'
25611 },
25612
25613 listeners: {
25614 'iron-resize': '_onTabSizingChanged',
25615 'iron-items-changed': '_onTabSizingChanged',
25616 'iron-select': '_onIronSelect',
25617 'iron-deselect': '_onIronDeselect'
25618 },
25619
25620 keyBindings: {
25621 'left:keyup right:keyup': '_onArrowKeyup'
25622 },
25623
25624 created: function() {
25625 this._holdJob = null;
25626 this._pendingActivationItem = undefined;
25627 this._pendingActivationTimeout = undefined;
25628 this._bindDelayedActivationHandler = this._delayedActivationHandler.bind (this);
25629 this.addEventListener('blur', this._onBlurCapture.bind(this), true);
25630 },
25631
25632 ready: function() {
25633 this.setScrollDirection('y', this.$.tabsContainer);
25634 },
25635
25636 detached: function() {
25637 this._cancelPendingActivation();
25638 },
25639
25640 _noinkChanged: function(noink) {
25641 var childTabs = Polymer.dom(this).querySelectorAll('paper-tab');
25642 childTabs.forEach(noink ? this._setNoinkAttribute : this._removeNoinkAtt ribute);
25643 },
25644
25645 _setNoinkAttribute: function(element) {
25646 element.setAttribute('noink', '');
25647 },
25648
25649 _removeNoinkAttribute: function(element) {
25650 element.removeAttribute('noink');
25651 },
25652
25653 _computeScrollButtonClass: function(hideThisButton, scrollable, hideScroll Buttons) {
25654 if (!scrollable || hideScrollButtons) {
25655 return 'hidden';
25656 }
25657
25658 if (hideThisButton) {
25659 return 'not-visible';
25660 }
25661
25662 return '';
25663 },
25664
25665 _computeTabsContentClass: function(scrollable, fitContainer) {
25666 return scrollable ? 'scrollable' + (fitContainer ? ' fit-container' : '' ) : ' fit-container';
25667 },
25668
25669 _computeSelectionBarClass: function(noBar, alignBottom) {
25670 if (noBar) {
25671 return 'hidden';
25672 } else if (alignBottom) {
25673 return 'align-bottom';
25674 }
25675
25676 return '';
25677 },
25678
25679 // TODO(cdata): Add `track` response back in when gesture lands.
25680
25681 _onTabSizingChanged: function() {
25682 this.debounce('_onTabSizingChanged', function() {
25683 this._scroll();
25684 this._tabChanged(this.selectedItem);
25685 }, 10);
25686 },
25687
25688 _onIronSelect: function(event) {
25689 this._tabChanged(event.detail.item, this._previousTab);
25690 this._previousTab = event.detail.item;
25691 this.cancelDebouncer('tab-changed');
25692 },
25693
25694 _onIronDeselect: function(event) {
25695 this.debounce('tab-changed', function() {
25696 this._tabChanged(null, this._previousTab);
25697 this._previousTab = null;
25698 // See polymer/polymer#1305
25699 }, 1);
25700 },
25701
25702 _activateHandler: function() {
25703 // Cancel item activations scheduled by keyboard events when any other
25704 // action causes an item to be activated (e.g. clicks).
25705 this._cancelPendingActivation();
25706
25707 Polymer.IronMenuBehaviorImpl._activateHandler.apply(this, arguments);
25708 },
25709
25710 /**
25711 * Activates an item after a delay (in milliseconds).
25712 */
25713 _scheduleActivation: function(item, delay) {
25714 this._pendingActivationItem = item;
25715 this._pendingActivationTimeout = this.async(
25716 this._bindDelayedActivationHandler, delay);
25717 },
25718
25719 /**
25720 * Activates the last item given to `_scheduleActivation`.
25721 */
25722 _delayedActivationHandler: function() {
25723 var item = this._pendingActivationItem;
25724 this._pendingActivationItem = undefined;
25725 this._pendingActivationTimeout = undefined;
25726 item.fire(this.activateEvent, null, {
25727 bubbles: true,
25728 cancelable: true
25729 });
25730 },
25731
25732 /**
25733 * Cancels a previously scheduled item activation made with
25734 * `_scheduleActivation`.
25735 */
25736 _cancelPendingActivation: function() {
25737 if (this._pendingActivationTimeout !== undefined) {
25738 this.cancelAsync(this._pendingActivationTimeout);
25739 this._pendingActivationItem = undefined;
25740 this._pendingActivationTimeout = undefined;
25741 }
25742 },
25743
25744 _onArrowKeyup: function(event) {
25745 if (this.autoselect) {
25746 this._scheduleActivation(this.focusedItem, this.autoselectDelay);
25747 }
25748 },
25749
25750 _onBlurCapture: function(event) {
25751 // Cancel a scheduled item activation (if any) when that item is
25752 // blurred.
25753 if (event.target === this._pendingActivationItem) {
25754 this._cancelPendingActivation();
25755 }
25756 },
25757
25758 get _tabContainerScrollSize () {
25759 return Math.max(
25760 0,
25761 this.$.tabsContainer.scrollWidth -
25762 this.$.tabsContainer.offsetWidth
25763 );
25764 },
25765
25766 _scroll: function(e, detail) {
25767 if (!this.scrollable) {
25768 return;
25769 }
25770
25771 var ddx = (detail && -detail.ddx) || 0;
25772 this._affectScroll(ddx);
25773 },
25774
25775 _down: function(e) {
25776 // go one beat async to defeat IronMenuBehavior
25777 // autorefocus-on-no-selection timeout
25778 this.async(function() {
25779 if (this._defaultFocusAsync) {
25780 this.cancelAsync(this._defaultFocusAsync);
25781 this._defaultFocusAsync = null;
25782 }
25783 }, 1);
25784 },
25785
25786 _affectScroll: function(dx) {
25787 this.$.tabsContainer.scrollLeft += dx;
25788
25789 var scrollLeft = this.$.tabsContainer.scrollLeft;
25790
25791 this._leftHidden = scrollLeft === 0;
25792 this._rightHidden = scrollLeft === this._tabContainerScrollSize;
25793 },
25794
25795 _onLeftScrollButtonDown: function() {
25796 this._scrollToLeft();
25797 this._holdJob = setInterval(this._scrollToLeft.bind(this), this._holdDel ay);
25798 },
25799
25800 _onRightScrollButtonDown: function() {
25801 this._scrollToRight();
25802 this._holdJob = setInterval(this._scrollToRight.bind(this), this._holdDe lay);
25803 },
25804
25805 _onScrollButtonUp: function() {
25806 clearInterval(this._holdJob);
25807 this._holdJob = null;
25808 },
25809
25810 _scrollToLeft: function() {
25811 this._affectScroll(-this._step);
25812 },
25813
25814 _scrollToRight: function() {
25815 this._affectScroll(this._step);
25816 },
25817
25818 _tabChanged: function(tab, old) {
25819 if (!tab) {
25820 // Remove the bar without animation.
25821 this.$.selectionBar.classList.remove('expand');
25822 this.$.selectionBar.classList.remove('contract');
25823 this._positionBar(0, 0);
25824 return;
25825 }
25826
25827 var r = this.$.tabsContent.getBoundingClientRect();
25828 var w = r.width;
25829 var tabRect = tab.getBoundingClientRect();
25830 var tabOffsetLeft = tabRect.left - r.left;
25831
25832 this._pos = {
25833 width: this._calcPercent(tabRect.width, w),
25834 left: this._calcPercent(tabOffsetLeft, w)
25835 };
25836
25837 if (this.noSlide || old == null) {
25838 // Position the bar without animation.
25839 this.$.selectionBar.classList.remove('expand');
25840 this.$.selectionBar.classList.remove('contract');
25841 this._positionBar(this._pos.width, this._pos.left);
25842 return;
25843 }
25844
25845 var oldRect = old.getBoundingClientRect();
25846 var oldIndex = this.items.indexOf(old);
25847 var index = this.items.indexOf(tab);
25848 var m = 5;
25849
25850 // bar animation: expand
25851 this.$.selectionBar.classList.add('expand');
25852
25853 var moveRight = oldIndex < index;
25854 var isRTL = this._isRTL;
25855 if (isRTL) {
25856 moveRight = !moveRight;
25857 }
25858
25859 if (moveRight) {
25860 this._positionBar(this._calcPercent(tabRect.left + tabRect.width - old Rect.left, w) - m,
25861 this._left);
25862 } else {
25863 this._positionBar(this._calcPercent(oldRect.left + oldRect.width - tab Rect.left, w) - m,
25864 this._calcPercent(tabOffsetLeft, w) + m);
25865 }
25866
25867 if (this.scrollable) {
25868 this._scrollToSelectedIfNeeded(tabRect.width, tabOffsetLeft);
25869 }
25870 },
25871
25872 _scrollToSelectedIfNeeded: function(tabWidth, tabOffsetLeft) {
25873 var l = tabOffsetLeft - this.$.tabsContainer.scrollLeft;
25874 if (l < 0) {
25875 this.$.tabsContainer.scrollLeft += l;
25876 } else {
25877 l += (tabWidth - this.$.tabsContainer.offsetWidth);
25878 if (l > 0) {
25879 this.$.tabsContainer.scrollLeft += l;
25880 }
25881 }
25882 },
25883
25884 _calcPercent: function(w, w0) {
25885 return 100 * w / w0;
25886 },
25887
25888 _positionBar: function(width, left) {
25889 width = width || 0;
25890 left = left || 0;
25891
25892 this._width = width;
25893 this._left = left;
25894 this.transform(
25895 'translateX(' + left + '%) scaleX(' + (width / 100) + ')',
25896 this.$.selectionBar);
25897 },
25898
25899 _onBarTransitionEnd: function(e) {
25900 var cl = this.$.selectionBar.classList;
25901 // bar animation: expand -> contract
25902 if (cl.contains('expand')) {
25903 cl.remove('expand');
25904 cl.add('contract');
25905 this._positionBar(this._pos.width, this._pos.left);
25906 // bar animation done
25907 } else if (cl.contains('contract')) {
25908 cl.remove('contract');
25909 }
25910 }
25911 });
25912 </script>
25913 </dom-module>
25914 <script>
25915 (function(){
25916
25917
25918 // This behavior wraps up all the shared bot-page functionality by
25919 // extending SwarmingBehaviors.CommonBehavior
25920 SwarmingBehaviors.BotPageBehavior = [SwarmingBehaviors.CommonBehavior, {
25921
25922 // timeDiffApprox returns the approximate difference between now and
25923 // the specified date.
25924 _timeDiffApprox: function(date){
25925 if (!date) {
25926 return "eons";
25927 }
25928 return sk.human.diffDate(date.getTime());
25929 },
25930
25931 // timeDiffExact returns the exact difference between the two specified
25932 // dates. E.g. 2d 22h 22m 28s ago If a second date is not provided,
25933 // now is used.
25934 _timeDiffExact: function(first, second){
25935 if (!first) {
25936 return "eons";
25937 }
25938 if (!second) {
25939 second = new Date();
25940 }
25941 return sk.human.strDuration((second.getTime() - first.getTime())/1000);
25942 },
25943
25944 }];
25945 })()
25946 </script>
25947 <dom-module id="bot-page-data" assetpath="/res/imp/botpage/">
24585 <script> 25948 <script>
24586 (function(){ 25949 (function(){
25950 // Time to wait before requesting a new bot. This is to allow a user to
25951 // type in a name and not have it make one set of requests for each
25952 // keystroke.
25953 var BOT_ID_DEBOUNCE_MS = 400;
25954 var lastRequest;
25955
25956 var BOT_TIMES = ["first_seen_ts", "last_seen_ts"];
25957 var TASK_TIMES = ["started_ts", "completed_ts", "abandoned_ts", "modified_ts "];
24587 25958
24588 Polymer({ 25959 Polymer({
24589 is: 'bot-page-data', 25960 is: 'bot-page-data',
24590 25961
24591 behaviors: [ 25962 behaviors: [
24592 SwarmingBehaviors.CommonBehavior, 25963 SwarmingBehaviors.BotPageBehavior,
24593 ], 25964 ],
24594 25965
24595 properties: { 25966 properties: {
24596 // inputs 25967 // inputs
24597 auth_headers: { 25968 auth_headers: {
24598 type: Object, 25969 type: Object,
24599 }, 25970 },
24600 bot_id: { 25971 bot_id: {
24601 type: String, 25972 type: String,
24602 }, 25973 },
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
24646 type: Object, 26017 type: Object,
24647 }, 26018 },
24648 }, 26019 },
24649 26020
24650 observers: [ 26021 observers: [
24651 "request(auth_headers,bot_id)", 26022 "request(auth_headers,bot_id)",
24652 ], 26023 ],
24653 26024
24654 request: function(){ 26025 request: function(){
24655 if (!this.bot_id || !this.auth_headers) { 26026 if (!this.bot_id || !this.auth_headers) {
24656 console.log("bot_id and auth_headers can't be empty");
24657 return; 26027 return;
24658 } 26028 }
24659 var baseUrl = "/_ah/api/swarming/v1/bot/"+this.bot_id; 26029 if (lastRequest) {
24660 this._getJsonAsync("_bot", baseUrl + "/get", 26030 clearTimeout(lastRequest);
24661 "_busy1", this.auth_headers); 26031 }
24662 // We limit the fields on these two queries to make them faster. 26032
24663 this._getJsonAsync("_events", 26033 lastRequest = setTimeout(function(){
24664 baseUrl + "/events?fields=items(event_type%2Cmessage%2Cquarantined%2Ct ask_id%2Cts%2Cversion)", 26034 lastRequest = undefined;
24665 "_busy2", this.auth_headers); 26035 var baseUrl = "/_ah/api/swarming/v1/bot/"+this.bot_id;
24666 this._getJsonAsync("_tasks", 26036 this._getJsonAsync("_bot", baseUrl + "/get",
24667 baseUrl + "/tasks?fields=items(abandoned_ts%2Cbot_version%2Ccompleted_ ts%2Cduration%2Cexit_code%2Cfailure%2Cinternal_failure%2Cmodified_ts%2Cname%2Cst arted_ts%2Cstate%2Ctask_id%2Ctry_number)", 26037 "_busy1", this.auth_headers);
24668 "_busy3", this.auth_headers); 26038 // We limit the fields on these two queries to make them faster.
26039 this._getJsonAsync("_events",
26040 baseUrl + "/events?fields=items(event_type%2Cmessage%2Cquarantined%2 Ctask_id%2Cts%2Cversion)",
26041 "_busy2", this.auth_headers);
26042 this._getJsonAsync("_tasks",
26043 baseUrl + "/tasks?fields=items(abandoned_ts%2Cbot_version%2Ccomplete d_ts%2Cduration%2Cexit_code%2Cfailure%2Cinternal_failure%2Cmodified_ts%2Cname%2C started_ts%2Cstate%2Ctask_id%2Ctry_number)",
26044 "_busy3", this.auth_headers);
26045 }.bind(this), BOT_ID_DEBOUNCE_MS);
26046
24669 }, 26047 },
24670 26048
24671 _parseBot: function(bot) { 26049 _parseBot: function(bot) {
24672 if (!bot) { 26050 if (!bot) {
24673 return {}; 26051 return {};
24674 } 26052 }
26053 // Do any preprocessing here
26054 bot.state = bot.state || "{}";
26055 bot.state = JSON.parse(bot.state);
26056
26057 // get the disks in an easier to deal with format, sorted by size.
26058 var disks = bot.state.disks || {};
26059 var keys = Object.keys(disks);
26060 if (!keys.length) {
26061 bot.disks = [{"id": "unknown", "mb": 0}];
26062 } else {
26063 bot.disks = [];
26064 for (var i = 0; i < keys.length; i++) {
26065 bot.disks.push({"id":keys[i], "mb":disks[keys[i]].free_mb});
26066 }
26067 // Sort these so the biggest disk comes first.
26068 bot.disks.sort(function(a, b) {
26069 return b.mb - a.mb;
26070 });
26071 }
26072
26073 // Date.toString() looks like "Mon Aug 29 2016 09:03:41 GMT-0400 (EDT)"
26074 // we want to extract the time zone part and append it to the
26075 // locale time.
26076 var str = (new Date()).toString();
26077 var timeZone = str.substring(str.indexOf("("))
26078
26079 BOT_TIMES.forEach(function(time) {
26080 if (bot[time]) {
26081 bot[time] = new Date(bot[time]);
26082 var locale = bot[time].toLocaleString();
26083 bot["human_"+time] = locale + " " + timeZone;
26084 }
26085 });
24675 return bot; 26086 return bot;
24676 }, 26087 },
24677 26088
24678 _parseEvents: function(events) { 26089 _parseEvents: function(events) {
24679 if (!events || !events.items) { 26090 if (!events || !events.items) {
24680 return []; 26091 return [];
24681 } 26092 }
24682 return events.items; 26093 var events = events.items;
26094 var str = (new Date()).toString();
26095 var timeZone = str.substring(str.indexOf("("))
26096 events.forEach(function(event){
26097 // Do any preprocessing here
26098 if (event.ts) {
26099 event.ts = new Date(event.ts);
26100 var locale = event.ts.toLocaleString();
26101 event.human_ts = locale + " " + timeZone;
26102 }
26103 });
26104
26105 // Sort the most recent events first.
26106 events.sort(function(a,b) {
26107 return b.ts - a.ts;
26108 });
26109
26110 return events;
24683 }, 26111 },
24684 26112
24685 _parseTasks: function(tasks) { 26113 _parseTasks: function(tasks) {
24686 if (!tasks || !tasks.items) { 26114 if (!tasks || !tasks.items) {
24687 return []; 26115 return [];
24688 } 26116 }
24689 return tasks.items; 26117 var tasks = tasks.items;
26118
26119 // Date.toString() looks like "Mon Aug 29 2016 09:03:41 GMT-0400 (EDT)"
26120 // we want to extract the time zone part and append it to the
26121 // locale time.
26122 var str = (new Date()).toString();
26123 var timeZone = str.substring(str.indexOf("("))
26124 tasks.forEach(function(task){
26125 // Do any preprocessing here
26126 TASK_TIMES.forEach(function(time) {
26127 if (task[time]) {
26128 task[time] = new Date(task[time]);
26129 var locale = task[time].toLocaleString();
26130 task["human_"+time] = locale + " " + timeZone;
26131 }
26132 });
26133
26134
26135 if (task.duration) {
26136 task.human_duration = sk.human.strDuration(task.duration) || "0s";
26137 } else {
26138 var end = task.completed_ts || task.abandoned_ts || task.modified_ts || new Date();
26139 task.human_duration = this._timeDiffExact(task.started_ts, end);
26140 }
26141
26142 task.state = task.state || "UNKNOWN";
26143 if (task.state === "COMPLETED") {
26144 if (task.failure) {
26145 task.state = "FAILURE";
26146 } else {
26147 task.state = "SUCCESS";
26148 }
26149 }
26150
26151 }.bind(this));
26152
26153 // Sort the most recent tasks first.
26154 tasks.sort(function(a,b) {
26155 return b.started_ts - a.started_ts;
26156 });
26157
26158 return tasks;
24690 } 26159 }
24691 26160
24692 }); 26161 });
24693 })(); 26162 })();
24694 </script> 26163 </script>
24695 </dom-module> 26164 </dom-module>
24696 <dom-module id="bot-page" assetpath="/res/imp/botpage/"> 26165 <dom-module id="bot-page" assetpath="/res/imp/botpage/">
24697 <template> 26166 <template>
24698 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style"> 26167 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style">
24699 26168
26169 .header {
26170 max-width: 450px;
26171 }
26172
26173 .title {
26174 font-size: 1.5em;
26175 font-weight: bold;
26176 margin-bottom: 5px;
26177 }
26178 .id_input {
26179 --paper-input-container-input: {
26180 font-size: 2em;
26181 };
26182 }
26183 .refresh {
26184 max-width: 65px;
26185 max-height: 65px;
26186 width: initial;
26187 height: initial;
26188 margin: 6px;
26189 }
26190
26191 table {
26192 border-collapse: collapse;
26193 margin-left: 5px;
26194 margin-bottom: 5px;
26195 }
26196 td, th {
26197 border: 1px solid #BBB;
26198 padding: 5px;
26199 }
26200
26201 .quarantined, .failed_task {
26202 background-color: #ffdddd;
26203 }
26204 .dead {
26205 background-color: #cccccc;
26206 }
26207
26208 .message {
26209 white-space: pre-line;
26210 font-family: monospace;
26211 }
26212
26213 .bot_state {
26214 white-space: pre;
26215 font-family: monospace;
26216 margin-bottom: 10px;
26217 }
26218
26219 .show_all {
26220 margin-bottom: 5px;
26221 }
26222
26223 paper-tabs {
26224 background-color: #1F78B4;
26225 color: #fff;
26226 max-width: 600px;
26227 margin-bottom: 5px;
26228 }
26229 paper-tab.iron-selected {
26230 background-color: #A6CEE3;
26231 border: 2px solid #1F78B4;
26232 color: #fff;
26233 font-weight: bold;
26234 text-decoration: underline;
26235 }
26236
24700 </style> 26237 </style>
24701 26238
24702 <url-param name="id" value="{{bot_id}}"> 26239 <url-param name="id" value="{{bot_id}}">
24703 </url-param> 26240 </url-param>
26241 <url-param name="show_all_events" value="{{_show_all}}">
26242 </url-param>
26243 <url-param name="selected" value="{{_selected}}">
26244 </url-param>
26245 <url-param name="show_state" value="{{_show_state}}">
26246 </url-param>
24704 26247
24705 <swarming-app client_id="[[client_id]]" auth_headers="{{_auth_headers}}" sig ned_in="{{_signed_in}}" busy="[[_busy]]" name="Swarming Bot Page"> 26248 <swarming-app client_id="[[client_id]]" auth_headers="{{_auth_headers}}" per missions="{{_permissions}}" signed_in="{{_signed_in}}" busy="[[_busy]]" name="Sw arming Bot Page">
24706 26249
24707 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> 26250 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
24708 26251
24709 <div hidden$="[[_not(_signed_in)]]"> 26252 <div hidden$="[[_not(_signed_in)]]">
24710 26253
24711 <bot-page-data auth_headers="[[_auth_headers]]" bot_id="[[bot_id]]" bot= "{{_bot}}" busy="{{_busy}}" events="{{_events}}" tasks="{{_tasks}}"> 26254 <bot-page-data auth_headers="[[_auth_headers]]" bot_id="[[bot_id]]" bot= "{{_bot}}" busy="{{_busy}}" events="{{_events}}" tasks="{{_tasks}}">
24712 </bot-page-data> 26255 </bot-page-data>
24713 26256
24714 <h1> Bot Page Stub </h1> 26257 <div class="header horizontal layout">
24715 </div> 26258 <paper-input class="id_input" label="Bot id" value="{{bot_id}}"></pape r-input>
26259 <paper-icon-button class="refresh" icon="icons:refresh"></paper-icon-b utton>
26260 </div>
26261
26262 <div>
26263 <table>
26264 <tbody><tr class$="[[_isDead(_bot)]]">
26265 <td>Last Seen</td>
26266 <td title="[[_bot.human_last_seen_ts]]">
26267 [[_timeDiffExact(_bot.last_seen_ts)]] ago</td>
26268 <td>
26269
26270 <template is="dom-if" if="[[_canShutdown(_bot,_permissions)]]">
26271 <paper-button raised="">
26272 Shut Down Gracefully
26273 </paper-button>
26274 </template>
26275 <template is="dom-if" if="[[_canDelete(_bot,_permissions)]]">
26276 <paper-button raised="">
26277 Delete Bot
26278 </paper-button>
26279 </template>
26280 </td>
26281 </tr>
26282 <template is="dom-if" if="[[_bot.quarantined]]">
26283 <tr class="quarantined">
26284 <td>Quarantined</td>
26285 <td colspan="2">[[_quarantineMessage(_bot)]]</td>
26286 </tr>
26287 </template>
26288 <tr>
26289 <td>Current Task</td>
26290 <td>
26291 <a target="_blank" href$="[[_taskLink(_bot.task_id)]]">
26292 [[_task(_bot)]]
26293 </a>
26294 </td>
26295 <td>
26296
26297 </td>
26298 </tr>
26299 <tr>
26300 <td rowspan$="[[_numRows(_bot.dimensions)]]">Dimensions</td>
26301 </tr>
26302 <template is="dom-repeat" items="[[_bot.dimensions]]" as="dim">
26303 <tr>
26304 <td>[[dim.key]]</td>
26305 <td>[[_concat(dim.value)]]</td>
26306 </tr>
26307 </template>
26308
26309 <tr>
26310 <td>External IP</td>
26311 <td>[[_bot.external_ip]]</td>
26312 <td></td>
26313 </tr>
26314 <tr>
26315 <td>Swarming Revision</td>
26316 <td>
26317 <a target="_blank" href$="[[_luciLink(_bot.version)]]">[[_shorte n(_bot.version,'8')]]</a>
26318 </td>
26319 <td></td>
26320 </tr>
26321 <tr>
26322 <td>First seen</td>
26323 <td title="[[_bot.human_first_seen_ts]]">
26324 [[_timeDiffApprox(_bot.first_seen_ts)]] ago
26325 </td>
26326 <td></td>
26327 </tr>
26328 <tr>
26329 <td>Authenticated as</td>
26330 <td>[[_bot.authenticated_as]]</td>
26331 <td></td>
26332 </tr>
26333 </tbody></table>
26334
26335 <span class="title">State</span>
26336 <paper-icon-button hidden="[[_show_state]]" icon="icons:add-circle-out line" on-tap="_toggleState">
26337 </paper-icon-button>
26338 <paper-icon-button hidden="[[_not(_show_state)]]" icon="icons:remove-c ircle-outline" on-tap="_toggleState">
26339 </paper-icon-button>
26340
26341 <iron-collapse id="collapse" opened="[[_show_state]]">
26342 <div class="bot_state">[[_prettyPrint(_bot.state)]]</div>
26343 </iron-collapse>
26344 </div>
26345
26346 <paper-tabs selected="{{_selected}}" no-bar="">
26347 <paper-tab>Tasks</paper-tab>
26348 <paper-tab>Events</paper-tab>
26349 </paper-tabs>
26350
26351 <template is="dom-if" if="[[_not(_selected)]]">
26352 <table>
26353 <thead>
26354 <tr>
26355 <th>Task</th>
26356 <th>Started</th>
26357 <th>Duration</th>
26358 <th>Result</th>
26359 </tr>
26360 </thead>
26361 <tbody>
26362 <template is="dom-repeat" items="{{_tasks}}" as="task">
26363 <tr>
26364 <td><a target="_blank" href$="[[_taskLink(task.task_id)]]">[[t ask.name]]</a></td>
26365 <td>[[task.human_started_ts]]</td>
26366 <td title="[[task.human_completed_ts]]">[[task.human_duration] ]</td>
26367 <td>[[task.state]]</td>
26368 </tr>
26369 </template>
26370 </tbody>
26371 </table>
26372 </template>
26373
26374 <template is="dom-if" if="[[_selected]]">
26375 <div class="tab_content">
26376 <paper-checkbox class="show_all" checked="{{_show_all}}">
26377 Show all events
26378 </paper-checkbox>
26379 <table class="events_table">
26380 <thead>
26381 <tr>
26382 <th>Message</th>
26383 <th>Type</th>
26384 <th>Timestamp</th>
26385 <th>Task ID</th>
26386 <th>Version</th>
26387 </tr>
26388 </thead>
26389 <tbody>
26390 <template is="dom-repeat" items="{{_eventList(_events,_show_all) }}" as="event">
26391 <tr>
26392 <td class="message">[[event.message]]</td>
26393 <td>[[event.event_type]]</td>
26394 <td>[[event.human_ts]]</td>
26395 <td><a target="_blank" href$="[[_taskLink(event.task_id)]]"> [[event.task_id]]</a></td>
26396 <td>
26397 <a target="_blank" href$="[[_luciLink(_bot.version)]]">[[_ shorten(_bot.version,'8')]]</a>
26398 </td>
26399 </tr>
26400 </template>
26401 </tbody>
26402 </table>
26403 </div>
26404 </template>
26405 </div>
26406
24716 26407
24717 </swarming-app> 26408 </swarming-app>
24718 26409
24719 </template> 26410 </template>
24720 <script> 26411 <script>
24721 (function(){ 26412 (function(){
24722 26413
24723 26414
24724 Polymer({ 26415 Polymer({
24725 is: 'bot-page', 26416 is: 'bot-page',
24726 26417
24727 behaviors: [ 26418 behaviors: [
24728 SwarmingBehaviors.CommonBehavior, 26419 SwarmingBehaviors.BotPageBehavior,
24729 ], 26420 ],
24730 26421
24731 properties: { 26422 properties: {
24732 bot_id: { 26423 bot_id: {
24733 type: String, 26424 type: String,
24734 }, 26425 },
24735 client_id: { 26426 client_id: {
24736 type: String, 26427 type: String,
24737 }, 26428 },
24738 26429
26430 // private
26431 _bot: {
26432 type: Object,
26433 },
26434 _selected: {
26435 type: Number,
26436 },
26437 _show_all: {
26438 type: Boolean,
26439 },
26440 _show_state: {
26441 type: Boolean,
26442 }
24739 }, 26443 },
24740 26444
26445 _canCancel: function(bot, permissions) {
26446 return bot && bot.task_id && permissions.cancel_task;
26447 },
26448
26449 _canDelete: function(bot, permissions) {
26450 return bot && bot.is_dead && permissions.delete_bot;
26451 },
26452
26453 _canShutdown: function(bot, permissions){
26454 return bot && !bot.is_dead && permissions.terminate_bot;
26455 },
26456
26457 _concat: function(arr) {
26458 if (!arr) {
26459 return "";
26460 }
26461 return arr.join(" | ");
26462 },
26463
26464 _eventList(events, showAll) {
26465 if (!events) {
26466 return [];
26467 }
26468 return events.filter(function(e){
26469 return showAll || e.message;
26470 });
26471 },
26472
26473 _isDead(bot){
26474 if (bot && bot.is_dead) {
26475 return "dead";
26476 }
26477 return "";
26478 },
26479
26480 _luciLink: function(revision) {
26481 if (!revision) {
26482 return undefined;
26483 }
26484 return "https://github.com/luci/luci-py/commit/" + revision;
26485
26486 },
26487
26488 _numRows: function(arr) {
26489 if (!arr || !arr.length) {
26490 return 1;
26491 }
26492 return 1 + arr.length;
26493 },
26494
26495 _prettyPrint: function(obj) {
26496 obj = obj || {};
26497 return JSON.stringify(obj, null, 2);
26498 },
26499
26500 _quarantineMessage: function(bot) {
26501 if (bot && bot.quarantined) {
26502 var msg = bot.state.quarantined;
26503 // Sometimes, the quarantined message is actually in "error". This
26504 // happens when the bot code has thrown an exception.
26505 if (msg === undefined || msg === "true" || msg === true) {
26506 msg = this._attribute(bot, "error");
26507 }
26508 return msg || "True";
26509 }
26510 return "";
26511 },
26512
26513 _shorten: function(str, length) {
26514 if (!str || ! length) {
26515 return "";
26516 }
26517 return str.substring(0, length);
26518 },
26519
26520 _task: function(bot) {
26521 return (bot && bot.task_id) || "idle";
26522 },
26523
26524 _taskLink: function(task_id) {
26525 // TODO(kjlubick): Migrate this to /newui/ when ready
26526 if (task_id) {
26527 return "/user/task/" + task_id;
26528 }
26529 return undefined;
26530 },
26531
26532 _toggleState: function() {
26533 this.set("_show_state", !this._show_state);
26534 }
26535
24741 }); 26536 });
24742 })(); 26537 })();
24743 </script> 26538 </script>
24744 </dom-module></div></body></html> 26539 </dom-module></div></body></html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698