host.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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/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. /**
  41. * List of hosts in cluster
  42. * @type {Array}
  43. */
  44. contentBinding: 'controller.content',
  45. onRequestErrorHandler: function() {
  46. this.set('requestError', null);
  47. this.get('controller').get('dataSource').setEach('isRequested', false);
  48. this.set('filteringComplete', true);
  49. this.propertyDidChange('pageContent');
  50. }.observes('requestError'),
  51. /**
  52. * flag to toggle displaying selected hosts counter
  53. */
  54. showSelectedFilter: Em.computed.bool('selectedHosts.length'),
  55. /**
  56. * return filtered number of all content number information displayed on the page footer bar
  57. * @returns {String}
  58. */
  59. filteredContentInfo: Em.computed.i18nFormat('hosts.filters.filteredHostsInfo', 'filteredCount', 'totalCount'),
  60. /**
  61. * request latest data filtered by new parameters
  62. * called when trigger property(<code>refreshTriggers</code>) is changed
  63. */
  64. refresh: function () {
  65. App.loadTimer.start('Hosts Page');
  66. this.set('filteringComplete', false);
  67. var updaterMethodName = this.get('updater.tableUpdaterMap')[this.get('tableName')];
  68. this.get('updater')[updaterMethodName](this.updaterSuccessCb.bind(this), this.updaterErrorCb.bind(this), true);
  69. return true;
  70. },
  71. /**
  72. * reset filters value by column to which filter belongs
  73. * @param columns {Array}
  74. */
  75. resetFilterByColumns: function (columns) {
  76. var filterConditions = this.get('filterConditions');
  77. columns.forEach(function (iColumn) {
  78. var filterCondition = filterConditions.findProperty('iColumn', iColumn);
  79. if (filterCondition) {
  80. filterCondition.value = '';
  81. this.saveFilterConditions(filterCondition.iColumn, filterCondition.value, filterCondition.type, filterCondition.skipFilter);
  82. }
  83. }, this);
  84. },
  85. /**
  86. * Return pagination information displayed on the page
  87. * @type {String}
  88. */
  89. paginationInfo: Em.computed.i18nFormat('tableView.filters.paginationInfo', 'startIndex', 'endIndex', 'filteredCount'),
  90. paginationLeftClass: function () {
  91. if (this.get("startIndex") > 1 && this.get('filteringComplete')) {
  92. return "paginate_previous";
  93. }
  94. return "paginate_disabled_previous";
  95. }.property("startIndex", 'filteringComplete'),
  96. previousPage: function () {
  97. if (this.get('paginationLeftClass') === 'paginate_previous') {
  98. this._super();
  99. }
  100. },
  101. paginationRightClass: function () {
  102. if ((this.get("endIndex")) < this.get("filteredCount") && this.get('filteringComplete')) {
  103. return "paginate_next";
  104. }
  105. return "paginate_disabled_next";
  106. }.property("endIndex", 'filteredCount', 'filteringComplete'),
  107. nextPage: function () {
  108. if (this.get('paginationRightClass') === 'paginate_next') {
  109. this._super();
  110. }
  111. },
  112. /**
  113. * Select View with list of "rows-per-page" options
  114. * @type {Ember.View}
  115. */
  116. rowsPerPageSelectView: Em.Select.extend({
  117. content: ['10', '25', '50', '100'],
  118. attributeBindings: ['disabled'],
  119. disabled: true,
  120. disableView: function () {
  121. Em.run.next(this, function(){
  122. this.set('disabled', !this.get('parentView.filteringComplete'));
  123. });
  124. }.observes('parentView.filteringComplete'),
  125. change: function () {
  126. this.get('parentView').saveDisplayLength();
  127. var self = this;
  128. if (this.get('parentView.startIndex') !== 1 && this.get('parentView.startIndex') !== 0) {
  129. Ember.run.next(function () {
  130. self.set('parentView.startIndex', 1);
  131. });
  132. }
  133. }
  134. }),
  135. saveStartIndex: function () {
  136. this.set('controller.startIndex', this.get('startIndex'));
  137. }.observes('startIndex'),
  138. clearFiltersObs: function() {
  139. var self = this;
  140. Em.run.next(function() {
  141. if (self.get('controller.clearFilters')) {
  142. self.clearFilters();
  143. }
  144. });
  145. },
  146. /**
  147. * Restore filter properties in view
  148. */
  149. willInsertElement: function () {
  150. if (!this.get('controller.showFilterConditionsFirstLoad')) {
  151. var didClearedSomething = this.clearFilterConditionsFromLocalStorage();
  152. this.set('controller.filterChangeHappened', didClearedSomething);
  153. }
  154. this._super();
  155. this.set('startIndex', this.get('controller.startIndex'));
  156. this.addObserver('pageContent.@each.selected', this, this.selectedHostsObserver);
  157. },
  158. /**
  159. * stub for filter function in TableView
  160. */
  161. filter: function () {
  162. //Since filtering moved to server side, function is empty
  163. },
  164. didInsertElement: function() {
  165. this.addObserver('controller.clearFilters', this, this.clearFiltersObs);
  166. this.clearFiltersObs();
  167. this.addObserver('selectAllHosts', this, this.toggleAllHosts);
  168. this.addObserver('filteringComplete', this, this.overlayObserver);
  169. this.addObserver('startIndex', this, 'updatePagination');
  170. this.addObserver('displayLength', this, 'updatePagination');
  171. this.addObserver('filteredCount', this, this.updatePaging);
  172. },
  173. willDestroyElement: function () {
  174. $('.tooltip').remove();
  175. },
  176. onInitialLoad: function () {
  177. if (this.get('tableFilteringComplete')) {
  178. if (this.get('controller.filterChangeHappened')) {
  179. this.refresh();
  180. } else {
  181. // no refresh but still need to enable pagination controls
  182. this.propertyDidChange('filteringComplete');
  183. }
  184. }
  185. // reset filter change marker
  186. this.set('controller.filterChangeHappened', false);
  187. }.observes('tableFilteringComplete'),
  188. /**
  189. * Set <code>selected</code> property for each App.Host
  190. */
  191. toggleAllHosts: function() {
  192. this.get('pageContent').setEach('selected', this.get('selectAllHosts'));
  193. },
  194. /**
  195. * Trigger updating <code>selectedHostsCount</code> only 1 time
  196. */
  197. selectedHostsObserver: function() {
  198. Ember.run.once(this, 'updateCheckedFlags');
  199. },
  200. /**
  201. * Update <code>selectAllHosts</code> value
  202. */
  203. updateCheckedFlags: function() {
  204. this.removeObserver('selectAllHosts', this, this.toggleAllHosts);
  205. if (this.get('pageContent').length) {
  206. this.set('selectAllHosts', this.get('pageContent').everyProperty('selected', true));
  207. }
  208. else {
  209. this.set('selectAllHosts', false);
  210. }
  211. this.combineSelectedFilter();
  212. //10 is an index of selected column
  213. var controllerName = this.get('controller.name');
  214. App.db.setSelectedHosts(controllerName, this.get('selectedHosts'));
  215. this.addObserver('selectAllHosts', this, this.toggleAllHosts);
  216. },
  217. /**
  218. * combine selected hosts on page with selected hosts which are filtered out but added to cluster
  219. */
  220. combineSelectedFilter: function () {
  221. var controllerName = this.get('controller.name');
  222. var previouslySelectedHosts = App.db.getSelectedHosts(controllerName);
  223. var selectedHosts = [];
  224. var hostsOnPage = this.get('pageContent').mapProperty('hostName');
  225. selectedHosts = this.get('pageContent').filterProperty('selected').mapProperty('hostName');
  226. previouslySelectedHosts.forEach(function (hostName) {
  227. if (!hostsOnPage.contains(hostName)) {
  228. selectedHosts.push(hostName);
  229. }
  230. }, this);
  231. this.set('selectedHosts', selectedHosts);
  232. },
  233. /**
  234. * filter selected hosts
  235. */
  236. filterSelected: function() {
  237. //10 is an index of selected column
  238. this.updateFilter(10, this.get('selectedHosts'), 'multiple');
  239. },
  240. /**
  241. * Show spinner when filter/sorting request is in processing
  242. * @method overlayObserver
  243. */
  244. overlayObserver: function() {
  245. var $tbody = this.$('tbody'),
  246. $overlay = this.$('.table-overlay'),
  247. $spinner = $($overlay).find('.spinner');
  248. if (!this.get('filteringComplete')) {
  249. if (!$tbody) return;
  250. var tbodyPos = $tbody.position();
  251. if (!tbodyPos) return;
  252. $spinner.css('display', 'block');
  253. $overlay.css({
  254. top: tbodyPos.top + 1,
  255. left: tbodyPos.left + 1,
  256. width: $tbody.width() - 1,
  257. height: $tbody.height() - 1
  258. });
  259. }
  260. },
  261. /**
  262. * Clear selectedFilter
  263. * Set <code>selected</code> to false for each host
  264. */
  265. clearSelection: function() {
  266. this.get('pageContent').setEach('selected', false);
  267. this.set('selectAllHosts', false);
  268. App.db.setSelectedHosts(this.get('controller.name'), []);
  269. this.get('selectedHosts').clear();
  270. this.filterSelected();
  271. },
  272. sortView: sort.serverWrapperView,
  273. nameSort: sort.fieldView.extend({
  274. column: 1,
  275. name:'hostName',
  276. displayName: Em.I18n.t('common.name')
  277. }),
  278. ipSort: sort.fieldView.extend({
  279. column: 2,
  280. name:'ip',
  281. displayName: Em.I18n.t('common.ipAddress'),
  282. type: 'ip'
  283. }),
  284. rackSort: sort.fieldView.extend({
  285. column: 12,
  286. name:'rack',
  287. displayName: Em.I18n.t('common.rack'),
  288. type: 'rack'
  289. }),
  290. cpuSort: sort.fieldView.extend({
  291. column: 3,
  292. name:'cpu',
  293. displayName: Em.I18n.t('common.cores'),
  294. type: 'number'
  295. }),
  296. memorySort: sort.fieldView.extend({
  297. column: 4,
  298. name:'memoryFormatted',
  299. displayName: Em.I18n.t('common.ram'),
  300. type: 'number'
  301. }),
  302. diskUsageSort: sort.fieldView.extend({
  303. name:'diskUsage',
  304. displayName: Em.I18n.t('common.diskUsage')
  305. }),
  306. loadAvgSort: sort.fieldView.extend({
  307. column: 5,
  308. name:'loadAvg',
  309. displayName: Em.I18n.t('common.loadAvg'),
  310. type: 'number'
  311. }),
  312. HostView:Em.View.extend({
  313. content:null,
  314. tagName: 'tr',
  315. didInsertElement: function(){
  316. App.tooltip(this.$("[rel='HealthTooltip'], [rel='UsageTooltip'], [rel='ComponentsTooltip']"));
  317. },
  318. willDestroyElement: function() {
  319. this.$("[rel='HealthTooltip'], [rel='UsageTooltip'], [rel='ComponentsTooltip']").remove();
  320. },
  321. displayComponents: function () {
  322. if (this.get('hasNoComponents')) {
  323. return;
  324. }
  325. var header = Em.I18n.t('common.components'),
  326. hostName = this.get('content.hostName'),
  327. items = this.get('content.hostComponents').getEach('displayName');
  328. App.showHostsTableListPopup(header, hostName, items);
  329. },
  330. displayVersions: function () {
  331. if (this.get('hasSingleVersion')) {
  332. return;
  333. }
  334. var header = Em.I18n.t('common.versions'),
  335. hostName = this.get('content.hostName'),
  336. items = this.get('content.stackVersions').filterProperty('isVisible').map(function (stackVersion) {
  337. return {
  338. name: stackVersion.get('displayName'),
  339. status: App.format.role(stackVersion.get('status'), false)
  340. };
  341. });
  342. App.showHostsTableListPopup(header, hostName, items);
  343. },
  344. /**
  345. * Tooltip message for "Restart Required" icon
  346. * @returns {String}
  347. */
  348. restartRequiredComponentsMessage: function() {
  349. var restartRequiredComponents = this.get('content.componentsWithStaleConfigs');
  350. var count = this.get('content.componentsWithStaleConfigsCount');
  351. if (count <= 5) {
  352. var word = (count == 1) ? Em.I18n.t('common.component') : Em.I18n.t('common.components');
  353. return Em.I18n.t('hosts.table.restartComponents.withNames').format(restartRequiredComponents.getEach('displayName').join(', ')) + ' ' + word.toLowerCase();
  354. }
  355. return Em.I18n.t('hosts.table.restartComponents.withoutNames').format(count);
  356. }.property('content.componentsWithStaleConfigs'),
  357. /**
  358. * Tooltip message for "Maintenance" icon
  359. * @returns {String}
  360. */
  361. componentsInPassiveStateMessage: function() {
  362. var componentsInPassiveState = this.get('content.componentsInPassiveState');
  363. var count = this.get('content.componentsInPassiveStateCount');
  364. if (count <= 5) {
  365. return Em.I18n.t('hosts.table.componentsInPassiveState.withNames').format(componentsInPassiveState.getEach('displayName').join(', '));
  366. }
  367. return Em.I18n.t('hosts.table.componentsInPassiveState.withoutNames').format(count);
  368. }.property('content.componentsInPassiveState'),
  369. /**
  370. * true if host has only one repoversion
  371. * in this case expander in version column is hidden
  372. * @returns {Boolean}
  373. */
  374. hasSingleVersion: function() {
  375. return this.get('content.stackVersions').filterProperty('isVisible', true).length < 2;
  376. }.property('content.stackVersions.length'),
  377. /**
  378. * true if host has no components
  379. * @returns {Boolean}
  380. */
  381. hasNoComponents: Em.computed.empty('content.hostComponents'),
  382. /**
  383. /**
  384. * this version is always shown others hidden unless expander is open
  385. * host may have no stack versions
  386. * @returns {String}
  387. */
  388. currentVersion: function() {
  389. var currentRepoVersion = this.get('content.stackVersions').findProperty('isCurrent') || this.get('content.stackVersions').objectAt(0);
  390. return currentRepoVersion ? currentRepoVersion.get('displayName') : "";
  391. }.property('content.stackVersions'),
  392. /**
  393. * CSS value for disk usage bar
  394. * @returns {String}
  395. */
  396. usageStyle:function () {
  397. return "width:" + this.get('content.diskUsage') + "%";
  398. }.property('content.diskUsage')
  399. }),
  400. /**
  401. * Update <code>hostsCount</code> in every category
  402. */
  403. updateHostsCount: function() {
  404. var hostsCountMap = this.get('controller.hostsCountMap');
  405. this.get('categories').forEach(function(category) {
  406. var hostsCount = (category.get('healthStatus').trim() === "") ? hostsCountMap['TOTAL'] : hostsCountMap[category.get('healthStatus')];
  407. if (!Em.isNone(hostsCount)) {
  408. category.set('hostsCount', hostsCount);
  409. category.set('hasHosts', (hostsCount > 0));
  410. }
  411. }, this);
  412. }.observes('controller.hostsCountMap'),
  413. /**
  414. * Category view for all hosts
  415. * @type {Object}
  416. */
  417. //@TODO maybe should be separated to two types (basing on <code>isHealthStatus</code>)
  418. categoryObject: Em.Object.extend({
  419. /**
  420. * Text used with <code>hostsCount</code> in category label
  421. * @type {String}
  422. */
  423. value: null,
  424. /**
  425. * Is category based on host health status
  426. * @type {bool}
  427. */
  428. isHealthStatus: true,
  429. /**
  430. * host health status (used if <code>isHealthStatus</code> is true)
  431. * @type {String}
  432. */
  433. healthStatusValue: '',
  434. /**
  435. * Should category be displayed on the top of the hosts table
  436. * @type {bool}
  437. */
  438. isVisible: true,
  439. /**
  440. * Is category selected now
  441. * @type {bool}
  442. */
  443. isActive: false,
  444. /**
  445. * String with path that category should observe
  446. * @type {String}
  447. */
  448. observes: null,
  449. /**
  450. * CSS-class for span in the category-link (used if <code>isHealthStatus</code> is false)
  451. * @type {String}
  452. */
  453. class: null,
  454. /**
  455. * Associated column number
  456. * @type {Number}
  457. */
  458. column: null,
  459. /**
  460. * Type of filter value (string, number, boolean)
  461. * @type {String}
  462. */
  463. type: null,
  464. /**
  465. * @type {String|Number|bool}
  466. */
  467. filterValue: null,
  468. /**
  469. * <code>App.Host</code> property that should be used to calculate <code>hostsCount</code> (used if <code>isHealthStatus</code> is false)
  470. * @type {String}
  471. */
  472. hostProperty: null,
  473. /**
  474. * Number of host in current category
  475. * @type {Number}
  476. */
  477. hostsCount: 0,
  478. /**
  479. * Determine if category has hosts
  480. * @type {bool}
  481. */
  482. hasHosts: false,
  483. /**
  484. * Add "active" class for category span-wrapper if current category is selected
  485. * @type {String}
  486. */
  487. itemClass: Em.computed.ifThenElse('isActive', 'active', ''),
  488. /**
  489. * Text shown on the right of category icon
  490. * @type {String}
  491. */
  492. label: function () {
  493. return "%@ (%@)".fmt(this.get('value'), this.get('hostsCount'));
  494. }.property('hostsCount')
  495. }),
  496. /**
  497. * List of categories used to filter hosts
  498. * @type {Array}
  499. */
  500. categories: function () {
  501. var self = this;
  502. var category_mocks = require('data/host/categories');
  503. return category_mocks.map(function(category_mock) {
  504. return self.categoryObject.create(category_mock);
  505. });
  506. }.property(),
  507. /**
  508. * Category for <code>selected</code> property of each App.Host
  509. */
  510. selectedCategory: Em.computed.findBy('categories', 'selected', true),
  511. statusFilter: Em.View.extend({
  512. column: 0,
  513. categories: [],
  514. value: null,
  515. class: "",
  516. comboBoxLabel: function(){
  517. var selected = this.get('categories').findProperty('isActive');
  518. if (!this.get('value') || !selected) {
  519. return "%@ (%@)".fmt(Em.I18n.t('common.all'), this.get('parentView.totalCount'));
  520. } else {
  521. return "%@ (%@)".fmt(selected.get('value'), selected.get('hostsCount'))
  522. }
  523. }.property('value', 'parentView.totalCount'),
  524. /**
  525. * switch active category label
  526. */
  527. onCategoryChange: function () {
  528. this.get('categories').setEach('isActive', false);
  529. var selected = this.get('categories').findProperty('healthStatus', this.get('value'));
  530. selected.set('isActive', true);
  531. this.set('class', selected.get('class') + ' ' + selected.get('healthClass'));
  532. }.observes('value'),
  533. showClearFilter: function () {
  534. var mockEvent = {
  535. context: this.get('categories').findProperty('healthStatus', this.get('value'))
  536. };
  537. this.selectCategory(mockEvent);
  538. },
  539. /**
  540. * Trigger on Category click
  541. * @param {Object} event
  542. */
  543. selectCategory: function(event){
  544. var category = event.context;
  545. this.set('value', category.get('healthStatus'));
  546. this.get('parentView').resetFilterByColumns([0, 7, 8, 9]);
  547. if (category.get('isHealthStatus')) {
  548. var status = category.get('healthStatus');
  549. if (!status) {
  550. // only "All" option has no specific status, just refresh
  551. this.get('parentView').refresh();
  552. } else {
  553. this.get('parentView').updateFilter(0, status, 'string');
  554. }
  555. } else {
  556. this.get('parentView').updateFilter(category.get('column'), category.get('filterValue'), category.get('type'));
  557. }
  558. },
  559. /**
  560. * set value
  561. * @param {string} value
  562. */
  563. setValue: function (value) {
  564. this.set('value', value);
  565. },
  566. clearFilter: function() {
  567. this.get('categories').setEach('isActive', false);
  568. this.set('value', '');
  569. this.set('class', '');
  570. this.showClearFilter();
  571. }
  572. }),
  573. /**
  574. * associations between host property and column index
  575. * @type {Array}
  576. */
  577. colPropAssoc: Em.computed.alias('controller.colPropAssoc'),
  578. /**
  579. * Run <code>clearFilter</code> in the each child filterView
  580. */
  581. clearFilters: function() {
  582. // clean filters stored in-memory and local storage
  583. this.set('filterConditions', []);
  584. this.clearFilterConditionsFromLocalStorage();
  585. // force refresh
  586. this.refresh();
  587. }
  588. });