rearview 1.2.0-jruby → 1.2.1-jruby

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/rearview/jobs_controller.rb +1 -0
  3. data/app/controllers/rearview/monitor_controller.rb +6 -2
  4. data/lib/generators/templates/README.md +3 -2
  5. data/lib/generators/templates/rearview.rb +1 -1
  6. data/lib/graphite/client.rb +2 -2
  7. data/lib/rearview/version.rb +1 -1
  8. data/public/rearview-src/help/cron.html +34 -0
  9. data/public/rearview-src/js/app.js +2 -1
  10. data/public/rearview-src/js/main.js +2 -2
  11. data/public/rearview-src/js/util/cron.js +50 -0
  12. data/public/rearview-src/js/view/addmonitor.js +111 -19
  13. data/public/rearview-src/js/view/expandedmonitor.js +112 -34
  14. data/public/rearview-src/less/rearview.less +64 -2
  15. data/public/rearview-src/templates/expandedmonitor.hbs +71 -43
  16. data/public/rearview-src/templates/schedulemonitor.hbs +36 -7
  17. data/public/rearview/build.txt +2 -1
  18. data/public/rearview/help/cron.html +34 -0
  19. data/public/rearview/js/app.js +1 -1
  20. data/public/rearview/js/main.js +23 -23
  21. data/public/rearview/js/util/cron.js +1 -0
  22. data/public/rearview/js/view/addmonitor.js +1 -1
  23. data/public/rearview/js/view/expandedmonitor.js +1 -1
  24. data/public/rearview/less/rearview.less +64 -2
  25. data/public/rearview/templates/expandedmonitor.hbs +71 -43
  26. data/public/rearview/templates/schedulemonitor.hbs +36 -7
  27. data/spec/controllers/dashboard_children_controller_spec.rb +4 -4
  28. data/spec/controllers/dashboards_controller_spec.rb +7 -7
  29. data/spec/controllers/jobs_controller_spec.rb +20 -11
  30. data/spec/controllers/monitor_controller_spec.rb +21 -4
  31. data/spec/controllers/user_controller_spec.rb +2 -2
  32. data/spec/dummy/log/development.log +90 -0
  33. data/spec/dummy/log/test.log +16754 -0
  34. data/spec/lib/graphite/client_spec.rb +9 -1
  35. data/spec/views/metric/create.json.jbuilder_spec.rb +31 -0
  36. metadata +8 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d9930818c3cb39841ee9774dbdee3ad09c4e72c4
4
- data.tar.gz: 0190f0a54cdebf77f29d161adeea0a9ea195a7d4
3
+ metadata.gz: caebe0965f0b933bfeab70bc95f3137fff502768
4
+ data.tar.gz: f776171448d8107b7ecc2288fba419047ed869fe
5
5
  SHA512:
6
- metadata.gz: a285f335cf44cb0d9fbfc174e419d9051544ab03a045a60cfa8529edb90752cf493625b957d9f983b50081ea063fcda6646454c7bd13ed40ef579a6b0b4c3503
7
- data.tar.gz: 17e8d45a1f71f0bac7028cb1204978f968f4bc63d30a178e409cba18d2471b8cf48ce19d50ebea818d3ac463ca4fa628692e29b9976db9a3e9c280b89dc57730
6
+ metadata.gz: cc87e87abf2be33a444f873648d2b7fb5006905ea776d194a0a5bd5382e06545a2c8ad0c8662867cc99c2c3f9604297f9b7abeab81eae7c2be480f1bf9314151
7
+ data.tar.gz: 1f287be94f0063f6b1fdcb78aecadd5567a3ee9597b4fb24131001f1920c1c2ffa1c84cad0a7a0787ad67d772a8461b2136934a6d976d243b11c60aff92ea815
@@ -36,6 +36,7 @@ module Rearview
36
36
  if dashboard_id.present? && dashboard_id.to_i!=@job.app_id
37
37
  @job.dashboard = Rearview::Dashboard.find(dashboard_id.to_i)
38
38
  end
