Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

WIP - basic functionality is all ported over, and the app works at a …

…basic level. Authentication is not implemented yet, nor is date range selection for the events page, and need to build out cucumber tests around the frontend.
  • Loading branch information...
commit 2e81ad6e6e111abd6f31c91c521c1f750dbc93f1 0 parents
@jayzes authored
Showing with 2,331 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +1 −0  .rvmrc
  3. +15 −0 Gemfile
  4. +138 −0 Gemfile.lock
  5. +256 −0 README
  6. +7 −0 Rakefile
  7. +3 −0  app/controllers/application_controller.rb
  8. +88 −0 app/controllers/events_controller.rb
  9. +12 −0 app/controllers/levels_controller.rb
  10. +73 −0 app/controllers/services_controller.rb
  11. +10 −0 app/controllers/status_images_controller.rb
  12. +77 −0 app/controllers/statuses_controller.rb
  13. +53 −0 app/helpers/application_helper.rb
  14. +2 −0  app/helpers/statuses_helper.rb
  15. +41 −0 app/models/event.rb
  16. +27 −0 app/models/level.rb
  17. +89 −0 app/models/service.rb
  18. +50 −0 app/models/status.rb
  19. +63 −0 app/views/layouts/application.html.erb
  20. +62 −0 app/views/services/_legend.html.erb
  21. +40 −0 app/views/services/index.html.erb
  22. +91 −0 app/views/services/show.html.erb
  23. +4 −0 config.ru
  24. +43 −0 config/application.rb
  25. +6 −0 config/boot.rb
  26. +8 −0 config/cucumber.yml
  27. +25 −0 config/database.yml
  28. +5 −0 config/environment.rb
  29. +26 −0 config/environments/development.rb
  30. +49 −0 config/environments/production.rb
  31. +35 −0 config/environments/test.rb
  32. +7 −0 config/initializers/backtrace_silencers.rb
  33. +10 −0 config/initializers/inflections.rb
  34. +1 −0  config/initializers/json.rb
  35. +5 −0 config/initializers/mime_types.rb
  36. +7 −0 config/initializers/secret_token.rb
  37. +8 −0 config/initializers/session_store.rb
  38. +5 −0 config/locales/en.yml
  39. +20 −0 config/routes.rb
  40. +15 −0 db/migrate/20110318043846_create_services.rb
  41. +17 −0 db/migrate/20110318045250_create_statuses.rb
  42. +17 −0 db/migrate/20110318051139_create_events.rb
  43. +48 −0 db/schema.rb
  44. +29 −0 db/seeds.rb
  45. +2 −0  doc/README_FOR_APP
  46. +88 −0 features/api/events.feature
  47. +13 −0 features/api/levels.feature
  48. +61 −0 features/api/services.feature
  49. +12 −0 features/api/status_images.feature
  50. +67 −0 features/api/statuses.feature
  51. +9 −0 features/step_definitions/api_steps.rb
  52. +3 −0  features/step_definitions/timecop_steps.rb
  53. +20 −0 features/step_definitions/utility_steps.rb
  54. +211 −0 features/step_definitions/web_steps.rb
  55. +46 −0 features/support/env.rb
  56. +33 −0 features/support/paths.rb
  57. +39 −0 features/support/selectors.rb
  58. 0  lib/tasks/.gitkeep
  59. +57 −0 lib/tasks/cucumber.rake
  60. +26 −0 public/404.html
  61. +26 −0 public/422.html
  62. +26 −0 public/500.html
  63. 0  public/favicon.ico
  64. BIN  public/images/button_bg.png
  65. BIN  public/images/datepicker.gif
  66. BIN  public/images/icon_sprite.png
  67. BIN  public/images/logo.png
  68. BIN  public/images/poweredbystash.png
  69. BIN  public/images/progress_bar.gif
  70. BIN  public/images/rails.png
  71. BIN  public/images/slider_h_bg.gif
  72. BIN  public/images/slider_handles.png
  73. BIN  public/images/slider_v_bg.gif
  74. BIN  public/images/small-information.png
  75. BIN  public/images/status/address-book--arrow.png
  76. BIN  public/images/status/address-book--exclamation.png
  77. BIN  public/images/status/address-book--minus.png
  78. BIN  public/images/status/address-book--pencil.png
  79. BIN  public/images/status/address-book--plus.png
  80. BIN  public/images/status/address-book-blue.png
  81. BIN  public/images/status/address-book-open.png
  82. BIN  public/images/status/address-book.png
  83. BIN  public/images/status/alarm-clock--arrow.png
  84. BIN  public/images/status/alarm-clock--exclamation.png
  85. BIN  public/images/status/alarm-clock--minus.png
  86. BIN  public/images/status/alarm-clock--pencil.png
  87. BIN  public/images/status/alarm-clock--plus.png
  88. BIN  public/images/status/alarm-clock-blue.png
  89. BIN  public/images/status/alarm-clock-select-remain.png
  90. BIN  public/images/status/alarm-clock-select.png
  91. BIN  public/images/status/alarm-clock.png
  92. BIN  public/images/status/anchor.png
  93. BIN  public/images/status/application--arrow.png
  94. BIN  public/images/status/application--exclamation.png
  95. BIN  public/images/status/application--minus.png
  96. BIN  public/images/status/application--pencil.png
  97. BIN  public/images/status/application--plus.png
  98. BIN  public/images/status/application-block.png
  99. BIN  public/images/status/application-blog.png
  100. BIN  public/images/status/application-blue.png
  101. BIN  public/images/status/application-browser.png
  102. BIN  public/images/status/application-detail.png
  103. BIN  public/images/status/application-dialog.png
  104. BIN  public/images/status/application-dock-090.png
  105. BIN  public/images/status/application-dock-180.png
  106. BIN  public/images/status/application-dock-270.png
  107. BIN  public/images/status/application-dock-tab.png
  108. BIN  public/images/status/application-dock.png
  109. BIN  public/images/status/application-document.png
  110. BIN  public/images/status/application-documents.png
  111. BIN  public/images/status/application-export.png
  112. BIN  public/images/status/application-form.png
  113. BIN  public/images/status/application-home.png
  114. BIN  public/images/status/application-icon.png
  115. BIN  public/images/status/application-image.png
  116. BIN  public/images/status/application-import.png
  117. BIN  public/images/status/application-list.png
  118. BIN  public/images/status/application-monitor.png
  119. BIN  public/images/status/application-network.png
  120. BIN  public/images/status/application-rename.png
  121. BIN  public/images/status/application-resize-actual.png
  122. BIN  public/images/status/application-resize-full.png
  123. BIN  public/images/status/application-resize.png
  124. BIN  public/images/status/application-run.png
  125. BIN  public/images/status/application-search-result.png
  126. BIN  public/images/status/application-share.png
  127. BIN  public/images/status/application-sidebar-collapse.png
  128. BIN  public/images/status/application-sidebar-expand.png
  129. BIN  public/images/status/application-sidebar-list.png
  130. BIN  public/images/status/application-sidebar.png
  131. BIN  public/images/status/application-small-blue.png
  132. BIN  public/images/status/application-small-list-blue.png
  133. BIN  public/images/status/application-small-list.png
  134. BIN  public/images/status/application-small.png
  135. BIN  public/images/status/application-split-tile.png
  136. BIN  public/images/status/application-split-vertical.png
  137. BIN  public/images/status/application-split.png
  138. BIN  public/images/status/application-table.png
  139. BIN  public/images/status/application-task.png
  140. BIN  public/images/status/application-terminal.png
  141. BIN  public/images/status/application-text-image.png
  142. BIN  public/images/status/application-text.png
  143. BIN  public/images/status/application-tree.png
  144. BIN  public/images/status/application-wave.png
  145. BIN  public/images/status/application.png
  146. BIN  public/images/status/applications-blue.png
  147. BIN  public/images/status/applications-stack.png
  148. BIN  public/images/status/applications.png
  149. BIN  public/images/status/arrow-000-medium.png
  150. BIN  public/images/status/arrow-000-small.png
  151. BIN  public/images/status/arrow-045-medium.png
  152. BIN  public/images/status/arrow-045-small.png
  153. BIN  public/images/status/arrow-045.png
  154. BIN  public/images/status/arrow-090-medium.png
  155. BIN  public/images/status/arrow-090-small.png
  156. BIN  public/images/status/arrow-090.png
  157. BIN  public/images/status/arrow-135-medium.png
  158. BIN  public/images/status/arrow-135-small.png
  159. BIN  public/images/status/arrow-135.png
  160. BIN  public/images/status/arrow-180-medium.png
  161. BIN  public/images/status/arrow-180-small.png
  162. BIN  public/images/status/arrow-180.png
  163. BIN  public/images/status/arrow-225-medium.png
  164. BIN  public/images/status/arrow-225-small.png
  165. BIN  public/images/status/arrow-225.png
  166. BIN  public/images/status/arrow-270-medium.png
  167. BIN  public/images/status/arrow-270-small.png
  168. BIN  public/images/status/arrow-270.png
  169. BIN  public/images/status/arrow-315-medium.png
  170. BIN  public/images/status/arrow-315-small.png
  171. BIN  public/images/status/arrow-315.png
  172. BIN  public/images/status/arrow-branch-000-left.png
  173. BIN  public/images/status/arrow-branch-090-left.png
  174. BIN  public/images/status/arrow-branch-090.png
  175. BIN  public/images/status/arrow-branch-180-left.png
  176. BIN  public/images/status/arrow-branch-180.png
  177. BIN  public/images/status/arrow-branch-270-left.png
  178. BIN  public/images/status/arrow-branch-270.png
  179. BIN  public/images/status/arrow-branch.png
  180. BIN  public/images/status/arrow-circle-045-left.png
  181. BIN  public/images/status/arrow-circle-135-left.png
  182. BIN  public/images/status/arrow-circle-135.png
  183. BIN  public/images/status/arrow-circle-225-left.png
  184. BIN  public/images/status/arrow-circle-225.png
  185. BIN  public/images/status/arrow-circle-315-left.png
  186. BIN  public/images/status/arrow-circle-315.png
  187. BIN  public/images/status/arrow-circle-double-135.png
  188. BIN  public/images/status/arrow-circle-double.png
  189. BIN  public/images/status/arrow-circle.png
  190. BIN  public/images/status/arrow-continue-000-top.png
  191. BIN  public/images/status/arrow-continue-090-left.png
  192. BIN  public/images/status/arrow-continue-090.png
  193. BIN  public/images/status/arrow-continue-180-top.png
  194. BIN  public/images/status/arrow-continue-180.png
  195. BIN  public/images/status/arrow-continue-270-left.png
  196. BIN  public/images/status/arrow-continue-270.png
  197. BIN  public/images/status/arrow-continue.png
  198. BIN  public/images/status/arrow-curve-000-double.png
  199. BIN  public/images/status/arrow-curve-000-left.png
  200. BIN  public/images/status/arrow-curve-090-left.png
  201. BIN  public/images/status/arrow-curve-090.png
  202. BIN  public/images/status/arrow-curve-180-double.png
  203. BIN  public/images/status/arrow-curve-180-left.png
  204. BIN  public/images/status/arrow-curve-180.png
  205. BIN  public/images/status/arrow-curve-270-left.png
  206. BIN  public/images/status/arrow-curve-270.png
  207. BIN  public/images/status/arrow-curve.png
  208. BIN  public/images/status/arrow-in.png
  209. BIN  public/images/status/arrow-join-090.png
  210. BIN  public/images/status/arrow-join-180.png
  211. BIN  public/images/status/arrow-join-270.png
  212. BIN  public/images/status/arrow-join.png
  213. BIN  public/images/status/arrow-merge-000-left.png
  214. BIN  public/images/status/arrow-merge-090-left.png
  215. BIN  public/images/status/arrow-merge-090.png
  216. BIN  public/images/status/arrow-merge-180-left.png
  217. BIN  public/images/status/arrow-merge-180.png
  218. BIN  public/images/status/arrow-merge-270-left.png
  219. BIN  public/images/status/arrow-merge-270.png
  220. BIN  public/images/status/arrow-merge.png
  221. BIN  public/images/status/arrow-move.png
  222. BIN  public/images/status/arrow-out.png
  223. BIN  public/images/status/arrow-repeat-once.png
  224. BIN  public/images/status/arrow-repeat.png
  225. BIN  public/images/status/arrow-resize-045.png
  226. BIN  public/images/status/arrow-resize-090.png
  227. BIN  public/images/status/arrow-resize-135.png
  228. BIN  public/images/status/arrow-resize.png
  229. BIN  public/images/status/arrow-return-000-left.png
  230. BIN  public/images/status/arrow-return-090-left.png
  231. BIN  public/images/status/arrow-return-090.png
  232. BIN  public/images/status/arrow-return-180-left.png
  233. BIN  public/images/status/arrow-return-180.png
  234. BIN  public/images/status/arrow-return-270-left.png
  235. BIN  public/images/status/arrow-return-270.png
  236. BIN  public/images/status/arrow-return.png
  237. BIN  public/images/status/arrow-retweet.png
  238. BIN  public/images/status/arrow-skip-090.png
  239. BIN  public/images/status/arrow-skip-180.png
  240. BIN  public/images/status/arrow-skip-270.png
  241. BIN  public/images/status/arrow-skip.png
  242. BIN  public/images/status/arrow-split-090.png
  243. BIN  public/images/status/arrow-split-180.png
  244. BIN  public/images/status/arrow-split-270.png
  245. BIN  public/images/status/arrow-split.png
  246. BIN  public/images/status/arrow-step-out.png
  247. BIN  public/images/status/arrow-step-over.png
  248. BIN  public/images/status/arrow-step.png
  249. BIN  public/images/status/arrow-stop-090.png
  250. BIN  public/images/status/arrow-stop-180.png
  251. BIN  public/images/status/arrow-stop-270.png
  252. BIN  public/images/status/arrow-stop.png
  253. BIN  public/images/status/arrow-switch-090.png
  254. BIN  public/images/status/arrow-switch-180.png
  255. BIN  public/images/status/arrow-switch-270.png
  256. BIN  public/images/status/arrow-switch.png
  257. BIN  public/images/status/arrow-transition-090.png
  258. BIN  public/images/status/arrow-transition-180.png
  259. BIN  public/images/status/arrow-transition-270.png
  260. BIN  public/images/status/arrow-transition.png
  261. BIN  public/images/status/arrow-turn-000-left.png
  262. BIN  public/images/status/arrow-turn-090-left.png
  263. BIN  public/images/status/arrow-turn-090.png
  264. BIN  public/images/status/arrow-turn-180-left.png
  265. BIN  public/images/status/arrow-turn-180.png
  266. BIN  public/images/status/arrow-turn-270-left.png
  267. BIN  public/images/status/arrow-turn-270.png
  268. BIN  public/images/status/arrow-turn.png
  269. BIN  public/images/status/arrow.png
  270. BIN  public/images/status/asterisk.png
  271. BIN  public/images/status/auction-hammer--arrow.png
  272. BIN  public/images/status/auction-hammer--exclamation.png
  273. BIN  public/images/status/auction-hammer--minus.png
  274. BIN  public/images/status/auction-hammer--pencil.png
  275. BIN  public/images/status/auction-hammer--plus.png
  276. BIN  public/images/status/auction-hammer-gavel.png
  277. BIN  public/images/status/auction-hammer.png
  278. BIN  public/images/status/balance--arrow.png
  279. BIN  public/images/status/balance--exclamation.png
  280. BIN  public/images/status/balance--minus.png
  281. BIN  public/images/status/balance--pencil.png
  282. BIN  public/images/status/balance--plus.png
  283. BIN  public/images/status/balance-unbalance.png
  284. BIN  public/images/status/balance.png
  285. BIN  public/images/status/balloon--arrow.png
  286. BIN  public/images/status/balloon--exclamation.png
  287. BIN  public/images/status/balloon--minus.png
  288. BIN  public/images/status/balloon--pencil.png
  289. BIN  public/images/status/balloon--plus.png
  290. BIN  public/images/status/balloon-ellipsis.png
  291. BIN  public/images/status/balloon-facebook-left.png
  292. BIN  public/images/status/balloon-facebook.png
  293. BIN  public/images/status/balloon-left.png
  294. BIN  public/images/status/balloon-quotation.png
  295. BIN  public/images/status/balloon-small-left.png
  296. BIN  public/images/status/balloon-small.png
  297. BIN  public/images/status/balloon-smiley.png
  298. BIN  public/images/status/balloon-sound.png
  299. BIN  public/images/status/balloon-twitter-left.png
  300. BIN  public/images/status/balloon-twitter-retweet.png
