rocketjob_mission_control 3.1.0 → 3.2.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.
- 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
|