flapjack 1.6.0 → 2.0.0b1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (301) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -6
  3. data/.gitmodules +1 -1
  4. data/.rspec +1 -1
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +12 -13
  7. data/CHANGELOG.md +2 -9
  8. data/CONTRIBUTING.md +7 -2
  9. data/Gemfile +4 -13
  10. data/LICENCE +1 -0
  11. data/README.md +8 -2
  12. data/Rakefile +2 -2
  13. data/bin/flapjack +3 -12
  14. data/build.sh +4 -2
  15. data/etc/flapjack_config.toml.example +273 -0
  16. data/features/ack_after_sched_maint.feature +18 -21
  17. data/features/cli.feature +11 -71
  18. data/features/cli_flapjack-feed-events.feature +14 -15
  19. data/features/cli_flapjack-nagios-receiver.feature +12 -41
  20. data/features/cli_flapper.feature +12 -41
  21. data/features/cli_purge.feature +5 -6
  22. data/features/cli_receive-events.feature +6 -7
  23. data/features/cli_simulate-failed-check.feature +5 -6
  24. data/features/events.feature +206 -181
  25. data/features/events_check_names.feature +4 -7
  26. data/features/notification_rules.feature +144 -223
  27. data/features/notifications.feature +65 -57
  28. data/features/rollup.feature +45 -47
  29. data/features/steps/cli_steps.rb +4 -5
  30. data/features/steps/events_steps.rb +163 -373
  31. data/features/steps/notifications_steps.rb +408 -264
  32. data/features/steps/packaging-lintian_steps.rb +0 -4
  33. data/features/steps/time_travel_steps.rb +0 -26
  34. data/features/support/daemons.rb +6 -31
  35. data/features/support/env.rb +65 -74
  36. data/flapjack.gemspec +22 -24
  37. data/lib/flapjack.rb +14 -7
  38. data/lib/flapjack/cli/flapper.rb +74 -173
  39. data/lib/flapjack/cli/maintenance.rb +278 -109
  40. data/lib/flapjack/cli/migrate.rb +950 -0
  41. data/lib/flapjack/cli/purge.rb +19 -22
  42. data/lib/flapjack/cli/receiver.rb +150 -326
  43. data/lib/flapjack/cli/server.rb +8 -235
  44. data/lib/flapjack/cli/simulate.rb +42 -57
  45. data/lib/flapjack/configuration.rb +51 -37
  46. data/lib/flapjack/coordinator.rb +138 -129
  47. data/lib/flapjack/data/acknowledgement.rb +177 -0
  48. data/lib/flapjack/data/alert.rb +97 -158
  49. data/lib/flapjack/data/check.rb +611 -0
  50. data/lib/flapjack/data/condition.rb +70 -0
  51. data/lib/flapjack/data/contact.rb +226 -456
  52. data/lib/flapjack/data/event.rb +96 -184
  53. data/lib/flapjack/data/extensions/associations.rb +59 -0
  54. data/lib/flapjack/data/extensions/short_name.rb +25 -0
  55. data/lib/flapjack/data/medium.rb +428 -0
  56. data/lib/flapjack/data/metrics.rb +194 -0
  57. data/lib/flapjack/data/notification.rb +22 -281
  58. data/lib/flapjack/data/rule.rb +473 -0
  59. data/lib/flapjack/data/scheduled_maintenance.rb +244 -0
  60. data/lib/flapjack/data/state.rb +221 -0
  61. data/lib/flapjack/data/statistic.rb +112 -0
  62. data/lib/flapjack/data/tag.rb +277 -0
  63. data/lib/flapjack/data/test_notification.rb +182 -0
  64. data/lib/flapjack/data/unscheduled_maintenance.rb +159 -0
  65. data/lib/flapjack/data/validators/id_validator.rb +20 -0
  66. data/lib/flapjack/exceptions.rb +6 -0
  67. data/lib/flapjack/filters/acknowledgement.rb +23 -16
  68. data/lib/flapjack/filters/base.rb +0 -5
  69. data/lib/flapjack/filters/delays.rb +53 -43
  70. data/lib/flapjack/filters/ok.rb +23 -14
  71. data/lib/flapjack/filters/scheduled_maintenance.rb +3 -3
  72. data/lib/flapjack/filters/unscheduled_maintenance.rb +12 -3
  73. data/lib/flapjack/gateways/aws_sns.rb +65 -49
  74. data/lib/flapjack/gateways/aws_sns/alert.text.erb +2 -2
  75. data/lib/flapjack/gateways/aws_sns/alert_subject.text.erb +2 -2
  76. data/lib/flapjack/gateways/aws_sns/rollup_subject.text.erb +1 -1
  77. data/lib/flapjack/gateways/email.rb +107 -90
  78. data/lib/flapjack/gateways/email/alert.html.erb +19 -18
  79. data/lib/flapjack/gateways/email/alert.text.erb +20 -14
  80. data/lib/flapjack/gateways/email/alert_subject.text.erb +2 -1
  81. data/lib/flapjack/gateways/email/rollup.html.erb +14 -13
  82. data/lib/flapjack/gateways/email/rollup.text.erb +13 -10
  83. data/lib/flapjack/gateways/jabber.rb +679 -671
  84. data/lib/flapjack/gateways/jabber/alert.text.erb +9 -6
  85. data/lib/flapjack/gateways/jsonapi.rb +164 -350
  86. data/lib/flapjack/gateways/jsonapi/data/join_descriptor.rb +44 -0
  87. data/lib/flapjack/gateways/jsonapi/data/method_descriptor.rb +21 -0
  88. data/lib/flapjack/gateways/jsonapi/helpers/headers.rb +63 -0
  89. data/lib/flapjack/gateways/jsonapi/helpers/miscellaneous.rb +136 -0
  90. data/lib/flapjack/gateways/jsonapi/helpers/resources.rb +227 -0
  91. data/lib/flapjack/gateways/jsonapi/helpers/serialiser.rb +313 -0
  92. data/lib/flapjack/gateways/jsonapi/helpers/swagger_docs.rb +322 -0
  93. data/lib/flapjack/gateways/jsonapi/methods/association_delete.rb +115 -0
  94. data/lib/flapjack/gateways/jsonapi/methods/association_get.rb +288 -0
  95. data/lib/flapjack/gateways/jsonapi/methods/association_patch.rb +178 -0
  96. data/lib/flapjack/gateways/jsonapi/methods/association_post.rb +116 -0
  97. data/lib/flapjack/gateways/jsonapi/methods/metrics.rb +71 -0
  98. data/lib/flapjack/gateways/jsonapi/methods/resource_delete.rb +119 -0
  99. data/lib/flapjack/gateways/jsonapi/methods/resource_get.rb +186 -0
  100. data/lib/flapjack/gateways/jsonapi/methods/resource_patch.rb +239 -0
  101. data/lib/flapjack/gateways/jsonapi/methods/resource_post.rb +197 -0
  102. data/lib/flapjack/gateways/jsonapi/middleware/array_param_fixer.rb +27 -0
  103. data/lib/flapjack/gateways/jsonapi/{rack → middleware}/json_params_parser.rb +7 -6
  104. data/lib/flapjack/gateways/jsonapi/middleware/request_timestamp.rb +18 -0
  105. data/lib/flapjack/gateways/oobetet.rb +222 -170
  106. data/lib/flapjack/gateways/pager_duty.rb +388 -0
  107. data/lib/flapjack/gateways/pager_duty/alert.text.erb +13 -0
  108. data/lib/flapjack/gateways/slack.rb +56 -48
  109. data/lib/flapjack/gateways/slack/alert.text.erb +1 -1
  110. data/lib/flapjack/gateways/slack/rollup.text.erb +1 -1
  111. data/lib/flapjack/gateways/sms_aspsms.rb +155 -0
  112. data/lib/flapjack/gateways/sms_aspsms/alert.text.erb +7 -0
  113. data/lib/flapjack/gateways/sms_aspsms/rollup.text.erb +2 -0
  114. data/lib/flapjack/gateways/sms_messagenet.rb +77 -57
  115. data/lib/flapjack/gateways/sms_messagenet/alert.text.erb +3 -2
  116. data/lib/flapjack/gateways/sms_nexmo.rb +53 -51
  117. data/lib/flapjack/gateways/sms_nexmo/alert.text.erb +2 -2
  118. data/lib/flapjack/gateways/sms_nexmo/rollup.text.erb +1 -1
  119. data/lib/flapjack/gateways/sms_twilio.rb +79 -62
  120. data/lib/flapjack/gateways/sms_twilio/alert.text.erb +3 -2
  121. data/lib/flapjack/gateways/web.rb +437 -345
  122. data/lib/flapjack/gateways/web/middleware/request_timestamp.rb +18 -0
  123. data/lib/flapjack/gateways/web/public/css/bootstrap.css +3793 -4340
  124. data/lib/flapjack/gateways/web/public/css/bootstrap.css.map +1 -0
  125. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
  126. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.svg +273 -214
  127. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  128. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
  129. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  130. data/lib/flapjack/gateways/web/public/js/bootstrap.js +1637 -1607
  131. data/lib/flapjack/gateways/web/public/js/self_stats.js +1 -2
  132. data/lib/flapjack/gateways/web/views/_pagination.html.erb +19 -0
  133. data/lib/flapjack/gateways/web/views/check.html.erb +159 -121
  134. data/lib/flapjack/gateways/web/views/checks.html.erb +82 -41
  135. data/lib/flapjack/gateways/web/views/contact.html.erb +59 -71
  136. data/lib/flapjack/gateways/web/views/contacts.html.erb +32 -8
  137. data/lib/flapjack/gateways/web/views/index.html.erb +2 -2
  138. data/lib/flapjack/gateways/web/views/{layout.erb → layout.html.erb} +7 -23
  139. data/lib/flapjack/gateways/web/views/self_stats.html.erb +32 -33
  140. data/lib/flapjack/gateways/web/views/tag.html.erb +32 -0
  141. data/lib/flapjack/gateways/web/views/tags.html.erb +51 -0
  142. data/lib/flapjack/logger.rb +34 -3
  143. data/lib/flapjack/notifier.rb +180 -112
  144. data/lib/flapjack/patches.rb +8 -63
  145. data/lib/flapjack/pikelet.rb +185 -143
  146. data/lib/flapjack/processor.rb +323 -191
  147. data/lib/flapjack/record_queue.rb +33 -0
  148. data/lib/flapjack/redis_proxy.rb +66 -0
  149. data/lib/flapjack/utility.rb +21 -15
  150. data/lib/flapjack/version.rb +2 -1
  151. data/libexec/httpbroker.go +218 -14
  152. data/libexec/oneoff.go +13 -10
  153. data/spec/lib/flapjack/configuration_spec.rb +286 -0
  154. data/spec/lib/flapjack/coordinator_spec.rb +103 -157
  155. data/spec/lib/flapjack/data/check_spec.rb +175 -0
  156. data/spec/lib/flapjack/data/contact_spec.rb +26 -349
  157. data/spec/lib/flapjack/data/event_spec.rb +76 -291
  158. data/spec/lib/flapjack/data/medium_spec.rb +19 -0
  159. data/spec/lib/flapjack/data/rule_spec.rb +43 -0
  160. data/spec/lib/flapjack/data/scheduled_maintenance_spec.rb +976 -0
  161. data/spec/lib/flapjack/data/unscheduled_maintenance_spec.rb +34 -0
  162. data/spec/lib/flapjack/gateways/aws_sns_spec.rb +111 -60
  163. data/spec/lib/flapjack/gateways/email_spec.rb +194 -161
  164. data/spec/lib/flapjack/gateways/jabber_spec.rb +961 -162
  165. data/spec/lib/flapjack/gateways/jsonapi/methods/check_links_spec.rb +155 -0
  166. data/spec/lib/flapjack/gateways/jsonapi/methods/checks_spec.rb +426 -0
  167. data/spec/lib/flapjack/gateways/jsonapi/methods/contact_links_spec.rb +217 -0
  168. data/spec/lib/flapjack/gateways/jsonapi/methods/contacts_spec.rb +425 -0
  169. data/spec/lib/flapjack/gateways/jsonapi/methods/events_spec.rb +271 -0
  170. data/spec/lib/flapjack/gateways/jsonapi/methods/media_spec.rb +257 -0
  171. data/spec/lib/flapjack/gateways/jsonapi/methods/medium_links_spec.rb +163 -0
  172. data/spec/lib/flapjack/gateways/jsonapi/methods/metrics_spec.rb +8 -0
  173. data/spec/lib/flapjack/gateways/jsonapi/methods/rule_links_spec.rb +212 -0
  174. data/spec/lib/flapjack/gateways/jsonapi/methods/rules_spec.rb +289 -0
  175. data/spec/lib/flapjack/gateways/jsonapi/methods/scheduled_maintenance_links_spec.rb +49 -0
  176. data/spec/lib/flapjack/gateways/jsonapi/methods/scheduled_maintenances_spec.rb +242 -0
  177. data/spec/lib/flapjack/gateways/jsonapi/methods/tag_links_spec.rb +274 -0
  178. data/spec/lib/flapjack/gateways/jsonapi/methods/tags_spec.rb +302 -0
  179. data/spec/lib/flapjack/gateways/jsonapi/methods/unscheduled_maintenance_links_spec.rb +49 -0
  180. data/spec/lib/flapjack/gateways/jsonapi/methods/unscheduled_maintenances_spec.rb +339 -0
  181. data/spec/lib/flapjack/gateways/jsonapi_spec.rb +1 -1
  182. data/spec/lib/flapjack/gateways/oobetet_spec.rb +151 -79
  183. data/spec/lib/flapjack/gateways/pager_duty_spec.rb +353 -0
  184. data/spec/lib/flapjack/gateways/slack_spec.rb +53 -53
  185. data/spec/lib/flapjack/gateways/sms_aspsms_spec.rb +106 -0
  186. data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +111 -54
  187. data/spec/lib/flapjack/gateways/sms_nexmo_spec.rb +50 -51
  188. data/spec/lib/flapjack/gateways/sms_twilio_spec.rb +108 -48
  189. data/spec/lib/flapjack/gateways/web_spec.rb +144 -216
  190. data/spec/lib/flapjack/notifier_spec.rb +132 -1
  191. data/spec/lib/flapjack/pikelet_spec.rb +111 -50
  192. data/spec/lib/flapjack/processor_spec.rb +210 -40
  193. data/spec/lib/flapjack/redis_proxy_spec.rb +45 -0
  194. data/spec/lib/flapjack/utility_spec.rb +11 -15
  195. data/spec/service_consumers/fixture_data.rb +547 -0
  196. data/spec/service_consumers/pact_helper.rb +21 -32
  197. data/spec/service_consumers/pacts/flapjack-diner_v2.0.json +4652 -0
  198. data/spec/service_consumers/provider_states_for_flapjack-diner.rb +279 -322
  199. data/spec/service_consumers/provider_support.rb +8 -0
  200. data/spec/spec_helper.rb +34 -44
  201. data/spec/support/erb_view_helper.rb +1 -1
  202. data/spec/support/factories.rb +58 -0
  203. data/spec/support/jsonapi_helper.rb +15 -26
  204. data/spec/support/mock_logger.rb +43 -0
  205. data/spec/support/xmpp_comparable.rb +24 -0
  206. data/src/flapjack/transport_test.go +30 -1
  207. data/tasks/dump_keys.rake +82 -0
  208. data/tasks/events.rake +7 -7
  209. data/tasks/support/flapjack_config_benchmark.toml +28 -0
  210. data/tasks/support/flapjack_config_benchmark.yaml +0 -2
  211. metadata +175 -222
  212. data/Guardfile +0 -14
  213. data/etc/flapjack_config.yaml.example +0 -477
  214. data/features/cli_flapjack-populator.feature +0 -90
  215. data/features/support/silent_system.rb +0 -4
  216. data/lib/flapjack/cli/import.rb +0 -108
  217. data/lib/flapjack/data/entity.rb +0 -652
  218. data/lib/flapjack/data/entity_check.rb +0 -1044
  219. data/lib/flapjack/data/message.rb +0 -56
  220. data/lib/flapjack/data/migration.rb +0 -234
  221. data/lib/flapjack/data/notification_rule.rb +0 -425
  222. data/lib/flapjack/data/semaphore.rb +0 -44
  223. data/lib/flapjack/data/tagged.rb +0 -48
  224. data/lib/flapjack/gateways/jsonapi/check_methods.rb +0 -206
  225. data/lib/flapjack/gateways/jsonapi/check_presenter.rb +0 -221
  226. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +0 -186
  227. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +0 -223
  228. data/lib/flapjack/gateways/jsonapi/medium_methods.rb +0 -185
  229. data/lib/flapjack/gateways/jsonapi/metrics_methods.rb +0 -132
  230. data/lib/flapjack/gateways/jsonapi/notification_rule_methods.rb +0 -141
  231. data/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods.rb +0 -139
  232. data/lib/flapjack/gateways/jsonapi/report_methods.rb +0 -146
  233. data/lib/flapjack/gateways/pagerduty.rb +0 -318
  234. data/lib/flapjack/gateways/pagerduty/alert.text.erb +0 -10
  235. data/lib/flapjack/gateways/web/public/css/select2-bootstrap.css +0 -87
  236. data/lib/flapjack/gateways/web/public/css/select2.css +0 -615
  237. data/lib/flapjack/gateways/web/public/css/tablesort.css +0 -67
  238. data/lib/flapjack/gateways/web/public/img/select2-spinner.gif +0 -0
  239. data/lib/flapjack/gateways/web/public/img/select2.png +0 -0
  240. data/lib/flapjack/gateways/web/public/img/select2x2.png +0 -0
  241. data/lib/flapjack/gateways/web/public/js/backbone.js +0 -1581
  242. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +0 -322
  243. data/lib/flapjack/gateways/web/public/js/flapjack.js +0 -82
  244. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.js +0 -1640
  245. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.widgets.js +0 -1390
  246. data/lib/flapjack/gateways/web/public/js/modules/contact.js +0 -520
  247. data/lib/flapjack/gateways/web/public/js/modules/entity.js +0 -28
  248. data/lib/flapjack/gateways/web/public/js/modules/medium.js +0 -40
  249. data/lib/flapjack/gateways/web/public/js/select2.js +0 -3397
  250. data/lib/flapjack/gateways/web/public/js/tablesort.js +0 -44
  251. data/lib/flapjack/gateways/web/public/js/underscore.js +0 -1276
  252. data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +0 -173
  253. data/lib/flapjack/gateways/web/views/entities.html.erb +0 -30
  254. data/lib/flapjack/gateways/web/views/entity.html.erb +0 -51
  255. data/lib/flapjack/rack_logger.rb +0 -47
  256. data/lib/flapjack/redis_pool.rb +0 -42
  257. data/spec/lib/flapjack/data/entity_check_spec.rb +0 -1418
  258. data/spec/lib/flapjack/data/entity_spec.rb +0 -872
  259. data/spec/lib/flapjack/data/message_spec.rb +0 -30
  260. data/spec/lib/flapjack/data/migration_spec.rb +0 -104
  261. data/spec/lib/flapjack/data/notification_rule_spec.rb +0 -232
  262. data/spec/lib/flapjack/data/notification_spec.rb +0 -53
  263. data/spec/lib/flapjack/data/semaphore_spec.rb +0 -24
  264. data/spec/lib/flapjack/filters/acknowledgement_spec.rb +0 -6
  265. data/spec/lib/flapjack/filters/delays_spec.rb +0 -6
  266. data/spec/lib/flapjack/filters/ok_spec.rb +0 -6
  267. data/spec/lib/flapjack/filters/scheduled_maintenance_spec.rb +0 -6
  268. data/spec/lib/flapjack/filters/unscheduled_maintenance_spec.rb +0 -6
  269. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +0 -315
  270. data/spec/lib/flapjack/gateways/jsonapi/check_presenter_spec.rb +0 -223
  271. data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +0 -131
  272. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +0 -389
  273. data/spec/lib/flapjack/gateways/jsonapi/medium_methods_spec.rb +0 -231
  274. data/spec/lib/flapjack/gateways/jsonapi/notification_rule_methods_spec.rb +0 -169
  275. data/spec/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods_spec.rb +0 -114
  276. data/spec/lib/flapjack/gateways/jsonapi/report_methods_spec.rb +0 -590
  277. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +0 -249
  278. data/spec/lib/flapjack/gateways/web/views/check.html.erb_spec.rb +0 -21
  279. data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +0 -24
  280. data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +0 -16
  281. data/spec/lib/flapjack/redis_pool_spec.rb +0 -29
  282. data/spec/service_consumers/pacts/flapjack-diner_v1.0.json +0 -4702
  283. data/tasks/entities.rake +0 -151
  284. data/tasks/profile.rake +0 -282
  285. data/tmp/acknowledge.rb +0 -13
  286. data/tmp/create_config_yaml.rb +0 -16
  287. data/tmp/create_event_ok.rb +0 -30
  288. data/tmp/create_event_unknown.rb +0 -30
  289. data/tmp/create_events_failure.rb +0 -34
  290. data/tmp/create_events_ok.rb +0 -32
  291. data/tmp/create_events_ok_fail_ack_ok.rb +0 -53
  292. data/tmp/create_events_ok_failure.rb +0 -41
  293. data/tmp/create_events_ok_failure_ack.rb +0 -53
  294. data/tmp/dummy_contacts.json +0 -43
  295. data/tmp/dummy_entities.json +0 -37
  296. data/tmp/generate_nagios_test_hosts.rb +0 -16
  297. data/tmp/notification_rules.rb +0 -73
  298. data/tmp/parse_config_yaml.rb +0 -7
  299. data/tmp/redis_find_spurious_unknown_states.rb +0 -52
  300. data/tmp/test_json_post.rb +0 -19
  301. data/tmp/test_notification_rules_api.rb +0 -171
