foreman-tasks 0.17.0 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/foreman_tasks/tasks.css.scss +3 -3
  3. data/app/controllers/foreman_tasks/api/tasks_controller.rb +50 -30
  4. data/app/controllers/foreman_tasks/tasks_controller.rb +8 -17
  5. data/app/models/foreman_tasks/task.rb +6 -4
  6. data/app/models/foreman_tasks/task/search.rb +0 -25
  7. data/app/models/setting/foreman_tasks.rb +19 -23
  8. data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -0
  9. data/app/views/foreman_tasks/layouts/react.html.erb +4 -1
  10. data/config/routes.rb +17 -3
  11. data/db/migrate/20180927120509_add_user_id.foreman_tasks.rb +4 -2
  12. data/lib/foreman_tasks/dynflow.rb +1 -1
  13. data/lib/foreman_tasks/dynflow/console_authorizer.rb +13 -2
  14. data/lib/foreman_tasks/engine.rb +1 -1
  15. data/lib/foreman_tasks/version.rb +1 -1
  16. data/package.json +1 -0
  17. data/test/controllers/tasks_controller_test.rb +35 -4
  18. data/test/unit/dynflow_console_authorizer_test.rb +1 -1
  19. data/test/unit/otp_manager_test.rb +24 -17
  20. data/test/unit/task_test.rb +48 -2
  21. data/webpack/ForemanTasks/Components/TaskDetails/Components/Task.js +29 -16
  22. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskHelper.js +3 -17
  23. data/webpack/ForemanTasks/Components/TaskDetails/Components/TaskInfo.js +18 -5
  24. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/TaskHelper.test.js +1 -56
  25. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/Task.test.js.snap +24 -44
  26. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskInfo.test.js.snap +22 -10
  27. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsActions.js +6 -0
  28. data/webpack/ForemanTasks/Components/TaskDetails/TaskDetailsSelectors.js +1 -1
  29. data/webpack/ForemanTasks/Components/TaskDetails/__tests__/__snapshots__/TaskDetails.test.js.snap +2 -0
  30. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboard.js +6 -3
  31. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardActions.js +2 -3
  32. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardHelper.js +10 -41
  33. data/webpack/ForemanTasks/Components/TasksDashboard/TasksDashboardReducer.js +0 -1
  34. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/TasksDashboard.test.js +1 -1
  35. data/webpack/ForemanTasks/Components/TasksDashboard/__tests__/__snapshots__/TasksDashboardReducer.test.js.snap +1 -6
  36. data/webpack/ForemanTasks/Components/TasksTable/SubTasksPage.js +30 -0
  37. data/webpack/ForemanTasks/Components/TasksTable/TaskTableFormmatters.js +53 -0
  38. data/webpack/ForemanTasks/Components/TasksTable/TasksIndexPage.js +10 -0
  39. data/webpack/ForemanTasks/Components/TasksTable/TasksTable.js +119 -0
  40. data/webpack/ForemanTasks/Components/TasksTable/TasksTableActions.js +67 -0
  41. data/webpack/ForemanTasks/Components/TasksTable/TasksTableConstants.js +5 -0
  42. data/webpack/ForemanTasks/Components/TasksTable/TasksTableHelpers.js +64 -0
  43. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.js +63 -0
  44. data/webpack/ForemanTasks/Components/TasksTable/TasksTablePage.scss +29 -0
  45. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +35 -0
  46. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSchema.js +67 -0
  47. data/webpack/ForemanTasks/Components/TasksTable/TasksTableSelectors.js +39 -0
  48. data/webpack/ForemanTasks/Components/TasksTable/__tests__/SubTasksPage.test.js +20 -0
  49. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksIndexPage.test.js +12 -0
  50. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.fixtures.js +42 -0
  51. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTable.test.js +9 -0
  52. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableActions.test.js +48 -0
  53. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableHelpers.test.js +28 -0
  54. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTablePage.test.js +26 -0
  55. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +37 -0
  56. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/SubTasksPage.test.js.snap +49 -0
  57. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksIndexPage.test.js.snap +42 -0
  58. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTable.test.js.snap +72 -0
  59. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableActions.test.js.snap +115 -0
  60. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTablePage.test.js.snap +194 -0
  61. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +32 -0
  62. data/webpack/ForemanTasks/Components/TasksTable/index.js +32 -0
  63. data/webpack/ForemanTasks/Components/common/ActionButtons/ActionButton.js +39 -0
  64. data/webpack/ForemanTasks/Components/common/ActionButtons/ActionButton.test.js +45 -0
  65. data/webpack/ForemanTasks/Components/common/ActionButtons/CancelButton.js +23 -0
  66. data/webpack/ForemanTasks/Components/common/ActionButtons/CancelButton.test.js +27 -0
  67. data/webpack/ForemanTasks/Components/common/ActionButtons/ResumeButton.js +23 -0
  68. data/webpack/ForemanTasks/Components/common/ActionButtons/ResumeButton.test.js +27 -0
  69. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/ActionButton.test.js.snap +28 -0
  70. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/CancelButton.test.js.snap +15 -0
  71. data/webpack/ForemanTasks/Components/common/ActionButtons/__snapshots__/ResumeButton.test.js.snap +15 -0
  72. data/webpack/ForemanTasks/ForemanTasks.js +1 -17
  73. data/webpack/ForemanTasks/ForemanTasksReducers.js +2 -0
  74. data/webpack/ForemanTasks/Routes/ForemanTasksRouter.test.js +5 -1
  75. data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.js +9 -3
  76. data/webpack/ForemanTasks/Routes/ForemanTasksRoutes.test.js +1 -1
  77. data/webpack/ForemanTasks/Routes/ShowTask/__tests__/ShowTask.test.js +5 -1
  78. data/webpack/ForemanTasks/Routes/__snapshots__/ForemanTasksRoutes.test.js.snap +18 -2
  79. data/webpack/ForemanTasks/__snapshots__/ForemanTasks.test.js.snap +1 -54
  80. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  81. data/webpack/__mocks__/foremanReact/common/urlHelpers.js +1 -0
  82. data/webpack/__mocks__/foremanReact/components/Pagination/PaginationWrapper.js +2 -0
  83. data/webpack/__mocks__/foremanReact/components/common/MessageBox.js +4 -0
  84. data/webpack/__mocks__/foremanReact/components/common/dates/LongDateTime.js +5 -0
  85. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +3 -0
  86. data/webpack/__mocks__/foremanReact/components/common/table.js +4 -0
  87. data/webpack/__mocks__/foremanReact/components/common/table/actionsHelpers/actionTypeCreator.js +7 -0
  88. data/webpack/__mocks__/foremanReact/constants.js +24 -0
  89. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
  90. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  91. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/components/ExportButton/ExportButton.js +5 -0
  92. data/webpack/index.js +5 -0
  93. metadata +49 -9
  94. data/app/views/foreman_tasks/tasks/index.html.erb +0 -46
  95. data/webpack/ForemanTasks/Components/TaskDetails/Components/__tests__/__snapshots__/TaskHelper.test.js.snap +0 -37
  96. data/webpack/ForemanTasks/Routes/IndexTasks/IndexTasks.js +0 -10
  97. data/webpack/ForemanTasks/Routes/IndexTasks/__tests__/IndexTasks.test.js +0 -10
  98. data/webpack/ForemanTasks/Routes/IndexTasks/__tests__/__snapshots__/IndexTasks.test.js.snap +0 -12
  99. data/webpack/ForemanTasks/Routes/IndexTasks/index.js +0 -1
  100. data/webpack/ForemanTasks/Routes/IndexTasks/indexTasks.scss +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 289b729c99fef5c4047a025dd9deabfe4421ea2f5c457922f22ebababf0bf4e9
