| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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> |
| OLD | NEW |