config_widget_view.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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. require('views/common/controls_view');
  20. /**
  21. * Common view for config widgets
  22. * @type {Em.View}
  23. */
  24. App.ConfigWidgetView = Em.View.extend(App.SupportsDependentConfigs, App.WidgetPopoverSupport, App.ConvertUnitWidgetViewMixin, App.ServiceConfigCalculateId, {
  25. /**
  26. * @type {App.ConfigProperty}
  27. */
  28. config: null,
  29. /**
  30. * Determines if user hover on widget-view
  31. * @type {boolean}
  32. */
  33. isHover: false,
  34. /**
  35. * Determines if widget controls should be disabled
  36. * @type {boolean}
  37. */
  38. disabled: false,
  39. /**
  40. * Determines if widget is editable
  41. * It true - show all control-elements (undo, override, finalize etc) for widget
  42. * If false - no widget control-elements will be shown
  43. * Bound from template
  44. * @type {boolean}
  45. */
  46. canEdit: true,
  47. canNotEdit: Em.computed.not('canEdit'),
  48. /**
  49. * Config label class attribute. Displays validation status of config.
  50. * @type {string}
  51. */
  52. configLabelClass: '',
  53. /**
  54. * defines if widget should be shown
  55. * if not, text-field with config value or label "Undefined" should be shown
  56. * @type {boolean}
  57. */
  58. doNotShowWidget: Em.computed.or('isPropertyUndefined', 'config.showAsTextBox'),
  59. /**
  60. * defines if property in not defined in selected version
  61. * in this case "Undefined" should be shown instead of widget
  62. * @type {boolean}
  63. */
  64. isPropertyUndefined: Em.computed.equal('config.value', 'Undefined'),
  65. /**
  66. * Tab where current widget placed
  67. * Bound in the template
  68. * @type {App.Tab}
  69. */
  70. tab: null,
  71. /**
  72. * Section where current widget placed
  73. * Bound in the template
  74. * @type {App.Section}
  75. */
  76. section: null,
  77. /**
  78. * Subsection where current widget placed
  79. * Bound in the template
  80. * @type {App.SubSection}
  81. */
  82. subSection: null,
  83. /**
  84. * Determines if user can switch custom widget-view to the input-field
  85. * @type {boolean}
  86. */
  87. supportSwitchToTextBox: false,
  88. /**
  89. * @type {boolean}
  90. */
  91. showPencil: Em.computed.and('supportSwitchToTextBox', '!disabled'),
  92. /**
  93. * Alias to <code>config.isOriginalSCP</code>
  94. * Should be used in the templates
  95. * Don't use original <code>config.isOriginalSCP</code> in the widget-templates!!!
  96. * @type {boolean}
  97. */
  98. isOriginalSCPBinding: 'config.isOriginalSCP',
  99. /**
  100. * Check if property validation failed for overridden property in case when its value is equal to parent
  101. * config property.
  102. * @type {boolean}
  103. */
  104. isOverrideEqualityError: function() {
  105. return this.get('config.parentSCP') && this.get('config.parentSCP.value') == this.get('config.value');
  106. }.property('config.isValid'),
  107. /**
  108. * Alias to <code>config.isComparison</code>
  109. * Should be used in the templates
  110. * Don't use original <code>config.isComparison</code> in the widget-templates!!!
  111. * @type {boolean}
  112. */
  113. isComparisonBinding: 'config.isComparison',
  114. classNameBindings:['isComparison:compare-mode', 'config.overrides.length:overridden-property'],
  115. issueMessage: '',
  116. issueView: Em.View.extend({
  117. tagName: 'i',
  118. classNames: ['icon-warning-sign'],
  119. classNameBindings: ['issueIconClass'],
  120. attributeBindings:['issueMessage:data-original-title'],
  121. /**
  122. * @type {App.ServiceConfigProperty}
  123. */
  124. config: null,
  125. /**
  126. * @type {string}
  127. */
  128. issueIconClass: '',
  129. /**
  130. * @type {string}
  131. */
  132. issueMessage: '',
  133. didInsertElement: function() {
  134. App.tooltip($(this.get('element')));
  135. this.errorLevelObserver();
  136. this.addObserver('issuedConfig.warnMessage', this, this.errorLevelObserver);
  137. this.addObserver('issuedConfig.errorMessage', this, this.errorLevelObserver);
  138. this.addObserver('parentView.isPropertyUndefined', this, this.errorLevelObserver);
  139. },
  140. willDestroyElement: function() {
  141. $(this.get('element')).tooltip('destroy');
  142. this.removeObserver('issuedConfig.warnMessage', this, this.errorLevelObserver);
  143. this.removeObserver('issuedConfig.errorMessage', this, this.errorLevelObserver);
  144. this.removeObserver('parentView.isPropertyUndefined', this, this.errorLevelObserver);
  145. },
  146. /**
  147. *
  148. * @method errorLevelObserver
  149. */
  150. errorLevelObserver: function() {
  151. var messageLevel = this.get('issuedConfig.errorMessage') ? 'ERROR': this.get('issuedConfig.warnMessage') ? 'WARN' : 'NONE';
  152. if (this.get('parentView.isPropertyUndefined')) {
  153. messageLevel = 'NONE';
  154. }
  155. var issue = {
  156. ERROR: {
  157. iconClass: '',
  158. message: this.get('issuedConfig.errorMessage'),
  159. configLabelClass: 'text-error'
  160. },
  161. WARN: {
  162. iconClass: 'warning',
  163. message: this.get('issuedConfig.warnMessage'),
  164. configLabelClass: 'text-warning'
  165. },
  166. NONE: {
  167. iconClass: 'hide',
  168. message: false,
  169. configLabelClass: ''
  170. }
  171. }[messageLevel];
  172. this.set('parentView.configLabelClass', issue.configLabelClass);
  173. this.set('issueIconClass', issue.iconClass);
  174. this.set('issueMessage', issue.message);
  175. this.set('parentView.issueMessage', issue.message);
  176. },
  177. /**
  178. * @type {App.ServiceConfigProperty}
  179. */
  180. issuedConfig: function() {
  181. var config = this.get('config');
  182. // check editable override
  183. if (!config.get('isEditable') && config.get('isOriginalSCP') && config.get('overrides.length') && config.get('overrides').someProperty('isEditable', true)) {
  184. config = config.get('overrides').findProperty('isEditable', true);
  185. } else if (config.get('isOriginalSCP') && config.get('isEditable')) {
  186. // use original config if it is not valid
  187. if (!config.get('isValid')) {
  188. return config;
  189. // scan overrides for non valid values and use it
  190. } else if (config.get('overrides.length') && config.get('overrides').someProperty('isValid', false)) {
  191. return config.get('overrides').findProperty('isValid', false);
  192. }
  193. }
  194. return config;
  195. }.property('config.isEditable', 'config.overrides.length')
  196. }),
  197. /**
  198. * Config name to display.
  199. * @type {String}
  200. */
  201. configLabel: Em.computed.firstNotBlank('config.stackConfigProperty.displayName', 'config.displayName', 'config.name'),
  202. /**
  203. * Error message computed in config property model
  204. * @type {String}
  205. */
  206. configErrorMessageBinding: 'config.errorMessage',
  207. /**
  208. * Determines if config-value was changed
  209. * @type {boolean}
  210. */
  211. valueIsChanged: function () {
  212. return !Em.isNone(this.get('config.savedValue')) && this.get('config.value') != this.get('config.savedValue');
  213. }.property('config.value', 'config.savedValue'),
  214. /**
  215. * Enable/disable widget state
  216. * @method toggleWidgetState
  217. */
  218. toggleWidgetState: function () {
  219. this.set('disabled', !this.get('config.isEditable'));
  220. }.observes('config.isEditable'),
  221. /**
  222. * Reset config-value to its default
  223. * @method restoreValue
  224. */
  225. restoreValue: function () {
  226. var self = this;
  227. this.set('config.value', this.get('config.savedValue'));
  228. this.sendRequestRorDependentConfigs(this.get('config')).done(function() {
  229. self.restoreDependentConfigs(self.get('config'));
  230. });
  231. if (this.get('config.supportsFinal')) {
  232. this.get('config').set('isFinal', this.get('config.savedIsFinal'));
  233. }
  234. Em.$('body > .tooltip').remove();
  235. },
  236. /**
  237. * set <code>recommendedValue<code> to config
  238. * and send request to change dependent configs
  239. * @method setRecommendedValue
  240. */
  241. setRecommendedValue: function() {
  242. var self = this;
  243. this.set('config.value', this.get('config.recommendedValue'));
  244. this.sendRequestRorDependentConfigs(this.get('config')).done(function() {
  245. if (self.get('config.value') === self.get('config.savedValue')) {
  246. self.restoreDependentConfigs(self.get('config'));
  247. }
  248. });
  249. if (this.get('config.supportsFinal')) {
  250. this.get('config').set('isFinal', this.get('config.recommendedIsFinal'));
  251. }
  252. Em.$('body > .tooltip').remove();
  253. },
  254. /**
  255. * Determines if override is allowed for <code>config</code>
  256. * @type {boolean}
  257. */
  258. overrideAllowed: function () {
  259. var config = this.get('config');
  260. if (!config) return false;
  261. return config.get('isOriginalSCP') && config.get('isPropertyOverridable') && !this.get('config.isComparison');
  262. }.property('config.isOriginalSCP', 'config.isPropertyOverridable', 'config.isComparison'),
  263. /**
  264. * Determines if undo is allowed for <code>config</code>
  265. * @type {boolean}
  266. */
  267. undoAllowed: function () {
  268. var config = this.get('config');
  269. if (!config) return false;
  270. if (!this.get('isOriginalSCP') || this.get('disabled')) return false;
  271. return !config.get('cantBeUndone') && config.get('isNotDefaultValue');
  272. }.property('config.cantBeUndone', 'config.isNotDefaultValue', 'isOriginalSCP', 'disabled'),
  273. /**
  274. * Determines if "final"-button should be shown
  275. * @type {boolean}
  276. */
  277. showFinalConfig: function () {
  278. var config = this.get('config');
  279. return config.get('isFinal') || (!config.get('isNotEditable') && this.get('isHover'));
  280. }.property('config.isFinal', 'config.isNotEditable', 'isHover'),
  281. /**
  282. *
  283. * @param {{context: App.ServiceConfigProperty}} event
  284. * @method toggleFinalFlag
  285. */
  286. toggleFinalFlag: function (event) {
  287. var configProperty = event.context;
  288. if (configProperty.get('isNotEditable')) {
  289. return;
  290. }
  291. configProperty.toggleProperty('isFinal');
  292. },
  293. /**
  294. * sync widget value with config value when dependent properties
  295. * have been loaded or changed
  296. * @method syncValueWithConfig
  297. */
  298. syncValueWithConfig: function() {
  299. this.setValue(this.get('config.value'));
  300. }.observes('controller.recommendationTimeStamp'),
  301. /**
  302. * defines if config has same config group as selected
  303. * @type {boolean}
  304. */
  305. referToSelectedGroup: function() {
  306. return this.get('controller.selectedConfigGroup.isDefault') && this.get('config.group') === null
  307. || this.get('controller.selectedConfigGroup.name') === this.get('config.group.name');
  308. }.property('controller.selectedConfigGroup.name', 'controller.selectedConfigGroup.isDefault'),
  309. didInsertElement: function () {
  310. App.tooltip(this.$('[data-toggle=tooltip]'), {placement: 'top'});
  311. App.tooltip($(this.get('element')).find('span'));
  312. var self = this;
  313. var element = this.$();
  314. if (element) {
  315. element.hover(function() {
  316. self.set('isHover', true);
  317. }, function() {
  318. self.set('isHover', false);
  319. });
  320. }
  321. this.initIncompatibleWidgetAsTextBox();
  322. },
  323. willInsertElement: function() {
  324. var configConditions = this.get('config.configConditions');
  325. var configAction = this.get('config.configAction');
  326. if (configConditions && configConditions.length) {
  327. this.configValueObserverForAttributes();
  328. //Add Observer to configCondition that depends on another config value
  329. var isConditionConfigDependent = configConditions.filterProperty('resource', 'config').length;
  330. if (isConditionConfigDependent) {
  331. this.addObserver('config.value', this, this.configValueObserverForAttributes);
  332. }
  333. if (configAction) {
  334. this.addObserver('config.value', this, this.configValueObserverForAction);
  335. }
  336. }
  337. },
  338. willDestroyElement: function() {
  339. this.$('[data-toggle=tooltip]').tooltip('destroy');
  340. $(this.get('element')).find('span').tooltip('destroy');
  341. if (this.get('config.configConditions')) {
  342. this.removeObserver('config.value', this, this.configValueObserverForAttributes);
  343. }
  344. if (this.get('config.configAction')) {
  345. this.removeObserver('config.value', this, this.configValueObserverForAction);
  346. }
  347. },
  348. configValueObserverForAttributes: function() {
  349. var configConditions = this.get('config.configConditions');
  350. var serviceName = this.get('config.serviceName');
  351. var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName',serviceName).get('configs');
  352. var isConditionTrue;
  353. configConditions.forEach(function(configCondition){
  354. var ifStatement = configCondition.get("if");
  355. if (configCondition.get("resource") === 'config') {
  356. isConditionTrue = App.configTheme.calculateConfigCondition(ifStatement, serviceConfigs);
  357. if (configCondition.get("type") === 'subsection' || configCondition.get("type") === 'subsectionTab') {
  358. this.changeSubsectionAttribute(configCondition, isConditionTrue);
  359. } else {
  360. this.changeConfigAttribute(configCondition, isConditionTrue);
  361. }
  362. } else if (configCondition.get("resource") === 'service') {
  363. var service = App.Service.find().findProperty('serviceName', ifStatement);
  364. var serviceName;
  365. if (service) {
  366. isConditionTrue = true;
  367. } else if (!service && this.get('controller.allSelectedServiceNames') && this.get('controller.allSelectedServiceNames').length) {
  368. isConditionTrue = this.get('controller.allSelectedServiceNames').contains(ifStatement);
  369. } else {
  370. isConditionTrue = false;
  371. }
  372. this.changeConfigAttribute(configCondition, isConditionTrue);
  373. }
  374. }, this);
  375. },
  376. /**
  377. * This is an observer that is fired when a value of a config that is suppose to add/delete a component is changed
  378. * @private
  379. * @method {configValueObserverForAction}
  380. */
  381. configValueObserverForAction: function() {
  382. var assignMasterOnStep7Controller = App.router.get('assignMasterOnStep7Controller');
  383. var configAction = this.get('config.configAction');
  384. var serviceName = this.get('config.serviceName');
  385. var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName', serviceName).get('configs');
  386. this.set('config.configActionComponent', null);
  387. var hostComponent = {
  388. componentName:configAction.get('componentName'),
  389. isClient: '',
  390. hostName: '',
  391. action: ''
  392. };
  393. var hostComponentObj = App.HostComponent.find().findProperty('componentName', hostComponent.componentName);
  394. var stackComponentObj = App.StackServiceComponent.find(configAction.get('componentName'));
  395. if (stackComponentObj) {
  396. hostComponent.isClient = stackComponentObj.get('isClient');
  397. }
  398. if (hostComponentObj) {
  399. hostComponent.hostName = hostComponentObj.get('hostName');
  400. }
  401. var isConditionTrue = App.configTheme.calculateConfigCondition(configAction.get("if"), serviceConfigs);
  402. var action = isConditionTrue ? configAction.get("then") : configAction.get("else");
  403. hostComponent.action = action;
  404. switch (action) {
  405. case 'add':
  406. // Disable save button until a host is selected
  407. var isComponentToBeInstalled = !App.HostComponent.find().someProperty('componentName', hostComponent.componentName);
  408. if (isComponentToBeInstalled) {
  409. this.set('controller.saveInProgress', true);
  410. assignMasterOnStep7Controller.execute(this, 'ADD', hostComponent);
  411. } else {
  412. assignMasterOnStep7Controller.clearComponentsToBeDeleted(hostComponent.componentName);
  413. }
  414. break;
  415. case 'delete':
  416. assignMasterOnStep7Controller.execute(this, 'DELETE', hostComponent);
  417. break;
  418. }
  419. },
  420. /**
  421. *
  422. * @param configCondition {App.ThemeCondition}
  423. * @param isConditionTrue {boolean}
  424. */
  425. changeConfigAttribute: function(configCondition, isConditionTrue) {
  426. var conditionalConfigName = configCondition.get("configName");
  427. var conditionalConfigFileName = configCondition.get("fileName");
  428. var serviceName = this.get('config.serviceName');
  429. var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName',serviceName).get('configs');
  430. var action = isConditionTrue ? configCondition.get("then") : configCondition.get("else");
  431. var valueAttributes = action.property_value_attributes;
  432. for (var key in valueAttributes) {
  433. if (valueAttributes.hasOwnProperty(key)) {
  434. var valueAttribute = App.StackConfigValAttributesMap[key] || key;
  435. var conditionalConfig = serviceConfigs.filterProperty('filename',conditionalConfigFileName).findProperty('name', conditionalConfigName);
  436. if (conditionalConfig) {
  437. conditionalConfig.set(valueAttribute, valueAttributes[key]);
  438. }
  439. }
  440. }
  441. },
  442. /**
  443. *
  444. * @param subsectionCondition {App.ThemeCondition}
  445. * @param isConditionTrue {boolean}
  446. */
  447. changeSubsectionAttribute: function(subsectionCondition, isConditionTrue) {
  448. var subsectionConditionName = subsectionCondition.get('name');
  449. var action = isConditionTrue ? subsectionCondition.get("then") : subsectionCondition.get("else");
  450. if (subsectionCondition.get('id')) {
  451. var valueAttributes = action.property_value_attributes;
  452. if (valueAttributes && !Em.none(valueAttributes['visible'])) {
  453. var themeResource;
  454. if (subsectionCondition.get('type') === 'subsection') {
  455. themeResource = App.SubSection.find().findProperty('name', subsectionConditionName);
  456. } else if (subsectionCondition.get('type') === 'subsectionTab') {
  457. themeResource = App.SubSectionTab.find().findProperty('name', subsectionConditionName);
  458. }
  459. themeResource.set('isHiddenByConfig', !valueAttributes['visible']);
  460. themeResource.get('configs').setEach('hiddenBySection', !valueAttributes['visible']);
  461. }
  462. }
  463. },
  464. /**
  465. * set widget value same as config value
  466. * useful for widgets that work with intermediate config value, not original
  467. * for now used in slider widget
  468. * @abstract
  469. */
  470. setValue: Em.K,
  471. /**
  472. * Config group bound property. Needed for correct render process in template.
  473. *
  474. * @returns {App.ConfigGroup|Boolean}
  475. */
  476. configGroup: function() {
  477. return !this.get('config.group') || this.get('config.group.isDefault') ? false : this.get('config.group');
  478. }.property('config.group.name'),
  479. /**
  480. * switcher to display config as widget or text field
  481. * @method toggleWidgetView
  482. */
  483. toggleWidgetView: function() {
  484. if (!this.get('isWidgetViewAllowed')) {
  485. return false;
  486. }
  487. if (this.get('config.showAsTextBox')) {
  488. this.textBoxToWidget();
  489. } else {
  490. this.widgetToTextBox();
  491. }
  492. },
  493. /**
  494. * switch display of config to text field
  495. * @method widgetToTextBox
  496. */
  497. widgetToTextBox: function() {
  498. this.set("config.showAsTextBox", true);
  499. },
  500. /**
  501. * switch display of config to widget
  502. * @method textBoxToWidget
  503. */
  504. textBoxToWidget: function() {
  505. if (this.isValueCompatibleWithWidget()) {
  506. this.setValue(this.get('config.value'));
  507. this.set("config.showAsTextBox", false);
  508. }
  509. },
  510. /**
  511. * check if config value can be converted to config widget value
  512. * IMPORTANT! Each config-widget that override this method should use <code>updateWarningsForCompatibilityWithWidget</code>
  513. * @returns {boolean}
  514. */
  515. isValueCompatibleWithWidget: function() {
  516. return (this.get('isOverrideEqualityError') && !this.get('config.isValid')) || this.get('config.isValid') || !this.get('supportSwitchToTextBox');
  517. },
  518. /**
  519. * Initialize widget with incompatible value as textbox
  520. */
  521. initIncompatibleWidgetAsTextBox : function() {
  522. this.get('config').set('showAsTextBox', !this.isValueCompatibleWithWidget());
  523. },
  524. /**
  525. * Returns <code>true</code> if raw value can be used by widget or widget view is activated.
  526. * @returns {Boolean}
  527. */
  528. isWidgetViewAllowed: function() {
  529. if (!this.get('config.showAsTextBox')) {
  530. return true;
  531. }
  532. return this.isValueCompatibleWithWidget();
  533. }.property('config.value', 'config.isFinal', 'config.showAsTextBox'),
  534. /**
  535. * Used in <code>isValueCompatibleWithWidget</code>
  536. * Updates issue-parameters if config is in the raw-mode
  537. * @param {string} message empty string if value compatible with widget, error-message if value isn't compatible with widget
  538. * @method updateWarningsForCompatibilityWithWidget
  539. */
  540. updateWarningsForCompatibilityWithWidget: function (message) {
  541. this.setProperties({
  542. warnMessage: message,
  543. 'config.warnMessage': message,
  544. issueMessage: message,
  545. configLabelClass: message ? 'text-warning' : ''
  546. });
  547. }
  548. });