Browse Source

AMBARI-8161. Alerts UI: Create individual alert definition page. (akovalenko)

Aleksandr Kovalenko 10 năm trước cách đây
mục cha
commit
2b0a9aa6e3

+ 1 - 0
ambari-web/app/controllers.js

@@ -68,6 +68,7 @@ require('controllers/main/admin/authentication');
 require('controllers/main/alerts_controller');
 require('controllers/main/alert_definitions_controller');
 require('controllers/main/alerts/alert_definitions_actions_controller');
+require('controllers/main/alerts/definition_details_controller');
 require('controllers/main/service');
 require('controllers/main/service/item');
 require('controllers/main/service/info/summary');

+ 1 - 3
ambari-web/app/controllers/main/alert_definitions_controller.js

@@ -101,8 +101,6 @@ App.MainAlertDefinitionsController = Em.ArrayController.extend({
     })
   }),
 
-  toggleState: Em.K,
-
-  gotoAlertDetails: Em.K
+  toggleState: Em.K
 
 });

+ 1 - 1
ambari-web/app/controllers/main/alerts/alert_definitions_actions_controller.js

@@ -18,7 +18,7 @@
 
 App.MainAlertDefinitionActionsController = Em.ArrayController.extend({
 
-  name: 'mainAlertDefinitionActions',
+  name: 'mainAlertDefinitionActionsController',
 
   /**
    * List of available actions for alert definitions

+ 22 - 0
ambari-web/app/controllers/main/alerts/definition_details_controller.js

@@ -0,0 +1,22 @@
+/**
+ * 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.
+ */
+
+App.MainAlertDefinitionDetailsController = Em.Controller.extend({
+
+  name: 'mainAlertDefinitionDetailsController'
+});

+ 8 - 0
ambari-web/app/messages.js

@@ -827,6 +827,14 @@ Em.I18n.translations = {
   'alerts.table.header.lastTriggered': 'Last Triggered',
   'alerts.filters.filteredAlertsInfo': '{0} of {1} alerts showing',
 
+  'alerts.thresholds': 'Thresholds',
+  'alerts.definition.details.enableDisable': 'Enable / Disable',
+  'alerts.definition.details.groups': 'Groups',
+  'alerts.definition.details.instances': 'Instances',
+  'alerts.definition.details.24-hour': '24-Hour',
+  'alerts.definition.details.notification': 'Notification',
+  'alerts.definition.details.noAlerts': 'No alert instances to show',
+
   'admin.advanced.caution':'This section is for advanced user only.<br/>Proceed with caution.',
   'admin.advanced.button.uninstallIncludingData':'Uninstall cluster including all data.',
   'admin.advanced.button.uninstallKeepData':'Uninstall cluster but keep data.',

+ 1 - 1
ambari-web/app/models.js

@@ -41,6 +41,7 @@ require('models/service/hbase');
 require('models/service/flume');
 require('models/service/storm');
 require('models/alert');
+require('models/alert_definition');
 require('models/alert_instance');
 require('models/alert_notification');
 require('models/user');
@@ -58,4 +59,3 @@ require('classes/run_class');
 require('classes/job_class');
 require('models/config_group');
 require('models/service_config_version');
-require('models/alertDefinition');

+ 38 - 2
ambari-web/app/models/alertDefinition.js → ambari-web/app/models/alert_definition.js

@@ -18,6 +18,7 @@
 
 var App = require('app');
 var dateUtils = require('utils/date');
+var dataUtils = require('utils/data_manipulation');
 
 App.AlertDefinition = DS.Model.extend({
 
@@ -36,10 +37,45 @@ App.AlertDefinition = DS.Model.extend({
    * Formatted timestamp for latest alert triggering for current alertDefinition
    * @type {string}
    */
-  lastTriggered: function() {
+  lastTriggered: function () {
     return dateUtils.dateFormat(Math.max.apply(Math, this.get('alerts').mapProperty('latestTimestamp')));
-  }.property('alerts.@each.latestTimestamp')
+  }.property('alerts.@each.latestTimestamp'),
 
+  /**
+   * Status generates from child-alerts
+   * Format: 1 OK / 2 WARN / 1 CRIT / 1 DISABLED / 1 UNKNOWN
+   * If some there are no alerts with some state, this state isn't shown
+   * Order is equal to example
+   * @type {string}
+   */
+  status: function () {
+    var typeIcons = this.get('typeIcons'),
+        ordered = ['OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'],
+        grouped = dataUtils.groupPropertyValues(this.get('alerts'), 'state');
+    return ordered.map(function (state) {
+      if (grouped[state]) {
+        return grouped[state].length + ' <span class="' + typeIcons[state] + ' alert-state-' + state + '"></span>';
+      }
+      return null;
+    }).compact().join(' / ');
+  }.property('alerts.@each.state'),
+
+  /**
+   * List of css-classes for alert types
+   * @type {object}
+   */
+  typeIcons: {
+    'OK': 'icon-ok-sign',
+    'WARNING': 'icon-warning-sign',
+    'CRITICAL': 'icon-remove',
+    'DISABLED': 'icon-off',
+    'UNKNOWN': 'icon-question-sign'
+  },
+
+  // todo: in future be mapped from server response
+  description: 'Description for the Alert Definition.',
+  // todo: in future be mapped from server response
+  thresholds: 'Thresholds for the Alert Definition.'
 });
 
 App.AlertReportDefinition = DS.Model.extend({

+ 10 - 1
ambari-web/app/models/alert_instance.js

@@ -17,6 +17,7 @@
  */
 
 var App = require('app');
+var dateUtils = require('utils/date');
 
 App.AlertInstance = DS.Model.extend({
   id: DS.attr('number'),
@@ -32,7 +33,15 @@ App.AlertInstance = DS.Model.extend({
   instance: DS.attr('string'),
   state: DS.attr('string'),
   text: DS.attr('string'),
-  notification: DS.hasMany('App.AlertNotification')
+  notification: DS.hasMany('App.AlertNotification'),
+
+  /**
+   * Formatted timestamp for latest instance triggering
+   * @type {string}
+   */
+  lastTriggered: function() {
+    return dateUtils.dateFormat(this.get('latestTimestamp'));
+  }.property('latestTimestamp')
 });
 
 App.AlertInstance.FIXTURES = [

+ 6 - 7
ambari-web/app/routes/main.js

@@ -324,14 +324,10 @@ module.exports = Em.Route.extend({
 
     alertDetails: Em.Route.extend({
       route: '/:alert_id',
-      connectOutlets: function (router, host) {
-      },
-
-      index: Em.Route.extend({
-        route: '/'
-      })
+      connectOutlets: function (router, alert) {
+        router.get('mainController').connectOutlet('mainAlertDefinitionDetails', alert);
+      }
     })
-
   }),
 
   admin: Em.Route.extend({
@@ -675,5 +671,8 @@ module.exports = Em.Route.extend({
     router.get('mainHostDetailsController').set('referer', router.location.lastSetURL);
     router.get('mainHostDetailsController').set('isFromHosts', true);
     router.transitionTo('hosts.hostDetails.summary', event.context);
+  },
+  gotoAlertDetails: function (router, event) {
+    router.transitionTo('alerts.alertDetails', event.context);
   }
 });

+ 120 - 15
ambari-web/app/styles/alerts.less

@@ -15,20 +15,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 @import 'common.less';
 
-.alert-state-OK {color: @health-status-green;}
-.alert-state-WARNING {color: @health-status-red;}
-.alert-state-CRITICAL {color: @health-status-red;}
-.alert-state-DISABLED {color: #000;}
-.alert-state-UNKNOWN {color: @health-status-yellow;}
+.alert-state-OK {
+  color: @health-status-green;
+}
+
+.alert-state-WARNING {
+  color: @health-status-red;
+}
+
+.alert-state-CRITICAL {
+  color: @health-status-red;
+}
+
+.alert-state-DISABLED {
+  color: #000;
+}
+
+.alert-state-UNKNOWN {
+  color: @health-status-yellow;
+}
 
-#alerts-table {
+.alerts-table {
 
   margin-top: 10px;
   margin-bottom: 10px;
-  font-size: 13px\9;
+  font-size: 13px \9;
 
   .filter-row {
     th {
@@ -42,7 +55,9 @@
       padding: 8px;
     }
   }
+}
 
+#alert-definitions-table {
   a {
     &.disabled {
       color: #000;
@@ -57,19 +72,19 @@
 
   .col1,
   td:first-child + td,
-  th:first-child + th{
+  th:first-child + th {
     width: 18%;
-    .filter-input-width{
-      width:90%;
+    .filter-input-width {
+      width: 90%;
     }
   }
 
   .col2,
   td:first-child + td + td,
-  th:first-child + th + th{
+  th:first-child + th + th {
     width: 17%;
-    .filter-input-width{
-      width:90%;
+    .filter-input-width {
+      width: 90%;
     }
   }
   .col3,
@@ -80,8 +95,98 @@
 
   .col4,
   td:first-child + td + td + td + td,
-  th:first-child + th + th + th + th{
+  th:first-child + th + th + th + th {
+    width: 10%;
+  }
+}
+
+#alert-instances-table {
+  .col0,
+  td:first-child,
+  th:first-child {
     width: 10%;
   }
 
+  .col1,
+  td:first-child + td,
+  th:first-child + th {
+    width: 45%;
+  }
+
+  .col2,
+  td:first-child + td + td,
+  th:first-child + th + th {
+    width: 20%;
+  }
+  .col3,
+  td:first-child + td + td + td,
+  th:first-child + th + th + th {
+    width: 10%
+  }
+
+  .col4,
+  td:first-child + td + td + td + td,
+  th:first-child + th + th + th + th {
+    width: 15%;
+  }
+}
+
+#alert-definition-details {
+  .definition-details-block {
+    margin-top: 30px;
+    .multiline-text {
+      padding-top: 4px;
+    }
+  }
+  hr {
+    margin: 10px 0 0 0;
+  }
+  .edit-description-button:hover {
+    text-decoration: none;
+  }
+
+  .service-name {
+    margin-bottom: 30px;
+  }
+
+  .right-column {
+    text-align: right;
+    button {
+      margin-top: 10px;
+      width: 100%;
+    }
+    .properties-list {
+      margin-top: 50px;
+      text-align: left;
+    }
+  }
+  .instances-label {
+    color: #0088cc;
+  }
+  .definition-name, .definition-name input {
+    font-size: 20px;
+    font-weight: bold;
+  }
+
+  .definition-name input {
+    width: 99%;
+    margin-bottom: 0px;
+    margin-top: -4px;
+    margin-left: -5px;
+  }
+
+  .edit-buttons {
+    text-align: right;
+    margin-bottom: 20px;
+  }
+
+  textarea {
+    width: 99%;
+    height: 100px;
+    margin-left: -5px;
+  }
+
+  .edit-link {
+    cursor: pointer;
+  }
 }

+ 18 - 18
ambari-web/app/templates/main/alerts.hbs

@@ -23,7 +23,7 @@
 <div id="alerts">
   <div class="box-header row">
   </div>
-  <table class="table advanced-header-table table-bordered table-striped" id="alerts-table">
+  <table class="table advanced-header-table table-bordered table-striped alerts-table" id="alert-definitions-table">
     <thead>
       {{#view view.sortView classNames="label-row" contentBinding="view.filteredContent"}}
         {{view view.parentView.nameSort class="first"}}
@@ -41,23 +41,23 @@
     </tr>
     </thead>
     <tbody>
-      {{#if view.pageContent}}
-        {{#each alertDefinition in view.pageContent}}
-          {{#view view.AlertDefinitionView contentBinding="alertDefinition"}}
-            <td class="first">
-              <a href="#" {{action "gotoAlertDetails" alertDefinition target="controller"}}>{{alertDefinition.label}}</a>
-            </td>
-            <td>{{{view.status}}}</td>
-            <td>{{alertDefinition.service.serviceName}}</td>
-            <td>{{alertDefinition.lastTriggered}}</td>
-            <td class="last">
-              <a href="#" {{action "toggleState" alertDefinition target="controller"}} {{bindAttr class="alertDefinition.enabled:enabled:disabled"}}>
-                <span class="icon-off"></span>
-              </a>
-            </td>
-          {{/view}}
-        {{/each}}
-      {{else}}
+    {{#if view.pageContent}}
+      {{#each alertDefinition in view.pageContent}}
+        <tr>
+          <td class="first">
+            <a href="#" {{action "gotoAlertDetails" alertDefinition}}>{{alertDefinition.label}}</a>
+          </td>
+          <td>{{{alertDefinition.status}}}</td>
+          <td>{{alertDefinition.service.serviceName}}</td>
+          <td>{{alertDefinition.lastTriggered}}</td>
+          <td class="last">
+            <a href="#" {{action "toggleState" alertDefinition target="controller"}} {{bindAttr class="alertDefinition.enabled:enabled:disabled"}}>
+              <span class="icon-off"></span>
+            </a>
+          </td>
+        </tr>
+      {{/each}}
+    {{else}}
       <tr>
         <td class="first"></td>
         <td colspan="4">

+ 158 - 0
ambari-web/app/templates/main/alerts/definition_details.hbs

@@ -0,0 +1,158 @@
+{{!
+* 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.
+}}
+
+<div id="alert-definition-details">
+  <div class="row-fluid">
+    <div class="span9">
+      <div class="definition-name">
+        {{#if view.editing.label.isEditing}}
+          <div {{bindAttr class="view.editing.label.isError:error :control-group"}}>
+            {{view Em.TextField valueBinding="view.editing.label.value"}}
+          </div>
+          <div class="edit-buttons">
+            <button {{action cancelEdit view.editing.label target="view"}} class="btn">{{t common.cancel}}</button>
+            <button {{bindAttr disabled="view.editing.label.isError"}} {{action saveEdit view.editing.label target="view"}}
+                class="btn btn-primary">{{t common.save}}
+            </button>
+          </div>
+        {{else}}
+          {{{controller.content.status}}} {{controller.content.label}}
+        {{/if}}
+        {{#unless view.editing.label.isEditing}}
+          <a {{action edit view.editing.label target="view"}} class="edit-description-button"><i
+              class="icon-pencil"></i></a>
+        {{/unless}}
+      </div>
+
+      <div class="definition-details-block">
+        <strong>{{t common.description}}</strong>
+        {{#unless view.editing.description.isEditing}}
+          <a {{action edit view.editing.description target="view"}} class="pull-right edit-link">
+            <strong>{{t common.edit}}</strong>
+          </a>
+        {{/unless}}
+        <hr>
+        {{#if view.editing.description.isEditing}}
+          <div {{bindAttr class="view.editing.description.isError:error :control-group"}}>
+            {{view Em.TextArea valueBinding="view.editing.description.value"}}
+          </div>
+          <div class="edit-buttons">
+            <button {{action cancelEdit view.editing.description target="view"}}
+                class="btn">{{t common.cancel}}</button>
+            <button {{bindAttr disabled="view.editing.description.isError"}} {{action saveEdit view.editing.description target="view"}}
+                class="btn btn-primary">{{t common.save}}
+            </button>
+          </div>
+        {{else}}
+          <div class="multiline-text">
+            {{controller.content.description}}
+          </div>
+        {{/if}}
+      </div>
+      <div class="definition-details-block">
+        <strong>{{t alerts.thresholds}}</strong>
+        {{#unless view.editing.thresholds.isEditing}}
+          <a {{action edit view.editing.thresholds target="view"}} class="pull-right edit-link">
+            <strong>{{t common.edit}}</strong>
+          </a>
+        {{/unless}}
+        <hr>
+        {{#if view.editing.thresholds.isEditing}}
+          <div {{bindAttr class="view.editing.thresholds.isError:error :control-group"}}>
+            {{view Em.TextArea valueBinding="view.editing.thresholds.value"}}
+          </div>
+          <div class="edit-buttons">
+            <button {{action cancelEdit view.editing.thresholds target="view"}} class="btn">{{t common.cancel}}</button>
+            <button {{bindAttr disabled="view.editing.thresholds.isError"}} {{action saveEdit view.editing.thresholds target="view"}}
+                class="btn btn-primary">{{t common.save}}
+            </button>
+          </div>
+        {{else}}
+          <div class="multiline-text">
+            {{controller.content.thresholds}}
+          </div>
+        {{/if}}
+      </div>
+    </div>
+    <div class="span3 right-column">
+      <div class="service-name">
+        {{t common.service}}: <span class="label label-info">{{controller.content.service.serviceName}}</span>
+      </div>
+      <div>
+        <button {{action disableEnableAlertDefinition target="view"}} class="btn btn-primary"><i
+            class="icon-power-off"></i>&nbsp;{{t alerts.definition.details.enableDisable}}</button>
+        <button {{action deleteAlertDefinition target="view"}} class="btn btn-primary"><i
+            class="icon-trash"></i>&nbsp;{{t common.delete}}</button>
+      </div>
+      <div class="properties-list">
+        <span>{{t common.type}}: {{controller.content.type}}</span>
+        <table>
+          <tbody>
+          <tr>
+            <td>{{t alerts.definition.details.groups}}:</td>
+            <td>HDFS Default (Admins)</td>
+          </tr>
+          <tr>
+            <td></td>
+            <td><strong>Group2 (Infra)</strong></td>
+          </tr>
+          </tbody>
+        </table>
+        <span>{{t alerts.table.header.lastTriggered}} : {{controller.content.lastTriggered}}</span>
+      </div>
+    </div>
+  </div>
+  <div class="definition-details-block">
+    <span class="instances-label">
+      <strong>{{t alerts.definition.details.instances}}</strong>
+    </span>
+    <hr>
+    <div>
+      <table class="table table-bordered table-striped alerts-table" id="alert-instances-table">
+        <thead>
+        <tr>
+          <th class="first">{{t common.status}}</th>
+          <th>{{t common.host}}</th>
+          <th>{{t alerts.table.header.lastTriggered}}</th>
+          <th>{{t alerts.definition.details.24-hour}}</th>
+          <th>{{t alerts.definition.details.notification}}</th>
+        </tr>
+        </thead>
+        <tbody>
+        {{#if view.pageContent}}
+          {{#each instance in view.pageContent}}
+            <tr>
+              <td class="first">{{instance.state}}</td>
+              <td><a {{action goToHostDetails instance.hostName target="view"}}>{{instance.hostName}}</a></td>
+              <td>{{instance.lastTriggered}}</td>
+              <td>10</td>
+              <td class="last">Admins</td>
+            </tr>
+          {{/each}}
+        {{else}}
+          <tr>
+            <td colspan="5">
+              {{t alerts.definition.details.noAlerts}}
+            </td>
+          </tr>
+        {{/if}}
+        </tbody>
+      </table>
+    </div>
+  </div>
+</div>

+ 1 - 0
ambari-web/app/views.js

@@ -42,6 +42,7 @@ require('views/login');
 require('views/main');
 require('views/main/menu');
 require('views/main/alert_definitions_view');
+require('views/main/alerts/definition_details_view');
 require('views/main/alerts/alert_definitions_actions_view');
 require('views/main/charts');
 require('views/main/views/details');

+ 2 - 44
ambari-web/app/views/main/alert_definitions_view.js

@@ -19,8 +19,7 @@
 var App = require('app');
 var filters = require('views/common/filter_view'),
   sort = require('views/common/sort_view'),
-  date = require('utils/date'),
-  dataUtils = require('utils/data_manipulation');
+  date = require('utils/date');
 
 App.MainAlertDefinitionsView = App.TableView.extend({
 
@@ -39,18 +38,6 @@ App.MainAlertDefinitionsView = App.TableView.extend({
 
   colPropAssoc: ['', 'label', 'state', 'service.serviceName', 'lastTriggered'],
 
-  /**
-   * List of css-classes for alert types
-   * @type {object}
-   */
-  typeIcons: {
-    'OK': 'icon-ok-sign',
-    'WARNING': 'icon-warning-sign',
-    'CRITICAL': 'icon-remove',
-    'DISABLED': 'icon-off',
-    'UNKNOWN': 'icon-question-sign'
-  },
-
   sortView: sort.wrapperView,
 
   /**
@@ -209,36 +196,7 @@ App.MainAlertDefinitionsView = App.TableView.extend({
     if (this.get('paginationRightClass') === 'paginate_next') {
       this._super();
     }
-  },
-
-  /**
-   * View for each table row with <code>alertDefinition</code>
-   * @type {Em.View}
-   */
-  AlertDefinitionView: Em.View.extend({
-
-    tagName: 'tr',
-
-    /**
-     * Status generates from child-alerts
-     * Format: 1 OK / 2 WARN / 1 CRIT / 1 DISABLED / 1 UNKNOWN
-     * If some there are no alerts with some state, this state isn't shown
-     * Order is equal to example
-     * @type {string}
-     */
-    status: function () {
-      var typeIcons = this.get('parentView.typeIcons'),
-        ordered = ['OK', 'WARNING', 'CRITICAL', 'DISABLED', 'UNKNOWN'],
-        grouped = dataUtils.groupPropertyValues(this.get('content.alerts'), 'state');
-      return ordered.map(function (state) {
-        if (grouped[state]) {
-          return grouped[state].length + ' <span class="' + typeIcons[state] + ' alert-state-' + state + '"></span>';
-        }
-        return null;
-      }).compact().join(' / ');
-    }.property('content.alerts.@each.status')
-
-  })
+  }
 
 
 });

+ 136 - 0
ambari-web/app/views/main/alerts/definition_details_view.js

@@ -0,0 +1,136 @@
+/**
+ * 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.MainAlertDefinitionDetailsView = App.TableView.extend({
+
+  templateName: require('templates/main/alerts/definition_details'),
+
+  didInsertElement: function () {
+    this.filter();
+  },
+
+  content: function () {
+    // todo: replace with this.get('controller.content.alerts') when this relationship will be provided
+    return App.AlertInstance.find().toArray();
+  }.property(),
+
+  // stores object with editing form data (label, description, thresholds)
+  editing: Em.Object.create({
+    label: Em.Object.create({
+      isEditing: false,
+      value: '',
+      originalValue: '',
+      isError: false,
+      bindingValue: 'controller.content.label'
+    }),
+    description: Em.Object.create({
+      isEditing: false,
+      value: '',
+      originalValue: '',
+      isError: false,
+      bindingValue: 'controller.content.description'
+    }),
+    thresholds: Em.Object.create({
+      isEditing: false,
+      value: '',
+      originalValue: '',
+      isError: false,
+      bindingValue: 'controller.content.thresholds'
+    })
+  }),
+
+  /**
+   * Validation function to define if label field populated correctly
+   */
+  labelValidation: function () {
+    this.set('editing.label.isError', !this.get('editing.label.value').trim());
+  }.observes('editing.label.value'),
+
+  /**
+   * Validation function to define if description field populated correctly
+   */
+  descriptionValidation: function () {
+    this.set('editing.description.isError', !this.get('editing.description.value').trim());
+  }.observes('editing.description.value'),
+
+  /**
+   * Validation function to define if thresholds field populated correctly
+   */
+  thresholsdValidation: function () {
+    this.set('editing.thresholds.isError', !this.get('editing.thresholds.value').trim());
+  }.observes('editing.thresholds.value'),
+
+  /**
+   * Edit button handler
+   * @param event
+   */
+  edit: function (event) {
+    var element = event.context;
+    var value = this.get(element.get('bindingValue'));
+    element.set('originalValue', value);
+    element.set('value', value);
+    element.set('isEditing', true);
+  },
+
+  /**
+   * Cancel button handler
+   * @param event
+   */
+  cancelEdit: function (event) {
+    var element = event.context;
+    element.set('value', element.get('originalValue'));
+    element.set('isEditing', false);
+  },
+
+  /**
+   * Save button handler
+   * @param event
+   */
+  saveEdit: function (event) {
+    var element = event.context;
+    // todo: add request to the server to save new value
+    this.set(element.get('bindingValue'), element.get('value'));
+    element.set('isEditing', false);
+  },
+
+  /**
+   * "Delete" button handler
+   * @param event
+   */
+  deleteAlertDefinition: function (event) {
+    // todo: provide deleting of alert definition
+  },
+
+  /**
+   * "Disable / Enable" button handler
+   * @param event
+   */
+  disableEnableAlertDefinition: function (event) {
+    // todo: provide disabling/enabling of alert definition
+  },
+
+  /**
+   * Router transition to host level alerts page
+   * @param event
+   */
+  goToHostDetails: function (event) {
+    // todo: provide transition to host level alert details
+  }
+});