@@ -1,6 +1,6 @@
1
1
  <% summary = @alert.summary -%>
2
- <%= @alert.type_sentence_case %>: '<%= @alert.check %>' on <%= @alert.entity -%>
2
+ <%= @alert.type_sentence_case %>: '<%= @alert.check.name %>' -%>
3
3
  <% unless ['acknowledgement', 'test'].include?(@alert.notification_type) -%>
4
4
  is <%= @alert.state_title_case -%>
5
5
  <% end -%>
6
- at <%= Time.at(@alert.time).strftime('%-d %b %H:%M') %><%= (summary.nil? || summary.empty?) ? '' : ", #{summary}" -%>
6
+ at <%= Time.at(@alert.time).strftime('%-d %b %H:%M') %><%= (summary.nil? || summary.empty?) ? '' : ", #{summary}" -%>
@@ -1,2 +1,2 @@
1
1
  <%= @alert.type_sentence_case %>: <%= @alert.rollup_states_summary -%>
2
- (<%= @alert.rollup_states_detail_text(:max_checks_per_state => 3) -%>)
2
+ (<%= @alert.rollup_states_detail_text(:max_checks_per_state => 3) -%>)
@@ -1,13 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'em-synchrony'
4
- require 'em-synchrony/em-http'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'uri/https'
6
+
5
7
  require 'active_support/inflector'
