resque-web-edge 1.0.0

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 (105) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +96 -0
  3. data/Rakefile +34 -0
  4. data/app/assets/images/resque_web/idle.png +0 -0
  5. data/app/assets/images/resque_web/lifebuoy.png +0 -0
  6. data/app/assets/images/resque_web/poll.png +0 -0
  7. data/app/assets/images/resque_web/rails.png +0 -0
  8. data/app/assets/images/resque_web/working.png +0 -0
  9. data/app/assets/javascripts/resque_web/application.js +16 -0
  10. data/app/assets/javascripts/resque_web/bootstrap.js.coffee +4 -0
  11. data/app/assets/javascripts/resque_web/failure.js.coffee +7 -0
  12. data/app/assets/javascripts/resque_web/jquery.relative-date.js +47 -0
  13. data/app/assets/javascripts/resque_web/polling.js.coffee +25 -0
  14. data/app/assets/javascripts/resque_web/relative_date.js.coffee +27 -0
  15. data/app/assets/stylesheets/resque_web/application.css +13 -0
  16. data/app/assets/stylesheets/resque_web/bootstrap_and_overrides.css.scss.erb +146 -0
  17. data/app/controllers/resque_web/application_controller.rb +26 -0
  18. data/app/controllers/resque_web/failures_controller.rb +62 -0
  19. data/app/controllers/resque_web/jobs_controller.rb +12 -0
  20. data/app/controllers/resque_web/overview_controller.rb +7 -0
  21. data/app/controllers/resque_web/queues_controller.rb +22 -0
  22. data/app/controllers/resque_web/stats_controller.rb +36 -0
  23. data/app/controllers/resque_web/workers_controller.rb +23 -0
  24. data/app/controllers/resque_web/working_controller.rb +8 -0
  25. data/app/helpers/resque_web/application_helper.rb +69 -0
  26. data/app/helpers/resque_web/failures_helper.rb +58 -0
  27. data/app/helpers/resque_web/overview_helper.rb +6 -0
  28. data/app/helpers/resque_web/queues_helper.rb +72 -0
  29. data/app/helpers/resque_web/stats_helper.rb +54 -0
  30. data/app/helpers/resque_web/workers_helper.rb +16 -0
  31. data/app/helpers/resque_web/working_helper.rb +19 -0
  32. data/app/views/layouts/resque_web/application.html.erb +75 -0
  33. data/app/views/resque_web/failures/_failed_job.html.erb +53 -0
  34. data/app/views/resque_web/failures/_overview.html.erb +24 -0
  35. data/app/views/resque_web/failures/index.html.erb +32 -0
  36. data/app/views/resque_web/failures/show.html.erb +20 -0
  37. data/app/views/resque_web/overview/show.html.erb +4 -0
  38. data/app/views/resque_web/queues/_queues.html.erb +4 -0
  39. data/app/views/resque_web/queues/_queues_advanced.html.erb +14 -0
  40. data/app/views/resque_web/queues/_queues_basic.html.erb +17 -0
  41. data/app/views/resque_web/queues/index.html.erb +1 -0
  42. data/app/views/resque_web/queues/show.html.erb +39 -0
  43. data/app/views/resque_web/stats/key.html.erb +26 -0
  44. data/app/views/resque_web/stats/keys.html.erb +17 -0
  45. data/app/views/resque_web/stats/redis.html.erb +14 -0
  46. data/app/views/resque_web/stats/resque.html.erb +14 -0
  47. data/app/views/resque_web/workers/index.html.erb +19 -0
  48. data/app/views/resque_web/workers/show.html.erb +38 -0
  49. data/app/views/resque_web/working/_working.html.erb +34 -0
  50. data/app/views/resque_web/working/index.html.erb +1 -0
  51. data/config/initializers/resque_config.rb +13 -0
  52. data/config/routes.rb +35 -0
  53. data/lib/resque_web.rb +4 -0
  54. data/lib/resque_web/engine.rb +13 -0
  55. data/lib/resque_web/version.rb +3 -0
  56. data/test/dummy/Rakefile +6 -0
  57. data/test/dummy/app/assets/javascripts/application.js +13 -0
  58. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  59. data/test/dummy/app/controllers/application_controller.rb +5 -0
  60. data/test/dummy/app/helpers/application_helper.rb +2 -0
  61. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  62. data/test/dummy/bin/bundle +3 -0
  63. data/test/dummy/bin/rails +4 -0
  64. data/test/dummy/bin/rake +4 -0
  65. data/test/dummy/config.ru +4 -0
  66. data/test/dummy/config/application.rb +23 -0
  67. data/test/dummy/config/boot.rb +5 -0
  68. data/test/dummy/config/database.yml +25 -0
  69. data/test/dummy/config/environment.rb +5 -0
  70. data/test/dummy/config/environments/development.rb +29 -0
  71. data/test/dummy/config/environments/production.rb +80 -0
  72. data/test/dummy/config/environments/test.rb +36 -0
  73. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  74. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  75. data/test/dummy/config/initializers/inflections.rb +16 -0
  76. data/test/dummy/config/initializers/mime_types.rb +5 -0
  77. data/test/dummy/config/initializers/secret_token.rb +12 -0
  78. data/test/dummy/config/initializers/session_store.rb +3 -0
  79. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  80. data/test/dummy/config/locales/en.yml +23 -0
  81. data/test/dummy/config/routes.rb +4 -0
  82. data/test/dummy/db/schema.rb +16 -0
  83. data/test/dummy/public/404.html +58 -0
  84. data/test/dummy/public/422.html +58 -0
  85. data/test/dummy/public/500.html +57 -0
  86. data/test/dummy/public/favicon.ico +0 -0
  87. data/test/functional/failures_controller_test.rb +74 -0
  88. data/test/functional/jobs_controller_test.rb +45 -0
  89. data/test/functional/overview_controller_test.rb +37 -0
  90. data/test/functional/queues_controller_test.rb +58 -0
  91. data/test/functional/stats_controller_test.rb +70 -0
  92. data/test/functional/workers_controller_test.rb +26 -0
  93. data/test/functional/working_controller_test.rb +19 -0
  94. data/test/integration/plugin_integration_test.rb +72 -0
  95. data/test/support/controller_test_helpers.rb +22 -0
  96. data/test/test_helper.rb +26 -0
  97. data/test/unit/helpers/failures_helper_test.rb +15 -0
  98. data/test/unit/helpers/jobs_helper_test.rb +4 -0
  99. data/test/unit/helpers/overview_helper_test.rb +4 -0
  100. data/test/unit/helpers/queues_helper_test.rb +4 -0
  101. data/test/unit/helpers/retry_controller_helper_test.rb +4 -0
  102. data/test/unit/helpers/stats_helper_test.rb +4 -0
  103. data/test/unit/helpers/workers_helper_test.rb +4 -0
  104. data/test/unit/helpers/working_helper_test.rb +4 -0
  105. metadata +267 -0
