flapjack 1.0.0rc3 → 1.0.0rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CONTRIBUTING.md +2 -2
  6. data/Gemfile +1 -1
  7. data/README.md +6 -16
  8. data/build.sh +13 -1
  9. data/etc/flapjack_config.yaml.example +98 -12
  10. data/features/cli.feature +8 -8
  11. data/features/cli_flapjack-nagios-receiver.feature +29 -37
  12. data/features/cli_flapper.feature +24 -12
  13. data/features/cli_simulate-failed-check.feature +2 -2
  14. data/features/notifications.feature +18 -1
  15. data/features/steps/cli_steps.rb +2 -2
  16. data/features/steps/notifications_steps.rb +71 -0
  17. data/features/support/env.rb +7 -6
  18. data/flapjack.gemspec +3 -1
  19. data/lib/flapjack/cli/flapper.rb +74 -25
  20. data/lib/flapjack/cli/import.rb +3 -4
  21. data/lib/flapjack/cli/maintenance.rb +182 -0
  22. data/lib/flapjack/cli/receiver.rb +110 -121
  23. data/lib/flapjack/cli/server.rb +30 -26
  24. data/lib/flapjack/cli/simulate.rb +2 -3
  25. data/lib/flapjack/data/contact.rb +1 -1
  26. data/lib/flapjack/data/entity.rb +425 -32
  27. data/lib/flapjack/data/entity_check.rb +212 -14
  28. data/lib/flapjack/data/event.rb +1 -1
  29. data/lib/flapjack/gateways/aws_sns.rb +134 -0
  30. data/lib/flapjack/gateways/aws_sns/alert.text.erb +5 -0
  31. data/lib/flapjack/gateways/aws_sns/rollup.text.erb +2 -0
  32. data/lib/flapjack/gateways/jabber.rb +2 -2
  33. data/lib/flapjack/gateways/jsonapi/check_methods.rb +1 -1
  34. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +1 -1
  35. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +15 -1
  36. data/lib/flapjack/gateways/jsonapi/metrics_methods.rb +4 -3
  37. data/lib/flapjack/gateways/jsonapi/report_methods.rb +1 -1
  38. data/lib/flapjack/gateways/web.rb +35 -16
  39. data/lib/flapjack/gateways/web/public/css/tablesort.css +0 -16
  40. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +1 -1
  41. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.widgets.js +0 -45
  42. data/lib/flapjack/gateways/web/public/js/modules/contact.js +2 -2
  43. data/lib/flapjack/gateways/web/public/js/modules/entity.js +2 -2
  44. data/lib/flapjack/gateways/web/public/js/modules/medium.js +4 -4
  45. data/lib/flapjack/gateways/web/public/js/self_stats.js +1 -1
  46. data/lib/flapjack/gateways/web/views/check.html.erb +10 -10
  47. data/lib/flapjack/gateways/web/views/checks.html.erb +1 -1
  48. data/lib/flapjack/gateways/web/views/contact.html.erb +5 -1
  49. data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +3 -4
  50. data/lib/flapjack/gateways/web/views/entities.html.erb +1 -1
  51. data/lib/flapjack/gateways/web/views/index.html.erb +2 -2
  52. data/lib/flapjack/gateways/web/views/layout.erb +3 -3
  53. data/lib/flapjack/gateways/web/views/self_stats.html.erb +5 -6
  54. data/lib/flapjack/notifier.rb +4 -1
  55. data/lib/flapjack/patches.rb +8 -2
  56. data/lib/flapjack/pikelet.rb +3 -1
  57. data/lib/flapjack/version.rb +1 -1
  58. data/libexec/httpbroker.go +1 -1
  59. data/spec/lib/flapjack/coordinator_spec.rb +3 -3
  60. data/spec/lib/flapjack/data/contact_spec.rb +2 -2
  61. data/spec/lib/flapjack/data/entity_check_spec.rb +805 -53
  62. data/spec/lib/flapjack/data/entity_spec.rb +661 -0
  63. data/spec/lib/flapjack/gateways/aws_sns_spec.rb +123 -0
  64. data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -1
  65. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +1 -1
  66. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +2 -2
  67. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +1 -1
  68. data/spec/lib/flapjack/gateways/web_spec.rb +11 -11
  69. data/spec/support/profile_all_formatter.rb +10 -10
  70. data/spec/support/uncolored_doc_formatter.rb +66 -4
  71. data/src/flapjack/event.go +1 -1
  72. data/tasks/benchmarks.rake +24 -20
  73. data/tasks/entities.rake +148 -0
  74. data/tmp/dummy_contacts.json +43 -0
  75. data/tmp/dummy_entities.json +37 -1
  76. metadata +43 -7
  77. data/tmp/test_entities.json +0 -1
