services_config.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. var validator = require('utils/validator');
  20. App.ServicesConfigView = Em.View.extend({
  21. templateName: require('templates/common/configs/services_config'),
  22. didInsertElement: function () {
  23. var controller = this.get('controller');
  24. controller.loadStep();
  25. }
  26. });
  27. App.ServiceConfigView = Em.View.extend({
  28. templateName: require('templates/common/configs/service_config'),
  29. isRestartMessageCollapsed: false,
  30. filter: '', //from template
  31. columns: [], //from template
  32. canEdit: true, // View is editable or read-only?
  33. toggleRestartMessageView: function(){
  34. this.$('.service-body').toggle('blind', 200);
  35. this.set('isRestartMessageCollapsed', !this.get('isRestartMessageCollapsed'));
  36. },
  37. didInsertElement: function () {
  38. this.$('.service-body').hide();
  39. $(".restart-required-property").tooltip({html:true});
  40. }
  41. });
  42. App.ServiceConfigsByCategoryView = Ember.View.extend({
  43. classNames: ['accordion-group', 'common-config-category'],
  44. classNameBindings: ['category.name', 'isShowBlock::hidden'],
  45. content: null,
  46. category: null,
  47. service: null,
  48. canEdit: true, // View is editable or read-only?
  49. serviceConfigs: null, // General, Advanced, NameNode, SNameNode, DataNode, etc.
  50. // total number of
  51. // hosts (by
  52. // default,
  53. // cacheable )
  54. categoryConfigs: function () {
  55. return this.get('serviceConfigs').filterProperty('category', this.get('category.name')).filterProperty('isVisible', true);
  56. }.property('serviceConfigs.@each', 'categoryConfigsAll.@each.isVisible').cacheable(),
  57. /**
  58. * This method provides all the properties which apply
  59. * to this category, irrespective of visibility. This
  60. * is helpful in Oozie/Hive database configuration, where
  61. * MySQL etc. database options don't show up, because
  62. * they were not visible initially.
  63. */
  64. categoryConfigsAll: function () {
  65. return this.get('serviceConfigs').filterProperty('category', this.get('category.name'));
  66. }.property('serviceConfigs.@each').cacheable(),
  67. /**
  68. * When the view is in read-only mode, it marks
  69. * the properties as read-only.
  70. */
  71. updateReadOnlyFlags: function(){
  72. var configs = this.get('serviceConfigs');
  73. var canEdit = this.get('canEdit');
  74. if(!canEdit && configs){
  75. configs.forEach(function(config){
  76. config.set('isEditable', false);
  77. });
  78. }
  79. },
  80. /**
  81. * Filtered <code>categoryConfigs</code> array. Used to show filtered result
  82. */
  83. filteredCategoryConfigs: function() {
  84. var filter = this.get('parentView.filter').toLowerCase();
  85. var isOnlyModified = this.get('parentView.columns').length && this.get('parentView.columns')[1].get('selected');
  86. var isOnlyOverridden = this.get('parentView.columns').length && this.get('parentView.columns')[0].get('selected');
  87. var isOnlyRestartRequired = this.get('parentView.columns').length && this.get('parentView.columns')[2].get('selected');
  88. var filteredResult = this.get('categoryConfigs').filter(function(config){
  89. if(isOnlyModified && !config.get('isNotDefaultValue')){
  90. return false;
  91. }
  92. if(isOnlyOverridden && !config.get('isOverridden')){
  93. return false;
  94. }
  95. if(isOnlyRestartRequired && !config.get('isRestartRequired')){
  96. return false;
  97. }
  98. var searchString = config.get('defaultValue') + config.get('description') +
  99. config.get('displayName') + config.get('name');
  100. return searchString.toLowerCase().indexOf(filter) > -1;
  101. });
  102. // modify the display order of Oozie/Hive server category to make it look in the same order
  103. if(filteredResult.someProperty('displayName', 'Oozie Database')) {
  104. var displayNameArray = ['Oozie Database', 'Database Type', 'Database Name', 'Database Username', 'Database Password', 'Database Host', 'Oozie Data Dir', 'Oozie Server host'];
  105. var sortedArray = [];
  106. displayNameArray.forEach(function(item){
  107. var obj = filteredResult.findProperty('displayName', item);
  108. if(obj) {
  109. sortedArray.push(obj);
  110. }
  111. }, this);
  112. return sortedArray;
  113. }
  114. else {
  115. if(filteredResult.someProperty('displayName', 'Hive Database')) {
  116. var displayNameArray = ['Hive Database', 'Database Type', 'Database Name', 'Database Username', 'Database Password', 'Database Host', 'Hive Metastore host'];
  117. var sortedArray = [];
  118. displayNameArray.forEach(function(item){
  119. var obj = filteredResult.findProperty('displayName', item);
  120. if(obj) {
  121. sortedArray.push(obj);
  122. }
  123. }, this);
  124. return sortedArray;
  125. }
  126. else {
  127. return filteredResult;
  128. }
  129. }
  130. }.property('categoryConfigs','parentView.filter', 'parentView.columns.@each.selected'),
  131. /**
  132. * Onclick handler for Config Group Header. Used to show/hide block
  133. */
  134. onToggleBlock: function () {
  135. this.$('.accordion-body').toggle('blind', 500);
  136. this.set('category.isCollapsed', !this.get('category.isCollapsed'));
  137. },
  138. /**
  139. * Should we show config group or not
  140. */
  141. isShowBlock: function(){
  142. return this.get('category.canAddProperty') || this.get('filteredCategoryConfigs').length > 0;
  143. }.property('category.canAddProperty', 'filteredCategoryConfigs.length'),
  144. didInsertElement: function () {
  145. var isCollapsed = (this.get('category.name').indexOf('Advanced') != -1);
  146. this.set('category.isCollapsed', isCollapsed);
  147. if(isCollapsed){
  148. this.$('.accordion-body').hide();
  149. }
  150. this.updateReadOnlyFlags();
  151. },
  152. childView: App.ServiceConfigsOverridesView,
  153. changeFlag: Ember.Object.create({
  154. val: 1
  155. }),
  156. isOneOfAdvancedSections: function () {
  157. var category = this.get('category');
  158. return category.indexOf("Advanced") != -1;
  159. },
  160. showAddPropertyWindow: function (event) {
  161. var serviceConfigNames = this.get('categoryConfigs').mapProperty('name');
  162. var serviceConfigObj = Ember.Object.create({
  163. name: '',
  164. value: '',
  165. defaultValue: null,
  166. filename: '',
  167. isUserProperty: true,
  168. isKeyError:false,
  169. errorMessage:"",
  170. observeAddPropertyValue:function(){
  171. var name = this.get('name');
  172. if(name.trim() != ""){
  173. if(validator.isValidConfigKey(name)){
  174. var configMappingProperty = App.config.configMapping.all().findProperty('name', name);
  175. if((configMappingProperty == null) && (!serviceConfigNames.contains(name))){
  176. this.set("isKeyError", false);
  177. this.set("errorMessage", "");
  178. } else {
  179. this.set("isKeyError", true);
  180. this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.error.derivedKey'));
  181. }
  182. } else {
  183. this.set("isKeyError", true);
  184. this.set("errorMessage", Em.I18n.t('form.validator.configKey'));
  185. }
  186. } else {
  187. this.set("isKeyError", true);
  188. this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.errorMessage'));
  189. }
  190. }.observes("name")
  191. });
  192. var category = this.get('category');
  193. serviceConfigObj.displayType = "advanced";
  194. serviceConfigObj.category = category.get('name');
  195. var serviceName = this.get('service.serviceName');
  196. var serviceConfigsMetaData = require('data/service_configs');
  197. var serviceConfigMetaData = serviceConfigsMetaData.findProperty('serviceName', serviceName);
  198. var categoryMetaData = serviceConfigMetaData == null ? null : serviceConfigMetaData.configCategories.findProperty('name', category.get('name'));
  199. if (categoryMetaData != null) {
  200. serviceConfigObj.filename = categoryMetaData.siteFileName;
  201. }
  202. var self = this;
  203. App.ModalPopup.show({
  204. // classNames: ['big-modal'],
  205. classNames: [ 'sixty-percent-width-modal'],
  206. header: "Add Property",
  207. primary: 'Add',
  208. secondary: 'Cancel',
  209. onPrimary: function () {
  210. serviceConfigObj.observeAddPropertyValue();
  211. /**
  212. * For the first entrance use this if (serviceConfigObj.name.trim() != "")
  213. */
  214. if (!serviceConfigObj.isKeyError) {
  215. serviceConfigObj.displayName = serviceConfigObj.name;
  216. serviceConfigObj.id = 'site property';
  217. serviceConfigObj.serviceName = serviceName;
  218. var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj);
  219. self.get('serviceConfigs').pushObject(serviceConfigProperty);
  220. this.hide();
  221. }
  222. },
  223. onSecondary: function () {
  224. this.hide();
  225. },
  226. bodyClass: Ember.View.extend({
  227. templateName: require('templates/common/configs/addPropertyWindow'),
  228. controllerBinding: 'App.router.mainServiceInfoConfigsController',
  229. serviceConfigProperty: serviceConfigObj
  230. })
  231. });
  232. },
  233. /**
  234. * Removes the top-level property from list of properties.
  235. * Should be only called on user properties.
  236. */
  237. removeProperty: function (event) {
  238. var serviceConfigProperty = event.contexts[0];
  239. this.get('serviceConfigs').removeObject(serviceConfigProperty);
  240. },
  241. /**
  242. * Restores given property's value to be its default value.
  243. * Does not update if there is no default value.
  244. */
  245. doRestoreDefaultValue: function (event) {
  246. var serviceConfigProperty = event.contexts[0];
  247. var value = serviceConfigProperty.get('value');
  248. var dValue = serviceConfigProperty.get('defaultValue');
  249. if (dValue != null) {
  250. if(serviceConfigProperty.get('displayType') === 'password'){
  251. serviceConfigProperty.set('retypedPassword', dValue);
  252. }
  253. serviceConfigProperty.set('value', dValue);
  254. }
  255. },
  256. createOverrideProperty: function (event) {
  257. var serviceConfigProperty = event.contexts[0];
  258. var overrides = serviceConfigProperty.get('overrides');
  259. if (!overrides) {
  260. overrides = [];
  261. serviceConfigProperty.set('overrides', overrides);
  262. }
  263. // create new override with new value
  264. var newSCP = App.ServiceConfigProperty.create(serviceConfigProperty);
  265. newSCP.set('value', '');
  266. newSCP.set('isOriginalSCP', false); // indicated this is overridden value,
  267. newSCP.set('parentSCP', serviceConfigProperty);
  268. newSCP.set('selectedHostOptions', Ember.A([]));
  269. console.debug("createOverrideProperty(): Added:", newSCP, " to main-property:", serviceConfigProperty);
  270. overrides.pushObject(newSCP);
  271. // Launch override window
  272. var dummyEvent = {contexts: [newSCP]};
  273. this.showOverrideWindow(dummyEvent);
  274. },
  275. showOverrideWindow: function (event) {
  276. // argument 1
  277. var serviceConfigProperty = event.contexts[0];
  278. var parentServiceConfigProperty = serviceConfigProperty.get('parentSCP');
  279. var alreadyOverriddenHosts = [];
  280. parentServiceConfigProperty.get('overrides').forEach(function(override){
  281. if (override!=null && override!=serviceConfigProperty && override.get('selectedHostOptions')!=null){
  282. alreadyOverriddenHosts = alreadyOverriddenHosts.concat(override.get('selectedHostOptions'))
  283. }
  284. });
  285. var selectedHosts = serviceConfigProperty.get('selectedHostOptions');
  286. /**
  287. * Get all the hosts available for selection. Since data is dependent on
  288. * controller, we ask it, instead of doing regular Ember's App.Host.find().
  289. * This should be an array of App.Host.
  290. */
  291. var allHosts = this.get('controller.getAllHosts');
  292. var availableHosts = Ember.A([]);
  293. allHosts.forEach(function(host){
  294. var hostId = host.get('id');
  295. if(alreadyOverriddenHosts.indexOf(hostId)<0){
  296. availableHosts.pushObject(Ember.Object.create({
  297. selected: selectedHosts.indexOf(hostId)>-1,
  298. host: host
  299. }));
  300. }
  301. });
  302. /**
  303. * From the currently selected service we want the service-components.
  304. * We only need an array of objects which have the 'componentName' and
  305. * 'displayName' properties. Since each controller has its own objects,
  306. * we ask for a normalized array back.
  307. */
  308. var validComponents = this.get('controller.getCurrentServiceComponents');
  309. App.ModalPopup.show({
  310. classNames: [ 'sixty-percent-width-modal' ],
  311. header: Em.I18n.t('hosts.selectHostsDialog.title'),
  312. primary: Em.I18n.t('ok'),
  313. secondary: Em.I18n.t('common.cancel'),
  314. warningMessage: null,
  315. onPrimary: function () {
  316. console.debug('serviceConfigProperty.(old-selectedHosts)=' + serviceConfigProperty.get('selectedHosts'));
  317. var arrayOfSelectedHosts = [];
  318. var selectedHosts = availableHosts.filterProperty('selected', true);
  319. selectedHosts.forEach(function(host){
  320. arrayOfSelectedHosts.push(host.get('host.id'));
  321. });
  322. if(arrayOfSelectedHosts.length>0){
  323. this.set('warningMessage', null);
  324. serviceConfigProperty.set('selectedHostOptions', arrayOfSelectedHosts);
  325. serviceConfigProperty.validate();
  326. console.debug('serviceConfigProperty.(new-selectedHosts)=', arrayOfSelectedHosts);
  327. this.hide();
  328. }else{
  329. this.set('warningMessage', 'Atleast one host needs to be selected.');
  330. }
  331. },
  332. onSecondary: function () {
  333. // If property has no hosts already, then remove it from the parent.
  334. var hostCount = serviceConfigProperty.get('selectedHostOptions.length');
  335. if( hostCount < 1 ){
  336. var parentSCP = serviceConfigProperty.get('parentSCP');
  337. var overrides = parentSCP.get('overrides');
  338. overrides.removeObject(serviceConfigProperty);
  339. }
  340. this.hide();
  341. },
  342. bodyClass: Ember.View.extend({
  343. templateName: require('templates/common/configs/overrideWindow'),
  344. controllerBinding: 'App.router.mainServiceInfoConfigsController',
  345. serviceConfigProperty: serviceConfigProperty,
  346. filterText: '',
  347. filterTextPlaceholder: Em.I18n.t('hosts.selectHostsDialog.filter.placeHolder'),
  348. availableHosts: availableHosts,
  349. filterColumn: Ember.Object.create({id:'ip', name:'IP Address', selected:false}),
  350. filterColumns: Ember.A([
  351. Ember.Object.create({id:'ip', name:'IP Address', selected:false}),
  352. Ember.Object.create({id:'cpu', name:'CPU', selected:false}),
  353. Ember.Object.create({id:'memory', name:'RAM', selected:false}),
  354. Ember.Object.create({id:'diskUsage', name:'Disk Usage', selected:false}),
  355. Ember.Object.create({id:'loadAvg', name:'Load Average', selected:false}),
  356. Ember.Object.create({id:'osArch', name:'OS Architecture', selected:false}),
  357. Ember.Object.create({id:'osType', name:'OS Type', selected:false})
  358. ]),
  359. showOnlySelectedHosts: false,
  360. filterComponents: validComponents,
  361. filterComponent: null,
  362. filteredHosts: function () {
  363. var hosts = this.get('availableHosts');
  364. var filterText = this.get('filterText');
  365. var showOnlySelectedHosts = this.get('showOnlySelectedHosts');
  366. var filteredHosts = Ember.A([]);
  367. var self = this;
  368. hosts.forEach(function (host) {
  369. var skip = false;
  370. var ahost = host.get('host');
  371. var filterColumn = self.get('filterColumn');
  372. if (filterColumn == null) {
  373. filterColumn = self.get('filterColumns').objectAt(0);
  374. }
  375. var value = ahost.get(filterColumn.id);
  376. host.set('filterColumnValue', value);
  377. if (filterText != null && filterText.length > 0) {
  378. if ((value==null || !value.match(filterText)) && !host.get('host.publicHostName').match(filterText)) {
  379. skip = true;
  380. }
  381. }
  382. var filterComponent = self.get('filterComponent');
  383. if (!skip && filterComponent != null) {
  384. var componentFound = false;
  385. var fcn = filterComponent.get('componentName');
  386. var hcs = ahost.get('hostComponents');
  387. if (hcs != null) {
  388. hcs.forEach(function (hc) {
  389. if (fcn === hc.get('componentName')) {
  390. componentFound = true;
  391. }
  392. });
  393. }
  394. if (!componentFound) {
  395. skip = true;
  396. }
  397. }
  398. if (!skip && showOnlySelectedHosts && !host.get('selected')){
  399. skip = true;
  400. }
  401. if (!skip) {
  402. filteredHosts.pushObject(host);
  403. }
  404. });
  405. return filteredHosts;
  406. }.property('availableHosts', 'filterText', 'filterColumn', 'filterComponent', 'filterComponent.componentName', 'showOnlySelectedHosts'),
  407. hostColumnValue: function(host, column){
  408. return host.get(column.id);
  409. },
  410. hostSelectMessage: function () {
  411. var hosts = this.get('availableHosts');
  412. var selectedHosts = hosts.filterProperty('selected', true);
  413. return this.t('hosts.selectHostsDialog.selectedHostsLink').format(selectedHosts.get('length'), hosts.get('length'))
  414. }.property('availableHosts.@each.selected'),
  415. selectFilterColumn: function(event){
  416. if(event!=null && event.context!=null && event.context.id!=null){
  417. var filterColumn = this.get('filterColumn');
  418. if(filterColumn!=null){
  419. filterColumn.set('selected', false);
  420. }
  421. event.context.set('selected', true);
  422. this.set('filterColumn', event.context);
  423. }
  424. },
  425. selectFilterComponent: function(event){
  426. if(event!=null && event.context!=null && event.context.componentName!=null){
  427. var currentFilter = this.get('filterComponent');
  428. if(currentFilter!=null){
  429. currentFilter.set('selected', false);
  430. }
  431. if(currentFilter!=null && currentFilter.componentName===event.context.componentName){
  432. // selecting the same filter deselects it.
  433. this.set('filterComponent', null);
  434. }else{
  435. this.set('filterComponent', event.context);
  436. event.context.set('selected', true);
  437. }
  438. }
  439. },
  440. allHostsSelected: false,
  441. toggleSelectAllHosts: function(event){
  442. if(this.get('allHostsSelected')){
  443. // Select all hosts
  444. this.get('availableHosts').setEach('selected', true);
  445. }else{
  446. // Deselect all hosts
  447. this.get('availableHosts').setEach('selected', false);
  448. }
  449. }.observes('allHostsSelected'),
  450. toggleShowSelectedHosts: function () {
  451. var currentFilter = this.get('filterComponent');
  452. if (currentFilter != null) {
  453. currentFilter.set('selected', false);
  454. }
  455. this.set('filterComponent', null);
  456. this.set('filterText', null);
  457. this.set('showOnlySelectedHosts', !this.get('showOnlySelectedHosts'));
  458. }
  459. })
  460. });
  461. }
  462. });
  463. App.ServiceConfigTab = Ember.View.extend({
  464. tagName: 'li',
  465. selectService: function (event) {
  466. this.set('controller.selectedService', event.context);
  467. },
  468. didInsertElement: function () {
  469. var serviceName = this.get('controller.selectedService.serviceName');
  470. this.$('a[href="#' + serviceName + '"]').tab('show');
  471. }
  472. });
  473. /**
  474. * custom view for capacity scheduler category
  475. * @type {*}
  476. */
  477. App.ServiceConfigCapacityScheduler = App.ServiceConfigsByCategoryView.extend({
  478. templateName: require('templates/common/configs/capacity_scheduler'),
  479. category: null,
  480. service: null,
  481. serviceConfigs: null,
  482. customConfigs: require('data/custom_configs'),
  483. /**
  484. * configs filtered by capacity-scheduler category
  485. */
  486. categoryConfigs: function () {
  487. return this.get('serviceConfigs').filterProperty('category', this.get('category.name'));
  488. }.property('queueObserver', 'serviceConfigs.@each'),
  489. /**
  490. * rewrote method to avoid incompatibility with parent
  491. */
  492. filteredCategoryConfigs: function(){
  493. return this.get('categoryConfigs');
  494. }.property(),
  495. advancedConfigs: function(){
  496. return this.get('categoryConfigs').filterProperty('isQueue', undefined) || [];
  497. }.property('categoryConfigs.@each'),
  498. didInsertElement: function(){
  499. this._super();
  500. this.createEmptyQueue(this.get('customConfigs').filterProperty('isQueue'));
  501. },
  502. //list of fields which will be populated by default in a new queue
  503. fieldsToPopulate: [
  504. "mapred.capacity-scheduler.queue.<queue-name>.minimum-user-limit-percent",
  505. "mapred.capacity-scheduler.queue.<queue-name>.user-limit-factor",
  506. "mapred.capacity-scheduler.queue.<queue-name>.supports-priority",
  507. "mapred.capacity-scheduler.queue.<queue-name>.maximum-initialized-active-tasks",
  508. "mapred.capacity-scheduler.queue.<queue-name>.maximum-initialized-active-tasks-per-user",
  509. "mapred.capacity-scheduler.queue.<queue-name>.init-accept-jobs-factor"
  510. ],
  511. /**
  512. * create empty queue
  513. * take some queue then copy it and set all config values to null
  514. * @param customConfigs
  515. */
  516. createEmptyQueue: function(customConfigs){
  517. var emptyQueue = {
  518. name: '<queue-name>',
  519. configs: []
  520. };
  521. var fieldsToPopulate = this.get('fieldsToPopulate');
  522. customConfigs.forEach(function(config){
  523. var newConfig = $.extend({}, config);
  524. if(fieldsToPopulate.contains(config.name)){
  525. App.config.setDefaultQueue(newConfig, emptyQueue.name);
  526. }
  527. newConfig = App.ServiceConfigProperty.create(newConfig);
  528. newConfig.validate();
  529. emptyQueue.configs.push(newConfig);
  530. });
  531. this.set('emptyQueue', emptyQueue);
  532. },
  533. queues: function(){
  534. var configs = this.get('categoryConfigs').filterProperty('isQueue', true);
  535. var queueNames = [];
  536. var queues = [];
  537. configs.mapProperty('name').forEach(function(name){
  538. var queueName = /^mapred\.capacity-scheduler\.queue\.(.*?)\./.exec(name);
  539. if(queueName){
  540. queueNames.push(queueName[1]);
  541. }
  542. });
  543. queueNames = queueNames.uniq();
  544. queueNames.forEach(function(queueName){
  545. queues.push({
  546. name: queueName,
  547. color: this.generateColor(queueName),
  548. configs: this.filterConfigsByQueue(queueName, configs)
  549. })
  550. }, this);
  551. return queues;
  552. }.property('queueObserver'),
  553. /**
  554. * filter configs by queue
  555. * @param queueName
  556. * @param configs
  557. */
  558. filterConfigsByQueue: function (queueName, configs) {
  559. var customConfigs = this.get('customConfigs');
  560. var queue = [];
  561. configs.forEach(function (config) {
  562. var customConfig = customConfigs.findProperty('name', config.name.replace('queue.' + queueName, 'queue.<queue-name>'));
  563. if ((config.name.indexOf('mapred.capacity-scheduler.queue.' + queueName) !== -1) ||
  564. (config.name.indexOf('mapred.queue.' + queueName) !== -1)) {
  565. if (customConfig) {
  566. config.set('description', customConfig.description);
  567. config.set('displayName', customConfig.displayName);
  568. config.set('isRequired', customConfig.isRequired);
  569. config.set('unit', customConfig.unit);
  570. config.set('displayType', customConfig.displayType);
  571. config.set('valueRange', customConfig.valueRange);
  572. config.set('isVisible', customConfig.isVisible);
  573. config.set('index', customConfig.index);
  574. }
  575. queue.push(config);
  576. }
  577. });
  578. //each queue consists of 10 properties if less then add missing properties
  579. if(queue.length < 10){
  580. this.addMissingProperties(queue, queueName);
  581. }
  582. return queue;
  583. },
  584. /**
  585. * add missing properties to queue
  586. * @param queue
  587. * @param queueName
  588. */
  589. addMissingProperties: function(queue, queueName){
  590. var customConfigs = this.get('customConfigs');
  591. customConfigs.forEach(function(_config){
  592. var serviceConfigProperty = $.extend({}, _config);
  593. serviceConfigProperty.name = serviceConfigProperty.name.replace(/<queue-name>/, queueName);
  594. if(!queue.someProperty('name', serviceConfigProperty.name)){
  595. App.config.setDefaultQueue(serviceConfigProperty, queueName);
  596. serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigProperty);
  597. serviceConfigProperty.validate();
  598. queue.push(serviceConfigProperty);
  599. }
  600. }, this);
  601. },
  602. /**
  603. * format table content from queues
  604. */
  605. tableContent: function(){
  606. var result = [];
  607. this.get('queues').forEach(function(queue){
  608. var usersAndGroups = queue.configs.findProperty('name', 'mapred.queue.' + queue.name + '.acl-submit-job').get('value');
  609. usersAndGroups = (usersAndGroups) ? usersAndGroups.split(' ') : [''];
  610. if(usersAndGroups.length == 1){
  611. usersAndGroups.push('');
  612. }
  613. var queueObject = {
  614. name: queue.name,
  615. color: 'background-color:' + queue.color + ';',
  616. users: usersAndGroups[0],
  617. groups: usersAndGroups[1],
  618. capacity: queue.configs.findProperty('name', 'mapred.capacity-scheduler.queue.' + queue.name + '.capacity').get('value'),
  619. maxCapacity: queue.configs.findProperty('name', 'mapred.capacity-scheduler.queue.' + queue.name + '.maximum-capacity').get('value'),
  620. minUserLimit: queue.configs.findProperty('name', 'mapred.capacity-scheduler.queue.' + queue.name + '.minimum-user-limit-percent').get('value'),
  621. userLimitFactor: queue.configs.findProperty('name', 'mapred.capacity-scheduler.queue.' + queue.name + '.user-limit-factor').get('value'),
  622. supportsPriority: queue.configs.findProperty('name', 'mapred.capacity-scheduler.queue.' + queue.name + '.supports-priority').get('value')
  623. };
  624. result.push(queueObject);
  625. }, this);
  626. return result;
  627. }.property('queues'),
  628. queueObserver: null,
  629. /**
  630. * uses as template for adding new queue
  631. */
  632. emptyQueue: [],
  633. generateColor: function(str){
  634. var hash = 0;
  635. for (var i = 0; i < str.length; i++) {
  636. hash = str.charCodeAt(i) + ((hash << 5) - hash);
  637. }
  638. return '#' + Number(Math.abs(hash)).toString(16).concat('00000').substr(0, 6);
  639. },
  640. /**
  641. * add new queue
  642. * add created configs to serviceConfigs with current queue name
  643. * @param queue
  644. */
  645. addQueue: function(queue){
  646. var serviceConfigs = this.get('serviceConfigs');
  647. var admin = [];
  648. var submit = [];
  649. var submitConfig;
  650. var adminConfig;
  651. queue.name = queue.configs.findProperty('name', 'queueName').get('value');
  652. queue.configs.forEach(function(config){
  653. if(config.name == 'mapred.queue.<queue-name>.acl-administer-jobs'){
  654. if(config.type == 'USERS'){
  655. admin[0] = config.value;
  656. }
  657. if(config.type == 'GROUPS'){
  658. admin[1] = config.value;
  659. }
  660. if(config.isQueue){
  661. adminConfig = config;
  662. }
  663. }
  664. if(config.name == 'mapred.queue.<queue-name>.acl-submit-job'){
  665. if(config.type == 'USERS'){
  666. submit[0] = config.value;
  667. }
  668. if(config.type == 'GROUPS'){
  669. submit[1] = config.value;
  670. }
  671. if(config.isQueue){
  672. submitConfig = config;
  673. }
  674. }
  675. config.set('name', config.get('name').replace('<queue-name>', queue.name));
  676. config.set('value', config.get('value').toString());
  677. if(config.isQueue){
  678. serviceConfigs.push(config);
  679. }
  680. });
  681. adminConfig.set('value', admin.join(' '));
  682. submitConfig.set('value', submit.join(' '));
  683. this.set('queueObserver', new Date().getTime());
  684. },
  685. /**
  686. * delete queue
  687. * delete configs from serviceConfigs which have current queue name
  688. * @param queue
  689. */
  690. deleteQueue: function(queue){
  691. var serviceConfigs = this.get('serviceConfigs');
  692. var configNames = queue.configs.filterProperty('isQueue').mapProperty('name');
  693. for(var i = 0, l = serviceConfigs.length; i < l; i++){
  694. if(configNames.contains(serviceConfigs[i].name)){
  695. serviceConfigs.splice(i, 1);
  696. l--;
  697. i--;
  698. }
  699. }
  700. this.set('queueObserver', new Date().getTime());
  701. },
  702. /**
  703. * save changes that was made to queue
  704. * edit configs from serviceConfigs which have current queue name
  705. * @param queue
  706. */
  707. editQueue: function(queue){
  708. var serviceConfigs = this.get('serviceConfigs');
  709. var configNames = queue.configs.filterProperty('isQueue').mapProperty('name');
  710. serviceConfigs.forEach(function(_config){
  711. var configName = _config.get('name');
  712. var admin = [];
  713. var submit = [];
  714. if(configNames.contains(_config.get('name'))){
  715. if(configName == 'mapred.queue.' + queue.name + '.acl-submit-job'){
  716. submit = queue.configs.filterProperty('name', configName);
  717. submit = submit.findProperty('type', 'USERS').get('value') + ' ' + submit.findProperty('type', 'GROUPS').get('value');
  718. _config.set('value', submit);
  719. } else if(configName == 'mapred.queue.' + queue.name + '.acl-administer-jobs'){
  720. admin = queue.configs.filterProperty('name', configName);
  721. admin = admin.findProperty('type', 'USERS').get('value') + ' ' + admin.findProperty('type', 'GROUPS').get('value');
  722. _config.set('value', admin);
  723. } else {
  724. _config.set('value', queue.configs.findProperty('name', _config.get('name')).get('value').toString());
  725. }
  726. //comparison executes including 'queue.<queue-name>' to avoid false matches
  727. _config.set('name', configName.replace('queue.' + queue.name, 'queue.' + queue.configs.findProperty('name', 'queueName').get('value')));
  728. }
  729. });
  730. this.set('queueObserver', new Date().getTime());
  731. },
  732. pieChart: App.ChartPieView.extend({
  733. w: 200,
  734. h: 200,
  735. queues: null,
  736. didInsertElement: function(){
  737. this.update();
  738. },
  739. data: [{"label":"default", "value":100}],
  740. update: function(){
  741. var self = this;
  742. var data = [];
  743. var queues = this.get('queues');
  744. var capacitiesSum = 0;
  745. queues.forEach(function(queue){
  746. data.push({
  747. label: queue.name,
  748. value: parseInt(queue.configs.findProperty('name', 'mapred.capacity-scheduler.queue.' + queue.name + '.capacity').get('value')),
  749. color: queue.color
  750. })
  751. });
  752. data.mapProperty('value').forEach(function(capacity){
  753. capacitiesSum += capacity;
  754. });
  755. if(capacitiesSum < 100){
  756. data.push({
  757. label: Em.I18n.t('common.empty'),
  758. value: (100 - capacitiesSum),
  759. color: 'transparent',
  760. isEmpty: true
  761. })
  762. }
  763. $(d3.select(this.get('selector'))[0]).children().remove();
  764. this.set('data', data);
  765. this.set('palette', new Rickshaw.Color.Palette({
  766. scheme: data.mapProperty('color')
  767. }));
  768. this.appendSvg();
  769. this.get('arcs')
  770. .on("click", function(d,i) {
  771. var event = {context: d.data.label};
  772. if (d.data.isEmpty !== true) self.get('parentView').queuePopup(event);
  773. }).on('mouseover', function(d, i){
  774. var position = d3.svg.mouse(this);
  775. var label = $('#section_label');
  776. label.css('left', position[0] + 100);
  777. label.css('top', position[1] + 100);
  778. label.text(d.data.label);
  779. label.show();
  780. })
  781. .on('mouseout', function(d, i){
  782. $('#section_label').hide();
  783. })
  784. }.observes('queues'),
  785. donut:d3.layout.pie().sort(null).value(function(d) {return d.value;})
  786. }),
  787. /**
  788. * open popup with chosen queue
  789. * @param event
  790. */
  791. queuePopup: function(event){
  792. //if queueName was handed that means "Edit" mode, otherwise "Add" mode
  793. var queueName = event.context || null;
  794. var self = this;
  795. App.ModalPopup.show({
  796. didInsertElement: function(){
  797. if(queueName){
  798. this.set('header', Em.I18n.t('services.mapReduce.config.editQueue'));
  799. this.set('secondary', Em.I18n.t('common.save'));
  800. if(self.get('queues').length > 1 && self.get('canEdit')){
  801. this.set('delete', Em.I18n.t('common.delete'));
  802. }
  803. }
  804. },
  805. header: Em.I18n.t('services.mapReduce.config.addQueue'),
  806. secondary: Em.I18n.t('common.add'),
  807. primary: Em.I18n.t('common.cancel'),
  808. delete: null,
  809. isError: function(){
  810. if(!self.get('canEdit')){
  811. return true;
  812. }
  813. var content = this.get('content');
  814. var configs = content.configs.filter(function(config){
  815. if((config.name == 'mapred.queue.' + content.name + '.acl-submit-job' ||
  816. config.name == 'mapred.queue.' + content.name + '.acl-administer-jobs') &&
  817. (config.isQueue)){
  818. return false;
  819. }
  820. return true;
  821. });
  822. return configs.someProperty('isValid', false);
  823. }.property('content.configs.@each.isValid'),
  824. onDelete: function(){
  825. var view = this;
  826. App.ModalPopup.show({
  827. header: Em.I18n.t('popup.confirmation.commonHeader'),
  828. body: Em.I18n.t('hosts.delete.popup.body'),
  829. primary: Em.I18n.t('yes'),
  830. onPrimary: function(){
  831. self.deleteQueue(view.get('content'));
  832. view.hide();
  833. this.hide();
  834. }
  835. });
  836. },
  837. onSecondary: function() {
  838. if(queueName){
  839. self.editQueue(this.get('content'));
  840. } else {
  841. self.addQueue(this.get('content'));
  842. }
  843. this.hide();
  844. },
  845. /**
  846. * Queue properties order:
  847. * 1. Queue Name
  848. * 2. Capacity
  849. * 3. Max Capacity
  850. * 4. Users
  851. * 5. Groups
  852. * 6. Admin Users
  853. * 7. Admin Groups
  854. * 8. Support Priority
  855. * ...
  856. */
  857. content: function(){
  858. var content = (queueName) ? self.get('queues').findProperty('name', queueName) : self.get('emptyQueue');
  859. var configs = [];
  860. var tableContent = self.get('tableContent');
  861. // copy of queue configs
  862. content.configs.forEach(function (config, index) {
  863. if (config.name == 'mapred.capacity-scheduler.queue.' + content.name + '.capacity') {
  864. config.reopen({
  865. validate: function () {
  866. var value = this.get('value');
  867. var isError = false;
  868. var capacities = [];
  869. var capacitySum = 0;
  870. if(tableContent){
  871. capacities = tableContent.mapProperty('capacity');
  872. for (var i = 0, l = capacities.length; i < l; i++) {
  873. capacitySum += parseInt(capacities[i]);
  874. }
  875. if (content.name != '<queue-name>') {
  876. capacitySum = capacitySum - parseInt(tableContent.findProperty('name', content.name).capacity);
  877. }
  878. }
  879. if (value == '') {
  880. if (this.get('isRequired')) {
  881. this.set('errorMessage', 'This is required');
  882. isError = true;
  883. } else {
  884. return;
  885. }
  886. }
  887. if (!isError) {
  888. if (!validator.isValidInt(value)) {
  889. this.set('errorMessage', 'Must contain digits only');
  890. isError = true;
  891. }
  892. }
  893. if (!isError) {
  894. if ((capacitySum + parseInt(value)) > 100) {
  895. isError = true;
  896. this.set('errorMessage', 'The sum of capacities more than 100');
  897. }
  898. if (!isError) {
  899. this.set('errorMessage', '');
  900. }
  901. }
  902. }.observes('value')
  903. });
  904. }
  905. if(config.name == 'mapred.capacity-scheduler.queue.' + content.name + '.supports-priority'){
  906. if(config.get('value') == 'true' || config.get('value') === true){
  907. config.set('value', true);
  908. } else {
  909. config.set('value', false);
  910. }
  911. }
  912. configs[index] = App.ServiceConfigProperty.create(config);
  913. });
  914. content = {
  915. name: content.name,
  916. configs: configs
  917. };
  918. content = this.insertExtraConfigs(content);
  919. content.configs = this.sortQueueProperties(content.configs);
  920. return content;
  921. }.property(),
  922. footerClass: Ember.View.extend({
  923. classNames: ['modal-footer', 'host-checks-update'],
  924. template: Ember.Handlebars.compile([
  925. '{{#if view.parentView.delete}}<div class="pull-left">',
  926. '<button class="btn btn-danger" {{action onDelete target="view.parentView"}}>',
  927. '{{view.parentView.delete}}</button></div>{{/if}}',
  928. '<p class="pull-right">',
  929. '{{#if view.parentView.primary}}<button type="button" class="btn" {{action onPrimary target="view.parentView"}}>',
  930. '{{view.parentView.primary}}</button>{{/if}}',
  931. '{{#if view.parentView.secondary}}',
  932. '<button type="button" {{bindAttr disabled="view.parentView.isError"}} class="btn btn-success" {{action onSecondary target="view.parentView"}}>',
  933. '{{view.parentView.secondary}}</button>{{/if}}',
  934. '</p>'
  935. ].join(''))
  936. }),
  937. bodyClass: Ember.View.extend({
  938. template: Ember.Handlebars.compile([
  939. '<form class="form-horizontal pre-scrollable">{{#each view.parentView.content.configs}}',
  940. '{{#if isVisible}}',
  941. '<div class="row-fluid control-group">',
  942. ' <div {{bindAttr class="errorMessage:error :control-label-span :span4"}}>',
  943. ' <label>{{displayName}}</label>',
  944. ' </div>',
  945. ' <div {{bindAttr class="errorMessage:error :control-group :span8"}}>',
  946. ' {{view viewClass serviceConfigBinding="this" categoryConfigsBinding="view.categoryConfigs" }}',
  947. ' <span class="help-inline">{{errorMessage}}</span>',
  948. ' </div>',
  949. '</div>',
  950. '{{/if}}',
  951. '{{/each}}</form>'
  952. ].join(''))
  953. }),
  954. /**
  955. * Insert extra config in popup according to queue
  956. *
  957. * the mapred.queue.default.acl-administer-jobs turns into two implicit configs:
  958. * "Admin Users" field and "Admin Groups" field
  959. * the mapred.queue.default.acl-submit-job turns into two implicit configs:
  960. * "Users" field and "Groups" field
  961. * Add implicit config that contain "Queue Name"
  962. * @param content
  963. * @return {*}
  964. */
  965. insertExtraConfigs: function(content){
  966. var that = this;
  967. var admin = content.configs.findProperty('name', 'mapred.queue.' + content.name + '.acl-administer-jobs').get('value');
  968. var submit = content.configs.findProperty('name', 'mapred.queue.' + content.name + '.acl-submit-job').get('value');
  969. admin = (admin) ? admin.split(' ') : [''];
  970. submit = (submit) ? submit.split(' ') : [''];
  971. if(admin.length < 2){
  972. admin.push('');
  973. }
  974. if(submit.length < 2){
  975. submit.push('');
  976. }
  977. var newField = App.ServiceConfigProperty.create({
  978. name: 'queueName',
  979. displayName: Em.I18n.t('services.mapReduce.extraConfig.queue.name'),
  980. description: Em.I18n.t('services.mapReduce.description.queue.name'),
  981. value: (content.name == '<queue-name>') ? '' : content.name,
  982. validate: function(){
  983. var queueNames = self.get('queues').mapProperty('name');
  984. var value = this.get('value');
  985. var isError = false;
  986. var regExp = /^[a-z]([\_\-a-z0-9]{0,50})\$?$/i;
  987. if(value == ''){
  988. if (this.get('isRequired')) {
  989. this.set('errorMessage', 'This is required');
  990. isError = true;
  991. } else {
  992. return;
  993. }
  994. }
  995. if(!isError){
  996. if((queueNames.indexOf(value) !== -1) && (value != content.name)){
  997. this.set('errorMessage', 'Queue name is already used');
  998. isError = true;
  999. }
  1000. }
  1001. if(!isError){
  1002. if(!regExp.test(value)){
  1003. this.set('errorMessage', 'Incorrect input');
  1004. isError = true;
  1005. }
  1006. }
  1007. if (!isError) {
  1008. this.set('errorMessage', '');
  1009. }
  1010. }.observes('value'),
  1011. isRequired: true,
  1012. isVisible: true,
  1013. isEditable: self.get('canEdit'),
  1014. index: 0
  1015. });
  1016. newField.validate();
  1017. content.configs.unshift(newField);
  1018. var submitUser = App.ServiceConfigProperty.create({
  1019. name: 'mapred.queue.' + content.name + '.acl-submit-job',
  1020. displayName: Em.I18n.t('common.users'),
  1021. value: submit[0],
  1022. description: Em.I18n.t('services.mapReduce.description.queue.submit.user'),
  1023. isRequired: true,
  1024. isVisible: true,
  1025. type: 'USERS',
  1026. displayType: "UNIXList",
  1027. isEditable: self.get('canEdit'),
  1028. index: 3
  1029. });
  1030. var submitGroup = App.ServiceConfigProperty.create({
  1031. name: 'mapred.queue.' + content.name + '.acl-submit-job',
  1032. displayName: Em.I18n.t('services.mapReduce.config.queue.groups'),
  1033. description: Em.I18n.t('services.mapReduce.description.queue.submit.group'),
  1034. value: submit[1],
  1035. isRequired: true,
  1036. isVisible: true,
  1037. "displayType": "UNIXList",
  1038. type: 'GROUPS',
  1039. isEditable: self.get('canEdit'),
  1040. index: 4
  1041. });
  1042. var adminUser = App.ServiceConfigProperty.create({
  1043. name: 'mapred.queue.' + content.name + '.acl-administer-jobs',
  1044. displayName: Em.I18n.t('services.mapReduce.config.queue.adminUsers'),
  1045. description: Em.I18n.t('services.mapReduce.description.queue.admin.user'),
  1046. value: admin[0],
  1047. isRequired: true,
  1048. isVisible: true,
  1049. type: 'USERS',
  1050. displayType: "UNIXList",
  1051. isEditable: self.get('canEdit'),
  1052. index: 5
  1053. });
  1054. var adminGroup = App.ServiceConfigProperty.create({
  1055. name: 'mapred.queue.' + content.name + '.acl-administer-jobs',
  1056. displayName: Em.I18n.t('services.mapReduce.config.queue.adminGroups'),
  1057. value: admin[1],
  1058. description: Em.I18n.t('services.mapReduce.description.queue.admin.group'),
  1059. isRequired: true,
  1060. isVisible: true,
  1061. "displayType": "UNIXList",
  1062. type: 'GROUPS',
  1063. isEditable: self.get('canEdit'),
  1064. index: 6
  1065. });
  1066. submitUser.reopen({
  1067. validate: function(){
  1068. that.userGroupValidation(this, submitGroup);
  1069. }.observes('value')
  1070. });
  1071. submitGroup.reopen({
  1072. validate: function(){
  1073. that.userGroupValidation(this, submitUser);
  1074. }.observes('value')
  1075. });
  1076. adminUser.reopen({
  1077. validate: function(){
  1078. that.userGroupValidation(this, adminGroup);
  1079. }.observes('value')
  1080. });
  1081. adminGroup.reopen({
  1082. validate: function(){
  1083. that.userGroupValidation(this, adminUser);
  1084. }.observes('value')
  1085. });
  1086. submitUser.validate();
  1087. adminUser.validate();
  1088. content.configs.push(submitUser);
  1089. content.configs.push(submitGroup);
  1090. content.configs.push(adminUser);
  1091. content.configs.push(adminGroup);
  1092. return content;
  1093. },
  1094. /**
  1095. * sort properties of queue by index
  1096. * @param configs
  1097. * @return {Array}
  1098. */
  1099. sortQueueProperties: function(configs){
  1100. var sortedConfigs = [];
  1101. var skippedConfigs = [];
  1102. configs.forEach(function(_config){
  1103. if(isFinite(_config.index)){
  1104. sortedConfigs[_config.index] = _config;
  1105. } else {
  1106. skippedConfigs.push(_config);
  1107. }
  1108. });
  1109. return sortedConfigs.concat(skippedConfigs);
  1110. },
  1111. /**
  1112. * Validate by follow rules:
  1113. * Users can be blank. If this is blank, Groups must not be blank.
  1114. * Groups can be blank. If this is blank, Users must not be blank.
  1115. * @param context
  1116. * @param boundConfig
  1117. */
  1118. userGroupValidation: function(context, boundConfig){
  1119. if(context.get('value') == ''){
  1120. if(boundConfig.get('value') == ''){
  1121. context._super();
  1122. } else {
  1123. boundConfig.validate();
  1124. }
  1125. } else {
  1126. if(boundConfig.get('value') == ''){
  1127. boundConfig.set('errorMessage', '');
  1128. }
  1129. context._super();
  1130. }
  1131. }
  1132. })
  1133. }
  1134. });