rocketjob_mission_control 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +3 -1
- data/app/controllers/rocket_job_mission_control/active_workers_controller.rb +1 -0
- data/app/controllers/rocket_job_mission_control/application_controller.rb +15 -2
- data/app/controllers/rocket_job_mission_control/dirmon_entries_controller.rb +19 -2
- data/app/controllers/rocket_job_mission_control/jobs_controller.rb +45 -17
- data/app/controllers/rocket_job_mission_control/servers_controller.rb +18 -2
- data/app/datatables/rocket_job_mission_control/abstract_datatable.rb +0 -1
- data/app/datatables/rocket_job_mission_control/jobs_datatable.rb +8 -6
- data/app/datatables/rocket_job_mission_control/servers_datatable.rb +20 -8
- data/app/models/rocket_job_mission_control/access_policy.rb +50 -0
- data/app/models/rocket_job_mission_control/authorization.rb +22 -0
- data/app/views/rocket_job_mission_control/dirmon_entries/_sidebar.html.erb +5 -2
- data/app/views/rocket_job_mission_control/dirmon_entries/show.html.erb +8 -2
- data/app/views/rocket_job_mission_control/jobs/show.html.erb +9 -5
- data/app/views/rocket_job_mission_control/servers/index.html.erb +10 -8
- data/lib/rocket_job_mission_control/engine.rb +9 -0
- data/lib/rocket_job_mission_control/version.rb +1 -1
- data/test/controllers/rocket_job_mission_control/dirmon_entries_controller_test.rb +78 -1
- data/test/controllers/rocket_job_mission_control/jobs_controller_test.rb +52 -1
- data/test/controllers/rocket_job_mission_control/servers_controller_test.rb +58 -1
- data/test/test_helper.rb +12 -10
- data/vendor/assets/stylesheets/{fontawesome-all.min.css → fontawesome-all.min.css.erb} +1 -1
- metadata +20 -4
@@ -42,7 +42,21 @@ module RocketJobMissionControl
|
|
42
42
|
|
43
43
|
def action_links_html(server)
|
44
44
|
actions = '<div class="actions">'
|
45
|
-
|
45
|
+
events = valid_events(server)
|
46
|
+
|
47
|
+
if events.include?(:resume) && view.can?(:resume, server)
|
48
|
+
actions += "#{ link_to "resume", resume_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Resume this server?"} }"
|
49
|
+
end
|
50
|
+
|
51
|
+
if events.include?(:pause) && view.can?(:pause, server)
|
52
|
+
actions += "#{ link_to "pause", pause_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Pause this server?"} }"
|
53
|
+
end
|
54
|
+
|
55
|
+
if events.include?(:stop) && view.can?(:stop, server)
|
56
|
+
actions += "#{ link_to "stop", stop_server_path(server), method: :patch, class: 'btn btn-danger', data: {confirm: "Stop this server?"} }"
|
57
|
+
end
|
58
|
+
|
59
|
+
if server.stopping? && view.can?(:destroy, server)
|
46
60
|
actions += "Server is stopping..."
|
47
61
|
confirmation = ''
|
48
62
|
unless server.zombie?
|
@@ -50,16 +64,14 @@ module RocketJobMissionControl
|
|
50
64
|
end
|
51
65
|
confirmation << "Are you sure you want to destroy #{server.name} ?"
|
52
66
|
actions += "#{ link_to "destroy", server_path(server), method: :delete, class: 'btn btn-danger', data: {confirm: confirmation} }"
|
53
|
-
else
|
54
|
-
if server.paused?
|
55
|
-
actions += "#{ link_to "resume", resume_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Resume this server?"} }"
|
56
|
-
else
|
57
|
-
actions += "#{ link_to "pause", pause_server_path(server), method: :patch, class: 'btn btn-default', data: {confirm: "Pause this server?"} }"
|
58
|
-
end
|
59
|
-
actions += "#{ link_to "stop", stop_server_path(server), method: :patch, class: 'btn btn-danger', data: {confirm: "Stop this server?"} }"
|
60
67
|
end
|
68
|
+
|
61
69
|
actions += '</div>'
|
62
70
|
end
|
63
71
|
|
72
|
+
def valid_events(server)
|
73
|
+
server.aasm.events.collect(&:name)
|
74
|
+
end
|
75
|
+
|
64
76
|
end
|
65
77
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module RocketJobMissionControl
|
2
|
+
class AccessPolicy
|
3
|
+
include AccessGranted::Policy
|
4
|
+
|
5
|
+
def configure
|
6
|
+
# Destroy Jobs, Dirmon Entries
|
7
|
+
role :admin, {admin: true} do
|
8
|
+
can %i[create destroy], RocketJob::Job
|
9
|
+
can :destroy, RocketJob::DirmonEntry
|
10
|
+
end
|
11
|
+
|
12
|
+
# View the contents of jobs and edit the data within them.
|
13
|
+
# Including encrypted records.
|
14
|
+
role :editor, {editor: true} do
|
15
|
+
can %i[read_records update_records], RocketJob::Job
|
16
|
+
end
|
17
|
+
|
18
|
+
# Stop, Pause, Resume, Destroy (force stop) Rocket Job Servers
|
19
|
+
role :operator, {operator: true} do
|
20
|
+
can %i[stop pause resume destroy update_all], RocketJob::Server
|
21
|
+
end
|
22
|
+
|
23
|
+
# Pause, Resume, Retry, Abort, Edit Jobs
|
24
|
+
role :manager, {manager: true} do
|
25
|
+
can %i[edit pause resume retry abort fail update run_now], RocketJob::Job
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create, Destroy, Enable, Disable, Edit Dirmon Entries
|
29
|
+
role :dirmon, {dirmon: true} do
|
30
|
+
can %i[create enable disable update edit], RocketJob::DirmonEntry
|
31
|
+
end
|
32
|
+
|
33
|
+
# A User can only edit their own jobs
|
34
|
+
role :user, {user: true} do
|
35
|
+
can %i[edit pause resume retry abort update], RocketJob::Job do |job, auth|
|
36
|
+
job.respond_to?(:login) && (job.login == auth.login)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Read only access
|
41
|
+
role :view do
|
42
|
+
can :read, RocketJob::Job
|
43
|
+
can :read, RocketJob::DirmonEntry
|
44
|
+
can :read, RocketJob::Server
|
45
|
+
can :read, RocketJob::Worker
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module RocketJobMissionControl
|
2
|
+
class Authorization
|
3
|
+
ROLES = %i[admin editor operator manager dirmon user view]
|
4
|
+
attr_accessor *ROLES
|
5
|
+
attr_accessor :login
|
6
|
+
|
7
|
+
def initialize(roles: [], login: nil)
|
8
|
+
@login = login
|
9
|
+
return if roles.blank?
|
10
|
+
invalid_roles = roles - ROLES
|
11
|
+
raise(ArgumentError, "Invalid Roles Supplied: #{invalid_roles.inspect}") unless invalid_roles.empty?
|
12
|
+
|
13
|
+
roles.each { |r| inherit_less_privilege_roles(r) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def inherit_less_privilege_roles(role)
|
17
|
+
index = ROLES.index(role)
|
18
|
+
roles = ROLES[index..-1]
|
19
|
+
roles.each { |role| public_send("#{role}=", true) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,7 +1,10 @@
|
|
1
1
|
<ul class='nav hidden-xs' id='lg-menu'>
|
2
2
|
<li>
|
3
|
-
|
4
|
-
|
3
|
+
|
4
|
+
<% if can?(:create, RocketJob::DirmonEntry) %>
|
5
|
+
<%= link_to(new_dirmon_entry_path) do %>
|
6
|
+
<i class='fas fa-plus'></i> Create
|
7
|
+
<% end %>
|
5
8
|
<% end %>
|
6
9
|
|
7
10
|
<%= link_to(dirmon_entries_path) do %>
|
@@ -9,21 +9,27 @@
|
|
9
9
|
|
10
10
|
<div class='btn-toolbar job-actions pull-right'>
|
11
11
|
<div class='btn-group'>
|
12
|
-
<% if @dirmon_entry.disabled? || @dirmon_entry.pending? %>
|
12
|
+
<% if @dirmon_entry.disabled? || @dirmon_entry.pending? && can?(:enable, @dirmon_entry) %>
|
13
13
|
<%= link_to 'Enable', enable_dirmon_entry_path(@dirmon_entry.id), method: :put, class: 'btn btn-default' %>
|
14
14
|
<% end %>
|
15
15
|
|
16
|
-
<% if @dirmon_entry.enabled? %>
|
16
|
+
<% if @dirmon_entry.enabled? && can?(:disable, @dirmon_entry)%>
|
17
17
|
<%= link_to 'Disable', disable_dirmon_entry_path(@dirmon_entry.id), method: :put, class: 'btn btn-default', data: { confirm: 'Are you sure?' } %>
|
18
18
|
<% end %>
|
19
19
|
</div>
|
20
20
|
|
21
|
+
|
21
22
|
<div class='btn-group'>
|
23
|
+
<% if can?(:destroy, @dirmon_entry)%>
|
22
24
|
<%= link_to 'Destroy', dirmon_entry_path(@dirmon_entry), method: :delete, class: 'btn btn-default', data: { confirm: 'Are you sure?' } %>
|
25
|
+
<% end %>
|
23
26
|
</div>
|
24
27
|
|
28
|
+
|
25
29
|
<div class='btn-group'>
|
30
|
+
<% if can?(:edit, @dirmon_entry)%>
|
26
31
|
<%= link_to 'Edit', edit_dirmon_entry_path(@dirmon_entry), class: 'btn btn-default' %>
|
32
|
+
<% end %>
|
27
33
|
</div>
|
28
34
|
</div>
|
29
35
|
|
@@ -31,34 +31,38 @@
|
|
31
31
|
<div class='btn-group'>
|
32
32
|
<% valid_events = @job.aasm.events.collect { |e| e.name } %>
|
33
33
|
|
34
|
-
<% if valid_events.include?(:pause) %>
|
34
|
+
<% if valid_events.include?(:pause) && can?(:pause, @job)%>
|
35
35
|
<%= job_action_link('Pause', rocket_job_mission_control.pause_job_path(@job), :patch) %>
|
36
36
|
<% end %>
|
37
37
|
|
38
|
-
<% if valid_events.include?(:resume) %>
|
38
|
+
<% if valid_events.include?(:resume) && can?(:resume, @job)%>
|
39
39
|
<%= job_action_link('Resume', rocket_job_mission_control.resume_job_path(@job), :patch) %>
|
40
40
|
<% end %>
|
41
41
|
|
42
|
-
<% if valid_events.include?(:retry) %>
|
42
|
+
<% if valid_events.include?(:retry) && can?(:retry, @job)%>
|
43
43
|
<%= job_action_link('Retry', rocket_job_mission_control.retry_job_path(@job), :patch) %>
|
44
44
|
<% end %>
|
45
45
|
</div>
|
46
46
|
|
47
47
|
<div class='btn-group'>
|
48
|
-
<% if valid_events.include?(:fail) %>
|
48
|
+
<% if valid_events.include?(:fail) && can?(:fail, @job)%>
|
49
49
|
<%= job_action_link('Fail', rocket_job_mission_control.fail_job_path(@job), :patch) %>
|
50
50
|
<% end %>
|
51
51
|
|
52
|
-
<% if valid_events.include?(:abort) %>
|
52
|
+
<% if valid_events.include?(:abort) && can?(:abort, @job) %>
|
53
53
|
<%= job_action_link('Abort', rocket_job_mission_control.abort_job_path(@job), :patch) %>
|
54
54
|
<% end %>
|
55
55
|
|
56
|
+
<% if can?(:destroy, @job)%>
|
56
57
|
<%= job_action_link('Destroy', rocket_job_mission_control.job_path(@job), :delete) %>
|
58
|
+
<% end %>
|
57
59
|
</div>
|
58
60
|
|
59
61
|
<% unless @job.completed? || @job.aborted? %>
|
60
62
|
<div class='btn-group'>
|
63
|
+
<% if can?(:edit, @job)%>
|
61
64
|
<%= link_to 'Edit', edit_job_path(@job), class: 'btn btn-default' %>
|
65
|
+
<% end %>
|
62
66
|
</div>
|
63
67
|
<% end %>
|
64
68
|
</div>
|
@@ -11,14 +11,16 @@
|
|
11
11
|
<div class='server-collection-actions'>
|
12
12
|
<ol>
|
13
13
|
<div class='btn-group'>
|
14
|
-
<%
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
<% if can?(:create, RocketJob::DirmonEntry) %>
|
15
|
+
<% Array(@actions).each do |action| %>
|
16
|
+
<%= link_to(
|
17
|
+
"#{action.to_s.humanize.capitalize}",
|
18
|
+
rocket_job_mission_control.update_all_servers_path(server_action: action),
|
19
|
+
method: :patch,
|
20
|
+
data: {confirm: t(:confirm, scope: [:server, :update_all], action: action.to_s.singularize.humanize.downcase)},
|
21
|
+
class: 'btn btn-default'
|
22
|
+
) %>
|
23
|
+
<% end %>
|
22
24
|
<% end %>
|
23
25
|
</div>
|
24
26
|
</ol>
|
@@ -1,4 +1,10 @@
|
|
1
1
|
module RocketJobMissionControl
|
2
|
+
|
3
|
+
# The authorization callback
|
4
|
+
module Config
|
5
|
+
mattr_accessor :authorization_callback
|
6
|
+
end
|
7
|
+
|
2
8
|
class Engine < ::Rails::Engine
|
3
9
|
isolate_namespace RocketJobMissionControl
|
4
10
|
|
@@ -7,11 +13,14 @@ module RocketJobMissionControl
|
|
7
13
|
require 'bootstrap-sass'
|
8
14
|
require 'sass-rails'
|
9
15
|
require 'jquery-datatables-rails'
|
16
|
+
require 'access-granted'
|
10
17
|
begin
|
11
18
|
require 'rocketjob_pro'
|
12
19
|
rescue LoadError
|
13
20
|
end
|
14
21
|
|
22
|
+
config.rocket_job_mission_control = ::RocketJobMissionControl::Config
|
23
|
+
|
15
24
|
config.to_prepare do
|
16
25
|
Rails.application.config.assets.precompile += %w(
|
17
26
|
rocket_job_mission_control/rocket-icon-64x64.png
|
@@ -5,6 +5,7 @@ module RocketJobMissionControl
|
|
5
5
|
class DirmonEntriesControllerTest < ActionController::TestCase
|
6
6
|
describe DirmonEntriesController do
|
7
7
|
before do
|
8
|
+
set_role(:admin)
|
8
9
|
RocketJob::DirmonEntry.delete_all
|
9
10
|
end
|
10
11
|
|
@@ -188,7 +189,11 @@ module RocketJobMissionControl
|
|
188
189
|
end
|
189
190
|
|
190
191
|
it 'alerts the user' do
|
191
|
-
|
192
|
+
if RocketJob::VERSION.to_f >= 3.5
|
193
|
+
assert_select 'div.message', "job_class_name: [\"Job FakeAndBadJob must be defined and inherit from RocketJob::Job\"]"
|
194
|
+
else
|
195
|
+
assert_select 'div.message', "job_class_name: [\"job_class_name must be defined and must be derived from RocketJob::Job\"]"
|
196
|
+
end
|
192
197
|
end
|
193
198
|
end
|
194
199
|
end
|
@@ -436,6 +441,78 @@ module RocketJobMissionControl
|
|
436
441
|
end
|
437
442
|
end
|
438
443
|
end
|
444
|
+
|
445
|
+
describe 'role base authentication control' do
|
446
|
+
let(:dirmon_params) do
|
447
|
+
{
|
448
|
+
name: 'Test',
|
449
|
+
pattern: '/files/*',
|
450
|
+
job_class_name: job_class_name,
|
451
|
+
properties: {description: '', priority: '42'}
|
452
|
+
}
|
453
|
+
end
|
454
|
+
|
455
|
+
%i[index disabled enabled failed pending].each do |method|
|
456
|
+
it "#{method} has read only default access" do
|
457
|
+
get method, format: :json
|
458
|
+
assert_response :success
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# PATCH
|
463
|
+
%i[enable disable update].each do |method|
|
464
|
+
describe method.to_s do
|
465
|
+
%i[admin editor operator manager dirmon].each do |role|
|
466
|
+
it "allows role #{role} to access #{method}" do
|
467
|
+
set_role(role)
|
468
|
+
params = {id: existing_dirmon_entry.id}
|
469
|
+
params = {params: params} if Rails.version.to_i >= 5
|
470
|
+
patch :enable, params
|
471
|
+
|
472
|
+
assert_response(:redirect)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# POST
|
479
|
+
%i[admin editor operator manager dirmon].each do |role|
|
480
|
+
it 'creates dirmon entry' do
|
481
|
+
set_role(role)
|
482
|
+
params = {rocket_job_dirmon_entry: dirmon_params}
|
483
|
+
params = {params: params} if Rails.version.to_i >= 5
|
484
|
+
post :create, params
|
485
|
+
|
486
|
+
assert_response(:redirect)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
it 'deletes dirmon entry' do
|
491
|
+
set_role(:admin)
|
492
|
+
params = {id: existing_dirmon_entry.id}
|
493
|
+
params = {params: params} if Rails.version.to_i >= 5
|
494
|
+
delete :destroy, params
|
495
|
+
|
496
|
+
assert_response(:redirect)
|
497
|
+
end
|
498
|
+
|
499
|
+
%i[editor operator manager dirmon].each do |role|
|
500
|
+
it "raises authentication error for #{role}" do
|
501
|
+
set_role(role)
|
502
|
+
assert_raises AccessGranted::AccessDenied do
|
503
|
+
params = {id: existing_dirmon_entry.id}
|
504
|
+
params = {params: params} if Rails.version.to_i >= 5
|
505
|
+
delete :destroy, params
|
506
|
+
end
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def set_role(r)
|
513
|
+
Config.authorization_callback = -> do
|
514
|
+
{roles: [r]}
|
515
|
+
end
|
439
516
|
end
|
440
517
|
end
|
441
518
|
end
|
@@ -14,6 +14,7 @@ module RocketJobMissionControl
|
|
14
14
|
|
15
15
|
describe JobsController do
|
16
16
|
before do
|
17
|
+
set_role(:admin)
|
17
18
|
RocketJob::Job.delete_all
|
18
19
|
end
|
19
20
|
|
@@ -52,7 +53,7 @@ module RocketJobMissionControl
|
|
52
53
|
|
53
54
|
let :one_job_for_every_state do
|
54
55
|
job_states.collect do |state|
|
55
|
-
RocketJob::Jobs::SimpleJob.create!(state: state, worker_name: 'worker')
|
56
|
+
RocketJob::Jobs::SimpleJob.create!(state: state, worker_name: 'worker', started_at: (Time.now - 0.5))
|
56
57
|
end
|
57
58
|
end
|
58
59
|
|
@@ -315,6 +316,7 @@ module RocketJobMissionControl
|
|
315
316
|
|
316
317
|
describe "with #{state} server" do
|
317
318
|
before do
|
319
|
+
set_role(:admin)
|
318
320
|
one_job_for_every_state
|
319
321
|
get state, format: :json
|
320
322
|
end
|
@@ -393,6 +395,55 @@ module RocketJobMissionControl
|
|
393
395
|
end
|
394
396
|
end
|
395
397
|
|
398
|
+
describe 'role base authentication control' do
|
399
|
+
%i[index aborted completed failed paused queued running scheduled].each do |method|
|
400
|
+
it "#{method} has read access as default" do
|
401
|
+
get method, format: :json
|
402
|
+
assert_response :success
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
%i[update abort retry pause resume run_now fail].each do |method|
|
407
|
+
describe "#{method}" do
|
408
|
+
before do
|
409
|
+
case method
|
410
|
+
when :pause, :fail, :abort
|
411
|
+
pausable_job.start!
|
412
|
+
when :resume
|
413
|
+
pausable_job.pause!
|
414
|
+
when :retry, :exception
|
415
|
+
pausable_job.fail!
|
416
|
+
end
|
417
|
+
|
418
|
+
@params = {id: pausable_job.id, job: {id: pausable_job.id, priority: pausable_job.priority}}
|
419
|
+
@params = {params: @params} if Rails.version.to_i >= 5
|
420
|
+
end
|
421
|
+
|
422
|
+
%i[admin editor operator manager].each do |role|
|
423
|
+
it "redirects with #{method} method and role #{role}" do
|
424
|
+
set_role(role)
|
425
|
+
patch method, @params
|
426
|
+
assert_response(:redirect)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
%i[dirmon user].each do |role|
|
431
|
+
it "raises authentication error for #{role}" do
|
432
|
+
set_role(role)
|
433
|
+
assert_raises AccessGranted::AccessDenied do
|
434
|
+
patch method, @params
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def set_role(r)
|
444
|
+
Config.authorization_callback = -> do
|
445
|
+
{roles: [r]}
|
446
|
+
end
|
396
447
|
end
|
397
448
|
end
|
398
449
|
end
|