flapjack 1.6.0 → 2.0.0b1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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