4
- data.tar.gz: 4c571c09518c2b3ce576b4cf31be1b62e5cae4c15bc8d8401a4ce8886bd60e4f
3
+ metadata.gz: bfc833d42aeafa9f146457f33ffe29db5f4ddc374a372511a384f05d6932754e
4
+ data.tar.gz: eda50a6c4a0e2c1257fd72c5764c5eed7df097beb230579304ccf1943a472477
5
5
  SHA512:
6
- metadata.gz: 5b751006f3adf4e24870a916e4ba5e13a67234d10bb977e1d66df84c626059c544179b4042d631acd50481b831eb38bfe20c6739cb3c9e1efda83e35805e2b34
7
- data.tar.gz: cb353429cb48a625176818a3625be9da6e0c7f4deede573c2e614364d4b20d263b2476f614fa2b45813131b1cfbf113e5d736ceb7522dbd2b8dd889f02ff920b
6
+ metadata.gz: cbbb4c98796edf5e91c2f7b3fd731a40162c6865b94e5c262f61865a5f4b18f939e38a76920756b9f6919501a72ba47b02653ba27fec54ad035e461d33df60bd
7
+ data.tar.gz: 868cbea935b39e9dd74ff7a616983e1e68d89355716134f493fd29485280c0cc2e4f01ea015358c1285ac82f3ece89c9234c5cac5a73add22edb6213cfbacd43
@@ -1,7 +1,7 @@
1
1
  .param-name {
2
- display: inline-block;
3
- width: 10em;
4
- }
2
+ display: inline-block;
3
+ width: 10em;
4
+ }
5
5
 
