/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var App = require('app'); var validator = require('utils/validator'); var numericUtils = require('utils/number_utils'); App.AlertConfigProperty = Ember.Object.extend({ /** * label to be shown for config property * @type {String} */ label: '', /** * PORT|METRIC|AGGREGATE * @type {String} */ type: '', /** * config property value * @type {*} */ value: null, /** * property value cache to realise undo function * @type {*} */ previousValue: null, /** * define either input is disabled or enabled * @type {Boolean} */ isDisabled: false, /** * options that Select list will have * @type {Array} */ options: [], /** * input displayType * one of 'textFields', 'textArea', 'select' or 'threshold' * @type {String} */ displayType: '', /** * unit to be shown with value * @type {String} */ unit: null, /** * space separated list of css class names to use * @type {String} */ classNames: '', /** * define whether row with property should be shifted right * @type {Boolean} */ isShifted: false, /** * name or names of properties related to config * may be either string for one property or array of strings for multiple properties * if this property is array, then apiFormattedValue should also be an array * example: apiProperty[0] relates to apiFormattedValue[0] * @type {String|Array} */ apiProperty: '', /** * for some metrics properties may be set true or false * depending on what property is related to (JMX or Ganglia) */ isJMXMetric: null, /** * define place to show label * if true - label is shown before input * if false - label is shown after input * @type {Boolean} */ isPreLabeled: Em.computed.notExistsIn('displayType', ['radioButton']), /** * value converted to appropriate format for sending to server * should be computed property * should be defined in child class * @type {*} */ apiFormattedValue: Em.computed.alias('value'), /** * define if property was changed by user * @type {Boolean} */ wasChanged: function () { return this.get('previousValue') !== null && this.get('value') !== this.get('previousValue'); }.property('value', 'previousValue'), /** * view class according to displayType * @type {Em.View} */ viewClass: function () { var displayType = this.get('displayType'); switch (displayType) { case 'textField': return App.AlertConfigTextFieldView; case 'textArea': return App.AlertConfigTextAreaView; case 'select': return App.AlertConfigSelectView; case 'threshold': return App.AlertConfigThresholdView; case 'radioButton': return App.AlertConfigRadioButtonView; case 'parameter': return App.AlertConfigParameterView; default: } }.property('displayType'), /** * Define whether property is valid * Computed property * Should be defined in child class * @type {Boolean} */ isValid: function () { return true; }.property() }); App.AlertConfigProperties = { AlertName: App.AlertConfigProperty.extend({ name: 'alert_name', label: 'Alert Name', displayType: 'textField', classNames: 'alert-text-input', apiProperty: 'name' }), AlertNameSelected: App.AlertConfigProperty.extend({ name: 'alert_name', label: 'Alert Name', displayType: 'select', apiProperty: 'name' }), ServiceAlertType: App.AlertConfigProperty.extend({ name: 'alert_type_service', label: 'Service Alert Definition', displayType: 'radioButton', group: 'alert_type' }), HostAlertType: App.AlertConfigProperty.extend({ name: 'alert_type_host', label: 'Host Alert Definition', displayType: 'radioButton', group: 'alert_type' }), Service: App.AlertConfigProperty.extend({ name: 'service', label: 'Service', displayType: 'select', apiProperty: 'service_name', apiFormattedValue: function () { return App.StackService.find().findProperty('displayName', this.get('value')).get('serviceName'); }.property('value') }), Component: App.AlertConfigProperty.extend({ name: 'component', label: 'Component', displayType: 'select', apiProperty: 'component_name', apiFormattedValue: function () { return App.StackServiceComponent.find().findProperty('displayName', this.get('value')).get('componentName'); }.property('value') }), Scope: App.AlertConfigProperty.extend({ name: 'scope', label: 'Scope', displayType: 'select', apiProperty: 'scope', apiFormattedValue: function () { return this.get('value').toUpperCase(); }.property('value') }), Description: App.AlertConfigProperty.extend({ name: 'description', label: 'Description', displayType: 'textArea', classNames: 'alert-config-text-area', // todo: check value after API will be provided apiProperty: 'description' }), Interval: App.AlertConfigProperty.extend({ name: 'interval', label: 'Check Interval', displayType: 'textField', unit: 'Minute', classNames: 'alert-interval-input', apiProperty: 'interval', isValid: function () { var value = this.get('value'); if (!value) return false; return String(value) === String(parseInt(value, 10)) && value >= 1; }.property('value') }), /** * Implements threshold * Main difference from other alertConfigProperties: * it has two editable parts - value and text * User may configure it to edit only one of them (use flags showInputForValue and showInputForText) * This flags also determines update value and text in the API-request or not (see App.AlertConfigProperties.Thresholds for more examples) * * @type {App.AlertConfigProperty.Threshold} */ Threshold: App.AlertConfigProperty.extend({ name: 'threshold', /** * Property text cache to realise undo function * @type {*} */ previousText: null, label: '', /** * OK|WARNING|CRITICAL * @type {string} */ badge: '', /** * threshold-value * @type {string} */ value: '', /** * Type of value. This will be a fixed set of types (like %). */ valueMetric: null, /** * Value actually displayed to the user. This value is transformed * based on the limited types of 'valueMetric's. Mappings from * 'value' to 'displayValue' is handled by observers. */ displayValue: '', /** * threshold-text * @type {string} */ text: '', displayType: 'threshold', classNames: 'alert-thresholds-input', apiProperty: [], init: function () { this.set('displayValue', this.getNewValue()); this._super(); }, /** * @type {string[]} */ apiFormattedValue: function () { var ret = []; if (this.get('showInputForValue')) { ret.push(this.get('value')); } if (this.get('showInputForText')) { ret.push(this.get('text')); } return ret; }.property('value', 'text', 'showInputForValue', 'showInputForText'), /** * Determines if value should be visible and editable (if not - won't update API-value) * @type {bool} */ showInputForValue: true, /** * Determines if text should be visible and editable (if not - won't update API-text) * @type {bool} */ showInputForText: true, /** * Custom css-class for different badges * type {string} */ badgeCssClass: Em.computed.format('alert-state-{0}', 'badge'), /** * Determines if value or text were changed * @type {bool} */ wasChanged: function () { return this.get('previousValue') !== null && this.get('value') !== this.get('previousValue') || this.get('previousText') !== null && this.get('text') !== this.get('previousText'); }.property('value', 'text', 'previousValue', 'previousText'), /** * May be redefined in child-models, mixins etc * @method getValue * @returns {string} */ getNewValue: function () { return this.get('value'); }, valueWasChanged: function () { var displayValue = this.get('displayValue'); var newDisplayValue = this.getNewValue(); if (newDisplayValue !== displayValue && !(isNaN(newDisplayValue) ||isNaN(displayValue))) { this.set('displayValue', newDisplayValue); } }.observes('value'), /** * May be redefined in child-models, mixins etc * @method getDisplayValue * @returns {string} */ getNewDisplayValue: function () { return this.get('displayValue'); }, displayValueWasChanged: function () { var value = this.get('value'); var newValue = this.getNewDisplayValue(); if (newValue !== value && !(isNaN(newValue) ||isNaN(value))) { this.set('value', newValue); } }.observes('displayValue'), /** * Check if displayValue is valid float number * If this value isn't shown (see showInputForValue), result is always true * @return {boolean} */ isValid: function () { if (!this.get('showInputForValue')) { return true; } var value = this.get('displayValue'); if (Em.isNone(value)) { return false; } value = ('' + value).trim(); //only allow 1/10th of a second if (numericUtils.getFloatDecimals(value) > 1) { return false; } return validator.isValidFloat(value); }.property('displayValue', 'showInputForValue') }), URI: App.AlertConfigProperty.extend({ name: 'uri', label: 'URI', displayType: 'textField', classNames: 'alert-text-input', apiProperty: 'source.uri' }), URIExtended: App.AlertConfigProperty.extend({ name: 'uri', label: 'URI', displayType: 'textArea', classNames: 'alert-config-text-area', apiProperty: 'source.uri', apiFormattedValue: function () { var result = {}; try { result = JSON.parse(this.get('value')); } catch (e) { console.error('Wrong format of URI'); } return result; }.property('value') }), ConnectionTimeout: App.AlertConfigProperty.extend({ name: 'connection_timeout', label: 'Connection Timeout', displayType: 'textField', classNames: 'alert-connection-timeout', apiProperty: 'source.uri.connection_timeout', isValid: function () { var value = this.get('value'); return numericUtils.isPositiveNumber(value); }.property('value') }), DefaultPort: App.AlertConfigProperty.extend({ name: 'default_port', label: 'Default Port', displayType: 'textField', classNames: 'alert-port-input', apiProperty: 'source.default_port' }), Path: App.AlertConfigProperty.extend({ name: 'path', label: 'Path', displayType: 'textField', classNames: 'alert-text-input', apiProperty: 'source.path' }), Metrics: App.AlertConfigProperty.extend({ name: 'metrics', label: 'JMX/Ganglia Metrics', displayType: 'textArea', classNames: 'alert-config-text-area', apiProperty: Em.computed.ifThenElse('isJMXMetric', 'source.jmx.property_list', 'source.ganglia.property_list'), apiFormattedValue: function () { return this.get('value').split(',\n'); }.property('value') }), FormatString: App.AlertConfigProperty.extend({ name: 'metrics_string', label: 'Format String', displayType: 'textArea', classNames: 'alert-config-text-area', apiProperty: Em.computed.ifThenElse('isJMXMetric', 'source.jmx.value', 'source.ganglia.value') }), Parameter: App.AlertConfigProperty.extend({ name: 'parameter', displayType: 'parameter', badge: Em.computed.alias('threshold'), thresholdNotExists: Em.computed.empty('threshold'), /** * Custom css-class for different badges * type {string} */ badgeCssClass: Em.computed.format('alert-state-{0}', 'badge'), }) }; App.AlertConfigProperties.Parameters = { StringMixin: Em.Mixin.create({ isValid: function () { var value = this.get('value'); return String(value).trim() !== ''; }.property('value') }), NumericMixin: Em.Mixin.create({ isValid: function () { var value = this.get('value'); if (!value) { return false; } value = ('' + value).trim(); if (!numericUtils.isPositiveNumber(value)) { return false; } value = parseFloat(value); return !isNaN(value); }.property('value') }), PercentageMixin: Em.Mixin.create({ isValid: function () { var value = this.get('value'); if (!value) { return false; } if (!validator.isValidFloat(value) || !numericUtils.isPositiveNumber(value)) { return false; } value = String(value).trim(); value = parseFloat(value); return !isNaN(value) && value > 0 && value <= 100; }.property('value') }) }; App.AlertConfigProperties.Thresholds = { OkThreshold: App.AlertConfigProperties.Threshold.extend({ badge: 'OK', name: 'ok_threshold', apiProperty: function () { var ret = []; if (this.get('showInputForValue')) { ret.push('source.reporting.ok.value'); } if (this.get('showInputForText')) { ret.push('source.reporting.ok.text'); } return ret; }.property('showInputForValue', 'showInputForText') }), WarningThreshold: App.AlertConfigProperties.Threshold.extend({ badge: 'WARNING', name: 'warning_threshold', apiProperty: function () { var ret = []; if (this.get('showInputForValue')) { ret.push('source.reporting.warning.value'); } if (this.get('showInputForText')) { ret.push('source.reporting.warning.text'); } return ret; }.property('showInputForValue', 'showInputForText') }), CriticalThreshold: App.AlertConfigProperties.Threshold.extend({ badge: 'CRITICAL', name: 'critical_threshold', apiProperty: function () { var ret = []; if (this.get('showInputForValue')) { ret.push('source.reporting.critical.value'); } if (this.get('showInputForText')) { ret.push('source.reporting.critical.text'); } return ret; }.property('showInputForValue', 'showInputForText') }), /** * Mixin for App.AlertConfigProperties.Threshold * Used to validate values in percentage range (0..1] * @type {Em.Mixin} */ PercentageMixin: Em.Mixin.create({ isValid: function () { var value = this.get('displayValue'); if (!value) { return false; } value = ('' + value).trim(); if (numericUtils.getFloatDecimals(value)) { return false; } value = parseFloat(value); //do not allow float values if (parseInt(value, 10) !== value) { return false; } return this.get('showInputForValue') ? !isNaN(value) && value > 0 && value <= 100 : true; }.property('displayValue', 'showInputForValue'), /** * Return value * 100 * @returns {string} */ getNewValue: function () { var value = this.get('value'); return value && !isNaN(value) ? Number(value) * 100 + '' : value; }, /** * Return displayValue / 100 * @returns {string} */ getNewDisplayValue: function () { var displayValue = this.get('displayValue'); return displayValue && !isNaN(displayValue) ? Number(displayValue) / 100 + '' : displayValue; } }), /** * Mixin for App.AlertConfigProperties.Threshold * Used to validate values that should be greater than 0 * @type {Em.Mixin} */ PositiveMixin: Em.Mixin.create({ isValid: function () { if (!this.get('showInputForValue')) { return true; } var value = this.get('displayValue'); if (!value) { return false; } //only allow 1/10th of a second if (numericUtils.getFloatDecimals(value) > 1) { return false; } value = ('' + value).trim(); value = parseFloat(value); return !isNaN(value) && value > 0; }.property('displayValue', 'showInputForValue') }) };