public/rearview-src/js/view/expandedmonitor.js
define([
'view/base',
'view/deletemonitor',
'model/monitor',
'util/cron',
'parsley'
], function(
BaseView,
DeleteMonitorView,
JobModel,
CronUtil
){
var ExpandedMonitorView = BaseView.extend({
cronScheduleFormValid : true,
cronScheduleWeekdaysSelected : false,
cronScheduleDaysDisabled : false,
events : {
'click .name-save' : 'updateMonitorName',
'click .schedule-tab' : '_setExpandedViewHeight',
'click #cronScheduleFormEdit .day-month-picker .day-picker button' : 'cronWeekdayButtonClicked',
'change #selectPreviousError' : '_setErrorDropDown'
},
subscriptions : {
'view:dashboard:init' : 'exit',
'view:deletemonitor:delete' : 'deleteMonitor'
},
initialize : function(options) {
_.bindAll(this);
var self = this;
this.user = options.user;
this.router = options.router;
this.templar = options.templar;
// init delete monitor view
this.deleteMonitorView = new DeleteMonitorView({
'el' : $('.delete-monitor-wrap'),
'templar' : this.templar
});
// use debounce to throttle resize events and set the height when
// the viewport changes.
var resize = _.debounce(this._setExpandedViewHeight, 500);
// Add the event listener
$(window).resize(resize);
},
render : function(id, categories, categoryId, dashboardId) {
this.monitorId = id;
this.categories = categories;
this.categoryId = categoryId;
this.dashboardId = dashboardId;
this.initMonitor();
},
/**
* ExpandedMonitorView#initMonitor(model)
* - model (Object): job (monitor) backbone model object
*
*
**/
initMonitor : function() {
var self = this;
// retrieve monitor model & graph data
self.getMonitor(self.monitorId, function(result) {
// make sure update has a reference to the instance and model
// at the time of editing/updating
self.model = result;
_.bind(self.updateMonitor, self, self.model);
_.each(self.categories, function(category) {
if (category.id === self.model.get('dashboardId')) {
category.selected = true;
} else {
category.selected = false;
}
});
self.getGraphData(self.monitorId, function(result) {
self._initErrorsList(function(errors) {
self.templar.render({
path : 'expandedmonitor',
el : self.$el,
data : {
'monitor' : self.model.toJSON(),
'errors' : errors,
'alerts' : errors,
'output' : self.model.get('output'),
'categories' : self.categories
}
});
self.$monitor = self.$el.find('.expanded-monitor');
//edit-monitor-wrap container element
self.$el.html( self.$monitor );
// add events to the view buttons in the alert window
self._setAlertsHistory();
// setup the graph area
self.chart = self.initGraph( self.$monitor.find('.graph')[0]);
// set error state
self._setErrorState();
self.updateGraph(self.model, function(output) {
self._initializeCodeMirror();
self._initializeDatePicker('.input-from-date');
self._setErrorDropDown();
self._setScheduleData();
self._setEditNameField();
self.$el.find('.save-changes').click({'model' : self.model}, self.updateMonitor);
self.$el.find('.cancel-changes').click(function() {
self.exit();
});
self.$el.find('.test-in-graph button').click({
'model' : new JobModel(),
'output' : output
}, self.testMonitor, self);
// finally open up the edit monitor widget
self.open();
}, self.formattedGraphData);
// inline help
self.setHelp();
self.setCronScheduleValidation();
// dynamically set the heights to maximum screen utilization
self._setExpandedViewHeight();
}, self.model);
});
});
},
setCronScheduleWeekdaysSelected : function() {
this.cronScheduleWeekdaysSelected = $('#cronScheduleFormEdit .day-month-picker .day-picker button.active').size() > 0
},
setCronScheduleDaysDisabled : function(disabled) {
if(disabled) {
if(!this.cronScheduleDaysDisabled) {
$('#inputDays').parsley('ParsleyField').reset();
$('#inputDays').val('?');
$('#inputDays').attr('disabled',"");
this.cronScheduleDaysDisabled = true;
}
}
else {
if(this.cronScheduleDaysDisabled) {
$('#inputDays').val('*');
$('#inputDays').removeAttr('disabled');
this.cronScheduleDaysDisabled = false;
}
}
},
cronWeekdayButtonClicked : function(event) {
$(event.target).button('toggle');
this.setCronScheduleWeekdaysSelected();
this.setCronScheduleDaysDisabled(this.cronScheduleWeekdaysSelected);
},
setCronScheduleValidation : function() {
this.cronScheduleForm = $('#cronScheduleFormEdit');
var validator = CronUtil.parsleyValidator();
_.extend(validator,{
errors: {
container: function (parsleyElement, parsleyTemplate, isRadioOrCheckbox) {
var container = $('#cronScheduleFormEditErrors');
container.append(parsleyTemplate);
return container;
}
},
listeners: {
onFormSubmit : function ( isFormValid, event, ParsleyForm ) {
this.cronScheduleFormValid = isFormValid;
}.bind(this)
}
});
this.cronScheduleForm.parsley(validator);
},
getGraphData : function(monitorId, cb) {
var self = this;
$.ajax({
url : '/jobs/' + monitorId + '/data',
success : function(result) {
if ( result.graph_data ) {
self.formattedGraphData = self.formatGraphData( result.graph_data );
}
// always call the callback function
// it loads the view
if ( !_.isUndefined(cb) ) cb(result);
},
error : function(result) {
if ( !_.isUndefined(cb) ) {
cb(result);
}
}
});
},
getMonitor : function(monitorId, cb) {
var self = this;
self.model = new JobModel({
id : monitorId
});
self.model.fetch({
success : function(result) {
if ( !_.isUndefined(cb) ) {
cb(result);
}
}
});
},
getErrorStatus : function(status) {
var self = this,
error = false;
switch ( status ) {
case 'error' :
error = true;
break;
case 'failed' :
error = true;
break;
case 'graphite_error' :
error = true;
break;
case 'graphite_metric_error' :
error = true;
break;
case 'security_error' :
error = true;
break;
}
return error;
},
setHelp : function() {
var self = this,
$quickContent = '';
$alertContent = '';
$cronHelpContent = '';
$.ajax({
url : rearview.path + '/help/cron.html',
async : false,
success : function( response ) {
$cronHelpContent = response;
}
});
$.ajax({
url : rearview.path + '/help/quick.html',
async : false,
success : function( response ) {
$quickContent = response;
}
});
$.ajax({
url : rearview.path + '/help/alert.html',
async : false,
success : function( response ) {
$alertContent = response;
}
});
self.$el.find('#viewSchedule .help.label').tooltip({
trigger : 'click',
html : true,
placement : 'left',
delay : { show : 100, hide : 200 },
title : $cronHelpContent
});
self.$el.find('.help:nth-child(2)').tooltip({
trigger : 'click',
html : true,
placement : 'right',
delay : { show : 100, hide : 200 },
title : $quickContent
});
self.$el.find('#viewSettings .monitor-meta .pager-duty .help.label').tooltip({
trigger : 'click',
html : true,
placement : 'left',
delay : { show : 100, hide : 200 },
title : $alertContent
});
},
/**
* ExpandedMonitorView#open()
*
*
**/
open : function() {
this.$el.addClass('active');
//publish for other views can update
Backbone.Mediator.pub('view:expandedmonitor:open', {
'nav' : {
'back' : true
}
});
// set route for hotlinking purposes
this.router.navigate('dash/' + this.dashboardId + '/expand/' + this.model.get('id'));
},
/**
* ExpandedMonitorView#exit()
*
*
**/
exit : function(monitor) {
var self = this,
monitor = ( monitor ) ? monitor : null;
self.$el.removeClass('active');
// set route for hotlinking purposes
if(self.model) {
self.router.navigate('dash/' + self.model.get('dashboardId'));
}
// fire event that expandedmonitor is exiting
Backbone.Mediator.pub('view:expandedmonitor:exit', {
status : 'delete'
});
},
/**
* ExpandedMonitorView#updateGraph(model, cb)
* - model (Object): job (monitor) backbone model object
* - cb (Function): callback function
*
**/
updateGraph : function(model, cb, graphData) {
var self = this;
if(graphData) {
self.renderGraphData(self.chart, graphData);
if(typeof cb === 'function') {
cb();
}
} else {
$.ajax({
url : '/monitor.json',
type : 'POST',
data : model.toJSON(),
success : function(result) {
if ( result.graph_data ) {
var formattedGraphData = self.formatGraphData( result.graph_data );
self.renderGraphData(self.chart, formattedGraphData);
self.$el.find('textarea.output-view').val(result.output);
}
if(result.status === 'error') {
Backbone.Mediator.pub('view:expandedmonitor:test', {
'model' : this.model,
'errors' : result.errors,
'raw' : result.output,
'attention' : 'Monitor Test Error!',
'status' : 'error'
});
}
if(typeof cb === 'function') {
cb(result.output);
}
},
failure : function(result) {
}
});
}
},
/**
* ExpandedMonitorView#testMonitor(e)
*
*
**/
testMonitor : function(e) {
var self = this,
monitor = e.data.model,
output = e.data.output,
$testBtn = $(e.target);
// set button state
$testBtn.button('loading');
monitor = self._setMetrics(monitor);
monitor = self._setSchedule(monitor);
// update to the output view for testing
self.$el.find('textarea.output-view').val(output);
self.$el.find('.output-tab').tab('show');
self.updateGraph(monitor, function() {
$testBtn.button('reset');
});
},
/**
* ExpandedMonitorView#viewErrorInGraph(e)
*
*
**/
viewErrorInGraph : function(e) {
var self = this,
monitor = e.data.model,
output = e.data.output,
toDate = e.data.toDate;
monitor.set('toDate', toDate);
// viewError in graph gets the from
// toDate field not from the html field
monitor = self._setMetrics(monitor, true);
monitor = self._setSchedule(monitor);
// otherwise update to the output view for testing
self.$el.find('textarea.output-view').val(output);
self.$el.find('.output-tab').tab('show');
self.updateGraph(monitor);
},
/**
* ExpandedMonitorView#updateMonitor(e)
*
*
**/
updateMonitor : function(e) {
var self = this,
monitor = e.data.model,
test = e.data.test,
toDate = e.data.toDate,
output = e.data.output;
this.cronScheduleForm.parsley('validate');
if(this.cronScheduleFormValid) {
monitor = self._setMetrics(monitor);
monitor = self._setSchedule(monitor);
monitor = self._setSettings(monitor);
monitor.save(null, {
success : function(model, response, options) {
Backbone.Mediator.pub('view:expandedmonitor:save', {
'model' : self.model,
'message' : "The monitor '" + model.get('name') + "' was saved.",
'attention' : 'Monitor Saved!',
'status' : 'success'
});
// quit out of the edit monitor view
self.exit(monitor);
self.updateGraph(monitor);
},
error : function(model, xhr, options) {
Backbone.Mediator.pub('view:expandedmonitor:save', {
'model' : self.model,
'tryJSON' : xhr.responseText,
'attention' : 'Monitor Saved Error!',
'status' : 'error'
});
}
});
}
},
/**
* ExpandedMonitorView#deleteMonitor(e)
*
*
**/
deleteMonitor : function() {
var self = this;
self.model.destroy({
success : function(model, response) {
Backbone.Mediator.pub('view:expandedmonitor:delete', {
'model' : self.model,
'message' : "The monitor '" + model.get('name') + "' was removed from the current dashboard.",
'attention' : 'Monitor Deleted!'
});
},
error : function(model, response) {
Backbone.Mediator.pub('view:expandedmonitor:delete', {
'model' : self.model,
'message' : "The monitor '" + model.get('name') + "' produced an error during the process of deletion.",
'attention' : 'Monitor Delete Error!',
'status' : 'error'
});
}
});
self.exit();
},
/**
* ExpandedMonitorView#updateMonitorName()
*/
updateMonitorName : function() {
var self = this;
var $nameField = self.$el.find('.name-field'),
previousName = $nameField.prev().html();
self.model.set({
'name' : $nameField.find('input').val()
});
$nameField.prev().html( self.model.get('name') );
$nameField.hide();
$nameField.prev().show();
},
/**
* ExpandedMonitorView#resize()
*/
resize : function() {
var self = this;
return _.debounce(self._setExpandedViewHeight, 500);
},
/**
* ExpandedMonitorView#destructor()
*/
destructor : function() {
var self = this,
$prevSibling = self.$el.prev();
// clean up nested views
self.deleteMonitorView.destructor();
// unsubscribe from mediator channels
self.destroySubscriptions();
self.remove();
self.unbind();
self.off();
$prevSibling.after("<div class='edit-monitor-wrap clearfix'>");
},
/**
*
*/
_setExpandedViewHeight : function() {
var windowOffSet = 580,
alertsOffset = 6,
outputOffset = 4,
tabHeight = ( this.$el.find('#viewMetrics') )
? this.$el.find('#viewMetrics').height()
: null;
// tab height fixing
if ( tabHeight ) {
this.$el.find('#viewSchedule, #viewSettings').css({
'height' : tabHeight + 'px'
});
}
this.$el.find('.output-view').css({
'height' : ( $(window).height() - windowOffSet - outputOffset ) + 'px'
});
this.$el.find('.alerts-history ul').css({
'height' : ( $(window).height() - windowOffSet - alertsOffset ) + 'px'
});
this.$el.find('.graph').css({
'height' : ( $(window).height() - windowOffSet ) + 'px'
});
// set chart height dynamically
var chartHeight = ( ($(window).height() - windowOffSet) > 150 ) ? $(window).height() - windowOffSet : 150;
if (this.chart && chartHeight > 150) {
this.chart.setSize(null, chartHeight);
}
},
/**
*
*/
_setErrorState : function() {
var errorStatus = this.getErrorStatus(this.model.get('status'));
if( errorStatus ) {
this.$monitor.addClass('red');
} else {
this.$monitor.removeClass('red');
}
},
/** internal
* ExpandedMonitorView#_initErrorsList(cb)
*
*
**/
_initErrorsList : function(cb) {
var self = this,
monitorId = self.model.get('id');
$.get('/jobs/' + monitorId + '/errors', function(data) {
data = _.map(data, function(error) {
return {
label : self.formatServerDateTime(error.date, true).toString("MM/dd/yyyy HH:mm"),
value : error.date,
message : error.message,
id : error.id
}
});
cb(data);
});
},
/** internal
* ExpandedMonitorView#_setErrorDropDown()
**/
_setErrorDropDown : function() {
var self = this,
dropDown = this.$el.find('#selectPreviousError');
if ( dropDown.find('option:selected').val() !== '' ) {
var selectedErrorDateTime = dropDown.find('option:selected').html();
self.fromDatePicker.datetimepicker('setDate', selectedErrorDateTime);
}
},
/** internal
* ExpandedMonitorView#_setEditNameField()
*
*
**/
_setEditNameField : function() {
var self = this,
$nameField = self.$el.find('.name-field');
$nameField.prev().click(function() {
$nameField.prev().hide();
$nameField.show();
$nameField.find('input').focus();
});
},
/** internal
* ExpandedMonitorView#_initializeCodeMirror()
*
*
**/
_initializeCodeMirror : function() {
var self = this,
$expressions = self.$el.find('#inputExpressions')[0],
$metrics = self.$el.find('#inputMetrics')[0];
self.expressionsMirror = CodeMirror.fromTextArea( $expressions, {
value : '',
lineNumbers : true,
lineWrapping : true,
height : '100',
mode : 'ruby',
theme : 'ambiance',
onKeyEvent : function(i, e) {
if (( e.keyCode == 70 && e.ctrlKey ) && e.type == 'keydown') {
e.stop();
return self._toggleFullscreen('.expanded-monitor .expressions .CodeMirror', self.expressionsMirror);
}
}
});
self.metricsMirror = CodeMirror.fromTextArea( $metrics, {
value : '',
lineNumbers : true,
lineWrapping : true,
mode : 'ruby',
theme : 'ambiance',
onKeyEvent : function(i, e) {
if (( e.keyCode == 70 && e.ctrlKey ) && e.type == 'keydown') {
e.stop();
return self._toggleFullscreen('.expanded-monitor .metrics .CodeMirror', self.metricsMirror);
}
}
});
},
/** internal
* ExpandedMonitorView#_initializeDatePicker()
*
*
**/
_initializeDatePicker : function(selector) {
var self = this;
self.fromDatePicker = self.$el.find(selector).datetimepicker();
},
/** internal
* ExpandedMonitorView#_setScheduleData(model)
*
*
**/
_setScheduleData : function() {
var self = this,
cronExpr = self.model.get('cronExpr').split(' '); // space delimited cron job expression
self.$el.find('#inputSeconds').val( cronExpr[0] );
self.$el.find('#inputMinutes').val( cronExpr[1] );
self.$el.find('#inputHours').val( cronExpr[2] );
self.$el.find('#inputDays').val( cronExpr[3] );
var month = cronExpr[4].split(','),
weekday = cronExpr[5].split(',');
self._setButtonGroup( self.$el.find('.day-picker button'), weekday );
self._setButtonGroup( self.$el.find('.month-picker button'), month );
},
/** internal
* ExpandedMonitorView#_setButtonGroup(collection, dataList)
*
*
**/
_setButtonGroup : function(collection, dataList) {
collection.each(function(index, day) {
day = $(day);
var dataValue = _.indexOf(dataList, day.attr('data-value'))
if ( dataValue > -1 ) {
day.addClass('active');
}
});
},
/** internal
* ExpandedMonitorView#_setMetrics(model)
*
* Set metrics data to the job model.
**/
_setMetrics : function( model, errorView ) {
var self = this;
model.set({
'userId' : self.user.get('id'),
'monitorExpr' : self.expressionsMirror.getValue(),
'metrics' : self.metricsMirror.getValue().split('\n'),
'minutes' : parseInt(self.$el.find('.input-minutes-back').val()),
'toDate' : ( errorView ) ? model.get('toDate') : self.$el.find('.input-from-date').val()
});
return model;
},
/** internal
* ExpandedMonitorView#_setSchedule(model)
*
* Set scheduling data to the job model.
**/
_setSchedule : function( model ) {
var monitorName = this.$el.find('.monitor-name')[0]
? this.$el.find('.monitor-name').val()
: model.get('name');
// grab form data
model.set({
'userId' : this.user.get('id'),
'name' : monitorName,
'description' : this.$el.find('#description').val(),
'alertKeys' : this.parseAlertKeys( this.$el.find('.pager-duty textarea').val() ),
'cronExpr' : this._createCronExpr()
});
return model;
},
_setSettings : function( model ) {
model.set({
'dashboardId' : parseInt(this.$el.find('#selectCategory').val(), 10)
});
return model;
},
_setAlertsHistory : function() {
var self = this;
self.$monitor.find('.alerts-history ul').delegate( "li button", "click", function(e) {
self.viewErrorInGraph({
'data' : {
'model' : new JobModel(),
'test' : true,
'toDate': $(e.target).attr('data-date')
}
});
// copy datetime to from field
self.fromDatePicker.datetimepicker('setDate', $(e.target).attr('data-date'));
});
}
});
return ExpandedMonitorView;
});