toro 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +7 -0
- data/Appraisals +7 -0
- data/Gemfile +3 -0
- data/app/assets/javascripts/toro/monitor/abstract_jobs_table.js.coffee +188 -0
- data/app/assets/javascripts/toro/monitor/application.js.coffee +14 -0
- data/app/assets/javascripts/toro/monitor/chart.js.coffee +203 -0
- data/app/assets/javascripts/toro/monitor/initialize.js.coffee.erb +25 -0
- data/app/assets/javascripts/toro/monitor/jobs_table.js.coffee +63 -0
- data/app/assets/javascripts/toro/monitor/processes_table.js.coffee +96 -0
- data/app/assets/javascripts/toro/monitor/queue_jobs_table.js.coffee +91 -0
- data/app/assets/stylesheets/toro/monitor/application.css.sass +5 -0
- data/app/assets/stylesheets/toro/monitor/jobs_table.css.sass +69 -0
- data/app/assets/stylesheets/toro/monitor/layout.css.sass +5 -0
- data/app/controllers/toro/monitor/api/jobs_controller.rb +65 -0
- data/app/controllers/toro/monitor/api/processes_controller.rb +13 -0
- data/app/controllers/toro/monitor/api/queues_controller.rb +24 -0
- data/app/controllers/toro/monitor/base_controller.rb +11 -0
- data/app/controllers/toro/monitor/jobs_controller.rb +11 -0
- data/app/controllers/toro/monitor/processes_controller.rb +8 -0
- data/app/controllers/toro/monitor/queues_controller.rb +9 -0
- data/app/datatables/toro/monitor/abstract_datatable.rb +71 -0
- data/app/datatables/toro/monitor/jobs_datatable.rb +60 -0
- data/app/datatables/toro/monitor/processes_datatable.rb +60 -0
- data/app/helpers/toro/monitor/toro_monitor_helper.rb +13 -0
- data/app/views/toro/monitor/jobs/_jobs.slim +14 -0
- data/app/views/toro/monitor/jobs/chart.slim +1 -0
- data/app/views/toro/monitor/jobs/index.slim +1 -0
- data/app/views/toro/monitor/layouts/application.slim +32 -0
- data/app/views/toro/monitor/processes/index.slim +14 -0
- data/app/views/toro/monitor/queues/_jobs.slim +14 -0
- data/app/views/toro/monitor/queues/index.slim +5 -0
- data/config/routes.rb +16 -0
- data/examples/chart.png +0 -0
- data/examples/job.png +0 -0
- data/examples/jobs.png +0 -0
- data/examples/processes.png +0 -0
- data/examples/queues.png +0 -0
- data/gemfiles/rails_3.gemfile +7 -0
- data/gemfiles/rails_3.gemfile.lock +146 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +147 -0
- data/lib/toro/version.rb +1 -1
- data/spec/config/database.yml.example +6 -0
- data/spec/lib/cli_spec.rb +28 -0
- data/spec/lib/client_spec.rb +26 -0
- data/spec/lib/fetcher_spec.rb +105 -0
- data/spec/lib/job_spec.rb +17 -0
- data/spec/lib/listener_spec.rb +72 -0
- data/spec/lib/manager_spec.rb +78 -0
- data/spec/lib/middleware/server/error_spec.rb +18 -0
- data/spec/lib/middleware/server/error_storage_spec.rb +22 -0
- data/spec/lib/middleware/server/properties_spec.rb +18 -0
- data/spec/lib/middleware/server/retry_spec.rb +38 -0
- data/spec/lib/processor_spec.rb +33 -0
- data/spec/lib/worker_spec.rb +50 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/active_record_spec_helper.rb +18 -0
- data/spec/support/jobs_helper.rb +4 -0
- data/spec/support/workers_helper.rb +56 -0
- data/toro.gemspec +29 -0
- data/vendor/assets/javascripts/toro/monitor/bootstrap-select.min.js +1 -0
- data/vendor/assets/javascripts/toro/monitor/bootstrap.min.js +6 -0
- data/vendor/assets/javascripts/toro/monitor/d3.js +8795 -0
- data/vendor/assets/javascripts/toro/monitor/datatables.bootstrap.js +96 -0
- data/vendor/assets/javascripts/toro/monitor/datatables.js +256 -0
- data/vendor/assets/javascripts/toro/monitor/datatables.standing_redraw.js.coffee +5 -0
- data/vendor/assets/javascripts/toro/monitor/jquery.timeago.js +27 -0
- data/vendor/assets/stylesheets/toro/monitor/bootstrap-select.min.css +1 -0
- data/vendor/assets/stylesheets/toro/monitor/bootstrap.min.css +9 -0
- metadata +103 -5
@@ -0,0 +1,63 @@
|
|
1
|
+
class ToroMonitor.JobsTable extends ToroMonitor.AbstractJobsTable
|
2
|
+
|
3
|
+
initialize: =>
|
4
|
+
options =
|
5
|
+
table_selector: 'table.jobs'
|
6
|
+
columns:
|
7
|
+
id: 0
|
8
|
+
started_by: 1
|
9
|
+
queue: 2
|
10
|
+
class_name: 3
|
11
|
+
name: 4
|
12
|
+
created_at: 5
|
13
|
+
started_at: 6
|
14
|
+
duration: 7
|
15
|
+
message: 8
|
16
|
+
status: 9
|
17
|
+
properties: 10
|
18
|
+
args: 11
|
19
|
+
column_options: [
|
20
|
+
{ bVisible: false }
|
21
|
+
{ bVisible: false }
|
22
|
+
null
|
23
|
+
null
|
24
|
+
{ bSortable: false }
|
25
|
+
{
|
26
|
+
fnRender: (oObj) =>
|
27
|
+
@format_time_ago(oObj.aData[@columns.created_at])
|
28
|
+
}
|
29
|
+
{
|
30
|
+
fnRender: (oObj) =>
|
31
|
+
@format_time_ago(oObj.aData[@columns.started_at])
|
32
|
+
}
|
33
|
+
null
|
34
|
+
{ bSortable: false }
|
35
|
+
{
|
36
|
+
fnRender: (oObj) =>
|
37
|
+
status = oObj.aData[@columns.status]
|
38
|
+
class_name = switch status
|
39
|
+
when 'failed'
|
40
|
+
'danger'
|
41
|
+
when 'complete'
|
42
|
+
'success'
|
43
|
+
when 'running'
|
44
|
+
'primary'
|
45
|
+
when 'scheduled'
|
46
|
+
'inverse'
|
47
|
+
else
|
48
|
+
'info'
|
49
|
+
html = """<a href="#" class="btn btn-#{class_name} btn-mini status-value">#{oObj.aData[@columns.status]}</a>"""
|
50
|
+
if status == 'failed'
|
51
|
+
html += """<a href="#" class="btn btn-mini btn-primary retry-job" data-job-id="#{oObj.aData[@columns.id]}">Retry<a>"""
|
52
|
+
"""<span class="action-buttons">#{html}</span>"""
|
53
|
+
}
|
54
|
+
{ bVisible: false }
|
55
|
+
{ bVisible: false }
|
56
|
+
]
|
57
|
+
|
58
|
+
return null unless $(options.table_selector).length
|
59
|
+
|
60
|
+
@initialize_with_options(options)
|
61
|
+
|
62
|
+
$ ->
|
63
|
+
new ToroMonitor.JobsTable
|
@@ -0,0 +1,96 @@
|
|
1
|
+
class ToroMonitor.ProcessesTable extends ToroMonitor.AbstractJobsTable
|
2
|
+
|
3
|
+
initialize: =>
|
4
|
+
@options =
|
5
|
+
table_selector: 'table.processes'
|
6
|
+
columns:
|
7
|
+
id: 0
|
8
|
+
started_by: 1
|
9
|
+
queue: 2
|
10
|
+
class_name: 3
|
11
|
+
name: 4
|
12
|
+
created_at: 5
|
13
|
+
started_at: 6
|
14
|
+
duration: 7
|
15
|
+
message: 8
|
16
|
+
status: 9
|
17
|
+
properties: 10
|
18
|
+
args: 11
|
19
|
+
column_options: [
|
20
|
+
{ bVisible: false }
|
21
|
+
{
|
22
|
+
fnRender: (oObj) =>
|
23
|
+
"""<span class="nowrap">#{oObj.aData[@columns.started_by]}</span>"""
|
24
|
+
}
|
25
|
+
null
|
26
|
+
null
|
27
|
+
{ bSortable: false }
|
28
|
+
{
|
29
|
+
fnRender: (oObj) =>
|
30
|
+
@format_time_ago(oObj.aData[@columns.created_at])
|
31
|
+
}
|
32
|
+
{
|
33
|
+
fnRender: (oObj) =>
|
34
|
+
@format_time_ago(oObj.aData[@columns.started_at])
|
35
|
+
}
|
36
|
+
null
|
37
|
+
{ bSortable: false }
|
38
|
+
{
|
39
|
+
fnRender: (oObj) =>
|
40
|
+
status = oObj.aData[@columns.status]
|
41
|
+
class_name = switch status
|
42
|
+
when 'failed'
|
43
|
+
'danger'
|
44
|
+
when 'complete'
|
45
|
+
'success'
|
46
|
+
when 'running'
|
47
|
+
'primary'
|
48
|
+
else
|
49
|
+
'info'
|
50
|
+
html = """<a href="#" class="btn btn-#{class_name} btn-mini status-value">#{oObj.aData[@columns.status]}</a>"""
|
51
|
+
if status == 'failed'
|
52
|
+
html += """<a href="#" class="btn btn-mini btn-primary retry-job" data-job-id="#{oObj.aData[@columns.id]}">Retry<a>"""
|
53
|
+
"""<span class="action-buttons">#{html}</span>"""
|
54
|
+
}
|
55
|
+
{ bVisible: false }
|
56
|
+
{ bVisible: false }
|
57
|
+
]
|
58
|
+
|
59
|
+
return null unless $(@options.table_selector).length
|
60
|
+
|
61
|
+
@table = $(@options.table_selector)
|
62
|
+
|
63
|
+
@columns = @options.columns
|
64
|
+
@status_filter = null
|
65
|
+
|
66
|
+
@table.dataTable
|
67
|
+
bProcessing: true
|
68
|
+
bServerSide: true
|
69
|
+
sAjaxSource: @table.data('source')
|
70
|
+
iDisplayLength: 10
|
71
|
+
aaSorting: [[@columns.created_at, 'desc']]
|
72
|
+
sPaginationType: 'bootstrap'
|
73
|
+
aoColumns: @options.column_options
|
74
|
+
oLanguage:
|
75
|
+
sInfo: '_TOTAL_ jobs'
|
76
|
+
sInfoFiltered: ' (filtered from _MAX_)'
|
77
|
+
sLengthMenu: 'Per page: _MENU_'
|
78
|
+
sSearch: ''
|
79
|
+
fnRowCallback: (nRow, aData, iDisplayIndex) =>
|
80
|
+
$('.timeago', nRow).timeago()
|
81
|
+
fnInitComplete: () =>
|
82
|
+
filter_container = @table.siblings('.dataTables_filter')
|
83
|
+
filter_container.find('input').attr('placeholder', 'Search...')
|
84
|
+
fnServerData: (sSource, aoData, fnCallback) =>
|
85
|
+
$.each @api_params, (key, value) =>
|
86
|
+
aoData.push
|
87
|
+
name: key
|
88
|
+
value: @api_params[key]
|
89
|
+
$.getJSON sSource, aoData, (json) -> fnCallback(json)
|
90
|
+
|
91
|
+
@table.parents('.dataTables_wrapper').addClass('jobs-table-wrapper')
|
92
|
+
|
93
|
+
@initialize_ui()
|
94
|
+
|
95
|
+
$ ->
|
96
|
+
new ToroMonitor.ProcessesTable
|
@@ -0,0 +1,91 @@
|
|
1
|
+
class ToroMonitor.QueueJobsTable extends ToroMonitor.AbstractJobsTable
|
2
|
+
|
3
|
+
initialize: =>
|
4
|
+
options =
|
5
|
+
table_selector: 'table.queue-jobs'
|
6
|
+
columns:
|
7
|
+
id: 0
|
8
|
+
started_by: 1
|
9
|
+
queue: 2
|
10
|
+
class_name: 3
|
11
|
+
name: 4
|
12
|
+
created_at: 5
|
13
|
+
started_at: 6
|
14
|
+
duration: 7
|
15
|
+
message: 8
|
16
|
+
status: 9
|
17
|
+
properties: 10
|
18
|
+
args: 11
|
19
|
+
column_options: [
|
20
|
+
{ bVisible: false }
|
21
|
+
{ bVisible: false }
|
22
|
+
{ bVisible: false }
|
23
|
+
null
|
24
|
+
{ bSortable: false }
|
25
|
+
{
|
26
|
+
fnRender: (oObj) =>
|
27
|
+
@format_time_ago(oObj.aData[@columns.created_at])
|
28
|
+
}
|
29
|
+
{
|
30
|
+
fnRender: (oObj) =>
|
31
|
+
@format_time_ago(oObj.aData[@columns.started_at])
|
32
|
+
}
|
33
|
+
null
|
34
|
+
{ bSortable: false }
|
35
|
+
{
|
36
|
+
fnRender: (oObj) =>
|
37
|
+
status = oObj.aData[@columns.status]
|
38
|
+
class_name = switch status
|
39
|
+
when 'failed'
|
40
|
+
'danger'
|
41
|
+
when 'complete'
|
42
|
+
'success'
|
43
|
+
when 'running'
|
44
|
+
'primary'
|
45
|
+
else
|
46
|
+
'info'
|
47
|
+
html = """<a href="#" class="btn btn-#{class_name} btn-mini status-value">#{oObj.aData[@columns.status]}</a>"""
|
48
|
+
if status == 'failed'
|
49
|
+
html += """<a href="#" class="btn btn-mini btn-primary retry-job" data-job-id="#{oObj.aData[@columns.id]}">Retry<a>"""
|
50
|
+
"""<span class="action-buttons">#{html}</span>"""
|
51
|
+
}
|
52
|
+
{ bVisible: false }
|
53
|
+
{ bVisible: false }
|
54
|
+
]
|
55
|
+
return null unless $(options.table_selector).length
|
56
|
+
|
57
|
+
@queue_select = $('[name=queue_select]')
|
58
|
+
@queue_select.selectpicker()
|
59
|
+
@queue_select.change =>
|
60
|
+
@load_selected_queue()
|
61
|
+
@reload_table()
|
62
|
+
@queue_select.val(@queue_select.find('option:first').val())
|
63
|
+
@load_selected_queue()
|
64
|
+
|
65
|
+
@initialize_with_options(options)
|
66
|
+
|
67
|
+
show_queue_stats: =>
|
68
|
+
$.getJSON ToroMonitor.settings.api_url("queues/#{@queue}"), (stats) =>
|
69
|
+
if stats
|
70
|
+
header_cells = ""
|
71
|
+
value_cells = ""
|
72
|
+
$.each stats.status_counts, (status, count) =>
|
73
|
+
header_cells += "<th>#{status}</th>"
|
74
|
+
value_cells += "<td>#{count}</td>"
|
75
|
+
html = """
|
76
|
+
<table class="table table-striped table-condensed table-bordered">
|
77
|
+
<tr>#{header_cells}</tr>
|
78
|
+
<tr>#{value_cells}</tr>
|
79
|
+
</table>
|
80
|
+
"""
|
81
|
+
else
|
82
|
+
html = ''
|
83
|
+
$('.queue-stats').html(html)
|
84
|
+
|
85
|
+
load_selected_queue: =>
|
86
|
+
@queue = @queue_select.val()
|
87
|
+
@api_params['queue'] = @queue
|
88
|
+
@show_queue_stats()
|
89
|
+
|
90
|
+
$ ->
|
91
|
+
new ToroMonitor.QueueJobsTable
|
@@ -0,0 +1,69 @@
|
|
1
|
+
div.dataTables_length label
|
2
|
+
float: right
|
3
|
+
|
4
|
+
.dataTables_processing
|
5
|
+
display: none
|
6
|
+
|
7
|
+
.modal-body
|
8
|
+
max-height: 500px
|
9
|
+
|
10
|
+
.dataTable
|
11
|
+
.nowrap
|
12
|
+
white-space: nowrap
|
13
|
+
|
14
|
+
.jobs-table
|
15
|
+
td
|
16
|
+
max-width: 200px
|
17
|
+
overflow: auto
|
18
|
+
text-overflow: ellipsis
|
19
|
+
white-space: nowrap
|
20
|
+
|
21
|
+
.jobs-table-wrapper
|
22
|
+
clear: both
|
23
|
+
|
24
|
+
.status-value
|
25
|
+
text-transform: capitalize
|
26
|
+
|
27
|
+
.status-filter
|
28
|
+
float: left
|
29
|
+
margin-right: 15px
|
30
|
+
button
|
31
|
+
text-transform: capitalize
|
32
|
+
|
33
|
+
.action-buttons
|
34
|
+
white-space: nowrap
|
35
|
+
|
36
|
+
.retry-job
|
37
|
+
margin-left: 5px
|
38
|
+
|
39
|
+
.dataTables_filter > label
|
40
|
+
float: left
|
41
|
+
|
42
|
+
.dataTables_filter > label > input
|
43
|
+
width: 120px
|
44
|
+
|
45
|
+
.queue-stats
|
46
|
+
float: right
|
47
|
+
font-size: 12px
|
48
|
+
margin-left: 10px
|
49
|
+
|
50
|
+
&:after
|
51
|
+
content: "."
|
52
|
+
display: block
|
53
|
+
height: 0
|
54
|
+
clear: both
|
55
|
+
visibility: hidden
|
56
|
+
|
57
|
+
table
|
58
|
+
margin-bottom: 10px
|
59
|
+
|
60
|
+
th, td
|
61
|
+
padding: 0 5px
|
62
|
+
text-align: center
|
63
|
+
|
64
|
+
th
|
65
|
+
text-transform: capitalize
|
66
|
+
font-weight: normal
|
67
|
+
|
68
|
+
td
|
69
|
+
font-weight: bold
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Toro
|
2
|
+
module Monitor
|
3
|
+
module Api
|
4
|
+
class JobsController < ActionController::Base
|
5
|
+
protect_from_forgery
|
6
|
+
|
7
|
+
def index
|
8
|
+
render json: JobsDatatable.new(view_context)
|
9
|
+
end
|
10
|
+
|
11
|
+
def chart
|
12
|
+
queues_jobs = Job.select('queue, status').all.group_by(&:queue)
|
13
|
+
queues = []
|
14
|
+
statuses = Job.statuses
|
15
|
+
queues_status_counts = queues_jobs.collect do |queue, jobs|
|
16
|
+
statuses_jobs = jobs.group_by(&:status)
|
17
|
+
statuses_job_counts = statuses_jobs.collect do |status, jobs|
|
18
|
+
next unless statuses.include?(status)
|
19
|
+
[status, jobs.length]
|
20
|
+
end
|
21
|
+
statuses_job_counts = Hash[statuses_job_counts.compact]
|
22
|
+
queues << queue unless queues.include?(queue)
|
23
|
+
{ queue: queue }.merge(statuses_job_counts)
|
24
|
+
end
|
25
|
+
queues_status_counts = queues_status_counts.sort_by { |q| q[:queue] }
|
26
|
+
render json: {
|
27
|
+
queues_status_counts: queues_status_counts,
|
28
|
+
statuses: statuses
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def custom_views
|
33
|
+
job = Job.find(params[:id])
|
34
|
+
render json: {}, status: 404 and return if job.blank?
|
35
|
+
|
36
|
+
views = CustomViews.for_job(job)
|
37
|
+
views = views.collect do |view|
|
38
|
+
{
|
39
|
+
name: view[:name],
|
40
|
+
html: render_to_string(view[:path], locals: {job: job, path: view[:path]})
|
41
|
+
}
|
42
|
+
end
|
43
|
+
render json: views, status: :ok
|
44
|
+
end
|
45
|
+
|
46
|
+
def retry
|
47
|
+
id = params[:id]
|
48
|
+
render json: {}, status: 404 and return if id.blank?
|
49
|
+
|
50
|
+
job = Job.find(id)
|
51
|
+
render json: {}, status: 404 and return if job.blank?
|
52
|
+
|
53
|
+
args = job.args
|
54
|
+
worker = job.class_name.constantize
|
55
|
+
worker.perform_async(*args)
|
56
|
+
render json: {}, status: :ok
|
57
|
+
end
|
58
|
+
|
59
|
+
def statuses
|
60
|
+
render json: Toro::Job.statuses, status: :ok
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Toro
|
2
|
+
module Monitor
|
3
|
+
module Api
|
4
|
+
class QueuesController < ActionController::Base
|
5
|
+
protect_from_forgery
|
6
|
+
|
7
|
+
def show
|
8
|
+
queue = params[:queue]
|
9
|
+
render json: {}, status: 404 and return if queue.blank?
|
10
|
+
|
11
|
+
status_counts = Toro::Job.where(queue: queue).count(group: 'status')
|
12
|
+
ordered_status_counts = {}
|
13
|
+
Job.statuses.each do |status|
|
14
|
+
ordered_status_counts[status] = status_counts.has_key?(status) ? status_counts[status] : 0
|
15
|
+
end
|
16
|
+
response = {
|
17
|
+
status_counts: ordered_status_counts
|
18
|
+
}
|
19
|
+
render json: response.to_json, status: :ok
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|