AMBARI-12845. Alert Instances overriding issue (onehiporenko)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/f1917d27 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/f1917d27 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/f1917d27 Branch: refs/heads/branch-2.1 Commit: f1917d27de4ce0916929e64b873c4fffcb8748b3 Parents: 4389cb1 Author: Oleg Nechiporenko <onechipore...@apache.org> Authored: Fri Aug 21 13:07:57 2015 +0300 Committer: Oleg Nechiporenko <onechipore...@apache.org> Committed: Fri Aug 21 13:13:03 2015 +0300 ---------------------------------------------------------------------- ambari-web/app/assets/test/tests.js | 6 +- .../main/alerts/alert_instances_controller.js | 13 +- .../alerts/definition_details_controller.js | 2 +- .../main/host/host_alerts_controller.js | 2 +- .../app/mappers/alert_instances_mapper.js | 14 +- ambari-web/app/models.js | 11 +- ambari-web/app/models/alert_config.js | 619 ------------------- ambari-web/app/models/alert_definition.js | 344 ----------- ambari-web/app/models/alert_group.js | 79 --- ambari-web/app/models/alert_instance.js | 165 ----- ambari-web/app/models/alert_notification.js | 33 - ambari-web/app/models/alerts/alert_config.js | 619 +++++++++++++++++++ .../app/models/alerts/alert_definition.js | 344 +++++++++++ ambari-web/app/models/alerts/alert_group.js | 79 +++ ambari-web/app/models/alerts/alert_instance.js | 165 +++++ .../app/models/alerts/alert_instance_local.js | 30 + .../app/models/alerts/alert_notification.js | 33 + .../main/alerts/definition_details_view.js | 4 +- .../app/views/main/host/host_alerts_view.js | 13 +- .../main/alert_definitions_controller_test.js | 2 +- ambari-web/test/models/alert_config_test.js | 332 ---------- ambari-web/test/models/alert_definition_test.js | 212 ------- ambari-web/test/models/alert_instance_test.js | 94 --- .../test/models/alerts/alert_config_test.js | 332 ++++++++++ .../test/models/alerts/alert_definition_test.js | 212 +++++++ .../test/models/alerts/alert_instance_test.js | 94 +++ 26 files changed, 1946 insertions(+), 1907 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index 759983c..ef339d4 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -283,9 +283,9 @@ var files = ['test/init_model_test', 'test/models/service/flume_test', 'test/models/service/hdfs_test', 'test/models/service/yarn_test', - 'test/models/alert_config_test', - 'test/models/alert_definition_test', - 'test/models/alert_instance_test', + 'test/models/alerts/alert_config_test', + 'test/models/alerts/alert_definition_test', + 'test/models/alerts/alert_instance_test', 'test/models/authentication_test', 'test/models/cluster_states_test', 'test/models/config_group_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/controllers/main/alerts/alert_instances_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/alerts/alert_instances_controller.js b/ambari-web/app/controllers/main/alerts/alert_instances_controller.js index 0117104..234fba0 100644 --- a/ambari-web/app/controllers/main/alerts/alert_instances_controller.js +++ b/ambari-web/app/controllers/main/alerts/alert_instances_controller.js @@ -182,7 +182,7 @@ App.MainAlertInstancesController = Em.Controller.extend({ * @method getAlertInstancesSuccessCallback */ getAlertInstancesSuccessCallback: function (json) { - App.alertInstanceMapper.map(json); + App.alertInstanceMapper.mapLocal(json); this.set('isLoaded', true); this.toggleProperty('reload'); }, @@ -309,15 +309,6 @@ App.MainAlertInstancesController = Em.Controller.extend({ this.set('filteredContent', this.get('content')); }.observes('content.length'), - refreshTooltips: function () { - this.ensureTooltip(); - }.observes('contents.[]'), - - ensureTooltip: function () { - Em.run.next(this, function () { - App.tooltip($(".timeago")); - }); - }, /** * Router transition to alert definition details page * @param event @@ -354,8 +345,6 @@ App.MainAlertInstancesController = Em.Controller.extend({ didInsertElement: function () { this.filter(); - this.addObserver('filteringComplete', this, this.overlayObserver); - this.overlayObserver(); return this._super(); } }) http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/controllers/main/alerts/definition_details_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/alerts/definition_details_controller.js b/ambari-web/app/controllers/main/alerts/definition_details_controller.js index 27e6a4a..336033e 100644 --- a/ambari-web/app/controllers/main/alerts/definition_details_controller.js +++ b/ambari-web/app/controllers/main/alerts/definition_details_controller.js @@ -21,7 +21,7 @@ App.MainAlertDefinitionDetailsController = Em.Controller.extend({ name: 'mainAlertDefinitionDetailsController', alerts: function () { - return App.AlertInstance.find().toArray() + return App.AlertInstanceLocal.find().toArray() .filterProperty('definitionId', this.get('content.id')); }.property('App.router.mainAlertInstancesController.isLoaded', 'App.router.mainAlertInstancesController.reload'), http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/controllers/main/host/host_alerts_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/host/host_alerts_controller.js b/ambari-web/app/controllers/main/host/host_alerts_controller.js index 2a47914..a44aa26 100644 --- a/ambari-web/app/controllers/main/host/host_alerts_controller.js +++ b/ambari-web/app/controllers/main/host/host_alerts_controller.js @@ -30,7 +30,7 @@ App.MainHostAlertsController = Em.ArrayController.extend({ * @type {App.AlertInstance[]} */ content: function () { - return App.AlertInstance.find().toArray().filterProperty('host', this.get('selectedHost')); + return App.AlertInstanceLocal.find().toArray().filterProperty('host', this.get('selectedHost')); }.property('App.router.mainAlertInstancesController.isLoaded', 'selectedHost'), /** http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/mappers/alert_instances_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/alert_instances_mapper.js b/ambari-web/app/mappers/alert_instances_mapper.js index af6804f..cd1f8bd 100644 --- a/ambari-web/app/mappers/alert_instances_mapper.js +++ b/ambari-web/app/mappers/alert_instances_mapper.js @@ -21,6 +21,8 @@ App.alertInstanceMapper = App.QuickDataMapper.create({ model : App.AlertInstance, + modelLocal: App.AlertInstanceLocal, + config : { id: 'Alert.id', label: 'Alert.label', @@ -41,10 +43,17 @@ App.alertInstanceMapper = App.QuickDataMapper.create({ }, map: function(json) { + return this.parse(json, this.get('model')); + }, + + mapLocal: function(json) { + return this.parse(json, this.get('modelLocal')); + }, + + parse: function(json, model) { console.time('App.alertInstanceMapper execution time'); if (json.items) { var alertInstances = []; - var model = this.get('model'); var alertsToDelete = model.find().mapProperty('id'); json.items.forEach(function (item) { @@ -57,8 +66,9 @@ App.alertInstanceMapper = App.QuickDataMapper.create({ model.find().clear(); } - App.store.loadMany(this.get('model'), alertInstances); + App.store.loadMany(model, alertInstances); console.timeEnd('App.alertInstanceMapper execution time'); } } + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models.js b/ambari-web/app/models.js index 64860ea..6bffb95 100644 --- a/ambari-web/app/models.js +++ b/ambari-web/app/models.js @@ -42,11 +42,12 @@ require('models/service/mapreduce2'); require('models/service/hbase'); require('models/service/flume'); require('models/service/storm'); -require('models/alert_definition'); -require('models/alert_instance'); -require('models/alert_notification'); -require('models/alert_config'); -require('models/alert_group'); +require('models/alerts/alert_definition'); +require('models/alerts/alert_instance'); +require('models/alerts/alert_instance_local'); +require('models/alerts/alert_notification'); +require('models/alerts/alert_config'); +require('models/alerts/alert_group'); require('models/user'); require('models/host'); require('models/rack'); http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alert_config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alert_config.js b/ambari-web/app/models/alert_config.js deleted file mode 100644 index df2e579..0000000 --- a/ambari-web/app/models/alert_config.js +++ /dev/null @@ -1,619 +0,0 @@ -/** - * 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 <code>apiFormattedValue</code> should also be an array - * example: <code>apiProperty[0]</code> relates to <code>apiFormattedValue[0]</code> - * @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: function () { - var afterLabeledTypes = ['radioButton']; - return !afterLabeledTypes.contains(this.get('displayType')); - }.property('displayType'), - - /** - * value converted to appropriate format for sending to server - * should be computed property - * should be defined in child class - * @type {*} - */ - apiFormattedValue: function () { - return this.get('value'); - }.property('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 <code>displayType</code> - * @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; - default: - console.error('Unable to find viewClass for displayType ', displayType); - } - }.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 - <code>value</code> and <code>text</code> - * User may configure it to edit only one of them (use flags <code>showInputForValue</code> and <code>showInputForText</code>) - * This flags also determines update value and text in the API-request or not (see <code>App.AlertConfigProperties.Thresholds</code> 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 <code>value</code> should be visible and editable (if not - won't update API-value) - * @type {bool} - */ - showInputForValue: true, - - /** - * Determines if <code>text</code> 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: function () { - return 'alert-state-' + this.get('badge'); - }.property('badge'), - - /** - * Determines if <code>value</code> or <code>text</code> 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 <code>displayValue</code> is valid float number - * If this value isn't shown (see <code>showInputForValue</code>), 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') - }), - - 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: function () { - return this.get('isJMXMetric') ? 'source.jmx.property_list' : 'source.ganglia.property_list' - }.property('isJMXMetric'), - 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: function () { - return this.get('isJMXMetric') ? 'source.jmx.value' : 'source.ganglia.value' - }.property('isJMXMetric') - }) - -}; - -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 <code>App.AlertConfigProperties.Threshold</code> - * 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 <code>value * 100</code> - * @returns {string} - */ - getNewValue: function () { - var value = this.get('value'); - return (value && !isNaN(value)) ? (Number(value) * 100) + '' : value; - }, - - /** - * Return <code>displayValue / 100</code> - * @returns {string} - */ - getNewDisplayValue: function () { - var displayValue = this.get('displayValue'); - return (displayValue && !isNaN(displayValue)) ? (Number(displayValue) / 100) + '' : displayValue; - } - - }), - - /** - * Mixin for <code>App.AlertConfigProperties.Threshold</code> - * 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') - - }) - -}; http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alert_definition.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alert_definition.js b/ambari-web/app/models/alert_definition.js deleted file mode 100644 index d1310ff..0000000 --- a/ambari-web/app/models/alert_definition.js +++ /dev/null @@ -1,344 +0,0 @@ -/** - * 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 dateUtils = require('utils/date'); - -App.AlertDefinition = DS.Model.extend({ - - name: DS.attr('string'), - label: DS.attr('string'), - description: DS.attr('string'), - service: DS.belongsTo('App.Service'), - serviceName: DS.attr('string'), - componentName: DS.attr('string'), - enabled: DS.attr('boolean'), - scope: DS.attr('string'), - interval: DS.attr('number'), - type: DS.attr('string'), - groups: DS.hasMany('App.AlertGroup'), - reporting: DS.hasMany('App.AlertReportDefinition'), - lastTriggered: DS.attr('number'), - - //relates only to SCRIPT-type alert definition - location: DS.attr('string'), - //relates only to AGGREGATE-type alert definition - alertName: DS.attr('string'), - //relates only to WEB and METRIC types of alert definition - uri: DS.belongsTo('App.AlertMetricsUriDefinition'), - //relates only METRIC-type alert definition - jmx: DS.belongsTo('App.AlertMetricsSourceDefinition'), - ganglia: DS.belongsTo('App.AlertMetricsSourceDefinition'), - //relates only PORT-type alert definition - defaultPort: DS.attr('number'), - portUri: DS.attr('string'), - - /** - * Raw data from AlertDefinition/source - * used to format request content for updating alert definition - * @type {Object} - */ - rawSourceData: {}, - - /** - * Counts of alert grouped by their status - * Format: - * <code> - * { - * "CRITICAL": { - * count: 1, - * maintenanceCount: 0 - * }, - * "OK": { - * count: 0, - * maintenanceCount: 1 - * }, - * "UNKNOWN": { - * count: 0, - * maintenanceCount: 0 - * }, - * "WARNING": { - * count: 1, - * maintenanceCount: 1 - * } - * } - * </code> - * @type {object} - */ - summary: {}, - - /** - * Formatted timestamp for latest alert triggering for current alertDefinition - * @type {string} - */ - lastTriggeredFormatted: function () { - return dateUtils.dateFormat(this.get('lastTriggered')); - }.property('lastTriggered'), - - /** - * Formatted timestamp with <code>$.timeago</code> - * @type {string} - */ - lastTriggeredAgoFormatted: function () { - var lastTriggered = this.get('lastTriggered'); - return lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; - }.property('lastTriggered'), - - lastTriggeredVerboseDisplay: function () { - var lastTriggered = this.get('lastTriggered'); - return Em.I18n.t('models.alert_definition.triggered.verbose').format(dateUtils.dateFormat(lastTriggered)); - }.property('lastTriggered'), - - /** - * Formatted timestamp in format: for 4 days - * @type {string} - */ - lastTriggeredForFormatted: function () { - var lastTriggered = this.get('lastTriggered'); - var previousSuffixAgo = $.timeago.settings.strings.suffixAgo; - var previousPrefixAgo = $.timeago.settings.strings.prefixAgo; - $.timeago.settings.strings.suffixAgo = null; - $.timeago.settings.strings.prefixAgo = 'for'; - var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; - $.timeago.settings.strings.suffixAgo = previousSuffixAgo; - $.timeago.settings.strings.prefixAgo = previousPrefixAgo; - return triggeredFor; - }.property('lastTriggered'), - - /** - * Formatted displayName for <code>componentName</code> - * @type {String} - */ - componentNameFormatted: function () { - return App.format.role(this.get('componentName')); - }.property('componentName'), - - /** - * Status generates from child-alerts - * Format: OK(1) WARN(2) CRIT(1) UNKN(1) - * If single host: show: OK/WARNING/CRITICAL/UNKNOWN - * If some there are no alerts with some state, this state isn't shown - * If no OK/WARN/CRIT/UNKN state, then show PENDING - * Order is equal to example - * @type {string} - */ - status: function () { - var order = this.get('order'), - summary = this.get('summary'), - hostCnt = 0, - self = this; - order.forEach(function (state) { - hostCnt += summary[state] ? summary[state].count + summary[state].maintenanceCount : 0; - }); - if (hostCnt > 1) { - // multiple hosts - return order.map(function (state) { - var shortState = self.get('shortState')[state]; - var result = ''; - result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + ' (' + summary[state].count + ')</span>' : ''; - // add status with maintenance mode icon - result += summary[state].maintenanceCount ? - '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + ' (' + summary[state].maintenanceCount + ')</span>' : ''; - return result; - }).without('').join(' '); - } else if (hostCnt == 1) { - // single host, single status - return order.map(function (state) { - var shortState = self.get('shortState')[state]; - var result = ''; - result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + '</span>' : ''; - // add status with maintenance mode icon - result += summary[state].maintenanceCount ? - '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + '</span>' : ''; - return result; - }).without('').join(' '); - } else if (hostCnt == 0) { - // none - return '<span class="alert-state-single-host label alert-state-PENDING">NONE</span>'; - } - return ''; - }.property('summary'), - - latestText: function () { - var order = this.get('order'), summary = this.get('summary'), text = ''; - order.forEach(function (state) { - var cnt = summary[state] ? summary[state].count + summary[state].maintenanceCount : 0; - if (cnt > 0) { - text = summary[state].latestText; - } - }); - return text; - }.property('summary'), - - isHostAlertDefinition: function () { - var serviceID = (this.get('service')._id === "AMBARI"), - component = (this.get('componentName') === "AMBARI_AGENT"); - return serviceID && component; - }.property('service', 'componentName'), - - typeIconClass: function () { - var typeIcons = this.get('typeIcons'), - type = this.get('type'); - return typeIcons[type]; - }.property('type'), - - /** - * if this definition is in state: CRITICAL / WARNING, if true, will show up in alerts fast access popup - * instances with maintenance mode ON are ignored - * @type {boolean} - */ - isCriticalOrWarning: function () { - return !!(this.get('summary.CRITICAL.count') || this.get('summary.WARNING.count')); - }.property('summary'), - - /** - * if this definition is in state: CRIT - * @type {boolean} - */ - isCritical: function () { - var summary = this.get('summary'); - var state = 'CRITICAL'; - return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); - }.property('summary'), - - /** - * if this definition is in state: WARNING - * @type {boolean} - */ - isWarning: function () { - var summary = this.get('summary'); - var state = 'WARNING'; - return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); - }.property('summary'), - - /** - * if this definition is in state: OK - * @type {boolean} - */ - isOK: function () { - var summary = this.get('summary'); - var state = 'OK'; - return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); - }.property('summary'), - - /** - * if this definition is in state: OK - * @type {boolean} - */ - isUnknown: function () { - var summary = this.get('summary'); - var state = 'UNKNOWN'; - return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); - }.property('summary'), - - /** - * For alerts we will have processes which are not typical - * cluster services - like Ambari-Server. This method unifies - * cluster services and other services into a common display-name. - * @see App.AlertInstance#serviceDisplayName() - */ - serviceDisplayName: function () { - var serviceName = this.get('service.displayName'); - if (!serviceName) { - serviceName = this.get('serviceName'); - if (serviceName) { - serviceName = serviceName.toCapital(); - } - } - return serviceName; - }.property('serviceName', 'service.displayName'), - - /** - * List of css-classes for alert types - * @type {object} - */ - typeIcons: { - 'METRIC': 'icon-bolt', - 'SCRIPT': 'icon-file-text', - 'WEB': 'icon-globe', - 'PORT': 'icon-signin', - 'AGGREGATE': 'icon-plus', - 'SERVER': 'icon-desktop' - }, - - /** - * Sort on load definitions by this severity order - */ - severityOrder: ['CRITICAL', 'WARNING', 'OK', 'UNKNOWN', 'PENDING'], - order: ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN'], - - shortState: { - 'CRITICAL': 'CRIT', - 'WARNING': 'WARN', - 'OK': 'OK', - 'UNKNOWN': 'UNKWN', - 'PENDING': 'NONE' - } -}); - -App.AlertDefinition.reopenClass({ - - /** - * Return function to sort list of AlertDefinitions by their status - * It sorts according to <code>severityOrder</code> - * @param {boolean} order true - DESC, false - ASC - * @returns {Function} - * @method getSortDefinitionsByStatus - */ - getSortDefinitionsByStatus: function (order) { - return function (a, b) { - var a_summary = a.get('summary'), - b_summary = b.get('summary'), - st_order = a.get('severityOrder'), - ret = 0; - for (var i = 0; i < st_order.length; i++) { - var a_v = Em.isNone(a_summary[st_order[i]]) ? 0 : a_summary[st_order[i]].count + a_summary[st_order[i]].maintenanceCount, - b_v = Em.isNone(b_summary[st_order[i]]) ? 0 : b_summary[st_order[i]].count + b_summary[st_order[i]].maintenanceCount; - ret = b_v - a_v; - if (ret !== 0) { - break; - } - } - return order ? ret : -ret; - }; - } - -}); - -App.AlertReportDefinition = DS.Model.extend({ - type: DS.attr('string'), - text: DS.attr('string'), - value: DS.attr('number') -}); - -App.AlertMetricsSourceDefinition = DS.Model.extend({ - propertyList: [], - value: DS.attr('string') -}); - -App.AlertMetricsUriDefinition = DS.Model.extend({ - http: DS.attr('string'), - https: DS.attr('string'), - httpsProperty: DS.attr('string'), - httpsPropertyValue: DS.attr('string') -}); - -App.AlertDefinition.FIXTURES = []; -App.AlertReportDefinition.FIXTURES = []; -App.AlertMetricsSourceDefinition.FIXTURES = []; -App.AlertMetricsUriDefinition.FIXTURES = []; http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alert_group.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alert_group.js b/ambari-web/app/models/alert_group.js deleted file mode 100644 index e914ed5..0000000 --- a/ambari-web/app/models/alert_group.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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'); - -/** - * Represents an alert-group on the cluster. - * A alert group is a collection of alert definitions - * - * Alert group hierarchy is at 2 levels. For - * each service there is a 'Default' alert group - * containing all definitions , this group is read-only - * - * User can create new alert group containing alert definitions from - * any service. - */ -App.AlertGroup = DS.Model.extend({ - - name: DS.attr('string'), - - description: DS.attr('string'), - - /** - * Is this group default for some service - * @type {boolean} - */ - default: DS.attr('boolean'), - - /** - * @type {App.AlertDefinition[]} - */ - definitions: DS.hasMany('App.AlertDefinition'), - - /** - * @type {App.AlertNotification[]} - */ - targets: DS.hasMany('App.AlertNotification'), - - /** - * @type {string} - */ - displayName: function () { - var name = this.get('name'); - if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) { - var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2); - name = name.substring(0, middle) + "..." + name.substring(name.length - middle); - } - return this.get('default') ? (name + ' Default') : name; - }.property('name', 'default'), - - /** - * @type {string} - */ - displayNameDefinitions: function () { - return this.get('displayName') + ' (' + this.get('definitions.length') + ')'; - }.property('displayName', 'definitions.length'), - - isAddDefinitionsDisabled: function () { - return this.get('default'); - }.property('default') -}); -App.AlertGroup.FIXTURES = []; - - http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alert_instance.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alert_instance.js b/ambari-web/app/models/alert_instance.js deleted file mode 100644 index 33df293..0000000 --- a/ambari-web/app/models/alert_instance.js +++ /dev/null @@ -1,165 +0,0 @@ -/** - * 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 dateUtils = require('utils/date'); - -App.AlertInstance = DS.Model.extend({ - id: DS.attr('number'), - label: DS.attr('string'), - definitionName: DS.attr('string'), - definitionId: DS.attr('number'), - service: DS.belongsTo('App.Service'), - serviceName: DS.attr('string'), - componentName: DS.attr('string'), - host: DS.belongsTo('App.Host'), - hostName: DS.attr('string'), - scope: DS.attr('string'), - originalTimestamp: DS.attr('number'), - latestTimestamp: DS.attr('number'), - maintenanceState: DS.attr('string'), - instance: DS.attr('string'), - state: DS.attr('string'), - text: DS.attr('string'), - notification: DS.hasMany('App.AlertNotification'), - - /** - * Status icon markup - * @type {string} - */ - status: function () { - var isMaintenanceStateOn = this.get('maintenanceState') === 'ON'; - var state = this.get('state'); - var stateClass = isMaintenanceStateOn ? 'PENDING' : state; - var shortState = this.get('shortState')[state]; - var maintenanceIcon = isMaintenanceStateOn ? '<span class="icon-medkit"></span> ' : ''; - return '<div class="label alert-state-single-host alert-state-' + stateClass + '">' + maintenanceIcon + shortState + '</div>'; - }.property('state'), - - /** - * For alerts we will have processes which are not typical - * cluster services - like Ambari-Server. This method unifies - * cluster services and other services into a common display-name. - * @see App.AlertDefinition#serviceDisplayName() - */ - serviceDisplayName: function () { - var serviceName = this.get('service.displayName'); - if (!serviceName) { - serviceName = this.get('serviceName'); - if (serviceName) { - serviceName = serviceName.toCapital(); - } - } - return serviceName; - }.property('serviceName', 'service.displayName'), - - /** - * Formatted timestamp for latest instance triggering - * @type {string} - */ - lastCheckedFormatted: function () { - return dateUtils.dateFormat(this.get('latestTimestamp')); - }.property('latestTimestamp'), - - /** - * Formatted timestamp for latest instance triggering - * @type {string} - */ - lastTriggeredFormatted: function () { - return dateUtils.dateFormat(this.get('originalTimestamp')); - }.property('originalTimestamp'), - - /** - * Formatted timestamp with <code>$.timeago</code> - * @type {string} - */ - lastTriggeredAgoFormatted: function () { - var lastTriggered = this.get('originalTimestamp'); - return lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; - }.property('originalTimestamp'), - - lastTriggeredVerboseDisplay: function () { - var originalTimestamp = this.get('originalTimestamp'); - var latestTimestamp = this.get('latestTimestamp'); - return Em.I18n.t('models.alert_instance.tiggered.verbose').format( - dateUtils.dateFormat(originalTimestamp), - dateUtils.dateFormat(latestTimestamp)); - }.property('originalTimestamp', 'latestTimestamp'), - - /** - * Formatted timestamp with <code>$.timeago</code> - * @type {string} - */ - lastTriggeredForFormatted: function () { - var lastTriggered = this.get('originalTimestamp'); - var previousSuffixAgo = $.timeago.settings.strings.suffixAgo; - var previousPrefixAgo = $.timeago.settings.strings.prefixAgo; - $.timeago.settings.strings.suffixAgo = null; - $.timeago.settings.strings.prefixAgo = 'for'; - var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; - $.timeago.settings.strings.suffixAgo = previousSuffixAgo; - $.timeago.settings.strings.prefixAgo = previousPrefixAgo; - return triggeredFor; - }.property('originalTimestamp'), - - /** - * escaped '<' and '>' special characters. - * @type {string} - */ - escapeSpecialCharactersFromTooltip: function () { - var displayedText = this.get('text'); - return displayedText.replace(/[<>]/g, ''); - }.property('text'), - - /** - * Formatted lastChecked and lastTriggered timestamp - * @returns {string} - */ - statusChangedAndLastCheckedFormatted: function () { - var lastCheckedFormatted = this.get('lastCheckedFormatted'); - var lastTriggeredFormatted = this.get('lastTriggeredFormatted'); - return Em.I18n.t('models.alert_definition.triggered.checked').format(lastTriggeredFormatted, lastCheckedFormatted); - }.property('lastCheckedFormatted', 'lastTriggeredFormatted'), - - /** - * List of css-classes for alert instance status - * @type {object} - */ - typeIcons: { - 'DISABLED': 'icon-off' - }, - - /** - * Define if definition serviceName is Ambari - * Used in some logic in templates to distinguish definitions with Ambari serviceName - * @returns {boolean} - */ - isAmbariServiceName: function () { - return this.get('serviceName') === 'AMBARI'; - }.property('serviceName'), - - shortState: { - 'CRITICAL': 'CRIT', - 'WARNING': 'WARN', - 'OK': 'OK', - 'UNKNOWN': 'UNKWN', - 'PENDING': 'NONE' - } -}); - -App.AlertInstance.FIXTURES = []; http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alert_notification.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alert_notification.js b/ambari-web/app/models/alert_notification.js deleted file mode 100644 index c2d7570..0000000 --- a/ambari-web/app/models/alert_notification.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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'); - -App.AlertNotification = DS.Model.extend({ - id: DS.attr('number'), - name: DS.attr('string'), - type: DS.attr('string'), - description: DS.attr('string'), - groups: DS.hasMany('App.AlertGroup'), - global: DS.attr('boolean'), - - properties: {}, - alertStates: [] -}); - -App.AlertNotification.FIXTURES = []; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alerts/alert_config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alerts/alert_config.js b/ambari-web/app/models/alerts/alert_config.js new file mode 100644 index 0000000..df2e579 --- /dev/null +++ b/ambari-web/app/models/alerts/alert_config.js @@ -0,0 +1,619 @@ +/** + * 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 <code>apiFormattedValue</code> should also be an array + * example: <code>apiProperty[0]</code> relates to <code>apiFormattedValue[0]</code> + * @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: function () { + var afterLabeledTypes = ['radioButton']; + return !afterLabeledTypes.contains(this.get('displayType')); + }.property('displayType'), + + /** + * value converted to appropriate format for sending to server + * should be computed property + * should be defined in child class + * @type {*} + */ + apiFormattedValue: function () { + return this.get('value'); + }.property('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 <code>displayType</code> + * @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; + default: + console.error('Unable to find viewClass for displayType ', displayType); + } + }.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 - <code>value</code> and <code>text</code> + * User may configure it to edit only one of them (use flags <code>showInputForValue</code> and <code>showInputForText</code>) + * This flags also determines update value and text in the API-request or not (see <code>App.AlertConfigProperties.Thresholds</code> 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 <code>value</code> should be visible and editable (if not - won't update API-value) + * @type {bool} + */ + showInputForValue: true, + + /** + * Determines if <code>text</code> 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: function () { + return 'alert-state-' + this.get('badge'); + }.property('badge'), + + /** + * Determines if <code>value</code> or <code>text</code> 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 <code>displayValue</code> is valid float number + * If this value isn't shown (see <code>showInputForValue</code>), 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') + }), + + 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: function () { + return this.get('isJMXMetric') ? 'source.jmx.property_list' : 'source.ganglia.property_list' + }.property('isJMXMetric'), + 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: function () { + return this.get('isJMXMetric') ? 'source.jmx.value' : 'source.ganglia.value' + }.property('isJMXMetric') + }) + +}; + +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 <code>App.AlertConfigProperties.Threshold</code> + * 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 <code>value * 100</code> + * @returns {string} + */ + getNewValue: function () { + var value = this.get('value'); + return (value && !isNaN(value)) ? (Number(value) * 100) + '' : value; + }, + + /** + * Return <code>displayValue / 100</code> + * @returns {string} + */ + getNewDisplayValue: function () { + var displayValue = this.get('displayValue'); + return (displayValue && !isNaN(displayValue)) ? (Number(displayValue) / 100) + '' : displayValue; + } + + }), + + /** + * Mixin for <code>App.AlertConfigProperties.Threshold</code> + * 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') + + }) + +}; http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alerts/alert_definition.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alerts/alert_definition.js b/ambari-web/app/models/alerts/alert_definition.js new file mode 100644 index 0000000..d1310ff --- /dev/null +++ b/ambari-web/app/models/alerts/alert_definition.js @@ -0,0 +1,344 @@ +/** + * 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 dateUtils = require('utils/date'); + +App.AlertDefinition = DS.Model.extend({ + + name: DS.attr('string'), + label: DS.attr('string'), + description: DS.attr('string'), + service: DS.belongsTo('App.Service'), + serviceName: DS.attr('string'), + componentName: DS.attr('string'), + enabled: DS.attr('boolean'), + scope: DS.attr('string'), + interval: DS.attr('number'), + type: DS.attr('string'), + groups: DS.hasMany('App.AlertGroup'), + reporting: DS.hasMany('App.AlertReportDefinition'), + lastTriggered: DS.attr('number'), + + //relates only to SCRIPT-type alert definition + location: DS.attr('string'), + //relates only to AGGREGATE-type alert definition + alertName: DS.attr('string'), + //relates only to WEB and METRIC types of alert definition + uri: DS.belongsTo('App.AlertMetricsUriDefinition'), + //relates only METRIC-type alert definition + jmx: DS.belongsTo('App.AlertMetricsSourceDefinition'), + ganglia: DS.belongsTo('App.AlertMetricsSourceDefinition'), + //relates only PORT-type alert definition + defaultPort: DS.attr('number'), + portUri: DS.attr('string'), + + /** + * Raw data from AlertDefinition/source + * used to format request content for updating alert definition + * @type {Object} + */ + rawSourceData: {}, + + /** + * Counts of alert grouped by their status + * Format: + * <code> + * { + * "CRITICAL": { + * count: 1, + * maintenanceCount: 0 + * }, + * "OK": { + * count: 0, + * maintenanceCount: 1 + * }, + * "UNKNOWN": { + * count: 0, + * maintenanceCount: 0 + * }, + * "WARNING": { + * count: 1, + * maintenanceCount: 1 + * } + * } + * </code> + * @type {object} + */ + summary: {}, + + /** + * Formatted timestamp for latest alert triggering for current alertDefinition + * @type {string} + */ + lastTriggeredFormatted: function () { + return dateUtils.dateFormat(this.get('lastTriggered')); + }.property('lastTriggered'), + + /** + * Formatted timestamp with <code>$.timeago</code> + * @type {string} + */ + lastTriggeredAgoFormatted: function () { + var lastTriggered = this.get('lastTriggered'); + return lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; + }.property('lastTriggered'), + + lastTriggeredVerboseDisplay: function () { + var lastTriggered = this.get('lastTriggered'); + return Em.I18n.t('models.alert_definition.triggered.verbose').format(dateUtils.dateFormat(lastTriggered)); + }.property('lastTriggered'), + + /** + * Formatted timestamp in format: for 4 days + * @type {string} + */ + lastTriggeredForFormatted: function () { + var lastTriggered = this.get('lastTriggered'); + var previousSuffixAgo = $.timeago.settings.strings.suffixAgo; + var previousPrefixAgo = $.timeago.settings.strings.prefixAgo; + $.timeago.settings.strings.suffixAgo = null; + $.timeago.settings.strings.prefixAgo = 'for'; + var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; + $.timeago.settings.strings.suffixAgo = previousSuffixAgo; + $.timeago.settings.strings.prefixAgo = previousPrefixAgo; + return triggeredFor; + }.property('lastTriggered'), + + /** + * Formatted displayName for <code>componentName</code> + * @type {String} + */ + componentNameFormatted: function () { + return App.format.role(this.get('componentName')); + }.property('componentName'), + + /** + * Status generates from child-alerts + * Format: OK(1) WARN(2) CRIT(1) UNKN(1) + * If single host: show: OK/WARNING/CRITICAL/UNKNOWN + * If some there are no alerts with some state, this state isn't shown + * If no OK/WARN/CRIT/UNKN state, then show PENDING + * Order is equal to example + * @type {string} + */ + status: function () { + var order = this.get('order'), + summary = this.get('summary'), + hostCnt = 0, + self = this; + order.forEach(function (state) { + hostCnt += summary[state] ? summary[state].count + summary[state].maintenanceCount : 0; + }); + if (hostCnt > 1) { + // multiple hosts + return order.map(function (state) { + var shortState = self.get('shortState')[state]; + var result = ''; + result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + ' (' + summary[state].count + ')</span>' : ''; + // add status with maintenance mode icon + result += summary[state].maintenanceCount ? + '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + ' (' + summary[state].maintenanceCount + ')</span>' : ''; + return result; + }).without('').join(' '); + } else if (hostCnt == 1) { + // single host, single status + return order.map(function (state) { + var shortState = self.get('shortState')[state]; + var result = ''; + result += summary[state].count ? '<span class="alert-state-single-host label alert-state-' + state + '">' + shortState + '</span>' : ''; + // add status with maintenance mode icon + result += summary[state].maintenanceCount ? + '<span class="alert-state-single-host label alert-state-PENDING"><span class="icon-medkit"></span> ' + shortState + '</span>' : ''; + return result; + }).without('').join(' '); + } else if (hostCnt == 0) { + // none + return '<span class="alert-state-single-host label alert-state-PENDING">NONE</span>'; + } + return ''; + }.property('summary'), + + latestText: function () { + var order = this.get('order'), summary = this.get('summary'), text = ''; + order.forEach(function (state) { + var cnt = summary[state] ? summary[state].count + summary[state].maintenanceCount : 0; + if (cnt > 0) { + text = summary[state].latestText; + } + }); + return text; + }.property('summary'), + + isHostAlertDefinition: function () { + var serviceID = (this.get('service')._id === "AMBARI"), + component = (this.get('componentName') === "AMBARI_AGENT"); + return serviceID && component; + }.property('service', 'componentName'), + + typeIconClass: function () { + var typeIcons = this.get('typeIcons'), + type = this.get('type'); + return typeIcons[type]; + }.property('type'), + + /** + * if this definition is in state: CRITICAL / WARNING, if true, will show up in alerts fast access popup + * instances with maintenance mode ON are ignored + * @type {boolean} + */ + isCriticalOrWarning: function () { + return !!(this.get('summary.CRITICAL.count') || this.get('summary.WARNING.count')); + }.property('summary'), + + /** + * if this definition is in state: CRIT + * @type {boolean} + */ + isCritical: function () { + var summary = this.get('summary'); + var state = 'CRITICAL'; + return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); + }.property('summary'), + + /** + * if this definition is in state: WARNING + * @type {boolean} + */ + isWarning: function () { + var summary = this.get('summary'); + var state = 'WARNING'; + return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); + }.property('summary'), + + /** + * if this definition is in state: OK + * @type {boolean} + */ + isOK: function () { + var summary = this.get('summary'); + var state = 'OK'; + return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); + }.property('summary'), + + /** + * if this definition is in state: OK + * @type {boolean} + */ + isUnknown: function () { + var summary = this.get('summary'); + var state = 'UNKNOWN'; + return !!summary[state] && !!(summary[state].count || summary[state].maintenanceCount); + }.property('summary'), + + /** + * For alerts we will have processes which are not typical + * cluster services - like Ambari-Server. This method unifies + * cluster services and other services into a common display-name. + * @see App.AlertInstance#serviceDisplayName() + */ + serviceDisplayName: function () { + var serviceName = this.get('service.displayName'); + if (!serviceName) { + serviceName = this.get('serviceName'); + if (serviceName) { + serviceName = serviceName.toCapital(); + } + } + return serviceName; + }.property('serviceName', 'service.displayName'), + + /** + * List of css-classes for alert types + * @type {object} + */ + typeIcons: { + 'METRIC': 'icon-bolt', + 'SCRIPT': 'icon-file-text', + 'WEB': 'icon-globe', + 'PORT': 'icon-signin', + 'AGGREGATE': 'icon-plus', + 'SERVER': 'icon-desktop' + }, + + /** + * Sort on load definitions by this severity order + */ + severityOrder: ['CRITICAL', 'WARNING', 'OK', 'UNKNOWN', 'PENDING'], + order: ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN'], + + shortState: { + 'CRITICAL': 'CRIT', + 'WARNING': 'WARN', + 'OK': 'OK', + 'UNKNOWN': 'UNKWN', + 'PENDING': 'NONE' + } +}); + +App.AlertDefinition.reopenClass({ + + /** + * Return function to sort list of AlertDefinitions by their status + * It sorts according to <code>severityOrder</code> + * @param {boolean} order true - DESC, false - ASC + * @returns {Function} + * @method getSortDefinitionsByStatus + */ + getSortDefinitionsByStatus: function (order) { + return function (a, b) { + var a_summary = a.get('summary'), + b_summary = b.get('summary'), + st_order = a.get('severityOrder'), + ret = 0; + for (var i = 0; i < st_order.length; i++) { + var a_v = Em.isNone(a_summary[st_order[i]]) ? 0 : a_summary[st_order[i]].count + a_summary[st_order[i]].maintenanceCount, + b_v = Em.isNone(b_summary[st_order[i]]) ? 0 : b_summary[st_order[i]].count + b_summary[st_order[i]].maintenanceCount; + ret = b_v - a_v; + if (ret !== 0) { + break; + } + } + return order ? ret : -ret; + }; + } + +}); + +App.AlertReportDefinition = DS.Model.extend({ + type: DS.attr('string'), + text: DS.attr('string'), + value: DS.attr('number') +}); + +App.AlertMetricsSourceDefinition = DS.Model.extend({ + propertyList: [], + value: DS.attr('string') +}); + +App.AlertMetricsUriDefinition = DS.Model.extend({ + http: DS.attr('string'), + https: DS.attr('string'), + httpsProperty: DS.attr('string'), + httpsPropertyValue: DS.attr('string') +}); + +App.AlertDefinition.FIXTURES = []; +App.AlertReportDefinition.FIXTURES = []; +App.AlertMetricsSourceDefinition.FIXTURES = []; +App.AlertMetricsUriDefinition.FIXTURES = []; http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alerts/alert_group.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alerts/alert_group.js b/ambari-web/app/models/alerts/alert_group.js new file mode 100644 index 0000000..e914ed5 --- /dev/null +++ b/ambari-web/app/models/alerts/alert_group.js @@ -0,0 +1,79 @@ +/** + * 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'); + +/** + * Represents an alert-group on the cluster. + * A alert group is a collection of alert definitions + * + * Alert group hierarchy is at 2 levels. For + * each service there is a 'Default' alert group + * containing all definitions , this group is read-only + * + * User can create new alert group containing alert definitions from + * any service. + */ +App.AlertGroup = DS.Model.extend({ + + name: DS.attr('string'), + + description: DS.attr('string'), + + /** + * Is this group default for some service + * @type {boolean} + */ + default: DS.attr('boolean'), + + /** + * @type {App.AlertDefinition[]} + */ + definitions: DS.hasMany('App.AlertDefinition'), + + /** + * @type {App.AlertNotification[]} + */ + targets: DS.hasMany('App.AlertNotification'), + + /** + * @type {string} + */ + displayName: function () { + var name = this.get('name'); + if (name && name.length > App.config.CONFIG_GROUP_NAME_MAX_LENGTH) { + var middle = Math.floor(App.config.CONFIG_GROUP_NAME_MAX_LENGTH / 2); + name = name.substring(0, middle) + "..." + name.substring(name.length - middle); + } + return this.get('default') ? (name + ' Default') : name; + }.property('name', 'default'), + + /** + * @type {string} + */ + displayNameDefinitions: function () { + return this.get('displayName') + ' (' + this.get('definitions.length') + ')'; + }.property('displayName', 'definitions.length'), + + isAddDefinitionsDisabled: function () { + return this.get('default'); + }.property('default') +}); +App.AlertGroup.FIXTURES = []; + + http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alerts/alert_instance.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alerts/alert_instance.js b/ambari-web/app/models/alerts/alert_instance.js new file mode 100644 index 0000000..33df293 --- /dev/null +++ b/ambari-web/app/models/alerts/alert_instance.js @@ -0,0 +1,165 @@ +/** + * 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 dateUtils = require('utils/date'); + +App.AlertInstance = DS.Model.extend({ + id: DS.attr('number'), + label: DS.attr('string'), + definitionName: DS.attr('string'), + definitionId: DS.attr('number'), + service: DS.belongsTo('App.Service'), + serviceName: DS.attr('string'), + componentName: DS.attr('string'), + host: DS.belongsTo('App.Host'), + hostName: DS.attr('string'), + scope: DS.attr('string'), + originalTimestamp: DS.attr('number'), + latestTimestamp: DS.attr('number'), + maintenanceState: DS.attr('string'), + instance: DS.attr('string'), + state: DS.attr('string'), + text: DS.attr('string'), + notification: DS.hasMany('App.AlertNotification'), + + /** + * Status icon markup + * @type {string} + */ + status: function () { + var isMaintenanceStateOn = this.get('maintenanceState') === 'ON'; + var state = this.get('state'); + var stateClass = isMaintenanceStateOn ? 'PENDING' : state; + var shortState = this.get('shortState')[state]; + var maintenanceIcon = isMaintenanceStateOn ? '<span class="icon-medkit"></span> ' : ''; + return '<div class="label alert-state-single-host alert-state-' + stateClass + '">' + maintenanceIcon + shortState + '</div>'; + }.property('state'), + + /** + * For alerts we will have processes which are not typical + * cluster services - like Ambari-Server. This method unifies + * cluster services and other services into a common display-name. + * @see App.AlertDefinition#serviceDisplayName() + */ + serviceDisplayName: function () { + var serviceName = this.get('service.displayName'); + if (!serviceName) { + serviceName = this.get('serviceName'); + if (serviceName) { + serviceName = serviceName.toCapital(); + } + } + return serviceName; + }.property('serviceName', 'service.displayName'), + + /** + * Formatted timestamp for latest instance triggering + * @type {string} + */ + lastCheckedFormatted: function () { + return dateUtils.dateFormat(this.get('latestTimestamp')); + }.property('latestTimestamp'), + + /** + * Formatted timestamp for latest instance triggering + * @type {string} + */ + lastTriggeredFormatted: function () { + return dateUtils.dateFormat(this.get('originalTimestamp')); + }.property('originalTimestamp'), + + /** + * Formatted timestamp with <code>$.timeago</code> + * @type {string} + */ + lastTriggeredAgoFormatted: function () { + var lastTriggered = this.get('originalTimestamp'); + return lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; + }.property('originalTimestamp'), + + lastTriggeredVerboseDisplay: function () { + var originalTimestamp = this.get('originalTimestamp'); + var latestTimestamp = this.get('latestTimestamp'); + return Em.I18n.t('models.alert_instance.tiggered.verbose').format( + dateUtils.dateFormat(originalTimestamp), + dateUtils.dateFormat(latestTimestamp)); + }.property('originalTimestamp', 'latestTimestamp'), + + /** + * Formatted timestamp with <code>$.timeago</code> + * @type {string} + */ + lastTriggeredForFormatted: function () { + var lastTriggered = this.get('originalTimestamp'); + var previousSuffixAgo = $.timeago.settings.strings.suffixAgo; + var previousPrefixAgo = $.timeago.settings.strings.prefixAgo; + $.timeago.settings.strings.suffixAgo = null; + $.timeago.settings.strings.prefixAgo = 'for'; + var triggeredFor = lastTriggered ? $.timeago(new Date(lastTriggered)) : ''; + $.timeago.settings.strings.suffixAgo = previousSuffixAgo; + $.timeago.settings.strings.prefixAgo = previousPrefixAgo; + return triggeredFor; + }.property('originalTimestamp'), + + /** + * escaped '<' and '>' special characters. + * @type {string} + */ + escapeSpecialCharactersFromTooltip: function () { + var displayedText = this.get('text'); + return displayedText.replace(/[<>]/g, ''); + }.property('text'), + + /** + * Formatted lastChecked and lastTriggered timestamp + * @returns {string} + */ + statusChangedAndLastCheckedFormatted: function () { + var lastCheckedFormatted = this.get('lastCheckedFormatted'); + var lastTriggeredFormatted = this.get('lastTriggeredFormatted'); + return Em.I18n.t('models.alert_definition.triggered.checked').format(lastTriggeredFormatted, lastCheckedFormatted); + }.property('lastCheckedFormatted', 'lastTriggeredFormatted'), + + /** + * List of css-classes for alert instance status + * @type {object} + */ + typeIcons: { + 'DISABLED': 'icon-off' + }, + + /** + * Define if definition serviceName is Ambari + * Used in some logic in templates to distinguish definitions with Ambari serviceName + * @returns {boolean} + */ + isAmbariServiceName: function () { + return this.get('serviceName') === 'AMBARI'; + }.property('serviceName'), + + shortState: { + 'CRITICAL': 'CRIT', + 'WARNING': 'WARN', + 'OK': 'OK', + 'UNKNOWN': 'UNKWN', + 'PENDING': 'NONE' + } +}); + +App.AlertInstance.FIXTURES = []; http://git-wip-us.apache.org/repos/asf/ambari/blob/f1917d27/ambari-web/app/models/alerts/alert_instance_local.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/alerts/alert_instance_local.js b/ambari-web/app/models/alerts/alert_instance_local.js new file mode 100644 index 0000000..be34565 --- /dev/null +++ b/ambari-web/app/models/alerts/alert_instance_local.js @@ -0,0 +1,30 @@ +/** + * 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'); + +/** + * Just a copy of <code>App.AlertInstance</code>, used to save separately alert instances for host and definition + * Sometimes alert instances can't be stored in the one model. Example: user is in the definition details page and open + * alerts popup. Different lists of instances should be shown and loaded. Also it's pretty hard to determine, which + * models should be removed from store, when user navigates in the popup or just leave the page + * @type {DS.Model} + */ +App.AlertInstanceLocal = App.AlertInstance.extend({}); + +App.AlertInstanceLocal.FIXTURES = []; \ No newline at end of file