6
8
 
7
- require 'flapjack/redis_pool'
9
+ require 'flapjack/redis_proxy'
10
+ require 'flapjack/record_queue'
11
+ require 'flapjack/utility'
12
+ require 'flapjack/exceptions'
8
13
 
9
14
  require 'flapjack/data/alert'
10
- require 'flapjack/utility'
15
+ require 'flapjack/data/check'
11
16
 
12
17
  module Flapjack
13
18
  module Gateways
@@ -19,83 +24,86 @@ module Flapjack
19
24
  # --data-urlencode 'Body=Sausage' \
20
25
  # -u [AccountSid]:[AuthToken]
21
26
 
27
+ TWILIO_DEFAULT_HOST = 'api.twilio.com'
28
+
29
+ attr_accessor :sent
30
+
22
31
  include Flapjack::Utility
23
32
 
24
33
  def initialize(opts = {})
34
+ @lock = opts[:lock]
35
+
25
36
  @config = opts[:config]
26
- @logger = opts[:logger]
27
- @redis_config = opts[:redis_config] || {}
28
- @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 1, :logger => @logger)
29
37
 
30
- @logger.info("starting")
31
- @logger.debug("new sms_twilio gateway pikelet with the following options: #{@config.inspect}")
38
+ # TODO support for config reloading
39
+ @queue = Flapjack::RecordQueue.new(@config['queue'] || 'sms_twilio_notifications',
40
+ Flapjack::Data::Alert)
32
41
 
33
42
  @sent = 0
34
- end
35
-
36
- def stop
37
- @logger.info("stopping")
38
- @should_quit = true
39
43
 
40
- redis_uri = @redis_config[:path] ||
41
- "redis://#{@redis_config[:host] || '127.0.0.1'}:#{@redis_config[:port] || '6379'}/#{@redis_config[:db] || '0'}"
42
- shutdown_redis = EM::Hiredis.connect(redis_uri)
43
- shutdown_redis.rpush(@config['queue'], Flapjack.dump_json('notification_type' => 'shutdown'))
44
+ Flapjack.logger.debug("new sms_twilio gateway pikelet with the following options: #{@config.inspect}")
44
45
  end
45
46
 
46
47
  def start
47
- queue = @config['queue']
48
-
49
- until @should_quit
50
- begin
51
- @logger.debug("sms_twilio gateway is going into blpop mode on #{queue}")
52
- alert = Flapjack::Data::Alert.next(queue, :redis => @redis, :logger => @logger)
53
- deliver(alert) unless alert.nil?
54
- rescue => e
55
- @logger.error "Error generating or dispatching SMS Twilio message: #{e.class}: #{e.message}\n" +
56
- e.backtrace.join("\n")
48
+ begin
49
+ Zermelo.redis = Flapjack.redis
50
+
51
+ loop do
52
+ @lock.synchronize do
53
+ @queue.foreach {|alert| handle_alert(alert) }
54
+ end
55
+
56
+ @queue.wait
57
57
  end
58
+ ensure
59
+ Flapjack.redis.quit
58
60
  end
59
61
  end
60
62
 
61
- def deliver(alert)
63
+ def stop_type
64
+ :exception
65
+ end
66
+
67
+ private
68
+
69
+ def handle_alert(alert)
62
70
  account_sid = @config["account_sid"]
63
- auth_token = @config["auth_token"]
64
- from = @config["from"]
65
- endpoint = @config["endpoint"] || "https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json"
71
+ auth_token = @config["auth_token"]
72
+ from = @config["from"]
73
+
74
+ endpoint_host = @config["endpoint_host"] || TWILIO_DEFAULT_HOST
75
+ endpoint_path = @config["endpoint_path"] || "/2010-04-01/Accounts/#{account_sid}/Messages.json"
66
76
 
67
- address = alert.address
68
- notification_id = alert.notification_id
69
- message_type = alert.rollup ? 'rollup' : 'alert'
77
+ address = alert.medium.address
78
+ notification_id = alert.id
79
+ message_type = alert.rollup ? 'rollup' : 'alert'
70
80
 
81
+ sms_dir = File.join(File.dirname(__FILE__), 'sms_twilio')
71
82
  sms_template_erb, sms_template =
72
- load_template(@config['templates'], message_type, 'text',
73
- File.join(File.dirname(__FILE__), 'sms_twilio'))
83
+ load_template(@config['templates'], message_type, 'text', sms_dir)
74
84
 
75
- @alert = alert
76
- bnd = binding
85
+ @alert = alert
86
+ bnd = binding
77
87
 
78
88
  begin
