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