Skip to content

Commit 066fa63

Browse files
jeremylenzclaude
andauthored
Add TaskProgress components and remove obsolete UI (theforeman#1127)
* Add TaskProgress components and modernize inventory upload UI This commit introduces a modernized UI for inventory upload tasks using PatternFly components and removes the previous terminal and shell-process-based implementation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 0b3a90f commit 066fa63

File tree

90 files changed

+1240
-2275
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+1240
-2275
lines changed

app/controllers/concerns/inventory_upload/report_actions.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ def initialize(**params)
1111
end
1212

1313
def start_report_generation(organization_id, disconnected)
14-
ForemanTasks.async_task(ForemanInventoryUpload::Async::GenerateReportJob, ForemanInventoryUpload.generated_reports_folder, organization_id, disconnected)
14+
upload = !disconnected
15+
ForemanTasks.async_task(
16+
ForemanInventoryUpload::Async::HostInventoryReportJob,
17+
ForemanInventoryUpload.generated_reports_folder,
18+
organization_id,
19+
"", # hosts_filter
20+
upload
21+
)
1522
end
1623

1724
def report_file(organization_id)

app/controllers/foreman_inventory_upload/accounts_controller.rb

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,20 @@ def index
66

77
accounts = Hash[
88
labels.map do |id, label|
9-
generate_report_status = status_for(id, ForemanInventoryUpload::Async::GenerateReportJob)
10-
upload_report_status = status_for(id, ForemanInventoryUpload::Async::UploadReportDirectJob)
9+
generate_task = latest_task_for(id, ForemanInventoryUpload::Async::HostInventoryReportJob)
10+
11+
# Check sub-action completion status
12+
generated_status = sub_action_status(generate_task, 'GenerateHostReport')
13+
uploaded_status = sub_action_status(generate_task, 'UploadReportDirectJob')
14+
1115
report_file_paths = ForemanInventoryUpload.report_file_paths(id)
1216

1317
[
1418
label,
1519
{
16-
generate_report_status: generate_report_status,
17-
upload_report_status: upload_report_status,
20+
generated_status: generated_status,
21+
uploaded_status: uploaded_status,
22+
generate_task: task_json(generate_task),
1823
report_file_paths: report_file_paths,
1924
id: id,
2025
},
@@ -30,9 +35,93 @@ def index
3035

3136
private
3237

33-
def status_for(label, job_class)
34-
label = job_class.output_label(label)
35-
ForemanInventoryUpload::Async::ProgressOutput.get(label)&.status
38+
def controller_permission
39+
'foreman_rh_cloud'
40+
end
41+
42+
def latest_task_for(org_id, job_class)
43+
ForemanTasks::Task
44+
.for_action_types([job_class.name])
45+
.joins(:links)
46+
.where(foreman_tasks_links: {
47+
resource_type: 'Organization',
48+
resource_id: org_id,
49+
})
50+
.with_duration
51+
.order('started_at DESC')
52+
.first
53+
end
54+
55+
def task_status_string(task)
56+
return nil unless task
57+
58+
if task.state == 'stopped'
59+
# Mimic old ProgressOutput format: "pid 12345 exit 0"
60+
exit_code = task.result == 'success' ? 0 : 1
61+
"pid #{Process.pid} exit #{exit_code}"
62+
else
63+
task.state
64+
end
65+
end
66+
67+
def sub_action_status(task, action_class_name)
68+
return nil unless task
69+
70+
# If task is still running, return the state
71+
return task.state unless task.state == 'stopped'
72+
73+
# For GenerateHostReport: always show status if task completed (generation always runs)
74+
if action_class_name == 'GenerateHostReport'
75+
exit_code = task.result == 'success' ? 0 : 1
76+
return "pid #{Process.pid} exit #{exit_code}"
77+
end
78+
79+
# For UploadReportDirectJob: only show status if task had upload enabled
80+
if action_class_name == 'UploadReportDirectJob'
81+
main_action = task.main_action
82+
return nil unless main_action.respond_to?(:input)
83+
84+
# Check if upload was enabled for this task
85+
return nil unless main_action.input[:upload]
86+
87+
# Show same status as overall task (upload runs as part of the task)
88+
exit_code = task.result == 'success' ? 0 : 1
89+
return "pid #{Process.pid} exit #{exit_code}"
90+
end
91+
92+
nil
93+
rescue StandardError => e
94+
Rails.logger.warn("Failed to get sub-action status for #{action_class_name} in task #{task.id}: #{e.message}")
95+
nil
96+
end
97+
98+
def task_json(task)
99+
return nil unless task
100+
101+
{
102+
id: task.id,
103+
label: task.label,
104+
state: task.state,
105+
result: task.result,
106+
progress: task.progress,
107+
started_at: task.started_at,
108+
ended_at: task.ended_at,
109+
duration: task.try(:duration)&.to_f,
110+
report_file_path: task_report_file_path(task),
111+
}
112+
end
113+
114+
def task_report_file_path(task)
115+
return nil unless task&.state == 'stopped'
116+
117+
# Get the main action from the task
118+
main_action = task.main_action
119+
return nil unless main_action.respond_to?(:report_file_path)
120+
121+
main_action.report_file_path
122+
rescue StandardError => e
123+
Rails.logger.warn("Failed to get report file path for task #{task.id}: #{e.message}")
124+
nil
36125
end
37126
end
38127
end
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
module ForemanInventoryUpload
2+
module Api
3+
class TasksController < ::Api::V2::BaseController
4+
ACTION_TYPES = [
5+
'ForemanInventoryUpload::Async::HostInventoryReportJob',
6+
'ForemanInventoryUpload::Async::UploadReportDirectJob',
7+
].freeze
8+
9+
# GET /foreman_inventory_upload/api/tasks/current
10+
# Returns current running/active tasks for inventory operations
11+
def current
12+
organization_id = validate_organization_id if params[:organization_id].present?
13+
return if performed?
14+
15+
tasks = ForemanTasks::Task
16+
.active
17+
.for_action_types(ACTION_TYPES)
18+
.with_duration
19+
20+
if organization_id.present?
21+
tasks = tasks.joins(:links)
22+
.where(foreman_tasks_links: {
23+
resource_type: 'Organization',
24+
resource_id: organization_id,
25+
})
26+
end
27+
28+
render json: {
29+
tasks: tasks.map { |task| task_json(task) },
30+
}
31+
end
32+
33+
# GET /foreman_inventory_upload/api/tasks/history
34+
# Returns recent task history for inventory operations
35+
def history
36+
organization_id = validate_organization_id if params[:organization_id].present?
37+
return if performed?
38+
39+
limit = validated_limit
40+
41+
tasks = ForemanTasks::Task
42+
.for_action_types(ACTION_TYPES)
43+
.with_duration
44+
.order('started_at DESC')
45+
.limit(limit)
46+
47+
if organization_id.present?
48+
tasks = tasks.joins(:links)
49+
.where(foreman_tasks_links: {
50+
resource_type: 'Organization',
51+
resource_id: organization_id,
52+
})
53+
end
54+
55+
render json: {
56+
tasks: tasks.map { |task| task_json(task) },
57+
}
58+
end
59+
60+
private
61+
62+
def controller_permission
63+
'foreman_rh_cloud'
64+
end
65+
66+
def task_json(task)
67+
{
68+
id: task.id,
69+
label: task.label,
70+
action: task.action,
71+
state: task.state,
72+
result: task.result,
73+
progress: task.progress,
74+
started_at: task.started_at,
75+
ended_at: task.ended_at,
76+
duration: task.duration&.to_f,
77+
humanized: task.humanized,
78+
report_file_path: task_report_file_path(task),
79+
}
80+
end
81+
82+
def task_report_file_path(task)
83+
return nil unless task.state == 'stopped' && task.result == 'success'
84+
return nil unless task.action == 'ForemanInventoryUpload::Async::HostInventoryReportJob'
85+
86+
task.main_action&.output&.[]('report_file')
87+
end
88+
89+
def validate_organization_id
90+
org_id = params[:organization_id].to_i
91+
if org_id.zero?
92+
render json: { message: 'Invalid organization_id parameter' }, status: :bad_request
93+
return nil
94+
end
95+
96+
begin
97+
Organization.find(org_id).id
98+
rescue ActiveRecord::RecordNotFound
99+
render json: { message: "Organization with id #{org_id} not found" }, status: :not_found
100+
nil
101+
end
102+
end
103+
104+
def validated_limit
105+
limit = params[:limit]&.to_i || 10
106+
limit.clamp(1, 100)
107+
end
108+
end
109+
end
110+
end

app/controllers/foreman_inventory_upload/reports_controller.rb

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,54 @@ module ForemanInventoryUpload
44
class ReportsController < ::ApplicationController
55
include InventoryUpload::ReportActions
66

7-
def last
8-
label = ForemanInventoryUpload::Async::GenerateReportJob.output_label(params[:organization_id])
9-
output = ForemanInventoryUpload::Async::ProgressOutput.get(label)&.full_output
10-
task_label = ForemanInventoryUpload::Async::GenerateAllReportsJob.name
11-
scheduled = ForemanTasks::Task.where(
12-
:label => task_label,
13-
:state => 'scheduled'
14-
).first&.start_at || nil
7+
def generate
8+
organization_id = validate_organization_id
9+
return if performed?
10+
11+
disconnected = boolean_param(:disconnected)
12+
13+
task = start_report_generation(organization_id, disconnected)
1514

1615
render json: {
17-
output: output,
18-
scheduled: scheduled,
16+
id: task.id,
17+
humanized: {
18+
action: task.action,
19+
},
1920
}, status: :ok
2021
end
2122

22-
def generate
23-
organization_id = params[:organization_id]
24-
disconnected = params[:disconnected]
23+
private
2524

26-
start_report_generation(organization_id, disconnected)
25+
def controller_permission
26+
'foreman_rh_cloud'
27+
end
2728

28-
render json: {
29-
action_status: 'success',
30-
}, status: :ok
29+
def action_permission
30+
case params[:action]
31+
when 'generate'
32+
'generate'
33+
else
34+
super
35+
end
36+
end
37+
38+
def validate_organization_id
39+
org_id = params[:organization_id].to_i
40+
if org_id.zero?
41+
render json: { message: 'Invalid organization_id parameter' }, status: :bad_request
42+
return nil
43+
end
44+
45+
begin
46+
Organization.find(org_id).id
47+
rescue ActiveRecord::RecordNotFound
48+
render json: { message: "Organization with id #{org_id} not found" }, status: :not_found
49+
nil
50+
end
51+
end
52+
53+
def boolean_param(key)
54+
Foreman::Cast.to_bool(params[key]) || false
3155
end
3256
end
3357
end

app/controllers/foreman_inventory_upload/uploads_controller.rb

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@ class UploadsController < ::ApplicationController
55

66
before_action :require_non_iop_smart_proxy, only: [:enable_cloud_connector]
77

8-
def last
9-
label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(params[:organization_id])
10-
output = ForemanInventoryUpload::Async::ProgressOutput.get(label)&.full_output
11-
12-
render json: {
13-
output: output,
14-
}, status: :ok
15-
end
16-
178
def download_file
189
filename, file = report_file(params[:organization_id])
1910

config/routes.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
Rails.application.routes.draw do
22
namespace :foreman_inventory_upload do
3-
get ':organization_id/reports/last', to: 'reports#last', constraints: { organization_id: %r{[^\/]+} }
43
post ':organization_id/reports', to: 'reports#generate', constraints: { organization_id: %r{[^\/]+} }
5-
get ':organization_id/uploads/last', to: 'uploads#last', constraints: { organization_id: %r{[^\/]+} }
64
get ':organization_id/uploads/file', to: 'uploads#download_file', constraints: { organization_id: %r{[^\/]+} }
75
get 'missing_hosts', to: 'missing_hosts#index'
86
get 'accounts', to: 'accounts#index'
@@ -76,6 +74,10 @@
7674
post 'enable_connector', to: 'inventory#enable_cloud_connector'
7775
post 'cloud_request', to: 'cloud_request#update'
7876
get 'advisor_engine_config', to: 'advisor_engine_config#show'
77+
78+
# Inventory upload task endpoints
79+
get 'inventory_upload/tasks/current', to: 'foreman_inventory_upload/api/tasks#current'
80+
get 'inventory_upload/tasks/history', to: 'foreman_inventory_upload/api/tasks#history'
7981
end
8082

8183
namespace 'advisor_engine' do

lib/foreman_inventory_upload/async/generate_all_reports_job.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def rescue_strategy_for_self
4242
end
4343

4444
def plan_generate_report(folder, organization, disconnected)
45-
plan_action(ForemanInventoryUpload::Async::GenerateReportJob, folder, organization.id, disconnected)
45+
plan_action(ForemanInventoryUpload::Async::HostInventoryReportJob, folder, organization.id, '', !disconnected)
4646
end
4747

4848
def logger

0 commit comments

Comments
 (0)