6
6
  .task-details pre {
7
7
  white-space: pre;
@@ -31,6 +31,15 @@ module ForemanTasks
31
31
  param :id, :identifier, desc: 'UUID of the task'
32
32
  def details; end
33
33
 
34
+ api :GET, '/tasks/:id/sub_tasks', 'Show sub_tasks details'
35
+ param :id, :identifier, desc: 'UUID of the task'
36
+ def sub_tasks
37
+ parent_task = resource_scope.find(params[:id])
38
+ filtered_scope = parent_task.sub_tasks
39
+ action_name = { "action_name" => parent_task.action }
40
+ render :json => action_name.merge(tasks_list(filtered_scope))
41
+ end
42
+
34
43
  api :POST, '/tasks/bulk_search', 'List dynflow tasks for uuids'
35
44
  param :searches, Array, :desc => 'List of uuids to fetch info about' do
36
45
  param :search_id, String, :desc => <<-DESC
@@ -121,35 +130,8 @@ module ForemanTasks
121
130
  param :order, String, :desc => N_('How to order the sorted results (e.g. ASC for ascending)')
122
131
  end
123
132
  def index
124
- total = resource_scope.count
125
- subtotal = resource_scope.search_for(params[:search]).select('DISTINCT foreman_tasks_tasks.id').count
126
-
127
- scope = resource_scope.search_for(params[:search]).select('DISTINCT foreman_tasks_tasks.*')
128
-
129
- ordering_params = {
130
- sort_by: params[:sort_by] || 'started_at',
131
- sort_order: params[:sort_order] || 'DESC'
132
- }
133
- scope = ordering_scope(scope, ordering_params)
134
-
135
- pagination_params = {
136
- page: params[:page] || 1,
137
- per_page: params[:per_page] || Setting[:entries_per_page] || 20
138
- }
139
- scope = pagination_scope(scope, pagination_params)
140
- results = scope.map { |task| task_hash(task) }
141
-
142
- render :json => {
143
- total: total,
144
- subtotal: subtotal,
145
- page: pagination_params[:page],
146
- per_page: pagination_params[:per_page],
147
- sort: {
148
- by: ordering_params[:sort_by],
149
- order: ordering_params[:sort_order]
150
- },
151
- results: results
152
- }
133
+ filtered_scope = DashboardTableFilter.new(resource_scope, params).scope
134
+ render :json => tasks_list(filtered_scope)
153
135
  end
154
136
 
155
137
  def_param_group :callback_target do
@@ -255,6 +237,7 @@ module ForemanTasks
255
237
  def ordering_scope(scope, ordering_params)
256
238
  sort_by = ordering_params[:sort_by] || 'started_at'
257
239
  sort_order = ordering_params[:sort_order] || 'DESC'
240
+ scope = scope.select("*, coalesce(ended_at, current_timestamp) - coalesce(coalesce(started_at, ended_at), current_timestamp) as duration")
258
241
  scope.order("#{sort_by} #{sort_order}")
259
242
  end
260
243
 
@@ -278,9 +261,13 @@ module ForemanTasks
278
261
  @resource_scope ||= ForemanTasks::Task.authorized("#{action_permission}_foreman_tasks")
279
262
  end
280
263
 
264
+ def resource_class
265
+ @resource_class ||= ForemanTasks::Task
266
+ end
267
+
281
268
  def action_permission
282
269
  case params[:action]
283
- when 'bulk_search', 'summary', 'details'
270
+ when 'bulk_search', 'summary', 'details', 'sub_tasks'
284
271
  :view
285
272
  when 'bulk_resume'
286
273
  :edit
@@ -288,6 +275,39 @@ module ForemanTasks
288
275
  super
289
276
  end
290
277
  end
278
+
279
+ def tasks_list(filtered_scope)
280
+ total = resource_scope.count
281
+
282
+ search_scope = filtered_scope.search_for(params[:search])
283
+ subtotal = search_scope.select('DISTINCT foreman_tasks_tasks.id').count
284
+ filtered_scope = search_scope.select('DISTINCT foreman_tasks_tasks.*')
285
+
286
+ ordering_params = {
287
+ sort_by: params[:sort_by] || 'started_at',
288
+ sort_order: params[:sort_order] || 'DESC'
289
+ }
290
+ filtered_scope = ordering_scope(filtered_scope, ordering_params)
291
+
292
+ pagination_params = {
293
+ page: params[:page] || 1,
294
+ per_page: params[:per_page] || Setting[:entries_per_page] || 20
295
+ }
296
+ filtered_scope = pagination_scope(filtered_scope, pagination_params)
297
+ results = filtered_scope.map { |task| task_hash(task) }
298
+
299
+ {
300
+ total: total,
301
+ subtotal: subtotal,
302
+ page: pagination_params[:page],
303
+ per_page: pagination_params[:per_page],
304
+ sort: {
305
+ by: ordering_params[:sort_by],
306
+ order: ordering_params[:sort_order]
307
+ },
308
+ results: results
309
+ }
310
+ end
291
311
  end
292
312
  end
293
313
  end
@@ -16,7 +16,8 @@ module ForemanTasks
16
16
  end
17
17
 
18
18
  def summary
19
- render json: Task::Summarizer.new(resource_base, params[:recent_timeframe].to_i).summary
19
+ scope = resource_base.search_for(current_taxonomy_search).select(:id)
20
+ render json: Task::Summarizer.new(Task.where(:id => scope), params[:recent_timeframe].to_i).summary
20
21
  end
21
22
 
22
23
  def sub_tasks
@@ -35,11 +36,10 @@ module ForemanTasks
35
36
  def cancel
36
37
  task = find_dynflow_task
37
38
  if task.cancel
38
- flash[:info] = _('Trying to cancel the task')
39
+ render json: { statusText: 'OK' }
39
40
  else
40
- flash[:warning] = _('The task cannot be cancelled at the moment.')
41
+ render json: {}, status: :bad_request
41
42
  end
42
- redirect_back(:fallback_location => foreman_tasks_task_path(task))
43
43
  end
44
44
 
45
45
  def abort
@@ -56,11 +56,10 @@ module ForemanTasks
56
56
  task = find_dynflow_task
57
57
  if task.resumable?
58
58
  ForemanTasks.dynflow.world.execute(task.execution_plan.id)
59
- flash[:info] = _('The execution was resumed.')
59
+ render json: { statusText: 'OK' }
60
60
  else
61
- flash[:warning] = _('The execution has to be resumable.')
61
+ render json: {}, status: :bad_request
62
62
  end
63
- redirect_back(:fallback_location => foreman_tasks_task_path(task))
64
63
  end
65
64
 
66
65
  def unlock
@@ -95,16 +94,8 @@ module ForemanTasks
95
94
  private
96
95
 
97
96
  def respond_with_tasks(scope)
98
- respond_to do |format|
99
- format.html do
100
- @tasks = filter(scope)
101
- render :index, layout: !request.xhr?
102
- end
103
- format.csv do
104
- @tasks = filter(scope, paginate: false)
105
- csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'User'])
106
- end
107
- end
97
+ @tasks = filter(scope, paginate: false)
98
+ csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'User'])
108
99
  end