79
89
  message = sms_template_erb.result(bnd).chomp
80
90
  rescue => e
81
- @logger.error "Error while executing the ERB for an sms: " +
91
+ Flapjack.logger.error "Error while executing the ERB for an sms: " +
82
92
  "ERB being executed: #{sms_template}"
83
93
  raise
84
94
  end
85
95
 
86
96
  if @config.nil? || (@config.respond_to?(:empty?) && @config.empty?)
87
- @logger.error "sms_twilio config is missing"
97
+ Flapjack.logger.error "sms twilio config is missing"
88
98
  return
89
99
  end
90
100
 
91
101
  errors = []
92
102
 
93
- safe_message = truncate(message, 159)
94
-
95
103
  [[account_sid, "Twilio account_sid is missing"],
96
104
  [auth_token, "Twilio auth_token is missing"],
97
- [from, "SMS from address is missing"],
98
- [address, "SMS address is missing"],
105
+ [from, "SMS from address is missing"],
106
+ [address, "SMS address is missing"],
99
107
  [notification_id, "Notification id is missing"]].each do |val_err|
100
108
 
101
109
  next unless val_err.first.nil? || (val_err.first.respond_to?(:empty?) && val_err.first.empty?)
@@ -103,33 +111,42 @@ module Flapjack
103
111
  end
104
112
 
105
113
  unless errors.empty?
106
- errors.each {|err| @logger.error err }
114
+ errors.each {|err| Flapjack.logger.error err }
107
115
  return
108
116
  end
109
117
 
110
- body_data = {'To' => address,
111
- 'From' => from,
112
- 'Body' => safe_message}
113
- @logger.debug "body_data: #{body_data.inspect}"
114
- @logger.debug "authorization: [#{account_sid}, #{auth_token[0..2]}...#{auth_token[-3..-1]}]"
118
+ body_data = {
119
+ 'To' => address,
120
+ 'From' => from,
121
+ 'Body' => truncate(message, 159)
122
+ }
123
+
124
+ Flapjack.logger.debug "body_data: #{body_data.inspect}"
125
+ Flapjack.logger.debug "authorization: [#{account_sid}, #{auth_token[0..2]}...#{auth_token[-3..-1]}]"
126
+
127
+ req = Net::HTTP::Post.new(endpoint_path)
128
+ req.set_form_data(body_data)
129
+ req['Authorization'] = [account_sid, auth_token]
130
+
131
+ http_response = Net::HTTP.start(endpoint_host, 443, :use_ssl => true) do |http|
132
+ http.request(req)
133
+ end
115
134
 
116
- http = EM::HttpRequest.new(endpoint).post(:body => body_data, :head => {'authorization' => [account_sid, auth_token]})
135
+ Flapjack.logger.debug "server response: #{http_response.inspect}"
117
136
 
118
- @logger.debug "server response: #{http.response}"
137
+ status = http_response.code
119
138
 
120
- status = (http.nil? || http.response_header.nil?) ? nil : http.response_header.status
121
- if (status >= 200) && (status <= 206)
139
+ if (status.to_i >= 200) && (status.to_i <= 206)
122
140
  @sent += 1
123
- alert.record_send_success!
124
- @logger.debug "Sent SMS via Twilio, response status is #{status}, " +
125
- "notification_id: #{notification_id}"
141
+ Flapjack.logger.info "Sent SMS via Twilio, response status is #{status}, " +
142
+ "alert id: #{alert.id}"
126
143
  else
127
- @logger.error "Failed to send SMS via Twilio, response status is #{status}, " +
128
- "notification_id: #{notification_id}"
144
+ Flapjack.logger.error "Failed to send SMS via Twilio, response status is #{status}, " +
145
+ "alert id: #{alert.id}"
129
146
  end
130
147
  rescue => e
131
- @logger.error "Error generating or delivering sms to #{alert.address}: #{e.class}: #{e.message}"
132
- @logger.error e.backtrace.join("\n")
148
+ Flapjack.logger.error "Error generating or delivering twilio sms to #{alert.medium.address}: #{e.class}: #{e.message}"
149
+ Flapjack.logger.error e.backtrace.join("\n")
133
150
  raise
134
151
  end
135
152
 
@@ -1,6 +1,7 @@
1
+ <% check = @alert.check -%>
1
2
  <% summary = @alert.summary -%>
2
- <%= @alert.type_sentence_case %>: '<%= @alert.check %>' on <%= @alert.entity -%>
3
- <% unless ['acknowledgement', 'test'].include?(@alert.notification_type) -%>
3
+ <%= @alert.type_sentence_case %>: <%= "'#{check.name}'" -%>
4
+ <% unless ['acknowledgement', 'test'].include?(@alert.type) -%>
4
5
  is <%= @alert.state_title_case -%>
5
6
  <% end -%>
6
7
  at <%= Time.at(@alert.time).strftime('%-d %b %H:%M') %><%= (summary.nil? || summary.empty?) ? '' : ", #{summary}" -%>
@@ -3,16 +3,13 @@
3
3
  require 'chronic'
4
4
  require 'chronic_duration'
5
5
  require 'sinatra/base'
6
- require 'erb'
7
- require 'rack/fiber_pool'
8
- require 'json'
6
+ require 'tilt/erb'
9
7
  require 'uri'
10
8
 
11
- require 'flapjack/rack_logger'
9
+ require 'flapjack/gateways/web/middleware/request_timestamp'
10
+
11
+ require 'flapjack-diner'
12
12
 
13
- require 'flapjack/data/contact'
14
- require 'flapjack/data/entity_check'
15
- require 'flapjack/redis_pool'
16
13
  require 'flapjack/utility'
17
14
 
18
15
  module Flapjack
@@ -21,67 +18,66 @@ module Flapjack
21
18
 
22
19
  class Web < Sinatra::Base
23
20
 
24
- rescue_exception = Proc.new do |env, e|
25
- if @config['show_exceptions'].is_a?(TrueClass)
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
37
- use Rack::FiberPool, :size => 25, :rescue_exception => rescue_exception
21
+ set :root, File.dirname(__FILE__)
38
22
 
23
+ use Flapjack::Gateways::Web::Middleware::RequestTimestamp
39
24
  use Rack::MethodOverride
40
25
 
26
+ set :sessions, :true
27
+
28
+ set :raise_errors, false
29
+ set :protection, except: :path_traversal
30
+
31
+ set :views, settings.root + '/web/views'
32
+ set :public_folder, settings.root + '/web/public'
33
+
34
+ set :erb, :layout => 'layout.html'.to_sym
35
+
41
36
  class << self
42
37
  def start
43
- @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 2, :logger => @logger)
38
+ Flapjack.logger.info "starting web - class"
44
39
 
45
- @logger.info "starting web - class"
40
+ set :show_exceptions, false
41
+ @show_exceptions = Sinatra::ShowExceptions.new(self)
46
42
 
47
- if accesslog = (@config && @config['access_log'])
48
- if not File.directory?(File.dirname(accesslog))
49
- puts "Parent directory for log file #{accesslog} doesn't exist"
50
- puts "Exiting!"
51
- exit
43
+ if access_log = (@config && @config['access_log'])
44
+ unless File.directory?(File.dirname(access_log))
45
+ raise "Parent directory for log file #{access_log} doesn't exist"
52
46
  end
53
47
 
54
- access_logger = Flapjack::AsyncLogger.new(@config['access_log'])
55
- use Flapjack::CommonLogger, access_logger
48
+ use Rack::CommonLogger, ::Logger.new(@config['access_log'])
56
49
  end
57
50
 
51
+ # session's only used for error message display, so
52
+ session_secret = @config['session_secret']
53
+
54
+ use Rack::Session::Cookie, :key => 'flapjack.session',
55
+ :path => '/',
56
+ :secret => session_secret || SecureRandom.hex(64)
57
+
58
58
  @api_url = @config['api_url']
59
- if @api_url
60
- if URI.regexp(['http', 'https']).match(@api_url).nil?
61
- @logger.error "api_url is not a valid http or https URI (#{@api_url}), discarding"
62
- @api_url = nil
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
59
+ if @api_url.nil?
60
+ raise "'api_url' config must contain a Flapjack API instance address"
68
61
  end
69
62
 
70
- unless @api_url
71
- @logger.error "api_url is not configured, parts of the web interface will be broken"
63
+ uri = begin
64
+ URI(@api_url)
65
+ rescue URI::InvalidURIError
66
+ # TODO should we just log and re-raise the exception?
67
+ raise "'api_url' is not a valid URI (#{@api_url})"
72
68
  end
73
69
 
74
- @base_url = @config['base_url']
75
- unless @base_url
76
- @logger.info "base_url is not configured, setting to '/'"
77
- @base_url = '/'
70
+ unless ['http', 'https'].include?(uri.scheme)
71
+ raise "'api_url' is not a valid http or https URI (#{@api_url})"
78
72
  end
79
-
80
- unless @base_url.match(/^.*\/$/)
81
- @logger.warn "base_url must end with a trailing '/', setting to '#{@base_url}/'"
82
- @base_url = "#{@base_url}/"
73
+ unless @api_url.match(/^.*\/$/)
74
+ Flapjack.logger.info "api_url must end with a trailing '/', setting to '#{@api_url}/'"
75
+ @api_url = "#{@api_url}/"
83
76
  end
84
77
 
