You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2016/02/05 17:58:56 UTC
[1/2] ambari git commit: AMBARI-14923. Log Search: Create Log File
Search modal popup (alexantonenko)
Repository: ambari
Updated Branches:
refs/heads/trunk cd35e806c -> b567ca441
AMBARI-14923. Log Search: Create Log File Search modal popup (alexantonenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/3a2add5d
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/3a2add5d
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/3a2add5d
Branch: refs/heads/trunk
Commit: 3a2add5dcde4f1cb730253627b87683f3057c5c1
Parents: cd35e80
Author: Alex Antonenko <hi...@gmail.com>
Authored: Fri Feb 5 18:21:10 2016 +0200
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Fri Feb 5 18:21:10 2016 +0200
----------------------------------------------------------------------
ambari-web/app/assets/licenses/NOTICE.txt | 5 +-
ambari-web/app/assets/test/tests.js | 1 +
ambari-web/app/config.js | 3 +-
ambari-web/app/messages.js | 9 +-
ambari-web/app/mixins.js | 1 +
.../app/mixins/common/infinite_scroll_mixin.js | 173 ++++++++++++
ambari-web/app/styles/log_file_search.less | 155 +++++++++++
.../app/templates/common/log_file_search.hbs | 109 ++++++++
ambari-web/app/views.js | 3 +
.../app/views/common/form/datepicker_view.js | 37 +++
.../app/views/common/log_file_search_view.js | 272 +++++++++++++++++++
.../modal_popups/log_file_search_popup.js | 26 ++
.../views/common/log_file_search_view_test.js | 103 +++++++
.../vendor/scripts/bootstrap-contextmenu.js | 205 ++++++++++++++
14 files changed, 1099 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/assets/licenses/NOTICE.txt
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/licenses/NOTICE.txt b/ambari-web/app/assets/licenses/NOTICE.txt
index bc29209..c750a37 100644
--- a/ambari-web/app/assets/licenses/NOTICE.txt
+++ b/ambari-web/app/assets/licenses/NOTICE.txt
@@ -56,4 +56,7 @@ This product includes bootstrap-checkbox v.1.0.1 (https://github.com/montrezorro
Copyright (C) 2014 Roberto Montresor (info [at] robertomontresor [*dot*] it)
This product includes sticky-kit v.1.1.2 (https://github.com/leafo/sticky-kit - MIT License)
-Copyright (C) 2015 Leaf Corcoran (leafot [at] gmail [*dot*] com)
\ No newline at end of file
+Copyright (C) 2015 Leaf Corcoran (leafot [at] gmail [*dot*] com)
+
+This product includes bootstrap-contextmenu v.0.3.3 (https://github.com/sydcanem/bootstrap-contextmenu - MIT License)
+Copyright (C) 2015 James Santos
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index 85c814d..e82a27e 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -288,6 +288,7 @@ var files = [
'test/views/common/configs/widgets/time_interval_spinner_view_test',
'test/views/common/form/spinner_input_view_test',
'test/views/common/form/manage_kdc_credentials_form_test',
+ 'test/views/common/log_file_search_view_test',
'test/views/wizard/step3/hostLogPopupBody_view_test',
'test/views/wizard/step3/hostWarningPopupBody_view_test',
'test/views/wizard/step3/hostWarningPopupFooter_view_test',
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/config.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/config.js b/ambari-web/app/config.js
index 0beafb3..7d727d7 100644
--- a/ambari-web/app/config.js
+++ b/ambari-web/app/config.js
@@ -80,7 +80,8 @@ App.supports = {
storeKDCCredentials: true,
preInstallChecks: false,
hostComboSearchBox: false,
- serviceAutoStart: false
+ serviceAutoStart: false,
+ logSearch: false
};
if (App.enableExperimental) {
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index f24876b..8d1f6f1 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -305,6 +305,13 @@ Em.I18n.translations = {
'common.openNewWindow': 'Open in New Window',
'common.fullLogPopup.clickToCopy': 'Click to Copy',
'common.nothingToDelete': 'Nothing to delete',
+ 'common.exclude': 'Exclude',
+ 'common.include': 'Include',
+ 'common.exclude.short': 'Excl',
+ 'common.include.short': 'Incl',
+ 'common.filters': 'Filters',
+ 'common.keywords': 'Keywods',
+ 'common.levels': 'Levels',
'models.alert_instance.tiggered.verbose': "Occurred on {0} <br> Checked on {1}",
'models.alert_definition.triggered.verbose': "Occurred on {0}",
@@ -1555,7 +1562,7 @@ Em.I18n.translations = {
'admin.stackUpgrade.failedHosts.details': "Open Details",
'admin.stackUpgrade.doThisLater': "Do This Later",
'admin.stackUpgrade.pauseUpgrade': "Pause Upgrade",
- 'admin.stackUpgrade.pauseDowngrade': "Pause Downgrade",
+ 'admin.stackUpgrade.pauseDowngrade': "Pause Downgrade",
'admin.stackUpgrade.downgrade.proceed': "Proceed with Downgrade",
'admin.stackUpgrade.downgrade.body': "Are you sure you wish to abort the upgrade process and downgrade to <b>{0}</b>?",
'admin.stackUpgrade.downgrade.retry.body': "Are you sure you wish to retry downgrade to <b>{0}</b>?",
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/mixins.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js
index 38d112d..460953e 100644
--- a/ambari-web/app/mixins.js
+++ b/ambari-web/app/mixins.js
@@ -22,6 +22,7 @@
require('mixins/common/blueprint');
require('mixins/common/kdc_credentials_controller_mixin');
require('mixins/common/localStorage');
+require('mixins/common/infinite_scroll_mixin');
require('mixins/common/userPref');
require('mixins/common/reload_popup');
require('mixins/common/serverValidator');
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/mixins/common/infinite_scroll_mixin.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/infinite_scroll_mixin.js b/ambari-web/app/mixins/common/infinite_scroll_mixin.js
new file mode 100644
index 0000000..70c424e
--- /dev/null
+++ b/ambari-web/app/mixins/common/infinite_scroll_mixin.js
@@ -0,0 +1,173 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+ /**
+ * @typedef {Object} InfiniteScrollMixinOptions
+ * @property {String} appendHtml html to append when scroll ends and callback executed. It's common
+ * that root node has unique <code>id</code> or <code>class</code> attributes.
+ * @property {Function} callback function to execute when scroll ends. This function should return
+ * <code>$.Deferred().promise()</code> instance
+ * @property {Function} onReject function to execute when <code>callback</code> rejected.
+ */
+
+/**
+ * @mixin App.InfiniteScrollMixin
+ * This mixin provides methods to attach infinite scroll to specific scrollable element.
+ *
+ * Usage:
+ * <code>
+ * // mix it
+ * var myView = Em.View.extend(App.InfiniteScrollMixin, {
+ * didInsertElement: function() {
+ * // call method infiniteScrollInit
+ * this.infiniteScrollInit($('.some-scrollable'), {
+ * callback: this.someCallbacOnEndReached
+ * });
+ * }
+ * });
+ * </code>
+ *
+ */
+App.InfiniteScrollMixin = Ember.Mixin.create({
+
+ /**
+ * Stores callback execution progress.
+ *
+ * @type {Boolean}
+ */
+ _infiniteScrollCallbackInProgress: false,
+
+ /**
+ * Stores HTMLElement infinite scroll initiated on.
+ *
+ * @type {HTMLElement}
+ */
+ _infiniteScrollEl: null,
+
+ /**
+ * Default options for infinite scroll.
+ *
+ * @type {InfiniteScrollMixinOptions}
+ */
+ _infiniteScrollDefaults: {
+ appendHtml: '<div id="infinite-scroll-append"><i class="icon-spinner icon-spin"></i></div>',
+ callback: function() { return $.Deferred().resolve().promise(); },
+ onReject: function() {},
+ onResolve: function() {}
+ },
+
+ /**
+ * Initialize infinite scroll on specified HTMLElement.
+ *
+ * @param {HTMLElement} el DOM element to attach infinite scroll.
+ * @param {InfiniteScrollMixinOptions} opts
+ */
+ infiniteScrollInit: function(el, opts) {
+ var options = $.extend({}, this.get('_infiniteScrollDefaults'), opts || {});
+ this.set('_infiniteScrollEl', el);
+ this.get('_infiniteScrollEl').on('scroll', this._infiniteScrollHandler.bind(this));
+ this.get('_infiniteScrollEl').on('infinite-scroll-end', this._infiniteScrollEndHandler(options).bind(this));
+ },
+
+ /**
+ * Handler executed on scrolling.
+ * @param {jQuery.Event} e
+ */
+ _infiniteScrollHandler: function(e) {
+ var el = $(e.target);
+ var height = el.get(0).clientHeight;
+ var scrollHeight = el.prop('scrollHeight');
+ var endPoint = scrollHeight - height;
+ if (endPoint === el.scrollTop() && !this.get('_infiniteScrollCallbackInProgress')) {
+ el.trigger('infinite-scroll-end');
+ }
+ },
+
+ /**
+ * Handler called when scroll ends.
+ *
+ * @param {InfiniteScrollMixinOptions} options
+ * @return {Function}
+ */
+ _infiniteScrollEndHandler: function(options) {
+ return function(e) {
+ var self = this;
+ if (this.get('_infiniteScrollCallbackInProgress')) return;
+ this._infiniteScrollAppendHtml(options.appendHtml);
+ // always scroll to bottom
+ this.get('_infiniteScrollEl').scrollTop(this.get('_infiniteScrollEl').get(0).scrollHeight);
+ this.set('_infiniteScrollCallbackInProgress', true);
+ options.callback().then(function() {
+ options.onResolve();
+ }, function() {
+ options.onReject();
+ }).always(function() {
+ self.set('_infiniteScrollCallbackInProgress', false);
+ self._infiniteScrollRemoveHtml(options.appendHtml);
+ });
+ }.bind(this);
+ },
+
+ /**
+ * Helper function to append String as html node to.
+ * @param {String} htmlString string to append
+ */
+ _infiniteScrollAppendHtml: function(htmlString) {
+ this.get('_infiniteScrollEl').append(htmlString);
+ },
+
+ /**
+ * Remove HTMLElement by specified string that can be converted to html. HTMLElement root node
+ * should have unique <code>id</code> or <code>class</code> attribute to avoid removing additional
+ * elements.
+ *
+ * @param {String} htmlString string to remove
+ */
+ _infiniteScrollRemoveHtml: function(htmlString) {
+ this.get('_infiniteScrollEl').find(this._infiniteScrollGetSelector(htmlString)).remove();
+ },
+
+ /**
+ * Get root node selector.
+ * <code>id</code> attribute has higher priority and will return if found.
+ * <code>class</code> if no <code>id</code> attribute found <code>class</code> attribute
+ * will be used.
+ *
+ * @param {String} htmlString string processed as HTML
+ * @return {[type]} [description]
+ */
+ _infiniteScrollGetSelector: function(htmlString) {
+ var html = $(htmlString);
+ var elId = html.attr('id');
+ var elClass = (html.attr('class') || '').split(' ').join('.');
+ html = null;
+ return !!elId ? '#' + elId : '.' + elClass;
+ },
+
+ /**
+ * Remove infinite scroll.
+ * Unbind all listeners.
+ */
+ infiniteScrollDestroy: function() {
+ this.get('_infiniteScrollEl').off('scroll', this._infiniteScrollHandler);
+ this.get('_infiniteScrollEl').off('infinite-scroll-end', this._infiniteScrollHandler);
+ this.set('_infiniteScrollEl', null);
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/styles/log_file_search.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/log_file_search.less b/ambari-web/app/styles/log_file_search.less
new file mode 100644
index 0000000..aeeea3f
--- /dev/null
+++ b/ambari-web/app/styles/log_file_search.less
@@ -0,0 +1,155 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+@toolbar-context-menu-width: 40px;
+@toolbar-padding: 10px;
+
+.log-file-search-popup {
+ .modal-body {
+ overflow: hidden;
+ }
+}
+
+.log-file-search-toolbar {
+ border: 1px solid #ddd;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background: #e6f1f6;
+
+ .toolbar-row {
+ margin-bottom: 5px;
+
+ input {
+ margin: 0;
+ }
+ &:last-child {
+ padding-bottom: @toolbar-padding;
+ margin-bottom: 0;
+ }
+ }
+
+ .filter-block {
+ width: calc(~"100%" - @toolbar-context-menu-width + 4px);
+ padding: @toolbar-padding;
+ box-sizing: border-box;
+ margin-bottom: -400px;
+ padding-bottom: 400px;
+
+ input {
+ height: 16px;
+ }
+
+ .date-filter {
+ margin-left: 10px;
+
+ input {
+ width: 100px;
+ }
+ }
+ }
+
+ .levels-filter-block {
+ .level-checkbox {
+ margin-right: 20px;
+ }
+ }
+
+ .include-exclude-filter-block {
+ .keywords-list {
+ display: inline-block;
+
+ .keyword-item {
+ font-size: 12px;
+ display: inline-block;
+ padding: 2px;
+ border-radius: 0;
+
+ .close {
+ color: black;
+ font-size: 16px;
+ margin-left: 4px;
+ }
+ }
+ }
+ }
+
+ .context-menu {
+ width: @toolbar-context-menu-width;
+ border-left: 2px solid #ddd;
+ margin-bottom: -400px;
+ padding-bottom: 400px;
+ text-align: center;
+
+ .icon-external-link,
+ .move-to-top,
+ .move-to-bottom {
+ display: block;
+ font-size: 28px;
+ color: grey;
+ }
+
+ .move-to-top {
+ line-height: 20px;
+ }
+ .move-to-bottom {
+ line-height: 18px;
+ }
+
+ .icon-external-link {
+ margin-top: 5px;
+ font-size: 20px;
+ padding-left: 4px;
+ }
+ }
+}
+
+.log-file-search-content {
+ &.container {
+ width: 100%;
+ height: 300px;
+ overflow-y: auto;
+ border: 1px solid #ddd;
+ }
+
+ .log-data-item {
+ border-bottom: 1px solid #ddd;
+
+ &:nth-child(2n) {
+ background: #f2f2f2;
+ }
+
+ .log-data-date {
+ border-right: 2px solid #ddd;
+ width: 7%;
+ padding-left: 4px;
+ }
+ .log-data-message {
+ width: 91%;
+ }
+ }
+
+ #infinite-scroll-append {
+ text-align: center;
+ margin-top: 10px;
+
+ .icon-spinner {
+ font-size: 33px;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/templates/common/log_file_search.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/common/log_file_search.hbs b/ambari-web/app/templates/common/log_file_search.hbs
new file mode 100644
index 0000000..a874df6
--- /dev/null
+++ b/ambari-web/app/templates/common/log_file_search.hbs
@@ -0,0 +1,109 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<div class="log-file-search-toolbar">
+ <div class="filter-block pull-left">
+ <div class="toolbar-row">
+ <div class="display-inline-block">
+ <div class="display-inline-block">{{t common.keywords}}:</div>
+ {{view view.keywordsFilterView classNames="display-inline-block" valueBinding="view.keywordsFilterValue"}}
+ <button {{action submitKeywordsValue target="view"}} class="btn btn-primary display-inline-block">Search</button>
+ </div>
+ <div class="display-inline-block date-filter">
+ <div class="display-inline-block date-filter-item">
+ {{t from}}:
+ {{view App.DatepickerFieldView valueBinding="view.dateFromValue"}}
+ </div>
+ <div class="display-inline-block date-filter-item">
+ {{t to}}:
+ {{view App.DatepickerFieldView valueBinding="view.dateToValue"}}
+ </div>
+ </div>
+ <div class="pull-right">
+ <a {{action resetKeywordsDateFilter target="view"}} href="#" class="reset-link">{{t common.reset}}</a>
+ </div>
+ </div>
+ <div class="clearfix"></div>
+ <div class="toolbar-row levels-filter-block">
+ <div class="display-inline-block">{{t common.levels}}:</div>
+ {{#each level in view.levelsContext}}
+ <div class="display-inline-block level-checkbox">
+ <label>
+ {{view Em.Checkbox checkedBinding="level.checked"}}
+ {{level.displayName}} ({{level.counter}})
+ </label>
+ </div>
+ {{/each}}
+ <div class="pull-right">
+ <a {{action resetLevelsFilter target="view"}} href="#" class="reset-link">{{t common.reset}}</a>
+ </div>
+ </div>
+ <div class="toolbar-row include-exclude-filter-block">
+ <div class="display-inline-block">
+ {{t common.filters}}:
+ <div class="keywords-list">
+ {{#each keyword in view.selectedKeywords}}
+ <div {{bindAttr class=":keyword-item :btn keyword.isIncluded:btn-success:btn-danger"}}>
+ {{#if keyword.isIncluded}}
+ {{t common.include.short}}:
+ {{else}}
+ {{t common.exclude.short}}:
+ {{/if}}
+ <span> '{{keyword.value}}'</span>
+ <a {{action removeKeyword keyword target="view"}} class="close" href="#">x</a>
+ </div>
+ {{/each}}
+ </div>
+ </div>
+ <div class="pull-right">
+ <a {{action resetKeywordsFilter target="view"}} href="#" class="reset-link">{{t common.reset}}</a>
+ </div>
+ </div>
+ </div>
+ <div class="context-menu pull-left">
+ <a href="#" {{action moveTableTop target="view"}} class="move-to-top icon-caret-up"></a>
+ <a href="#" {{action moveTableBottom target="view"}} class="move-to-bottom icon-caret-down"></a>
+ <a href="#" {{action navigateToLogUI target="view"}} class="icon-external-link"></a>
+ </div>
+ <div class="clearfix"></div>
+</div>
+<div class="log-file-search-content container">
+ {{#each logData in view.content}}
+ <div class="row-fluid log-data-item">
+ <div class="span1 log-data-date">
+ {{logData.date}}
+ <br />
+ <span class="log-data-level">
+ {{logData.level}}
+ </span>
+ </div>
+ <div class="span11 log-data-message">{{logData.message}}</div>
+ <div class="clearfix"></div>
+ </div>
+ {{/each}}
+</div>
+<div id="log-file-search-item-context-menu">
+ <ul class="dropdown-menu" role="menu">
+ <li>
+ <a {{action includeSelected target="view"}} href="#">{{t common.include}}</a>
+ </li>
+ <li>
+ <a {{action excludeSelected target="view"}} href="#">{{t common.exclude}}</a>
+ </li>
+ </ul>
+</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index afb12e1..4404a2a 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -20,6 +20,7 @@
// load all views here
require('views/application');
+require('views/common/log_file_search_view');
require('views/common/global/spinner');
require('views/common/ajax_default_error_popup_body');
require('views/common/chart');
@@ -37,6 +38,7 @@ require('views/common/modal_popups/invalid_KDC_popup');
require('views/common/modal_popups/dependent_configs_list_popup');
require('views/common/modal_popups/select_groups_popup');
require('views/common/modal_popups/logs_popup');
+require('views/common/modal_popups/log_file_search_popup');
require('views/common/editable_list');
require('views/common/host_progress_popup_body_view');
require('views/common/rolling_restart_view');
@@ -45,6 +47,7 @@ require('views/common/metric');
require('views/common/time_range');
require('views/common/time_range_list');
require('views/common/form/field');
+require('views/common/form/datepicker_view');
require('views/common/form/spinner_input_view');
require('views/common/form/manage_credentials_form_view');
require('views/common/quick_view_link_view');
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/views/common/form/datepicker_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/form/datepicker_view.js b/ambari-web/app/views/common/form/datepicker_view.js
new file mode 100644
index 0000000..b5bd104
--- /dev/null
+++ b/ambari-web/app/views/common/form/datepicker_view.js
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.DatepickerFieldView = Em.TextField.extend({
+ value: '',
+ dateFormat: 'dd/mm/yy',
+ maxDate: new Date(),
+
+ didInsertElement: function() {
+ this.$().datetimepicker({
+ format: this.get('dateFormat'),
+ endDate: this.get('maxDate')
+ });
+ this.$().on('changeDate', this.onChangeDate.bind(this));
+ },
+
+ onChangeDate: function(e) {
+ this.set('value', e.target.value);
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/views/common/log_file_search_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/log_file_search_view.js b/ambari-web/app/views/common/log_file_search_view.js
new file mode 100644
index 0000000..c242ec8
--- /dev/null
+++ b/ambari-web/app/views/common/log_file_search_view.js
@@ -0,0 +1,272 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var filters = require('views/common/filter_view');
+
+/**
+* @augments App.InfiniteScrollMixin
+* @type {Em.View}
+*/
+App.LogFileSearchView = Em.View.extend(App.InfiniteScrollMixin, {
+ classNames: ['log-file-search'],
+ templateName: require('templates/common/log_file_search'),
+ logLevels: ['fatal', 'critical', 'error', 'warning', 'info', 'debug'],
+
+ /**
+ * @typedef {Em.Object} FilterKeyword
+ * @property {Boolean} isIncluded determines include/exclude status of keyword
+ * @property {String} id unique identifier
+ * @property {String} value keyword value
+ */
+
+ /**
+ * Stores all selected keywords.
+ *
+ * @type {FilterKeyword[]}
+ */
+ selectedKeywords: [],
+
+ selectedKeywordsDidChange: function() {
+ this.fetchContent();
+ }.observes('selectedKeywords.length'),
+
+ levelsContext: function() {
+ var self = this;
+ var levels = this.get('logLevels');
+
+ return Em.A(levels.map(function(level) {
+ return Em.Object.create({name: level.toUpperCase(), counter: 0, displayName: level.capitalize(), checked: false});
+ }));
+ }.property(),
+
+ levelsContextDidChange: function(e) {
+ this.fetchContent();
+ }.observes('levelsContext.@each.checked'),
+
+ /** mock data **/
+ content: function() {
+ var data = [{
+ message: 'java.lang.NullPointerException',
+ date: '05.12.2016, 10:10:20',
+ level: 'INFO'
+ },
+ {
+ message: 'java.lang.NullPointerException',
+ date: '05.12.2016, 10:10:20',
+ level: 'ERROR'
+ }];
+
+ var initialSize = 20;
+ var ret = [];
+
+ for (var i = 0; i < 20; i++) {
+ ret.push(Em.Object.create(data[Math.ceil(Math.random()*2) - 1]));
+ }
+ return ret;
+ }.property(),
+
+ contentDidChange: function() {
+ this.refreshLevelCounters();
+ }.observes('content.length'),
+
+ dateFromValue: null,
+ dateToValue: null,
+
+ keywordsFilterView: filters.createTextView({
+ layout: Em.Handlebars.compile('{{yield}}')
+ }),
+
+ keywordsFilterValue: null,
+
+ didInsertElement: function() {
+ this._super();
+ this.infiniteScrollInit(this.$().find('.log-file-search-content'), {
+ callback: this.loadMore.bind(this)
+ });
+ this.$().find('.log-file-search-content').contextmenu({
+ target: '#log-file-search-item-context-menu'
+ });
+ this.refreshLevelCounters();
+ },
+
+ /** mock data **/
+ loadMore: function() {
+ var dfd = $.Deferred();
+ var self = this;
+ setTimeout(function() {
+ var data = self.get('content');
+ self.get('content').pushObjects(data.slice(0, 10));
+ dfd.resolve();
+ }, Math.ceil(Math.random()*4000));
+ return dfd.promise();
+ },
+
+ refreshLevelCounters: function() {
+ var self = this;
+ this.get('logLevels').forEach(function(level) {
+ var levelContext = self.get('levelsContext').findProperty('name', level.toUpperCase());
+ levelContext.set('counter', self.get('content').filterProperty('level', level.toUpperCase()).length);
+ });
+ },
+
+ /**
+ * Make request and get content with applied filters.
+ */
+ fetchContent: function(params) {
+ console.debug('Make Request with params:', this.serializeFilters());
+ },
+
+ submitKeywordsValue: function() {
+ this.fetchContent();
+ },
+
+ serializeFilters: function() {
+ var levels = this.serializeLevelFilters();
+ var keywords = this.serializeKeywordsFilter();
+ var date = this.serializeDateFilter();
+ var includedExcludedKeywords = this.serializeIncludedExcludedKeywordFilter();
+
+ return [levels, keywords, date, includedExcludedKeywords].compact().join('&');
+ },
+
+ serializeKeywordsFilter: function() {
+ return !!this.get('keywordsFilterValue') ? 'keywords=' + this.get('keywordsFilterValue'): null;
+ },
+
+ serializeDateFilter: function() {
+ var dateFrom = !!this.get('dateFromValue') ? 'dateFrom=' + this.get('dateFromValue') : null;
+ var dateTo = !!this.get('dateToValue') ? 'dateTo=' + this.get('dateFromValue') : null;
+ var ret = [dateTo, dateFrom].compact();
+ return ret.length ? ret.join('&') : null;
+ },
+
+ serializeLevelFilters: function() {
+ var selectedLevels = this.get('levelsContext').filterProperty('checked').mapProperty('name');
+ return selectedLevels.length ? 'levels=' + selectedLevels.join(',') : null;
+ },
+
+ serializeIncludedExcludedKeywordFilter: function() {
+ var self = this;
+ var getValues = function(included) {
+ return self.get('selectedKeywords').filterProperty('isIncluded', included).mapProperty('value');
+ };
+ var included = getValues(true).join(',');
+ var excluded = getValues(false).join(',');
+ var ret = [];
+ if (included.length) ret.push('include=' + included);
+ if (excluded.length) ret.push('exclude=' + excluded);
+ return ret.length ? ret.join('&') : null;
+ },
+
+ /** include/exclude keywords methods **/
+
+ keywordToId: function(keyword) {
+ return keyword.toLowerCase().split(' ').join('_');
+ },
+
+ /**
+ * Create keyword object
+ * @param {string} keyword keyword value
+ * @param {object} [opts]
+ * @return {Em.Object}
+ */
+ createSelectedKeyword: function(keyword, opts) {
+ var defaultOpts = {
+ isIncluded: false,
+ id: this.keywordToId(keyword),
+ value: keyword
+ };
+ return Em.Object.create($.extend({}, defaultOpts, opts));
+ },
+
+ /**
+ * Adds keyword if not added.
+ * @param {FilterKeyword} keywordObject
+ */
+ addKeywordToList: function(keywordObject) {
+ if (!this.get('selectedKeywords').someProperty('id', keywordObject.get('id'))) {
+ this.get('selectedKeywords').pushObject(keywordObject);
+ }
+ },
+
+ /**
+ * @param {FilterKeyword} keyword
+ */
+ includeSelectedKeyword: function(keyword) {
+ this.addKeywordToList(this.createSelectedKeyword(keyword, { isIncluded: true }));
+ },
+
+ /**
+ * @param {FilterKeyword} keyword
+ */
+ excludeSelectedKeyword: function(keyword) {
+ this.addKeywordToList(this.createSelectedKeyword(keyword, { isIncluded: false }));
+ },
+
+ /** view actions **/
+
+ /** toolbar context menu actions **/
+ moveTableTop: function(e) {
+ var $el = $('.log-file-search-content');
+ $el.scrollTop(0);
+ $el = null;
+ },
+
+ moveTableBottom: function(e) {
+ var $el = $('.log-file-search-content');
+ $el.scrollTop($el.get(0).scrollHeight);
+ $el = null;
+ },
+
+ navigateToLogUI: function(e) {
+ console.error('navigate to Log UI');
+ },
+
+ removeKeyword: function(e) {
+ this.get('selectedKeywords').removeObject(e.context);
+ },
+
+ /** toolbar reset filter actions **/
+ resetKeywordsDateFilter: function(e) {
+ this.setProperties({
+ keywordsFilterValue: '',
+ dateFromValue: '',
+ dateToValue: ''
+ });
+ },
+
+ resetLevelsFilter: function(e) {
+ this.get('levelsContext').invoke('set', 'checked', false);
+ },
+
+ resetKeywordsFilter: function(e) {
+ this.get('selectedKeywords').clear();
+ },
+
+ /** log search item context menu actions **/
+ includeSelected: function() {
+ var selection = window.getSelection().toString();
+ if (!!selection) this.includeSelectedKeyword(selection);
+ },
+
+ excludeSelected: function() {
+ var selection = window.getSelection().toString();
+ if (!!selection) this.excludeSelectedKeyword(selection);
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/app/views/common/modal_popups/log_file_search_popup.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/modal_popups/log_file_search_popup.js b/ambari-web/app/views/common/modal_popups/log_file_search_popup.js
new file mode 100644
index 0000000..4730a19
--- /dev/null
+++ b/ambari-web/app/views/common/modal_popups/log_file_search_popup.js
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.LogFileSearchPopup = function(header) {
+ return App.ModalPopup.show({
+ classNames: ['modal-full-width', 'sixty-percent-width-modal', 'log-file-search-popup'],
+ bodyClass: App.LogFileSearchView.extend({})
+ });
+};
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/test/views/common/log_file_search_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/log_file_search_view_test.js b/ambari-web/test/views/common/log_file_search_view_test.js
new file mode 100644
index 0000000..ca208b3
--- /dev/null
+++ b/ambari-web/test/views/common/log_file_search_view_test.js
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.LogFileSearchView', function() {
+ describe('#serializeFilters', function() {
+ var makeLevelItem = function(level, isChecked) {
+ return Em.Object.create({
+ name: level.toUpperCase(),
+ checked: !!isChecked
+ });
+ };
+ var makeSelectedKeyword = function(keyword, isIncluded) {
+ return Em.Object.create({
+ value: keyword,
+ isIncluded: !!isIncluded
+ });
+ };
+ var cases = [
+ {
+ viewContent: {
+ keywordsFilterValue: 'some_keyword'
+ },
+ e: 'keywords=some_keyword'
+ },
+ {
+ viewContent: {
+ keywordsFilterValue: 'some_keyword',
+ levelsContext: [
+ makeLevelItem('debug', true),
+ makeLevelItem('error', false),
+ makeLevelItem('info', true)
+ ]
+ },
+ e: 'levels=DEBUG,INFO&keywords=some_keyword'
+ },
+ {
+ viewContent: {
+ keywordsFilterValue: 'some_keyword',
+ dateFromValue: '12/12/2015',
+ dateToValue: '14/12/2015',
+ levelsContext: [
+ makeLevelItem('debug', true),
+ makeLevelItem('error', true),
+ makeLevelItem('info', true)
+ ]
+ },
+ e: 'levels=DEBUG,ERROR,INFO&keywords=some_keyword&dateTo=12/12/2015&dateFrom=12/12/2015'
+ },
+ {
+ viewContent: {
+ keywordsFilterValue: 'some_keyword',
+ dateFromValue: '12/12/2015',
+ levelsContext: [
+ makeLevelItem('debug', true),
+ makeLevelItem('error', true),
+ makeLevelItem('info', true)
+ ]
+ },
+ e: 'levels=DEBUG,ERROR,INFO&keywords=some_keyword&dateFrom=12/12/2015'
+ },
+ {
+ viewContent: {
+ keywordsFilterValue: 'some_keyword',
+ dateFromValue: '12/12/2015',
+ levelsContext: [
+ makeLevelItem('debug', true),
+ makeLevelItem('error', true),
+ makeLevelItem('info', true)
+ ],
+ selectedKeywords: [
+ makeSelectedKeyword("keyword1", true),
+ makeSelectedKeyword("keyword2", true),
+ makeSelectedKeyword("keyword3", false)
+ ]
+ },
+ e: 'levels=DEBUG,ERROR,INFO&keywords=some_keyword&dateFrom=12/12/2015&include=keyword1,keyword2&exclude=keyword3'
+ }
+ ].forEach(function(test) {
+ it('validate result: ' + test.e, function() {
+ var view = App.LogFileSearchView.extend(test.viewContent).create();
+ expect(view.serializeFilters()).to.be.eql(test.e);
+ view.destroy();
+ })
+ });
+ });
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/3a2add5d/ambari-web/vendor/scripts/bootstrap-contextmenu.js
----------------------------------------------------------------------
diff --git a/ambari-web/vendor/scripts/bootstrap-contextmenu.js b/ambari-web/vendor/scripts/bootstrap-contextmenu.js
new file mode 100644
index 0000000..62a60cd
--- /dev/null
+++ b/ambari-web/vendor/scripts/bootstrap-contextmenu.js
@@ -0,0 +1,205 @@
+/*!
+ * Bootstrap Context Menu
+ * Author: @sydcanem
+ * https://github.com/sydcanem/bootstrap-contextmenu
+ *
+ * Inspired by Bootstrap's dropdown plugin.
+ * Bootstrap (http://getbootstrap.com).
+ *
+ * Licensed under MIT
+ * ========================================================= */
+
+;(function($) {
+
+ 'use strict';
+
+ /* CONTEXTMENU CLASS DEFINITION
+ * ============================ */
+ var toggle = '[data-toggle="context"]';
+
+ var ContextMenu = function (element, options) {
+ this.$element = $(element);
+
+ this.before = options.before || this.before;
+ this.onItem = options.onItem || this.onItem;
+ this.scopes = options.scopes || null;
+
+ if (options.target) {
+ this.$element.data('target', options.target);
+ }
+
+ this.listen();
+ };
+
+ ContextMenu.prototype = {
+
+ constructor: ContextMenu
+ ,show: function(e) {
+
+ var $menu
+ , evt
+ , tp
+ , items
+ , relatedTarget = { relatedTarget: this, target: e.currentTarget };
+
+ if (this.isDisabled()) return;
+
+ this.closemenu();
+
+ if (this.before.call(this,e,$(e.currentTarget)) === false) return;
+
+ $menu = this.getMenu();
+ $menu.trigger(evt = $.Event('show.bs.context', relatedTarget));
+
+ tp = this.getPosition(e, $menu);
+ items = 'li:not(.divider)';
+ $menu.attr('style', '')
+ .css(tp)
+ .addClass('open')
+ .on('click.context.data-api', items, $.proxy(this.onItem, this, $(e.currentTarget)))
+ .trigger('shown.bs.context', relatedTarget);
+
+ // Delegating the `closemenu` only on the currently opened menu.
+ // This prevents other opened menus from closing.
+ $('html')
+ .on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this));
+
+ return false;
+ }
+
+ ,closemenu: function(e) {
+ var $menu
+ , evt
+ , items
+ , relatedTarget;
+
+ $menu = this.getMenu();
+
+ if(!$menu.hasClass('open')) return;
+
+ relatedTarget = { relatedTarget: this };
+ $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget));
+
+ items = 'li:not(.divider)';
+ $menu.removeClass('open')
+ .off('click.context.data-api', items)
+ .trigger('hidden.bs.context', relatedTarget);
+
+ $('html')
+ .off('click.context.data-api', $menu.selector);
+ // Don't propagate click event so other currently
+ // opened menus won't close.
+ e.stopPropagation();
+ }
+
+ ,keydown: function(e) {
+ if (e.which == 27) this.closemenu(e);
+ }
+
+ ,before: function(e) {
+ return true;
+ }
+
+ ,onItem: function(e) {
+ return true;
+ }
+
+ ,listen: function () {
+ this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this));
+ $('html').on('click.context.data-api', $.proxy(this.closemenu, this));
+ $('html').on('keydown.context.data-api', $.proxy(this.keydown, this));
+ }
+
+ ,destroy: function() {
+ this.$element.off('.context.data-api').removeData('context');
+ $('html').off('.context.data-api');
+ }
+
+ ,isDisabled: function() {
+ return this.$element.hasClass('disabled') ||
+ this.$element.attr('disabled');
+ }
+
+ ,getMenu: function () {
+ var selector = this.$element.data('target')
+ , $menu;
+
+ if (!selector) {
+ selector = this.$element.attr('href');
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7
+ }
+
+ $menu = $(selector);
+
+ return $menu && $menu.length ? $menu : this.$element.find(selector);
+ }
+
+ ,getPosition: function(e, $menu) {
+ var mouseX = e.clientX
+ , mouseY = e.clientY
+ , boundsX = $(window).width()
+ , boundsY = $(window).height()
+ , menuWidth = $menu.find('.dropdown-menu').outerWidth()
+ , menuHeight = $menu.find('.dropdown-menu').outerHeight()
+ , tp = {"position":"absolute","z-index":9999}
+ , Y, X, parentOffset;
+
+ if (mouseY + menuHeight > boundsY) {
+ Y = {"top": mouseY - menuHeight + $(window).scrollTop()};
+ } else {
+ Y = {"top": mouseY + $(window).scrollTop()};
+ }
+
+ if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) {
+ X = {"left": mouseX - menuWidth + $(window).scrollLeft()};
+ } else {
+ X = {"left": mouseX + $(window).scrollLeft()};
+ }
+
+ // If context-menu's parent is positioned using absolute or relative positioning,
+ // the calculated mouse position will be incorrect.
+ // Adjust the position of the menu by its offset parent position.
+ parentOffset = $menu.offsetParent().offset();
+ X.left = X.left - parentOffset.left;
+ Y.top = Y.top - parentOffset.top;
+
+ return $.extend(tp, Y, X);
+ }
+
+ };
+
+ /* CONTEXT MENU PLUGIN DEFINITION
+ * ========================== */
+
+ $.fn.contextmenu = function (option,e) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('context')
+ , options = (typeof option == 'object') && option;
+
+ if (!data) $this.data('context', (data = new ContextMenu($this, options)));
+ if (typeof option == 'string') data[option].call(data, e);
+ });
+ };
+
+ $.fn.contextmenu.Constructor = ContextMenu;
+
+ /* APPLY TO STANDARD CONTEXT MENU ELEMENTS
+ * =================================== */
+
+ $(document)
+ .on('contextmenu.context.data-api', function() {
+ $(toggle).each(function () {
+ var data = $(this).data('context');
+ if (!data) return;
+ data.closemenu();
+ });
+ })
+ .on('contextmenu.context.data-api', toggle, function(e) {
+ $(this).contextmenu('show', e);
+
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
+}(jQuery));
[2/2] ambari git commit: AMBARI-14927. Log Search: Add Host
Components Logs tab to Host Details page (alexantonenko)
Posted by al...@apache.org.
AMBARI-14927. Log Search: Add Host Components Logs tab to Host Details page
(alexantonenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/b567ca44
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/b567ca44
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/b567ca44
Branch: refs/heads/trunk
Commit: b567ca441c213c816019dc5a45884f69a286f54c
Parents: 3a2add5
Author: Alex Antonenko <hi...@gmail.com>
Authored: Fri Feb 5 18:30:24 2016 +0200
Committer: Alex Antonenko <hi...@gmail.com>
Committed: Fri Feb 5 18:30:24 2016 +0200
----------------------------------------------------------------------
.../app/controllers/global/update_controller.js | 4 +-
ambari-web/app/messages.js | 3 +
ambari-web/app/routes/main.js | 11 ++
ambari-web/app/styles/application.less | 3 +-
ambari-web/app/templates/main/host/logs.hbs | 62 ++++++++
ambari-web/app/views.js | 1 +
ambari-web/app/views/main/host/logs_view.js | 147 +++++++++++++++++++
ambari-web/app/views/main/host/menu.js | 11 +-
ambari-web/test/views/main/host/menu_test.js | 20 ++-
9 files changed, 257 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/controllers/global/update_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/global/update_controller.js b/ambari-web/app/controllers/global/update_controller.js
index 0a5d913..0168398 100644
--- a/ambari-web/app/controllers/global/update_controller.js
+++ b/ambari-web/app/controllers/global/update_controller.js
@@ -223,7 +223,7 @@ App.UpdateController = Em.Controller.extend({
}
else {
if (App.router.get('currentState.parentState.name') == 'hostDetails') {
- hostDetailsFilter = App.router.get('location.lastSetURL').match(/\/hosts\/(.*)\/(summary|configs|alerts|stackVersions)/)[1];
+ hostDetailsFilter = App.router.get('location.lastSetURL').match(/\/hosts\/(.*)\/(summary|configs|alerts|stackVersions|logs)/)[1];
App.updater.updateInterval('updateHost', App.get('componentsUpdateInterval'));
//if host details page opened then request info only of one displayed host
this.get('queryParams').set('Hosts', [
@@ -579,7 +579,7 @@ App.UpdateController = Em.Controller.extend({
complete: callback
});
},
-
+
updateUpgradeState: function (callback) {
var currentStateName = App.get('router.currentState.name'),
parentStateName = App.get('router.currentState.parentState.name'),
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 8d1f6f1..a8b5db6 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -312,6 +312,7 @@ Em.I18n.translations = {
'common.filters': 'Filters',
'common.keywords': 'Keywods',
'common.levels': 'Levels',
+ 'common.extension': 'Extension',
'models.alert_instance.tiggered.verbose': "Occurred on {0} <br> Checked on {1}",
'models.alert_definition.triggered.verbose': "Occurred on {0}",
@@ -2280,6 +2281,8 @@ Em.I18n.translations = {
'hosts.host.serviceNotAvailable': 'Service not available on this host',
+ 'hosts.host.menu.logs': 'Logs',
+
'hosts.host.menu.stackVersions': 'Versions',
'hosts.host.stackVersions.table.allVersions': 'All Versions',
'hosts.host.stackVersions.table.allNames': 'All Names',
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/routes/main.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/routes/main.js b/ambari-web/app/routes/main.js
index bd66945..cbaf34d 100644
--- a/ambari-web/app/routes/main.js
+++ b/ambari-web/app/routes/main.js
@@ -264,6 +264,17 @@ module.exports = Em.Route.extend(App.RouterRedirections, {
}
}),
+ logs: Em.Route.extend({
+ route: '/logs',
+ connectOutlets: function (router, context) {
+ if (App.get('supports.logSearch')) {
+ router.get('mainHostDetailsController').connectOutlet('mainHostLogs')
+ } else {
+ router.transitionTo('summary');
+ }
+ }
+ }),
+
hostNavigate: function (router, event) {
var parent = event.view._parentView;
parent.deactivateChildViews();
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/styles/application.less
----------------------------------------------------------------------
diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less
index ce10a98..a1969db 100644
--- a/ambari-web/app/styles/application.less
+++ b/ambari-web/app/styles/application.less
@@ -3700,7 +3700,8 @@ table.graphs {
}
}
}
- .host-tab-content {
+ .host-tab-content,
+ .logs-tab-content {
margin-top: 20px;
.table thead {
background: none repeat scroll 0 0 #F8F8F8;
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/templates/main/host/logs.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/host/logs.hbs b/ambari-web/app/templates/main/host/logs.hbs
new file mode 100644
index 0000000..000b4aa
--- /dev/null
+++ b/ambari-web/app/templates/main/host/logs.hbs
@@ -0,0 +1,62 @@
+{{!
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+}}
+
+<table class="table advanced-header-table table-bordered table-striped">
+ <thead>
+ {{#view view.sortView classNames="label-row" contentBinding="view.filteredContent"}}
+ {{view view.parentView.serviceNameSort classNames="first"}}
+ {{view view.parentView.componentNameSort}}
+ {{view view.parentView.fileExtensionsSort}}
+ {{/view}}
+ <tr class="filter-row">
+ <th class="first service-name-filter">{{view view.serviceNameFilterView}}</th>
+ <th class="component-name-filter">{{view view.componentNameFilterView}}</th>
+ <th class="file-extension-filter">{{view view.fileExtensionsFilter}}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {{#if view.pageContent}}
+ {{#each row in view.pageContent}}
+ <tr>
+ <td>{{row.serviceName}}</td>
+ <td>{{row.componentName}}</td>
+ <td>
+ <a {{action openLogFile row target="view"}} href="#">{{row.fileName}}</a>
+ </td>
+ </tr>
+ {{/each}}
+ {{/if}}
+ </tbody>
+</table>
+
+<div class="page-bar">
+ <div class="filtered-info span4">
+ {{#if view.showFilteredContent}}
+ <label>{{view.filteredContentInfo}} - <a {{action clearFilters target="view"}}
+ href="#">{{t tableView.filters.clearAllFilters}}</a></label>
+ {{/if}}
+ </div>
+ <div class="items-on-page">
+ <label>{{t common.show}}: {{view view.rowsPerPageSelectView selectionBinding="view.parentView.displayLength"}}</label>
+ </div>
+ <div class="info">{{view.paginationInfo}}</div>
+ <div class="paging_two_button">
+ {{view view.paginationLeft}}
+ {{view view.paginationRight}}
+ </div>
+</div>
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/views.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views.js b/ambari-web/app/views.js
index 4404a2a..2224393 100644
--- a/ambari-web/app/views.js
+++ b/ambari-web/app/views.js
@@ -122,6 +122,7 @@ require('views/main/host/details/host_component_views/datanode_view');
require('views/main/host/details/host_component_views/nodemanager_view');
require('views/main/host/details/host_component_views/regionserver_view');
require('views/main/host/details/host_component_views/tasktracker_view');
+require('views/main/host/logs_view');
require('views/main/host/menu');
require('views/main/host/summary');
require('views/main/host/configs');
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/views/main/host/logs_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/logs_view.js b/ambari-web/app/views/main/host/logs_view.js
new file mode 100644
index 0000000..dfe00e4
--- /dev/null
+++ b/ambari-web/app/views/main/host/logs_view.js
@@ -0,0 +1,147 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+var App = require('app');
+var filters = require('views/common/filter_view');
+var sort = require('views/common/sort_view');
+
+App.MainHostLogsView = App.TableView.extend({
+ templateName: require('templates/main/host/logs'),
+
+ classNames: ['logs-tab-content'],
+
+ /**
+ * @type {Ember.Object}
+ */
+ host: Em.computed.alias('App.router.mainHostDetailsController.content'),
+
+ content: function() {
+ return [
+ Em.Object.create({
+ serviceName: 'HDFS',
+ componentName: 'DATANODE',
+ fileExtension: '.log',
+ fileName: 'HDFS_DATANODE.log'
+ })
+ ];
+ }.property(),
+ /**
+ * @type {Ember.View}
+ */
+ sortView: sort.wrapperView,
+
+ serviceNameSort: sort.fieldView.extend({
+ column: 1,
+ name: 'serviceName',
+ displayName: Em.I18n.t('common.service')
+ }),
+
+ componentNameSort: sort.fieldView.extend({
+ column: 2,
+ name: 'componentName',
+ displayName: Em.I18n.t('common.component')
+ }),
+
+ fileExtensionsSort: sort.fieldView.extend({
+ column: 3,
+ name: 'extension',
+ displayName: Em.I18n.t('common.extension')
+ }),
+
+ serviceNameFilterView: filters.createSelectView({
+ column: 1,
+ fieldType: 'filter-input-width',
+ content: function() {
+ return [{
+ value: '',
+ label: Em.I18n.t('common.all')
+ }].concat(App.Service.find().mapProperty('serviceName').uniq().map(function(item) {
+ return {
+ value: item,
+ label: item
+ }
+ }));
+ }.property('App.router.clusterController.isLoaded'),
+ onChangeValue: function() {
+ this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+ }
+ }),
+
+ componentNameFilterView: filters.createSelectView({
+ column: 2,
+ fieldType: 'filter-input-width',
+ content: function() {
+ var hostName = this.get('parentView').get('host.hostName'),
+ hostComponents = App.HostComponent.find().filterProperty('hostName', hostName),
+ componentsMap = hostComponents.map(function(item) {
+ return {
+ value: item.get('componentName'),
+ label: item.get('displayName')
+ }
+ });
+ return [{
+ value: '',
+ label: Em.I18n.t('common.all')
+ }].concat(componentsMap);
+ }.property('App.router.clusterController.isLoaded'),
+ onChangeValue: function() {
+ this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+ }
+ }),
+
+ fileExtensionsFilter: filters.createSelectView({
+ column: 3,
+ fieldType: 'filter-input-width',
+ content: function() {
+ return [{
+ value: '',
+ label: Em.I18n.t('common.all')
+ }].concat([
+ '.out',
+ '.log'
+ ].map(function(item) {
+ return {
+ value: item,
+ label: item
+ }
+ }))
+ }.property('App.router.clusterController.isLoaded'),
+ onChangeValue: function() {
+ this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'select');
+ }
+ }),
+
+ /**
+ * @type {string[]}
+ */
+ colPropAssoc: function () {
+ var ret = [];
+ ret[1] = 'serviceName';
+ ret[2] = 'componentName';
+ ret[3] = 'fileExtension';
+ return ret;
+ }.property(),
+
+ openLogFile: function(e) {
+ var fileData = e.context;
+ if (e.context) {
+ App.LogFileSearchPopup(fileData.fileName);
+ }
+ }
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/app/views/main/host/menu.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/host/menu.js b/ambari-web/app/views/main/host/menu.js
index 15608d1..a53c21a 100644
--- a/ambari-web/app/views/main/host/menu.js
+++ b/ambari-web/app/views/main/host/menu.js
@@ -53,6 +53,15 @@ App.MainHostMenuView = Em.CollectionView.extend({
return !App.get('supports.stackUpgrade') || !App.get('stackVersionsAvailable')
}.property('App.supports.stackUpgrade'),
id: 'host-details-summary-version'
+ }),
+ Em.Object.create({
+ name: 'logs',
+ label: Em.I18n.t('hosts.host.menu.logs'),
+ routing: 'logs',
+ hidden: function () {
+ return !App.get('supports.logSearch');
+ }.property('App.supports.logSearch'),
+ id: 'host-details-summary-logs'
})
];
}.property('App.stackVersionsAvailable'),
@@ -104,4 +113,4 @@ App.MainHostMenuView = Em.CollectionView.extend({
'{{view.content.badgeText}}' +
'</span> {{/if}}</a>{{/unless}}')
})
-});
\ No newline at end of file
+});
http://git-wip-us.apache.org/repos/asf/ambari/blob/b567ca44/ambari-web/test/views/main/host/menu_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/main/host/menu_test.js b/ambari-web/test/views/main/host/menu_test.js
index 4540486..59facf1 100644
--- a/ambari-web/test/views/main/host/menu_test.js
+++ b/ambari-web/test/views/main/host/menu_test.js
@@ -69,6 +69,24 @@ describe('App.MainHostMenuView', function () {
});
});
+ Em.A([
+ {
+ logSearch: false,
+ m: '`logs` tab is invisible',
+ e: true
+ },
+ {
+ logSearch: true,
+ m: '`logs` tab is visible',
+ e: false
+ }
+ ]).forEach(function(test) {
+ it(test.m, function() {
+ this.mock.withArgs('supports.logSearch').returns(test.logSearch);
+ view.propertyDidChange('content');
+ expect(view.get('content').findProperty('name', 'logs').get('hidden')).to.equal(test.e);
+ });
+ });
});
-});
\ No newline at end of file
+});