109
100
 
110
101
  def restrict_dangerous_actions
@@ -49,10 +49,12 @@ module ForemanTasks
49
49
  scoped_search :on => :user_id,
50
50
  :complete_value => true,
51
51
  :rename => 'user.id',
52
- :validator => ->(value) { ScopedSearch::Validators::INTEGER.call(value) || value == 'current_user' },
53
- :aliases => ['owner.id'], :ext_method => :search_by_owner, :only_explicit => true
54
- scoped_search :relation => :user, :on => :login, :rename => 'user.login', :complete_value => true, :aliases => ['owner.login', 'user'], :ext_method => :search_by_owner, :only_explicit => true
55
- scoped_search :relation => :user, :on => :firstname, :rename => 'user.firstname', :complete_value => true, :aliases => ['owner.firstname'], :ext_method => :search_by_owner, :only_explicit => true
52
+ :validator => ->(value) { ScopedSearch::Validators::INTEGER.call(value) },
53
+ :value_translation => ->(value) { value == 'current_user' ? User.current.id : value },
54
+ :special_values => %w[current_user],
55
+ :aliases => ['owner.id'], :only_explicit => true
56
+ scoped_search :relation => :user, :on => :login, :complete_value => true, :aliases => ['owner.login', 'user'], :only_explicit => true
57
+ scoped_search :relation => :user, :on => :firstname, :rename => 'user.firstname', :complete_value => true, :aliases => ['owner.firstname'], :only_explicit => true
56
58
  scoped_search :relation => :task_groups, :on => :id, :complete_value => true, :rename => 'task_group.id', :validator => ScopedSearch::Validators::INTEGER
