flapjack 0.6.36 → 0.6.37

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.
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ gemfile:
5
+ - Gemfile
6
+ services:
7
+ - redis-server
8
+ # uncomment this line if your project needs to run something other than `rake`:
9
+ # script: bundle exec rspec spec
10
+
data/Rakefile CHANGED
@@ -27,7 +27,7 @@ end
27
27
  require 'rspec/core/rake_task'
28
28
  RSpec::Core::RakeTask.new(:spec)
29
29
 
30
- task :default => :spec
30
+ task :default => :tests
31
31
 
32
32
  namespace :verify do
33
33
  task :uncommitted do
@@ -46,3 +46,4 @@ end
46
46
 
47
47
  # FIXME: getting that intermittent gherken lexing error so removing :features from verify list
48
48
  task :verify => [ 'verify:all', :spec, :features]
49
+ task :tests => [ :spec, :features]
@@ -47,8 +47,13 @@ module Flapjack
47
47
 
48
48
  class API < Sinatra::Base
49
49
 
50
- # doesn't work with Rack::Test for some reason
51
- unless 'test'.eql?(FLAPJACK_ENV)
50
+ set :show_exceptions, false
51
+
52
+ if 'test'.eql?(FLAPJACK_ENV)
53
+ # expose test errors properly
54
+ set :raise_errors, true
55
+ else
56
+ # doesn't work with Rack::Test unless we wrap tests in EM.synchrony blocks
52
57
  rescue_exception = Proc.new { |env, exception|
53
58
  logger.error exception.message
54
59
  logger.error exception.backtrace.join("\n")
@@ -62,8 +67,6 @@ module Flapjack
62
67
 
63
68
  extend Flapjack::Pikelet
64
69
 
65
- set :show_exceptions, 'development'.eql?(FLAPJACK_ENV)
66
-
67
70
  before do
68
71
  # will only initialise the first time it's run
69
72
  Flapjack::API.bootstrap
@@ -1,77 +1,115 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # NB: use of redis.keys probably indicates we should maintain a data
4
+ # structure to avoid the need for this type of query
5
+
3
6
  module Flapjack
4
7
 
5
8
  module Data
6
9
 
7
10
  class Contact
8
11
 
9
- # takes a check, looks up contacts that are interested in this check (or in the check's entity)
10
- # and returns an array of contact ids
11
- def self.find_all_for_entity_check(entity_check, options = {})
12
+ attr_accessor :first_name, :last_name, :email, :media, :pagerduty_credentials, :id
13
+
14
+ def self.all(options = {})
12
15
  raise "Redis connection not set" unless redis = options[:redis]
13
- logger = options[:logger]
14
16
 
15
- entity = entity_check.entity
16
- check = entity_check.check
17
+ contact_keys = redis.keys('contact:*')
17
18
 
18
- if logger
19
- logger.debug("contacts for #{entity.id} (#{entity.name}): " + redis.smembers("contacts_for:#{entity.id}").length.to_s)
20
- logger.debug("contacts for #{check}: " + redis.smembers("contacts_for:#{check}").length.to_s)
21
- end
22
-
23
- union = redis.sunion("contacts_for:#{entity.id}", "contacts_for:#{check}")
24
- logger.debug("contacts for union of #{entity.id} and #{check}: " + union.length.to_s) if logger
25
- union
19
+ contact_keys.inject([]) {|ret, k|
20
+ k =~ /^contact:(\d+)$/
21
+ id = $1
22
+ contact = self.find_by_id(id, :redis => redis)
23
+ ret << contact if contact
24
+ ret
25
+ }
26
26
  end
27
27
 
28
28
  def self.delete_all(options = {})
29
29
  raise "Redis connection not set" unless redis = options[:redis]
30
30
 
31
- contacts = redis.keys('contact:*')
31
+ redis.del( redis.keys("contact:*") +
32
+ redis.keys("contact_media:*") +
33
+ redis.keys("contact_pagerduty:*") +
34
+ redis.keys('contacts_for:*') )
35
+ end
32
36
 
33
- contacts.each do |c|
34
- c =~ /^contact:(\d+)$/
35
- id = $1
37
+ def self.find_by_id(id, options = {})
38
+ raise "Redis connection not set" unless redis = options[:redis]
39
+ raise "No id value passed" unless id
40
+ logger = options[:logger]
36
41
 
37
- redis.del("contact:#{id}")
38
- redis.del("contact_media:#{id}")
39
- redis.del("contact_pagerduty:#{id}")
42
+ fn, ln, em = redis.hmget("contact:#{id}", 'first_name', 'last_name', 'email')
43
+ me = redis.hgetall("contact_media:#{id}")
44
+
45
+ # similar to code in instance method pagerduty_credentials
46
+ pc = nil
47
+ if service_key = redis.hget("contact_media:#{id}", 'pagerduty')
48
+ pc = redis.hgetall("contact_pagerduty:#{id}").merge('service_key' => service_key)
40
49
  end
50
+
51
+ self.new(:first_name => fn, :last_name => ln,
52
+ :email => em, :id => id, :media => me, :pagerduty_credentials => pc, :redis => redis )
41
53
  end
42
54
 
55
+
43
56
  # NB: should probably be called in the context of a Redis multi block; not doing so
44
57
  # here as calling classes may well be adding/updating multiple records in the one
45
58
  # operation
59
+ # TODO maybe return the instantiated Contact record?
46
60
  def self.add(contact, options = {})
47
61
  raise "Redis connection not set" unless redis = options[:redis]
48
62
 
49
- redis.del("contact:#{contact['id']}")
50
- redis.del("contact_media:#{contact['id']}")
51
- redis.del("contact_pagerduty:#{contact['id']}")
52
- ['first_name', 'last_name', 'email'].each do |field|
53
- redis.hset("contact:#{contact['id']}", field, contact[field])
54
- end
63
+ redis.del("contact:#{contact['id']}",
64
+ "contact_media:#{contact['id']}",
65
+ "contact_pagerduty:#{contact['id']}")
66
+
67
+ redis.hmset("contact:#{contact['id']}",
68
+ *['first_name', 'last_name', 'email'].collect {|f| [f, contact[f]]})
69
+
55
70
  contact['media'].each_pair {|medium, address|
56
71
  case medium
57
72
  when 'pagerduty'
58
73
  redis.hset("contact_media:#{contact['id']}", medium, address['service_key'])
59
- redis.hset("contact_pagerduty:#{contact['id']}", 'subdomain', address['subdomain'])
60
- redis.hset("contact_pagerduty:#{contact['id']}", 'username', address['username'])
61
- redis.hset("contact_pagerduty:#{contact['id']}", 'password', address['password'])
74
+ redis.hmset("contact_pagerduty:#{contact['id']}",
75
+ *['subdomain', 'username', 'password'].collect {|f| [f, address[f]]})
62
76
  else
63
77
  redis.hset("contact_media:#{contact['id']}", medium, address)
64
78
  end
65
79
  }
66
80
  end
67
81
 
68
- def self.pagerduty_credentials_for_contact(contact, options = {})
69
- raise "Redis connection not set" unless redis = options[:redis]
82
+ def pagerduty_credentials
83
+ return unless service_key = @redis.hget("contact_media:#{self.id}", 'pagerduty')
84
+ @redis.hgetall("contact_pagerduty:#{self.id}").
85
+ merge('service_key' => service_key)
86
+ end
87
+
88
+ def entities
89
+ @redis.keys('contacts_for:*').inject([]) {|ret, k|
90
+ if @redis.sismember(k, self.id)
91
+ k =~ /^contacts_for:(.+)$/
92
+ entity_id = $1
93
+ if entity_name = @redis.hget("entity:#{entity_id}", 'name')
94
+ ret << Flapjack::Data::Entity.new(:name => entity_name,
95
+ :id => entity_id, :redis => @redis)
96
+ end
97
+ end
98
+ ret
99
+ }
100
+ end
70
101
 
71
- return unless service_key = redis.hget("contact_media:#{contact}", 'pagerduty')
102
+ def name
103
+ [(self.first_name || ''), (self.last_name || '')].join(" ").strip
104
+ end
72
105
 
73
- redis.hgetall("contact_pagerduty:#{contact}").
74
- merge('service_key' => service_key)
106
+ private
107
+
108
+ def initialize(options = {})
109
+ raise "Redis connection not set" unless @redis = options[:redis]
110
+ [:first_name, :last_name, :email, :media, :id].each do |field|
111
+ instance_variable_set(:"@#{field.to_s}", options[field])
112
+ end
75
113
  end
76
114
 
77
115
  end
@@ -6,6 +6,9 @@ require 'flapjack/patches'
6
6
 
7
7
  require 'flapjack/data/entity'
8
8
 
9
+ # TODO might want to split the class methods out to a separate class, DAO pattern
10
+ # ( http://en.wikipedia.org/wiki/Data_access_object ).
11
+
9
12
  module Flapjack
10
13
 
11
14
  module Data
@@ -415,21 +418,33 @@ module Flapjack
415
418
  # has pagerduty credentials then there'll be one hash in the array for each set of
416
419
  # credentials.
417
420
  def pagerduty_credentials(options)
418
- creds = []
419
- raise "Redis connection not set" unless redis = options[:redis]
420
- logger = options[:logger]
421
+ # raise "Redis connection not set" unless redis = options[:redis]
421
422
 
422
- contacts = Flapjack::Data::Contact.find_all_for_entity_check(self, { :redis => redis, :logger => logger })
423
- contacts.each {|contact|
424
- cred = Flapjack::Data::Contact.pagerduty_credentials_for_contact(contact, { :redis => redis, :logger => logger })
425
- creds << cred if cred
423
+ self.contacts.inject([]) {|ret, contact|
424
+ cred = contact.pagerduty_credentials
425
+ ret << cred if cred
426
+ ret
426
427
  }
427
- creds
428
+ end
429
+
430
+ # takes a check, looks up contacts that are interested in this check (or in the check's entity)
431
+ # and returns an array of contact records
432
+ def contacts
433
+ entity = @entity
434
+ check = @check
435
+
436
+ if @logger
437
+ @logger.debug("contacts for #{@entity.id} (#{@entity.name}): " + @redis.smembers("contacts_for:#{@entity.id}").length.to_s)
438
+ @logger.debug("contacts for #{check}: " + @redis.smembers("contacts_for:#{check}").length.to_s)
439
+ end
440
+
441
+ union = @redis.sunion("contacts_for:#{@entity.id}", "contacts_for:#{check}")
442
+ @logger.debug("contacts for union of #{@entity.id} and #{check}: " + union.length.to_s) if @logger
443
+ union.collect {|c_id| Flapjack::Data::Contact.find_by_id(c_id, :redis => @redis) }
428
444
  end
429
445
 
430
446
  private
431
447
 
432
- # Passing around the redis handle like this is a SMELL.
433
448
  def initialize(entity, check, options = {})
434
449
  raise "Redis connection not set" unless @redis = options[:redis]
435
450
  raise "Invalid entity" unless @entity = entity
@@ -204,8 +204,7 @@ module Flapjack
204
204
  @redis.rpush("#{event.id}:#{notification_type}_notifications", timestamp)
205
205
  @logger.debug("Notification of type #{notification_type} is being generated for #{event.id}.")
206
206
 
207
- send_notifications(event, notification_type,
208
- Flapjack::Data::Contact.find_all_for_entity_check(entity_check, :redis => @redis))
207
+ send_notifications(event, notification_type, entity_check.contacts)
209
208
  end
210
209
 
211
210
  # takes an event, a notification type, and an array of contacts and creates jobs in resque
@@ -217,20 +216,28 @@ module Flapjack
217
216
  'time' => event.time,
218
217
  'notification_type' => notification_type }
219
218
 
220
- contacts.each {|contact_id|
221
- media = media_for_contact(contact_id)
219
+ if contacts.empty?
220
+ @notifylog.info("#{Time.now.to_s} | #{event.id} | #{notification_type} | NO CONTACTS")
221
+ return
222
+ end
222
223
 
223
- contact_deets = {'contact_id' => contact_id,
224
- 'contact_first_name' => @redis.hget("contact:#{contact_id}", 'first_name'),
225
- 'contact_last_name' => @redis.hget("contact:#{contact_id}", 'last_name'), }
224
+ contacts.each {|contact|
226
225
 
227
- notification = notification.merge(contact_deets)
226
+ if contact.media.empty?
227
+ @notifylog.info("#{Time.now.to_s} | #{event.id} | #{notification_type} | #{contact.id} | NO MEDIA FOR CONTACT")
228
+ next
229
+ end
228
230
 
229
- media.each_pair {|media_type, address|
231
+ notification.merge!({'contact_id' => contact.id,
232
+ 'contact_first_name' => contact.first_name,
233
+ 'contact_last_name' => contact.last_name, })
234
+
235
+ contact.media.each_pair {|media_type, address|
236
+
237
+ @notifylog.info("#{Time.now.to_s} | #{event.id} | " +
238
+ "#{notification_type} | #{contact.id} | #{media_type} | #{address}")
230
239
 
231
- @notifylog.info("#{Time.now.to_s} | #{event.id} | #{notification_type} | #{contact_id} | #{media} | #{address}")
232
240
  # queue this notification
233
- # FIXME: make a Contact class perhaps
234
241
  notif = notification.dup
235
242
  notif['media'] = media_type
236
243
  notif['address'] = address
@@ -239,43 +246,26 @@ module Flapjack
239
246
  notif['duration'] = dur if dur
240
247
  @logger.debug("send_notifications: sending notification: #{notif.inspect}")
241
248
 
249
+ unless @queues[media_type.to_sym]
250
+ # TODO log error
251
+ next
252
+ end
253
+
254
+ # TODO consider changing Resque jobs to use raw blpop like the others
242
255
  case media_type
243
256
  when "sms"
244
- if @queues[:sms]
245
- Resque.enqueue_to(@queues[:sms], Notification::Sms, notif)
246
- end
257
+ Resque.enqueue_to(@queues[:sms], Notification::Sms, notif)
247
258
  when "email"
248
- if @queues[:email]
249
- Resque.enqueue_to(@queues[:email], Notification::Email, notif)
250
- end
259
+ Resque.enqueue_to(@queues[:email], Notification::Email, notif)
251
260
  when "jabber"
252
- if @queues[:jabber]
253
- notif['event_count'] = @event_count if @event_count
254
- # puts a notification into the jabber queue (redis list)
255
- @redis.rpush(@queues[:jabber], Yajl::Encoder.encode(notif))
256
- end
261
+ # TODO move next line up into other notif value setting above?
262
+ notif['event_count'] = @event_count if @event_count
263
+ @redis.rpush(@queues[:jabber], Yajl::Encoder.encode(notif))
257
264
  when "pagerduty"
258
- if @queues[:pagerduty]
259
- @redis.rpush(@queues[:pagerduty], Yajl::Encoder.encode(notif))
260
- end
265
+ @redis.rpush(@queues[:pagerduty], Yajl::Encoder.encode(notif))
261
266
  end
262
267
  }
263
- if media.length == 0
264
- @notifylog.info("#{Time.now.to_s} | #{event.id} | #{notification_type} | #{contact_id} | NO MEDIA FOR CONTACT")
265
- end
266
268
  }
267
- if contacts.length == 0
268
- @notifylog.info("#{Time.now.to_s} | #{event.id} | #{notification_type} | NO CONTACTS")
269
- end
270
- end
271
-
272
- # takes a contact ID and returns a hash containing each of the media the contact wishes to be
273
- # contacted by, and the associated address for each.
274
- # eg:
275
- # media_for_contact('123') -> { :sms => "+61401234567", :email => "gno@free.dom" }
276
- #
277
- def media_for_contact(contact)
278
- @redis.hgetall("contact_media:#{contact}")
279
269
  end
280
270
 
281
271
  # generates a fairly unique identifier to use as a message id
@@ -134,7 +134,7 @@ module Flapjack
134
134
  # ok lets do it
135
135
  unacknowledged_failing_checks.each {|check|
136
136
  entity_check = Flapjack::Data::EntityCheck.for_event_id(check, { :redis => @redis_timer, :logger => @logger } )
137
- pagerduty_credentials = entity_check.pagerduty_credentials( { :redis => @redis_timer, :logger => @logger } )
137
+ pagerduty_credentials = entity_check.pagerduty_credentials(:redis => @redis_timer)
138
138
 
139
139
  if pagerduty_credentials.length == 0
140
140
  @logger.debug("Found no pagerduty creditials for #{entity_check.entity_name}:#{entity_check.check}, moving on")
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.6.36"
4
+ VERSION = "0.6.37"
5
5
  end
@@ -9,20 +9,33 @@ require 'haml'
9
9
  require 'rack/fiber_pool'
10
10
 
11
11
  require 'flapjack/pikelet'
12
+ require 'flapjack/data/contact'
12
13
  require 'flapjack/data/entity_check'
13
14
  require 'flapjack/utility'
14
15
 
15
16
  module Flapjack
16
17
  class Web < Sinatra::Base
17
18
 
18
- # doesn't work with Rack::Test for some reason
19
- unless 'test'.eql?(FLAPJACK_ENV)
20
- rescue_exception = Proc.new { |env, exception|
21
- logger.error exception.message
22
- logger.error exception.backtrace.join("\n")
23
- [503, {}, exception.message]
24
- }
19
+ if 'test'.eql?(FLAPJACK_ENV)
20
+ # expose test errors properly
21
+ set :raise_errors, true
22
+ set :show_exceptions, false
23
+ else
24
+ rescue_exception = Proc.new do |env, e|
25
+ if settings.show_exceptions?
26
+ # ensure the sinatra error page shows properly
27
+ request = Sinatra::Request.new(env)
28
+ printer = Sinatra::ShowExceptions.new(proc{ raise e })
29
+ s, h, b = printer.call(env)
30
+ [s, h, b]
31
+ else
32
+ logger.error e.message
33
+ logger.error e.backtrace.join("\n")
34
+ [503, {}, ""]
35
+ end
36
+ end
25
37
 
38
+ # doesn't work with Rack::Test unless we wrap tests in EM.synchrony blocks
26
39
  use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
27
40
  end
28
41
  use Rack::MethodOverride
@@ -62,7 +75,6 @@ module Flapjack
62
75
  end
63
76
 
64
77
  get '/check' do
65
- #begin
66
78
  @entity = params[:entity]
67
79
  @check = params[:check]
68
80
 
@@ -83,12 +95,9 @@ module Flapjack
83
95
  @current_scheduled_maintenance = entity_check.current_maintenance(:scheduled => true)
84
96
  @current_unscheduled_maintenance = entity_check.current_maintenance(:scheduled => false)
85
97
 
86
- haml :check
87
- #rescue Exception => e
88
- # puts e.message
89
- # puts e.backtrace.join("\n")
90
- #end
98
+ @contacts = entity_check.contacts
91
99
 
100
+ haml :check
92
101
  end
93
102
 
94
103
  post '/acknowledgements/:entity/:check' do
@@ -110,6 +119,7 @@ module Flapjack
110
119
  end
111
120
 
112
121
  # FIXME: there is bound to be a more idiomatic / restful way of doing this
122
+ # (probably using 'delete' or 'patch')
113
123
  post '/end_unscheduled_maintenance/:entity/:check' do
114
124
  @entity = params[:entity]
115
125
  @check = params[:check]
@@ -140,29 +150,20 @@ module Flapjack
140
150
 
141
151
  # modify scheduled maintenance
142
152
  patch '/scheduled_maintenances/:entity/:check' do
153
+ entity_check = get_entity_check(params[:entity], params[:check])
154
+ return 404 if entity_check.nil?
143
155
 
144
- begin
145
- puts "params: #{params.inspect}"
146
-
147
- entity_check = get_entity_check(params[:entity], params[:check])
148
- return 404 if entity_check.nil?
149
-
150
- end_time = Chronic.parse(params[:end_time]).to_i
151
- start_time = params[:start_time].to_i
152
- raise ArgumentError, "start time parsed to zero" unless start_time > 0
153
-
154
- patches = {}
155
- patches[:end_time] = end_time if end_time && (end_time > start_time)
156
+ end_time = Chronic.parse(params[:end_time]).to_i
157
+ start_time = params[:start_time].to_i
158
+ raise ArgumentError, "start time parsed to zero" unless start_time > 0
156
159
 
157
- raise ArgumentError.new("no valid data received to patch with") if patches.empty?
160
+ patches = {}
161
+ patches[:end_time] = end_time if end_time && (end_time > start_time)
158
162
 
159
- entity_check.update_scheduled_maintenance(start_time, patches)
160
- redirect back
163
+ raise ArgumentError.new("no valid data received to patch with") if patches.empty?
161
164
 
162
- rescue Exception => e
163
- puts e.message
164
- puts e.backtrace.join("\n")
165
- end
165
+ entity_check.update_scheduled_maintenance(start_time, patches)
166
+ redirect back
166
167
  end
167
168
 
168
169
  # delete a scheduled maintenance
@@ -174,6 +175,32 @@ module Flapjack
174
175
  redirect back
175
176
  end
176
177
 
178
+ get '/contacts' do
179
+ @contacts = Flapjack::Data::Contact.all(:redis => @@redis)
180
+ haml :contacts
181
+ end
182
+
183
+ get "/contacts/:contact" do
184
+ contact_id = params[:contact]
185
+
186
+ if contact_id
187
+ @contact = Flapjack::Data::Contact.find_by_id(contact_id, :redis => @@redis)
188
+ end
189
+
190
+ unless @contact
191
+ status 404
192
+ return
193
+ end
194
+
195
+ if @contact.media.has_key?('pagerduty')
196
+ @pagerduty_credentials = @contact.pagerduty_credentials
197
+ end
198
+
199
+ @entities = @contact.entities
200
+
201
+ haml :contact
202
+ end
203
+
177
204
  private
178
205
 
179
206
  def get_entity_check(entity, check)
@@ -0,0 +1,38 @@
1
+ :css
2
+ * { margin: 0; padding: 0; }
3
+ html { font-size: 62.5%; }
4
+ body { font-size: 16px; }
5
+ div#wrapper {
6
+ margin: 24px auto;
7
+ width: 1000px;
8
+ }
9
+ h1, h2, h3, h4, h5 {
10
+ font-family: Helvetica Neue, sans-serif;
11
+ margin-bottom: 12px;
12
+ }
13
+ table {
14
+ text-align: left;
15
+ width: 100%;
16
+ }
17
+ table th {
18
+ font-family: Helvetica Neue, sans-serif;
19
+ background-color: #eee;
20
+ }
21
+ table td, table th {
22
+ padding: 4px;
23
+ }
24
+ table td.critical {
25
+ background-color: #fb9a99;
26
+ }
27
+ table td.down {
28
+ background-color: #fb9a99;
29
+ }
30
+ table td.warning {
31
+ background-color: #f9bb34;
32
+ }
33
+ table td.ok {
34
+ background-color: #B2DF8A;
35
+ }
36
+ table td.up {
37
+ background-color: #B2DF8A;
38
+ }
@@ -0,0 +1,8 @@
1
+ %p
2
+ %a(title='' href='/') All Checks
3
+ |
4
+ %a(title='' href='/failing') Failing Checks
5
+ |
6
+ %a(title='' href='/self_stats') Internal Statistics
7
+ |
8
+ %a(title='' href='/contacts') Contacts
@@ -1,50 +1,15 @@
1
1
  !!! 5
2
+ - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
+ - nav = nav_haml.render
4
+ - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
+ - css = css_haml.render
2
6
  %html
3
7
  %head
4
- :css
5
- * { margin: 0; padding: 0; }
6
- html { font-size: 62.5%; }
7
- body { font-size: 16px; }
8
- div#wrapper {
9
- margin: 24px auto;
10
- width: 1000px;
11
- }
12
- h1, h2, h3, h4, h5 {
13
- font-family: Helvetica Neue, sans-serif;
14
- margin-bottom: 12px;
15
- }
16
- table {
17
- text-align: left;
18
- width: 100%;
19
- }
20
- table th {
21
- font-family: Helvetica Neue, sans-serif;
22
- background-color: #eee;
23
- }
24
- table td, table th {
25
- padding: 4px;
26
- }
27
- table td.critical {
28
- background-color: #fb9a99;
29
- }
30
- table td.down {
31
- background-color: #fb9a99;
32
- }
33
- table td.warning {
34
- background-color: #f9bb34;
35
- }
36
- table td.ok {
37
- background-color: #B2DF8A;
38
- }
39
- table td.up {
40
- background-color: #B2DF8A;
41
- }
8
+ %title Flapjack - Check: #{@entity}:#{@check}
9
+ = css
42
10
  %body
43
11
  %div#wrapper
44
- %p
45
- %a(title='Dashboard' href='/') All Checks
46
- |
47
- %a(title='Dashboard' href='/failing') Failing Checks
12
+ = nav
48
13
  %h1 #{@check} on #{@entity}
49
14
  %form{:action => "/acknowledgements/#{@entity}/#{@check}", :method => "post"}
50
15
  %h2
@@ -107,7 +72,7 @@
107
72
  %td= last_ack_relative
108
73
  %td= last_ack
109
74
  %h3 Scheduled Maintenance Periods
110
- - if @scheduled_maintenances && (@scheduled_maintenances.length > 0)
75
+ - if @scheduled_maintenances && !@scheduled_maintenances.empty?
111
76
  %table
112
77
  %tr
113
78
  %th Start
@@ -165,4 +130,21 @@
165
130
  %td
166
131
  %td
167
132
  %input{:type => 'submit', :value => 'Save', :class => 'button'}
168
-
133
+ %h3 Contacts
134
+ - if @contacts && !@contacts.empty?
135
+ %table
136
+ %tr
137
+ %th Name
138
+ %th Media
139
+ - @contacts.sort_by {|c| [c.last_name, c.first_name] }.each do |contact|
140
+ %tr
141
+ %td
142
+ - link = "/contacts/#{contact.id}"
143
+ %a(title='contact details' href=link) #{contact.name}
144
+ %td
145
+ - if contact.media && !contact.media.empty?
146
+ %p= contact.media.keys.collect(&:capitalize).join(", ")
147
+ - else
148
+ %p No media
149
+ - else
150
+ %p No contacts
@@ -0,0 +1,50 @@
1
+ !!! 5
2
+ - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
+ - nav = nav_haml.render
4
+ - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
+ - css = css_haml.render
6
+ %html
7
+ %head
8
+ %title Flapjack - Contact: #{@contact.name}
9
+ = css
10
+ %body
11
+ %div#wrapper
12
+ = nav
13
+ %h1= @contact.name
14
+ - if @entities && !@entities.empty?
15
+ %table
16
+ %tr
17
+ %th Entity name
18
+ %th Checks
19
+ %th Media
20
+ - @entities.sort_by(&:name).each do |entity|
21
+ %tr
22
+ %td
23
+ %p= entity.name
24
+ %td
25
+ - checks = entity.check_list
26
+ - if !checks.empty?
27
+ - checks.each do |check|
28
+ - link = "/check?entity=#{entity.name}&check=#{check}"
29
+ %p
30
+ %a(title='check status' href=link) #{check}
31
+ - else
32
+ %p No checks
33
+ %td
34
+ - if @contact.media && !@contact.media.empty?
35
+ %ul
36
+ - @contact.media.each_pair do |mk, mv|
37
+ - if 'pagerduty'.eql?(mk)
38
+ %li= "PagerDuty: "
39
+ %ul
40
+ - @pagerduty_credentials.each_pair do |pk, pv|
41
+ - if 'password'.eql?(pk)
42
+ %li= "#{pk}: ..."
43
+ - else
44
+ %li= "#{pk}: #{pv}"
45
+ - else
46
+ %li= "#{mk.capitalize}: #{mv}"
47
+ - else
48
+ %p No media
49
+ - else
50
+ %p No entities
@@ -0,0 +1,26 @@
1
+ !!! 5
2
+ - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
+ - nav = nav_haml.render
4
+ - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
+ - css = css_haml.render
6
+ %html
7
+ %head
8
+ %title Flapjack - Contacts
9
+ = css
10
+ %body
11
+ %div#wrapper
12
+ = nav
13
+ %h1 Contacts
14
+ - if @contacts && !@contacts.empty?
15
+ %table
16
+ %tr
17
+ %th Name
18
+ %th Email
19
+ - @contacts.sort_by {|c| [c.last_name, c.first_name] }.each do |contact|
20
+ %tr
21
+ %td
22
+ - link = "/contacts/#{contact.id}"
23
+ %a(title='contact details' href=link) #{contact.name}
24
+ %td= contact.email
25
+ - else
26
+ %p No contacts
@@ -1,52 +1,15 @@
1
1
  !!! 5
2
+ - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
+ - nav = nav_haml.render
4
+ - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
+ - css = css_haml.render
2
6
  %html
3
7
  %head
4
- :css
5
- * { margin: 0; padding: 0; }
6
- html { font-size: 62.5%; }
7
- body { font-size: 16px; }
8
- div#wrapper {
9
- margin: 24px auto;
10
- width: 1000px;
11
- }
12
- h1, h2, h3, h4, h5 {
13
- font-family: Helvetica Neue, sans-serif;
14
- margin-bottom: 12px;
15
- }
16
- table {
17
- text-align: left;
18
- width: 100%;
19
- }
20
- table th {
21
- font-family: Helvetica Neue, sans-serif;
22
- background-color: #eee;
23
- }
24
- table td, table th {
25
- padding: 4px;
26
- }
27
- table td.critical {
28
- background-color: #fb9a99;
29
- }
30
- table td.down {
31
- background-color: #fb9a99;
32
- }
33
- table td.warning {
34
- background-color: #f9bb34;
35
- }
36
- table td.ok {
37
- background-color: #B2DF8A;
38
- }
39
- table td.up {
40
- background-color: #B2DF8A;
41
- }
8
+ %title Flapjack
9
+ = css
42
10
  %body
43
11
  %div#wrapper
44
- %p
45
- %a(title='internal stats' href='/') All Checks
46
- |
47
- %a(title='internal stats' href='/failing') Failing Checks
48
- |
49
- %a(title='internal stats' href='/self_stats') Internal Statistics
12
+ = nav
50
13
  %h1 Flapjack Status
51
14
  %h4
52
15
  Events queued:
@@ -1,46 +1,15 @@
1
1
  !!! 5
2
+ - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
+ - nav = nav_haml.render
4
+ - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
+ - css = css_haml.render
2
6
  %html
3
7
  %head
4
- :css
5
- * { margin: 0; padding: 0; }
6
- html { font-size: 62.5%; }
7
- body { font-size: 20px; }
8
- div#wrapper {
9
- margin: 24px auto;
10
- width: 800px;
11
- }
12
- h1, h2, h3, h4, h5 {
13
- font-family: Helvetica Neue, sans-serif;
14
- margin-bottom: 12px;
15
- }
16
- table {
17
- text-align: left;
18
- width: 100%;
19
- }
20
- table th {
21
- font-family: Helvetica Neue, sans-serif;
22
- background-color: #eee;
23
- }
24
- table td, table th {
25
- padding: 4px;
26
- }
27
- table td.critical {
28
- background-color: #fb9a99;
29
- }
30
- table td.down {
31
- background-color: #fb9a99;
32
- }
33
- table td.warning {
34
- background-color: #f9bb34;
35
- }
36
- table td.ok {
37
- background-color: #B2DF8A;
38
- }
39
- table td.up {
40
- background-color: #B2DF8A;
41
- }
8
+ %title Flapjack - Self Stats
9
+ = css
42
10
  %body
