Repository: ambari Updated Branches: refs/heads/trunk bbde993be -> d00330230
AMBARI-5504. Views: Ambari Web Layout Update. (xiwang via yusaku) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/d0033023 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/d0033023 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/d0033023 Branch: refs/heads/trunk Commit: d00330230679c89fab3f345d677394a1ce2ed27d Parents: bbde993 Author: Yusaku Sako <[email protected]> Authored: Fri Apr 18 14:48:28 2014 -0700 Committer: Yusaku Sako <[email protected]> Committed: Fri Apr 18 14:48:28 2014 -0700 ---------------------------------------------------------------------- ambari-web/app/controllers/main/dashboard.js | 55 ++ .../app/templates/main/dashboard/widgets.hbs | 58 ++ ambari-web/app/views/main/dashboard/widgets.js | 594 +++++++++++++++++++ 3 files changed, 707 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/d0033023/ambari-web/app/controllers/main/dashboard.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/dashboard.js b/ambari-web/app/controllers/main/dashboard.js new file mode 100644 index 0000000..e3eb0b2 --- /dev/null +++ b/ambari-web/app/controllers/main/dashboard.js @@ -0,0 +1,55 @@ +/** + * 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.MainDashboardController = Em.Controller.extend({ + name: 'mainDashboardController', + categorySelected: 'widgets', + + scRequest: function(request) { + return App.router.get('mainServiceController').get(request); + }, + + isAllServicesInstalled: function() { + return this.scRequest('isAllServicesInstalled'); + }.property('App.router.mainServiceController.content.content.@each', + 'App.router.mainServiceController.content.content.length'), + + isStartAllDisabled: function() { + return this.scRequest('isStartAllDisabled'); + }.property('App.router.mainServiceController.isStartStopAllClicked', + '[email protected]'), + + isStopAllDisabled: function() { + return this.scRequest('isStopAllDisabled'); + }.property('App.router.mainServiceController.isStartStopAllClicked', + '[email protected]'), + + gotoAddService: function() { + App.router.get('mainServiceController').gotoAddService(); + }, + + startAllService: function(event){ + App.router.get('mainServiceController').startAllService(event); + }, + + stopAllService: function(event){ + App.router.get('mainServiceController').stopAllService(event); + } +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/d0033023/ambari-web/app/templates/main/dashboard/widgets.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/dashboard/widgets.hbs b/ambari-web/app/templates/main/dashboard/widgets.hbs new file mode 100644 index 0000000..f3079f3 --- /dev/null +++ b/ambari-web/app/templates/main/dashboard/widgets.hbs @@ -0,0 +1,58 @@ +{{! +* 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. +}} +{{#if view.isDataLoaded}} + <div class="box"> + <div class="box-header"> + <div class="row-fluid"> + <h4 class="span10"></h4> + <a class="add-widget-button span1">{{view view.plusButtonFilterView}}</a> + <div class="btn-group"> + <button class="btn dropdown-toggle span1 more-options-button" data-toggle="dropdown"> + <i class="icon-cog"></i> + <span class= "caret"></span> + </button> + <ul class="dropdown-menu right-align-dropdown"> + <li> + <a href="#" {{action "resetAllWidgets" target="view"}}> + <i class="icon-refresh"></i> + {{t dashboard.button.reset}} + </a> + </li> + <li> + <a target="_blank" {{bindAttr href="view.gangliaUrl"}}> + <i class="icon-share"></i> + {{t dashboard.button.gangliaLink}} + </a> + </li> + </ul> + </div> + </div> + </div> + <div id="dashboard-widgets" class="widgets-container"> + <div class="thumbnails row-fluid" id="sortable"> + {{#if view.visibleWidgets.length}} + {{#each widgetClass in view.visibleWidgets}} + <div {{bindAttr class="widgetClass.class"}}> + {{view widgetClass }} + </div> + {{/each}} + {{/if}} + </div> + </div> + </div> +{{/if}} http://git-wip-us.apache.org/repos/asf/ambari/blob/d0033023/ambari-web/app/views/main/dashboard/widgets.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/dashboard/widgets.js b/ambari-web/app/views/main/dashboard/widgets.js new file mode 100644 index 0000000..8900541 --- /dev/null +++ b/ambari-web/app/views/main/dashboard/widgets.js @@ -0,0 +1,594 @@ +/** + * 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'); + +App.MainDashboardWidgetsView = Em.View.extend(App.UserPref, App.LocalStorage, { + + name: 'mainDashboardWidgetsView', + + templateName:require('templates/main/dashboard/widgets'), + + didInsertElement:function () { + this.setWidgetsDataModel(); + this.setInitPrefObject(); + this.setOnLoadVisibleWidgets(); + this.set('isDataLoaded',true); + Em.run.next(this, 'makeSortable'); + }, + + /** + * List of services + * @type {Ember.Enumerable} + */ + content:[], + + /** + * @type {bool} + */ + isDataLoaded: false, + + /** + * Make widgets' list sortable on New Dashboard style + */ + makeSortable: function () { + var self = this; + $( "#sortable" ).sortable({ + items: "> div", + //placeholder: "sortable-placeholder", + cursor: "move", + update: function (event, ui) { + if (!App.testMode) { + // update persist then translate to real + var widgetsArray = $('div[viewid]'); // get all in DOM + self.getUserPref(self.get('persistKey')); + var oldValue = self.get('currentPrefObject') || self.getDBProperty(self.get('persistKey')); + var newValue = Em.Object.create({ + dashboardVersion: oldValue.dashboardVersion, + visible: [], + hidden: oldValue.hidden, + threshold: oldValue.threshold + }); + var size = oldValue.visible.length; + for(var j = 0; j <= size -1; j++){ + var viewID = widgetsArray.get(j).getAttribute('viewid'); + var id = viewID.split("-").get(1); + newValue.visible.push(id); + } + self.postUserPref(self.get('persistKey'), newValue); + self.setDBProperty(self.get('persistKey'), newValue); + //self.translateToReal(newValue); + } + } + }).disableSelection(); + }, + + /** + * Set Service model values + */ + setWidgetsDataModel: function () { + var services = App.Service.find(); + var self = this; + services.forEach(function (item) { + switch (item.get('serviceName')) { + case "HDFS": + self.set('hdfs_model', App.HDFSService.find(item.get('id')) || item); + break; + case "YARN": + self.set('yarn_model', App.YARNService.find(item.get('id')) || item); + break; + case "MAPREDUCE": + self.set('mapreduce_model', App.MapReduceService.find(item.get('id')) || item); + break; + case "HBASE": + self.set('hbase_model', App.HBaseService.find(item.get('id')) || item); + break; + case "STORM": + self.set('storm_model', item); + break; + case "FLUME": + self.set('flume_model', item); + break; + } + }, this); + }, + + /** + * Load widget statuses to <code>initPrefObject</code> + */ + setInitPrefObject: function() { + //in case of some service not installed + var visibleFull = [ + '2', '4', '8', '10', + '17', '11', '12', '13', '14', + '18', '1', '6', '5', '9', + '3', '7', '15', '16', '20', + '19', '21', '23', + '24', '25', '26', '27',// all yarn + '28', // storm + '29' // flume + ]; // all in order + var hiddenFull = [['22','Region In Transition']]; + if (this.get('hdfs_model') == null) { + var hdfs= ['1', '2', '3', '4', '5', '15', '17']; + hdfs.forEach ( function (item) { + visibleFull = visibleFull.without(item); + }, this); + } + if (this.get('mapreduce_model') == null) { + var map = ['6', '7', '8', '9', '10', '16', '18']; + map.forEach ( function (item) { + visibleFull = visibleFull.without(item); + }, this); + } + if (this.get('hbase_model') == null) { + var hbase = ['19', '20', '21', '23']; + hbase.forEach ( function (item) { + visibleFull = visibleFull.without(item); + }, this); + hiddenFull = []; + } + if (this.get('yarn_model') == null) { + var yarn = ['24', '25', '26', '27']; + yarn.forEach ( function (item) { + visibleFull = visibleFull.without(item); + }, this); + } + if (this.get('storm_model') == null) { + var storm = ['28']; + storm.forEach(function(item) { + visibleFull = visibleFull.without(item); + }, this); + } + if (this.get('flume_model') == null) { + var flume = ['29']; + flume.forEach(function(item) { + visibleFull = visibleFull.without(item); + }, this); + } + var obj = this.get('initPrefObject'); + obj.set('visible', visibleFull); + obj.set('hidden', hiddenFull); + }, + + hdfs_model: null, + + mapreduce_model: null, + + mapreduce2_model: null, + + yarn_model: null, + + hbase_model: null, + + storm_model: null, + + flume_model: null, + + /** + * List of visible widgets + * @type {Ember.Enumerable} + */ + visibleWidgets: [], + + /** + * List of hidden widgets + * @type {Ember.Enumerable} + */ + hiddenWidgets: [], // widget child view will push object in this array if deleted + + /** + * Submenu view for New Dashboard style + * @type {Ember.View} + */ + plusButtonFilterView: filters.createComponentView({ + /** + * Base methods was implemented in <code>filters.componentFieldView</code> + */ + hiddenWidgetsBinding: 'parentView.hiddenWidgets', + visibleWidgetsBinding: 'parentView.visibleWidgets', + layout: null, + + filterView: filters.componentFieldView.extend({ + templateName:require('templates/main/dashboard/plus_button_filter'), + hiddenWidgetsBinding: 'parentView.hiddenWidgets', + visibleWidgetsBinding: 'parentView.visibleWidgets', + valueBinding: '', + applyFilter:function() { + this._super(); + var parent = this.get('parentView').get('parentView'); + var hiddenWidgets = this.get('hiddenWidgets'); + var checkedWidgets = hiddenWidgets.filterProperty('checked', true); + + if (App.testMode) { + var visibleWidgets = this.get('visibleWidgets'); + checkedWidgets.forEach(function(item){ + var newObj = parent.widgetsMapper(item.id); + visibleWidgets.pushObject(newObj); + hiddenWidgets.removeObject(item); + }, this); + } else { + //save in persist + parent.getUserPref(parent.get('persistKey')); + var oldValue = parent.get('currentPrefObject') || parent.getDbProperty(parent.get('persistKey')); + var newValue = Em.Object.create({ + dashboardVersion: oldValue.dashboardVersion, + visible: oldValue.visible, + hidden: [], + threshold: oldValue.threshold + }); + checkedWidgets.forEach(function(item){ + newValue.visible.push(item.id); + hiddenWidgets.removeObject(item); + }, this); + hiddenWidgets.forEach(function(item){ + newValue.hidden.push([item.id, item.displayName]); + }, this); + parent.postUserPref(parent.get('persistKey'), newValue); + parent.setDBProperty(parent.get('persistKey'), newValue); + parent.translateToReal(newValue); + } + } + }) + }), + + /** + * Translate from Json value got from persist to real widgets view + */ + translateToReal: function (value) { + var version = value.dashboardVersion; + var visible = value.visible; + var hidden = value.hidden; + var threshold = value.threshold; + + if (version == 'classic') { + this.set('isClassicDashboard', true); + } else if (version == 'new') { + this.set('isClassicDashboard', false); + var visibleWidgets = []; + var hiddenWidgets = []; + // re-construct visibleWidgets and hiddenWidgets + for (var j = 0; j <= visible.length -1; j++) { + var id = visible[j]; + var widgetClass = this.widgetsMapper(id); + //override with new threshold + if (threshold[id].length > 0) { + widgetClass.reopen({ + thresh1: threshold[id][0], + thresh2: threshold[id][1] + }); + } + visibleWidgets.pushObject(widgetClass); + } + for (var j = 0; j <= hidden.length -1; j++) { + var id = hidden[j][0]; + var title = hidden[j][1]; + hiddenWidgets.pushObject(Em.Object.create({displayName:title , id: id, checked: false})); + } + this.set('visibleWidgets', visibleWidgets); + this.set('hiddenWidgets', hiddenWidgets); + } + }, + + /** + * Set visibility-status for widgets + */ + setOnLoadVisibleWidgets: function () { + if (App.testMode) { + this.translateToReal(this.get('initPrefObject')); + } else { + // called when first load/refresh/jump back page + this.getUserPref(this.get('persistKey')); + var currentPrefObject = this.get('currentPrefObject') || this.getDBProperty(this.get('persistKey')); + if (currentPrefObject) { // fit for no dashboard version + if (!currentPrefObject.dashboardVersion) { + currentPrefObject.dashboardVersion = 'new'; + this.postUserPref(this.get('persistKey'), currentPrefObject); + this.setDBProperty(this.get('persistKey'), currentPrefObject); + } + this.set('currentPrefObject', this.checkServicesChange(currentPrefObject)); + this.translateToReal(this.get('currentPrefObject')); + } + else { + // post persist then translate init object + this.postUserPref(this.get('persistKey'), this.get('initPrefObject')); + this.setDBProperty(this.get('persistKey'), this.get('initPrefObject')); + this.translateToReal(this.get('initPrefObject')); + } + } + }, + + /** + * Remove widget from visible and hidden lists + * @param {Object} value + * @param {Object} widget + * @returns {*} + */ + removeWidget: function (value, widget) { + value.visible = value.visible.without(widget); + for (var j = 0; j <= value.hidden.length -1; j++) { + if (value.hidden[j][0] == widget) { + value.hidden.splice(j, 1); + } + } + return value; + }, + + /** + * Check if widget is in visible or hidden list + * @param {Object} value + * @param {Object} widget + * @returns {bool} + */ + containsWidget: function (value, widget) { + var flag = value.visible.contains (widget); + for (var j = 0; j <= value.hidden.length -1; j++) { + if ( !flag && value.hidden[j][0] == widget) { + flag = true; + break; + } + } + return flag; + }, + + /** + * check if stack has upgraded from HDP 1.0 to 2.0 OR add/delete services. + * Update the value on server if true. + * @param {Object} currentPrefObject + * @return {Object} + */ + checkServicesChange: function (currentPrefObject) { + var toDelete = $.extend(true, {}, currentPrefObject); + var toAdd = []; + var self = this; + + // check each service, find out the newly added service and already deleted service + if (this.get('hdfs_model') != null) { + var hdfsAndMetrics= ['1', '2', '3', '4', '5', '15', '17', '11', '12', '13', '14']; + hdfsAndMetrics.forEach ( function (item) { + toDelete = self.removeWidget(toDelete, item); + }, this); + } + if (this.get('mapreduce_model') != null) { + var map = ['6', '7', '8', '9', '10', '16', '18']; + var flag = self.containsWidget(toDelete, map[0]); + if (flag) { + map.forEach ( function (item) { + toDelete = self.removeWidget(toDelete, item); + }, this); + } else { + toAdd = toAdd.concat(map); + } + } + if (this.get('hbase_model') != null) { + var hbase = ['19', '20', '21', '22', '23']; + var flag = self.containsWidget(toDelete, hbase[0]); + if (flag) { + hbase.forEach ( function (item) { + toDelete = self.removeWidget(toDelete, item); + }, this); + } else { + toAdd = toAdd.concat(hbase); + } + } + if (this.get('yarn_model') != null) { + var yarn = ['24', '25', '26', '27']; + var flag = self.containsWidget(toDelete, yarn[0]); + if (flag) { + yarn.forEach ( function (item) { + toDelete = self.removeWidget(toDelete, item); + }, this); + } else { + toAdd = toAdd.concat(yarn); + } + } + if (this.get('storm_model') != null) { + var storm = ['28']; + var flag = self.containsWidget(toDelete, storm[0]); + if (flag) { + storm.forEach ( function (item) { + toDelete = self.removeWidget(toDelete, item); + }, this); + } else { + toAdd = toAdd.concat(storm); + } + } + if (this.get('flume_model') != null) { + var flume = ['29']; + var flag = self.containsWidget(toDelete, flume[0]); + if (flag) { + flume.forEach ( function (item) { + toDelete = self.removeWidget(toDelete, item); + }, this); + } else { + toAdd = toAdd.concat(flume); + } + } + var value = currentPrefObject; + if (toDelete.visible.length || toDelete.hidden.length) { + toDelete.visible.forEach ( function (item) { + value = self.removeWidget(value, item); + }, this); + toDelete.hidden.forEach ( function (item) { + value = self.removeWidget(value, item[0]); + }, this); + } + if (toAdd.length) { + value.visible = value.visible.concat(toAdd); + var allThreshold = this.get('initPrefObject').threshold; + // add new threshold OR override with default value + toAdd.forEach ( function (item) { + value.threshold[item] = allThreshold[item]; + }, this); + } + return value; + }, + + /** + * Get view for widget by widget's id + * @param {string} id + * @returns {Ember.View} + */ + widgetsMapper: function (id) { + return Em.get({ + '1': App.NameNodeHeapPieChartView, + '2': App.NameNodeCapacityPieChartView, + '3': App.NameNodeCpuPieChartView, + '4': App.DataNodeUpView, + '5': App.NameNodeRpcView, + '6': App.JobTrackerHeapPieChartView, + '7': App.JobTrackerCpuPieChartView, + '8': App.TaskTrackerUpView, + '9': App.JobTrackerRpcView, + '10': App.MapReduceSlotsView, + '11': App.ChartClusterMetricsMemoryWidgetView, + '12': App.ChartClusterMetricsNetworkWidgetView, + '13': App.ChartClusterMetricsCPUWidgetView, + '14': App.ChartClusterMetricsLoadWidgetView, + '15': App.NameNodeUptimeView, + '16': App.JobTrackerUptimeView, + '17': App.HDFSLinksView, + '18': App.MapReduceLinksView, + '19': App.HBaseLinksView, + '20': App.HBaseMasterHeapPieChartView, + '21': App.HBaseAverageLoadView, + '22': App.HBaseRegionsInTransitionView, + '23': App.HBaseMasterUptimeView, + '24': App.ResourceManagerHeapPieChartView, + '25': App.ResourceManagerUptimeView, + '26': App.NodeManagersLiveView, + '27': App.YARNMemoryPieChartView, + '28': App.SuperVisorUpView, + '29': App.FlumeAgentUpView + }, id); + }, + + /** + * @type {Object|null} + */ + currentPrefObject: null, + + /** + * @type {Ember.Object} + */ + initPrefObject: Em.Object.create({ + dashboardVersion: 'new', + visible: [], + hidden: [], + threshold: {1: [80, 90], 2: [85, 95], 3: [90, 95], 4: [80, 90], 5: [1000, 3000], 6: [70, 90], 7: [90, 95], 8: [50, 75], 9: [30000, 120000], + 10: [], 11: [], 12: [], 13: [], 14: [], 15: [], 16: [], 17: [], 18: [], 19: [], 20: [70, 90], 21: [10, 19.2], 22: [3, 10], 23: [], + 24: [70, 90], 25: [], 26: [50, 75], 27: [50, 75], 28: [85, 95], 29: [85, 95]} // id:[thresh1, thresh2] + }), + + /** + * Key-name to store data in Local Storage and Persist + * @type {string} + */ + persistKey: function () { + return 'user-pref-' + App.router.get('loginName') + '-dashboard'; + }.property(), + + makeRequestAsync: false, + + getUserPrefSuccessCallback: function (response, request, data) { + if (response) { + console.log('Got persist value from server with key ' + data.key + '. Value is: ' + response); + this.set('currentPrefObject', response); + } + }, + + getUserPrefErrorCallback: function (request) { + // this user is first time login + if (request.status == 404) { + console.log('Persist did NOT find the key'); + } + }, + + /** + * Reset widgets visibility-status + */ + resetAllWidgets: function() { + var self = this; + App.showConfirmationPopup(function() { + if(!App.testMode) { + self.postUserPref(self.get('persistKey'), self.get('initPrefObject')); + self.setDBProperty(self.get('persistKey'), self.get('initPrefObject')); + } + self.translateToReal(self.get('initPrefObject')); + }); + }, + + /** + * @type {string} + */ + gangliaUrl: function () { + return App.router.get('clusterController.gangliaUrl') + "/?r=hour&cs=&ce=&m=&s=by+name&c=HDPSlaves&tab=m&vn="; + }.property('App.router.clusterController.gangliaUrl'), + + showAlertsPopup: function (event) { + var service = event.context; + App.router.get('mainAlertsController').loadAlerts(service.get('serviceName'), "SERVICE"); + App.ModalPopup.show({ + header: this.t('services.alerts.headingOfList'), + bodyClass: Ember.View.extend({ + templateName: require('templates/main/dashboard/alert_notification_popup'), + service: service, + controllerBinding: 'App.router.mainAlertsController', + warnAlerts: function () { + return this.get('controller.alerts').filterProperty('isOk', false).filterProperty('ignoredForServices', false); + }.property('controller.alerts'), + + warnAlertsCount: function () { + return this.get('warnAlerts').length; + }.property('warnAlerts'), + + warnAlertsMessage: function() { + return Em.I18n.t('services.alerts.head').format(this.get('warnAlertsCount')); + }.property('warnAlertsCount'), + + nagiosUrl: function () { + return App.router.get('clusterController.nagiosUrl'); + }.property('App.router.clusterController.nagiosUrl'), + + closePopup: function () { + this.get('parentView').hide(); + }, + + viewNagiosUrl: function () { + window.open(this.get('nagiosUrl'), "_blank"); + this.closePopup(); + }, + + selectService: function () { + App.router.transitionTo('services.service.summary', service); + this.closePopup(); + } + }), + primary: Em.I18n.t('common.close'), + secondary : null, + didInsertElement: function () { + this.$().find('.modal-footer').addClass('align-center'); + this.$().children('.modal').css({'margin-top': '-350px'}); + } + }); + event.stopPropagation(); + } + +}); +