57
59
 
58
60
  scope :active, -> { where('foreman_tasks_tasks.state != ?', :stopped) }
@@ -22,31 +22,6 @@ module ForemanTasks
22
22
  sql = "foreman_tasks_locks_taxonomy#{uniq_suffix}.resource_id #{operator} ? OR foreman_tasks_locks_taxonomy#{uniq_suffix}.resource_id IS NULL"
23
23
  { :conditions => sanitize_sql_for_conditions([sql, value]), :joins => joins }
24
24
  end
25
-
26
- def search_by_owner(key, operator, value)
27
- return { :conditions => '0 = 1' } if value == 'current_user' && User.current.nil?
28
-
29
- key = 'owners.login' if key == 'user'
30
- # using uniq suffix to avoid colisions when searching by two different owners via ScopedSearch
31
- uniq_suffix = SecureRandom.hex(3)
32
- key_name = connection.quote_column_name(key.sub(/^.*\./, ''))
33
- value.sub!('*', '%%')
34
- condition = if key.blank?
35
- sanitize_sql_for_conditions(["users_#{uniq_suffix}.login #{operator} ? or users_#{uniq_suffix}.firstname #{operator} ? ", value, value])
36
- elsif key =~ /\.id\Z/
37
- value = User.current.id if value == 'current_user'
38
- sanitize_sql_for_conditions(["foreman_tasks_tasks.user_id #{operator} ?", value])
39
- else
40
- placeholder, value = operator == 'IN' ? ['(?)', value.split(',').map(&:strip)] : ['?', value]
41
- sanitize_sql_for_conditions(["users_#{uniq_suffix}.#{key_name} #{operator} #{placeholder}", value])
42
- end
43
- { :conditions => condition, :joins => joins_for_user_search(key, uniq_suffix) }
44
- end
45
-
46
- def joins_for_user_search(key, uniq_suffix)
47
- return '' if key =~ /\.id\Z/
48
- "INNER JOIN users AS users_#{uniq_suffix} ON users_#{uniq_suffix}.id = foreman_tasks_tasks.user_id"
49
- end
50
25
  end