43
11
  %div#wrapper
12
+ = nav
44
13
  %h1 Flapjack Self Status
45
14
  %h4
46
15
  Events queued:
@@ -105,6 +105,7 @@ describe Flapjack::Web, :sinatra => true, :redis => true do
105
105
  entity_check.should_receive(:failed?).and_return(false)
106
106
  entity_check.should_receive(:current_maintenance).with(:scheduled => true).and_return(false)
107
107
  entity_check.should_receive(:current_maintenance).with(:scheduled => false).and_return(false)
108
+ entity_check.should_receive(:contacts).and_return([])
108
109
 
109
110
  Flapjack::Data::Entity.should_receive(:find_by_name).
110
111
  with(entity_name, :redis => @redis).and_return(entity)
@@ -207,4 +208,15 @@ describe Flapjack::Web, :sinatra => true, :redis => true do
207
208
  last_response.status.should == 302
208
209
  end
209
210
 
211
+ it "shows a list of all known contacts" do
212
+ Flapjack::Data::Contact.should_receive(:all)
213
+
214
+ get "/contacts"
215
+ last_response.should be_ok
216
+ end
217
+
218
+ it "shows details of an individual contact found by email"
219
+
220
+ it "shows details of an individual contact found by id"
221
+
210
222
  end
@@ -6,6 +6,8 @@ if ENV['COVERAGE']
6
6
  end
