flapjack 0.6.36 → 0.6.37

Sign up to get free protection for your applications and to get access to all the features.
@@ -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