51
26
  end
52
27
  end
@@ -1,28 +1,24 @@
1
1
  class Setting::ForemanTasks < Setting
2
- def self.load_defaults
3
- # Check the table exists
4
- return unless super
2
+ def self.default_settings
3
+ [
4
+ set('foreman_tasks_sync_task_timeout', N_('Number of seconds to wait for synchronous task to finish.'), 120),
5
+ set('dynflow_allow_dangerous_actions', N_('Allow unlocking actions which can have dangerous consequences.'), false),
6
+ set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true),
7
+ set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true),
8
+ set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4),
9
+ set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15),
10
+ set('foreman_tasks_proxy_batch_trigger', N_('Allow triggering tasks on the smart proxy in batches'), true),
11
+ set('foreman_tasks_proxy_batch_size', N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'), 100),
12
+ set('foreman_tasks_troubleshooting_url',
13
+ N_('Url pointing to the task troubleshooting documentation. '\
14
+ 'It should contain %{label} placeholder, that will be replaced with normalized task label '\
15
+ '(restricted to only alphanumeric characters)). %{version} placeholder is also available.'),
16
+ nil)
17
+ ]
18
+ end
5
19
 
20
+ def self.load_defaults
6
21
  Setting::BLANK_ATTRS.push('foreman_tasks_troubleshooting_url')
7
-
8
- transaction do
9
- [
10
- set('foreman_tasks_sync_task_timeout', N_('Number of seconds to wait for synchronous task to finish.'), 120),
11
- set('dynflow_allow_dangerous_actions', N_('Allow unlocking actions which can have dangerous consequences.'), false),
12
- set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true),
13
- set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true),
14
- set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4),
15
- set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15),
16
- set('foreman_tasks_proxy_batch_trigger', N_('Allow triggering tasks on the smart proxy in batches'), true),
17
- set('foreman_tasks_proxy_batch_size', N_('Number of tasks which should be sent to the smart proxy in one request, if foreman_tasks_proxy_batch_trigger is enabled'), 100),
18
- set('foreman_tasks_troubleshooting_url',
19
- N_('Url pointing to the task troubleshooting documentation. '\
20
- 'It should contain %{label} placeholder, that will be replaced with normalized task label '\
21
- '(restricted to only alphanumeric characters)). %{version} placeholder is also available.'),
22
- nil)
23
- ].each { |s| create! s.update(:category => 'Setting::ForemanTasks') }
24
- end
25
-
26
- true
22
+ super
27
23
  end
28
24
  end
@@ -3,3 +3,4 @@ object @task if @task
3
3
  attributes :id, :label, :pending, :action
4
4
  attributes :username, :started_at, :ended_at, :state, :result, :progress
5
5
  attributes :input, :output, :humanized, :cli_example
6
+ node(:available_actions) { |t| {cancellable: t.execution_plan&.cancellable?, resumable: t.resumable? }}
@@ -1,6 +1,9 @@
1
1
  <% content_for(:javascripts) do %>
2
2
  <%= webpacked_plugins_js_for :'foreman-tasks' %>
3
3
  <% end %>
4
+ <% content_for(:stylesheets) do %>
5
+ <%= webpacked_plugins_css_for :'foreman-tasks' %>
6
+ <% end %>
4
7
 
5
8
  <% content_for(:content) do %>
6
9
  <%= notifications %>
@@ -9,4 +12,4 @@
9
12
  <div id="foremanTasksReactRoot"></div>
10
13
  <% end %>
11
14
  <%= render file: "layouts/base" %>
12
- <%= mount_react_component('ForemanTasks', '#foremanTasksReactRoot',{ :flatten_data => true }) %>
15
+ <%= mount_react_component('ForemanTasks', '#foremanTasksReactRoot') %>
data/config/routes.rb CHANGED
@@ -8,13 +8,12 @@ Foreman::Application.routes.draw do
8
8
  end
9
9
  end
10
10
 
