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

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

Issue 2291323002: Introduce new bot-page UI (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@bot-page
Patch Set: Tweak table to look contiguous Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | appengine/swarming/elements/res/imp/botlist/bot-list.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 <!DOCTYPE html><html><head><!-- 1 <!DOCTYPE html><html><head><!--
2 @license 2 @license
3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt 4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
7 Code distributed by Google as part of the polymer project is also 7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
9 --><!-- 9 --><!--
10 @license 10 @license
(...skipping 8099 matching lines...) Expand 10 before | Expand all | Expand 10 after
8110 // because computed values in Polymer don't fire if a property is 8110 // because computed values in Polymer don't fire if a property is
8111 // undefined. Clients should check that bindTo is not falsey. 8111 // undefined. Clients should check that bindTo is not falsey.
8112 _getJsonAsync: function(bindTo, url, busy, headers, params) { 8112 _getJsonAsync: function(bindTo, url, busy, headers, params) {
8113 if (!bindTo || !url) { 8113 if (!bindTo || !url) {
8114 console.log("Need at least a polymer element to bind to and a url"); 8114 console.log("Need at least a polymer element to bind to and a url");
8115 return; 8115 return;
8116 } 8116 }
8117 if (busy) { 8117 if (busy) {
8118 this.set(busy, true); 8118 this.set(busy, true);
8119 } 8119 }
8120 url = url + "?" + sk.query.fromParamSet(params); 8120 if (params) {
8121 url = url + "?" + sk.query.fromParamSet(params);
8122 }
8121 sk.request("GET", url, "", headers).then(JSON.parse).then(function(json) { 8123 sk.request("GET", url, "", headers).then(JSON.parse).then(function(json) {
8122 this.set(bindTo, json); 8124 this.set(bindTo, json);
8123 if (busy) { 8125 if (busy) {
8124 this.set(busy, false); 8126 this.set(busy, false);
8125 } 8127 }
8126 }.bind(this)).catch(function(reason){ 8128 }.bind(this)).catch(function(reason){
8127 console.log("Reason for failure of request to " + url, reason); 8129 console.log("Reason for failure of request to " + url, reason);
8128 this.set(bindTo, false); 8130 this.set(bindTo, false);
8129 if (busy) { 8131 if (busy) {
8130 this.set(busy, false); 8132 this.set(busy, false);
(...skipping 8371 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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
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>
OLDNEW
« no previous file with comments | « no previous file | appengine/swarming/elements/res/imp/botlist/bot-list.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698