Sorry, we could not display the entire diff because too many files (2,743) changed.
4 .gitignore
@@ -0,0 +1,4 @@
+.bundle
+db/*.sqlite3
+log/*.log
+tmp/
1  .rvmrc
@@ -0,0 +1 @@
+rvm use ree@stashboard
15 Gemfile
@@ -0,0 +1,15 @@
+source :rubygems
+
+gem 'rails', '3.0.6'
+gem 'sqlite3'
+# gem 'omniauth'
+
+group :development, :test do
+ gem 'ruby-debug'
+ gem 'timecop'
+ gem 'factory_girl_rails'
+ gem 'cucumber-rails'
+ gem 'capybara'
+ gem 'database_cleaner'
+ gem 'cucumber-api-steps', '0.4', :require => false
+end
138 Gemfile.lock
@@ -0,0 +1,138 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ abstract (1.0.0)
+ actionmailer (3.0.6)
+ actionpack (= 3.0.6)
+ mail (~> 2.2.15)
+ actionpack (3.0.6)
+ activemodel (= 3.0.6)
+ activesupport (= 3.0.6)
+ builder (~> 2.1.2)
+ erubis (~> 2.6.6)
+ i18n (~> 0.5.0)
+ rack (~> 1.2.1)
+ rack-mount (~> 0.6.14)
+ rack-test (~> 0.5.7)
+ tzinfo (~> 0.3.23)
+ activemodel (3.0.6)
+ activesupport (= 3.0.6)
+ builder (~> 2.1.2)
+ i18n (~> 0.5.0)
+ activerecord (3.0.6)
+ activemodel (= 3.0.6)
+ activesupport (= 3.0.6)
+ arel (~> 2.0.2)
+ tzinfo (~> 0.3.23)
+ activeresource (3.0.6)
+ activemodel (= 3.0.6)
+ activesupport (= 3.0.6)
+ activesupport (3.0.6)
+ arel (2.0.9)
+ builder (2.1.2)
+ capybara (0.4.1.2)
+ celerity (>= 0.7.9)
+ culerity (>= 0.2.4)
+ mime-types (>= 1.16)
+ nokogiri (>= 1.3.3)
+ rack (>= 1.0.0)
+ rack-test (>= 0.5.4)
+ selenium-webdriver (>= 0.0.27)
+ xpath (~> 0.1.3)
+ celerity (0.8.9)
+ childprocess (0.1.8)
+ ffi (~> 1.0.6)
+ columnize (0.3.2)
+ cucumber (0.10.2)
+ builder (>= 2.1.2)
+ diff-lcs (>= 1.1.2)
+ gherkin (>= 2.3.5)
+ json (>= 1.4.6)
+ term-ansicolor (>= 1.0.5)
+ cucumber-api-steps (0.4)
+ cucumber (>= 0.8.3)
+ jsonpath (>= 0.1.2)
+ cucumber-rails (0.4.1)
+ cucumber (>= 0.10.1)
+ nokogiri (>= 1.4.4)
+ rack-test (>= 0.5.7)
+ culerity (0.2.15)
+ database_cleaner (0.6.6)
+ diff-lcs (1.1.2)
+ erubis (2.6.6)
+ abstract (>= 1.0.0)
+ factory_girl (1.3.3)
+ factory_girl_rails (1.0.1)
+ factory_girl (~> 1.3)
+ railties (>= 3.0.0)
+ ffi (1.0.7)
+ rake (>= 0.8.7)
+ gherkin (2.3.5)
+ json (>= 1.4.6)
+ i18n (0.5.0)
+ json (1.5.1)
+ json_pure (1.5.1)
+ jsonpath (0.2.1)
+ json
+ linecache (0.43)
+ mail (2.2.15)
+ activesupport (>= 2.3.6)
+ i18n (>= 0.4.0)
+ mime-types (~> 1.16)
+ treetop (~> 1.4.8)
+ mime-types (1.16)
+ nokogiri (1.4.4)
+ polyglot (0.3.1)
+ rack (1.2.2)
+ rack-mount (0.6.14)
+ rack (>= 1.0.0)
+ rack-test (0.5.7)
+ rack (>= 1.0)
+ rails (3.0.6)
+ actionmailer (= 3.0.6)
+ actionpack (= 3.0.6)
+ activerecord (= 3.0.6)
+ activeresource (= 3.0.6)
+ activesupport (= 3.0.6)
+ bundler (~> 1.0)
+ railties (= 3.0.6)
+ railties (3.0.6)
+ actionpack (= 3.0.6)
+ activesupport (= 3.0.6)
+ rake (>= 0.8.7)
+ thor (~> 0.14.4)
+ rake (0.8.7)
+ ruby-debug (0.10.4)
+ columnize (>= 0.1)
+ ruby-debug-base (~> 0.10.4.0)
+ ruby-debug-base (0.10.4)
+ linecache (>= 0.3)
+ rubyzip (0.9.4)
+ selenium-webdriver (0.1.4)
+ childprocess (>= 0.1.7)
+ ffi (>= 1.0.7)
+ json_pure
+ rubyzip
+ sqlite3 (1.3.3)
+ term-ansicolor (1.0.5)
+ thor (0.14.6)
+ timecop (0.3.5)
+ treetop (1.4.9)
+ polyglot (>= 0.3.1)
+ tzinfo (0.3.26)
+ xpath (0.1.3)
+ nokogiri (~> 1.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ capybara
+ cucumber-api-steps (= 0.4)
+ cucumber-rails
+ database_cleaner
+ factory_girl_rails
+ rails (= 3.0.6)
+ ruby-debug
+ sqlite3
+ timecop
256 README
@@ -0,0 +1,256 @@
+== Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the Model-View-Control pattern.
+
+This pattern splits the view (also called the presentation) into "dumb"
+templates that are primarily responsible for inserting pre-built data in between
+HTML tags. The model contains the "smart" domain objects (such as Account,
+Product, Person, Post) that holds all the business logic and knows how to
+persist themselves to a database. The controller handles the incoming requests
+(such as Save New Account, Update Product, Show Post) by manipulating the model
+and directing data to the view.
+
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in
+link:files/vendor/rails/activerecord/README.html.
+
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails. You can read more about Action Pack in
+link:files/vendor/rails/actionpack/README.html.
+
+
+== Getting Started
+
+1. At the command prompt, create a new Rails application:
+ <tt>rails new myapp</tt> (where <tt>myapp</tt> is the application name)
+
+2. Change directory to <tt>myapp</tt> and start the web server:
+ <tt>cd myapp; rails server</tt> (run with --help for options)
+
+3. Go to http://localhost:3000/ and you'll see:
+ "Welcome aboard: You're riding Ruby on Rails!"
+
+4. Follow the guidelines to start developing your application. You can find
+the following resources handy:
+
+* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
+* Ruby on Rails Tutorial Book: http://www.railstutorial.org/
+
+
+== Debugging Rails
+
+Sometimes your application goes wrong. Fortunately there are a lot of tools that
+will help you debug it and get it back on the rails.
+
+First area to check is the application log files. Have "tail -f" commands
+running on the server.log and development.log. Rails will automatically display
+debugging and runtime information to these files. Debugging info will also be
+shown in the browser on requests from 127.0.0.1.
+
+You can also log your own messages directly into the log file from your code
+using the Ruby logger class from inside your controllers. Example:
+
+ class WeblogController < ActionController::Base
+ def destroy
+ @weblog = Weblog.find(params[:id])
+ @weblog.destroy
+ logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
+ end
+ end
+
+The result will be a message in your log file along the lines of:
+
+ Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
+
+More information on how to use the logger is at http://www.ruby-doc.org/core/
+
+Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
+several books available online as well:
+
+* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
+* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
+
+These two books will bring you up to speed on the Ruby language and also on
+programming in general.
+
+
+== Debugger
+
+Debugger support is available through the debugger command when you start your
+Mongrel or WEBrick server with --debugger. This means that you can break out of
+execution at any point in the code, investigate and change the model, and then,
+resume execution! You need to install ruby-debug to run the server in debugging
+mode. With gems, use <tt>sudo gem install ruby-debug</tt>. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find(:all)
+ debugger
+ end
+ end
+
+So the controller will accept the action, run the first line, then present you
+with a IRB prompt in the server window. Here you can do things like:
+
+ >> @posts.inspect
+ => "[#<Post:0x14a6be8
+ @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>,
+ #<Post:0x14a6620
+ @attributes={"title"=>"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
+ >> @posts.first.title = "hello from a debugger"
+ => "hello from a debugger"
+
+...and even better, you can examine how your runtime objects actually work:
+
+ >> f = @posts.first
+ => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
+ >> f.
+ Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you can enter "cont".
+
+
+== Console
+
+The console is a Ruby shell, which allows you to interact with your
+application's domain model. Here you'll have all parts of the application
+configured, just like it is when the application is running. You can inspect
+domain models, change values, and save to the database. Starting the script
+without arguments will launch it in the development environment.
+
+To start the console, run <tt>rails console</tt> from the application
+directory.
+
+Options:
+
+* Passing the <tt>-s, --sandbox</tt> argument will rollback any modifications
+ made to the database.
+* Passing an environment name as an argument will load the corresponding
+ environment. Example: <tt>rails console production</tt>.
+
+To reload your controllers and models after launching the console run
+<tt>reload!</tt>
+
+More information about irb can be found at:
+link:http://www.rubycentral.com/pickaxe/irb.html
+
+
+== dbconsole
+
+You can go to the command line of your database directly through <tt>rails
+dbconsole</tt>. You would be connected to the database with the credentials
+defined in database.yml. Starting the script without arguments will connect you
+to the development database. Passing an argument will connect you to a different
+database, like <tt>rails dbconsole production</tt>. Currently works for MySQL,
+PostgreSQL and SQLite 3.
+
+== Description of Contents
+
+The default directory structure of a generated Ruby on Rails application:
+
+ |-- app
+ | |-- controllers
+ | |-- helpers
+ | |-- mailers
+ | |-- models
+ | `-- views
+ | `-- layouts
+ |-- config
+ | |-- environments
+ | |-- initializers
+ | `-- locales
+ |-- db
+ |-- doc
+ |-- lib
+ | `-- tasks
+ |-- log
+ |-- public
+ | |-- images
+ | |-- javascripts
+ | `-- stylesheets
+ |-- script
+ |-- test
+ | |-- fixtures
+ | |-- functional
+ | |-- integration
+ | |-- performance
+ | `-- unit
+ |-- tmp
+ | |-- cache
+ | |-- pids
+ | |-- sessions
+ | `-- sockets
+ `-- vendor
+ `-- plugins
+
+app
+ Holds all the code that's specific to this particular application.
+
+app/controllers
+ Holds controllers that should be named like weblogs_controller.rb for
+ automated URL mapping. All controllers should descend from
+ ApplicationController which itself descends from ActionController::Base.
+
+app/models
+ Holds models that should be named like post.rb. Models descend from
+ ActiveRecord::Base by default.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblogs/index.html.erb for the WeblogsController#index action. All views use
+ eRuby syntax by default.
+
+app/views/layouts
+ Holds the template files for layouts to be used with views. This models the
+ common header/footer method of wrapping views. In your views, define a layout
+ using the <tt>layout :default</tt> and create a file named default.html.erb.
+ Inside default.html.erb, call <% yield %> to render the view using this
+ layout.
+
+app/helpers
+ Holds view helpers that should be named like weblogs_helper.rb. These are
+ generated for you automatically when using generators for controllers.
+ Helpers can be used to wrap functionality for your views into methods.
+
+config
+ Configuration files for the Rails environment, the routing map, the database,
+ and other dependencies.
+
+db
+ Contains the database schema in schema.rb. db/migrate contains all the
+ sequence of Migrations for your schema.
+
+doc
+ This directory is where your application documentation will be stored when
+ generated using <tt>rake doc:app</tt>
+
+lib
+ Application specific libraries. Basically, any kind of custom code that
+ doesn't belong under controllers, models, or helpers. This directory is in
+ the load path.
+
+public
+ The directory available for the web server. Contains subdirectories for
+ images, stylesheets, and javascripts. Also contains the dispatchers and the
+ default HTML files. This should be set as the DOCUMENT_ROOT of your web
+ server.
+
+script
+ Helper scripts for automation and generation.
+
+test
+ Unit and functional tests along with fixtures. When using the rails generate
+ command, template test files will be generated for you and placed in this
+ directory.
+
+vendor
+ External libraries that the application depends on. Also includes the plugins
+ subdirectory. If the app has frozen rails, those gems also go here, under
+ vendor/rails/. This directory is in the load path.
7 Rakefile
@@ -0,0 +1,7 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require File.expand_path('../config/application', __FILE__)
+require 'rake'
+
+StashboardRails::Application.load_tasks
3  app/controllers/application_controller.rb
@@ -0,0 +1,3 @@
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+end
88 app/controllers/events_controller.rb
@@ -0,0 +1,88 @@
+class EventsController < ApplicationController
+
+ before_filter :find_service
+
+ # GET /api/v1/services/1/events.json
+ def index
+ @events = @service.events
+
+ respond_to do |format|
+ format.json { render :json => {:events => @events } }
+ end
+ end
+
+ # GET /api/v1/services/1/current.json
+ def current
+ @event = @service.current_event
+
+ respond_to do |format|
+ format.json { render :json => @event }
+ end
+ end
+
+ # GET /api/v1/services/1/events/2.json
+ def show
+ @event = @service.events.find(params[:id])
+
+ respond_to do |format|
+ format.json { render :json => @event }
+ end
+ end
+
+ # GET /api/v1/services/1/events/new.json
+ def new
+ @event = Event.new
+
+ respond_to do |format|
+ format.json { render :json => @event }
+ end
+ end
+
+ # GET /events/1/edit
+ def edit
+ @event = Event.find(params[:id])
+ end
+
+ # POST /api/v1/services/1/events.json
+ def create
+ @event = @service.events.build(params[:event])
+
+ respond_to do |format|
+ if @event.save!
+ format.json { render :json => @event, :status => :created }
+ else
+ format.json { render :json => @event.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /api/v1/services/1/events/2.json
+ def update
+ @event = @service.events.find(params[:id])
+
+ respond_to do |format|
+ if @event.update_attributes(params[:event])
+ format.json { head :ok }
+ else
+ format.json { render :json => @event.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /api/v1/services/1/events/1.json
+ def destroy
+ @event = @service.events.find(params[:id])
+ @event.destroy
+
+ respond_to do |format|
+ format.json { head :ok }
+ end
+ end
+
+ protected
+
+ def find_service
+ @service = Service.find_by_slug!(params[:service_id])
+ end
+
+end
12 app/controllers/levels_controller.rb
@@ -0,0 +1,12 @@
+class LevelsController < ApplicationController
+
+ # GET /api/v1/levels.json
+ def index
+ @levels = Level.all
+
+ respond_to do |format|
+ format.json { render :json => {:levels => @levels.keys } }
+ end
+ end
+
+end
73 app/controllers/services_controller.rb
@@ -0,0 +1,73 @@
+class ServicesController < ApplicationController
+
+ # GET /api/v1/services.json
+ def index
+ @services = Service.all(:order => :name)
+
+ respond_to do |format|
+ format.html
+ format.json { render :json => {:services => @services } }
+ end
+ end
+
+ # GET /api/v1/services/1.json
+ def show
+ @service = Service.find_by_slug!(params[:id])
+
+ respond_to do |format|
+ format.html
+ format.json { render :json => @service }
+ end
+ end
+
+ # GET /api/v1/services/new.json
+ def new
+ @service = Service.new
+
+ respond_to do |format|
+ format.json { render :json => @service }
+ end
+ end
+
+ # POST /api/v1/services.json
+ def create
+ puts params
+ @service = Service.new(params[:service])
+
+ respond_to do |format|
+ if @service.save
+ format.html { redirect_to(@service, :notice => 'Service was successfully created.') }
+ format.json { render :json => @service, :status => :created }
+ else
+ format.html { render :action => "new" }
+ format.json { render :json => @service.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /api/v1/services/1.json
+ def update
+ @service = Service.find_by_slug!(params[:id])
+
+ respond_to do |format|
+ if @service.update_attributes(params[:service])
+ format.html { redirect_to(@service, :notice => 'Service was successfully updated.') }
+ format.json { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.json { render :json => @service.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /api/v1/services/1.json
+ def destroy
+ @service = Service.find_by_slug!(params[:id])
+ @service.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(services_url) }
+ format.json { head :ok }
+ end
+ end
+end
10 app/controllers/status_images_controller.rb
@@ -0,0 +1,10 @@
+class StatusImagesController < ApplicationController
+
+ # GET /api/v1/status-images.json
+ def index
+ images = Dir[Rails.root.join('public','images','status','*.png')].map { |i| {:url => "/images/status/#{File.basename(i)}", :name => File.basename(i, '.png') } }
+ respond_to do |format|
+ format.json { render :json => {:images => images } }
+ end
+ end
+end
77 app/controllers/statuses_controller.rb
@@ -0,0 +1,77 @@
+class StatusesController < ApplicationController
+ # GET /api/v1/statuses.json
+ def index
+ @statuses = Status.all
+
+ respond_to do |format|
+ format.html # index.html.erb
+ format.json { render :json => {:statuses => @statuses } }
+ end
+ end
+
+ # GET /api/v1/statuses/1.json
+ def show
+ @status = Status.find_by_slug!(params[:id])
+
+ respond_to do |format|
+ format.html # show.html.erb
+ format.json { render :json => @status }
+ end
+ end
+
+ # GET /api/v1/statuses/new.json
+ def new
+ @status = Status.new
+
+ respond_to do |format|
+ format.html # new.html.erb
+ format.json { render :json => @status }
+ end
+ end
+
+ # GET /api/v1/statuses/1/edit.json
+ def edit
+ @status = Status.find_by_slug!(params[:id])
+ end
+
+ # POST /api/v1/statuses.json
+ def create
+ @status = Status.new(params[:status])
+
+ respond_to do |format|
+ if @status.save
+ format.html { redirect_to(@status, :notice => 'Status was successfully created.') }
+ format.json { render :json => @status, :status => :created }
+ else
+ format.html { render :action => "new" }
+ format.json { render :json => @status.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /api/v1/statuses/1.json
+ def update
+ @status = Status.find_by_slug!(params[:id])
+
+ respond_to do |format|
+ if @status.update_attributes(params[:status])
+ format.html { redirect_to(@status, :notice => 'Status was successfully updated.') }
+ format.json { head :ok }
+ else
+ format.html { render :action => "edit" }
+ format.json { render :json => @status.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /api/v1/statuses/1.json
+ def destroy
+ @status = Status.find_by_slug!(params[:id])
+ @status.destroy
+
+ respond_to do |format|
+ format.html { redirect_to(statuses_url) }
+ format.json { head :ok }
+ end
+ end
+end
53 app/helpers/application_helper.rb
@@ -0,0 +1,53 @@
+module ApplicationHelper
+
+ def user_is_admin?
+ false
+ end
+
+ def login_link
+ "GREETING"
+ # if user:
+ # greeting = users.create_logout_url("/")
+ # else:
+ # greeting = users.create_login_url("/")
+ end
+
+ def title
+ "Rails Stashboard"
+ end
+
+ def user
+ nil
+ end
+
+ def start_date
+ nil
+ end
+
+ def common_statuses
+ [
+ [
+ "tick-circle",
+ "cross-circle",
+ "exclamation",
+ "wrench",
+ "flag",
+ ],
+ [
+ "clock",
+ "heart",
+ "hard-hat",
+ "information",
+ "lock",
+ ],
+ [
+ "plug",
+ "question",
+ "traffic-cone",
+ "bug",
+ "broom",
+ ],
+ ]
+ end
+
+end
2  app/helpers/statuses_helper.rb
@@ -0,0 +1,2 @@
+module StatusesHelper
+end
41 app/models/event.rb
@@ -0,0 +1,41 @@
+class Event < ActiveRecord::Base
+
+ validates :started_at, :presence => true
+ validates :message, :presence => true
+ validates :status, :presence => true
+
+ belongs_to :status
+ belongs_to :service
+
+ before_validation_on_create do
+ self.started_at ||= Time.now
+ self.status ||= Status.default
+ end
+
+ def status=(value)
+ if value.is_a?(String)
+ self.status_id = Status.find_by_slug!(value).id
+ elsif value.is_a?(Status)
+ self.status_id = value.id
+ else
+ self.status_id = nil
+ end
+ end
+
+ def resource_url
+ self.service.resource_url.gsub('.json','') + "/events/" + self.to_param + '.json'
+ end
+
+ def as_json(options = {})
+ base_url = '/api/v1'
+ m = {}
+ m["sid"] = self.to_param
+ m["timestamp"] = self.started_at.to_s(:rfc822)
+ m["status"] = self.status.as_json
+ m["message"] = self.message
+ m["url"] = base_url + self.resource_url
+ m['informational'] = self.is_informational?
+ m
+ end
+
+end
27 app/models/level.rb
@@ -0,0 +1,27 @@
+class Level
+
+ NORMAL = "NORMAL"
+ WARNING = "WARNING"
+ ERROR = "ERROR"
+ CRITICAL = "CRITICAL"
+
+ LEVELS = ActiveSupport::OrderedHash.new
+
+ LEVELS[NORMAL] = 10
+ LEVELS[WARNING] = 30
+ LEVELS[ERROR] = 40
+ LEVELS[CRITICAL] = 50
+
+ def self.get_severity(level)
+ LEVELS[level]
+ end
+
+ def self.get_level(severity)
+ LEVELS.invert[severity]
+ end
+
+ def self.all
+ Level::LEVELS
+ end
+
+end
89 app/models/service.rb
@@ -0,0 +1,89 @@
+class Service < ActiveRecord::Base
+ # A service to track
+
+ validates :name, :presence => true, :uniqueness => true
+ validates :slug, :presence => true, :uniqueness => true
+ validates :description, :presence => true
+
+ has_many :events, :dependent => :destroy, :order => 'created_at DESC'
+
+ before_validation_on_create do
+ self.slug ||= self.name.parameterize
+ end
+
+ def current_event
+ self.events.first(:order => 'started_at DESC')
+ end
+
+ # Specialty function for front page
+ def last_five_days
+ lowest = Status.default
+ severity = lowest.severity
+
+ yesterday = date.today() - timedelta(days=1)
+ ago = yesterday - timedelta(days=5)
+
+ events = self.events.where(:start => (1.day.ago)..(5.days.ago)).limit(100)
+ stats = {}
+
+ 1..5.each do |i|
+ stats[yesterday.day] = {
+ "image" => lowest.image,
+ "day" => yesterday,
+ }
+ yesterday = yesterday - timedelta(days=1)
+ end
+
+ for event in events
+ if event.status.severity > severity
+ stats[event.start.day]["image"] = "information"
+ stats[event.start.day]["information"] = true
+ end
+ end
+
+ results = []
+
+ keys = stats.keys()
+ keys.sort()
+ keys.reverse()
+
+ keys.each do |k|
+ results.append(stats[k])
+ end
+
+ return results
+ end
+
+
+ def events_for_day(day)
+ return self.events.where(:started_at => day.beginning_of_day..day.end_of_day).limit(40)
+ end
+
+ def compare(other_status)
+ 0
+ end
+
+ def resource_url
+ "/services/" + self.slug + '.json'
+ end
+
+ def as_json(options = {})
+ base_url = '/api/v1'
+
+ m = {}
+ m["name"] = self.name
+ m["id"] = self.slug
+ m["description"] = self.description
+ m["url"] = base_url + self.resource_url()
+
+ event = self.current_event
+ if event
+ m["current-event"] = event.as_json
+ else
+ m["current-event"] = nil
+ end
+
+ return m
+ end
+
+end
50 app/models/status.rb
@@ -0,0 +1,50 @@
+class Status < ActiveRecord::Base
+
+ has_many :events, :dependent => :destroy
+
+ validates :name, :presence => true, :uniqueness => true
+ validates :slug, :presence => true, :uniqueness => true
+ validates :description, :presence => true
+ validates :image, :presence => true
+ validates :severity, :presence => true # Should probably be a relationship to level
+
+ before_validation_on_create do
+ self.slug ||= (self.name || '').parameterize
+ end
+
+ def level
+ Level.get_level(self.severity)
+ end
+
+ def level=(level)
+ self.severity = Level.get_severity(level)
+ end
+
+ def self.default
+ # Return the first status with a NORMAL level.
+ normal = Level.get_severity(Level::NORMAL)
+ where(:severity => normal).first
+ end
+
+ def image_url
+ "/images/status/#{image}.png"
+ end
+
+ def resource_url
+ "/statuses/#{slug}.json"
+ end
+
+ def as_json(options = {})
+ base_url = '/api/v1'
+
+ m = {}
+ m["name"] = self.name
+ m["id"] = self.slug
+ m["description"] = self.description
+ m["level"] = self.level
+ m["url"] = base_url + self.resource_url
+ m["image"] = self.image_url # Not a direct port, but may work
+ m
+ end
+
+end
63 app/views/layouts/application.html.erb
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <title><%= title %></title>
+ <%= stylesheet_link_tag 'reset', 'style', 'jqueryui', 'prettify', :media => :screen %>
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
+ <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.0/jquery-ui.min.js"></script>
+ <%= javascript_include_tag 'common' %>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ $(".button").button();
+ $("#notice a").click(function(){
+ $("#notice").hide();
+ });
+ });
+ </script>
+ <%= yield :js %>
+ </head>
+ <body>
+ <div id="wrapper">
+ <div id="header">
+ <div id="notice"><span></span><a href="#">X</a></div>
+ <img id="logo" src="/images/logo.png" alt="Logo"/>
+ <a href="/"><h1 id="title"><%= title %></h1></a>
+ </div>
+ <div id="content">
+ <%= yield %>
+ </div>
+ <div id="push"></div>
+ </div>
+ <div id="footer">
+ <ul class="left">
+ <li>
+ <% if login_link %>
+ <a href="<%= login_link %>">
+ <% if user %>
+ Logout
+ <% else %>
+ Login
+ <% end %>
+ </a>
+ <% end %>
+ </li>
+ <li>-</li>
+ <li>
+ <a href="/documentation/overview">
+ API Documentation
+ </a>
+ </li>
+ <li>-</li>
+ <li>Copyright (c) <%= Date.today.year %> <a href="http://www.twilio.com">Twilio Inc.</a></li>
+ <li>-</li>
+ <li id="icons">Icons by <a href="http://p.yusukekamiyamane.com/">Yusuke Kamiyamane</a></li>
+ </ul>
+ <ul class="right">
+ <li id="powered"><a href="http://www.stashboard.org">POWERED BY <img src="/images/poweredbystash.png" alt="stash!" /> STASHBOARD</a></li>
+ </ul>
+ </div>
+ <%= yield :js_end %>
+ <%= javascript_include_tag 'prettify' %>
+ </body>
+</html>
62 app/views/services/_legend.html.erb
@@ -0,0 +1,62 @@
+<div class="legend" id="legend">
+ <h4> Legend </h4>
+ <table>
+ <% if user_is_admin? %>
+ <thead>
+ <tr>
+ <th></th>
+ <th>Description</th>
+ <th>Level</th>
+ <th>Edit</th>
+ <th>Delete</th>
+ </tr>
+ </thead>
+ <% end %>
+
+ <tbody id="legend-body">
+ </tbody>
+ </table>
+
+ <% if user_is_admin? %>
+ <a id="add-status" class="button" href="#">Add New Status</a>
+
+ <div id="add-status-modal" class="dialog" title="Add New Status">
+ <label class="side-label" for="status-name">Name</label>
+ <input type="text" id="status-name"name="status-name">
+
+ <label class="side-label" for="status-level">Level</label>
+ <select id="statusLevel" class="status-level" name="level">
+ </select>
+
+ <label for="status-image">Image</label>
+ <% for image_row in common_statuses %>
+ <div>
+ <% for image in image_row %>
+ <input type="radio" name="status-image" value="<%= image %>">
+ <img src="/images/status/<%= image %>.png" />
+ <% end %>
+ </div>
+
+ <% end %>
+
+ <label for="status-description">Description</label>
+ <textarea
+ id="status-description" name="status-description"></textarea>
+ </div>
+
+ <div class="dialog" id="delete-status-modal" title="Delete Status">
+ <p>Are you sure you want to delete this status? ALL EVENTS with this status will also be deleted</p>
+ </div>
+
+ <% end %>
+
+ <script type="text/javascript">
+ $(document).ready(function(){
+ <% if user_is_admin? %>
+ stashboard.fillLegend(true);
+ <% else %>
+ stashboard.fillLegend(false);
+ <% end %>
+ });
+ </script>
+</div>
40 app/views/services/index.html.erb
@@ -0,0 +1,40 @@
+<% content_for :js_end do %>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ stashboard.fillIndex();
+ });
+ </script>
+<% end %>
+
+<div id="webservices" class="frame">
+
+ <table id="service-list">
+ <thead>
+ <tr>
+ <th>Service</th>
+ <th class="today">Current</th>
+ </thead>
+
+ <tbody id="services-body">
+ </tbody>
+ </table>
+
+ <% if user_is_admin? %>
+ <a id="add-service" class="button" href="#">Add New Service</a>
+ <% end %>
+
+ <%= render :partial => 'legend' %>
+
+</div>
+
+<% if user_is_admin? %>
+
+ <div class="dialog" id="add-service-modal" title="Add New Service">
+ <label for="service_name">Name</label>
+ <input type="text" id="service_name" name="service[name]">
+ <label for="service_description">Description</label>
+ <textarea id="service_description" name="service[description]"></textarea>
+ </div>
+
+<% end %>
+
91 app/views/services/show.html.erb
@@ -0,0 +1,91 @@
+<% content_for :js_end do %>
+ <script type="text/javascript">
+ $(document).ready(function(){
+ var startDate = false;
+ var endDate = false;
+ var isAdmin = false;
+ var service = "<%= @service.slug %>";
+
+ <% if start_date %>
+ var startDate = new Date("{{ start_date_stamp }}");
+ var endDate = new Date("{{ end_date_stamp }}");
+ <% end %>
+
+ <% if user_is_admin? %>
+ var isAdmin = true;
+ <% end %>
+
+ stashboard.fillService(service, isAdmin, startDate, endDate);
+ });
+ </script>
+<% end %>
+
+<div id="webservices" class="frame">
+
+ <% if user_is_admin? %>
+ <a class="button" id="update-status">Update Status</a>
+ <a class="button" id="add-note">Add Note</a>
+ <% end %>
+
+ <h2><span></span></h2>
+
+ <p id="serviceDescription"></p>
+ <% if start_date %>
+ <h3 class="date-range">
+ <%= start_date.strftime('%n/%j/%Y') %>
+ </h3>
+ <% end %>
+
+ <table class="event-log" cellpadding="10">
+ <thead>
+ <tr>
+ <th class="time-header">Time</th>
+ <th class="status-header">Status</th>
+ <th>Message</th>
+ <% if user_is_admin? %>
+ <th class="delete-header">Delete</th>
+ <% end %>
+ </tr>
+ </thead>
+ <tbody id="events-tbody">
+ </tbody>
+ </table>
+
+ <% if user_is_admin? %>
+ <a class="button" id="delete-service">
+ Delete Service
+ </a>
+ <a class="button" id="edit-service">
+ Edit Service
+ </a>
+ <% end %>
+
+</div>
+
+<% if user_is_admin? %>
+
+ <div class="dialog" id="delete-service-modal" title="Delete Service">
+ <p>Are you sure you want to delete this service?</p>
+ </div>
+
+ <div class="dialog" id="add-note-modal" title="Add Note">
+ <label id="messageLabel" for="message">Message:</label>
+ <textarea id="noteMessage" class="update-message" name="message"></textarea>
+ </div>
+
+ <div class="dialog" id="add-event-modal" title="Update Status">
+ <label id="statusLabel" for="status">Status:</label>
+ <select id="statusValue" class="update-status" name="severity"></select>
+ <label id="messageLabel" for="message">Message:</label>
+ <textarea id="eventMessage" class="update-message" name="message"></textarea>
+ </div>
+
+ <div class="dialog" id="edit-service-modal" title="Edit Service">
+ <label for="service-name">Name</label>
+ <input type="text" id="service-name" name="service-name">
+ <label for="service-description">Description</label>
+ <textarea
+ id="service-description" name="service-description"></textarea>
+ </div>
+
+<% end %>
4 config.ru
@@ -0,0 +1,4 @@
+# This file is used by Rack-based servers to start the application.
+
+require ::File.expand_path('../config/environment', __FILE__)
+run StashboardRails::Application
43 config/application.rb
@@ -0,0 +1,43 @@
+require File.expand_path('../boot', __FILE__)
+
+require 'rails/all'
+
+# If you have a Gemfile, require the gems listed there, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(:default, Rails.env) if defined?(Bundler)
+
+module StashboardRails
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+
+ # Custom directories with classes and modules you want to be autoloadable.
+ # config.autoload_paths += %W(#{config.root}/extras)
+
+ # Only load the plugins named here, in the order given (default is alphabetical).
+ # :all can be used as a placeholder for all plugins not explicitly named.
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+
+ # Activate observers that should always be running.
+ # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
+
+ # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
+ # config.time_zone = 'Central Time (US & Canada)'
+
+ # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
+ # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
+ # config.i18n.default_locale = :de
+
+ # JavaScript files you want as :defaults (application.js is always included).
+ # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
+
+ # Configure the default encoding used in templates for Ruby 1.9.
+ config.encoding = "utf-8"
+
+ # Configure sensitive parameters which will be filtered from the log file.
+ config.filter_parameters += [:password]
+
+ end
+end
6 config/boot.rb
@@ -0,0 +1,6 @@
+require 'rubygems'
+
+# Set up gems listed in the Gemfile.
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+
+require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
8 config/cucumber.yml
@@ -0,0 +1,8 @@
+<%
+rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : ""
+rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}"
+std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip"
+%>
+default: <%= std_opts %> features
+wip: --tags @wip:3 --wip features
+rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip
25 config/database.yml
@@ -0,0 +1,25 @@
+# SQLite version 3.x
+# gem install sqlite3
+development:
+ adapter: sqlite3
+ database: db/development.sqlite3
+ pool: 5
+ timeout: 5000
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test: &test
+ adapter: sqlite3
+ database: db/test.sqlite3
+ pool: 5
+ timeout: 5000
+
+production:
+ adapter: sqlite3
+ database: db/production.sqlite3
+ pool: 5
+ timeout: 5000
+
+cucumber:
+ <<: *test
5 config/environment.rb
@@ -0,0 +1,5 @@
+# Load the rails application
+require File.expand_path('../application', __FILE__)
+
+# Initialize the rails application
+StashboardRails::Application.initialize!
26 config/environments/development.rb
@@ -0,0 +1,26 @@
+StashboardRails::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the webserver when you make code changes.
+ config.cache_classes = false
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_view.debug_rjs = true
+ config.action_controller.perform_caching = false
+
+ # Don't care if the mailer can't send
+ config.action_mailer.raise_delivery_errors = false
+
+ # Print deprecation notices to the Rails logger
+ config.active_support.deprecation = :log
+
+ # Only use best-standards-support built into browsers
+ config.action_dispatch.best_standards_support = :builtin
+end
+
49 config/environments/production.rb
@@ -0,0 +1,49 @@
+StashboardRails::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # The production environment is meant for finished, "live" apps.
+ # Code is not reloaded between requests
+ config.cache_classes = true
+
+ # Full error reports are disabled and caching is turned on
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Specifies the header that your server uses for sending files
+ config.action_dispatch.x_sendfile_header = "X-Sendfile"
+
+ # For nginx:
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+
+ # If you have no front-end server that supports something like X-Sendfile,
+ # just comment this out and Rails will serve the files
+
+ # See everything in the log (default is :info)
+ # config.log_level = :debug
+
+ # Use a different logger for distributed setups
+ # config.logger = SyslogLogger.new
+
+ # Use a different cache store in production
+ # config.cache_store = :mem_cache_store
+
+ # Disable Rails's static asset server
+ # In production, Apache or nginx will already do this
+ config.serve_static_assets = false
+
+ # Enable serving of images, stylesheets, and javascripts from an asset server
+ # config.action_controller.asset_host = "http://assets.example.com"
+
+ # Disable delivery errors, bad email addresses will be ignored
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable threaded mode
+ # config.threadsafe!
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation can not be found)
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners
+ config.active_support.deprecation = :notify
+end
35 config/environments/test.rb
@@ -0,0 +1,35 @@
+StashboardRails::Application.configure do
+ # Settings specified here will take precedence over those in config/application.rb
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Log error messages when you accidentally call methods on nil.
+ config.whiny_nils = true
+
+ # Show full error reports and disable caching
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment
+ config.action_controller.allow_forgery_protection = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Print deprecation notices to the stderr
+ config.active_support.deprecation = :stderr
+end
7 config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
10 config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
1  config/initializers/json.rb
@@ -0,0 +1 @@
+ActiveRecord::Base.include_root_in_json = true
5 config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
7 config/initializers/secret_token.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+StashboardRails::Application.config.secret_token = '4f1b9e5001e6519dc7ee71264dbe19e00b42529f6788e99bab97b111c411093f0588a3d317e66d3cd7999d38410c86886f0ef0d38de8e2141a14a0cb900b93d9'
8 config/initializers/session_store.rb
@@ -0,0 +1,8 @@
+# Be sure to restart your server when you modify this file.
+
+StashboardRails::Application.config.session_store :cookie_store, :key => '_stashboard-rails_session'
+
+# Use the database for sessions instead of the cookie-based default,
+# which shouldn't be used to store highly confidential information
+# (create the session table with "rails generate session_migration")
+# StashboardRails::Application.config.session_store :active_record_store
5 config/locales/en.yml
@@ -0,0 +1,5 @@
+# Sample localization file for English. Add more files in this directory for other locales.
+# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
+
+en:
+ hello: "Hello world"
20 config/routes.rb
@@ -0,0 +1,20 @@
+StashboardRails::Application.routes.draw do
+
+ resources :services
+
+ scope 'api/v1' do
+
+ resources :services do
+ resources :events do
+ get 'current', :on => :collection
+ end
+ end
+
+ resources :levels, :only => :index
+ resources :statuses
+ resources :status_images, :only => :index
+ end
+
+ root :to => 'services#index'
+
+end
15 db/migrate/20110318043846_create_services.rb
@@ -0,0 +1,15 @@
+class CreateServices < ActiveRecord::Migration
+ def self.up
+ create_table :services do |t|
+ t.string :name
+ t.text :description
+ t.string :slug
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :services
+ end
+end
17 db/migrate/20110318045250_create_statuses.rb
@@ -0,0 +1,17 @@
+class CreateStatuses < ActiveRecord::Migration
+ def self.up
+ create_table :statuses do |t|
+ t.string :name
+ t.string :slug
+ t.text :description
+ t.string :image
+ t.integer :severity
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :statuses
+ end
+end
17 db/migrate/20110318051139_create_events.rb
@@ -0,0 +1,17 @@
+class CreateEvents < ActiveRecord::Migration
+ def self.up
+ create_table :events do |t|
+ t.datetime :started_at
+ t.boolean :is_informational, :default => false
+ t.integer :status_id
+ t.text :message
+ t.integer :service_id
+
+ t.timestamps
+ end
+ end
+
+ def self.down
+ drop_table :events
+ end
+end
48 db/schema.rb
@@ -0,0 +1,48 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended to check this file into your version control system.
+
+ActiveRecord::Schema.define(:version => 20110411041113) do
+
+ create_table "events", :force => true do |t|
+ t.datetime "started_at"
+ t.boolean "is_informational", :default => false
+ t.integer "status_id"
+ t.text "message"
+ t.integer "service_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "services", :force => true do |t|
+ t.string "name"
+ t.text "description"
+ t.string "slug"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "services_services", :force => true do |t|
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ create_table "statuses", :force => true do |t|
+ t.string "name"
+ t.string "slug"
+ t.text "description"
+ t.string "image"
+ t.integer "severity"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+end
29 db/seeds.rb
@@ -0,0 +1,29 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
+#
+# Examples:
+#
+# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
+# Mayor.create(:name => 'Daley', :city => cities.first)
+
+normal = Level.get_severity(Level::NORMAL)
+warning = Level.get_severity(Level::WARNING)
+error = Level.get_severity(Level::ERROR)
+
+Status.create!( :name => "Down",
+ :slug => "down",
+ :image => "cross-circle",
+ :severity => error,
+ :description => "The service is currently down" )
+
+Status.create!( :name => "Up",
+ :slug => "up",
+ :image => "tick-circle",
+ :severity => normal,
+ :description => "The service is up" )
+
+Status.create!( :name => "Warning",
+ :slug => "warning",
+ :image => "exclamation",
+ :severity => warning,
+ :description => "The service is experiencing intermittent problems" )
2  doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
88 features/api/events.feature
@@ -0,0 +1,88 @@
+@api
+Feature: Events JSON service
+ As an API user
+ Stashboard should
+ Provide a resource for interacting with service events
+
+ Background:
+ Given the time is Jan 1 2011, 10:00 MST
+ And the following services exist:
+ | Name | Description |
+ | DNS | I serve DNS requests |
+ And the following statuses exist:
+ | Name | Description | Image | Severity |
+ | Good | I am a status | chain | 10 |
+
+ Scenario: GET to the service events resource should return an array of events
+ Given the following events exist:
+ | Service | Status | Message |
+ | Name: DNS | Name: Good | Everything's cool |
+ And I send a GET request for "/api/v1/services/dns/events.json"
+ Then the response status should be "200"
+ And show me the response
+ And the JSON response should have "$.events[0].sid" with the text "1"
+ And the JSON response should have "$.events[0].message" with the text "Everything's cool"
+ And the JSON response should have "$.events[0].timestamp" with the text "Sat, 01 Jan 2011 17:00:00 +0000"
+ And the JSON response should have "$.events[0].url" with the text "/api/v1/services/dns/events/1.json"
+ And the JSON response should have "$.events[0].status.id" with the text "good"
+ And the JSON response should have "$.events[0].status.name" with the text "Good"
+ And the JSON response should have "$.events[0].status.description" with the text "I am a status"
+ And the JSON response should have "$.events[0].status.level" with the text "NORMAL"
+ And the JSON response should have "$.events[0].status.image" with the text "/images/status/chain.png"
+ And the JSON response should have "$.events[0].status.url" with the text "/api/v1/statuses/good.json"
+
+ Scenario: POST to the service events resource should create a new event
+ Given I send a POST request to "/api/v1/services/dns/events.json" with the following:
+ """
+ event[status]=good&event[message]=I%20am%20a%20new%20message
+ """
+ Then the response status should be "201"
+ When I send a GET request for "/api/v1/services/dns/events/1.json"
+ And the JSON response should have "$.message" with the text "I am a new message"
+ And the JSON response should have "$.status.id" with the text "good"
+
+ Scenario: GET to the current service event resource should return the current event
+ Given the following events exist:
+ | Service | Status | Message |
+ | Name: DNS | Name: Good | Everything's cool |
+ And I send a GET request for "/api/v1/services/dns/events/current.json"
+ And show me the response
+ Then the response status should be "200"
+ And the JSON response should have "$.sid" with the text "1"
+ And the JSON response should have "$.message" with the text "Everything's cool"
+ And the JSON response should have "$.timestamp" with the text "Sat, 01 Jan 2011 17:00:00 +0000"
+ And the JSON response should have "$.url" with the text "/api/v1/services/dns/events/1.json"
+ And the JSON response should have "$.status.id" with the text "good"
+ And the JSON response should have "$.status.name" with the text "Good"
+ And the JSON response should have "$.status.description" with the text "I am a status"
+ And the JSON response should have "$.status.level" with the text "NORMAL"
+ And the JSON response should have "$.status.image" with the text "/images/status/chain.png"
+ And the JSON response should have "$.status.url" with the text "/api/v1/statuses/good.json"
+
+ Scenario: GET to an individual service event resource should return the details of that event
+ Given the following events exist:
+ | Service | Status | Message |
+ | Name: DNS | Name: Good | Everything's cool |
+ And I send a GET request for "/api/v1/services/dns/events/1.json"
+ And show me the response
+ Then the response status should be "200"
+ And the JSON response should have "$.sid" with the text "1"
+ And the JSON response should have "$.message" with the text "Everything's cool"
+ And the JSON response should have "$.timestamp" with the text "Sat, 01 Jan 2011 17:00:00 +0000"
+ And the JSON response should have "$.url" with the text "/api/v1/services/dns/events/1.json"
+ And the JSON response should have "$.status.id" with the text "good"
+ And the JSON response should have "$.status.name" with the text "Good"
+ And the JSON response should have "$.status.description" with the text "I am a status"
+ And the JSON response should have "$.status.level" with the text "NORMAL"
+ And the JSON response should have "$.status.image" with the text "/images/status/chain.png"
+ And the JSON response should have "$.status.url" with the text "/api/v1/statuses/good.json"
+
+ @allow-rescue
+ Scenario: DELETE to an individual service event resource should delete that event
+ Given the following events exist:
+ | Service | Status | Message |
+ | Name: DNS | Name: Good | Everything's cool |
+ And I send a DELETE request to "/api/v1/services/dns/events/1.json"
+ Then the response status should be "200"
+ When I send a GET request for "/api/v1/services/dns/events/1.json"
+ Then the response status should be "404"
13 features/api/levels.feature
@@ -0,0 +1,13 @@
+@api
+Feature: Levels JSON service
+ As an API user
+ Stashboard should
+ Provide a list of levels in JSON format
+
+ Scenario: Return an array of levels
+ Given I send a GET request for "/api/v1/levels.json"
+ Then the response status should be "200"
+ And the JSON response should have "$.levels[0]" with the text "NORMAL"
+ And the JSON response should have "$.levels[1]" with the text "WARNING"
+ And the JSON response should have "$.levels[2]" with the text "ERROR"
+ And the JSON response should have "$.levels[3]" with the text "CRITICAL"
61 features/api/services.feature
@@ -0,0 +1,61 @@
+@api
+Feature: Services JSON service
+ As an API user
+ Stashboard should
+ Provide a resource for interacting with services
+
+ Scenario: GET to the services resource should return an array of services
+ Given the following services exist:
+ | Name | Description |
+ | DNS | I serve DNS requests |
+ And I send a GET request for "/api/v1/services.json"
+ Then the response status should be "200"
+ And the JSON response should have "$.services[0].name" with the text "DNS"
+ And the JSON response should have "$.services[0].id" with the text "dns"
+ And the JSON response should have "$.services[0].description" with the text "I serve DNS requests"
+ And the JSON response should have "$.services[0].url" with the text "/api/v1/services/dns.json"
+
+ Scenario: POST to the services resource should create a new service
+ Given I send a POST request to "/api/v1/services.json" with the following:
+ """
+ service[name]=New%20Service&service[description]=I%20am%20a%20new%20service
+ """
+ Then the response status should be "201"
+ When I send a GET request for "/api/v1/services/new-service.json"
+ And the JSON response should have "$.name" with the text "New Service"
+ And the JSON response should have "$.id" with the text "new-service"
+ And the JSON response should have "$.description" with the text "I am a new service"
+ And the JSON response should have "$.url" with the text "/api/v1/services/new-service.json"
+
+ Scenario: GET to an individual service resource should return the details of that service
+ Given the following services exist:
+ | Name | Description |
+ | DNS | I am a service |
+ And I send a GET request for "/api/v1/services/dns.json"
+ Then the response status should be "200"
+ And the JSON response should have "$.name" with the text "DNS"
+ And the JSON response should have "$.id" with the text "dns"
+ And the JSON response should have "$.description" with the text "I am a service"
+ And the JSON response should have "$.url" with the text "/api/v1/services/dns.json"
+
+ Scenario: PUT to an individual service resource should update that service
+ Given the following services exist:
+ | Name | Description |
+ | DNS | I am a service |
+ When I send a PUT request to "/api/v1/services/dns.json" with the following:
+ """
+ service[description]=I%20am%20a%20different%20service
+ """
+ Then the response status should be "200"
+ When I send a GET request for "/api/v1/services/dns.json"
+ And the JSON response should have "$.description" with the text "I am a different service"
+
+ @allow-rescue
+ Scenario: DELETE to an individual status resource should delete that status
+ Given the following services exist:
+ | Name | Description |
+ | DNS | I am a service |
+ And I send a DELETE request to "/api/v1/services/dns.json"
+ Then the response status should be "200"
+ When I send a GET request for "/api/v1/services/dns.json"
+ Then the response status should be "404"
12 features/api/status_images.feature
@@ -0,0 +1,12 @@
+@api
+Feature: Status Images JSON service
+ As an API user
+ Stashboard should
+ Provide a list of status images in JSON format
+
+ Scenario: Return an array of status images
+ Given I send a GET request for "/api/v1/status_images.json"
+ Then the response status should be "200"
+ And the JSON response should have "$..name" appear 2627 times
+ And the JSON response should have "$.images[0].name" with the text "address-book--arrow"
+ And the JSON response should have "$.images[0].url" with the text "/images/status/address-book--arrow.png"
67 features/api/statuses.feature
@@ -0,0 +1,67 @@
+@api
+Feature: Status JSON service
+ As an API user
+ Stashboard should
+ Provide a resource for interacting with statuses
+
+ Scenario: GET to the statuses resource should return an array of statuses
+ Given the following statuses exist:
+ | Name | Description | Image | Severity |
+ | Good | I am a status | chain | 10 |
+ And I send a GET request for "/api/v1/statuses.json"
+ Then the response status should be "200"
+ And the JSON response should have "$.statuses[0].description" with the text "I am a status"
+ And the JSON response should have "$.statuses[0].level" with the text "NORMAL"
+ And the JSON response should have "$.statuses[0].url" with the text "/api/v1/statuses/good.json"
+ And the JSON response should have "$.statuses[0].image" with the text "/images/status/chain.png"
+ And the JSON response should have "$.statuses[0].id" with the text "good"
+ And the JSON response should have "$.statuses[0].name" with the text "Good"
+
+ Scenario: POST to the statuses resource should create a new status
+ Given I send a POST request to "/api/v1/statuses.json" with the following:
+ """
+ status[name]=New%20Status&status[description]=I%20am%20a%20new%20status&status[level]=ERROR&status[image]=anchor
+ """
+ Then the response status should be "201"
+ When I send a GET request for "/api/v1/statuses/new-status.json"
+ And the JSON response should have "$.description" with the text "I am a new status"
+ And the JSON response should have "$.level" with the text "ERROR"
+ And the JSON response should have "$.url" with the text "/api/v1/statuses/new-status.json"
+ And the JSON response should have "$.image" with the text "/images/status/anchor.png"
+ And the JSON response should have "$.id" with the text "new-status"
+ And the JSON response should have "$.name" with the text "New Status"
+
+ Scenario: GET to an individual status resource should return the details of that status
+ Given the following statuses exist:
+ | Name | Description | Image | Severity |
+ | Good | I am a status | chain | 10 |
+ And I send a GET request for "/api/v1/statuses/good.json"
+ Then the response status should be "200"
+ And the JSON response should have "$.description" with the text "I am a status"
+ And the JSON response should have "$.level" with the text "NORMAL"
+ And the JSON response should have "$.url" with the text "/api/v1/statuses/good.json"
+ And the JSON response should have "$.image" with the text "/images/status/chain.png"
+ And the JSON response should have "$.id" with the text "good"
+ And the JSON response should have "$.name" with the text "Good"
+
+ Scenario: PUT to an individual status resource should update that status
+ Given the following statuses exist:
+ | Name | Description | Image | Severity |
+ | Good | I am a status | chain | 10 |
+ When I send a PUT request to "/api/v1/statuses/good.json" with the following:
+ """
+ status[description]=I%20am%20a%20different%20status
+ """
+ Then the response status should be "200"
+ When I send a GET request for "/api/v1/statuses/good.json"
+ And the JSON response should have "$.description" with the text "I am a different status"
+
+ @allow-rescue
+ Scenario: DELETE to an individual status resource should delete that status
+ Given the following statuses exist:
+ | Name | Description | Image | Severity |
+ | Good | I am a status | chain | 10 |
+ And I send a DELETE request to "/api/v1/statuses/good.json"
+ Then the response status should be "200"
+ When I send a GET request for "/api/v1/statuses/good.json"
+ Then the response status should be "404"
9 features/step_definitions/api_steps.rb
@@ -0,0 +1,9 @@
+Then /^the JSON response should have "([^\"]*)" appear (\d+) times$/ do |json_path, times|
+ json = JSON.parse(page.driver.last_response.body)
+ results = JsonPath.new(json_path).on(json).to_a
+ if page.respond_to? :should
+ results.size.should == times.to_i
+ else
+ assert_equal times.to_i, results.size
+ end
+end
3  features/step_definitions/timecop_steps.rb
@@ -0,0 +1,3 @@
+Given 'the time is $time' do |time|
+ Timecop.freeze Time.parse(time)
+end
20 features/step_definitions/utility_steps.rb
@@ -0,0 +1,20 @@
+Then /(?:|I )debug/ do
+ debugger
+ true
+end
+
+Then /(?:|I )sleep a while/ do
+ Then('I sleep for 5 seconds')
+end
+
+Then /(?:|I )sleep forever/ do
+ Then('I sleep for 600 seconds')
+end
+
+Then /(?:|I )pause for a second/ do
+ Then('I sleep for 1 seconds')
+end
+
+Then /(?:|I )sleep for (\d*) seconds?/ do |seconds|
+ sleep seconds.to_i
+end
211 features/step_definitions/web_steps.rb
@@ -0,0 +1,211 @@
+# TL;DR: YOU SHOULD DELETE THIS FILE
+#
+# This file was generated by Cucumber-Rails and is only here to get you a head start
+# These step definitions are thin wrappers around the Capybara/Webrat API that lets you
+# visit pages, interact with widgets and make assertions about page content.
+#
+# If you use these step definitions as basis for your features you will quickly end up
+# with features that are:
+#
+# * Hard to maintain
+# * Verbose to read
+#
+# A much better approach is to write your own higher level step definitions, following
+# the advice in the following blog posts:
+#
+# * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
+# * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/
+# * http://elabs.se/blog/15-you-re-cuking-it-wrong
+#
+
+
+require 'uri'
+require 'cgi'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "paths"))
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "support", "selectors"))
+
+module WithinHelpers
+ def with_scope(locator)
+ locator ? within(*selector_for(locator)) { yield } : yield
+ end
+end
+World(WithinHelpers)
+
+# Single-line step scoper
+When /^(.*) within ([^:]+)$/ do |step, parent|
+ with_scope(parent) { When step }
+end
+
+# Multi-line step scoper
+When /^(.*) within ([^:]+):$/ do |step, parent, table_or_string|
+ with_scope(parent) { When "#{step}:", table_or_string }
+end
+
+Given /^(?:|I )am on (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )go to (.+)$/ do |page_name|
+ visit path_to(page_name)
+end
+
+When /^(?:|I )press "([^"]*)"$/ do |button|
+ click_button(button)
+end
+
+When /^(?:|I )follow "([^"]*)"$/ do |link|
+ click_link(link)
+end
+
+When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
+ fill_in(field, :with => value)
+end
+
+When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field|
+ fill_in(field, :with => value)
+end
+
+# Use this to fill in an entire form with data from a table. Example:
+#
+# When I fill in the following:
+# | Account Number | 5002 |
+# | Expiry date | 2009-11-01 |
+# | Note | Nice guy |
+# | Wants Email? | |
+#
+# TODO: Add support for checkbox, select og option
+# based on naming conventions.
+#
+When /^(?:|I )fill in the following:$/ do |fields|
+ fields.rows_hash.each do |name, value|
+ When %{I fill in "#{name}" with "#{value}"}
+ end
+end
+
+When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
+ select(value, :from => field)
+end
+
+When /^(?:|I )check "([^"]*)"$/ do |field|
+ check(field)
+end
+
+When /^(?:|I )uncheck "([^"]*)"$/ do |field|
+ uncheck(field)
+end
+
+When /^(?:|I )choose "([^"]*)"$/ do |field|
+ choose(field)
+end
+
+When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
+ attach_file(field, File.expand_path(path))
+end
+