11
- resources :tasks, :only => [:index, :show] do
11
+ resources :tasks, :only => [:show] do
12
12
  collection do
13
13
  get 'auto_complete_search'
14
14
  get '/summary/:recent_timeframe', action: 'summary'
15
15
  end
16
16
  member do
17
- get :sub_tasks
18
17
  post :abort
19
18
  post :cancel
20
19
  post :resume
@@ -23,8 +22,15 @@ Foreman::Application.routes.draw do
23
22
  post :cancel_step
24
23
  end
25
24
  end
25
+ resources :tasks, :only => [:show], constraints: ->(req) { req.format == :csv } do
26
+ member do
27
+ get :sub_tasks
28
+ end
29
+ end
30
+ resources :tasks, :only => [:index], constraints: ->(req) { req.format == :csv }
26
31
 
27
- match '/ex_tasks' => 'react#index', :via => [:get]
32
+ match '/tasks' => 'react#index', :via => [:get]
33
+ match '/tasks/:id/sub_tasks' => 'react#index', :via => [:get]
28
34
  match '/ex_tasks/:id' => 'react#index', :via => [:get]
29
35
 
30
36
  namespace :api do
@@ -37,6 +43,7 @@ Foreman::Application.routes.draw do
37
43
  resources :tasks, :only => [:show, :index] do
38
44
  member do
39
45
  get :details
46
+ get :sub_tasks
40
47
  end
41
48
  collection do
42
49
  post :bulk_search
@@ -50,6 +57,13 @@ Foreman::Application.routes.draw do
50
57
  if ForemanTasks.dynflow.required?
51
58
  require 'dynflow/web'
52
59
  mount ForemanTasks.dynflow.web_console => '/dynflow'
60
+ if defined? ::Sidekiq
61
+ require 'sidekiq/web'
62
+ redis_url = SETTINGS.dig(:dynflow, :redis_url)
63
+ Sidekiq.redis = { url: redis_url }
64
+ Sidekiq::Web.set :sessions, false
65
+ mount Sidekiq::Web => '/sidekiq', :constraints => ForemanTasks::Dynflow::SidekiqConsoleConstraint.new
66
+ end
53
67
  end
54
68
  end
55
69
  end
@@ -5,8 +5,10 @@ class AddUserId < ActiveRecord::Migration[5.0]
5
5
  return if User.unscoped.find_by(:login => User::ANONYMOUS_ADMIN).nil?
6
6
  User.as_anonymous_admin do
7
7
  user_locks.select(:resource_id).distinct.pluck(:resource_id).each do |owner_id|
8
- tasks = ForemanTasks::Task.joins(:locks).where(:locks => user_locks.where(:resource_id => owner_id))
9
- tasks.update_all(:user_id => owner_id)
8
+ if User.exists?(:id => owner_id)
9
+ tasks = ForemanTasks::Task.joins(:locks).where(:locks => user_locks.where(:resource_id => owner_id))
10
+ tasks.update_all(:user_id => owner_id)
11
+ end
10
12
  user_locks.where(:resource_id => owner_id).delete_all
11
13
  end
12
14
  end
@@ -9,7 +9,7 @@ module ForemanTasks
9
9
  ::Dynflow::Web.setup do
10
10
  before do
11
11
  if !Setting[:dynflow_enable_console] ||
12
- (Setting[:dynflow_console_require_auth] && !ConsoleAuthorizer.new(env).allow?)
12
+ (Setting[:dynflow_console_require_auth] && !ConsoleAuthorizer.from_env(env).allow?)
13
13
  halt 403, 'Access forbidden'
14
14
  end
15
15
  end
@@ -1,7 +1,18 @@
1
1
  module ForemanTasks
2
+ class Dynflow::SidekiqConsoleConstraint
3
+ def matches?(request)
4
+ Setting[:dynflow_enable_console] &&
5
+ (!Setting[:dynflow_console_require_auth] || Dynflow::ConsoleAuthorizer.new(request).allow?)
6
+ end
7
+ end
8
+
2
9
  class Dynflow::ConsoleAuthorizer
