service_config_property.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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. /**
  21. * @class ServiceConfigProperty
  22. */
  23. App.ServiceConfigProperty = Em.Object.extend({
  24. name: '',
  25. displayName: '',
  26. /**
  27. * value that is shown on IU
  28. * and is changing by user
  29. * @type {String|null}
  30. */
  31. value: '',
  32. /**
  33. * value that is saved on cluster configs
  34. * and stored in /api/v1/clusters/{name}/configurations
  35. * @type {String|null}
  36. */
  37. savedValue: null,
  38. /**
  39. * value that is returned from server as recommended
  40. * or stored on stack
  41. * @type {String|null}
  42. */
  43. recommendedValue: null,
  44. /**
  45. * initial value of config. if value is saved it will be initial
  46. * otherwise first recommendedValue will be initial
  47. * @type {String|null}
  48. */
  49. initialValue: null,
  50. /**
  51. * value that is shown on IU
  52. * and is changing by user
  53. * @type {boolean}
  54. */
  55. isFinal: false,
  56. /**
  57. * value that is saved on cluster configs api
  58. * @type {boolean}
  59. */
  60. savedIsFinal: null,
  61. /**
  62. * value that is returned from server as recommended
  63. * or stored on stack
  64. * @type {boolean}
  65. */
  66. recommendedIsFinal: null,
  67. /**
  68. * @type {boolean}
  69. */
  70. supportsFinal: false,
  71. /**
  72. * Hint message to display in tooltip. Tooltip will be wrapped on question mark icon.
  73. * If value is <code>false</code> no tooltip and question mark icon.
  74. *
  75. * @type {boolean|string}
  76. */
  77. hintMessage: false,
  78. /**
  79. * Display label on the right side from input. In general used for checkbox only.
  80. *
  81. * @type {boolean}
  82. */
  83. rightSideLabel: false,
  84. /**
  85. * Text to be shown as placeholder
  86. * By default savedValue is shown as placeholder
  87. * @type {String}
  88. */
  89. placeholderText: '',
  90. /**
  91. * type of widget View
  92. * @type {string}
  93. * @default null
  94. */
  95. widgetType: null,
  96. /**
  97. * Placeholder used for configs with input type text
  98. */
  99. placeholder: Em.computed.firstNotBlank('placeholderText', 'savedValue'),
  100. retypedPassword: '',
  101. description: '',
  102. displayType: 'string', // string, digits, number, directories, custom
  103. unit: '',
  104. category: 'General',
  105. isRequired: true, // by default a config property is required
  106. isReconfigurable: true, // by default a config property is reconfigurable
  107. isEditable: true, // by default a config property is editable
  108. isNotEditable: Em.computed.not('isEditable'),
  109. hideFinalIcon: Em.computed.and('!isFinal', 'isNotEditable'),
  110. isVisible: true,
  111. isMock: false, // mock config created created only to displaying
  112. isRequiredByAgent: true, // Setting it to true implies property will be stored in configuration
  113. isSecureConfig: false,
  114. errorMessage: '',
  115. warnMessage: '',
  116. serviceConfig: null, // points to the parent App.ServiceConfig object
  117. filename: '',
  118. isOriginalSCP : true, // if true, then this is original SCP instance and its value is not overridden value.
  119. parentSCP: null, // This is the main SCP which is overridden by this. Set only when isOriginalSCP is false.
  120. overrides : null,
  121. overrideValues: [],
  122. group: null, // Contain group related to this property. Set only when isOriginalSCP is false.
  123. isUserProperty: null, // This property was added by user. Hence they get removal actions etc.
  124. isOverridable: true,
  125. compareConfigs: [],
  126. isComparison: false,
  127. hasCompareDiffs: false,
  128. showLabel: true,
  129. error: false,
  130. warn: false,
  131. previousValue: null, // cached value before changing config <code>value</code>
  132. /**
  133. * List of <code>isFinal</code>-values for overrides
  134. * Set in the controller
  135. * Should be empty array by default!
  136. * @type {boolean[]}
  137. */
  138. overrideIsFinalValues: [],
  139. /**
  140. * true if property has warning or error
  141. * @type {boolean}
  142. */
  143. hasIssues: function () {
  144. var originalSCPIssued = (this.get('errorMessage') + this.get('warnMessage')) !== "";
  145. var overridesIssue = false;
  146. (this.get('overrides') || []).forEach(function(override) {
  147. if (override.get('errorMessage') + override.get('warnMessage') !== "") {
  148. overridesIssue = true;
  149. return;
  150. }
  151. });
  152. return originalSCPIssued || overridesIssue;
  153. }.property('errorMessage', 'warnMessage', 'overrides.@each.warnMessage', 'overrides.@each.errorMessage'),
  154. index: null, //sequence number in category
  155. editDone: false, //Text field: on focusOut: true, on focusIn: false
  156. isNotSaved: false, // user property was added but not saved
  157. hasInitialValue: false, //if true then property value is defined and saved to server
  158. isHiddenByFilter: false, //if true then hide this property (filtered out)
  159. rowStyleClass: null, // CSS-Class to be applied on the row showing this config
  160. showAsTextBox: false,
  161. /**
  162. * config is invisible since wrapper section is hidden
  163. * @type {boolean}
  164. */
  165. hiddenBySection: false,
  166. /**
  167. * @type {boolean}
  168. */
  169. recommendedValueExists: function () {
  170. return !Em.isNone(this.get('recommendedValue')) && (this.get('recommendedValue') != "")
  171. && this.get('isRequiredByAgent') && !this.get('cantBeUndone');
  172. }.property('recommendedValue'),
  173. /**
  174. * Usage example see on <code>App.ServiceConfigRadioButtons.handleDBConnectionProperty()</code>
  175. *
  176. * @property {Ember.View} additionalView - custom view related to property
  177. **/
  178. additionalView: null,
  179. /**
  180. * If config is saved we should compare config <code>value<code> with <code>savedValue<code> to
  181. * find out if it was changed, but if config in not saved there is no <code>savedValue<code>, so
  182. * we should use <code>initialValue<code> instead.
  183. */
  184. isNotInitialValue: function() {
  185. if (Em.isNone(this.get('savedValue')) && !Em.isNone(this.get('initialValue'))) {
  186. var value = this.get('value'), initialValue = this.get('initialValue');
  187. if (this.get('stackConfigProperty.valueAttributes.type') == 'float') {
  188. initialValue = !Em.isNone(initialValue) ? '' + parseFloat(initialValue) : null;
  189. value = '' + parseFloat(value);
  190. }
  191. return initialValue !== value;
  192. }
  193. return false;
  194. }.property('initialValue', 'savedValue', 'value', 'stackConfigProperty.valueAttributes.type'),
  195. /**
  196. * Is property has active override with error
  197. */
  198. isValidOverride: function () {
  199. return this.get('overrides.length') ? !this.get('overrides').find(function(o) {
  200. return Em.get(o, 'isEditable') && Em.get(o, 'errorMessage');
  201. }) : true;
  202. }.property("overrides.@each.errorMessage"),
  203. /**
  204. * No override capabilities for fields which are not edtiable
  205. * and fields which represent master hosts.
  206. */
  207. isPropertyOverridable: function () {
  208. var overrideable = this.get('isOverridable');
  209. var editable = this.get('isEditable');
  210. var overrides = this.get('overrides');
  211. var dt = this.get('displayType');
  212. return overrideable && (editable || !overrides || !overrides.length) && (!["componentHost", "password"].contains(dt));
  213. }.property('isEditable', 'displayType', 'isOverridable', 'overrides.length'),
  214. isOverridden: function() {
  215. return (this.get('overrides') != null && this.get('overrides.length') > 0) || !this.get('isOriginalSCP');
  216. }.property('overrides', 'overrides.length', 'isOriginalSCP'),
  217. isOverrideChanged: function () {
  218. if (Em.isNone(this.get('overrides')) && this.get('overrideValues.length') === 0) return false;
  219. return JSON.stringify(this.get('overrides').mapProperty('isFinal')) !== JSON.stringify(this.get('overrideIsFinalValues'))
  220. || JSON.stringify(this.get('overrides').mapProperty('value')) !== JSON.stringify(this.get('overrideValues'));
  221. }.property('isOverridden', 'overrides.@each.isNotDefaultValue', 'overrideValues.length'),
  222. isRemovable: function() {
  223. return this.get('isEditable') && this.get('isRequiredByAgent') && !(this.get('overrides.length') > 0)
  224. && (this.get('isUserProperty') || !this.get('isOriginalSCP'));
  225. }.property('isUserProperty', 'isOriginalSCP', 'overrides.length', 'isRequiredByAgent'),
  226. init: function () {
  227. if (Em.isNone(this.get('value'))) {
  228. if (!Em.isNone(this.get('savedValue'))) {
  229. this.set('value', this.get('savedValue'));
  230. } else if (!Em.isNone(this.get('recommendedValue'))) {
  231. this.set('value', this.get('recommendedValue'));
  232. }
  233. }
  234. if(this.get("displayType") === "password") {
  235. this.set('retypedPassword', this.get('value'));
  236. this.set('recommendedValue', '');
  237. }
  238. this.set('initialValue', this.get('value'));
  239. this.updateDescription();
  240. },
  241. /**
  242. * Indicates when value is not the default value.
  243. * Returns false when there is no default value.
  244. */
  245. isNotDefaultValue: function () {
  246. var value = this.get('value');
  247. var savedValue = this.get('savedValue');
  248. var supportsFinal = this.get('supportsFinal');
  249. var isFinal = this.get('isFinal');
  250. var savedIsFinal = this.get('savedIsFinal');
  251. // ignore precision difference for configs with type of `float` which value may ends with 0
  252. // e.g. between 0.4 and 0.40
  253. if (this.get('stackConfigProperty') && this.get('stackConfigProperty.valueAttributes.type') == 'float') {
  254. savedValue = !Em.isNone(savedValue) ? '' + parseFloat(savedValue) : null;
  255. value = '' + parseFloat(value);
  256. }
  257. return (savedValue != null && value !== savedValue) || (supportsFinal && !Em.isNone(savedIsFinal) && isFinal !== savedIsFinal);
  258. }.property('value', 'savedValue', 'isEditable', 'isFinal', 'savedIsFinal'),
  259. /**
  260. * Don't show "Undo" for hosts on Installer Step7
  261. */
  262. cantBeUndone: Em.computed.existsIn('displayType', ["componentHost", "componentHosts", "radio button"]),
  263. isValid: Em.computed.equal('errorMessage', ''),
  264. viewClass: function () {
  265. switch (this.get('displayType')) {
  266. case 'checkbox':
  267. case 'boolean':
  268. if (this.get('dependentConfigPattern')) {
  269. return App.ServiceConfigCheckboxWithDependencies;
  270. } else {
  271. return App.ServiceConfigCheckbox;
  272. }
  273. case 'password':
  274. return App.ServiceConfigPasswordField;
  275. case 'combobox':
  276. return App.ServiceConfigComboBox;
  277. case 'radio button':
  278. return App.ServiceConfigRadioButtons;
  279. break;
  280. case 'directories':
  281. return App.ServiceConfigTextArea;
  282. break;
  283. case 'directory':
  284. return App.ServiceConfigTextField;
  285. break;
  286. case 'content':
  287. return App.ServiceConfigTextAreaContent;
  288. break;
  289. case 'multiLine':
  290. return App.ServiceConfigTextArea;
  291. break;
  292. case 'custom':
  293. return App.ServiceConfigBigTextArea;
  294. case 'componentHost':
  295. return App.ServiceConfigMasterHostView;
  296. case 'label':
  297. return App.ServiceConfigLabelView;
  298. case 'componentHosts':
  299. return App.ServiceConfigComponentHostsView;
  300. case 'supportTextConnection':
  301. return App.checkConnectionView;
  302. case 'capacityScheduler':
  303. return App.CapacitySceduler;
  304. default:
  305. if (this.get('unit')) {
  306. return App.ServiceConfigTextFieldWithUnit;
  307. } else {
  308. return App.ServiceConfigTextField;
  309. }
  310. }
  311. }.property('displayType'),
  312. validate: function () {
  313. var value = this.get('value');
  314. var supportsFinal = this.get('supportsFinal');
  315. var isFinal = this.get('isFinal');
  316. var valueRange = this.get('valueRange');
  317. var isError = false;
  318. var isWarn = false;
  319. if (typeof value === 'string' && value.length === 0) {
  320. if (this.get('isRequired') && this.get('widgetType') != 'test-db-connection') {
  321. this.set('errorMessage', 'This is required');
  322. isError = true;
  323. } else {
  324. return;
  325. }
  326. }
  327. if (!isError) {
  328. switch (this.get('displayType')) {
  329. case 'int':
  330. if (('' + value).trim().length === 0) {
  331. this.set('errorMessage', '');
  332. isError = false;
  333. return;
  334. }
  335. if (validator.isConfigValueLink(value)) {
  336. isError = false;
  337. } else if (!validator.isValidInt(value)) {
  338. this.set('errorMessage', 'Must contain digits only');
  339. isError = true;
  340. } else {
  341. if(valueRange){
  342. if(value < valueRange[0] || value > valueRange[1]){
  343. this.set('errorMessage', 'Must match the range');
  344. isError = true;
  345. }
  346. }
  347. }
  348. break;
  349. case 'float':
  350. if (validator.isConfigValueLink(value)) {
  351. isError = false;
  352. } else if (!validator.isValidFloat(value)) {
  353. this.set('errorMessage', 'Must be a valid number');
  354. isError = true;
  355. }
  356. break;
  357. case 'checkbox':
  358. break;
  359. case 'directories':
  360. case 'directory':
  361. if (this.get('configSupportHeterogeneous')) {
  362. if (!validator.isValidDataNodeDir(value)) {
  363. this.set('errorMessage', 'dir format is wrong, can be "[{storage type}]/{dir name}"');
  364. isError = true;
  365. }
  366. } else {
  367. if (!validator.isValidDir(value)) {
  368. this.set('errorMessage', 'Must be a slash or drive at the start, and must not contain white spaces');
  369. isError = true;
  370. }
  371. }
  372. if (!isError) {
  373. if (!validator.isAllowedDir(value)) {
  374. this.set('errorMessage', 'Can\'t start with "home(s)"');
  375. isError = true;
  376. } else {
  377. // Invalidate values which end with spaces.
  378. if (value !== ' ' && validator.isNotTrimmedRight(value)) {
  379. this.set('errorMessage', Em.I18n.t('form.validator.error.trailingSpaces'));
  380. isError = true;
  381. }
  382. }
  383. }
  384. break;
  385. case 'custom':
  386. break;
  387. case 'email':
  388. if (!validator.isValidEmail(value)) {
  389. this.set('errorMessage', 'Must be a valid email address');
  390. isError = true;
  391. }
  392. break;
  393. case 'supportTextConnection':
  394. case 'host':
  395. var connectionProperties = ['kdc_hosts'];
  396. if ((validator.isNotTrimmed(value) && connectionProperties.contains(this.get('name')) || validator.isNotTrimmed(value))) {
  397. this.set('errorMessage', Em.I18n.t('host.trimspacesValidation'));
  398. isError = true;
  399. }
  400. break;
  401. case 'password':
  402. // retypedPassword is set by the retypePasswordView child view of App.ServiceConfigPasswordField
  403. if (value !== this.get('retypedPassword')) {
  404. this.set('errorMessage', 'Passwords do not match');
  405. isError = true;
  406. }
  407. break;
  408. case 'user':
  409. case 'database':
  410. case 'db_user':
  411. if (!validator.isValidDbName(value)){
  412. this.set('errorMessage', 'Value is not valid');
  413. isError = true;
  414. }
  415. break;
  416. case 'multiLine':
  417. case 'content':
  418. default:
  419. if(this.get('name')=='javax.jdo.option.ConnectionURL' || this.get('name')=='oozie.service.JPAService.jdbc.url') {
  420. if (validator.isConfigValueLink(value)) {
  421. isError = false;
  422. } else if (validator.isNotTrimmed(value)) {
  423. this.set('errorMessage', Em.I18n.t('host.trimspacesValidation'));
  424. isError = true;
  425. }
  426. } else {
  427. // Avoid single space values which is work around for validate empty properties.
  428. // Invalidate values which end with spaces.
  429. if (value !== ' ' && validator.isNotTrimmedRight(value)) {
  430. this.set('errorMessage', Em.I18n.t('form.validator.error.trailingSpaces'));
  431. isError = true;
  432. }
  433. }
  434. break;
  435. }
  436. }
  437. if (!isWarn || isError) { // Errors get priority
  438. this.set('warnMessage', '');
  439. this.set('warn', false);
  440. } else {
  441. this.set('warn', true);
  442. }
  443. if (!isError) {
  444. this.set('errorMessage', '');
  445. this.set('error', false);
  446. } else {
  447. this.set('error', true);
  448. }
  449. }.observes('value', 'isFinal', 'retypedPassword'),
  450. /**
  451. * defines specific directory properties that
  452. * allows setting drive type before dir name
  453. * ex: [SSD]/usr/local/my_dir
  454. * @param config
  455. * @returns {*|Boolean|boolean}
  456. */
  457. configSupportHeterogeneous: function() {
  458. return ['directories', 'directory'].contains(this.get('displayType')) && ['dfs.datanode.data.dir'].contains(this.get('name'));
  459. }.property('displayType', 'name'),
  460. /**
  461. * Get override for selected group
  462. *
  463. * @param {String} groupName
  464. * @returns {App.ServiceConfigProperty|null}
  465. */
  466. getOverride: function(groupName) {
  467. Em.assert('Group name should be defined string', (typeof groupName === 'string') && groupName);
  468. if (this.get('overrides.length')) {
  469. return this.get('overrides').findProperty('group.name', groupName);
  470. }
  471. return null;
  472. },
  473. /**
  474. * Update description for `password`-config
  475. * Add extra-message about their comparison
  476. *
  477. * @method updateDescription
  478. */
  479. updateDescription: function () {
  480. var description = this.get('description');
  481. var displayType = this.get('displayType');
  482. var additionalDescription = Em.I18n.t('services.service.config.password.additionalDescription');
  483. if ('password' === displayType) {
  484. if (description) {
  485. if (!description.contains(additionalDescription)) {
  486. description += '<br />' + additionalDescription;
  487. }
  488. } else {
  489. description = additionalDescription;
  490. }
  491. }
  492. this.set('description', description);
  493. }
  494. });