@@ -0,0 +1,26 @@
1
+ module ResqueWeb
2
+ class ApplicationController < ActionController::Base
3
+ include ActionView::Helpers::TextHelper
4
+ protect_from_forgery
5
+ before_filter :set_subtabs, :authorize
6
+
7
+ helper :all
8
+
9
+ def self.subtabs(*tab_names)
10
+ return defined?(@subtabs) ? @subtabs : [] if tab_names.empty?
11
+ @subtabs = tab_names
12
+ end
13
+
14
+ def set_subtabs(subtabs = self.class.subtabs)
15
+ @subtabs = subtabs
16
+ end
17
+
18
+ private
19
+
20
+ def authorize
21
+ if ENV["RESQUE_WEB_HTTP_BASIC_AUTH_USER"] && ENV["RESQUE_WEB_HTTP_BASIC_AUTH_PASSWORD"]
22
+ authenticate_or_request_with_http_basic {|u, p| u == ENV["RESQUE_WEB_HTTP_BASIC_AUTH_USER"] && p == ENV["RESQUE_WEB_HTTP_BASIC_AUTH_PASSWORD"] }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ module ResqueWeb
2
+ class FailuresController < ResqueWeb::ApplicationController
3
+
4
+ # Display all jobs in the failure queue
5
+ #
6
+ # @param [Hash] params
7
+ # @option params [String] :class filters failures shown by class
8
+ # @option params [String] :queue filters failures shown by failure queue name
9
+ def index
10
+ end
11
+
12
+ # remove an individual job from the failure queue
13
+ def destroy
14
+ Resque::Failure.remove(params[:id])
15
+ redirect_to failures_path(redirect_params)
16
+ end
17
+
18
+ # destroy all jobs from the failure queue
19
+ def destroy_all
20
+ queue = params[:queue] || 'failed'
21
+ Resque::Failure.clear(queue)
22
+ redirect_to failures_path(redirect_params)
23
+ end
24
+
25
+ # retry an individual job from the failure queue
26
+ def retry
27
+ reque_single_job(params[:id])
28
+ redirect_to failures_path(redirect_params)
29
+ end
30
+
31
+ # retry all jobs from the failure queue
32
+ def retry_all
33
+ if params[:queue].present? && params[:queue]!="failed"
34
+ Resque::Failure.requeue_queue(params[:queue])
35
+ else
36
+ (Resque::Failure.count-1).downto(0).each { |id| reque_single_job(id) }
37
+ end
38
+ redirect_to failures_path(redirect_params)
39
+ end
40
+
41
+ private
42
+
43
+ #API agnostic for Resque 2 with duck typing on requeue_and_remove
44
+ def reque_single_job(id)
45
+ if Resque::Failure.respond_to?(:requeue_and_remove)
46
+ Resque::Failure.requeue_and_remove(id)
47
+ else
48
+ Resque::Failure.requeue(id)
49
+ Resque::Failure.remove(id)
50
+ end
51
+ end
52
+
53
+ def redirect_params
54
+ {}.tap do |p|
55
+ if params[:queue].present?
56
+ p[:queue] = params[:queue]
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,12 @@
1
+ module ResqueWeb
2
+ class JobsController < ApplicationController
3
+
4
+ def destroy
5
+ args = JSON.parse(params[:args])
6
+ destroyed = Resque::Job.destroy(params[:queue], params[:job_class], *args)
7
+ flash[:info] = "#{pluralize(destroyed, 'job')} deleted."
8
+ redirect_to queue_path(params[:queue])
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module ResqueWeb
2
+ class OverviewController < ResqueWeb::ApplicationController
3
+ def show
4
+ render :layout => !request.xhr?, :locals => { :polling => request.xhr? }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module ResqueWeb
2
+ class QueuesController < ResqueWeb::ApplicationController
3
+
4
+ def index
5
+ end
6
+
7
+ def show
8
+ set_subtabs view_context.queue_names
9
+ end
10
+
11
+ def destroy
12
+ Resque.remove_queue(params[:id])
13
+ redirect_to queues_path
14
+ end
15
+
16
+ def clear
17
+ Resque.redis.del("queue:#{params[:id]}")
18
+ redirect_to queue_path(params[:id])
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module ResqueWeb
2
+ class StatsController < ResqueWeb::ApplicationController
3
+ subtabs :resque, :redis, :keys
4
+
5
+ def index
6
+ redirect_to action: "resque"
7
+ end
8
+
9
+ def resque
10
+ respond_to do |format|
11
+ format.html
12
+ format.json { render json: Hash[Resque.info.sort] }
13
+ end
14
+ end
15
+
16
+ def redis
17
+ respond_to do |format|
18
+ format.html
19
+ format.json { render json: Hash[Resque.redis.info.sort] }
20
+ end
21
+ end
22
+
23
+ def keys
24
+ respond_to do |format|
25
+ format.html do
26
+ if params[:id]
27
+ render 'key'
28
+ else
29
+ render 'keys'
30
+ end
31
+ end
32
+ format.json { render json: Resque.keys.sort }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ module ResqueWeb
2
+ class WorkersController < ResqueWeb::ApplicationController
3
+ before_filter :display_subtabs
4
+
5
+ def index
6
+ end
7
+
8
+ def show
9
+ if params[:id] && params[:id] != 'all'
10
+ @workers = view_context.worker_hosts[params[:id]].map { |id| Resque::Worker.find(id) }
11
+ else
12
+ @workers = Resque.workers
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def display_subtabs
19
+ set_subtabs view_context.worker_hosts.map(&:first)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ module ResqueWeb
2
+ class WorkingController < ResqueWeb::ApplicationController
3
+
4
+ def index
5
+ end
6
+
7
+ end
8
+ end
@@ -0,0 +1,69 @@
1
+ module ResqueWeb
2
+ module ApplicationHelper
3
+
4
+ PER_PAGE = 20
5
+
6
+ def tabs
7
+ t = {'overview' => ResqueWeb::Engine.app.url_helpers.overview_path,
8
+ 'working' => ResqueWeb::Engine.app.url_helpers.working_index_path,
9
+ 'failures' => ResqueWeb::Engine.app.url_helpers.failures_path,
10
+ 'queues' => ResqueWeb::Engine.app.url_helpers.queues_path,
11
+ 'workers' => ResqueWeb::Engine.app.url_helpers.workers_path,
12
+ 'stats' => ResqueWeb::Engine.app.url_helpers.stats_path
13
+ }
14
+ ResqueWeb::Plugins.plugins.each do |p|
15
+ p.tabs.each { |tab| t.merge!(tab) }
16
+ end
17
+ t
18
+ end
19
+
20
+ def tab(name,path)
21
+ content_tag :li, link_to(name.capitalize, path), :class => current_tab?(name) ? "active" : nil
22
+ end
23
+
24
+ def current_tab
25
+ params[:controller].gsub(/resque_web\//, "#{root_path}")
26
+ end
27
+
28
+ def current_tab?(name)
29
+ request.path.starts_with? tabs[name.to_s]
30
+ end
31
+
32
+ attr_reader :subtabs
33
+
34
+ def subtab(name)
35
+ content_tag :li, link_to(name, "#{current_tab}/#{name}"), :class => current_subtab?(name) ? "current" : nil
36
+ end
37
+
38
+ def current_subtab?(name)
39
+ params[:id] == name.to_s
40
+ end
41
+
42
+ def pagination(options = {})
43
+ start = options[:start] || 1
44
+ per_page = options[:per_page] || PER_PAGE
45
+ total = options[:total] || 0
46
+ return if total < per_page
47
+
48
+ markup = ""
49
+ if start - per_page >= 0
50
+ markup << link_to(raw("&laquo; less"), params.merge(:start => start - per_page), :class => 'btn less')
51
+ end
52
+
53
+ if start + per_page <= total
54
+ markup << link_to(raw("more &raquo;"), params.merge(:start => start + per_page), :class => 'btn more')
55
+ end
56
+
57
+ content_tag :p, raw(markup), :class => 'pagination'
58
+ end
59
+
60
+ def poll(polling=false)
61
+ if polling
62
+ text = "Last Updated: #{Time.now.strftime("%H:%M:%S")}".html_safe
63
+ else
64
+ text = "<a href='#{h(request.path)}' rel='poll'>Live Poll</a>".html_safe
65
+ end
66
+ content_tag :p, text, :class => 'poll'
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,58 @@
1
+ module ResqueWeb
2
+ module FailuresHelper
3
+ def each_failure(&block)
4
+ Resque::Failure.each(failure_start_at, failure_per_page, params[:queue], params[:class], &block)
5
+ end
6
+
7
+ def failure_date_format
8
+ "%Y/%m/%d %T %z"
9
+ end
10
+
11
+ def multiple_failure_queues?
12
+ @multiple_failure_queues ||= Resque::Failure.queues.size > 1
13
+ end
14
+
15
+ def failure_queue
16
+ multiple_failure_queues? ? params[:id] : 'failed'
17
+ end
18
+
19
+ def failure_queue_name
20
+ @failure_queue_name ||= params[:queue] ? params[:queue] : 'Failed'
21
+ end
22
+
23
+ def failure_size
24
+ @failure_size ||= Resque::Failure.count(params[:id], params[:class])
25
+ end
26
+
27
+ def failure_per_page
28
+ @failures_per_page ||= params[:class] ? failure_size : 20
29
+ end
30
+
31
+ def failure_start_at
32
+ params[:start].to_i
33
+ end
34
+
35
+ def failure_end_at
36
+ if failure_start_at + failure_per_page > failure_size
37
+ failure_size
38
+ else
39
+ failure_start_at + failure_per_page
40
+ end
41
+ end
42
+
43
+ def failure_class_counts(queue = params[:id])
44
+ classes = Hash.new(0)
45
+ Resque::Failure.each(0, Resque::Failure.count(queue), queue) do |_, item|
46
+ class_name = item['payload']['class'] if item['payload']
47
+ class_name ||= "nil"
48
+ classes[class_name] += 1
49
+ end
50
+ classes.sort_by { |name,_| name }
51
+ end
52
+
53
+ def job_arguments(job)
54
+ return 'nil' unless job['payload']
55
+ Array(job['payload']['args']).map { |arg| arg.inspect }.join("\n")
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,6 @@
1
+ module ResqueWeb
2
+ module OverviewHelper
3
+ include QueuesHelper
4
+ include WorkingHelper
5
+ end
6
+ end
@@ -0,0 +1,72 @@
1
+ require 'resque/failure/redis_multi_queue'
2
+
3
+ module ResqueWeb
4
+ module QueuesHelper
5
+ def queues_partial_name
6
+ if Resque::Failure.backend == Resque::Failure::RedisMultiQueue
7
+ 'resque_web/queues/queues_advanced'
8
+ else
9
+ 'resque_web/queues/queues_basic'
10
+ end
11
+ end
12
+
13
+ def queue_names
14
+ Resque.queues.sort_by(&:to_s)
15
+ end
16
+
17
+ def queue_start_at
18
+ params[:start].to_i
19
+ end
20
+
21
+ def queue_end_at
22
+ if queue_start_at + queue_per_page > queue_size
23
+ queue_size
24
+ else
25
+ queue_start_at + queue_per_page
26
+ end
27
+ end
28
+
29
+ def queue_per_page
30
+ 20
31
+ end
32
+
33
+ def queue_size(queue_name = params[:id])
34
+ Resque.size queue_name
35
+ end
36
+
37
+ def queue_jobs
38
+ @queue_jobs ||= Resque.peek(params[:id], queue_start_at, queue_per_page)
39
+ end
40
+
41
+ def failed_queue_names
42
+ Resque::Failure.queues.sort_by(&:to_s)
43
+ end
44
+
45
+ def failed_queue_name(original_queue_name)
46
+ "#{original_queue_name}_failed"
47
+ end
48
+
49
+ def failed_queue_class(queue_name)
50
+ Resque::Failure.count(queue_name).zero? ? "failed" : "failure"
51
+ end
52
+
53
+ def failed_queue_size(queue_name)
54
+ Resque::Failure.count(queue_name)
55
+ end
56
+
57
+ def failed_queue_info(queue_name)
58
+ failed_queue = failed_queue_name(queue_name)
59
+ size = failed_queue_size(failed_queue)
60
+
61
+ if size > 0
62
+ css_class = "badge badge-important"
63
+ badge = link_to(size, failure_path(failed_queue))
64
+ else
65
+ css_class = "badge"
66
+ badge = size.to_s
67
+ end
68
+
69
+ raw "<span class=\"#{css_class}\">#{badge}</span>"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ module ResqueWeb
2
+ module StatsHelper
3
+ def resque_info
4
+ Resque.info.sort_by { |i| i[0].to_s }
5
+ end
6
+
7
+ def redis_info
8
+ Resque.redis.info.to_a.sort_by { |i| i[0].to_s }
9
+ end
10
+
11
+ def redis_key_type(key)
12
+ Resque.redis.type(key)
13
+ end
14
+
15
+ def redis_key_size(key)
16
+ # FIXME: there's a potential race in this method if a key is modified
17
+ # "in flight". Not sure how to fix it, unfortunately :(
18
+ case redis_key_type(key)
19
+ when 'none'
20
+ 0
21
+ when 'list'
22
+ Resque.redis.llen(key)
23
+ when 'set'
24
+ Resque.redis.scard(key)
25
+ when 'string'
26
+ string = Resque.redis.get(key)
27
+ string ? string.length : 0
28
+ when 'zset'
29
+ Resque.redis.zcard(key)
30
+ end
31
+ end
32
+
33
+ def redis_get_array(key, start=0)
34
+ case redis_key_type(key)
35
+ when 'none'
36
+ []
37
+ when 'list'
38
+ Resque.redis.lrange(key, start, start + 20)
39
+ when 'set'
40
+ Resque.redis.smembers(key)[start..(start + 20)]
41
+ when 'string'
42
+ [Resque.redis.get(key)]
43
+ when 'zset'
44
+ Resque.redis.zrange(key, start, start + 20)
45
+ when 'hash'
46
+ Resque.redis.hgetall(key)
47
+ end
48
+ end
49
+
50
+ def current_subtab?(name)
51
+ params[:action] == name.to_s
52
+ end
53
+ end
54
+ end