78
+ Flapjack::Diner.base_uri(@api_url)
79
+ Flapjack::Diner.logger = Flapjack.logger
80
+
85
81
  # constants won't be exposed to eRb scope
86
82
  @default_logo_url = "img/flapjack-2013-notext-transparent-300-300.png"
87
83
  @logo_image_file = nil
@@ -92,21 +88,17 @@ module Flapjack
92
88
  @logo_image_file = logo_image_path
93
89
  @logo_image_ext = File.extname(logo_image_path)
94
90
  else
95
- @logger.error "logo_image_path '#{logo_image_path}'' does not point to a valid file."
91
+ Flapjack.logger.error "logo_image_path '#{logo_image_path}'' does not point to a valid file."
96
92
  end
97
93
  end
98
94
 
99
- @auto_refresh = @config['auto_refresh'].respond_to?('to_i') && @config['auto_refresh'].to_i > 0 ? @config['auto_refresh'].to_i : false
95
+ @auto_refresh = (@config['auto_refresh'].respond_to?('to_i') &&
96
+ (@config['auto_refresh'].to_i > 0)) ? @config['auto_refresh'].to_i : false
100
97
  end
101
98
  end
102
99
 
103
100
  include Flapjack::Utility
104
101
 
105
- set :protection, :except => :path_traversal
106
-
107
- set :views, settings.root + '/web/views'
108
- set :public_folder, settings.root + '/web/public'
109
-
110
102
  helpers do
111
103
  def h(text)
112
104
  ERB::Util.h(text)
@@ -117,28 +109,29 @@ module Flapjack
117
109
  end
118
110
 
119
111
  def include_active?(path)
120
- request.path == "/#{path}" ? " class='active'" : ""
112
+ return '' unless request.path == "/#{path}"
113
+ " class='active'"
121
114
  end
122
115
 
123
116
  def charset_for_content_type(ct)
124
117
  charset = Encoding.default_external
125
- charset.nil? ? ct : "#{ct}; charset=#{charset}"
118
+ charset.nil? ? ct : "#{ct}; charset=#{charset.name}"
126
119
  end
127
120
  end
128
121
 
129
- def redis
130
- self.class.instance_variable_get('@redis')
131
- end
132
-
133
- def logger
134
- self.class.instance_variable_get('@logger')
122
+ ['config'].each do |class_inst_var|
123
+ define_method(class_inst_var.to_sym) do
124
+ self.class.instance_variable_get("@#{class_inst_var}")
125
+ end
135
126
  end
136
127
 
137
128
  before do
138
129
  content_type charset_for_content_type('text/html')
139
130
 
140
- @api_url = self.class.instance_variable_get('@api_url')
141
- @base_url = self.class.instance_variable_get('@base_url')
131
+ # needs to be done per-thread
132
+ Flapjack.configure_log('web', config['logger'])
133
+
134
+ @base_url = "#{request.base_url}/"
142
135
  @default_logo_url = self.class.instance_variable_get('@default_logo_url')
143
136
  @logo_image_file = self.class.instance_variable_get('@logo_image_file')
144
137
  @logo_image_ext = self.class.instance_variable_get('@logo_image_ext')
@@ -146,14 +139,14 @@ module Flapjack
146
139
 
147
140
  input = nil
148
141
  query_string = (request.query_string.respond_to?(:length) &&
149
- request.query_string.length > 0) ? "?#{request.query_string}" : ""
150
- if logger.debug?
142
+ request.query_string.length > 0) ? "?#{request.query_string}" : ""
143
+ if Flapjack.logger.debug?
151
144
  input = env['rack.input'].read
152
- logger.debug("#{request.request_method} #{request.path_info}#{query_string} #{input}")
153
- elsif logger.info?
145
+ Flapjack.logger.debug("#{request.request_method} #{request.path_info}#{query_string} #{input}")
146
+ elsif Flapjack.logger.info?
154
147
  input = env['rack.input'].read
155
148
  input_short = input.gsub(/\n/, '').gsub(/\s+/, ' ')
156
- logger.info("#{request.request_method} #{request.path_info}#{query_string} #{input_short[0..80]}")
149
+ Flapjack.logger.info("#{request.request_method} #{request.path_info}#{query_string} #{input_short[0..80]}")
157
150
  end
158
151
  env['rack.input'].rewind unless input.nil?
159
152
  end
@@ -161,366 +154,435 @@ module Flapjack
161
154
  get '/img/branding.*' do
162
155
  halt(404) unless @logo_image_file && params[:splat].first.eql?(@logo_image_ext[1..-1])
163
156
  send_file(@logo_image_file)
164
- end
157
+ end
165
158
 
166
159
  get '/' do
167
- check_stats
168
- entity_stats
160
+ @metrics = Flapjack::Diner.metrics
169
161
 
170
162
  erb 'index.html'.to_sym
171
163
  end
172
164
 
173
- get '/checks_all' do
174
- check_stats
175
- @adjective = ''
176
-
177
- checks_by_entity = Flapjack::Data::EntityCheck.find_current_names_by_entity(:redis => redis)
178
- @states = checks_by_entity.keys.inject({}) {|result, entity_name|
179
- Flapjack::Data::Entity.find_by_name(entity_name, :redis => redis, :create => true)
180
- result[entity_name] = checks_by_entity[entity_name].sort.map {|check|
181
- [check] + entity_check_state(entity_name, check)
182
- }
183
- result
184
- }
185
- @entities_sorted = checks_by_entity.keys.sort
165
+ get '/self_stats' do
166
+ @current_time = Time.now
186
167
 
187
- erb 'checks.html'.to_sym
188
- end
168
+ @api_url = self.class.instance_variable_get('@api_url')
189
169
 
190
- get '/checks_failing' do
191
- check_stats
192
- @adjective = 'failing'
170
+ @metrics = Flapjack::Diner.metrics
171
+ statistics = Flapjack::Diner.statistics
193
172
 
194
- checks_by_entity = Flapjack::Data::EntityCheck.find_current_names_failing_by_entity(:redis => redis)
195
- @states = checks_by_entity.keys.inject({}) {|result, entity|
196
- result[entity] = checks_by_entity[entity].sort.map {|check|
197
- [check] + entity_check_state(entity, check)
198
- }
199
- result
200
- }
201
- @entities_sorted = checks_by_entity.keys.sort
173
+ unless statistics.nil?
174
+ @executive_instances = statistics.each_with_object({}) do |stats, memo|
175
+ if 'global'.eql?(stats[:instance_name])
176
+ @global_stats = stats
177
+ next
178
+ end
179
+ boot_time = Time.parse(stats[:created_at])
180
+ uptime = @current_time - boot_time
181
+ uptime_string = ChronicDuration.output(uptime, :format => :short,
182
+ :keep_zero => true, :units => 2) || '0s'
202
183
 
203
- erb 'checks.html'.to_sym
204
- end
184
+ event_counters = {}
185
+ event_rates = {}
205
186
 
206
- get '/self_stats' do
207
- logger.debug "calculating self_stats"
208
- self_stats
209
- logger.debug "calculating entity_stats"
210
- entity_stats
211
- logger.debug "calculating check_stats"
212
- check_stats
187
+ [:all_events, :ok_events, :failure_events, :action_events,
188
+ :invalid_events].each do |evt|
213
189
 
214
- erb 'self_stats.html'.to_sym
215
- end
190
+ count = stats[evt]
191
+ event_counters[evt] = count
192
+ event_rates[evt] = (uptime > 0) ? (count.to_f / uptime).round : nil
193
+ end
216
194
 