@@ -398,7 +398,7 @@ module Flapjack
398
398
  when number_found == 0
399
399
  msg = "found no entities matching /#{entity_pattern}/"
400
400
  when number_found >= 1
401
- failing_list = Flapjack::Data::EntityCheck.find_all_failing_by_entity(:redis => @redis)
401
+ failing_list = Flapjack::Data::EntityCheck.find_current_failing_by_entity(:redis => @redis)
402
402
  entities = failing_list.select {|k,v| v.count >= 1 && entity_list.include?(k) }
403
403
  if entities.length >= 1
404
404
  entities.each_pair do |entity,check_list|
@@ -474,7 +474,7 @@ module Flapjack
474
474
  msg = "found no entities matching /#{entity_pattern}/"
475
475
  when number_found >= 1
476
476
 
477
- failing_list = Flapjack::Data::EntityCheck.find_all_failing_by_entity(:redis => @redis)
477
+ failing_list = Flapjack::Data::EntityCheck.find_current_failing_by_entity(:redis => @redis)
478
478
 
479
479
  my_failing_checks = Hash[failing_list.map do |k,v|
480
480
  if entity_list.include?(k)
@@ -43,7 +43,7 @@ module Flapjack
43
43
  Flapjack::Data::EntityCheck.for_event_id(req_check, :logger => logger, :redis => redis)
44
44
  end
45
45
  else
46
- Flapjack::Data::EntityCheck.find_all(:redis => redis)
46
+ Flapjack::Data::EntityCheck.find_current(:redis => redis)
47
47
  end
48
48
  checks.compact!
49
49
 
@@ -97,7 +97,7 @@ module Flapjack
97
97
  end
98
98
 
99
99
  # Returns all (/contacts) or some (/contacts/1,2,3) or one (/contacts/2) contact(s)
100
- # https://github.com/flapjack/flapjack/wiki/API#wiki-get_contacts
100
+ # http://flapjack.io/docs/1.0/jsonapi/#contacts
101
101
  app.get %r{^/contacts(?:/)?([^/]+)?$} do
102
102
  requested_contacts = if params[:captures] && params[:captures][0]
103
103
  params[:captures][0].split(',').uniq
@@ -44,7 +44,21 @@ module Flapjack
44
44
  entities = if requested_entities
45
45
  Flapjack::Data::Entity.find_by_ids(requested_entities, :logger => logger, :redis => redis)
46
46
  else
47
- Flapjack::Data::Entity.all(:redis => redis).reject {|e| e.id.nil? || e.id.empty? }
47
+ enabled = if params.has_key?(:enabled)
48
+ case params[:enabled].downcase
49
+ when '0', 'f', 'false', 'n', 'no'
50
+ false
51
+ when '1', 't', 'true', 'y', 'yes'
52
+ true
53
+ else
54
+ nil
55
+ end
56
+ else
57
+ nil
58
+ end
59
+
60
+ Flapjack::Data::Entity.all(:enabled => enabled,
61
+ :redis => redis).reject {|e| e.id.nil? || e.id.empty? }
48
62
  end
49
63
  entities.compact!
50
64
 
@@ -48,15 +48,16 @@ module Flapjack
48
48
 
49
49
  def entities
50
50
  {
51
- 'all' => Flapjack::Data::Entity.find_all_with_checks(:redis => redis).length,
51
+ 'all' => Flapjack::Data::Entity.all(:redis => redis).length,
52
+ 'enabled' => Flapjack::Data::Entity.all(:enabled => true, :redis => redis).length,
52
53
  'failing' => Flapjack::Data::Entity.find_all_with_failing_checks(:redis => redis).length,
53
54
  }
54
55
  end
55
56
 
56
57
  def checks
57
58
  {
58
- 'all' => Flapjack::Data::EntityCheck.count_all(:redis => redis),
59
- 'failing' => Flapjack::Data::EntityCheck.count_all_failing(:redis => redis),
59
+ 'all' => Flapjack::Data::EntityCheck.count_current(:redis => redis),
60
+ 'failing' => Flapjack::Data::EntityCheck.count_current_failing(:redis => redis),
60
61
  }
61
62
  end
62
63
 
@@ -27,7 +27,7 @@ module Flapjack
27
27
  end
28
28
 
29
29
  checks = if event_ids.nil?
30
- Flapjack::Data::EntityCheck.find_all(:redis => redis).collect {|check_name|
30
+ Flapjack::Data::EntityCheck.find_current(:redis => redis).collect {|check_name|
31
31
  find_entity_check_by_name(*check_name.split(':', 2))
32
32
  }
33
33
  elsif !event_ids.empty?
@@ -58,9 +58,13 @@ module Flapjack
58
58
  @api_url = @config['api_url']
59
59
  if @api_url
60
60
  if (@api_url =~ /^#{URI::regexp(%w(http https))}$/).nil?
61
- @logger.error "api_url is not a valid http or https URI (#{@api_url})"
61
+ @logger.error "api_url is not a valid http or https URI (#{@api_url}), discarding"
62
62
  @api_url = nil
63
63
  end
64
+ unless @api_url.match(/^.*\/$/)
65
+ @logger.info "api_url must end with a trailing '/', setting to '#{@api_url}/'"
66
+ @api_url = "#{@api_url}/"
67
+ end
64
68
  end
65
69
 
66
70
  unless @api_url
@@ -147,10 +151,11 @@ module Flapjack
147
151
  check_stats
148
152
  @adjective = 'all'
149
153
 
150
- checks_by_entity = Flapjack::Data::EntityCheck.find_all_by_entity(:redis => redis)
151
- @states = checks_by_entity.keys.inject({}) {|result, entity|
152
- result[entity] = checks_by_entity[entity].sort.map {|check|
153
- [check] + entity_check_state(entity, check)
154
+ checks_by_entity = Flapjack::Data::EntityCheck.find_current_by_entity(:redis => redis)
155
+ @states = checks_by_entity.keys.inject({}) {|result, entity_name|
156
+ Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis, :create => true)
157
+ result[entity_name] = checks_by_entity[entity_name].sort.map {|check|
158
+ [check] + entity_check_state(entity_name, check)
154
159
  }
155
160
  result
156
161
  }
@@ -163,7 +168,7 @@ module Flapjack
163
168
  check_stats
164
169
  @adjective = 'failing'
165
170
 
166
- checks_by_entity = Flapjack::Data::EntityCheck.find_all_failing_by_entity(:redis => redis)
171
+ checks_by_entity = Flapjack::Data::EntityCheck.find_current_failing_by_entity(:redis => redis)
167
172
  @states = checks_by_entity.keys.inject({}) {|result, entity|
168
173
  result[entity] = checks_by_entity[entity].sort.map {|check|
169
174
  [check] + entity_check_state(entity, check)
@@ -189,9 +194,9 @@ module Flapjack
189
194
  check_stats
190
195
  {
191
196
  'events_queued' => @events_queued,
192
- 'all_entities' => @count_all_entities,
197
+ 'all_entities' => @count_current_entities,
193
198
  'failing_entities' => @count_failing_entities,
194
- 'all_checks' => @count_all_checks,
199
+ 'all_checks' => @count_current_checks,
195
200
  'failing_checks' => @count_failing_checks,
196
201
  'processed_events' => {
197
202
  'all_time' => {
@@ -211,9 +216,22 @@ module Flapjack
211
216
  end
212
217
 
213
218
  get '/entities_all' do
219
+ redirect '/entities'
220
+ end
221
+
222
+ get '/entities' do
223
+ @entities = Flapjack::Data::Entity.all(:enabled => true, :redis => redis).map {|e| e.name}
224
+
225
+ entity_stats(@entities)
226
+ @adjective = ''
227
+
228
+ erb 'entities.html'.to_sym
229
+ end
230
+
231
+ get '/entities_decommissioned' do
214
232
  entity_stats
215
- @adjective = 'all'
216
- @entities = Flapjack::Data::Entity.find_all_with_checks(:redis => redis)
233
+ @adjective = 'decommissioned'
234
+ @entities = Flapjack::Data::Entity.all(:enabled => false, :redis => redis)
217
235
 
218
236
  erb 'entities.html'.to_sym
219
237
  end
@@ -229,7 +247,7 @@ module Flapjack
229
247
  get '/entity/:entity' do
230
248
  @entity = params[:entity]
231
249
  entity_stats
232
- @states = Flapjack::Data::EntityCheck.find_all_for_entity_name(@entity, :redis => redis).sort.map { |check|
250
+ @states = Flapjack::Data::EntityCheck.find_current_for_entity_name(@entity, :redis => redis).sort.map { |check|
233
251
  [check] + entity_check_state(@entity, check)
234
252
  }.sort_by {|parts| parts }
235
253
 
@@ -237,10 +255,12 @@ module Flapjack
237
255
  end
238
256
 
239
257
  get '/check' do
258
+
240
259
  @entity = params[:entity]
241
260
  @check = params[:check]
242
261
 
243
262
  entity_check = get_entity_check(@entity, @check)
263
+
244
264
  return 404 if entity_check.nil?
245
265
 
246
266
  check_stats
@@ -447,15 +467,14 @@ module Flapjack
447
467
  @current_checks_ages = Flapjack::Data::EntityCheck.find_all_split_by_freshness([0, 60, 300, 900, 3600], {:redis => redis, :counts => true } )
448
468
  end
449
469
 
450
- def entity_stats
451
- @count_all_entities = Flapjack::Data::Entity.find_all_with_checks(:redis => redis).length
470
+ def entity_stats(entities = nil)
471
+ @count_current_entities = (entities || Flapjack::Data::Entity.all(:enabled => true, :redis => redis)).length
452
472
  @count_failing_entities = Flapjack::Data::Entity.find_all_with_failing_checks(:redis => redis).length
453
473
  end
454
474
 
455
475
  def check_stats
456
- # FIXME: move this logic to Flapjack::Data::EntityCheck
457
- @count_all_checks = Flapjack::Data::EntityCheck.count_all(:redis => redis)
458
- @count_failing_checks = Flapjack::Data::EntityCheck.count_all_failing(:redis => redis)
476
+ @count_current_checks = Flapjack::Data::EntityCheck.count_current(:redis => redis)
477
+ @count_failing_checks = Flapjack::Data::EntityCheck.count_current_failing(:redis => redis)
459
478
  end
460
479
 
461
480
  def last_notification_data(entity_check)
@@ -15,22 +15,6 @@
15
15
  cursor: not-allowed;
16
16
  }
17
17
 
18
- .tablesorter-filter-row.hideme td {
19
- padding: 2px;
20
- margin: 0;
21
- line-height: 0;
22
- cursor: pointer;
23
- }
24
- .tablesorter-filter-row.hideme .tablesorter-filter {
25
- height: 1px;
26
- min-height: 0;
27
- border: 0;
28
- padding: 0;
29
- margin: 0;
30
- opacity: 0;
31
- filter: alpha(opacity=0);
32
- }
33
-
34
18
  .tablesorter-filter {
35
19
  width: 95%;
36
20
  height: inherit;
@@ -187,7 +187,7 @@ Backbone.JSONAPIModel = Backbone.Model.extend({
187
187
  this.linked[type].remove(obj);
188
188
  },
189
189
 
190
- urlRoot: function() { return(flapjack.api_url + "/" + this.name); },
190
+ urlRoot: function() { return(flapjack.api_url + this.name); },
191
191
 
192
192
  // can only be called from inside a 'change' event
193
193
  setDirty: function() {
@@ -603,10 +603,6 @@ ts.filter = {
603
603
  ts.filter.checkFilters(table, filter);
604
604
  });
605
605
 
606
- if (wo.filter_hideFilters) {
607
- ts.filter.hideFilters(table, c);
608
- }
609
-
610
606
  // show processing icon
611
607
  if (c.showProcessing) {
612
608
  c.$table.bind('filterStart.tsfilter filterEnd.tsfilter', function(event, columns) {
@@ -747,10 +743,6 @@ ts.filter = {
747
743
  if (filterArray) {
748
744
  ts.setFilters( table, filters );
749
745
  }
750
- if (wo.filter_hideFilters) {
751
- // show/hide filter row as needed
752
- c.$table.find('.tablesorter-filter-row').trigger( combinedFilters === '' ? 'mouseleave' : 'mouseenter' );
753
- }
754
746
  // return if the last search is the same; but filter === false when updating the search
755
747
  // see example-widget-filter.html filter toggle buttons
756
748
  if (c.lastCombinedFilter === combinedFilters && filter !== false) {
@@ -771,43 +763,6 @@ ts.filter = {
771
763
  return false;
772
764
  }
773
765
  },
774
- hideFilters: function(table, c) {
775
- var $filterRow, $filterRow2, timer;
776
- c.$table
777
- .find('.tablesorter-filter-row')
778
- .addClass('hideme')
779
- .bind('mouseenter mouseleave', function(e) {
780
- // save event object - http://bugs.jquery.com/ticket/12140
781
- var event = e;
782
- $filterRow = $(this);
783
- clearTimeout(timer);
784
- timer = setTimeout(function() {
785
- if ( /enter|over/.test(event.type) ) {
786
- $filterRow.removeClass('hideme');
787
- } else {
788
- // don't hide if input has focus
789
- // $(':focus') needs jQuery 1.6+
790
- if ( $(document.activeElement).closest('tr')[0] !== $filterRow[0] ) {
791
- // don't hide row if any filter has a value
792
- if (ts.getFilters(table).join('') === '') {
793
- $filterRow.addClass('hideme');
794
- }
795
- }
796
- }
797
- }, 200);
798
- })
799
- .find('input, select').bind('focus blur', function(e) {
800
- $filterRow2 = $(this).closest('tr');
801
- clearTimeout(timer);
802
- var event = e;
803
- timer = setTimeout(function() {
804
- // don't hide row if any filter has a value
805
- if (ts.getFilters(table).join('') === '') {
806
- $filterRow2[ event.type === 'focus' ? 'removeClass' : 'addClass']('hideme');
807
- }
808
- }, 200);
809
- });
810
- },
811
766
  findRows: function(table, filters, combinedFilters) {
812
767
  if (table.config.lastCombinedFilter === combinedFilters) { return; }
813
768
  var cached, len, $rows, rowIndex, tbodyIndex, $tbody, $cells, columnIndex,
@@ -48,7 +48,7 @@
48
48
 
49
49
  Contact.List = Backbone.JSONAPICollection.extend({
50
50
  model: Contact.Model,
51
- url: function() { return flapjack.api_url + "/contacts"; }
51
+ url: function() { return flapjack.api_url + "contacts"; }
52
52
  });
53
53
 
54
54
  Contact.Views.List = Backbone.View.extend({
@@ -518,4 +518,4 @@
518
518
  }
519
519
  });
520
520
 
521
- })(flapjack, flapjack.module("contact"));
521
+ })(flapjack, flapjack.module("contact"));
@@ -22,7 +22,7 @@
22
22
  Entity.List = Backbone.JSONAPICollection.extend({
23
23
  model: Entity.Model,
24
24
  comparator: 'name',
25
- url: function() { return flapjack.api_url + "/entities"; }
25
+ url: function() { return flapjack.api_url + "entities"; }
26
26
  });
27
27
 
28
- })(flapjack, flapjack.module("entity"));
28
+ })(flapjack, flapjack.module("entity"));
@@ -21,9 +21,9 @@
21
21
  },
22
22
  sync: function(method, model, options) {
23
23
  if ( method == 'create') {
24
- options.url = flapjack.api_url + '/contacts/' + model.contact.get('id') + '/' + this.name;
24
+ options.url = flapjack.api_url + 'contacts/' + model.contact.get('id') + '/' + this.name;
25
25
  } else {
26
- options.url = flapjack.api_url + '/' + this.name + '/' + model.contact.get('id') + '_' + model.get('type');
26
+ options.url = flapjack.api_url + this.name + '/' + model.contact.get('id') + '_' + model.get('type');
27
27
  }
28
28
  Backbone.JSONAPIModel.prototype.sync(method, model, options);
29
29
  }
@@ -33,7 +33,7 @@
33
33
  Medium.List = Backbone.JSONAPICollection.extend({
34
34
  model: Medium.Model,
35
35
  comparator: 'type',
36
- url: function() { return flapjack.api_url + "/media"; }
36
+ url: function() { return flapjack.api_url + "media"; }
37
37
  });
38
38
 
39
- })(flapjack, flapjack.module("medium"));
39
+ })(flapjack, flapjack.module("medium"));
@@ -62,7 +62,7 @@ $(document).ready(function() {
62
62
 
63
63
  function updateData() {
64
64
  var api_url = $('div#data-api-url').data('api-url');
65
- $.get(api_url + '/metrics?filter=event_queue_length', function(json) {
65
+ $.get(api_url + 'metrics?filter=event_queue_length', function(json) {
66
66
  var d = new Date().getTime();
67
67
  var value = {x: d, y: json.event_queue_length}
68
68
  data[0].values.push(value);
@@ -9,7 +9,7 @@
9
9
  </div>
10
10
 
11
11
  <div class="row">
12
- <div class="col-md-6">
12
+ <div id="state" class="col-md-6">
13
13
  <%
14
14
  state_qualifier = @check_enabled ? '' : "DISABLED. Last "
15
15
  state_class = case @check_state
@@ -37,7 +37,7 @@
37
37
  !@current_unscheduled_maintenance and
38
38
  !(['warning', 'critical', 'unknown'].include?(@check_state))
39
39
  %>
40
- <div class="alert alert-info">
40
+ <div id="no-maintenance-summary" class="alert alert-info">
41
41
  <form class="form-horizontal" role="form">
42
42
  <div class="form-group">
43
43
  <label class="col-md-2 control-label">State</label>
@@ -60,7 +60,7 @@
60
60
  <% end %>
61
61
 
62
62
  <% if @current_unscheduled_maintenance %>
63
- <div class="alert alert-warning">
63
+ <div id="unscheduled-maintenance-summary" class="alert alert-warning">
64
64
 
65
65
  <form action="<%= @base_url %>end_unscheduled_maintenance/<%= check_path_escaped %>" method="post" class="form-horizontal" role="form">
66
66
 
@@ -129,7 +129,7 @@
129
129
  <% end %>
130
130
 
131
131
  <% if (['warning', 'critical', 'unknown'].include?(@check_state) and !@current_scheduled_maintenance) %>
132
- <div class="alert alert-warning">
132
+ <div id="add-unscheduled-maintenance" class="alert alert-warning">
133
133
  <% action = @current_unscheduled_maintenance ? "Re-acknowledge" : "Acknowledge" %>
134
134
  <h4><%= action %> alert</h4>
135
135
  <form action="<%= @base_url %>acknowledgements/<%= check_path_escaped %>" method="post" class="form-horizontal">
@@ -169,7 +169,7 @@
169
169
  <% end %>
170
170
 
171
171
  <% if @current_scheduled_maintenance %>
172
- <div class="alert alert-warning">
172
+ <div id="scheduled-maintenance-summary" class="alert alert-warning">
173
173
  <div class="row">
174
174
  <div class="col-md-3 text-right">State</div>
175
175
  <div class="col-md-9">
@@ -217,7 +217,7 @@
217
217
  </div><!-- row -->
218
218
 
219
219
  <div class="row">
220
- <div class="col-md-6">
220
+ <div id="output" class="col-md-6">
221
221
  <h3>Output: <%= h @check_summary %></h3>
222
222
  <p><%= h @check_details %></p>
223
223
  <% if @check_perfdata && @check_perfdata.is_a?(Array) %>
@@ -255,7 +255,7 @@
255
255
  </table>
256
256
  </div><!-- col-md-6 -->
257
257
 
258
- <div class="col-md-6">
258
+ <div id="recent-state-changes" class="col-md-6">
259
259
  <h3>Recent state changes</h3>
260
260
  <% if @state_changes && !@state_changes.empty? %>
261
261
  <table class="table table-hover table-condensed">
@@ -279,7 +279,7 @@
279
279
  </div><!-- row -->
280
280
 
281
281
  <div class="row">
282
- <div class="col-md-12">
282
+ <div id="scheduled-maintenance-periods" class="col-md-12">
283
283
  <a name="scheduled_maintenance_periods"></a>
284
284
  <h3>Scheduled Maintenance Periods</h3>
285
285
  <% if @scheduled_maintenances && !@scheduled_maintenances.empty? %>
@@ -328,7 +328,7 @@
328
328
  <div class="row">
329
329
  <div class="col-md-8">
330
330
 
331
- <div class="panel panel-default">
331
+ <div id="add-scheduled-maintenance" class="panel panel-default">
332
332
  <div class="panel-heading">
333
333
  <h3 class="panel-title">Add Scheduled Maintenance</h4>
334
334
  </div>
@@ -384,7 +384,7 @@
384
384
 
385
385
 
386
386
  <div class="row">
387
- <div class="col-md-8">
387
+ <div id="contacts" class="col-md-8">
388
388
  <h3>Contacts</h3>
389
389
  <% if @contacts && !@contacts.empty? %>
390
390
  <table class="table table-bordered table-hover table-condensed">