| /** |
| * 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 validator = require('utils/validator'); |
| var stringUtils = require('utils/string_utils'); |
| var hostsUtils = require('utils/hosts'); |
| |
| App.ServicesConfigView = Em.View.extend({ |
| templateName: require('templates/common/configs/services_config'), |
| didInsertElement: function () { |
| var controller = this.get('controller'); |
| controller.loadStep(); |
| } |
| }); |
| |
| App.ServiceConfigView = Em.View.extend({ |
| templateName: require('templates/common/configs/service_config'), |
| isRestartMessageCollapsed: false, |
| filter: '', //from template |
| columns: [], //from template |
| canEdit: true, // View is editable or read-only? |
| supportsHostOverrides: function () { |
| switch (this.get('controller.name')) { |
| case 'wizardStep7Controller': |
| return App.supports.hostOverridesInstaller; |
| case 'mainServiceInfoConfigsController': |
| return App.supports.hostOverrides; |
| case 'mainHostServiceConfigsController': |
| return App.supports.hostOverridesHost; |
| default: |
| return false; |
| } |
| }.property('controller.name'), |
| toggleRestartMessageView: function () { |
| this.$('.service-body').toggle('blind', 200); |
| this.set('isRestartMessageCollapsed', !this.get('isRestartMessageCollapsed')); |
| }, |
| didInsertElement: function () { |
| if(this.get('isNotEditable') === true) { |
| this.set('canEdit', false); |
| } |
| this.$('.service-body').hide(); |
| $(".restart-required-property").tooltip({html: true}); |
| $(".icon-lock").tooltip({placement: 'right'}); |
| } |
| }); |
| |
| |
| App.ServiceConfigsByCategoryView = Ember.View.extend({ |
| |
| classNames: ['accordion-group', 'common-config-category'], |
| classNameBindings: ['category.name', 'isShowBlock::hidden'], |
| |
| content: null, |
| category: null, |
| service: null, |
| canEdit: true, // View is editable or read-only? |
| serviceConfigs: null, // General, Advanced, NameNode, SNameNode, DataNode, etc. |
| // total number of |
| // hosts (by |
| // default, |
| // cacheable ) |
| categoryConfigs: function () { |
| return this.get('serviceConfigs').filterProperty('category', this.get('category.name')).filterProperty('isVisible', true); |
| }.property('serviceConfigs.@each', 'categoryConfigsAll.@each.isVisible').cacheable(), |
| |
| /** |
| * This method provides all the properties which apply |
| * to this category, irrespective of visibility. This |
| * is helpful in Oozie/Hive database configuration, where |
| * MySQL etc. database options don't show up, because |
| * they were not visible initially. |
| */ |
| categoryConfigsAll: function () { |
| return this.get('serviceConfigs').filterProperty('category', this.get('category.name')); |
| }.property('serviceConfigs.@each').cacheable(), |
| |
| /** |
| * Warn/prompt user to adjust Service props when changing user/groups in Misc |
| * Is triggered when user ended editing text field |
| */ |
| miscConfigChange: function (manuallyChangedProperty) { |
| var changedProperty; |
| if(manuallyChangedProperty.get("id")){ |
| changedProperty = [manuallyChangedProperty]; |
| }else{ |
| changedProperty = this.get("serviceConfigs").filterProperty("editDone", true); |
| } |
| |
| if (changedProperty.length > 0) { |
| changedProperty = changedProperty.objectAt(0); |
| } else { |
| return; |
| } |
| if (this.get('controller.selectedService.serviceName') == 'MISC') { |
| var newValue = changedProperty.get("value"); |
| var stepConfigs = this.get("controller.stepConfigs"); |
| this.affectedProperties = []; |
| var curConfigs = ""; |
| var affectedPropertyName = App.get('isHadoop2Stack') ? "dfs.permissions.superusergroup" : "dfs.permissions.supergroup"; |
| if (changedProperty.get("name") == "hdfs_user") { |
| curConfigs = stepConfigs.findProperty("serviceName", "HDFS").get("configs"); |
| if (newValue != curConfigs.findProperty("name", affectedPropertyName).get("value")) { |
| this.affectedProperties.push( |
| { |
| serviceName: "HDFS", |
| propertyName: affectedPropertyName, |
| propertyDisplayName: affectedPropertyName, |
| newValue: newValue, |
| curValue: curConfigs.findProperty("name", affectedPropertyName).get("value"), |
| changedPropertyName: "hdfs_user" |
| } |
| ); |
| } |
| if ($.trim(newValue) != $.trim(curConfigs.findProperty("name", "dfs.cluster.administrators").get("value"))) { |
| this.affectedProperties.push( |
| { |
| serviceName: "HDFS", |
| propertyName: "dfs.cluster.administrators", |
| propertyDisplayName: "dfs.cluster.administrators", |
| newValue: " " + $.trim(newValue), |
| curValue: curConfigs.findProperty("name", "dfs.cluster.administrators").get("value"), |
| changedPropertyName: "hdfs_user" |
| } |
| ); |
| } |
| } else if (changedProperty.get("name") == "hbase_user" && !App.get('isHadoop2Stack')) { |
| curConfigs = stepConfigs.findProperty("serviceName", "HDFS").get("configs"); |
| if (newValue != curConfigs.findProperty("name", "dfs.block.local-path-access.user").get("value")) { |
| this.affectedProperties.push( |
| { |
| serviceName: "HDFS", |
| propertyName: "dfs.block.local-path-access.user", |
| propertyDisplayName: "dfs.block.local-path-access.user", |
| newValue: newValue, |
| curValue: curConfigs.findProperty("name", "dfs.block.local-path-access.user").get("value"), |
| changedPropertyName: "hbase_user" |
| } |
| ); |
| } |
| var hbaseCurConfigs = stepConfigs.findProperty("serviceName", "HBASE").get("configs"); |
| if (newValue != hbaseCurConfigs.findProperty("name", "hbase.superuser").get("value")) { |
| this.affectedProperties.push( |
| { |
| serviceName: "HBASE", |
| propertyName: "hbase.superuser", |
| propertyDisplayName: "hbase.superuser", |
| newValue: newValue, |
| curValue: hbaseCurConfigs.findProperty("name", "hbase.superuser").get("value"), |
| changedPropertyName: "hbase_user" |
| } |
| ); |
| } |
| } else if (changedProperty.get("name") == "user_group") { |
| if (!((this.get("controller.selectedServiceNames").indexOf("MAPREDUCE") >= 0) || (this.get("controller.selectedServiceNames").indexOf("YARN") >= 0))) { |
| return; |
| } |
| if(this.get("controller.selectedServiceNames").indexOf("MAPREDUCE") >= 0) { |
| curConfigs = stepConfigs.findProperty("serviceName", "MAPREDUCE").get("configs"); |
| if (newValue != curConfigs.findProperty("name", "mapreduce.tasktracker.group").get("value")) { |
| this.affectedProperties.push( |
| { |
| serviceName: "MAPREDUCE", |
| propertyName: "mapreduce.tasktracker.group", |
| propertyDisplayName: "mapreduce.tasktracker.group", |
| newValue: newValue, |
| curValue: curConfigs.findProperty("name", "mapreduce.tasktracker.group").get("value"), |
| changedPropertyName: "user_group" |
| } |
| ) |
| } |
| if ($.trim(newValue) != $.trim(curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"))) { |
| this.affectedProperties.push( |
| { |
| serviceName: "MAPREDUCE", |
| propertyName: "mapreduce.cluster.administrators", |
| propertyDisplayName: "mapreduce.cluster.administrators", |
| newValue: " " + $.trim(newValue), |
| curValue: curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"), |
| changedPropertyName: "user_group" |
| } |
| ); |
| } |
| } |
| if(this.get("controller.selectedServiceNames").indexOf("MAPREDUCE2") >= 0) { |
| curConfigs = stepConfigs.findProperty("serviceName", "MAPREDUCE2").get("configs"); |
| if ($.trim(newValue) != $.trim(curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"))) { |
| this.affectedProperties.push( |
| { |
| serviceName: "MAPREDUCE2", |
| propertyName: "mapreduce.cluster.administrators", |
| propertyDisplayName: "mapreduce.cluster.administrators", |
| newValue: " " + $.trim(newValue), |
| curValue: curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"), |
| changedPropertyName: "user_group" |
| } |
| ); |
| } |
| } |
| if (this.get("controller.selectedServiceNames").indexOf("YARN") >= 0) { |
| curConfigs = stepConfigs.findProperty("serviceName", "YARN").get("configs"); |
| if (newValue != curConfigs.findProperty("name", "yarn.nodemanager.linux-container-executor.group").get("value")) { |
| this.affectedProperties.push( |
| { |
| serviceName: "YARN", |
| propertyName: "yarn.nodemanager.linux-container-executor.group", |
| propertyDisplayName: "yarn.nodemanager.linux-container-executor.group", |
| newValue: newValue, |
| curValue: curConfigs.findProperty("name", "yarn.nodemanager.linux-container-executor.group").get("value"), |
| changedPropertyName: "user_group" |
| } |
| ) |
| } |
| } |
| } |
| if (this.affectedProperties.length > 0 && !this.get("controller.miscModalVisible")) { |
| this.newAffectedProperties = this.affectedProperties; |
| var self = this; |
| return App.ModalPopup.show({ |
| classNames: ['modal-690px-width'], |
| showCloseButton: false, |
| header: "Warning: you must also change these Service properties", |
| onApply: function () { |
| self.get("newAffectedProperties").forEach(function (item) { |
| self.get("controller.stepConfigs").findProperty("serviceName", item.serviceName).get("configs") |
| .findProperty("name", item.propertyName).set("value", item.newValue); |
| }); |
| self.get("controller").set("miscModalVisible", false); |
| this.hide(); |
| }, |
| onIgnore: function () { |
| self.get("controller").set("miscModalVisible", false); |
| this.hide(); |
| }, |
| onUndo: function () { |
| var affected = self.get("newAffectedProperties").objectAt(0); |
| self.get("controller.stepConfigs").findProperty("serviceName", "MISC").get("configs") |
| .findProperty("name", affected.changedPropertyName).set("value", $.trim(affected.curValue)); |
| self.get("controller").set("miscModalVisible", false); |
| this.hide(); |
| }, |
| footerClass: Ember.View.extend({ |
| classNames: ['modal-footer'], |
| template: Ember.Handlebars.compile([ |
| '<div class="pull-right">', |
| '<button class="btn" {{action onUndo target="view.parentView"}}>Cancel</button>', |
| '<button class="btn btn-warning" {{action onIgnore target="view.parentView"}}>Ignore</button>', |
| '<button class="btn btn-success" {{action onApply target="view.parentView"}}>Apply</button>', |
| '</div>' |
| ].join('')) |
| }), |
| bodyClass: Ember.View.extend({ |
| templateName: require('templates/common/configs/propertyDependence'), |
| controller: this, |
| propertyChange: self.get("newAffectedProperties"), |
| didInsertElement: function () { |
| self.get("controller").set("miscModalVisible", true); |
| } |
| }) |
| }); |
| } |
| } |
| }.observes('categoryConfigs.@each.editDone'), |
| |
| /** |
| * When the view is in read-only mode, it marks |
| * the properties as read-only. |
| */ |
| updateReadOnlyFlags: function () { |
| var configs = this.get('serviceConfigs'); |
| var canEdit = this.get('canEdit'); |
| if (!canEdit && configs) { |
| configs.forEach(function(c){ |
| c.set('isEditable', false); |
| var overrides = c.get('overrides'); |
| if (overrides!=null) { |
| overrides.setEach('isEditable', false); |
| } |
| }); |
| } |
| }, |
| |
| /** |
| * Filtered <code>categoryConfigs</code> array. Used to show filtered result |
| */ |
| filteredCategoryConfigs: function () { |
| var filter = this.get('parentView.filter').toLowerCase(); |
| //var isOnlyModified = this.get('parentView.columns').length && this.get('parentView.columns')[1].get('selected'); |
| var isOnlyOverridden = this.get('parentView.columns').length && this.get('parentView.columns')[0].get('selected'); |
| //var isOnlyRestartRequired = this.get('parentView.columns').length && this.get('parentView.columns')[2].get('selected'); |
| var filteredResult = this.get('categoryConfigs').filter(function (config) { |
| |
| /* if (isOnlyModified && !config.get('isNotDefaultValue')) { |
| return false; |
| } |
| |
| if (isOnlyRestartRequired && !config.get('isRestartRequired')) { |
| return false; |
| }*/ |
| |
| if (isOnlyOverridden && !config.get('isOverridden')) { |
| return false; |
| } |
| |
| var searchString = config.get('defaultValue') + config.get('description') + |
| config.get('displayName') + config.get('name'); |
| |
| if (config.get('overrides')) { |
| config.get('overrides').forEach(function(overriddenConf){ |
| searchString += overriddenConf.get('value') + overriddenConf.get('group.name'); |
| }); |
| } |
| |
| |
| return searchString.toLowerCase().indexOf(filter) > -1; |
| }); |
| filteredResult = this.sortByIndex(filteredResult); |
| return filteredResult; |
| }.property('categoryConfigs', 'parentView.filter', 'parentView.columns.@each.selected'), |
| |
| /** |
| * sort configs in current category by index |
| * @param configs |
| * @return {*} |
| */ |
| sortByIndex: function (configs) { |
| var sortedConfigs = []; |
| var unSorted = []; |
| if (!configs.someProperty('index')) { |
| return configs; |
| } |
| configs.forEach(function (config) { |
| var index = config.get('index'); |
| if ((index !== null) && isFinite(index)) { |
| sortedConfigs[index] ? sortedConfigs.splice(index, 0, config) : sortedConfigs[index] = config; |
| } else { |
| unSorted.push(config); |
| } |
| }); |
| // remove undefined elements from array |
| sortedConfigs = sortedConfigs.filter(function (config) { |
| if (config !== undefined) return true; |
| }); |
| return sortedConfigs.concat(unSorted); |
| }, |
| /** |
| * Onclick handler for Config Group Header. Used to show/hide block |
| */ |
| onToggleBlock: function () { |
| this.$('.accordion-body').toggle('blind', 500); |
| this.set('category.isCollapsed', !this.get('category.isCollapsed')); |
| }, |
| |
| /** |
| * Should we show config group or not |
| */ |
| isShowBlock: function () { |
| return this.get('category.canAddProperty') || this.get('filteredCategoryConfigs').length > 0; |
| }.property('category.canAddProperty', 'filteredCategoryConfigs.length'), |
| |
| didInsertElement: function () { |
| var isCollapsed = (this.get('category.name').indexOf('Advanced') != -1); |
| var self = this; |
| this.set('category.isCollapsed', isCollapsed); |
| if (isCollapsed) { |
| this.$('.accordion-body').hide(); |
| } |
| this.updateReadOnlyFlags(); |
| Em.run.next(function() { |
| self.updateReadOnlyFlags(); |
| }); |
| }, |
| childView: App.ServiceConfigsOverridesView, |
| changeFlag: Ember.Object.create({ |
| val: 1 |
| }), |
| isOneOfAdvancedSections: function () { |
| var category = this.get('category'); |
| return category.indexOf("Advanced") != -1; |
| }, |
| showAddPropertyWindow: function (event) { |
| var configsOfFile = this.get('service.configs').filterProperty('filename', this.get('category.siteFileName')); |
| var self =this; |
| var serviceConfigObj = Ember.Object.create({ |
| name: '', |
| value: '', |
| defaultValue: null, |
| filename: '', |
| isUserProperty: true, |
| isKeyError: false, |
| errorMessage: "", |
| observeAddPropertyValue: function () { |
| var name = this.get('name'); |
| if (name.trim() != "") { |
| if (validator.isValidConfigKey(name)) { |
| var configMappingProperty = App.config.get('configMapping').all().filterProperty('filename',self.get('category.siteFileName')).findProperty('name', name); |
| if ((configMappingProperty == null) && (!configsOfFile.findProperty('name', name))) { |
| this.set("isKeyError", false); |
| this.set("errorMessage", ""); |
| } else { |
| this.set("isKeyError", true); |
| this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.error.derivedKey')); |
| } |
| } else { |
| this.set("isKeyError", true); |
| this.set("errorMessage", Em.I18n.t('form.validator.configKey')); |
| } |
| } else { |
| this.set("isKeyError", true); |
| this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.errorMessage')); |
| } |
| }.observes("name") |
| }); |
| |
| var category = this.get('category'); |
| serviceConfigObj.displayType = "advanced"; |
| serviceConfigObj.category = category.get('name'); |
| |
| var serviceName = this.get('service.serviceName'); |
| var serviceConfigsMetaData = App.config.get('preDefinedServiceConfigs'); |
| var serviceConfigMetaData = serviceConfigsMetaData.findProperty('serviceName', serviceName); |
| var categoryMetaData = serviceConfigMetaData == null ? null : serviceConfigMetaData.configCategories.findProperty('name', category.get('name')); |
| if (categoryMetaData != null) { |
| serviceConfigObj.filename = categoryMetaData.siteFileName; |
| } |
| |
| var self = this; |
| App.ModalPopup.show({ |
| // classNames: ['big-modal'], |
| classNames: [ 'sixty-percent-width-modal'], |
| header: "Add Property", |
| primary: 'Add', |
| secondary: 'Cancel', |
| onPrimary: function () { |
| serviceConfigObj.observeAddPropertyValue(); |
| /** |
| * For the first entrance use this if (serviceConfigObj.name.trim() != "") |
| */ |
| if (!serviceConfigObj.isKeyError) { |
| serviceConfigObj.displayName = serviceConfigObj.name; |
| serviceConfigObj.id = 'site property'; |
| serviceConfigObj.serviceName = serviceName; |
| serviceConfigObj.displayType = stringUtils.isSingleLine(serviceConfigObj.get('value')) ? 'advanced' : 'multiLine'; |
| var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj); |
| self.get('controller.secureConfigs').filterProperty('filename', self.get('category.siteFileName')).forEach(function (_secureConfig) { |
| if (_secureConfig.name === serviceConfigProperty.get('name')) { |
| serviceConfigProperty.set('isSecureConfig', true); |
| } |
| }, this); |
| self.get('serviceConfigs').pushObject(serviceConfigProperty); |
| this.hide(); |
| } |
| }, |
| onSecondary: function () { |
| this.hide(); |
| }, |
| bodyClass: Ember.View.extend({ |
| templateName: require('templates/common/configs/addPropertyWindow'), |
| controllerBinding: 'App.router.mainServiceInfoConfigsController', |
| serviceConfigProperty: serviceConfigObj |
| }) |
| }); |
| |
| }, |
| |
| /** |
| * Removes the top-level property from list of properties. |
| * Should be only called on user properties. |
| */ |
| removeProperty: function (event) { |
| var serviceConfigProperty = event.contexts[0]; |
| this.get('serviceConfigs').removeObject(serviceConfigProperty); |
| }, |
| |
| /** |
| * Restores given property's value to be its default value. |
| * Does not update if there is no default value. |
| */ |
| doRestoreDefaultValue: function (event) { |
| var serviceConfigProperty = event.contexts[0]; |
| var value = serviceConfigProperty.get('value'); |
| var dValue = serviceConfigProperty.get('defaultValue'); |
| if (dValue != null) { |
| if (serviceConfigProperty.get('displayType') === 'password') { |
| serviceConfigProperty.set('retypedPassword', dValue); |
| } |
| serviceConfigProperty.set('value', dValue); |
| } |
| this.miscConfigChange(serviceConfigProperty); |
| }, |
| |
| createOverrideProperty: function (event) { |
| var serviceConfigProperty = event.contexts[0]; |
| var serviceConfigController = this.get('controller'); |
| var selectedConfigGroup = serviceConfigController.get('selectedConfigGroup'); |
| if (selectedConfigGroup.get('isDefault')) { |
| // Launch dialog to pick/create Config-group |
| App.config.launchConfigGroupSelectionCreationDialog(this.get('service.serviceName'), |
| serviceConfigController.get('configGroups'), serviceConfigProperty, function (selectedGroupInPopup) { |
| console.log("launchConfigGroupSelectionCreationDialog(): Selected/Created:", selectedGroupInPopup); |
| if (selectedGroupInPopup) { |
| serviceConfigController.set('overrideToAdd', serviceConfigProperty); |
| serviceConfigController.set('selectedConfigGroup', selectedGroupInPopup); |
| } |
| }); |
| } else { |
| serviceConfigController.addOverrideProperty(serviceConfigProperty); |
| } |
| }, |
| |
| showOverrideWindow: function (event) { |
| // argument 1 |
| var serviceConfigProperty = event.contexts[0]; |
| var parentServiceConfigProperty = serviceConfigProperty.get('parentSCP'); |
| var alreadyOverriddenHosts = []; |
| parentServiceConfigProperty.get('overrides').forEach(function (override) { |
| if (override != null && override != serviceConfigProperty && override.get('selectedHostOptions') != null) { |
| alreadyOverriddenHosts = alreadyOverriddenHosts.concat(override.get('selectedHostOptions')) |
| } |
| }); |
| var selectedHosts = serviceConfigProperty.get('selectedHostOptions'); |
| /** |
| * Get all the hosts available for selection. Since data is dependent on |
| * controller, we ask it, instead of doing regular Ember's App.Host.find(). |
| * This should be an array of App.Host. |
| */ |
| var allHosts = this.get('controller.getAllHosts'); |
| var availableHosts = Ember.A([]); |
| allHosts.forEach(function (host) { |
| var hostId = host.get('id'); |
| if (alreadyOverriddenHosts.indexOf(hostId) < 0) { |
| availableHosts.pushObject(Ember.Object.create({ |
| selected: selectedHosts.indexOf(hostId) > -1, |
| host: host |
| })); |
| } |
| }); |
| /** |
| * From the currently selected service we want the service-components. |
| * We only need an array of objects which have the 'componentName' and |
| * 'displayName' properties. Since each controller has its own objects, |
| * we ask for a normalized array back. |
| */ |
| var validComponents = this.get('controller.getCurrentServiceComponents'); |
| hostsUtils.launchHostsSelectionDialog(availableHosts, selectedHosts, |
| false, validComponents, function(newSelectedHosts){ |
| if (newSelectedHosts!=null) { |
| serviceConfigProperty.set('selectedHostOptions', newSelectedHosts); |
| serviceConfigProperty.validate(); |
| } else { |
| // Dialog cancelled |
| // If property has no hosts already, then remove it from the parent. |
| var hostCount = serviceConfigProperty.get('selectedHostOptions.length'); |
| if (hostCount < 1) { |
| var parentSCP = serviceConfigProperty.get('parentSCP'); |
| var overrides = parentSCP.get('overrides'); |
| overrides.removeObject(serviceConfigProperty); |
| } |
| } |
| }); |
| } |
| }); |
| |
| App.ServiceConfigTab = Ember.View.extend({ |
| |
| tagName: 'li', |
| |
| selectService: function (event) { |
| this.set('controller.selectedService', event.context); |
| }, |
| |
| didInsertElement: function () { |
| var serviceName = this.get('controller.selectedService.serviceName'); |
| this.$('a[href="#' + serviceName + '"]').tab('show'); |
| } |
| }); |
| |
| /** |
| * custom view for capacity scheduler category |
| * @type {*} |
| */ |
| App.ServiceConfigCapacityScheduler = App.ServiceConfigsByCategoryView.extend({ |
| templateName: require('templates/common/configs/capacity_scheduler'), |
| category: null, |
| service: null, |
| serviceConfigs: null, |
| customConfigs: function(){ |
| return App.config.get('preDefinedCustomConfigs'); |
| }.property('App.config.preDefinedCustomConfigs'), |
| /** |
| * configs filtered by capacity-scheduler category |
| */ |
| categoryConfigs: function () { |
| return this.get('serviceConfigs').filterProperty('category', this.get('category.name')); |
| }.property('queueObserver', 'serviceConfigs.@each'), |
| /** |
| * rewrote method to avoid incompatibility with parent |
| */ |
| filteredCategoryConfigs: function () { |
| return this.get('categoryConfigs'); |
| }.property(), |
| advancedConfigs: function () { |
| return this.get('categoryConfigs').filterProperty('isQueue', undefined) || []; |
| }.property('categoryConfigs.@each'), |
| didInsertElement: function () { |
| this._super(); |
| this.createEmptyQueue(this.get('customConfigs').filterProperty('isQueue')); |
| }, |
| //list of fields which will be populated by default in a new queue |
| fieldsToPopulate: function(){ |
| if(App.get('isHadoop2Stack')){ |
| return ["yarn.scheduler.capacity.root.<queue-name>.user-limit-factor", |
| "yarn.scheduler.capacity.root.<queue-name>.state"]; |
| } |
| return [ |
| "mapred.capacity-scheduler.queue.<queue-name>.minimum-user-limit-percent", |
| "mapred.capacity-scheduler.queue.<queue-name>.user-limit-factor", |
| "mapred.capacity-scheduler.queue.<queue-name>.supports-priority", |
| "mapred.capacity-scheduler.queue.<queue-name>.maximum-initialized-active-tasks", |
| "mapred.capacity-scheduler.queue.<queue-name>.maximum-initialized-active-tasks-per-user", |
| "mapred.capacity-scheduler.queue.<queue-name>.init-accept-jobs-factor" |
| ]; |
| }.property('App.isHadoop2Stack'), |
| /** |
| * create empty queue |
| * take some queue then copy it and set all config values to null |
| * @param customConfigs |
| */ |
| createEmptyQueue: function (customConfigs) { |
| var emptyQueue = { |
| name: '<queue-name>', |
| configs: [] |
| }; |
| var fieldsToPopulate = this.get('fieldsToPopulate'); |
| customConfigs.forEach(function (config) { |
| var newConfig = $.extend({}, config); |
| if (fieldsToPopulate.contains(config.name)) { |
| newConfig.value = config.defaultValue; |
| } |
| newConfig = App.ServiceConfigProperty.create(newConfig); |
| newConfig.validate(); |
| emptyQueue.configs.push(newConfig); |
| }); |
| this.set('emptyQueue', emptyQueue); |
| }, |
| deriveQueueNames: function(configs){ |
| var queueNames = []; |
| configs.mapProperty('name').forEach(function(name){ |
| var queueName; |
| if(App.get('isHadoop2Stack')){ |
| queueName = /^yarn\.scheduler\.capacity\.root\.(.*?)\./.exec(name); |
| } else { |
| queueName = /^mapred\.capacity-scheduler\.queue\.(.*?)\./.exec(name); |
| } |
| if(queueName){ |
| queueNames.push(queueName[1]); |
| } |
| }); |
| return queueNames.uniq(); |
| }, |
| queues: function(){ |
| var configs = this.get('categoryConfigs').filterProperty('isQueue', true); |
| var queueNames = this.deriveQueueNames(configs); |
| var queues = []; |
| |
| queueNames.forEach(function(queueName){ |
| queues.push({ |
| name: queueName, |
| color: this.generateColor(queueName), |
| configs: this.groupConfigsByQueue(queueName, configs) |
| }) |
| }, this); |
| return queues; |
| }.property('queueObserver'), |
| /** |
| * group configs by queue |
| * @param queueName |
| * @param configs |
| */ |
| groupConfigsByQueue: function (queueName, configs) { |
| var customConfigs = []; |
| var queue = []; |
| this.get('customConfigs').forEach(function(_config){ |
| var copy = $.extend({}, _config); |
| copy.name = _config.name.replace('<queue-name>', queueName); |
| customConfigs.push(copy); |
| }); |
| configs.forEach(function (config) { |
| var customConfig = customConfigs.findProperty('name', config.get('name')); |
| if (customConfig) { |
| config.set('description', customConfig.description); |
| config.set('displayName', customConfig.displayName); |
| config.set('isRequired', customConfig.isRequired); |
| config.set('unit', customConfig.unit); |
| config.set('displayType', customConfig.displayType); |
| config.set('valueRange', customConfig.valueRange); |
| config.set('isVisible', customConfig.isVisible); |
| config.set('inTable', customConfig.inTable); |
| config.set('index', customConfig.index); |
| queue.push(config); |
| } |
| }); |
| if(queue.length < customConfigs.length){ |
| this.addMissingProperties(queue, customConfigs); |
| } |
| return queue; |
| }, |
| /** |
| * add missing properties to queue when they don't come from server |
| * @param queue |
| * @param customConfigs |
| */ |
| addMissingProperties: function(queue, customConfigs){ |
| customConfigs.forEach(function(_config){ |
| var serviceConfigProperty; |
| if(!queue.someProperty('name', _config.name)){ |
| _config.value = _config.defaultValue; |
| serviceConfigProperty = App.ServiceConfigProperty.create(_config); |
| serviceConfigProperty.validate(); |
| queue.push(serviceConfigProperty); |
| } |
| }, this); |
| }, |
| /** |
| * convert queues to table content |
| */ |
| tableContent: function () { |
| var result = []; |
| this.get('queues').forEach(function (queue) { |
| var usersAndGroups = queue.configs.findProperty('name', this.getUserAndGroupNames(queue.name)[0]).get('value'); |
| usersAndGroups = (usersAndGroups) ? usersAndGroups.split(' ') : ['']; |
| if (usersAndGroups.length == 1) { |
| usersAndGroups.push(''); |
| } |
| var queueObject = { |
| name: queue.name, |
| color: 'background-color:' + queue.color + ';', |
| configs: this.sortByIndex(queue.configs.filterProperty('inTable')) |
| }; |
| //push acl_submit_jobs users |
| queueObject.configs.unshift({ |
| value: usersAndGroups[1], |
| inTable: true, |
| displayName: Em.I18n.t('common.users') |
| }); |
| //push acl_submit_jobs groups |
| queueObject.configs.unshift({ |
| value: usersAndGroups[0], |
| inTable: true, |
| displayName: Em.I18n.t('services.mapReduce.config.queue.groups') |
| }); |
| result.push(queueObject); |
| }, this); |
| return result; |
| }.property('queues'), |
| /** |
| * create headers depending on existed properties in queue |
| */ |
| tableHeaders: function(){ |
| var headers = [ |
| Em.I18n.t('services.mapReduce.config.queue.name') |
| ]; |
| return (this.get('tableContent').length) ? |
| headers.concat(this.get('tableContent').objectAt(0).configs.filterProperty('inTable').mapProperty('displayName')): |
| headers; |
| }.property('tableContent'), |
| queueObserver: null, |
| /** |
| * uses as template for adding new queue |
| */ |
| emptyQueue: {}, |
| /** |
| * get capacities sum of queues except of current |
| * @param queueName |
| * @return {Number} |
| */ |
| getQueuesCapacitySum: function(queueName){ |
| var capacitySum = 0; |
| this.get('queues').filter(function(queue){ |
| return queue.name !== queueName; |
| }).forEach(function(queue){ |
| capacitySum = capacitySum + window.parseInt(queue.configs.find(function(config){ |
| return config.get('name').substr(-9, 9) === '.capacity'; |
| }).get('value')); |
| }); |
| return capacitySum; |
| }, |
| /** |
| * get names of configs, for users and groups, which have different names in HDP1 and HDP2 |
| * @param queueName |
| * @return {Array} |
| */ |
| getUserAndGroupNames: function(queueName){ |
| queueName = queueName || '<queue-name>'; |
| if(App.config.get('isHDP2')){ |
| return ['yarn.scheduler.capacity.root.' + queueName + '.acl_submit_jobs', |
| 'yarn.scheduler.capacity.root.' + queueName + '.acl_administer_jobs'] |
| } |
| return ['mapred.queue.' + queueName + '.acl-submit-job', |
| 'mapred.queue.' + queueName + '.acl-administer-jobs'] |
| }, |
| generateColor: function (str) { |
| var hash = 0; |
| for (var i = 0; i < str.length; i++) { |
| hash = str.charCodeAt(i) + ((hash << 5) - hash); |
| } |
| return '#' + Number(Math.abs(hash)).toString(16).concat('00000').substr(0, 6); |
| }, |
| /** |
| * add new queue |
| * add created configs to serviceConfigs with current queue name |
| * @param queue |
| */ |
| addQueue: function (queue) { |
| var serviceConfigs = this.get('serviceConfigs'); |
| var admin = []; |
| var submit = []; |
| var submitConfig; |
| var adminConfig; |
| queue.name = queue.configs.findProperty('name', 'queueName').get('value'); |
| queue.configs.forEach(function (config) { |
| var adminName = this.getUserAndGroupNames()[1]; |
| var submitName = this.getUserAndGroupNames()[0]; |
| if(config.name == adminName){ |
| if (config.type == 'USERS') { |
| admin[0] = config.value; |
| } |
| if (config.type == 'GROUPS') { |
| admin[1] = config.value; |
| } |
| if (config.isQueue) { |
| adminConfig = config; |
| } |
| } |
| if(config.name == submitName){ |
| if (config.type == 'USERS') { |
| submit[0] = config.value; |
| } |
| if (config.type == 'GROUPS') { |
| submit[1] = config.value; |
| } |
| if (config.isQueue) { |
| submitConfig = config; |
| } |
| } |
| config.set('name', config.get('name').replace('<queue-name>', queue.name)); |
| config.set('value', config.get('value').toString()); |
| if (config.isQueue) { |
| serviceConfigs.push(config); |
| } |
| }, this); |
| adminConfig.set('value', admin.join(' ')); |
| submitConfig.set('value', submit.join(' ')); |
| this.set('queueObserver', new Date().getTime()); |
| }, |
| /** |
| * delete queue |
| * delete configs from serviceConfigs which have current queue name |
| * @param queue |
| */ |
| deleteQueue: function (queue) { |
| var serviceConfigs = this.get('serviceConfigs'); |
| var configNames = queue.configs.filterProperty('isQueue').mapProperty('name'); |
| for (var i = 0, l = serviceConfigs.length; i < l; i++) { |
| if (configNames.contains(serviceConfigs[i].name)) { |
| serviceConfigs.splice(i, 1); |
| l--; |
| i--; |
| } |
| } |
| this.set('queueObserver', new Date().getTime()); |
| }, |
| /** |
| * save changes that was made to queue |
| * edit configs from serviceConfigs which have current queue name |
| * @param queue |
| */ |
| editQueue: function (queue) { |
| var serviceConfigs = this.get('serviceConfigs'); |
| var configNames = queue.configs.filterProperty('isQueue').mapProperty('name'); |
| serviceConfigs.forEach(function (_config) { |
| var configName = _config.get('name'); |
| var admin = []; |
| var submit = []; |
| //comparison executes including 'queue.<queue-name>' to avoid false matches |
| var queueNamePrefix = App.config.get('isHDP2') ? 'root.' : 'queue.'; |
| if (configNames.contains(_config.get('name'))) { |
| if(configName == this.getUserAndGroupNames(queue.name)[0]){ |
| submit = queue.configs.filterProperty('name', configName); |
| submit = submit.findProperty('type', 'USERS').get('value') + ' ' + submit.findProperty('type', 'GROUPS').get('value'); |
| _config.set('value', submit); |
| } else if(configName == this.getUserAndGroupNames(queue.name)[1]){ |
| admin = queue.configs.filterProperty('name', configName); |
| admin = admin.findProperty('type', 'USERS').get('value') + ' ' + admin.findProperty('type', 'GROUPS').get('value'); |
| _config.set('value', admin); |
| } else { |
| _config.set('value', queue.configs.findProperty('name', _config.get('name')).get('value').toString()); |
| } |
| _config.set('name', configName.replace(queueNamePrefix + queue.name, queueNamePrefix + queue.configs.findProperty('name', 'queueName').get('value'))); |
| } |
| }, this); |
| this.set('queueObserver', new Date().getTime()); |
| }, |
| pieChart: App.ChartPieView.extend({ |
| w: 200, |
| h: 200, |
| queues: null, |
| didInsertElement: function () { |
| this.update(); |
| }, |
| data: [{"label":"default", "value":100}], |
| update: function () { |
| var self = this; |
| var data = []; |
| var queues = this.get('queues'); |
| var capacitiesSum = 0; |
| queues.forEach(function (queue) { |
| var value = window.parseInt(queue.configs.find(function(_config){ |
| return _config.get('name').substr(-9, 9) === '.capacity'; |
| }).get('value')); |
| data.push({ |
| label: queue.name, |
| value: value, |
| color: queue.color |
| }) |
| }); |
| |
| data.mapProperty('value').forEach(function (capacity) { |
| capacitiesSum += capacity; |
| }); |
| if (capacitiesSum < 100) { |
| data.push({ |
| label: Em.I18n.t('common.empty'), |
| value: (100 - capacitiesSum), |
| color: 'transparent', |
| isEmpty: true |
| }) |
| } |
| $(d3.select(this.get('selector'))[0]).children().remove(); |
| this.set('data', data); |
| this.set('palette', new Rickshaw.Color.Palette({ |
| scheme: data.mapProperty('color') |
| })); |
| this.appendSvg(); |
| |
| this.get('arcs') |
| .on("click",function (d, i) { |
| var event = {context: d.data.label}; |
| if (d.data.isEmpty !== true) self.get('parentView').queuePopup(event); |
| }).on('mouseover', function (d, i) { |
| var position = d3.svg.mouse(this); |
| var label = $('#section_label'); |
| label.css('left', position[0] + 100); |
| label.css('top', position[1] + 100); |
| label.text(d.data.label); |
| label.show(); |
| }) |
| .on('mouseout', function (d, i) { |
| $('#section_label').hide(); |
| }) |
| |
| }.observes('queues'), |
| donut: d3.layout.pie().sort(null).value(function (d) { |
| return d.value; |
| }) |
| }), |
| /** |
| * open popup with chosen queue |
| * @param event |
| */ |
| queuePopup: function (event) { |
| //if queueName was handed that means "Edit" mode, otherwise "Add" mode |
| var queueName = event.context || null; |
| var self = this; |
| App.ModalPopup.show({ |
| didInsertElement: function () { |
| if (queueName) { |
| this.set('header', Em.I18n.t('services.mapReduce.config.editQueue')); |
| this.set('secondary', Em.I18n.t('common.save')); |
| if (self.get('queues').length > 1 && self.get('canEdit')) { |
| this.set('delete', Em.I18n.t('common.delete')); |
| } |
| } |
| }, |
| header: Em.I18n.t('services.mapReduce.config.addQueue'), |
| secondary: Em.I18n.t('common.add'), |
| primary: Em.I18n.t('common.cancel'), |
| delete: null, |
| isError: function () { |
| if (!self.get('canEdit')) { |
| return true; |
| } |
| var content = this.get('content'); |
| var configs = content.configs.filter(function (config) { |
| return !(config.name == self.getUserAndGroupNames(content.name)[0] || |
| config.name == self.getUserAndGroupNames(content.name)[1] && |
| config.isQueue); |
| }); |
| return configs.someProperty('isValid', false); |
| }.property('content.configs.@each.isValid'), |
| onDelete: function () { |
| var view = this; |
| App.ModalPopup.show({ |
| header: Em.I18n.t('popup.confirmation.commonHeader'), |
| body: Em.I18n.t('hosts.delete.popup.body'), |
| primary: Em.I18n.t('yes'), |
| onPrimary: function () { |
| self.deleteQueue(view.get('content')); |
| view.hide(); |
| this.hide(); |
| } |
| }); |
| }, |
| onSecondary: function () { |
| if (queueName) { |
| self.editQueue(this.get('content')); |
| } else { |
| self.addQueue(this.get('content')); |
| } |
| this.hide(); |
| }, |
| /** |
| * Queue properties order: |
| * 1. Queue Name |
| * 2. Capacity |
| * 3. Max Capacity |
| * 4. Users |
| * 5. Groups |
| * 6. Admin Users |
| * 7. Admin Groups |
| * 8. Support Priority |
| * ... |
| */ |
| content: function () { |
| var content = (queueName) ? self.get('queues').findProperty('name', queueName) : self.get('emptyQueue'); |
| var configs = []; |
| // copy of queue configs |
| content.configs.forEach(function (config, index) { |
| if(config.get('name').substr(-9, 9) === '.capacity') { |
| //added validation function for capacity property |
| config.reopen({ |
| validate: function () { |
| var value = this.get('value'); |
| var isError = false; |
| var capacitySum = self.getQueuesCapacitySum(content.name); |
| if (value == '') { |
| if (this.get('isRequired')) { |
| this.set('errorMessage', 'This is required'); |
| isError = true; |
| } else { |
| return; |
| } |
| } |
| if (!isError) { |
| if (!validator.isValidInt(value)) { |
| this.set('errorMessage', 'Must contain digits only'); |
| isError = true; |
| } |
| } |
| if (!isError) { |
| if ((capacitySum + parseInt(value)) > 100) { |
| isError = true; |
| this.set('errorMessage', 'The sum of capacities more than 100'); |
| } |
| if (!isError) { |
| this.set('errorMessage', ''); |
| } |
| } |
| }.observes('value') |
| }); |
| } |
| if (config.name == 'mapred.capacity-scheduler.queue.' + content.name + '.supports-priority') { |
| if (config.get('value') == 'true' || config.get('value') === true) { |
| config.set('value', true); |
| } else { |
| config.set('value', false); |
| } |
| } |
| if(config.name === 'yarn.scheduler.capacity.root.' + content.name + '.state'){ |
| config.reopen({ |
| validate: function(){ |
| var value = this.get('value'); |
| this._super(); |
| if(!this.get('errorMessage')){ |
| if(!(value === 'STOPPED' || value === 'RUNNING')){ |
| this.set('errorMessage', 'State value should be RUNNING or STOPPED'); |
| } |
| } |
| }.observes('value') |
| }) |
| } |
| configs[index] = App.ServiceConfigProperty.create(config); |
| }); |
| content = { |
| name: content.name, |
| configs: configs |
| }; |
| content = this.insertExtraConfigs(content); |
| content.configs = self.sortByIndex(content.configs); |
| return content; |
| }.property(), |
| footerClass: Ember.View.extend({ |
| classNames: ['modal-footer', 'host-checks-update'], |
| template: Ember.Handlebars.compile([ |
| '{{#if view.parentView.delete}}<div class="pull-left">', |
| '<button class="btn btn-danger" {{action onDelete target="view.parentView"}}>', |
| '{{view.parentView.delete}}</button></div>{{/if}}', |
| '<p class="pull-right">', |
| '{{#if view.parentView.primary}}<button type="button" class="btn" {{action onPrimary target="view.parentView"}}>', |
| '{{view.parentView.primary}}</button>{{/if}}', |
| '{{#if view.parentView.secondary}}', |
| '<button type="button" {{bindAttr disabled="view.parentView.isError"}} class="btn btn-success" {{action onSecondary target="view.parentView"}}>', |
| '{{view.parentView.secondary}}</button>{{/if}}', |
| '</p>' |
| ].join('')) |
| }), |
| bodyClass: Ember.View.extend({ |
| template: Ember.Handlebars.compile([ |
| '<form class="form-horizontal pre-scrollable" autocomplete="off">{{#each view.parentView.content.configs}}', |
| '{{#if isVisible}}', |
| '<div class="row-fluid control-group">', |
| ' <div {{bindAttr class="errorMessage:error :control-label-span :span4"}}>', |
| ' <label>{{displayName}}</label>', |
| ' </div>', |
| ' <div {{bindAttr class="errorMessage:error :control-group :span8"}}>', |
| ' {{view viewClass serviceConfigBinding="this" categoryConfigsBinding="view.categoryConfigs" }}', |
| ' <span class="help-inline">{{errorMessage}}</span>', |
| ' </div>', |
| '</div>', |
| '{{/if}}', |
| '{{/each}}</form>' |
| ].join('')) |
| }), |
| /** |
| * Insert extra config in popup according to queue |
| * |
| * the mapred.queue.default.acl-administer-jobs turns into two implicit configs: |
| * "Admin Users" field and "Admin Groups" field |
| * the mapred.queue.default.acl-submit-job turns into two implicit configs: |
| * "Users" field and "Groups" field |
| * Add implicit config that contain "Queue Name" |
| * @param content |
| * @return {*} |
| */ |
| insertExtraConfigs: function (content) { |
| var that = this; |
| var admin = content.configs.findProperty('name', self.getUserAndGroupNames(content.name)[1]).get('value'); |
| var submit = content.configs.findProperty('name', self.getUserAndGroupNames(content.name)[0]).get('value'); |
| admin = (admin) ? admin.split(' ') : ['']; |
| submit = (submit) ? submit.split(' ') : ['']; |
| if (admin.length < 2) { |
| admin.push(''); |
| } |
| if (submit.length < 2) { |
| submit.push(''); |
| } |
| var nameField = App.ServiceConfigProperty.create({ |
| name: 'queueName', |
| displayName: Em.I18n.t('services.mapReduce.extraConfig.queue.name'), |
| description: Em.I18n.t('services.mapReduce.description.queue.name'), |
| value: (content.name == '<queue-name>') ? '' : content.name, |
| validate: function () { |
| var queueNames = self.get('queues').mapProperty('name'); |
| var value = this.get('value'); |
| var isError = false; |
| var regExp = /^[a-z]([\_\-a-z0-9]{0,50})\$?$/i; |
| if (value == '') { |
| if (this.get('isRequired')) { |
| this.set('errorMessage', 'This is required'); |
| isError = true; |
| } else { |
| return; |
| } |
| } |
| if (!isError) { |
| if ((queueNames.indexOf(value) !== -1) && (value != content.name)) { |
| this.set('errorMessage', 'Queue name is already used'); |
| isError = true; |
| } |
| } |
| if (!isError) { |
| if (!regExp.test(value)) { |
| this.set('errorMessage', 'Incorrect input'); |
| isError = true; |
| } |
| } |
| if (!isError) { |
| this.set('errorMessage', ''); |
| } |
| }.observes('value'), |
| isRequired: true, |
| isVisible: true, |
| isEditable: self.get('canEdit'), |
| index: 0 |
| }); |
| nameField.validate(); |
| content.configs.unshift(nameField); |
| |
| var submitUser = App.ServiceConfigProperty.create({ |
| name: self.getUserAndGroupNames(content.name)[0], |
| displayName: Em.I18n.t('common.users'), |
| value: submit[0], |
| description: Em.I18n.t('services.mapReduce.description.queue.submit.user'), |
| isRequired: true, |
| isVisible: true, |
| type: 'USERS', |
| displayType: "UNIXList", |
| isEditable: self.get('canEdit'), |
| index: 3 |
| }); |
| |
| var submitGroup = App.ServiceConfigProperty.create({ |
| name: self.getUserAndGroupNames(content.name)[0], |
| displayName: Em.I18n.t('services.mapReduce.config.queue.groups'), |
| description: Em.I18n.t('services.mapReduce.description.queue.submit.group'), |
| value: submit[1], |
| isRequired: true, |
| isVisible: true, |
| "displayType": "UNIXList", |
| type: 'GROUPS', |
| isEditable: self.get('canEdit'), |
| index: 4 |
| }); |
| |
| var adminUser = App.ServiceConfigProperty.create({ |
| name: self.getUserAndGroupNames(content.name)[1], |
| displayName: Em.I18n.t('services.mapReduce.config.queue.adminUsers'), |
| description: Em.I18n.t('services.mapReduce.description.queue.admin.user'), |
| value: admin[0], |
| isRequired: true, |
| isVisible: true, |
| type: 'USERS', |
| displayType: "UNIXList", |
| isEditable: self.get('canEdit'), |
| index: 5 |
| }); |
| |
| var adminGroup = App.ServiceConfigProperty.create({ |
| name: self.getUserAndGroupNames(content.name)[1], |
| displayName: Em.I18n.t('services.mapReduce.config.queue.adminGroups'), |
| value: admin[1], |
| description: Em.I18n.t('services.mapReduce.description.queue.admin.group'), |
| isRequired: true, |
| isVisible: true, |
| "displayType": "UNIXList", |
| type: 'GROUPS', |
| isEditable: self.get('canEdit'), |
| index: 6 |
| }); |
| |
| submitUser.reopen({ |
| validate: function () { |
| that.userGroupValidation(this, submitGroup); |
| }.observes('value') |
| }); |
| submitGroup.reopen({ |
| validate: function () { |
| that.userGroupValidation(this, submitUser); |
| }.observes('value') |
| }); |
| adminUser.reopen({ |
| validate: function () { |
| that.userGroupValidation(this, adminGroup); |
| }.observes('value') |
| }); |
| adminGroup.reopen({ |
| validate: function () { |
| that.userGroupValidation(this, adminUser); |
| }.observes('value') |
| }); |
| |
| submitUser.validate(); |
| adminUser.validate(); |
| content.configs.push(submitUser); |
| content.configs.push(submitGroup); |
| content.configs.push(adminUser); |
| content.configs.push(adminGroup); |
| |
| return content; |
| }, |
| /** |
| * Validate by follow rules: |
| * Users can be blank. If it is blank, Groups must not be blank. |
| * Groups can be blank. If it is blank, Users must not be blank. |
| * @param context |
| * @param boundConfig |
| */ |
| userGroupValidation: function (context, boundConfig) { |
| if (context.get('value') == '') { |
| if (boundConfig.get('value') == '') { |
| context._super(); |
| } else { |
| boundConfig.validate(); |
| } |
| } else { |
| if (boundConfig.get('value') == '') { |
| boundConfig.set('errorMessage', ''); |
| } |
| context._super(); |
| } |
| } |
| }) |
| } |
| }); |