rocketjob_mission_control 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/rocket_job_mission_control/base.js.coffee +3 -13
- data/app/assets/stylesheets/rocket_job_mission_control/base.scss +38 -5
- data/app/assets/stylesheets/rocket_job_mission_control/bootstrap_and_overrides.scss +1 -1
- data/app/controllers/rocket_job_mission_control/active_processes_controller.rb +8 -7
- data/app/controllers/rocket_job_mission_control/jobs/index_filters_controller.rb +14 -14
- data/app/controllers/rocket_job_mission_control/jobs_controller.rb +9 -4
- data/app/controllers/rocket_job_mission_control/workers_controller.rb +1 -1
- data/app/datatables/rocket_job_mission_control/active_processes_datatable.rb +11 -11
- data/app/datatables/rocket_job_mission_control/completed_jobs_datatable.rb +2 -1
- data/app/datatables/rocket_job_mission_control/interrupted_jobs_datatable.rb +1 -0
- data/app/datatables/rocket_job_mission_control/jobs_datatable.rb +40 -3
- data/app/datatables/rocket_job_mission_control/queued_jobs_datatable.rb +1 -0
- data/app/datatables/rocket_job_mission_control/running_jobs_datatable.rb +1 -0
- data/app/datatables/rocket_job_mission_control/scheduled_jobs_datatable.rb +1 -6
- data/app/helpers/rocket_job_mission_control/jobs_helper.rb +1 -9
- data/app/views/layouts/rocket_job_mission_control/partials/_header.html.haml +3 -4
- data/app/views/rocket_job_mission_control/active_processes/index.html.haml +1 -1
- data/app/views/rocket_job_mission_control/dirmon_entries/_sidebar.html.haml +1 -1
- data/app/views/rocket_job_mission_control/jobs/edit.html.haml +34 -0
- data/app/views/rocket_job_mission_control/jobs/index.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/aborted.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/completed.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/failed.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/paused.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/queued.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/running.html.haml +2 -1
- data/app/views/rocket_job_mission_control/jobs/index_filters/scheduled.html.haml +2 -2
- data/app/views/rocket_job_mission_control/jobs/show.html.haml +21 -36
- data/app/views/rocket_job_mission_control/workers/index.html.haml +1 -1
- data/config/routes.rb +1 -1
- data/lib/rocket_job_mission_control/engine.rb +6 -0
- data/lib/rocket_job_mission_control/version.rb +1 -1
- data/spec/controllers/dirmon_entries/index_filters_controller_spec.rb +74 -0
- data/spec/controllers/jobs/index_filters_controller_spec.rb +65 -0
- data/spec/controllers/jobs_controller_spec.rb +1 -1
- data/spec/controllers/workers/index_filters_controller_spec.rb +59 -0
- data/spec/dummy/log/test.log +792 -0
- data/spec/rails_helper.rb +2 -0
- metadata +16 -8
@@ -1,6 +1,5 @@
|
|
1
1
|
module RocketJobMissionControl
|
2
2
|
class ScheduledJobsDatatable < JobsDatatable
|
3
|
-
delegate :run_now_job_path, to: :@view
|
4
3
|
private
|
5
4
|
|
6
5
|
def data
|
@@ -10,7 +9,7 @@ module RocketJobMissionControl
|
|
10
9
|
'1' => h(job.description.try(:truncate, 50)),
|
11
10
|
'2' => h(time_till_run(job)),
|
12
11
|
'3' => h(cron_schedule(job)),
|
13
|
-
'4' =>
|
12
|
+
'4' => action_buttons(job),
|
14
13
|
'DT_RowClass' => "card callout callout-#{job.state}"
|
15
14
|
}
|
16
15
|
end
|
@@ -28,9 +27,5 @@ module RocketJobMissionControl
|
|
28
27
|
def cron_schedule(job)
|
29
28
|
job.cron_schedule if job.respond_to?(:cron_schedule)
|
30
29
|
end
|
31
|
-
|
32
|
-
def run_now(job)
|
33
|
-
"#{ link_to 'Run', run_now_job_path(job), method: :patch, class: 'btn btn-default btn-xs', data: { confirm: "Run #{job.class.name} now?" } }"
|
34
|
-
end
|
35
30
|
end
|
36
31
|
end
|
@@ -20,20 +20,12 @@ module RocketJobMissionControl
|
|
20
20
|
action,
|
21
21
|
path,
|
22
22
|
method: http_method,
|
23
|
+
title: "#{action} job",
|
23
24
|
class: 'btn btn-default',
|
24
25
|
data: {confirm: t(:confirm, scope: [:job, :action], action: action)}
|
25
26
|
)
|
26
27
|
end
|
27
28
|
|
28
|
-
def view_logs_link(job)
|
29
|
-
link_to(
|
30
|
-
'view logs',
|
31
|
-
"http://localhost:5601/app/kibana#/discover?_g=(refreshInterval:(display:'5%20seconds',pause:!f,section:1,value:5000),time:(from:now-15m,mode:quick,to:now))&_a=(columns:!(level,duration,name,message,host),index:'semantic_logger-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'tags:#{job.id.to_s}')),sort:!(timestamp,desc))",
|
32
|
-
class: 'btn btn-default',
|
33
|
-
target: '_blank'
|
34
|
-
)
|
35
|
-
end
|
36
|
-
|
37
29
|
def job_states
|
38
30
|
@job_states ||= RocketJob::Job.aasm.states.map { |state| state.name.to_s }
|
39
31
|
end
|
@@ -7,9 +7,8 @@
|
|
7
7
|
%span.icon-bar
|
8
8
|
%span.icon-bar
|
9
9
|
%a.navbar-brand.brand{ href: 'http://rocketjob.io', target: '_blank'}
|
10
|
-
= image_tag('rocket_job_mission_control/rocket-icon-64x64.png'
|
11
|
-
Rocket
|
12
|
-
%span Job
|
10
|
+
= image_tag('rocket_job_mission_control/rocket-icon-64x64.png')
|
11
|
+
Rocket Job
|
13
12
|
.navbar-collapse.collapse
|
14
13
|
%ul.nav.navbar-nav
|
15
14
|
%li
|
@@ -30,4 +29,4 @@
|
|
30
29
|
Directory Monitors
|
31
30
|
%ul.nav.navbar-nav.navbar-right
|
32
31
|
%li
|
33
|
-
%a{:href =>
|
32
|
+
%a{:href => '/' } Home
|
@@ -0,0 +1,34 @@
|
|
1
|
+
.container-fluid
|
2
|
+
.lead= "Edit #{@job._type}"
|
3
|
+
|
4
|
+
- if @job.errors.present?
|
5
|
+
.alert.alert-alert
|
6
|
+
Invalid Job!
|
7
|
+
- @job.errors.messages.each_pair do |field, message|
|
8
|
+
.message
|
9
|
+
= "#{field.to_s.humanize} is not valid."
|
10
|
+
|
11
|
+
= form_for(@job, as: :job, url: job_path(@job)) do |f|
|
12
|
+
.row
|
13
|
+
.col-md-8
|
14
|
+
= f.label :description
|
15
|
+
= f.text_area :description, value: @job.description, class: 'form-control description-block'
|
16
|
+
.col-md-4
|
17
|
+
= f.label :priority
|
18
|
+
= f.number_field :priority, value: @job.priority, class: 'form-control'
|
19
|
+
.col-md-4
|
20
|
+
= f.label :log_level
|
21
|
+
= f.select :log_level, ['', 'trace', 'debug', 'info', 'warn', 'error', 'fatal'], {}, { class: 'form-control' }
|
22
|
+
- if @job.method_exists?(:max_active_workers)
|
23
|
+
.col-md-4
|
24
|
+
= f.label :max_active_workers
|
25
|
+
= f.number_field :max_active_workers, class: 'form-control'
|
26
|
+
- if @job.method_exists?(:cron_schedule)
|
27
|
+
.col-md-4
|
28
|
+
= f.label :cron_schedule
|
29
|
+
= f.text_field :cron_schedule, class: 'form-control'
|
30
|
+
.row
|
31
|
+
.col-md-12
|
32
|
+
.buttons
|
33
|
+
= f.submit 'Update', class: 'btn btn-primary'
|
34
|
+
= link_to 'Cancel', :back, class: 'btn btn-default'
|
@@ -13,12 +13,13 @@
|
|
13
13
|
%th Description
|
14
14
|
%th Created
|
15
15
|
%th Duration
|
16
|
+
%th Actions
|
16
17
|
%tbody
|
17
18
|
|
18
19
|
:javascript
|
19
20
|
jQuery(function() {
|
20
21
|
new RjmcDatatable(
|
21
22
|
$(".jobs-datatable"),
|
22
|
-
[{data: '0'}, {data: '1'}, {data: '2'}, {data: '3', orderable: false}]
|
23
|
+
[{data: '0'}, {data: '1'}, {data: '2'}, {data: '3', orderable: false}, {data: '4', orderable: false}]
|
23
24
|
);
|
24
25
|
});
|
@@ -12,11 +12,12 @@
|
|
12
12
|
%th Class
|
13
13
|
%th Description
|
14
14
|
%th Aborted
|
15
|
+
%th Actions
|
15
16
|
%tbody
|
16
17
|
|
17
18
|
:javascript
|
18
19
|
jQuery(function() {
|
19
20
|
new RjmcDatatable(
|
20
21
|
$(".jobs-datatable"),
|
21
|
-
[{data: '0'}, {data: '1'}, {data: '2'}]);
|
22
|
+
[{data: '0', width: '30%'}, {data: '1', width: '30%'}, {data: '2'}, {data: '3', orderable: false}]);
|
22
23
|
});
|
@@ -13,11 +13,12 @@
|
|
13
13
|
%th Description
|
14
14
|
%th Duration
|
15
15
|
%th Completed
|
16
|
+
%th Actions
|
16
17
|
%tbody
|
17
18
|
|
18
19
|
:javascript
|
19
20
|
jQuery(function() {
|
20
21
|
new RjmcDatatable(
|
21
22
|
$(".jobs-datatable"),
|
22
|
-
[{data: '0'}, {data: '1'}, {data: '2', orderable: false}, {data: '3'}]);
|
23
|
+
[{data: '0'}, {data: '1'}, {data: '2', orderable: false}, {data: '3'}, {data: '4', orderable: false}]);
|
23
24
|
});
|
@@ -12,11 +12,12 @@
|
|
12
12
|
%th Class
|
13
13
|
%th Description
|
14
14
|
%th Failed
|
15
|
+
%th Actions
|
15
16
|
%tbody
|
16
17
|
|
17
18
|
:javascript
|
18
19
|
jQuery(function() {
|
19
20
|
new RjmcDatatable(
|
20
21
|
$(".jobs-datatable"),
|
21
|
-
[{data: '0'}, {data: '1'}, {data: '2'}]);
|
22
|
+
[{data: '0', width: '30%'}, {data: '1', width: '30%'}, {data: '2'}, {data: '3', orderable: false}]);
|
22
23
|
});
|
@@ -12,11 +12,12 @@
|
|
12
12
|
%th Class
|
13
13
|
%th Description
|
14
14
|
%th Paused
|
15
|
+
%th Actions
|
15
16
|
%tbody
|
16
17
|
|
17
18
|
:javascript
|
18
19
|
jQuery(function() {
|
19
20
|
new RjmcDatatable(
|
20
21
|
$(".jobs-datatable"),
|
21
|
-
[{data: '0'}, {data: '1'}, {data: '2'}]);
|
22
|
+
[{data: '0', width: '30%'}, {data: '1', width: '30%'}, {data: '2'}, {data: '3', orderable: false}]);
|
22
23
|
});
|
@@ -13,11 +13,12 @@
|
|
13
13
|
%th Description
|
14
14
|
%th Priority
|
15
15
|
%th Queued For
|
16
|
+
%th Actions
|
16
17
|
%tbody
|
17
18
|
|
18
19
|
:javascript
|
19
20
|
jQuery(function() {
|
20
21
|
new RjmcDatatable(
|
21
22
|
$(".jobs-datatable"),
|
22
|
-
[{data: '0'}, {data: '1'}, {data: '2'}, {data: '3', orderable: false}]);
|
23
|
+
[{data: '0'}, {data: '1'}, {data: '2'}, {data: '3', orderable: false}, {data: '4', orderable: false}]);
|
23
24
|
});
|
@@ -14,11 +14,12 @@
|
|
14
14
|
%th Progress
|
15
15
|
%th Priority
|
16
16
|
%th Started
|
17
|
+
%th Actions
|
17
18
|
%tbody
|
18
19
|
|
19
20
|
:javascript
|
20
21
|
jQuery(function() {
|
21
22
|
new RjmcDatatable(
|
22
23
|
$(".jobs-datatable"),
|
23
|
-
[{data: '0'}, {data: '1'}, {data: '2', orderable: false}, {data: '3'}, {data: '4'}]);
|
24
|
+
[{data: '0'}, {data: '1'}, {data: '2', orderable: false}, {data: '3'}, {data: '4'}, {data: '5', orderable: false}]);
|
24
25
|
});
|
@@ -13,12 +13,12 @@
|
|
13
13
|
%th Description
|
14
14
|
%th Runs in
|
15
15
|
%th Cron Schedule
|
16
|
-
%th
|
16
|
+
%th Actions
|
17
17
|
%tbody
|
18
18
|
|
19
19
|
:javascript
|
20
20
|
jQuery(function() {
|
21
21
|
new RjmcDatatable(
|
22
22
|
$(".jobs-datatable"),
|
23
|
-
[{data: '0'}, {data: '1'}, {data: '2'
|
23
|
+
[{data: '0'}, {data: '1'}, {data: '2'}, {data: '3'}, {data: '4', orderable: false}]);
|
24
24
|
});
|
@@ -8,46 +8,31 @@
|
|
8
8
|
.right{ class: @job.state.to_s }
|
9
9
|
= @job.state
|
10
10
|
|
11
|
-
.
|
12
|
-
|
11
|
+
.btn-toolbar.job-actions.pull-right
|
12
|
+
.btn-group
|
13
|
+
- valid_events = @job.aasm.events.collect{ |e| e.name }
|
14
|
+
- if valid_events.include?(:abort)
|
15
|
+
= job_action_link('Abort', rocket_job_mission_control.abort_job_path(@job), :patch)
|
13
16
|
|
14
|
-
|
15
|
-
=
|
17
|
+
- if @job.completed? || @job.aborted?
|
18
|
+
= job_action_link('Destroy', rocket_job_mission_control.job_path(@job), :delete)
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
%i.fa.fa-caret-up
|
20
|
+
- if valid_events.include?(:fail)
|
21
|
+
= job_action_link('Fail', rocket_job_mission_control.fail_job_path(@job), :patch)
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
+
- if valid_events.include?(:pause)
|
24
|
+
= job_action_link('Pause', rocket_job_mission_control.pause_job_path(@job), :patch)
|
23
25
|
|
24
|
-
|
26
|
+
- if valid_events.include?(:resume)
|
27
|
+
= job_action_link('Resume', rocket_job_mission_control.resume_job_path(@job), :patch)
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
- if valid_events.include?(:abort)
|
29
|
-
= job_action_link('abort', rocket_job_mission_control.abort_job_path(@job), :patch)
|
29
|
+
- if valid_events.include?(:retry)
|
30
|
+
= job_action_link('Retry', rocket_job_mission_control.retry_job_path(@job), :patch)
|
30
31
|
|
31
|
-
|
32
|
-
|
32
|
+
- if @job.respond_to?(:input) && @job.input.failed_count > 0
|
33
|
+
= link_to('view errors', job_failures_path(@job), class: 'btn btn-default')
|
34
|
+
.btn-group
|
35
|
+
= link_to "Edit", edit_job_path(@job), class: "btn btn-default"
|
33
36
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
- if valid_events.include?(:pause)
|
38
|
-
= job_action_link('pause', rocket_job_mission_control.pause_job_path(@job), :patch)
|
39
|
-
|
40
|
-
- if valid_events.include?(:resume)
|
41
|
-
= job_action_link('resume', rocket_job_mission_control.resume_job_path(@job), :patch)
|
42
|
-
|
43
|
-
- if valid_events.include?(:retry)
|
44
|
-
= job_action_link('retry', rocket_job_mission_control.retry_job_path(@job), :patch)
|
45
|
-
|
46
|
-
- if @job.respond_to?(:input) && @job.input.failed_count > 0
|
47
|
-
= link_to('view errors', job_failures_path(@job), class: 'btn btn-default')
|
48
|
-
|
49
|
-
= view_logs_link(@job)
|
50
|
-
|
51
|
-
.clearfix
|
52
|
-
|
53
|
-
= render partial: 'status', locals: { job: @job }
|
37
|
+
.col-md-12
|
38
|
+
= render partial: 'status', locals: { job: @job }
|
@@ -33,5 +33,5 @@
|
|
33
33
|
jQuery(function() {
|
34
34
|
new RjmcDatatable(
|
35
35
|
$('.workers-datatable'),
|
36
|
-
[{data: '0'}, {data: '1', orderable: false}, {data: '2'
|
36
|
+
[{data: '0'}, {data: '1', orderable: false}, {data: '2'}, {data: '3'}, {data: '4', orderable: false}]);
|
37
37
|
});
|
data/config/routes.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
RocketJobMissionControl::Engine.routes.draw do
|
2
2
|
|
3
|
-
resources :jobs, only: [:index, :show, :update, :destroy] do
|
3
|
+
resources :jobs, only: [:index, :show, :update, :destroy, :edit] do
|
4
4
|
collection do
|
5
5
|
get :running, to: 'jobs/index_filters#running'
|
6
6
|
get :scheduled, to: 'jobs/index_filters#scheduled'
|
@@ -13,5 +13,11 @@ module RocketJobMissionControl
|
|
13
13
|
require 'coffee-rails'
|
14
14
|
require 'kaminari'
|
15
15
|
require 'jquery-datatables-rails'
|
16
|
+
|
17
|
+
config.to_prepare do
|
18
|
+
Rails.application.config.assets.precompile += %w(
|
19
|
+
rocket_job_mission_control/rocket-icon-64x64.png
|
20
|
+
)
|
21
|
+
end
|
16
22
|
end
|
17
23
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
class AJob < RocketJob::Job
|
4
|
+
def perform(id)
|
5
|
+
id
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module RocketJobMissionControl
|
10
|
+
module DirmonEntries
|
11
|
+
RSpec.describe IndexFiltersController do
|
12
|
+
routes { Engine.routes }
|
13
|
+
|
14
|
+
states = %w(pending enabled failed disabled)
|
15
|
+
|
16
|
+
states.each_with_index do |state, i|
|
17
|
+
describe "GET ##{state}" do
|
18
|
+
describe "with no #{state} dirmons" do
|
19
|
+
before do
|
20
|
+
get state.to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
it "succeeds" do
|
24
|
+
expect(response.status).to be(200)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'renders template' do
|
28
|
+
expect(response).to render_template(state)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns no jobs" do
|
32
|
+
expect(assigns(:dirmons).count).to eq(0)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "with #{state} dirmons" do
|
37
|
+
let(:not_state) { states[i-1] }
|
38
|
+
let!(:state_dirmon) { RocketJob::DirmonEntry.create!(state: state,
|
39
|
+
pattern: '21',
|
40
|
+
arguments: ['42'],
|
41
|
+
job_class_name: 'AJob') }
|
42
|
+
|
43
|
+
before do
|
44
|
+
RocketJob::DirmonEntry.create!(
|
45
|
+
name: 'Test',
|
46
|
+
state: not_state,
|
47
|
+
arguments: ['42'],
|
48
|
+
pattern: '21',
|
49
|
+
job_class_name: 'AJob')
|
50
|
+
get state.to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
after do
|
54
|
+
DatabaseCleaner.clean
|
55
|
+
end
|
56
|
+
|
57
|
+
it "succeeds" do
|
58
|
+
expect(response.status).to be(200)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'renders template' do
|
62
|
+
expect(response).to render_template(state)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "grabs a filtered list of dirmons" do
|
66
|
+
expect(assigns(:dirmons)).to match_array([state_dirmon])
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
module RocketJobMissionControl
|
4
|
+
module Jobs
|
5
|
+
RSpec.describe IndexFiltersController do
|
6
|
+
routes { Engine.routes }
|
7
|
+
|
8
|
+
states = %w(running paused completed aborted failed queued scheduled)
|
9
|
+
|
10
|
+
states.each_with_index do |state, i|
|
11
|
+
describe "GET ##{state}" do
|
12
|
+
describe "with no #{state} jobs" do
|
13
|
+
before do
|
14
|
+
get state.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
it "succeeds" do
|
18
|
+
expect(response.status).to be(200)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'renders template' do
|
22
|
+
expect(response).to render_template(state)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns no jobs" do
|
26
|
+
expect(assigns(:jobs).count).to eq(0)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "with #{state} jobs" do
|
31
|
+
let(:not_state) { states[i-1] }
|
32
|
+
let(:state_job) {
|
33
|
+
if state == 'scheduled'
|
34
|
+
RocketJob::Job.create(state: :queued, run_at: Date.tomorrow)
|
35
|
+
else
|
36
|
+
RocketJob::Job.create(state: state)
|
37
|
+
end
|
38
|
+
}
|
39
|
+
|
40
|
+
before do
|
41
|
+
RocketJob::Job.create(state: not_state)
|
42
|
+
get state.to_sym
|
43
|
+
end
|
44
|
+
|
45
|
+
after do
|
46
|
+
DatabaseCleaner.clean
|
47
|
+
end
|
48
|
+
|
49
|
+
it "succeeds" do
|
50
|
+
expect(response.status).to be(200)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'renders template' do
|
54
|
+
expect(response).to render_template(state)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "grabs a filtered list of rocket jobs" do
|
58
|
+
expect(assigns(:jobs)).to match_array([state_job])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|