7
7
  end
8
8
 
9
+ $testing = true
10
+
9
11
  FLAPJACK_ENV = ENV["FLAPJACK_ENV"] || 'test'
10
12
  require 'bundler'
11
13
  Bundler.require(:default, :test)
@@ -41,4 +43,4 @@ RSpec.configure do |config|
41
43
  end
42
44
 
43
45
  config.include Rack::Test::Methods, :sinatra => true
44
- end
46
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flapjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.36
4
+ version: 0.6.37
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-09-27 00:00:00.000000000 Z
14
+ date: 2012-10-03 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: daemons
@@ -352,6 +352,7 @@ files:
352
352
  - .gitignore
353
353
  - .rbenv-version
354
354
  - .rspec
355
+ - .travis.yml
355
356
  - Gemfile
356
357
  - Guardfile
357
358
  - LICENCE
@@ -415,7 +416,6 @@ files:
415
416
  - lib/flapjack/data/entity.rb
416
417
  - lib/flapjack/data/entity_check.rb
417
418
  - lib/flapjack/data/event.rb
418
- - lib/flapjack/data/notification.rb
419
419
  - lib/flapjack/executive.rb
420
420
  - lib/flapjack/filters/acknowledgement.rb
421
421
  - lib/flapjack/filters/base.rb
@@ -458,7 +458,11 @@ files:
458
458
  - lib/flapjack/utility.rb
459
459
  - lib/flapjack/version.rb
460
460
  - lib/flapjack/web.rb
461
+ - lib/flapjack/web/views/_css.haml
462
+ - lib/flapjack/web/views/_nav.haml
461
463
  - lib/flapjack/web/views/check.haml
464
+ - lib/flapjack/web/views/contact.haml
465
+ - lib/flapjack/web/views/contacts.haml
462
466
  - lib/flapjack/web/views/index.haml
463
467
  - lib/flapjack/web/views/self_stats.haml
464
468
  - lib/flapjack/worker/application.rb
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- module Flapjack
4
-
5
- module Data
6
-
7
- class Notification
8
-
9
- end
10
-
11
- end
12
-
13
- end