AMBARI-7238 Slider View: FE - Show alerts provided by alerts API. (atkach)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/c91fab72 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/c91fab72 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/c91fab72 Branch: refs/heads/branch-alerts-dev Commit: c91fab72a2290b537e2a4036d02239b8f23e2d0f Parents: d3bfd49 Author: atkach <atk...@hortonworks.com> Authored: Wed Sep 10 14:35:58 2014 +0300 Committer: atkach <atk...@hortonworks.com> Committed: Wed Sep 10 14:35:58 2014 +0300 ---------------------------------------------------------------------- .../resources/ui/app/assets/data/apps/apps.json | 124 +++++++++++ .../src/main/resources/ui/app/helpers/helper.js | 47 +++- .../ui/app/mappers/slider_apps_mapper.js | 79 ++++--- .../main/resources/ui/app/models/slider_app.js | 5 + .../resources/ui/app/models/slider_app_alert.js | 172 +++++++++++++++ .../resources/ui/app/styles/application.less | 60 ++++++ .../ui/app/templates/slider_app/summary.hbs | 27 ++- .../src/main/resources/ui/app/translations.js | 14 ++ .../ui/app/views/slider_app/summary_view.js | 20 +- .../slider/src/main/resources/ui/config.js | 1 + .../ui/vendor/scripts/common/jquery.timeago.js | 214 +++++++++++++++++++ 11 files changed, 732 insertions(+), 31 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/assets/data/apps/apps.json ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/assets/data/apps/apps.json b/contrib/views/slider/src/main/resources/ui/app/assets/data/apps/apps.json index 60cc831..d774387 100644 --- a/contrib/views/slider/src/main/resources/ui/app/assets/data/apps/apps.json +++ b/contrib/views/slider/src/main/resources/ui/app/assets/data/apps/apps.json @@ -18,6 +18,68 @@ "version" : "1.0.0", "view_name" : "SLIDER", "yarnId" : "application_1409333994422_0005", + "alerts" : { + "detail" : [ + { + "description" : "alert1", + "host_name" : "Ñ6402.ambari.apache.org", + "last_status" : "CRITICAL", + "last_status_time" : 1409689349, + "service_name" : "HBASE", + "status" : "OK", + "status_time" : 1409703869, + "output" : "Connection refused", + "actual_status" : "CRITICAL" + }, + { + "description" : "alert2", + "host_name" : "Ñ6402.ambari.apache.org", + "last_status" : "OK", + "last_status_time" : 1409334745, + "service_name" : "HBASE", + "status" : "WARNING", + "status_time" : 1409703865, + "output" : "TCP OK - 0.001 second response time on port 9083", + "actual_status" : "OK" + }, + { + "description" : "NameNode edit logs directory status on dev01.hortonworks.com", + "host_name" : "dev01.hortonworks.com", + "last_status" : "WARNING", + "last_status_time" : 1389788011, + "service_name" : "HBASE", + "status" : "CRITICAL", + "status_time" : 1389793171, + "output" : "WARNING: NameNode directory status not available via http://dev01.hortonworks.com:50070/jmx url, code 0" + }, + { + "description" : "NameNode host CPU utilization on dev01.hortonworks.com", + "host_name" : "dev01.hortonworks.com", + "last_status" : "OK", + "last_status_time" : 1389636534, + "service_name" : "HBASE", + "status" : "PASSIVE", + "status_time" : 1389793060, + "output" : "1 CPU, load 19.0% < 200% : OK" + }, + { + "description" : "App Timeline Server Web UI", + "host_name" : "dev01.hortonworks.com", + "last_status" : "WARNING", + "last_status_time" : 1389636534, + "service_name" : "HBASE", + "status" : "UNKNOWN", + "status_time" : 1389793060, + "output" : "1 CPU, load 19.0% < 200% : OK" + } + ], + "summary" : { + "CRITICAL" : 1, + "OK" : 1, + "PASSIVE" : 0, + "WARNING" : 0 + } + }, "components" : { "HBASE_MASTER" : { "componentName" : "HBASE_MASTER", @@ -403,6 +465,68 @@ "version" : "1.0.0", "view_name" : "SLIDER", "yarnId" : "application_1409333994422_0009", + "alerts" : { + "detail" : [ + { + "description" : "alert1", + "host_name" : "Ñ6402.ambari.apache.org", + "last_status" : "CRITICAL", + "last_status_time" : 1409689349, + "service_name" : "HBASE", + "status" : "OK", + "status_time" : 1409703869, + "output" : "Connection refused", + "actual_status" : "CRITICAL" + }, + { + "description" : "alert2", + "host_name" : "Ñ6402.ambari.apache.org", + "last_status" : "OK", + "last_status_time" : 1409334745, + "service_name" : "HBASE", + "status" : "WARNING", + "status_time" : 1409703865, + "output" : "TCP OK - 0.001 second response time on port 9083", + "actual_status" : "OK" + }, + { + "description" : "NameNode edit logs directory status on dev01.hortonworks.com", + "host_name" : "dev01.hortonworks.com", + "last_status" : "WARNING", + "last_status_time" : 1389788011, + "service_name" : "HBASE", + "status" : "CRITICAL", + "status_time" : 1389793171, + "output" : "WARNING: NameNode directory status not available via http://dev01.hortonworks.com:50070/jmx url, code 0" + }, + { + "description" : "NameNode host CPU utilization on dev01.hortonworks.com", + "host_name" : "dev01.hortonworks.com", + "last_status" : "OK", + "last_status_time" : 1389636534, + "service_name" : "HBASE", + "status" : "PASSIVE", + "status_time" : 1389793060, + "output" : "1 CPU, load 19.0% < 200% : OK" + }, + { + "description" : "App Timeline Server Web UI", + "host_name" : "dev01.hortonworks.com", + "last_status" : "WARNING", + "last_status_time" : 1389636534, + "service_name" : "HBASE", + "status" : "UNKNOWN", + "status_time" : 1389793060, + "output" : "1 CPU, load 19.0% < 200% : OK" + } + ], + "summary" : { + "CRITICAL" : 1, + "OK" : 1, + "PASSIVE" : 0, + "WARNING" : 0 + } + }, "components" : { "HBASE_MASTER" : { "componentName" : "HBASE_MASTER", http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/helpers/helper.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/helpers/helper.js b/contrib/views/slider/src/main/resources/ui/app/helpers/helper.js index 88dfcb7..3f47ffd 100644 --- a/contrib/views/slider/src/main/resources/ui/app/helpers/helper.js +++ b/contrib/views/slider/src/main/resources/ui/app/helpers/helper.js @@ -100,4 +100,49 @@ App.registerBoundHelper('humanize', Em.View.extend({ var content = this.get('content'); return content && (content[0].toUpperCase() + content.slice(1)).replace(/([A-Z])/g, ' $1').replace(/_/g, ' '); }.property('content') -})); \ No newline at end of file +})); + +/** + * Ambari overrides the default date transformer. + * This is done because of the non-standard data + * sent. For example Nagios sends date as "12345678". + * The problem is that it is a String and is represented + * only in seconds whereas Javascript's Date needs + * milliseconds representation. + */ +DS.attr.transforms = { + date: { + from: function (serialized) { + var type = typeof serialized; + if (type === 'string') { + serialized = parseInt(serialized); + type = typeof serialized; + } + if (type === 'number') { + if (!serialized) { //serialized timestamp = 0; + return 0; + } + // The number could be seconds or milliseconds. + // If seconds, then the length is 10 + // If milliseconds, the length is 13 + if (serialized.toString().length < 13) { + serialized = serialized * 1000; + } + return new Date(serialized); + } else if (serialized === null || serialized === undefined) { + // if the value is not present in the data, + // return undefined, not null. + return serialized; + } else { + return null; + } + }, + to: function (deserialized) { + if (deserialized instanceof Date) { + return deserialized.getTime(); + } else { + return null; + } + } + } +}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/mappers/slider_apps_mapper.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/mappers/slider_apps_mapper.js b/contrib/views/slider/src/main/resources/ui/app/mappers/slider_apps_mapper.js index 23d8a8e..6b83eea 100644 --- a/contrib/views/slider/src/main/resources/ui/app/mappers/slider_apps_mapper.js +++ b/contrib/views/slider/src/main/resources/ui/app/mappers/slider_apps_mapper.js @@ -27,7 +27,7 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { * @method load * @return {$.ajax} */ - load: function() { + load: function () { return App.ajax.send({ name: 'mapper.applicationApps', sender: this, @@ -37,18 +37,47 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { /** * Parse loaded data + * Load <code>App.Alert</code> model + * @param {object} data received from server data + * @method parse + */ + parseAlerts: function (data) { + var alerts = [], + appId = data.id; + + if (data.alerts && data.alerts.detail) { + data.alerts.detail.forEach(function (alert) { + alerts.push({ + id: appId + alert.description, + title: alert.description, + serviceName: alert.service_name, + status: alert.status, + message: alert.output, + hostName: alert.host_name, + lastTime: alert.status_time, + appId: appId, + lastCheck: alert.last_status_time + }); + }); + App.SliderApp.store.pushMany('sliderAppAlert', alerts); + } + return alerts.mapProperty('id'); + }, + + /** + * Parse loaded data * Load <code>App.SliderAppComponent</code> model * @param {object} data received from server data * @method parse */ - parseComponents: function(data) { + parseComponents: function (data) { var components = [], - appId = data.id; + appId = data.id; Object.keys(data.components).forEach(function (key) { var component = data.components[key]; activeContainers = Object.keys(component.activeContainers); - for(var i= 0; i < component.instanceCount; i++){ + for (var i = 0; i < component.instanceCount; i++) { components.pushObject( Ember.Object.create({ id: appId + component.componentName + i, @@ -70,7 +99,7 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { * @param {object} data received from server data * @method parse */ - parseConfigs : function(data) { + parseConfigs: function (data) { var configs = {}; Object.keys(data.configs).forEach(function (key) { configs[key] = data.configs[key]; @@ -84,25 +113,25 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { * @param {object} data received from server data * @method parse */ - parseQuickLinks : function(data) { + parseQuickLinks: function (data) { var quickLinks = [], - appId = data.id; + appId = data.id; quickLinks.push( Ember.Object.create({ id: 'YARN application', label: 'YARN application', - url: "http://"+window.location.hostname+":8088" + url: "http://" + window.location.hostname + ":8088" }) ); - if(!data.urls){ + if (!data.urls) { return quickLinks.mapProperty('id'); } Object.keys(data.urls).forEach(function (key) { quickLinks.push( Ember.Object.create({ - id: appId+key, + id: appId + key, label: key, url: data.urls[key] }) @@ -112,9 +141,9 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { return quickLinks.mapProperty('id'); }, - parseObject: function(o) { + parseObject: function (o) { if (Ember.typeOf(o) !== 'object') return []; - return Ember.keys(o).map(function(key) { + return Ember.keys(o).map(function (key) { return {key: key, value: o[key]}; }); }, @@ -125,22 +154,23 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { * @param {object} data received from server data * @method parse */ - parse: function(data) { + parse: function (data) { var apps = [], - self = this, - appsToDelete = App.SliderApp.store.all('sliderApp').get('content').mapProperty('id'); + self = this, + appsToDelete = App.SliderApp.store.all('sliderApp').get('content').mapProperty('id'); - data.items.forEach(function(app) { + data.items.forEach(function (app) { var componentsId = app.components ? self.parseComponents(app) : [], - configs = app.configs ? self.parseConfigs(app) : {}, - quickLinks = self.parseQuickLinks(app), - jmx = self.parseObject(app.jmx), - masterActiveTime = jmx.findProperty('key', 'MasterActiveTime'), - masterStartTime = jmx.findProperty('key', 'MasterStartTime'); - if(masterActiveTime){ + configs = app.configs ? self.parseConfigs(app) : {}, + quickLinks = self.parseQuickLinks(app), + alerts = self.parseAlerts(app), + jmx = self.parseObject(app.jmx), + masterActiveTime = jmx.findProperty('key', 'MasterActiveTime'), + masterStartTime = jmx.findProperty('key', 'MasterStartTime'); + if (masterActiveTime) { masterActiveTime.value = new Date(Date.now() - masterActiveTime.value).getHours() + "h:" + new Date(Date.now() - masterActiveTime.value).getMinutes() + "m"; } - if(masterStartTime){ + if (masterStartTime) { masterStartTime.value = (new Date(parseInt(masterStartTime.value)).toUTCString()); } apps.push( @@ -157,6 +187,7 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { description: app.description || "-", components: componentsId, quickLinks: quickLinks, + alerts: alerts, configs: configs, jmx: jmx, runtimeProperties: app.configs @@ -168,7 +199,7 @@ App.SliderAppsMapper = App.Mapper.createWithMixins(App.RunPeriodically, { appsToDelete.forEach(function (app) { var appRecord = App.SliderApp.store.getById('sliderApp', app); - if(appRecord){ + if (appRecord) { appRecord.destroyRecord(); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/models/slider_app.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/models/slider_app.js b/contrib/views/slider/src/main/resources/ui/app/models/slider_app.js index 69f2479..6d6bfcc 100644 --- a/contrib/views/slider/src/main/resources/ui/app/models/slider_app.js +++ b/contrib/views/slider/src/main/resources/ui/app/models/slider_app.js @@ -74,6 +74,11 @@ App.SliderApp = DS.Model.extend({ quickLinks: DS.hasMany('quickLink', {async:true}), /** + * @type {App.SliderAppAlert[]} + */ + alerts: DS.hasMany('sliderAppAlert', {async:true}), + + /** * @type {App.TypedProperty[]} */ runtimeProperties: DS.hasMany('typedProperty', {async:true}), http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/models/slider_app_alert.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/models/slider_app_alert.js b/contrib/views/slider/src/main/resources/ui/app/models/slider_app_alert.js new file mode 100644 index 0000000..c8cf0bd --- /dev/null +++ b/contrib/views/slider/src/main/resources/ui/app/models/slider_app_alert.js @@ -0,0 +1,172 @@ +/** + * 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.SliderAppAlert = DS.Model.extend({ + /** + * @type {string} + */ + title: DS.attr('string'), + + /** + * @type {string} + */ + serviceName: DS.attr('string'), + + /** + * @type {string} + */ + status: DS.attr('string'), + + /** + * @type {string} + */ + message: DS.attr('string'), + + /** + * @type {string} + */ + hostName: DS.attr('string'), + + /** + * @type {number} + */ + lastTime: DS.attr('number'), + + /** + * @type {number} + */ + lastCheck: DS.attr('number'), + + /** + * @type {App.SliderApp} + */ + appId: DS.belongsTo('sliderApp'), + + /** + * @type {string} + */ + iconClass: function () { + var statusMap = Em.Object.create({ + 'OK': 'icon-ok', + 'WARNING': 'icon-warning-sign', + 'CRITICAL': 'icon-remove', + 'PASSIVE': 'icon-medkit' + }); + return statusMap.getWithDefault(this.get('status'), 'icon-question-sign'); + }.property('status'), + + /** + * @type {object} + */ + date: function () { + return DS.attr.transforms.date.from(this.get('lastTime')); + }.property('lastTime'), + + /** + * Provides how long ago this alert happened. + * + * @type {String} + */ + timeSinceAlert: function () { + var d = this.get('date'); + var timeFormat; + if (d) { + switch (this.get('status')) { + case "0": + timeFormat = Em.I18n.t('sliderApp.alerts.OK.timePrefix'); + break; + case "1": + timeFormat = Em.I18n.t('sliderApp.alerts.WARN.timePrefix'); + break; + case "2": + timeFormat = Em.I18n.t('sliderApp.alerts.CRIT.timePrefix'); + break; + case "3": + timeFormat = Em.I18n.t('sliderApp.alerts.MAINT.timePrefix'); + break; + default: + timeFormat = Em.I18n.t('sliderApp.alerts.UNKNOWN.timePrefix'); + break; + } + var prevSuffix = $.timeago.settings.strings.suffixAgo; + $.timeago.settings.strings.suffixAgo = ''; + var since = timeFormat.format($.timeago(this.makeTimeAtleastMinuteAgo(d))); + $.timeago.settings.strings.suffixAgo = prevSuffix; + return since; + } else if (d == 0) { + switch (this.get('status')) { + case "0": + timeFormat = Em.I18n.t('sliderApp.alerts.OK.timePrefixShort'); + break; + case "1": + timeFormat = Em.I18n.t('sliderApp.alerts.WARN.timePrefixShort'); + break; + case "2": + timeFormat = Em.I18n.t('sliderApp.alerts.CRIT.timePrefixShort'); + break; + case "3": + timeFormat = Em.I18n.t('sliderApp.alerts.MAINT.timePrefixShort'); + break; + default: + timeFormat = Em.I18n.t('sliderApp.alerts.UNKNOWN.timePrefixShort'); + } + return timeFormat; + } else { + return ""; + } + }.property('date', 'status'), + + /** + * + * @param d + * @return {object} + */ + makeTimeAtleastMinuteAgo: function (d) { + var diff = (new Date).getTime() - d.getTime(); + if (diff < 60000) { + diff = 60000 - diff; + return new Date(d.getTime() - diff); + } + return d; + }, + + /** + * Provides more details about when this alert happened. + * + * @type {String} + */ + timeSinceAlertDetails: function () { + var details = ""; + var date = this.get('date'); + if (date) { + var dateString = date.toDateString(); + dateString = dateString.substr(dateString.indexOf(" ") + 1); + dateString = Em.I18n.t('sliderApp.alerts.occurredOn').format(dateString, date.toLocaleTimeString()); + details += dateString; + } + var lastCheck = this.get('lastCheck'); + if (lastCheck) { + lastCheck = new Date(lastCheck * 1000); + details = details ? details + Em.I18n.t('sliderApp.alerts.brLastCheck').format($.timeago(lastCheck)) : Em.I18n.t('sliderApp.alerts.lastCheck').format($.timeago(lastCheck)); + } + return details; + }.property('lastCheck', 'date') + +}); + +App.SliderAppAlert.FIXTURES = []; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/styles/application.less ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/styles/application.less b/contrib/views/slider/src/main/resources/ui/app/styles/application.less index 3eb6d55..11ec39b 100644 --- a/contrib/views/slider/src/main/resources/ui/app/styles/application.less +++ b/contrib/views/slider/src/main/resources/ui/app/styles/application.less @@ -608,3 +608,63 @@ a { margin-top: 20px; } } + +.app-alerts { + overflow-y: auto; + ul { + padding-left: 0; + margin-bottom: 0; + } + li { + border-bottom: 1px solid #eee; + list-style: none; + padding: 5px; + background-position: 14px 9px; + background-repeat: no-repeat; + .date-time { + float: right; + } + p { + margin-bottom: 2px; + } + .container-fluid { + padding-left: 10px; + padding-right: 10px; + } + .title { + font-weight: normal; + font-size: 13px; + } + .row-fluid [class*="span"] { + min-height: 0px; + } + .status-icon { + padding-top: 7px; + } + .date-time { + color: #999; + font-style: italic; + font-size: small; + text-align: right; + } + .message { + font-size: 12px; + color: #777; + } + .serviceLink { + padding-left: 7px; + } + } + .icon-ok { + color: #5AB400; + } + .icon-remove { + color: #FF4B4B; + } + .icon-warning-sign { + color: #FDB82F; + } + .icon-question-sign { + color: #999; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/templates/slider_app/summary.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/templates/slider_app/summary.hbs b/contrib/views/slider/src/main/resources/ui/app/templates/slider_app/summary.hbs index 974186d..0ba3998 100644 --- a/contrib/views/slider/src/main/resources/ui/app/templates/slider_app/summary.hbs +++ b/contrib/views/slider/src/main/resources/ui/app/templates/slider_app/summary.hbs @@ -69,14 +69,35 @@ <div class="panel-heading"> Alerts <div class="btn-group pull-right panel-link"> - <a class="btn btn-default btn-sm" target="_blank" rel="tooltip" +{{!-- <a class="btn btn-default btn-sm" target="_blank" rel="tooltip" {{translateAttr title="sliderApp.summary.go_to_nagios"}} {{bindAttr href="view.nagiosUrl"}}> <i class="icon-link"></i> - </a> + </a>--}} </div> </div> - <div class="panel-body"> + <div class="app-alerts"> + <ul> + {{#each controller.model.alerts}} + {{#view view.AlertView contentBinding="this"}} + <div class="container-fluid"> + <div class="row"> + <div class="col-md-1 status-icon"> + <i {{bindAttr class="iconClass :icon-large"}}></i> + </div> + <div class="col-md-11"> + <div class="row"> + <div class="col-md-7 title">{{title}}</div> + + <div {{bs-bind-tooltip view.tooltip}} data-placement="right" class="col-md-5 date-time">{{timeSinceAlert}}</div> + </div> + <div class="message">{{message}}</div> + </div> + </div> + </div> + {{/view}} + {{/each}} + </ul> </div> </div> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/translations.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/translations.js b/contrib/views/slider/src/main/resources/ui/app/translations.js index 4af412a..e5e6a79 100644 --- a/contrib/views/slider/src/main/resources/ui/app/translations.js +++ b/contrib/views/slider/src/main/resources/ui/app/translations.js @@ -79,6 +79,20 @@ Em.I18n.translations = { 'sliderApp.summary.go_to_nagios': 'Go to Nagios', 'sliderApp.summary.go_to_ganglia': 'Go to Ganglia', + 'sliderApp.alerts.OK.timePrefixShort': 'OK', + 'sliderApp.alerts.WARN.timePrefixShort': 'WARN', + 'sliderApp.alerts.CRIT.timePrefixShort': 'CRIT', + 'sliderApp.alerts.MAINT.timePrefixShort': 'MAINT', + 'sliderApp.alerts.UNKNOWN.timePrefixShort': 'UNKNOWN', + 'sliderApp.alerts.OK.timePrefix': 'OK for {0}', + 'sliderApp.alerts.WARN.timePrefix': 'WARN for {0}', + 'sliderApp.alerts.CRIT.timePrefix': 'CRIT for {0}', + 'sliderApp.alerts.MAINT.timePrefix': 'MAINT for {0}', + 'sliderApp.alerts.UNKNOWN.timePrefix': 'UNKNOWN for {0}', + 'sliderApp.alerts.lastCheck': 'Last Checked {0}', + 'sliderApp.alerts.brLastCheck': "\nLast Checked {0}", + 'sliderApp.alerts.occurredOn': 'Occurred on {0}, {1}', + 'wizard.name': 'Create Slider App', 'wizard.step1.name': 'Select Type', 'wizard.step1.header': 'Available Types', http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/app/views/slider_app/summary_view.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/app/views/slider_app/summary_view.js b/contrib/views/slider/src/main/resources/ui/app/views/slider_app/summary_view.js index cb3b64a..1be586d 100644 --- a/contrib/views/slider/src/main/resources/ui/app/views/slider_app/summary_view.js +++ b/contrib/views/slider/src/main/resources/ui/app/views/slider_app/summary_view.js @@ -38,9 +38,23 @@ App.SliderAppSummaryView = Ember.View.extend({ return 'http://' + App.get('nagiosHost') + '/nagios'; }.property(), - fitPanels:function () { + fitPanels: function () { var heightLeft = parseInt(this.$('.panel-summury').css('height')); - this.$('.panel-components, .panel-alerts').css('height',((heightLeft<200)?200:heightLeft-20)/2); - }.on('didInsertElement') + this.$('.panel-components').css('height', ((heightLeft < 200) ? 200 : heightLeft - 20) / 2); + this.$('.panel-alerts .app-alerts').css('height', ((heightLeft < 200) ? 200 : heightLeft - 106) / 2); + }.on('didInsertElement'), + + AlertView: Em.View.extend({ + content: null, + tagName: 'li', + tooltip: function () { + var self = this; + return Ember.Object.create({ + trigger: 'hover', + content: this.get('content.timeSinceAlertDetails'), + placement: "right" + }); + }.property('content') + }) }); http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/config.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/config.js b/contrib/views/slider/src/main/resources/ui/config.js index 7acda84..e464128 100755 --- a/contrib/views/slider/src/main/resources/ui/config.js +++ b/contrib/views/slider/src/main/resources/ui/config.js @@ -37,6 +37,7 @@ exports.config = { 'vendor/scripts/common/jquery.ui.widget.js', 'vendor/scripts/common/jquery.ui.mouse.js', 'vendor/scripts/common/jquery.ui.sortable.js', + 'vendor/scripts/common/jquery.timeago.js', 'vendor/scripts/common/handlebars.js', 'vendor/scripts/development/ember.js', 'vendor/scripts/production/ember-data.js', http://git-wip-us.apache.org/repos/asf/ambari/blob/c91fab72/contrib/views/slider/src/main/resources/ui/vendor/scripts/common/jquery.timeago.js ---------------------------------------------------------------------- diff --git a/contrib/views/slider/src/main/resources/ui/vendor/scripts/common/jquery.timeago.js b/contrib/views/slider/src/main/resources/ui/vendor/scripts/common/jquery.timeago.js new file mode 100644 index 0000000..90e9553 --- /dev/null +++ b/contrib/views/slider/src/main/resources/ui/vendor/scripts/common/jquery.timeago.js @@ -0,0 +1,214 @@ +/** + * Timeago is a jQuery plugin that makes it easy to support automatically + * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). + * + * @name timeago + * @version 1.4.1 + * @requires jQuery v1.2.3+ + * @author Ryan McGeary + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://timeago.yarp.com/ + * + * Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) + */ + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + $.timeago = function(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords($.timeago.parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return inWords($.timeago.datetime(timestamp)); + } + }; + var $t = $.timeago; + + $.extend($.timeago, { + settings: { + refreshMillis: 60000, + allowPast: true, + allowFuture: false, + localeTitle: false, + cutoff: 0, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + inPast: 'any moment now', + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }, + + inWords: function(distanceMillis) { + if(!this.settings.allowPast && ! this.settings.allowFuture) { + throw 'timeago allowPast and allowFuture settings can not both be set to false.'; + } + + var $l = this.settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (this.settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + if(!this.settings.allowPast && distanceMillis >= 0) { + return this.settings.strings.inPast; + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return $.trim([prefix, words, suffix].join(separator)); + }, + + parse: function(iso8601) { + var s = $.trim(iso8601); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + s = s.replace(/([\+\-]\d\d)$/," $100"); // +09 -> +0900 + return new Date(s); + }, + datetime: function(elem) { + var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); + return $t.parse(iso8601); + }, + isTime: function(elem) { + // jQuery's `is()` doesn't play well with HTML5 in IE + return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); + } + }); + + // functions that can be called via $(el).timeago('action') + // init is default when no action is given + // functions are called with context of a single element + var functions = { + init: function(){ + var refresh_el = $.proxy(refresh, this); + refresh_el(); + var $s = $t.settings; + if ($s.refreshMillis > 0) { + this._timeagoInterval = setInterval(refresh_el, $s.refreshMillis); + } + }, + update: function(time){ + var parsedTime = $t.parse(time); + $(this).data('timeago', { datetime: parsedTime }); + if($t.settings.localeTitle) $(this).attr("title", parsedTime.toLocaleString()); + refresh.apply(this); + }, + updateFromDOM: function(){ + $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); + refresh.apply(this); + }, + dispose: function () { + if (this._timeagoInterval) { + window.clearInterval(this._timeagoInterval); + this._timeagoInterval = null; + } + } + }; + + $.fn.timeago = function(action, options) { + var fn = action ? functions[action] : functions.init; + if(!fn){ + throw new Error("Unknown function name '"+ action +"' for timeago"); + } + // each over objects here and call the requested function + this.each(function(){ + fn.call(this, options); + }); + return this; + }; + + function refresh() { + var data = prepareData(this); + var $s = $t.settings; + + if (!isNaN(data.datetime)) { + if ( $s.cutoff == 0 || Math.abs(distance(data.datetime)) < $s.cutoff) { + $(this).text(inWords(data.datetime)); + } + } + return this; + } + + function prepareData(element) { + element = $(element); + if (!element.data("timeago")) { + element.data("timeago", { datetime: $t.datetime(element) }); + var text = $.trim(element.text()); + if ($t.settings.localeTitle) { + element.attr("title", element.data('timeago').datetime.toLocaleString()); + } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { + element.attr("title", text); + } + } + return element.data("timeago"); + } + + function inWords(date) { + return $t.inWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); +})); \ No newline at end of file