39
+ params["alert_keys"] = [] if params["alert_keys"].blank?
39
40
  if @job.update_attributes(job_update_params)
40
41
  @job.sync_monitor_service
41
42
  render :show
@@ -11,8 +11,12 @@ module Rearview
11
11
  # monitor job model instead of custom params
12
12
  #
13
13
  def create
14
- metrics_validator = Rearview::MetricsValidator.new({attributes: [:metrics]})
15
- @errors = params[:metrics].inject([]) { |a,v| a << "Metrics contains an invalid metric: #{v}" unless(metrics_validator.metric_valid?(v)); a }
14
+ @errors = if params[:metrics].empty?
15
+ [ "No metrics were provided" ]
16
+ else
17
+ metrics_validator = Rearview::MetricsValidator.new({attributes: [:metrics]})
18
+ params[:metrics].inject([]) { |a,v| a << "Metrics contains an invalid metric: #{v}" unless(metrics_validator.metric_valid?(v)); a }
19
+ end
16
20
  results = if @errors.empty?
17
21
  Rearview::MonitorRunner.run(params[:metrics],params[:monitorExpr],params[:minutes],{},false,params[:toDate],true)
18
22
  else
@@ -6,7 +6,8 @@ Edit the following file:
6
6
 
7
7
  config/initializers/rearview.rb
8
8
 
9
- You must set **config.graphite_url** and **config.sandbox_exec** for rearview
10
- to run properly. Most of the other settings you should be able to leave as is.
9
+ You must set **config.graphite_connection** and **config.sandbox_exec**
10
+ for rearview to run properly. Most of the other settings you should be able to
11
+ leave as is.
11
12
 
12
13
  ===============================================================================
@@ -69,7 +69,7 @@ Rearview.configure do |config|
69
69
  # Set schedule for checking for invalid metrics (cron expression). Recommended only once per day.
70
70
  # see http://quartz-scheduler.org/api/2.0.0/org/quartz/CronExpression.html. Only necessary if
71
71
  # enable_metrics_validator is true.
72
- config.metrics_validator_schedule = '* * 23 * *'
72
+ config.metrics_validator_schedule = '0 0 23 * *'
73
73
 
74
74
  case Rails.env
75
75
  when "test"
@@ -25,7 +25,7 @@ module Graphite
25
25
  exists = false
26
26
  response = find_metric(metric)
27
27
  # Does this need to handle redirects?
28
- if response.status == 200 && response.headers['content-type']=='application/json'
28
+ if response.status == 200 && response.headers['content-type'].include?('application/json')
29
29
  json = JSON.parse(response.body)
30
30
  exists = true unless json.empty?
31
31
  end
@@ -35,7 +35,7 @@ module Graphite
35
35
  reachable = false
36
36
  begin
37
37
  response = connection.get('/render')
38
- reachable = response.status==200 && response.headers['content-type']=='image/png' && response.headers['content-length'].to_i > 0
38
+ reachable = response.status==200 && response.headers['content-type'].include?('image/png') && response.headers['content-length'].to_i > 0
39
39
  rescue Exception => e
40
40
  end
41
41
  reachable
