host.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  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 filters = require('views/common/filter_view');
  20. var sort = require('views/common/sort_view');
  21. var date = require('utils/date');
  22. App.MainHostView = App.TableView.extend(App.TableServerViewMixin, {
  23. templateName:require('templates/main/host'),
  24. tableName: 'Hosts',
  25. updaterBinding: 'App.router.updateController',
  26. filterConditions: [],
  27. /**
  28. * Select/deselect all visible hosts flag
  29. * @property {bool}
  30. */
  31. selectAllHosts: false,
  32. /**
  33. * Contains all selected hosts on cluster
  34. */
  35. selectedHosts: [],
  36. /**
  37. * Request error data
  38. */
  39. requestError: null,
  40. colspan: function () {
  41. return App.get('supports.stackUpgrade') ? 11 : 10;
  42. }.property("App.supports.stackUpgrade"),
  43. /**
  44. * List of hosts in cluster
  45. * @type {Array}
  46. */
  47. content: function () {
  48. var controllerName = this.get('controller.name');
  49. var selectedHosts = App.db.getSelectedHosts(controllerName);
  50. if (this.get('controller')) {
  51. return this.get('controller.content').filter(function (host) {
  52. host.set('selected', selectedHosts.contains(host.get('hostName')));
  53. return true;
  54. });
  55. }
  56. return [];
  57. }.property('controller.content'),
  58. onRequestErrorHandler: function() {
  59. this.set('requestError', null);
  60. this.get('controller').get('dataSource').setEach('isRequested', false);
  61. this.set('filteringComplete', true);
  62. this.propertyDidChange('pageContent');
  63. }.observes('requestError'),
  64. /**
  65. * flag to toggle displaying selected hosts counter
  66. */
  67. showSelectedFilter: function () {
  68. return this.get('selectedHosts.length') > 0;
  69. }.property('selectedHosts'),
  70. /**
  71. * return filtered number of all content number information displayed on the page footer bar
  72. * @returns {String}
  73. */
  74. filteredContentInfo: function () {
  75. return this.t('hosts.filters.filteredHostsInfo').format(this.get('filteredCount'), this.get('totalCount'));
  76. }.property('filteredCount', 'totalCount'),
  77. /**
  78. * request latest data filtered by new parameters
  79. * called when trigger property(<code>refreshTriggers</code>) is changed
  80. */
  81. refresh: function () {
  82. var self = this;
  83. this.set('filteringComplete', false);
  84. var updaterMethodName = this.get('updater.tableUpdaterMap')[this.get('tableName')];
  85. this.get('updater')[updaterMethodName](function () {
  86. self.set('filteringComplete', true);
  87. self.propertyDidChange('pageContent');
  88. }, function() {
  89. self.set('requestError', arguments);
  90. });
  91. return true;
  92. },
  93. /**
  94. * reset filters value by column to which filter belongs
  95. * @param columns {Array}
  96. */
  97. resetFilterByColumns: function (columns) {
  98. var filterConditions = this.get('filterConditions');
  99. columns.forEach(function (iColumn) {
  100. var filterCondition = filterConditions.findProperty('iColumn', iColumn);
  101. if (filterCondition) {
  102. filterCondition.value = '';
  103. this.saveFilterConditions(filterCondition.iColumn, filterCondition.value, filterCondition.type, filterCondition.skipFilter);
  104. }
  105. }, this);
  106. },
  107. /**
  108. * Return pagination information displayed on the page
  109. * @type {String}
  110. */
  111. paginationInfo: function () {
  112. return this.t('tableView.filters.paginationInfo').format(this.get('startIndex'), this.get('endIndex'), this.get('filteredCount'));
  113. }.property('startIndex', 'endIndex', 'filteredCount'),
  114. paginationLeftClass: function () {
  115. if (this.get("startIndex") > 1 && this.get('filteringComplete')) {
  116. return "paginate_previous";
  117. }
  118. return "paginate_disabled_previous";
  119. }.property("startIndex", 'filteringComplete'),
  120. previousPage: function () {
  121. if (this.get('paginationLeftClass') === 'paginate_previous') {
  122. this._super();
  123. }
  124. },
  125. paginationRightClass: function () {
  126. if ((this.get("endIndex")) < this.get("filteredCount") && this.get('filteringComplete')) {
  127. return "paginate_next";
  128. }
  129. return "paginate_disabled_next";
  130. }.property("endIndex", 'filteredCount', 'filteringComplete'),
  131. nextPage: function () {
  132. if (this.get('paginationRightClass') === 'paginate_next') {
  133. this._super();
  134. }
  135. },
  136. /**
  137. * Select View with list of "rows-per-page" options
  138. * @type {Ember.View}
  139. */
  140. rowsPerPageSelectView: Em.Select.extend({
  141. content: ['10', '25', '50', '100'],
  142. attributeBindings: ['disabled'],
  143. disabled: true,
  144. disableView: function () {
  145. Em.run.next(this, function(){
  146. this.set('disabled', !this.get('parentView.filteringComplete'));
  147. });
  148. }.observes('parentView.filteringComplete'),
  149. change: function () {
  150. this.get('parentView').saveDisplayLength();
  151. var self = this;
  152. if (this.get('parentView.startIndex') === 1 || this.get('parentView.startIndex') === 0) {
  153. Ember.run.next(function () {
  154. self.get('parentView').updatePagination();
  155. });
  156. } else {
  157. Ember.run.next(function () {
  158. self.set('parentView.startIndex', 1);
  159. });
  160. }
  161. }
  162. }),
  163. saveStartIndex: function () {
  164. this.set('controller.startIndex', this.get('startIndex'));
  165. }.observes('startIndex'),
  166. clearFiltersObs: function() {
  167. var self = this;
  168. Em.run.next(function() {
  169. if (self.get('controller.clearFilters')) {
  170. self.clearFilters();
  171. }
  172. });
  173. },
  174. /**
  175. * Restore filter properties in view
  176. */
  177. willInsertElement: function () {
  178. if (!this.get('controller.showFilterConditionsFirstLoad')) {
  179. this.clearFilterCondition();
  180. }
  181. this._super();
  182. this.set('startIndex', this.get('controller.startIndex'));
  183. this.addObserver('pageContent.@each.selected', this, this.selectedHostsObserver);
  184. },
  185. /**
  186. * stub for filter function in TableView
  187. */
  188. filter: function () {
  189. //Since filtering moved to server side, function is empty
  190. },
  191. didInsertElement: function() {
  192. this.addObserver('controller.clearFilters', this, this.clearFiltersObs);
  193. this.clearFiltersObs();
  194. this.addObserver('selectAllHosts', this, this.toggleAllHosts);
  195. this.set('controller.isCountersUpdating', true);
  196. this.get('controller').updateStatusCounters();
  197. this.addObserver('filteringComplete', this, this.overlayObserver);
  198. this.addObserver('startIndex', this, 'updatePagination');
  199. this.addObserver('displayLength', this, 'updatePagination');
  200. this.addObserver('filteredCount', this, this.updatePaging);
  201. this.overlayObserver();
  202. },
  203. onInitialLoad: function () {
  204. if (this.get('tableFilteringComplete')) {
  205. this.refresh();
  206. }
  207. }.observes('tableFilteringComplete'),
  208. willDestroyElement: function() {
  209. this.set('controller.isCountersUpdating', false);
  210. },
  211. /**
  212. * Set <code>selected</code> property for each App.Host
  213. */
  214. toggleAllHosts: function() {
  215. this.get('pageContent').setEach('selected', this.get('selectAllHosts'));
  216. },
  217. /**
  218. * Trigger updating <code>selectedHostsCount</code> only 1 time
  219. */
  220. selectedHostsObserver: function() {
  221. Ember.run.once(this, 'updateCheckedFlags');
  222. },
  223. /**
  224. * Update <code>selectAllHosts</code> value
  225. */
  226. updateCheckedFlags: function() {
  227. this.removeObserver('selectAllHosts', this, this.toggleAllHosts);
  228. if (this.get('pageContent').length) {
  229. this.set('selectAllHosts', this.get('pageContent').everyProperty('selected', true));
  230. }
  231. else {
  232. this.set('selectAllHosts', false);
  233. }
  234. this.combineSelectedFilter();
  235. //10 is an index of selected column
  236. var controllerName = this.get('controller.name');
  237. App.db.setSelectedHosts(controllerName, this.get('selectedHosts'));
  238. this.addObserver('selectAllHosts', this, this.toggleAllHosts);
  239. },
  240. /**
  241. * combine selected hosts on page with selected hosts which are filtered out but added to cluster
  242. */
  243. combineSelectedFilter: function () {
  244. var controllerName = this.get('controller.name');
  245. var previouslySelectedHosts = App.db.getSelectedHosts(controllerName);
  246. var selectedHosts = [];
  247. var hostsOnPage = this.get('pageContent').mapProperty('hostName');
  248. selectedHosts = this.get('pageContent').filterProperty('selected').mapProperty('hostName');
  249. previouslySelectedHosts.forEach(function (hostName) {
  250. if (!hostsOnPage.contains(hostName)) {
  251. selectedHosts.push(hostName);
  252. }
  253. }, this);
  254. this.set('selectedHosts', selectedHosts);
  255. },
  256. /**
  257. * filter selected hosts
  258. */
  259. filterSelected: function() {
  260. //10 is an index of selected column
  261. this.updateFilter(10, this.get('selectedHosts'), 'multiple');
  262. },
  263. /**
  264. * Show spinner when filter/sorting request is in processing
  265. * @method overlayObserver
  266. */
  267. overlayObserver: function() {
  268. var $tbody = this.$('tbody'),
  269. $overlay = this.$('.hosts-overlay'),
  270. $spinner = $($overlay).find('.spinner');
  271. if (!this.get('filteringComplete')) {
  272. if (!$tbody) return;
  273. var tbodyPos = $tbody.position();
  274. if (!tbodyPos) return;
  275. $spinner.css('display', 'block');
  276. $overlay.css({
  277. top: tbodyPos.top + 1,
  278. left: tbodyPos.left + 1,
  279. width: $tbody.width() - 1,
  280. height: $tbody.height() - 1
  281. });
  282. }
  283. },
  284. /**
  285. * Clear selectedFilter
  286. * Set <code>selected</code> to false for each host
  287. */
  288. clearSelection: function() {
  289. this.get('pageContent').setEach('selected', false);
  290. this.set('selectAllHosts', false);
  291. App.db.setSelectedHosts(this.get('controller.name'), []);
  292. this.get('selectedHosts').clear();
  293. this.filterSelected();
  294. },
  295. /**
  296. * Returs all hostNames if amount is less than {minShown} or
  297. * first elements of array (number of elements - {minShown}) converted to string
  298. * @param {Array} hostNames - array of all listed hostNames
  299. * @param {String} divider - string to separate hostNames
  300. * @param {Number} minShown - min amount of hostName to be shown
  301. * @returns {String} hostNames
  302. * @method showHostNames
  303. */
  304. showHostNames: function(hostNames, divider, minShown) {
  305. if (hostNames.length > minShown) {
  306. return hostNames.slice(0, minShown).join(divider) + divider + Em.I18n.t("installer.step8.other").format(hostNames.length - minShown);
  307. } else {
  308. return hostNames.join(divider);
  309. }
  310. },
  311. /**
  312. * Confirmation Popup for bulk Operations
  313. */
  314. bulkOperationConfirm: function(operationData, selection) {
  315. var hostsNames = [],
  316. queryParams = [];
  317. switch(selection) {
  318. case 's':
  319. hostsNames = this.get('selectedHosts');
  320. if(hostsNames.length > 0){
  321. queryParams.push({
  322. key: 'Hosts/host_name',
  323. value: hostsNames,
  324. type: 'MULTIPLE'
  325. });
  326. }
  327. break;
  328. case 'f':
  329. queryParams = this.get('controller').getQueryParameters(true).filter(function (obj) {
  330. return !(obj.key == 'page_size' || obj.key == 'from');
  331. });
  332. break;
  333. }
  334. var loadingPopup = App.ModalPopup.show({
  335. header: Em.I18n.t('jobs.loadingTasks'),
  336. primary: false,
  337. secondary: false,
  338. bodyClass: Ember.View.extend({
  339. template: Ember.Handlebars.compile('<div class="spinner"></div>')
  340. })
  341. });
  342. var parameters = App.router.get('updateController').computeParameters(queryParams);
  343. if (!parameters.length) parameters = '&';
  344. App.ajax.send({
  345. name: 'hosts.bulk.operations',
  346. sender: this,
  347. data: {
  348. parameters: parameters.substring(0, parameters.length - 1),
  349. operationData: operationData,
  350. loadingPopup: loadingPopup
  351. },
  352. success: 'getHostsForBulkOperationSuccessCallback'
  353. });
  354. },
  355. convertHostsObjects: function(hosts) {
  356. var newHostArr = [];
  357. hosts.forEach(function (host) {
  358. newHostArr.push({
  359. index:host.index,
  360. id:host.id,
  361. clusterId: host.cluster_id,
  362. passiveState: host.passive_state,
  363. isRequested: host.is_requested,
  364. hostName: host.host_name,
  365. hostComponents: host.host_components
  366. })
  367. });
  368. return newHostArr;
  369. },
  370. getHostsForBulkOperationSuccessCallback: function(json, opt, param) {
  371. var self = this,
  372. operationData = param.operationData,
  373. hosts = this.convertHostsObjects(App.hostsMapper.map(json, true));
  374. // no hosts - no actions
  375. if (!hosts.length) {
  376. console.log('No bulk operation if no hosts selected.');
  377. return;
  378. }
  379. var hostNames = hosts.mapProperty('hostName');
  380. var hostsToSkip = [];
  381. if (operationData.action == "DECOMMISSION") {
  382. var hostComponentStatusMap = {}; // "DATANODE_c6401.ambari.apache.org" => "STARTED"
  383. var hostComponentIdMap = {}; // "DATANODE_c6401.ambari.apache.org" => "DATANODE"
  384. if (json.items) {
  385. json.items.forEach(function(host) {
  386. if (host.host_components) {
  387. host.host_components.forEach(function(component) {
  388. hostComponentStatusMap[component.id] = component.HostRoles.state;
  389. hostComponentIdMap[component.id] = component.HostRoles.component_name;
  390. });
  391. }
  392. });
  393. }
  394. hostsToSkip = hosts.filter(function(host) {
  395. var invalidStateComponents = host.hostComponents.filter(function(component) {
  396. return hostComponentIdMap[component] == operationData.realComponentName && hostComponentStatusMap[component] == 'INSTALLED';
  397. });
  398. return invalidStateComponents.length > 0;
  399. });
  400. }
  401. var hostNamesSkipped = hostsToSkip.mapProperty('hostName');
  402. var message;
  403. if (operationData.componentNameFormatted) {
  404. message = Em.I18n.t('hosts.bulkOperation.confirmation.hostComponents').format(operationData.message, operationData.componentNameFormatted, hostNames.length);
  405. }
  406. else {
  407. message = Em.I18n.t('hosts.bulkOperation.confirmation.hosts').format(operationData.message, hostNames.length);
  408. }
  409. param.loadingPopup.hide();
  410. App.ModalPopup.show({
  411. header: Em.I18n.t('hosts.bulkOperation.confirmation.header'),
  412. hostNames: hostNames.join("\n"),
  413. visibleHosts: self.showHostNames(hostNames, "\n", 3),
  414. hostNamesSkippedVisible: self.showHostNames(hostNamesSkipped, "\n", 3),
  415. hostNamesSkipped: function() {
  416. if (hostNamesSkipped.length) {
  417. return hostNamesSkipped.join("\n");
  418. }
  419. return false;
  420. }.property(),
  421. expanded: false,
  422. didInsertElement: function() {
  423. this.set('expanded', hostNames.length <= 3);
  424. },
  425. onPrimary: function() {
  426. self.get('controller').bulkOperation(operationData, hosts);
  427. this._super();
  428. },
  429. bodyClass: Em.View.extend({
  430. templateName: require('templates/main/host/bulk_operation_confirm_popup'),
  431. message: message,
  432. warningInfo: Em.I18n.t('hosts.bulkOperation.warningInfo.body'),
  433. textareaVisible: false,
  434. textTrigger: function() {
  435. this.set('textareaVisible', !this.get('textareaVisible'));
  436. },
  437. showAll: function() {
  438. this.set('parentView.visibleHosts', this.get('parentView.hostNames'));
  439. this.set('parentView.hostNamesSkippedVisible', this.get('parentView.hostNamesSkipped'));
  440. this.set('parentView.expanded', true);
  441. },
  442. putHostNamesToTextarea: function() {
  443. var hostNames = this.get('parentView.hostNames');
  444. if (this.get('textareaVisible')) {
  445. var wrapper = $(".task-detail-log-maintext");
  446. $('.task-detail-log-clipboard').html(hostNames).width(wrapper.width()).height(250);
  447. Em.run.next(function() {
  448. $('.task-detail-log-clipboard').select();
  449. });
  450. }
  451. }.observes('textareaVisible')
  452. })
  453. });
  454. },
  455. sortView: sort.serverWrapperView,
  456. nameSort: sort.fieldView.extend({
  457. column: 1,
  458. name:'publicHostName',
  459. displayName: Em.I18n.t('common.name')
  460. }),
  461. ipSort: sort.fieldView.extend({
  462. column: 2,
  463. name:'ip',
  464. displayName: Em.I18n.t('common.ipAddress'),
  465. type: 'ip'
  466. }),
  467. cpuSort: sort.fieldView.extend({
  468. column: 3,
  469. name:'cpu',
  470. displayName: Em.I18n.t('common.cores'),
  471. type: 'number'
  472. }),
  473. memorySort: sort.fieldView.extend({
  474. column: 4,
  475. name:'memory',
  476. displayName: Em.I18n.t('common.ram'),
  477. type: 'number'
  478. }),
  479. diskUsageSort: sort.fieldView.extend({
  480. name:'diskUsage',
  481. displayName: Em.I18n.t('common.diskUsage')
  482. }),
  483. loadAvgSort: sort.fieldView.extend({
  484. column: 5,
  485. name:'loadAvg',
  486. displayName: Em.I18n.t('common.loadAvg'),
  487. type: 'number'
  488. }),
  489. HostView:Em.View.extend({
  490. content:null,
  491. tagName: 'tr',
  492. didInsertElement: function(){
  493. App.tooltip(this.$("[rel='HealthTooltip'], [rel='UsageTooltip'], [rel='ComponentsTooltip']"));
  494. this.set('isComponentsCollapsed', true);
  495. this.set('isVersionsCollapsed', true);
  496. },
  497. toggleComponents: function(event) {
  498. this.toggleProperty('isComponentsCollapsed');
  499. this.$('.host-components').toggle();
  500. },
  501. toggleVersions: function(){
  502. this.toggleProperty('isVersionsCollapsed');
  503. this.$('.stack-versions').toggle();
  504. },
  505. /**
  506. * Tooltip message for "Restart Required" icon
  507. * @returns {String}
  508. */
  509. restartRequiredComponentsMessage: function() {
  510. var restartRequiredComponents = this.get('content.hostComponents').filterProperty('staleConfigs', true);
  511. var count = restartRequiredComponents.length;
  512. if (count <= 5) {
  513. var word = (count == 1) ? Em.I18n.t('common.component') : Em.I18n.t('common.components');
  514. return Em.I18n.t('hosts.table.restartComponents.withNames').format(restartRequiredComponents.getEach('displayName').join(', ')) + ' ' + word.toLowerCase();
  515. }
  516. return Em.I18n.t('hosts.table.restartComponents.withoutNames').format(count);
  517. }.property('content.hostComponents.@each.staleConfigs'),
  518. /**
  519. * Tooltip message for "Maintenance" icon
  520. * @returns {String}
  521. */
  522. componentsInPassiveStateMessage: function() {
  523. var componentsInPassiveState = this.get('content.hostComponents').filter(function(component) {
  524. return component.get('passiveState') !== 'OFF';
  525. });
  526. var count = componentsInPassiveState.length;
  527. if (count <= 5) {
  528. return Em.I18n.t('hosts.table.componentsInPassiveState.withNames').format(componentsInPassiveState.getEach('displayName').join(', '));
  529. }
  530. return Em.I18n.t('hosts.table.componentsInPassiveState.withoutNames').format(count);
  531. }.property('content.hostComponents.@each.passiveState'),
  532. /**
  533. * String with list of host components <code>displayName</code>
  534. * @returns {String}
  535. */
  536. labels: function() {
  537. return this.get('content.hostComponents').getEach('displayName').join("<br />");
  538. }.property('content.hostComponents.length'),
  539. /**
  540. * String with list of host components <code>displayName</code>
  541. * @returns {String}
  542. */
  543. versionlabels: function () {
  544. return this.get('content.stackVersions').map(function (version) {
  545. return Em.I18n.t('hosts.host.stackVersions.table.labels').format(version.get('version'), version.get('displayStatus'));
  546. }).join("<br />");
  547. }.property('content.stackVersions.length'),
  548. /**
  549. * CSS value for disk usage bar
  550. * @returns {String}
  551. */
  552. usageStyle:function () {
  553. return "width:" + this.get('content.diskUsage') + "%";
  554. }.property('content.diskUsage')
  555. }),
  556. /**
  557. * Update <code>hostsCount</code> in every category
  558. */
  559. updateHostsCount: function() {
  560. var hostsCountMap = this.get('controller.hostsCountMap');
  561. this.get('categories').forEach(function(category) {
  562. var hostsCount = (category.get('healthStatus').trim() === "") ? hostsCountMap['TOTAL'] : hostsCountMap[category.get('healthStatus')];
  563. if (!Em.isNone(hostsCount)) {
  564. category.set('hostsCount', hostsCount);
  565. category.set('hasHosts', (hostsCount > 0));
  566. }
  567. }, this);
  568. }.observes('controller.hostsCountMap'),
  569. /**
  570. * Category view for all hosts
  571. * @type {Object}
  572. */
  573. //@TODO maybe should be separated to two types (basing on <code>isHealthStatus</code>)
  574. categoryObject: Em.Object.extend({
  575. /**
  576. * Text used with <code>hostsCount</code> in category label
  577. * @type {String}
  578. */
  579. value: null,
  580. /**
  581. * Is category based on host health status
  582. * @type {bool}
  583. */
  584. isHealthStatus: true,
  585. /**
  586. * host health status (used if <code>isHealthStatus</code> is true)
  587. * @type {String}
  588. */
  589. healthStatusValue: '',
  590. /**
  591. * Should category be displayed on the top of the hosts table
  592. * @type {bool}
  593. */
  594. isVisible: true,
  595. /**
  596. * Is category selected now
  597. * @type {bool}
  598. */
  599. isActive: false,
  600. /**
  601. * String with path that category should observe
  602. * @type {String}
  603. */
  604. observes: null,
  605. /**
  606. * CSS-class for span in the category-link (used if <code>isHealthStatus</code> is false)
  607. * @type {String}
  608. */
  609. class: null,
  610. /**
  611. * Associated column number
  612. * @type {Number}
  613. */
  614. column: null,
  615. /**
  616. * Type of filter value (string, number, boolean)
  617. * @type {String}
  618. */
  619. type: null,
  620. /**
  621. * @type {String|Number|bool}
  622. */
  623. filterValue: null,
  624. /**
  625. * <code>App.Host</code> property that should be used to calculate <code>hostsCount</code> (used if <code>isHealthStatus</code> is false)
  626. * @type {String}
  627. */
  628. hostProperty: null,
  629. /**
  630. * Number of host in current category
  631. * @type {Number}
  632. */
  633. hostsCount: 0,
  634. /**
  635. * Determine if category has hosts
  636. * @type {bool}
  637. */
  638. hasHosts: false,
  639. /**
  640. * Add "active" class for category span-wrapper if current category is selected
  641. * @type {String}
  642. */
  643. itemClass: function() {
  644. return this.get('isActive') ? 'active' : '';
  645. }.property('isActive'),
  646. /**
  647. * Text shown on the right of category icon
  648. * @type {String}
  649. */
  650. label: function () {
  651. return "%@ (%@)".fmt(this.get('value'), this.get('hostsCount'));
  652. }.property('hostsCount')
  653. }),
  654. /**
  655. * List of categories used to filter hosts
  656. * @type {Array}
  657. */
  658. categories: function () {
  659. var self = this;
  660. var category_mocks = require('data/host/categories');
  661. return category_mocks.map(function(category_mock) {
  662. return self.categoryObject.create(category_mock);
  663. });
  664. }.property(),
  665. /**
  666. * Category for <code>selected</code> property of each App.Host
  667. */
  668. selectedCategory: function() {
  669. return this.get('categories').findProperty('selected', true);
  670. }.property('categories.@each.selected'),
  671. statusFilter: Em.View.extend({
  672. column: 0,
  673. categories: [],
  674. value: null,
  675. class: "",
  676. comboBoxLabel: function(){
  677. var selected = this.get('categories').findProperty('isActive');
  678. if (!this.get('value') || !selected) {
  679. return "%@ (%@)".fmt(Em.I18n.t('common.all'), this.get('parentView.totalCount'));
  680. } else {
  681. return "%@ (%@)".fmt(selected.get('value'), selected.get('hostsCount'))
  682. }
  683. }.property('value', 'parentView.totalCount'),
  684. /**
  685. * switch active category label
  686. */
  687. onCategoryChange: function () {
  688. this.get('categories').setEach('isActive', false);
  689. var selected = this.get('categories').findProperty('healthStatus', this.get('value'));
  690. selected.set('isActive', true);
  691. this.set('class', selected.get('class') + ' ' + selected.get('healthClass'));
  692. }.observes('value'),
  693. showClearFilter: function () {
  694. var mockEvent = {
  695. context: this.get('categories').findProperty('healthStatus', this.get('value'))
  696. };
  697. this.selectCategory(mockEvent);
  698. },
  699. /**
  700. * Trigger on Category click
  701. * @param {Object} event
  702. */
  703. selectCategory: function(event){
  704. var category = event.context;
  705. this.set('value', category.get('healthStatus'));
  706. this.get('parentView').resetFilterByColumns([0, 7, 8, 9]);
  707. if (category.get('isHealthStatus')) {
  708. this.get('parentView').updateFilter(0, category.get('healthStatus'), 'string');
  709. } else {
  710. this.get('parentView').updateFilter(category.get('column'), category.get('filterValue'), category.get('type'));
  711. }
  712. },
  713. clearFilter: function() {
  714. this.get('categories').setEach('isActive', false);
  715. this.set('value', '');
  716. this.set('class', '');
  717. this.showClearFilter();
  718. }
  719. }),
  720. /**
  721. * Filter view for name column
  722. * Based on <code>filters</code> library
  723. */
  724. nameFilterView: filters.createTextView({
  725. column: 1,
  726. fieldType: 'filter-input-width',
  727. onChangeValue: function(){
  728. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'string');
  729. }
  730. }),
  731. /**
  732. * Filter view for ip column
  733. * Based on <code>filters</code> library
  734. */
  735. ipFilterView: filters.createTextView({
  736. column: 2,
  737. fieldType: 'filter-input-width',
  738. onChangeValue: function(){
  739. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'string');
  740. }
  741. }),
  742. /**
  743. * Filter view for Cpu column
  744. * Based on <code>filters</code> library
  745. */
  746. cpuFilterView: filters.createTextView({
  747. fieldType: 'filter-input-width',
  748. fieldId: 'cpu_filter',
  749. column: 3,
  750. onChangeValue: function(){
  751. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'number');
  752. }
  753. }),
  754. /**
  755. * Filter view for Ram column
  756. * Based on <code>filters</code> library
  757. */
  758. ramFilterView: filters.createTextView({
  759. fieldType: 'filter-input-width',
  760. fieldId: 'ram_filter',
  761. column: 4,
  762. onChangeValue: function () {
  763. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'ambari-bandwidth');
  764. }
  765. }),
  766. /**
  767. * Filter view for LoadAverage column
  768. * Based on <code>filters</code> library
  769. */
  770. loadAvgFilterView: filters.createTextView({
  771. fieldType: 'filter-input-width',
  772. fieldId: 'load_avg_filter',
  773. column: 5,
  774. onChangeValue: function(){
  775. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'number');
  776. }
  777. }),
  778. /**
  779. * Filter view for HostComponents column
  780. * Based on <code>filters</code> library
  781. */
  782. componentsFilterView: filters.createComponentView({
  783. column: 6,
  784. /**
  785. * Inner FilterView. Used just to render component. Value bind to <code>mainview.value</code> property
  786. * Base methods was implemented in <code>filters.componentFieldView</code>
  787. */
  788. filterView: filters.componentFieldView.extend({
  789. templateName: require('templates/main/host/component_filter'),
  790. /**
  791. * Components which will be shown in component filter
  792. * @returns {Array}
  793. */
  794. componentsForFilter: function () {
  795. var installedComponents = App.StackServiceComponent.find().toArray();
  796. installedComponents.setEach('checkedForHostFilter', false);
  797. return installedComponents;
  798. }.property('App.router.clusterController.isLoaded'),
  799. /**
  800. * Master components
  801. * @returns {Array}
  802. */
  803. masterComponents: function () {
  804. return this.get('componentsForFilter').filterProperty('isMaster', true);
  805. }.property('componentsForFilter'),
  806. /**
  807. * Slave components
  808. * @returns {Array}
  809. */
  810. slaveComponents: function () {
  811. return this.get('componentsForFilter').filterProperty('isSlave', true);
  812. }.property('componentsForFilter'),
  813. /**
  814. * Client components
  815. * @returns {Array}
  816. */
  817. clientComponents: function () {
  818. return this.get('componentsForFilter').filterProperty('isClient', true);
  819. }.property('componentsForFilter'),
  820. /**
  821. * Checkbox for quick selecting/deselecting of master components
  822. */
  823. masterComponentsChecked:false,
  824. toggleMasterComponents:function () {
  825. this.get('masterComponents').setEach('checkedForHostFilter', this.get('masterComponentsChecked'));
  826. }.observes('masterComponentsChecked'),
  827. /**
  828. * Checkbox for quick selecting/deselecting of slave components
  829. */
  830. slaveComponentsChecked:false,
  831. toggleSlaveComponents:function () {
  832. this.get('slaveComponents').setEach('checkedForHostFilter', this.get('slaveComponentsChecked'));
  833. }.observes('slaveComponentsChecked'),
  834. /**
  835. * Checkbox for quick selecting/deselecting of client components
  836. */
  837. clientComponentsChecked: false,
  838. toggleClientComponents: function() {
  839. this.get('clientComponents').setEach('checkedForHostFilter', this.get('clientComponentsChecked'));
  840. }.observes('clientComponentsChecked'),
  841. /**
  842. * Clear filter.
  843. * Called by parent view, when user clicks on <code>x</code> button(clear button)
  844. */
  845. clearFilter:function() {
  846. this.set('masterComponentsChecked', false);
  847. this.set('slaveComponentsChecked', false);
  848. this.set('clientComponentsChecked', false);
  849. this.get('masterComponents').setEach('checkedForHostFilter', false);
  850. this.get('slaveComponents').setEach('checkedForHostFilter', false);
  851. this.get('clientComponents').setEach('checkedForHostFilter', false);
  852. this._super();
  853. },
  854. /**
  855. * Onclick handler for <code>Apply filter</code> button
  856. */
  857. applyFilter:function() {
  858. this._super();
  859. var self = this;
  860. var chosenComponents = [];
  861. this.get('masterComponents').filterProperty('checkedForHostFilter', true).forEach(function(item){
  862. chosenComponents.push(item.get('id'));
  863. });
  864. this.get('slaveComponents').filterProperty('checkedForHostFilter', true).forEach(function(item){
  865. chosenComponents.push(item.get('id'));
  866. });
  867. this.get('clientComponents').filterProperty('checkedForHostFilter', true).forEach(function(item){
  868. chosenComponents.push(item.get('id'));
  869. });
  870. Em.run.next(function() {
  871. self.set('value', chosenComponents);
  872. });
  873. },
  874. /**
  875. * Verify that checked checkboxes are equal to value stored in hidden field (components ids list)
  876. */
  877. checkComponents: function() {
  878. var components = this.get('value');
  879. var self = this;
  880. if (components) {
  881. components.forEach(function(componentId) {
  882. if(!self.tryCheckComponent(self, 'masterComponents', componentId)) {
  883. if(!self.tryCheckComponent(self, 'slaveComponents', componentId)) {
  884. self.tryCheckComponent(self, 'clientComponents', componentId);
  885. }
  886. }
  887. });
  888. }
  889. }.observes('value'),
  890. tryCheckComponent: function(self, category, componentId) {
  891. var c = self.get(category).findProperty('id', componentId);
  892. if (c) {
  893. if (!c.get('checkedForHostFilter')) {
  894. c.set('checkedForHostFilter', true);
  895. return true;
  896. }
  897. }
  898. return false;
  899. }
  900. }),
  901. onChangeValue: function(){
  902. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'multiple');
  903. }
  904. }),
  905. versionsFilterView: filters.wrapperView.extend({
  906. column: 11,
  907. filterView: filters.componentFieldView.extend({
  908. templateName: require('templates/main/host/version_filter'),
  909. selectedVersion: null,
  910. selecteStatus: null,
  911. value: [],
  912. versionSelectView: filters.createSelectView({
  913. classNames: ['notActive'],
  914. fieldType: 'filter-input-width',
  915. content: function () {
  916. return [
  917. {
  918. value: '',
  919. label: Em.I18n.t('common.all')
  920. }
  921. ].concat(App.HostStackVersion.find().mapProperty('version').uniq().map(function (version) {
  922. return {
  923. value: version,
  924. label: version
  925. }
  926. }));
  927. }.property('App.router.clusterController.isLoaded'),
  928. onChangeValue: function () {
  929. this.set('parentView.selectedVersion', this.get('value'));
  930. }
  931. }),
  932. statusSelectView: filters.createSelectView({
  933. classNames: ['notActive'],
  934. fieldType: 'filter-input-width',
  935. content: function () {
  936. return [
  937. {
  938. value: '',
  939. label: Em.I18n.t('common.all')
  940. }
  941. ].concat(App.HostStackVersion.statusDefinition.map(function (status) {
  942. return {
  943. value: status,
  944. label: App.HostStackVersion.formatStatus(status)
  945. }
  946. }));
  947. }.property('App.router.clusterController.isLoaded'),
  948. onChangeValue: function () {
  949. this.set('parentView.selectedStatus', this.get('value'));
  950. }
  951. }),
  952. /**
  953. * Onclick handler for <code>Apply filter</code> button
  954. */
  955. applyFilter: function () {
  956. this._super();
  957. var self = this;
  958. var filterProperties = [];
  959. if (this.get('selectedVersion')) {
  960. filterProperties.push({
  961. property: 'version',
  962. value: this.get('selectedVersion')
  963. });
  964. }
  965. if (this.get('selectedStatus')) {
  966. filterProperties.push({
  967. property: 'state',
  968. value: this.get('selectedStatus')
  969. });
  970. }
  971. self.set('value', filterProperties);
  972. },
  973. /**
  974. * Clear filter to initial state
  975. */
  976. clearFilter: function () {
  977. this.set('value', []);
  978. this.get('childViews').forEach(function (view) {
  979. if (typeof view.clearFilter === "function") view.clearFilter();
  980. });
  981. }
  982. }),
  983. onChangeValue: function () {
  984. this.get('parentView').updateFilter(this.get('column'), this.get('value'), 'sub-resource');
  985. },
  986. clearFilter: function () {
  987. this._super();
  988. this.get('childViews').forEach(function (view) {
  989. if (typeof view.clearFilter === "function") view.clearFilter();
  990. });
  991. }
  992. }),
  993. /**
  994. * associations between host property and column index
  995. * @type {Array}
  996. */
  997. colPropAssoc: function () {
  998. return this.get('controller.colPropAssoc');
  999. }.property('controller.colPropAssoc')
  1000. });