3
- def initialize(env)
4
- @rack_request = Rack::Request.new(env)
10
+ def self.from_env(env)
11
+ new(Rack::Request.new(env))
12
+ end
13
+
14
+ def initialize(request)
15
+ @rack_request = request
5
16
  @user_id = @rack_request.session[:user]
6
17
  @expires_at = @rack_request.session[:expires_at]
7
18
  @user = User.unscoped.where(:id => @user_id).first unless session_expired?
@@ -58,7 +58,7 @@ module ForemanTasks
58
58
  security_block :foreman_tasks do |_map|
59
59
  permission :view_foreman_tasks, { :'foreman_tasks/tasks' => [:auto_complete_search, :sub_tasks, :index, :summary, :show],
60
60
  :'foreman_tasks/react' => [:index],
61
- :'foreman_tasks/api/tasks' => [:bulk_search, :show, :index, :summary, :details] }, :resource_type => ForemanTasks::Task.name
61
+ :'foreman_tasks/api/tasks' => [:bulk_search, :show, :index, :summary, :details, :sub_tasks] }, :resource_type => ForemanTasks::Task.name
62
62
  permission :edit_foreman_tasks, { :'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step, :cancel, :abort],
63
63
  :'foreman_tasks/api/tasks' => [:bulk_resume] }, :resource_type => ForemanTasks::Task.name
64
64
 
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '0.17.0'.freeze
2
+ VERSION = '0.17.1'.freeze
3
3
  end
data/package.json CHANGED
@@ -23,6 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@theforeman/vendor": "^1.4.0",
26
+ "c3": "^0.4.11",
26
27
  "humanize-duration": "^3.20.1",
27
28
  "react-intl": "^2.8.0"
28
29
  },
@@ -3,10 +3,6 @@ require 'foreman_tasks_test_helper'
3
3
  module ForemanTasks
4
4
  class TasksControllerTest < ActionController::TestCase
5
5
  describe ForemanTasks::TasksController do
6
- basic_index_test('tasks')
7
- basic_pagination_per_page_test
8
- basic_pagination_rendered_test
9
-
10
6
  # rubocop:disable Naming/AccessorMethodName
11
7
  def get_factory_name
12
8
  :dynflow_task
@@ -41,6 +37,41 @@ module ForemanTasks
41
37
  response = JSON.parse(@response.body)
42
38
  assert_equal 0, response['stopped']['total']
43
39
  end
40
+
41
+ describe 'taxonomy scoping' do
42
+ before do
43
+ @organizations = [0, 0].map { FactoryBot.create(:organization) }
44
+ @locations = [0, 0].map { FactoryBot.create(:location) }
45
+ @tasks = [0, 0].map { FactoryBot.create(:some_task) }
46
+ @tasks.zip(@organizations, @locations).each do |task, org, loc|
47
+ Lock.link!(org, task.id)
48
+ Lock.link!(loc, task.id)
49
+ end
50
+ end
51
+
52
+ it 'does not limit if unset' do
53
+ get(:summary, params: { recent_timeframe: 24 }, session: set_session_user)
54
+ assert_response :success
55
+ response = JSON.parse(@response.body)
56
+ assert_equal 2, response['stopped']['total']
57
+ end
58
+
59
+ it 'finds only tasks with matching taxonomies' do
60
+ get(:summary, params: { recent_timeframe: 24 },
61
+ session: set_session_user.merge(:organization_id => @organizations.first, :location_id => @locations.first))
62
+ assert_response :success
63
+ response = JSON.parse(@response.body)
64
+ assert_equal 1, response['stopped']['total']
65
+ end
66
+
67
+ it 'find no tasks when taxonomy combination contains no tasks' do
68
+ get(:summary, params: { recent_timeframe: 24 },
69
+ session: set_session_user.merge(:organization_id => @organizations.first, :location_id => @locations.last))
70
+ assert_response :success
71
+ response = JSON.parse(@response.body)
72
+ assert_equal 0, response['stopped']['total']
73
+ end
74
+ end
44
75
  end
45
76
 
46
77
  it 'supports csv export' do