@@ -1,3 +1,3 @@
1
1
  module Rearview
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -0,0 +1,34 @@
1
+ <table>
2
+ <tr>
3
+ <th>field</th>
4
+ <td>description</td>
5
+ </tr>
6
+ <tr>
7
+ <th>minutes</th>
8
+ <td>0-59 [, - * /]</td>
9
+ </tr>
10
+ <tr>
11
+ <th>hours</th>
12
+ <td>0-23 [, - * /]</td>
13
+ </tr>
14
+ <tr>
15
+ <th>days</th>
16
+ <td>1-31 [, - * ? / L W]</td>
17
+ </tr>
18
+ <tr>
19
+ <th>weekday</th>
20
+ <td>select which days to run (otherwise it will run everyday)</td>
21
+ </tr>
22
+ <tr>
23
+ <th>month</th>
24
+ <td>select which month to run (otherwise it will run everymonth)</td>
25
+ </tr>
26
+ </table>
27
+ <div class="cron-help-details">
28
+ <p>The '*' character is used to specify all values. For example, "*" in the minute field means "every minute".</p>
29
+ <p>The '?' character is allowed for the days field. It is used to specify 'no specific value'. This is useful when you need to specify something in one of the weekday field.</p>
30
+ <p>The '-' character is used to specify ranges For example "10-12" in the hour field means "the hours 10, 11 and 12".</p>
31
+ <p>The ',' character is used to specify additional values. For example "MON,WED,FRI" in the day-of-week field means "the days Monday, Wednesday, and Friday".</p>
32
+ <p>The '/' character is used to specify increments. For example "0/15" in the seconds field means "the seconds 0, 15, 30, and 45".</p>
33
+ <a href="http://quartz-scheduler.org/api/2.1.0/org/quartz/CronExpression.html" target="_new">more about cron schedules</a>
34
+ </div>
@@ -3,6 +3,7 @@ define([
3
3
  'underscore',
4
4
  'backbone',
5
5
  'util/templar',
6
+ 'util/cron',
6
7
  'route/index',
7
8
  'view/alert',
8
9
  'view/primarynav',
@@ -26,6 +27,7 @@ define([
26
27
  _,
27
28
  Backbone,
28
29
  Templar,
30
+ CronUtil,
29
31
  IndexRouter,
30
32
  AlertView,
31
33
  PrimaryNavView,
@@ -55,7 +57,6 @@ define([
55
57
 
56
58
  initialize : function() {
57
59
  _.bindAll(this);
58
-
59
60
  // check authorization
60
61
  this.auth();
61
62
 
@@ -14,7 +14,7 @@ require.config({
14
14
  'jquery-validate' : '../vendor/jquery/plugins/jquery.validate',
15
15
  'jquery-ui' : '../vendor/jquery/plugins/jquery-ui/js/jquery-ui-1.10.3.custom.min',
16
16
  'jquery-timepicker' : '../vendor/jquery/plugins/timepicker-ui/js/jquery.timepicker',
17
- 'parsley' : '../vendor/parsley/js/parsley.min',
17
+ 'parsley' : '../vendor/parsley/js/parsley',
18
18
  'underscore' : '../vendor/underscore/js/underscore',
19
19
  'underscore-string' : '../vendor/underscore/plugins/underscore-string/js/underscore.string',
20
20
  'xdate' : '../vendor/xdate/js/xdate',
@@ -86,4 +86,4 @@ define([
86
86
  App
87
87
  ){
88
88
  App.initialize();
89
- });
89
+ });
@@ -0,0 +1,50 @@
1
+ define([
2
+ 'underscore'
3
+ ],
4
+ function(_){
5
+ var CronUtil = function() {};
6
+
7
+ _.extend(CronUtil, {
8
+
9
+ //TODO this just checks that the characters are in the valid
10
+ //set but does not assert the syntax of cron
11
+ minuteRegex : RegExp(/^[0-9\*\-,\/]+$/),
12
+ hourRegex : RegExp(/^[0-9\*\-,\/]+$/),
13
+ dayRegex : RegExp(/^[\?0-9\*\-,\/,LW]+$/i),
14
+
15
+ parsleyValidator : function(cb) {
16
+ var validator = function() {};
17
+ _.extend(validator, {
18
+ validationMinlength: 1,
19
+ validators : {
20
+ cronfield : function (val,param) {
21
+ var valid = null;
22
+ switch(param) {
23
+ case 'day':
24
+ valid = val.match(this.dayRegex);
25
+ break;
26
+ case 'hour':
27
+ valid = val.match(this.hourRegex);
28
+ break;
29
+ case 'minute':
30
+ valid = val.match(this.minuteRegex);
31
+ break;
32
+ }
33
+ if(_.isFunction(cb)) {
34
+ valid = cb(val,param,valid);
35
+ }
36
+ return valid!=null;
37
+ }.bind(this)
38
+ },
39
+ messages : {
40
+ cronfield : "Not a valid value for cron field '%s'"
41
+ }
42
+ });
43
+ return validator;
44
+ }
45
+
46
+ });
47
+
48
+ return CronUtil;
49
+ });
50
+
@@ -2,19 +2,25 @@ define([
2
2
  'view/base',
3
3
  'model/monitor',
4
4
  'codemirror',
5
+ 'util/cron',
5
6
  'codemirror-ruby',
6
7
  'jquery-validate',
7
8
  'parsley'
8
9
  ], function(
9
10
  BaseView,
10
11
  MonitorModel,
11
- CodeMirror
12
+ CodeMirror,
13
+ CronUtil
12
14
  ){
13
15
 
14
16
  var AddMonitorView = BaseView.extend({
15
- scheduleViewInitialized : false,
16
- metricsViewInitialized : false,
17
- scheduleView : true,
17
+ scheduleViewInitialized : false,
18
+ metricsViewInitialized : false,
19
+ scheduleView : true,
20
+ cronScheduleFormValid : false,
21
+ cronScheduleWeekdaysSelected : false,
22
+ cronScheduleDaysDisabled : false,
23
+ namePagerFormValid : false,
18
24
 
19
25
  el : '.add-monitor-wrap',
20
26
 
@@ -29,6 +35,7 @@ define([
29
35
  'click .nameSchedule' : 'backToSchedule',
30
36
  'click .saveFinish' : 'saveFinish',
31
37
  'click .back' : 'exitFullScreen',
38
+ 'click #cronScheduleForm .day-month-picker .day-picker button' : 'cronWeekdayButtonClicked',
32
39
  'hide #addMonitor' : 'modalClose',
33
40
  'show #addMonitor' : 'modalShow',
34
41
  'shown #addMonitor' : 'focusFirst'
@@ -68,8 +75,9 @@ define([
68
75
 
69
76
  this.scheduleViewInitialized = true;
70
77
 
71
- // scheduling is the first step in add monitor workflow
72
- this.setScheduleValidation();
78
+ this.setNamePagerValidation();
79
+ this.setCronScheduleValidation();
80
+ this.setNameScheduleHelp();
73
81
 
74
82
  // store reference to modal
75
83
  this.$modal = this.$el.find('.add-monitor');
@@ -135,8 +143,12 @@ define([
135
143
  * view.
136
144
  **/
137
145
  advanceToMetrics : function() {
138
- // validate form
139
- this.scheduleForm.parsley('validate');
146
+ this.namePagerForm.parsley('validate');
147
+ this.cronScheduleForm.parsley('validate');
148
+ if(this.namePagerFormValid && this.cronScheduleFormValid) {
149
+ this._setSchedule();
150
+ this._setupMetricsView();
151
+ }
140
152
  },
141
153
  /**
142
154
  * AddMonitorView#backToSchedule()
@@ -176,27 +188,107 @@ define([
176
188
  }
177
189
  this.expressionsMirror.refresh();
178
190
  },
191
+
192
+ setCronScheduleWeekdaysSelected : function() {
193
+ this.cronScheduleWeekdaysSelected = $('#cronScheduleForm .day-month-picker .day-picker button.active').size() > 0
194
+ },
195
+
196
+ setCronScheduleDaysDisabled : function(disabled) {
197
+ if(disabled) {
198
+ if(!this.cronScheduleDaysDisabled) {
199
+ $('#inputDays').parsley('ParsleyField').reset();
200
+ $('#inputDays').val('?');
201
+ $('#inputDays').attr('disabled',"");
202
+ this.cronScheduleDaysDisabled = true;
203
+ }
204
+ }
205
+ else {
206
+ if(this.cronScheduleDaysDisabled) {
207
+ $('#inputDays').val('*');
208
+ $('#inputDays').removeAttr('disabled');
209
+ this.cronScheduleDaysDisabled = false;
210
+ }
211
+ }
212
+ },
213
+
214
+ cronWeekdayButtonClicked : function(event) {
215
+ $(event.target).button('toggle');
216
+ this.setCronScheduleWeekdaysSelected();
217
+ this.setCronScheduleDaysDisabled(this.cronScheduleWeekdaysSelected);
218
+ },
219
+
220
+ setNameScheduleHelp : function() {
221
+ $cronHelpContent = '';
222
+ $alertHelpContent = '';
223
+ $.ajax({
224
+ url : rearview.path + '/help/cron.html',
225
+ async : false,
226
+ success : function( response ) {
227
+ $cronHelpContent = response;
228
+ }
229
+ });
230
+
231
+ $.ajax({
232
+ url : rearview.path + '/help/alert.html',
233
+ async : false,
234
+ success : function( response ) {
235
+ $alertHelpContent = response;
236
+ }
237
+ });
238
+
239
+ this.$el.find('#cronScheduleForm .set-schedule .help.label').tooltip({
240
+ trigger : 'click',
241
+ html : true,
242
+ placement : 'left',
243
+ delay : { show : 100, hide : 200 },
244
+ title : $cronHelpContent
245
+ });
246
+
247
+ this.$el.find('#namePagerForm .pager-duty .help.label').tooltip({
248
+ trigger : 'click',
249
+ html : true,
250
+ placement : 'left',
251
+ delay : { show : 100, hide : 200 },
252
+ title : $alertHelpContent
253
+ });
254
+ },
179
255
  /**
180
- * AddMonitorView#setScheduleValidation()
256
+ * AddMonitorView#setNamePagerValidation()
181
257
  *
182
258
  * Sets up the front end form validation for the name field which is required.
183
259
  * If name is present, save the sceduling data to the monitor model and setup the
184
260
  * next view in the add monitor workflow to set up the metrics data.
185
261
  **/
186
- setScheduleValidation : function() {
187
- this.scheduleForm = $('#namePagerForm');
188
-
189
- var validator = this.scheduleForm.parsley({
262
+ setNamePagerValidation : function() {
263
+ this.namePagerForm = $('#namePagerForm');
264
+ var validator = this.namePagerForm.parsley({
190
265
  listeners: {
191
266
  onFormSubmit : function ( isFormValid, event, ParsleyForm ) {
192
- if (isFormValid) {
193
- this._setSchedule();
194
- this._setupMetricsView();
195
- }
267
+ this.namePagerFormValid = isFormValid;
196
268
  }.bind(this)
197
269
  }
198
270
  });
199
271
  },
272
+ setCronScheduleValidation : function() {
273
+ this.cronScheduleForm = $('#cronScheduleForm');
274
+ var validator = CronUtil.parsleyValidator();
275
+ _.extend(validator,{
276
+ errors: {
277
+ container: function (parsleyElement, parsleyTemplate, isRadioOrCheckbox) {
278
+ var container = $('#cronScheduleFormErrors');
279
+ container.append(parsleyTemplate);
280
+ return container;
281
+ }
282
+ },
283
+ listeners: {
284
+ onFormSubmit : function ( isFormValid, event, ParsleyForm ) {
285
+ this.cronScheduleFormValid = isFormValid;
286
+ }.bind(this)
287
+ }
288
+ });
289
+ this.cronScheduleForm.parsley(validator);
290
+
291
+ },
200
292
  setMetricsValidation : function() {
201
293
  $.validator.addMethod('code', function(value, element) {
202
294
  var mirror = $(element).data('CodeMirror'),
@@ -374,7 +466,7 @@ define([
374
466
 
375
467
  this.metricsForm.submit();
376
468
  },
377
- setHelp : function() {
469
+ setMetricsHelp : function() {
378
470
  var $content = '';
379
471
 
380
472
  $.ajax({
@@ -624,7 +716,7 @@ define([
624
716
  this._initCodeMirror();
625
717
  this._initDatePicker();
626
718
  this.initGraph( modalContainerEl.find('.graph')[0] );
627
- this.setHelp();
719
+ this.setMetricsHelp();
628
720
  this.setMetricsValidation();
629
721
 
630
722
  // set that metrics view has been initialized to
@@ -1,17 +1,25 @@
1
1
  define([
2
2
  'view/base',
3
3
  'view/deletemonitor',
4
- 'model/monitor'
4
+ 'model/monitor',
5
+ 'util/cron',
6
+ 'parsley'
5
7
  ], function(
6
8
  BaseView,
7
9
  DeleteMonitorView,
8
- JobModel
10
+ JobModel,
11
+ CronUtil
9
12
  ){
10
13
  var ExpandedMonitorView = BaseView.extend({
11
14
 
15
+ cronScheduleFormValid : true,
16
+ cronScheduleWeekdaysSelected : false,
17
+ cronScheduleDaysDisabled : false,
18
+
12
19
  events : {
13
20
  'click .name-save' : 'updateMonitorName',
14
21
  'click .schedule-tab' : '_setExpandedViewHeight',
22
+ 'click #cronScheduleFormEdit .day-month-picker .day-picker button' : 'cronWeekdayButtonClicked',
15
23
  'change #selectPreviousError' : '_setErrorDropDown'
16
24
  },
17
25
 
@@ -47,7 +55,6 @@ define([
47
55
  this.categories = categories;
48
56
  this.categoryId = categoryId;
49
57
  this.dashboardId = dashboardId;
50
-
51
58
  this.initMonitor();
52
59
  },
53
60
  /**
@@ -120,6 +127,7 @@ define([
120
127
 
121
128
  // inline help
122
129
  self.setHelp();
130
+ self.setCronScheduleValidation();
123
131
 
124
132
  // dynamically set the heights to maximum screen utilization
125
133
  self._setExpandedViewHeight();
@@ -128,6 +136,55 @@ define([
128
136
  });
129
137
  });
130
138
 
139
+
140
+ },
141
+
142
+ setCronScheduleWeekdaysSelected : function() {
143
+ this.cronScheduleWeekdaysSelected = $('#cronScheduleFormEdit .day-month-picker .day-picker button.active').size() > 0
144
+ },
145
+
146
+ setCronScheduleDaysDisabled : function(disabled) {
147
+ if(disabled) {
148
+ if(!this.cronScheduleDaysDisabled) {
149
+ $('#inputDays').parsley('ParsleyField').reset();
150
+ $('#inputDays').val('?');
151
+ $('#inputDays').attr('disabled',"");
152
+ this.cronScheduleDaysDisabled = true;
153
+ }
154
+ }
155
+ else {
156
+ if(this.cronScheduleDaysDisabled) {
157
+ $('#inputDays').val('*');
158
+ $('#inputDays').removeAttr('disabled');
159
+ this.cronScheduleDaysDisabled = false;
160
+ }
161
+ }
162
+ },
163
+
164
+ cronWeekdayButtonClicked : function(event) {
165
+ $(event.target).button('toggle');
166
+ this.setCronScheduleWeekdaysSelected();
167
+ this.setCronScheduleDaysDisabled(this.cronScheduleWeekdaysSelected);
168
+ },
169
+
170
+ setCronScheduleValidation : function() {
171
+ this.cronScheduleForm = $('#cronScheduleFormEdit');
172
+ var validator = CronUtil.parsleyValidator();
173
+ _.extend(validator,{
174
+ errors: {
175
+ container: function (parsleyElement, parsleyTemplate, isRadioOrCheckbox) {
176
+ var container = $('#cronScheduleFormEditErrors');
177
+ container.append(parsleyTemplate);
178
+ return container;
179
+ }
180
+ },
181
+ listeners: {
182
+ onFormSubmit : function ( isFormValid, event, ParsleyForm ) {
183
+ this.cronScheduleFormValid = isFormValid;
184
+ }.bind(this)
185
+ }
186
+ });
187
+ this.cronScheduleForm.parsley(validator);
131
188
  },
132
189
 
133
190
  getGraphData : function(monitorId, cb) {
@@ -192,18 +249,26 @@ define([
192
249
  },
193
250
  setHelp : function() {
194
251
  var self = this,
195
- $content = '';
252
+ $quickContent = '';
196
253
  $alertContent = '';
197
- //TODO fix this path
254
+ $cronHelpContent = '';
255
+
256
+ $.ajax({
257
+ url : rearview.path + '/help/cron.html',
258
+ async : false,
259
+ success : function( response ) {
260
+ $cronHelpContent = response;
261
+ }
262
+ });
263
+
198
264
  $.ajax({
199
265
  url : rearview.path + '/help/quick.html',
200
266
  async : false,
201
267
  success : function( response ) {
202
- $content = response;
268
+ $quickContent = response;
203
269
  }
204
270
  });
205
271
 
206
- //TODO fix this path
207
272
  $.ajax({
208
273
  url : rearview.path + '/help/alert.html',
209
274
  async : false,
@@ -212,15 +277,23 @@ define([
212
277
  }
213
278
  });
214
279
 
280
+ self.$el.find('#viewSchedule .help.label').tooltip({
281
+ trigger : 'click',
282
+ html : true,
283
+ placement : 'left',
284
+ delay : { show : 100, hide : 200 },
285
+ title : $cronHelpContent
286
+ });
287
+
215
288
  self.$el.find('.help:nth-child(2)').tooltip({
216
289
  trigger : 'click',
217
290
  html : true,
218
291
  placement : 'right',
219
292
  delay : { show : 100, hide : 200 },
220
- title : $content
293
+ title : $quickContent
221
294
  });
222
295
 
223
- self.$el.find('.help:nth-child(1)').tooltip({
296
+ self.$el.find('#viewSettings .monitor-meta .pager-duty .help.label').tooltip({
224
297
  trigger : 'click',
225
298
  html : true,
226
299
  placement : 'left',
@@ -372,32 +445,37 @@ define([
372
445
  toDate = e.data.toDate,
373
446
  output = e.data.output;
374
447
 
375
- monitor = self._setMetrics(monitor);
376
- monitor = self._setSchedule(monitor);
377
- monitor = self._setSettings(monitor);
448
+ this.cronScheduleForm.parsley('validate');
449
+ if(this.cronScheduleFormValid) {
450
+
451
+ monitor = self._setMetrics(monitor);
452
+ monitor = self._setSchedule(monitor);
453
+ monitor = self._setSettings(monitor);
454
+
455
+ monitor.save(null, {
456
+ success : function(model, response, options) {
457
+ Backbone.Mediator.pub('view:expandedmonitor:save', {
458
+ 'model' : self.model,
459
+ 'message' : "The monitor '" + model.get('name') + "' was saved.",
460
+ 'attention' : 'Monitor Saved!',
461
+ 'status' : 'success'
462
+ });
463
+
464
+ // quit out of the edit monitor view
465
+ self.exit(monitor);
466
+ self.updateGraph(monitor);
467
+ },
468
+ error : function(model, xhr, options) {
469
+ Backbone.Mediator.pub('view:expandedmonitor:save', {
470
+ 'model' : self.model,
471
+ 'tryJSON' : xhr.responseText,
472
+ 'attention' : 'Monitor Saved Error!',
473
+ 'status' : 'error'
474
+ });
475
+ }
476
+ });
378
477
 
379
- monitor.save(null, {
380
- success : function(model, response, options) {
381
- Backbone.Mediator.pub('view:expandedmonitor:save', {
382
- 'model' : self.model,
383
- 'message' : "The monitor '" + model.get('name') + "' was saved.",
384
- 'attention' : 'Monitor Saved!',
385
- 'status' : 'success'
386
- });
387
-
388
- // quit out of the edit monitor view
389
- self.exit(monitor);
390
- self.updateGraph(monitor);
391
- },
392
- error : function(model, xhr, options) {
393
- Backbone.Mediator.pub('view:expandedmonitor:save', {
394
- 'model' : self.model,
395
- 'tryJSON' : xhr.responseText,
396
- 'attention' : 'Monitor Saved Error!',
397
- 'status' : 'error'
398
- });
399
- }
400
- });
478
+ }
401
479
  },
402
480
  /**
403
481
  * ExpandedMonitorView#deleteMonitor(e)