217
- get '/self_stats.json' do
218
- self_stats
219
- entity_stats
220
- check_stats
221
- json_data = {
222
- 'events_queued' => @events_queued,
223
- 'all_entities' => @count_current_entities,
224
- 'failing_entities' => @count_failing_entities,
225
- 'all_checks' => @count_current_checks,
226
- 'failing_checks' => @count_failing_checks,
227
- 'processed_events' => {
228
- 'all_time' => {
229
- 'total' => @event_counters['all'].to_i,
230
- 'ok' => @event_counters['ok'].to_i,
231
- 'failure' => @event_counters['failure'].to_i,
232
- 'action' => @event_counters['action'].to_i,
195
+ memo[stats[:instance_name]] = {
196
+ :uptime => uptime,
197
+ :uptime_string => uptime_string,
198
+ :event_counters => event_counters,
199
+ :event_rates => event_rates
233
200
  }
234
- },
235
- 'check_freshness' => @current_checks_ages,
236
- 'total_keys' => @dbsize,
237
- 'uptime' => @uptime_string,
238
- 'boottime' => @boot_time,
239
- 'current_time' => Time.now,
240
- 'executive_instances' => @executive_instances,
241
- }
242
- Flapjack.dump_json(json_data)
243
- end
201
+ end
202
+ end
244
203
 
245
- get '/entities_all' do
246
- redirect '/entities'
204
+ erb 'self_stats.html'.to_sym
247
205
  end
248
206
 
249
- get '/entities' do
250
- @entities = Flapjack::Data::Entity.all(:enabled => true, :redis => redis).map {|e| e.name}
207
+ get '/tags' do
208
+ opts = {}
209
+ @name = params[:name]
210
+ opts.update(:name => @name) unless @name.nil? || @name.empty?
211
+
212
+ @tags = Flapjack::Diner.tags(:filter => opts,
213
+ :page => (params[:page] || 1))
251
214
 
252
- entity_stats(@entities)
253
- @adjective = ''
215
+ unless @tags.nil? || @tags.empty?
216
+ @pagination = pagination_from_context(Flapjack::Diner.context)
217
+ unless @pagination.nil?
218
+ @links = create_pagination_links(@pagination[:page],
219
+ @pagination[:total_pages])
220
+ end
221
+ end
254
222
 
255
- erb 'entities.html'.to_sym
223
+ erb 'tags.html'.to_sym
256
224
  end
257
225
 
258
- get '/entities_decommissioned' do
259
- entity_stats
260
- @adjective = 'decommissioned'
261
- @entities = Flapjack::Data::Entity.all(:enabled => false, :redis => redis)
226
+ get '/tags/:id' do
227
+ tag_id = params[:id]
262
228
 
263
- erb 'entities.html'.to_sym
264
- end
229
+ @tag = Flapjack::Diner.tags(tag_id, :include => 'checks')
230
+ err(404, "Could not find tag '#{tag_id}'") if @tag.nil?
265
231
 
266
- get '/entities_failing' do
267
- entity_stats
268
- @adjective = 'failing'
269
- @entities = Flapjack::Data::Entity.find_all_names_with_failing_checks(:redis => redis)
232
+ @checks = Flapjack::Diner.related(@tag, :checks)
270
233
 
271
- erb 'entities.html'.to_sym
234
+ erb 'tag.html'.to_sym
272
235
  end
273
236
 
274
- get '/entity/:entity' do
275
- @entity = params[:entity]
276
- entity_stats
277
- @states = Flapjack::Data::EntityCheck.find_current_names_for_entity_name(@entity, :redis => redis).sort.map { |check|
278
- [check] + entity_check_state(@entity, check)
279
- }.sort_by {|parts| parts }
237
+ get '/checks' do
238
+ time = Time.now
239
+
240
+ opts = {}
241
+
242
+ @name = params[:name]
243
+ opts.update(:name => @name) unless @name.nil? || @name.empty?
244
+
245
+ @enabled = boolean_from_str(params[:enabled])
246
+ opts.update(:enabled => @enabled) unless @enabled.nil?
247
+
248
+ @failing = boolean_from_str(params[:failing])
249
+ opts.update(:failing => @failing) unless @failing.nil?
250
+
251
+ @checks = Flapjack::Diner.checks(:filter => opts,
252
+ :page => (params[:page] || 1),
253
+ :include => ['current_state', 'latest_notifications',
254
+ 'current_scheduled_maintenances',
255
+ 'current_unscheduled_maintenance'])
256
+
257
+ @states = {}
258
+
259
+ unless @checks.nil? || @checks.empty?
260
+ @pagination = pagination_from_context(Flapjack::Diner.context)
261
+ unless @pagination.nil?
262
+ @links = create_pagination_links(@pagination[:page],
263
+ @pagination[:total_pages])
264
+ end
280
265
 
281
- erb 'entity.html'.to_sym
266
+ @states = @checks.each_with_object({}) do |check, memo|
267
+ memo[check[:id]] = check_state(check, time)
268
+ end
269
+ end
270
+
271
+ erb 'checks.html'.to_sym
282
272
  end
283
273
 
284
- get '/check' do
274
+ get '/checks/:id' do
275
+ check_id = params[:id]
285
276
 
286
- @entity = params[:entity]
287
- @check = params[:check]
277
+ @current_time = DateTime.now
288
278
 
289
- entity_check = get_entity_check(@entity, @check)
290
- halt(404, "Could not find check '#{@entity}:#{@check}'") if entity_check.nil?
279
+ # contacts.media will also return contacts, per JSONAPI v1 relationships
280
+ @check = Flapjack::Diner.checks(check_id,
281
+ :include => ['contacts.media', 'current_state',
282
+ 'latest_notifications',
283
+ 'current_scheduled_maintenances',
284
+ 'current_unscheduled_maintenance'])
291
285
 
292
- last_change = entity_check.last_change
286
+ halt(404, "Could not find check '#{check_id}'") if @check.nil?
293
287
 
294
- @check_state = entity_check.state
295
- @check_enabled = entity_check.enabled?
296
- @check_last_update = entity_check.last_update
297
- @check_last_change = last_change
298
- @check_summary = entity_check.summary
299
- @check_details = entity_check.details
300
- @check_perfdata = entity_check.perfdata
288
+ @contacts = []
289
+ @media_by_contact_id = {}
301
290
 
302
- @check_initial_failure_delay = entity_check.initial_failure_delay ||
303
- Flapjack::DEFAULT_INITIAL_FAILURE_DELAY
304
- @check_repeat_failure_delay = entity_check.repeat_failure_delay ||
305
- Flapjack::DEFAULT_REPEAT_FAILURE_DELAY
291
+ @state = check_extra_state(@check, @current_time)
306
292
 
307
- @check_initial_failure_delay_is_default = entity_check.initial_failure_delay ? false : true
308
- @check_repeat_failure_delay_is_default = entity_check.repeat_failure_delay ? false : true
293
+ @contacts = Flapjack::Diner.related(@check, :contacts)
309
294
 
310
- @last_notifications = last_notification_data(entity_check)
295
+ @media_by_contact_id = @contacts.inject({}) do |memo, contact|
296
+ memo[contact[:id]] = Flapjack::Diner.related(contact, :media)
297
+ memo
298
+ end
299
+
300
+ # these two requests will only get first page of 20 records, which is what we want
301
+ state_links = Flapjack::Diner.check_link_states(check_id,
302
+ :include => 'states')
311
303
 
312
- @scheduled_maintenances = entity_check.maintenances(nil, nil, :scheduled => true)
313
- @acknowledgement_id = entity_check.failed? ? entity_check.ack_hash : nil
304
+ incl = Flapjack::Diner.included_data
305
+ unless incl.nil?
306
+ @state_changes = incl['state'].nil? ? [] : incl['state'].values
307
+ end
314
308
 
315
- @current_scheduled_maintenance = entity_check.current_maintenance(:scheduled => true)
316
- @current_unscheduled_maintenance = entity_check.current_maintenance(:scheduled => false)
309
+ sm_links = Flapjack::Diner.check_link_scheduled_maintenances(check_id,
310
+ :include => 'scheduled_maintenances')
317
311
 
318
- @contacts = entity_check.contacts
312
+ incl = Flapjack::Diner.included_data
313
+ unless incl.nil?
314
+ @scheduled_maintenances = incl['scheduled_maintenance'].nil? ? [] : incl['scheduled_maintenance'].values
315
+ end
319
316
 
320
- @state_changes = entity_check.historical_states(nil, Time.now.to_i,
321
- :order => 'desc', :limit => 20)
317
+ @error = session[:error]; session.delete(:error)
322
318
 
323
319
  erb 'check.html'.to_sym
324
320
  end
325
321
 
326
- post '/acknowledgements/:entity/:check' do
327
- @entity = params[:entity]
328
- @check = params[:check]
329
- @summary = params[:summary]
330
- @acknowledgement_id = params[:acknowledgement_id]
322
+ post "/acknowledgements" do
323
+ summary = params[:summary]
324
+ check_id = params[:check_id]
331
325
 
332
326
  dur = ChronicDuration.parse(params[:duration] || '')
333
- @duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
327
+ duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
334
328
 
335
- entity_check = get_entity_check(@entity, @check)
336
- halt(404, "Could not find check '#{@entity}:#{@check}'") if entity_check.nil?
329
+ # FIXME create with known id, poll a few times and return
330
+ # success/failure in session -- or maybe some AJAX method to
331
+ # show success/failure?
337
332
 
338
- ack = Flapjack::Data::Event.create_acknowledgement(
339
- @entity, @check,
340
- :summary => (@summary || ''),
341
- :acknowledgement_id => @acknowledgement_id,
342
- :duration => @duration,
343
- :redis => redis)
333
+ Flapjack::Diner.create_acknowledgements(:summary => summary,
334
+ :duration => duration, :check => check_id)
335
+
336
+ err = Flapjack::Diner.error
337
+ unless err.nil?
338
+ session[:error] = "Could not create the acknowledgement: #{err}"
339
+ end
344
340
 
345
341
  redirect back
346
342
  end
347
343
 
348
- # FIXME: there is bound to be a more idiomatic / restful way of doing this
349
- # (probably using 'delete' or 'patch')
350
- post '/end_unscheduled_maintenance/:entity/:check' do
351
- @entity = params[:entity]
352
- @check = params[:check]
344
+ patch '/unscheduled_maintenances/:id' do
345
+ unscheduled_maintenance_id = params[:id]
353
346
 
354
- entity_check = get_entity_check(@entity, @check)
355
- halt(404, "Could not find check '#{@entity}:#{@check}'") if entity_check.nil?
347
+ Flapjack::Diner.update_unscheduled_maintenances(
348
+ :id => unscheduled_maintenance_id, :end_time => Time.now)
356
349
 
357
- entity_check.end_unscheduled_maintenance(Time.now.to_i)
350
+ err = Flapjack::Diner.error
351
+ unless err.nil?
352
+ session[:error] = "Could not end unscheduled maintenance: #{err}"
353
+ end
358
354
 
359
355
  redirect back
360
356
  end
361
357
 
362
- # create scheduled maintenance
363
- post '/scheduled_maintenances/:entity/:check' do
364
- start_time = Chronic.parse(params[:start_time]).to_i
365
- halt(400, "Start time '#{params[:start_time]}' parsed to 0") if start_time == 0
358
+ post '/scheduled_maintenances' do
359
+ check_id = params[:check_id]
360
+
361
+ start_time = Chronic.parse(params[:start_time])
362
+ raise ArgumentError, "start time parsed to nil" if start_time.nil?
366
363
  duration = ChronicDuration.parse(params[:duration])
367
364
  summary = params[:summary]
368
365
 
369
- entity_check = get_entity_check(params[:entity], params[:check])
370
- halt(404, "Could not find check '#{params[:entity]}:#{params[:check]}'") if entity_check.nil?
366
+ Flapjack::Diner.create_scheduled_maintenances(:summary => summary,
367
+ :start_time => start_time, :end_time => (start_time + duration),
368
+ :check => check_id)
369
+
370
+ err = Flapjack::Diner.error
371
+ unless err.nil?
372
+ Flapjack.logger.info "Could not create scheduled maintenance: #{err}"
373
+ session[:error] = "Could not create scheduled maintenance for the check."
374
+ end
371
375
 
372
- entity_check.create_scheduled_maintenance(start_time, duration,
373
- :summary => summary)
374
376
  redirect back
375
377
  end
376
378
 
377
- # delete a scheduled maintenance
378
- delete '/scheduled_maintenances/:entity/:check' do
379
- entity_check = get_entity_check(params[:entity], params[:check])
380
- halt(404, "Could not find check '#{params[:entity]}:#{params[:check]}'") if entity_check.nil?
379
+ patch '/checks/:id' do
380
+ check_id = params[:id]
381
+
382
+ Flapjack::Diner.update_checks(:id => check_id, :enabled => false)
383
+
384
+ err = Flapjack::Diner.error
385
+ unless err.nil?
386
+ Flapjack.logger.info "Could not disable check: #{err}"
387
+ session[:error] = "Could not disable the check."
388
+ end
389
+
390
+ redirect '/'
391
+ end
392
+
393
+ patch '/scheduled_maintenances/:id' do
394
+ scheduled_maintenance_id = params[:id]
395
+
396
+ Flapjack::Diner.update_scheduled_maintenances({:id => scheduled_maintenance_id,
397
+ :end_time => Time.now})
398
+
399
+ err = Flapjack::Diner.error
400
+ unless err.nil?
401
+ Flapjack.logger.info "Could not end scheduled maintenance: #{err}"
402
+ session[:error] = "Could not end scheduled maintenance."
403
+ end
381
404
 
382
- entity_check.end_scheduled_maintenance(params[:start_time].to_i)
383
405
  redirect back
384
406
  end
385
407
 
386
- # delete a check (actually just disables it)
387
- delete '/checks/:entity/:check' do
388
- entity_check = get_entity_check(params[:entity], params[:check])
389
- halt(404, "Could not find check '#{params[:entity]}:#{params[:check]}'") if entity_check.nil?
408
+ # FIXME should fail if its start time or end_time is in the past
409
+ # we'll allow the API to delete without fear or favour though
410
+ delete '/scheduled_maintenances/:id' do
411
+ scheduled_maintenance_id = params[:id]
412
+
413
+ Flapjack::Diner.delete_scheduled_maintenances(scheduled_maintenance_id)
414
+
415
+ err = Flapjack::Diner.error
416
+ unless err.nil?
417
+ Flapjack.logger.info "Could not delete scheduled maintenance: #{err}"
418
+ session[:error] = "Could not delete scheduled maintenance."
419
+ end
390
420
 
391
- entity_check.disable!
392
421
  redirect back
393
422
  end
394
423
 
395
424
  get '/contacts' do
396
- @contacts = Flapjack::Data::Contact.all(:redis => redis)
425
+ opts = {}
426
+ @name = params[:name]
427
+ opts.update(:name => @name) unless @name.nil?
428
+
429
+ @contacts = Flapjack::Diner.contacts(:page => params[:page] || 1,
430
+ :filter => opts, :sort => '+name')
431
+
432
+ unless @contacts.nil?
433
+ @pagination = pagination_from_context(Flapjack::Diner.context)
434
+ unless @pagination.nil?
435
+ @links = create_pagination_links(@pagination[:page],
436
+ @pagination[:total_pages])
437
+ end
438
+ end
397
439
 
398
440
  erb 'contacts.html'.to_sym
399
441
  end
400
442
 
401
- get '/edit_contacts' do
402
- erb 'edit_contacts.html'.to_sym
403
- end
404
-
405
- get "/contacts/:contact" do
406
- contact_id = params[:contact]
407
- halt(404, "No contact id") if contact_id.nil?
443
+ get "/contacts/:id" do
444
+ contact_id = params[:id]
408
445
 
409
- @contact = Flapjack::Data::Contact.find_by_id(contact_id, :redis => redis)
446
+ @contact = Flapjack::Diner.contacts(contact_id,
447
+ :include => ['checks', 'media.alerting_checks',
448
+ 'rules.tags', 'rules.media'])
410
449
  halt(404, "Could not find contact '#{contact_id}'") if @contact.nil?
411
450
 
412
- if @contact.media.has_key?('pagerduty')
413
- @pagerduty_credentials = @contact.pagerduty_credentials
451
+ @checks = []
452
+ @media = []
453
+ @rules = []
454
+
455
+ @alerting_checks_by_media_id = {}
456
+
457
+ @tags_by_rule_id = {}
458
+ @media_by_rule_id = {}
459
+
460
+ @checks = Flapjack::Diner.related(@contact, :checks)
461
+ @media = Flapjack::Diner.related(@contact, :media)
462
+
463
+ unless @media.nil? || @media.empty?
464
+ @alerting_checks_by_media_id = @media.inject({}) do |memo, medium|
465
+ memo[medium[:id]] = Flapjack::Diner.related(medium, :alerting_checks)
466
+ memo
467
+ end
414
468
  end
415
469
 
416
- # FIXME: intersect with current checks, or push down to Contact.entities
417
- @entities_and_checks = @contact.entities(:checks => true).sort_by {|ec|
418
- ec[:entity].name
419
- }
470
+ @rules = Flapjack::Diner.related(@contact, :rules)
471
+
472
+ unless @rules.nil? || @rules.empty?
473
+ @tags_by_rule_id = @rules.inject({}) do |memo, rule|
474
+ memo[rule[:id]] = Flapjack::Diner.related(rule, :tags)
475
+ memo
476
+ end
477
+
478
+ @media_by_rule_id = @rules.inject({}) do |memo, rule|
479
+ memo[rule[:id]] = Flapjack::Diner.related(rule, :media)
480
+ memo
481
+ end
482
+ end
420
483
 
421
484
  erb 'contact.html'.to_sym
422
485
  end
423
486
 
424
- private
487
+ error do
488
+ e = env['sinatra.error']
489
+ # trace = e.backtrace.join("\n")
490
+ # puts trace
425
491
 
426
- def get_entity_check(entity, check)
427
- entity_obj = (entity && entity.length > 0) ?
428
- Flapjack::Data::Entity.find_by_name(entity, :redis => redis) : nil
429
- return if entity_obj.nil? || (check.nil? || check.length == 0)
430
- Flapjack::Data::EntityCheck.for_entity(entity_obj, check, :redis => redis)
492
+ # Rack::CommonLogger doesn't log requests which result in exceptions.
493
+ # If you want something done properly, do it yourself...
494
+ access_log = self.class.instance_variable_get('@middleware').detect {|mw|
495
+ mw.first.is_a?(::Rack::CommonLogger)
496
+ }
497
+ unless access_log.nil?
498
+ access_log.first.send(:log, status_code,
499
+ ::Rack::Utils::HeaderHash.new(headers), msg,
500
+ env['request_timestamp'])
501
+ end
502
+ self.class.instance_variable_get('@show_exceptions').pretty(env, e)
431
503
  end
432
504
 
433
- def entity_check_state(entity_name, check)
434
- entity = Flapjack::Data::Entity.find_by_name(entity_name,
435
- :redis => redis)
436
- return ['-', '-', 'never', 'never', false, false, 'never'] if entity.nil?
437
- entity_check = Flapjack::Data::EntityCheck.for_entity(entity,
438
- check, :redis => redis)
439
- summary = entity_check.summary
440
- summary = summary[0..76] + '...' unless (summary.nil? || (summary.length < 81))
441
- latest_notif =
442
- {:problem => entity_check.last_notification_for_state(:problem)[:timestamp],
443
- :recovery => entity_check.last_notification_for_state(:recovery)[:timestamp],
444
- :acknowledgement => entity_check.last_notification_for_state(:acknowledgement)[:timestamp]
445
- }.max_by {|n| n[1] || 0}
446
-
447
- lc = entity_check.last_change
448
- last_change = lc ? (ChronicDuration.output(Time.now.to_i - lc.to_i,
449
- :format => :short, :keep_zero => true, :units => 2) || '0s') : 'never'
450
-
451
- lu = entity_check.last_update
452
- last_update = lu ? (ChronicDuration.output(Time.now.to_i - lu.to_i,
453
- :format => :short, :keep_zero => true, :units => 2) || '0s') : 'never'
454
-
455
- ln = latest_notif[1]
456
- last_notified = ln ? (ChronicDuration.output(Time.now.to_i - ln.to_i,
457
- :format => :short, :keep_zero => true, :units => 2) || '0s') + ", #{latest_notif[0]}" : 'never'
458
-
459
- [(entity_check.state || '-'),
460
- (summary || '-'),
461
- last_change,
462
- last_update,
463
- entity_check.in_unscheduled_maintenance?,
464
- entity_check.in_scheduled_maintenance?,
465
- last_notified
466
- ]
467
- end
505
+ private
468
506
 
469
- def self_stats
470
- @fqdn = `/bin/hostname -f`.chomp
471
- @pid = Process.pid
472
-
473
- @dbsize = redis.dbsize
474
- @executive_instances = redis.keys("executive_instance:*").inject({}) do |memo, i|
475
- instance_id = i.match(/executive_instance:(.*)/)[1]
476
- boot_time = redis.hget(i, 'boot_time').to_i
477
- uptime = Time.now.to_i - boot_time
478
- uptime_string = (ChronicDuration.output(uptime, :format => :short, :keep_zero => true, :units => 2) || '0s')
479
- event_counters = redis.hgetall("event_counters:#{instance_id}")
480
- event_rates = event_counters.inject({}) do |er, ec|
481
- er[ec[0]] = uptime && uptime > 0 ? (ec[1].to_f / uptime).round : nil
482
- er
507
+ def check_state(check, time)
508
+ current_state = Flapjack::Diner.related(check, :current_state)
509
+
510
+ last_changed = if current_state.nil? || current_state[:created_at].nil?
511
+ nil
512
+ else
513
+ begin
514
+ DateTime.parse(current_state[:created_at])
515
+ rescue ArgumentError
516
+ Flapjack.logger.warn("error parsing check state :created_at ( #{current_state.inspect} )")
483
517
  end
484
- memo[instance_id] = {
485
- 'boot_time' => boot_time,
486
- 'uptime' => uptime,
487
- 'uptime_string' => uptime_string,
488
- 'event_counters' => event_counters,
489
- 'event_rates' => event_rates
490
- }
491
- memo
492
518
  end
493
- @event_counters = redis.hgetall('event_counters')
494
- @events_queued = redis.llen('events')
495
- @current_checks_ages = Flapjack::Data::EntityCheck.find_all_split_by_freshness([0, 60, 300, 900, 3600], {:redis => redis, :logger => logger, :counts => true } )
496
- end
497
519
 
498
- def entity_stats(entities = nil)
499
- @count_current_entities = (entities || Flapjack::Data::Entity.all(:enabled => true, :redis => redis)).length
500
- @count_failing_entities = Flapjack::Data::Entity.find_all_names_with_failing_checks(:redis => redis).length
501
- end
520
+ last_updated = if current_state.nil? || current_state[:updated_at].nil?
521
+ nil
522
+ else
523
+ begin
524
+ DateTime.parse(current_state[:updated_at])
525
+ rescue ArgumentError
526
+ Flapjack.logger.warn("error parsing check state :updated_at ( #{current_state.inspect} )")
527
+ end
528
+ end
529
+
530
+ latest_notifications = Flapjack::Diner.related(check, :latest_notifications)
502
531
 
503
- def check_stats
504
- @count_current_checks = Flapjack::Data::EntityCheck.count_current(:redis => redis)
505
- @count_failing_checks = Flapjack::Data::EntityCheck.count_current_failing(:redis => redis)
532
+ current_scheduled_maintenances = Flapjack::Diner.related(check, :current_scheduled_maintenances)
533
+ current_scheduled_maintenance = current_scheduled_maintenances.max_by do |sm|
534
+ begin
535
+ DateTime.parse(sm[:end_time]).to_i
536
+ rescue ArgumentError
537
+ Flapjack.logger.warn "Couldn't parse time from current_scheduled_maintenances"
538
+ -1
539
+ end
540
+ end
541
+
542
+ in_scheduled_maintenance = !current_scheduled_maintenance.nil?
543
+
544
+ current_unscheduled_maintenance = Flapjack::Diner.related(check, :current_unscheduled_maintenance)
545
+ in_unscheduled_maintenance = !current_unscheduled_maintenance.nil?
546
+
547
+ {
548
+ :condition => current_state.nil? ? '-' : current_state[:condition],
549
+ :summary => current_state.nil? ? '-' : current_state[:summary],
550
+ :latest_notifications => (latest_notifications || []),
551
+ :last_changed => last_changed,
552
+ :last_updated => last_updated,
553
+ :in_scheduled_maintenance => in_scheduled_maintenance,
554
+ :in_unscheduled_maintenance => in_unscheduled_maintenance
555
+ }
506
556
  end
507
557
 
508
- def last_notification_data(entity_check)
509
- last_notifications = entity_check.last_notifications_of_each_type
510
- [:critical, :warning, :unknown, :recovery, :acknowledgement].inject({}) do |memo, type|
511
- if last_notifications[type] && last_notifications[type][:timestamp]
512
- t = Time.at(last_notifications[type][:timestamp])
513
- memo[type] = {:time => t.to_s,
514
- :relative => relative_time_ago(t) + " ago",
515
- :summary => last_notifications[type][:summary]}
558
+ def check_extra_state(check, time)
559
+ state = check_state(check, time)
560
+
561
+ current_state = Flapjack::Diner.related(check, :current_state)
562
+
563
+ current_scheduled_maintenances = Flapjack::Diner.related(check, :current_scheduled_maintenances)
564
+ current_scheduled_maintenance = current_scheduled_maintenances.max_by do |sm|
565
+ begin
566
+ DateTime.parse(sm[:end_time]).to_i
567
+ rescue ArgumentError
568
+ Flapjack.logger.warn "Couldn't parse time from current_scheduled_maintenances"
569
+ -1
516
570
  end
517
- memo
518
571
  end
572
+
573
+ current_unscheduled_maintenance = Flapjack::Diner.related(check, :current_unscheduled_maintenance)
574
+
575
+ state.merge(
576
+ :details => current_state.nil? ? '-' : current_state[:details],
577
+ :perfdata => current_state.nil? ? '-' : current_state[:perfdata],
578
+ :current_scheduled_maintenances => (current_scheduled_maintenances || []),
579
+ :current_scheduled_maintenance => current_scheduled_maintenance,
580
+ :current_unscheduled_maintenance => current_unscheduled_maintenance,
581
+ )
519
582
  end
520
583
 
521
- def require_css(*css)
522
- @required_css ||= []
523
- @required_css += css
584
+ def pagination_from_context(context)
585
+ ((context || {})[:meta] || {})[:pagination]
524
586
  end
525
587
 
526
588
  def require_js(*js)
@@ -529,24 +591,24 @@ module Flapjack
529
591
  @required_js.uniq!
530
592
  end
531
593
 
594
+ def require_css(*css)
595
+ @required_css ||= []
596
+ @required_css += css
597
+ @required_css.uniq!
598
+ end
599
+
532
600
  def include_required_js
533
- if @required_js
534
- @required_js.map { |filename|
535
- "<script type='text/javascript' src='#{link_to("js/#{filename}.js")}'></script>"
536
- }.join("\n ")
537
- else
538
- ""
539
- end
601
+ return "" if @required_js.nil?
602
+ @required_js.map { |filename|
603
+ "<script type='text/javascript' src='#{link_to("js/#{filename}.js")}'></script>"
604
+ }.join("\n ")
540
605
  end
541
606
 
542
607
  def include_required_css
543
- if @required_css
544
- @required_css.map { |filename|
545
- %(<link rel="stylesheet" href="#{link_to("css/#{filename}.css")}" media="screen">)
546
- }.join("\n ")
547
- else
548
- ""
549
- end
608
+ return "" if @required_css.nil?
609
+ @required_css.map { |filename|
610
+ %(<link rel="stylesheet" href="#{link_to("css/#{filename}.css")}" media="screen">)
611
+ }.join("\n ")
550
612
  end
551
613
 
552
614
  # from http://gist.github.com/98310
@@ -573,7 +635,37 @@ module Flapjack
573
635
  end
574
636
 
575
637
  def include_page_title
576
- @page_title ? "#{@page_title} | Flapjack" : "Flapjack"
638
+ if instance_variable_defined?('@page_title') && !@page_title.nil?
639
+ return "#{@page_title} | Flapjack"
640
+ end
641
+ "Flapjack"
642
+ end
643
+
644
+ def boolean_from_str(str)
645
+ case str
646
+ when '0', 'f', 'false', 'n', 'no'
647
+ false
648
+ when '1', 't', 'true', 'y', 'yes'
649
+ true
650
+ end
651
+ end
652
+
653
+ def create_pagination_links(page, total_pages)
654
+ pages = {}
655
+ pages[:first] = 1
656
+ pages[:prev] = page - 1 if (page > 1)
657
+ pages[:next] = page + 1 if page < total_pages
658
+ pages[:last] = total_pages
659
+
660
+ url_without_params = request.url.split('?').first
661
+
662
+ links = {}
663
+ pages.each do |key, value|
664
+ page_params = {'page' => value }
665
+ new_params = request.params.merge(page_params)
666
+ links[key] = "#{url_without_params}?#{new_params.to_query}"
667
+ end
668
+ links
577
669
  end
578
670
  end
579
671
  end