foreman_remote_execution 6.0.0 → 6.1.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_controller_extensions.rb +5 -0
  3. data/app/controllers/ui_job_wizard_controller.rb +1 -1
  4. data/app/helpers/remote_execution_helper.rb +4 -3
  5. data/app/lib/actions/remote_execution/run_host_job.rb +1 -1
  6. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +4 -0
  7. data/app/models/concerns/foreman_remote_execution/nic_extensions.rb +6 -4
  8. data/app/models/remote_execution_provider.rb +10 -1
  9. data/app/models/ssh_execution_provider.rb +8 -2
  10. data/app/views/api/v2/host/main.rabl +1 -0
  11. data/app/views/dashboard/_latest-jobs.html.erb +1 -1
  12. data/app/views/templates/{ssh → script}/check_update.erb +2 -2
  13. data/app/views/templates/{ssh → script}/module_action.erb +2 -2
  14. data/app/views/templates/{ssh → script}/package_action.erb +5 -2
  15. data/app/views/templates/{ssh → script}/power_action.erb +2 -2
  16. data/app/views/templates/{ssh → script}/puppet_agent_disable.erb +2 -2
  17. data/app/views/templates/{ssh → script}/puppet_agent_enable.erb +2 -2
  18. data/app/views/templates/{ssh → script}/puppet_install_modules_from_forge.erb +2 -2
  19. data/app/views/templates/{ssh → script}/puppet_install_modules_from_git.erb +2 -2
  20. data/app/views/templates/{ssh → script}/puppet_run_once.erb +2 -2
  21. data/app/views/templates/{ssh → script}/run_command.erb +2 -2
  22. data/app/views/templates/{ssh → script}/service_action.erb +2 -2
  23. data/db/migrate/20220321101835_rename_ssh_provider_to_script.rb +29 -0
  24. data/jsconfig.json +8 -0
  25. data/lib/foreman_remote_execution/engine.rb +4 -2
  26. data/lib/foreman_remote_execution/version.rb +1 -1
  27. data/locale/action_names.rb +1 -1
  28. data/package.json +6 -7
  29. data/test/unit/concerns/host_extensions_test.rb +2 -1
  30. data/test/unit/remote_execution_provider_test.rb +2 -0
  31. data/webpack/JobWizard/JobWizard.js +8 -2
  32. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +24 -15
  33. data/webpack/JobWizard/steps/form/GroupedSelectField.js +7 -1
  34. data/webpack/global_index.js +4 -0
  35. data/webpack/react_app/components/FeaturesDropdown/actions.js +13 -0
  36. data/webpack/react_app/components/FeaturesDropdown/constant.js +2 -0
  37. data/webpack/react_app/components/FeaturesDropdown/index.js +74 -0
  38. data/webpack/react_app/components/HostKebab/KebabItems.js +22 -0
  39. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +6 -1
  40. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +2 -2
  41. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  42. data/webpack/react_app/extend/fillKebabItems.js +11 -0
  43. data/webpack/react_app/extend/fillRexFeaturesDropdown.js +11 -0
  44. metadata +23 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61d64bd14e3ce43367e26ad39ec10408baf8ec7920f45aa4ea1353340d3cf742
4
- data.tar.gz: 0ab68114a29368fa4c2a269287b875368cdcc31db63dd481df223a1f91a36cef
3
+ metadata.gz: 910d4fd508188efbad4cfb121085171d077d65881e7ef8b5c9f1dea4092aa182
4
+ data.tar.gz: af7a5ca195d2b0d6ba269962bb3d726cd19dd6cc23358e6e6dc21cc77b36b3b2
5
5
  SHA512:
6
- metadata.gz: 5d908f4f766c2b55bf6814035058b5faea29ba1ae3c61729275d2492e62b5f5aba2f60838d0b8383b9075f75daed15788d34ae1b2b9e091fea3ec26f812c670a
7
- data.tar.gz: 05d63bba759d31d2b85030bc8cb4e39faeb9af50e784374e016edb4637d84906f6527b533efd5600da969a1b2315f34392f5f994d1ae17bbc840f5a2dfdf04a9
6
+ metadata.gz: 030ed44dd17a6da7b334f3c3621d34cec9d4afc6323f2cbacd1054716520dc4f955f5da4d0aa5bfb93b0f83699c6dd15a3d75a9ff770fa1b9c5d6df2afbba440
7
+ data.tar.gz: 80d41c53f5e1620919069e08a97b23d22d884406275ad426b49ee08c97dc92c958762fe79ff5bb703b63859f1551fb3cfad838f1b63e75cbc143983dc864e7fd
@@ -13,6 +13,7 @@ module ForemanRemoteExecution
13
13
 
14
14
  def host_setup_extension
15
15
  remote_execution_interface
16
+ reset_host_known_keys! unless @host.new_record?
16
17
  super
17
18
  end
18
19
 
@@ -21,6 +22,10 @@ module ForemanRemoteExecution
21
22
 
22
23
  @host.set_execution_interface(params['remote_execution_interface'])
23
24
  end
25
+
26
+ def reset_host_known_keys!
27
+ @host.host_proxy_invocations.destroy_all
28
+ end
24
29
  end
25
30
  end
26
31
  end
@@ -40,7 +40,7 @@ class UiJobWizardController < ApplicationController
40
40
  end
41
41
 
42
42
  def with_katello
43
- !!defined?(::Katello)
43
+ ::Foreman::Plugin.installed? 'katello'
44
44
  end
45
45
 
46
46
  def resource_class
@@ -1,6 +1,6 @@
1
1
  module RemoteExecutionHelper
2
2
  def providers_options
3
- RemoteExecutionProvider.providers.map { |key, provider| [ key, _(provider.humanized_name) ] }
3
+ RemoteExecutionProvider.providers.reject { |key, _provider| key == 'SSH' }.map { |key, provider| [ key, _(provider.humanized_name) ] }
4
4
  end
5
5
 
6
6
  def job_hosts_authorizer
@@ -147,9 +147,10 @@ module RemoteExecutionHelper
147
147
  end
148
148
  end
149
149
 
150
- def invocation_description(invocation)
150
+ def invocation_description(invocation, keep_tooltip: true)
151
151
  description = invocation.description.try(:capitalize) || invocation.job_category
152
- trunc_with_tooltip(description, 80)
152
+ description = trunc_with_tooltip(description, 80) if keep_tooltip
153
+ description
153
154
  end
154
155
 
155
156
  def invocation_result(invocation, key)
@@ -40,7 +40,7 @@ module Actions
40
40
  provider = template_invocation.template.provider
41
41
  proxy_selector = provider.required_proxy_selector_for(template_invocation.template) || proxy_selector
42
42
 
43
- provider_type = template_invocation.template.provider_type.to_s
43
+ provider_type = provider.proxy_feature
44
44
  proxy = determine_proxy!(proxy_selector, provider_type, host)
45
45
  link!(proxy)
46
46
  input[:proxy_id] = proxy.id
@@ -46,6 +46,10 @@ module ForemanRemoteExecution
46
46
  end
47
47
  end
48
48
 
49
+ def cockpit_url
50
+ SSHExecutionProvider.cockpit_url_for_host(self.name)
51
+ end
52
+
49
53
  def execution_status(options = {})
50
54
  @execution_status ||= get_status(HostStatus::ExecutionStatus).to_status(options)
51
55
  end
@@ -3,16 +3,18 @@ module ForemanRemoteExecution
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- before_validation :set_execution_flag
7
6
  validate :exclusive_execution_interface
7
+ before_validation :move_execution_flag
8
8
  end
9
9
 
10
10
  private
11
11
 
12
- def set_execution_flag
13
- return unless primary? && host.present?
12
+ def move_execution_flag
13
+ return unless host && self.execution?
14
14
 
15
- self.execution = true if host.interfaces.detect(&:execution).nil?
15
+ host.interfaces
16
+ .select { |i| i.execution? && i != self }
17
+ .each { |i| i.execution = false }
16
18
  end
17
19
 
18
20
  def exclusive_execution_interface
@@ -4,7 +4,16 @@ class RemoteExecutionProvider
4
4
 
5
5
  class << self
6
6
  def provider_for(type)
7
- providers[type.to_s] || providers[:SSH]
7
+ providers[type.to_s] || providers[:script]
8
+ end
9
+
10
+ def registered_name
11
+ klass = self
12
+ providers.key(klass)
13
+ end
14
+
15
+ def proxy_feature
16
+ registered_name
8
17
  end
9
18
 
10
19
  def providers
@@ -1,4 +1,4 @@
1
- class SSHExecutionProvider < RemoteExecutionProvider
1
+ class ScriptExecutionProvider < RemoteExecutionProvider
2
2
  class << self
3
3
  def proxy_command_options(template_invocation, host)
4
4
  super.merge(:ssh_user => ssh_user(host),
@@ -9,7 +9,7 @@ class SSHExecutionProvider < RemoteExecutionProvider
9
9
  end
10
10
 
11
11
  def humanized_name
12
- _('SSH')
12
+ _('Script')
13
13
  end
14
14
 
15
15
  def supports_effective_user?
@@ -53,6 +53,10 @@ class SSHExecutionProvider < RemoteExecutionProvider
53
53
  Setting[:remote_execution_cockpit_url] % { :host => host } if Setting[:remote_execution_cockpit_url].present?
54
54
  end
55
55
 
56
+ def proxy_feature
57
+ 'SSH'
58
+ end
59
+
56
60
  private
57
61
 
58
62
  def ssh_user(host)
@@ -64,3 +68,5 @@ class SSHExecutionProvider < RemoteExecutionProvider
64
68
  end
65
69
  end
66
70
  end
71
+
72
+ SSHExecutionProvider = ScriptExecutionProvider
@@ -0,0 +1 @@
1
+ attributes :cockpit_url
@@ -10,7 +10,7 @@
10
10
  </tr>
11
11
  <% JobInvocation.latest_jobs.each do |invocation| %>
12
12
  <tr>
13
- <td class="ellipsis"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
13
+ <td class="ellipsis"><%= link_to_if_authorized invocation_description(invocation, keep_tooltip: false), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
14
14
  <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
15
15
  <td><%= time_in_words_span(invocation.start_at) %></td>
16
16
  </tr>
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Check Update - SSH Default
3
+ name: Check Update - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Packages
6
6
  description_format: "Check for package updates"
7
- provider_type: SSH
7
+ provider_type: script
8
8
  %>
9
9
 
10
10
  <%
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Module Action - SSH Default
3
+ name: Module Action - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Modules
6
6
  description_format: "Module %{action} %{module_spec}"
7
- provider_type: SSH
7
+ provider_type: script
8
8
  template_inputs:
9
9
  - name: pre_script
10
10
  description: A script to run prior to the module action
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Package Action - SSH Default
3
+ name: Package Action - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Packages
6
6
  description_format: "%{action} package(s) %{package}"
7
- provider_type: SSH
7
+ provider_type: script
8
8
  template_inputs:
9
9
  - name: pre_script
10
10
  description: A script to run prior to the package action
@@ -69,8 +69,11 @@ handle_zypp_res_codes () {
69
69
 
70
70
  if [ "${ZYPP_RES_CODES[$RETVAL]}" != "" ]; then
71
71
  echo ${ZYPP_RES_CODES[$RETVAL]}
72
+ fi
73
+ if [[ $RETVAL -ge 100 && $RETVAL -le 103 ]]; then
72
74
  RETVAL=0
73
75
  fi
76
+
74
77
  return $RETVAL
75
78
  }
76
79
  <% end -%>
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Power Action - SSH Default
3
+ name: Power Action - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Power
6
6
  description_format: '%{action} host'
7
- provider_type: SSH
7
+ provider_type: script
8
8
  template_inputs:
9
9
  - name: action
10
10
  description: Action to perform on the service
@@ -1,5 +1,5 @@
1
1
  <%#
2
- name: Puppet Agent Disable - SSH Default
2
+ name: Puppet Agent Disable - Script Default
3
3
  model: JobTemplate
4
4
  job_category: Puppet
5
5
  description_format: Disable Puppet agent
@@ -10,7 +10,7 @@ template_inputs:
10
10
  input_type: user
11
11
  description: Reason for disabling the Puppet agent
12
12
  advanced: false
13
- provider_type: SSH
13
+ provider_type: script
14
14
  kind: job_template
15
15
  -%>
16
16
  <% if @host.operatingsystem.family == 'Debian' -%>
@@ -1,10 +1,10 @@
1
1
  <%#
2
- name: Puppet Agent Enable - SSH Default
2
+ name: Puppet Agent Enable - Script Default
3
3
  model: JobTemplate
4
4
  job_category: Puppet
5
5
  description_format: Enable Puppet agent
6
6
  snippet: false
7
- provider_type: SSH
7
+ provider_type: script
8
8
  kind: job_template
9
9
  -%>
10
10
  <% if @host.operatingsystem.family == 'Debian' -%>
@@ -1,5 +1,5 @@
1
1
  <%#
2
- name: Puppet Module - Install from forge - SSH Default
2
+ name: Puppet Module - Install from forge - Script Default
3
3
  model: JobTemplate
4
4
  job_category: Puppet
5
5
  description_format: Install Puppet Module "%{puppet_module}" from forge
@@ -30,7 +30,7 @@ template_inputs:
30
30
  input_type: user
31
31
  description: Do not attempt to install dependencies. Type "true" to ignore dependencies.
32
32
  advanced: true
33
- provider_type: SSH
33
+ provider_type: script
34
34
  kind: job_template
35
35
  -%>
36
36
  <% if @host.operatingsystem.family == 'Debian' -%>
@@ -1,5 +1,5 @@
1
1
  <%#
2
- name: Puppet Module - Install from git - SSH Default
2
+ name: Puppet Module - Install from git - Script Default
3
3
  model: JobTemplate
4
4
  job_category: Puppet
5
5
  description_format: Install Puppet Module "%{puppet_module}" from git
@@ -15,7 +15,7 @@ template_inputs:
15
15
  input_type: user
16
16
  description: For example, '/etc/puppetlabs/code/environments/production/modules/puppet'.
17
17
  advanced: false
18
- provider_type: SSH
18
+ provider_type: script
19
19
  kind: job_template
20
20
  -%>
21
21
  git clone <%= input('git_repository') %> <%= input('target_dir') %>
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Puppet Run Once - SSH Default
3
+ name: Puppet Run Once - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Puppet
6
6
  description_format: 'Run Puppet once with "%{puppet_options}"'
7
- provider_type: SSH
7
+ provider_type: script
8
8
  template_inputs:
9
9
  - name: puppet_options
10
10
  description: Additional options to pass to puppet
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Run Command - SSH Default
3
+ name: Run Command - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Commands
6
6
  description_format: "Run %{command}"
7
- provider_type: SSH
7
+ provider_type: script
8
8
  template_inputs:
9
9
  - name: command
10
10
  description: Command to run on the host
@@ -1,10 +1,10 @@
1
1
  <%#
2
2
  kind: job_template
3
- name: Service Action - SSH Default
3
+ name: Service Action - Script Default
4
4
  model: JobTemplate
5
5
  job_category: Services
6
6
  description_format: '%{action} service %{service}'
7
- provider_type: SSH
7
+ provider_type: script
8
8
  template_inputs:
9
9
  - name: action
10
10
  description: Action to perform on the service
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RenameSshProviderToScript < ActiveRecord::Migration[6.0]
4
+
5
+ def do_change(from, to, from_re, new_label)
6
+ setting = Setting.find_by(:name => 'remote_execution_form_job_template')
7
+ default_template = nil
8
+
9
+ Template.where(:provider_type => from).each do |t|
10
+ default_template = t if t.name == setting&.value
11
+ t.provider_type = to
12
+ t.name = t.name.gsub(from_re, new_label)
13
+ t.save!
14
+ end
15
+
16
+ if default_template
17
+ setting.value = default_template.name
18
+ setting.save!
19
+ end
20
+ end
21
+
22
+ def up
23
+ do_change 'SSH', 'script', /SSH Default$/, 'Script Default'
24
+ end
25
+
26
+ def down
27
+ do_change 'script', 'SSH', /Script Default$/, 'SSH Default'
28
+ end
29
+ end
data/jsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "paths": {
5
+ "foremanReact/*": ["../foreman/webpack/assets/javascripts/react_app/*"]
6
+ }
7
+ }
8
+ }
@@ -146,7 +146,7 @@ module ForemanRemoteExecution
146
146
  setting 'remote_execution_form_job_template',
147
147
  type: :string,
148
148
  description: N_('Choose a job template that is pre-selected in job invocation form'),
149
- default: 'Run Command - SSH Default',
149
+ default: 'Run Command - Script Default',
150
150
  full_name: N_('Form Job Template'),
151
151
  collection: proc { Hash[JobTemplate.unscoped.map { |template| [template.name, template.name] }] }
152
152
  setting 'remote_execution_job_invocation_report_template',
@@ -265,6 +265,7 @@ module ForemanRemoteExecution
265
265
  extend_rabl_template 'api/v2/smart_proxies/main', 'api/v2/smart_proxies/pubkey'
266
266
  extend_rabl_template 'api/v2/interfaces/main', 'api/v2/interfaces/execution_flag'
267
267
  extend_rabl_template 'api/v2/subnets/show', 'api/v2/subnets/remote_execution_proxies'
268
+ extend_rabl_template 'api/v2/hosts/main', 'api/v2/host/main'
268
269
  parameter_filter ::Subnet, :remote_execution_proxy_ids
269
270
  describe_host { overview_buttons_provider :host_overview_buttons }
270
271
 
@@ -328,7 +329,8 @@ module ForemanRemoteExecution
328
329
  require_dependency 'foreman_tasks/task'
329
330
  ForemanTasks::Task.include ForemanRemoteExecution::ForemanTasksTaskExtensions
330
331
  ForemanTasks::Cleaner.include ForemanRemoteExecution::ForemanTasksCleanerExtensions
331
- RemoteExecutionProvider.register(:SSH, SSHExecutionProvider)
332
+ RemoteExecutionProvider.register(:SSH, ::SSHExecutionProvider)
333
+ RemoteExecutionProvider.register(:script, ::ScriptExecutionProvider)
332
334
 
333
335
  ForemanRemoteExecution.register_rex_feature
334
336
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '6.0.0'.freeze
2
+ VERSION = '6.1.0'.freeze
3
3
  end
@@ -2,4 +2,4 @@
2
2
  _("Remote action:")
3
3
  _("»Import facts«")
4
4
  _("»Import Puppet classes«")
5
- _("»Action with sub plans«")
5
+ _("»Action with sub plans«")
data/package.json CHANGED
@@ -21,20 +21,19 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.7.0",
24
- "@theforeman/builder": "^8.16.0",
25
- "@theforeman/eslint-plugin-foreman": "^8.16.0",
26
- "@theforeman/stories": "^8.16.0",
27
- "@theforeman/test": "^8.16.0",
28
- "@theforeman/vendor-dev": "^8.16.0",
24
+ "@theforeman/builder": "^10.1.0",
25
+ "@theforeman/eslint-plugin-foreman": "^10.1.0",
26
+ "@theforeman/stories": "^10.1.0",
27
+ "@theforeman/test": "^10.1.0",
28
+ "@theforeman/vendor-dev": "^10.1.0",
29
29
  "babel-eslint": "^10.0.0",
30
30
  "eslint": "^6.8.0",
31
31
  "prettier": "^1.19.1",
32
- "@patternfly/react-catalog-view-extension": "^4.8.126",
33
32
  "redux-mock-store": "^1.2.2",
34
33
  "graphql-tag": "^2.11.0",
35
34
  "graphql": "^15.5.0"
36
35
  },
37
36
  "peerDependencies": {
38
- "@theforeman/vendor": "^8.16.0"
37
+ "@theforeman/vendor": "^10.1.0"
39
38
  }
40
39
  }
@@ -67,7 +67,8 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
67
67
  it 'should only have one execution interface' do
68
68
  host.interfaces << FactoryBot.build(:nic_managed)
69
69
  host.interfaces.each { |interface| interface.execution = true }
70
- _(host).wont_be :valid?
70
+ _(host).must_be :valid?
71
+ _(host.interfaces.count(&:execution?)).must_equal 1
71
72
  end
72
73
 
73
74
  it 'returns the execution interface' do
@@ -6,9 +6,11 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
6
6
  it { _(providers).must_be_kind_of HashWithIndifferentAccess }
7
7
  it 'makes providers accessible using symbol' do
8
8
  _(providers[:SSH]).must_equal SSHExecutionProvider
9
+ _(providers[:script]).must_equal ScriptExecutionProvider
9
10
  end
10
11
  it 'makes providers accessible using string' do
11
12
  _(providers['SSH']).must_equal SSHExecutionProvider
13
+ _(providers['script']).must_equal ScriptExecutionProvider
12
14
  end
13
15
  end
14
16
 
@@ -21,6 +21,7 @@ import {
21
21
  selectJobTemplate,
22
22
  selectIsSubmitting,
23
23
  selectRouterSearch,
24
+ selectIsLoading,
24
25
  } from './JobWizardSelectors';
25
26
  import Schedule from './steps/Schedule/';
26
27
  import HostsAndInputs from './steps/HostsAndInputs/';
@@ -130,9 +131,14 @@ export const JobWizard = () => {
130
131
  const templateError = !!useSelector(selectTemplateError);
131
132
  const templateResponse = useSelector(selectJobTemplate);
132
133
  const isSubmitting = useSelector(selectIsSubmitting);
134
+ const isTemplatesLoading = useSelector(state =>
135
+ selectIsLoading(state, JOB_TEMPLATE)
136
+ );
133
137
  const isTemplate =
134
- !templateError && !!jobTemplateID && templateResponse.job_template;
135
-
138
+ !isTemplatesLoading &&
139
+ !templateError &&
140
+ !!jobTemplateID &&
141
+ templateResponse.job_template;
136
142
  const steps = [
137
143
  {
138
144
  name: WIZARD_TITLES.categoryAndTemplate,
@@ -1,11 +1,13 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { useSelector } from 'react-redux';
3
4
  import { Text, TextVariants, Form, Alert } from '@patternfly/react-core';
4
5
  import { translate as __ } from 'foremanReact/common/I18n';
5
6
  import { SelectField } from '../form/SelectField';
6
7
  import { GroupedSelectField } from '../form/GroupedSelectField';
7
8
  import { WizardTitle } from '../form/WizardTitle';
8
- import { WIZARD_TITLES } from '../../JobWizardConstants';
9
+ import { WIZARD_TITLES, JOB_TEMPLATES } from '../../JobWizardConstants';
10
+ import { selectIsLoading } from '../../JobWizardSelectors';
9
11
 
10
12
  export const CategoryAndTemplate = ({
11
13
  jobCategories,
@@ -17,18 +19,23 @@ export const CategoryAndTemplate = ({
17
19
  errors,
18
20
  }) => {
19
21
  const templatesGroups = {};
20
- jobTemplates.forEach(template => {
21
- if (templatesGroups[template.provider_type]?.options)
22
- templatesGroups[template.provider_type].options.push({
23
- label: template.name,
24
- value: template.id,
25
- });
26
- else
27
- templatesGroups[template.provider_type] = {
28
- options: [{ label: template.name, value: template.id }],
29
- groupLabel: template.provider_type,
30
- };
31
- });
22
+ const isTemplatesLoading = useSelector(state =>
23
+ selectIsLoading(state, JOB_TEMPLATES)
24
+ );
25
+ if (!isTemplatesLoading) {
26
+ jobTemplates.forEach(template => {
27
+ if (templatesGroups[template.provider_type]?.options)
28
+ templatesGroups[template.provider_type].options.push({
29
+ label: template.name,
30
+ value: template.id,
31
+ });
32
+ else
33
+ templatesGroups[template.provider_type] = {
34
+ options: [{ label: template.name, value: template.id }],
35
+ groupLabel: template.provider_type,
36
+ };
37
+ });
38
+ }
32
39
 
33
40
  const selectedTemplate = jobTemplates.find(
34
41
  template => template.id === selectedTemplateID
@@ -60,8 +67,10 @@ export const CategoryAndTemplate = ({
60
67
  fieldId="job_template"
61
68
  groups={Object.values(templatesGroups)}
62
69
  setSelected={setJobTemplate}
63
- selected={selectedTemplate}
64
- isDisabled={!!(categoryError || allTemplatesError)}
70
+ selected={isTemplatesLoading ? [] : selectedTemplate}
71
+ isDisabled={
72
+ !!(categoryError || allTemplatesError || isTemplatesLoading)
73
+ }
65
74
  placeholderText={allTemplatesError ? __('Error') : ''}
66
75
  />
67
76
  {isError && (
@@ -69,6 +69,8 @@ export const GroupedSelectField = ({
69
69
  className="without_select2"
70
70
  onClear={onClear}
71
71
  menuAppendTo={() => document.body}
72
+ aria-labelledby={fieldId}
73
+ toggleAriaLabel={`${label} toggle`}
72
74
  {...props}
73
75
  >
74
76
  {options}
@@ -81,7 +83,11 @@ GroupedSelectField.propTypes = {
81
83
  label: PropTypes.string.isRequired,
82
84
  fieldId: PropTypes.string.isRequired,
83
85
  groups: PropTypes.array,
84
- selected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
86
+ selected: PropTypes.oneOfType([
87
+ PropTypes.string,
88
+ PropTypes.number,
89
+ PropTypes.array,
90
+ ]),
85
91
  setSelected: PropTypes.func.isRequired,
86
92
  };
87
93
 
@@ -2,9 +2,13 @@ import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
2
  import routes from './Routes/routes';
3
3
  import fillregistrationAdvanced from './react_app/extend/fillregistrationAdvanced';
4
4
  import fillRecentJobsCard from './react_app/extend/fillRecentJobsCard';
5
+ import fillFeaturesDropdown from './react_app/extend/fillRexFeaturesDropdown';
6
+ import fillKebabItems from './react_app/extend/fillKebabItems';
5
7
  import registerReducers from './react_app/extend/reducers';
6
8
 
7
9
  registerReducers();
8
10
  registerRoutes('foreman_remote_execution', routes);
11
+ fillFeaturesDropdown();
9
12
  fillRecentJobsCard();
10
13
  fillregistrationAdvanced();
14
+ fillKebabItems();
@@ -0,0 +1,13 @@
1
+ import { foremanUrl } from 'foremanReact/common/helpers';
2
+ import { sprintf, translate as __ } from 'foremanReact/common/I18n';
3
+ import { post } from 'foremanReact/redux/API';
4
+
5
+ export const runFeature = (hostId, feature, label) => dispatch => {
6
+ const url = foremanUrl(
7
+ `/job_invocations?feature=${feature}&host_ids%5B%5D=${hostId}`
8
+ );
9
+
10
+ const successToast = () => sprintf(__('%s job has been invoked'), label);
11
+ const errorToast = ({ message }) => message;
12
+ dispatch(post({ key: feature.toUpperCase(), url, successToast, errorToast }));
13
+ };
@@ -0,0 +1,2 @@
1
+ export const REX_FEATURES_API = '/api/remote_execution_features';
2
+ export const NEW_JOB_PAGE = '/job_invocations/new?host_ids%5B%5D';
@@ -0,0 +1,74 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useState } from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+ import {
5
+ DropdownItem,
6
+ Dropdown,
7
+ DropdownToggle,
8
+ DropdownToggleAction,
9
+ } from '@patternfly/react-core';
10
+ import { push } from 'connected-react-router';
11
+
12
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
13
+ import { translate as __ } from 'foremanReact/common/I18n';
14
+ import { foremanUrl } from 'foremanReact/common/helpers';
15
+ import { STATUS } from 'foremanReact/constants';
16
+
17
+ import { REX_FEATURES_API, NEW_JOB_PAGE } from './constant';
18
+ import { runFeature } from './actions';
19
+
20
+ const FeaturesDropdown = ({ hostId }) => {
21
+ const [isOpen, setIsOpen] = useState(false);
22
+ const {
23
+ response: { results: features },
24
+ status,
25
+ } = useAPI('get', foremanUrl(REX_FEATURES_API));
26
+
27
+ const dispatch = useDispatch();
28
+ const dropdownItems = features
29
+ ?.filter(feature => feature.host_action_button)
30
+ ?.map(({ name, label, id, description }) => (
31
+ <DropdownItem
32
+ onClick={() => dispatch(runFeature(hostId, label, name))}
33
+ key={id}
34
+ description={description}
35
+ >
36
+ {name}
37
+ </DropdownItem>
38
+ ));
39
+ const scheduleJob = [
40
+ <DropdownToggleAction
41
+ onClick={() => dispatch(push(`${NEW_JOB_PAGE}=${hostId}`))}
42
+ key="schedule-job-action"
43
+ >
44
+ {__('Schedule a job')}
45
+ </DropdownToggleAction>,
46
+ ];
47
+
48
+ return (
49
+ <Dropdown
50
+ alignments={{ default: 'right' }}
51
+ onSelect={() => setIsOpen(false)}
52
+ toggle={
53
+ <DropdownToggle
54
+ splitButtonItems={scheduleJob}
55
+ toggleVariant="primary"
56
+ onToggle={() => setIsOpen(prev => !prev)}
57
+ isDisabled={status === STATUS.PENDING}
58
+ splitButtonVariant="action"
59
+ />
60
+ }
61
+ isOpen={isOpen}
62
+ dropdownItems={dropdownItems}
63
+ />
64
+ );
65
+ };
66
+
67
+ FeaturesDropdown.propTypes = {
68
+ hostId: PropTypes.number,
69
+ };
70
+ FeaturesDropdown.defaultProps = {
71
+ hostId: undefined,
72
+ };
73
+
74
+ export default FeaturesDropdown;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { DropdownItem } from '@patternfly/react-core';
4
+ import { CodeIcon } from '@patternfly/react-icons';
5
+ import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors';
6
+ import { translate as __ } from 'foremanReact/common/I18n';
7
+ import { HOST_DETAILS_KEY } from 'foremanReact/components/HostDetails/consts';
8
+
9
+ const HostKebabItems = () => {
10
+ const { cockpit_url: consoleUrl } = useSelector(state =>
11
+ selectAPIResponse(state, HOST_DETAILS_KEY)
12
+ );
13
+
14
+ if (!consoleUrl) return null;
15
+ return (
16
+ <DropdownItem icon={<CodeIcon />} href={consoleUrl}>
17
+ {__('Web Console')}
18
+ </DropdownItem>
19
+ );
20
+ };
21
+
22
+ export default HostKebabItems;
@@ -52,7 +52,12 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
52
52
  </DropdownItem>,
53
53
  ]}
54
54
  >
55
- <Tabs mountOnEnter activeKey={activeTab} onSelect={handleTabClick}>
55
+ <Tabs
56
+ mountOnEnter
57
+ unmountOnExit
58
+ activeKey={activeTab}
59
+ onSelect={handleTabClick}
60
+ >
56
61
  <Tab
57
62
  eventKey={FINISHED_TAB}
58
63
  title={<TabTitleText>{__('Finished')}</TabTitleText>}
@@ -19,7 +19,7 @@ import { translate as __ } from 'foremanReact/common/I18n';
19
19
  import { foremanUrl } from 'foremanReact/common/helpers';
20
20
 
21
21
  import JobStatusIcon from './JobStatusIcon';
22
- import { JOB_API_URL, JOBS_IN_CARD } from './constants';
22
+ import { JOB_API_URL, JOBS_IN_CARD, RECENT_JOBS_KEY } from './constants';
23
23
 
24
24
  const RecentJobsTable = ({ status, hostId }) => {
25
25
  const jobsUrl =
@@ -30,7 +30,7 @@ const RecentJobsTable = ({ status, hostId }) => {
30
30
  const {
31
31
  response: { results: jobs },
32
32
  status: responseStatus,
33
- } = useAPI('get', jobsUrl);
33
+ } = useAPI('get', jobsUrl, RECENT_JOBS_KEY);
34
34
 
35
35
  return (
36
36
  <DataList aria-label="recent-jobs-table" isCompact>
@@ -10,3 +10,4 @@ export const JOB_BASE_URL = '/job_invocations?search=host+%3D+';
10
10
  export const JOB_API_URL =
11
11
  '/api/job_invocations?order=start_at+DESC&search=targeted_host_id%3D';
12
12
  export const JOBS_IN_CARD = 3;
13
+ export const RECENT_JOBS_KEY = { key: 'RECENT_JOBS_KEY' };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
+ import KebabItems from '../components/HostKebab/KebabItems';
4
+
5
+ export default () =>
6
+ addGlobalFill(
7
+ 'host-details-kebab',
8
+ 'rex-host-details-kebab-job',
9
+ <KebabItems key="rex-host-details-kebab-job" />,
10
+ 100
11
+ );
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
+ import FeaturesDropdown from '../components/FeaturesDropdown';
4
+
5
+ export default () =>
6
+ addGlobalFill(
7
+ '_rex-host-features',
8
+ '_rex-host-features',
9
+ <FeaturesDropdown key="_rex-host-features" />,
10
+ 1000
11
+ );
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-17 00:00:00.000000000 Z
11
+ date: 2022-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -201,6 +201,7 @@ files:
201
201
  - app/views/api/v2/foreign_input_sets/index.json.rabl
202
202
  - app/views/api/v2/foreign_input_sets/main.json.rabl
203
203
  - app/views/api/v2/foreign_input_sets/show.json.rabl
204
+ - app/views/api/v2/host/main.rabl
204
205
  - app/views/api/v2/interfaces/execution_flag.json.rabl
205
206
  - app/views/api/v2/job_invocations/base.json.rabl
206
207
  - app/views/api/v2/job_invocations/create.json.rabl
@@ -263,17 +264,17 @@ files:
263
264
  - app/views/template_invocations/_refresh.js.erb
264
265
  - app/views/template_invocations/show.html.erb
265
266
  - app/views/template_invocations/show.js.erb
266
- - app/views/templates/ssh/check_update.erb
267
- - app/views/templates/ssh/module_action.erb
268
- - app/views/templates/ssh/package_action.erb
269
- - app/views/templates/ssh/power_action.erb
270
- - app/views/templates/ssh/puppet_agent_disable.erb
271
- - app/views/templates/ssh/puppet_agent_enable.erb
272
- - app/views/templates/ssh/puppet_install_modules_from_forge.erb
273
- - app/views/templates/ssh/puppet_install_modules_from_git.erb
274
- - app/views/templates/ssh/puppet_run_once.erb
275
- - app/views/templates/ssh/run_command.erb
276
- - app/views/templates/ssh/service_action.erb
267
+ - app/views/templates/script/check_update.erb
268
+ - app/views/templates/script/module_action.erb
269
+ - app/views/templates/script/package_action.erb
270
+ - app/views/templates/script/power_action.erb
271
+ - app/views/templates/script/puppet_agent_disable.erb
272
+ - app/views/templates/script/puppet_agent_enable.erb
273
+ - app/views/templates/script/puppet_install_modules_from_forge.erb
274
+ - app/views/templates/script/puppet_install_modules_from_git.erb
275
+ - app/views/templates/script/puppet_run_once.erb
276
+ - app/views/templates/script/run_command.erb
277
+ - app/views/templates/script/service_action.erb
277
278
  - config/routes.rb
278
279
  - db/migrate/20150612121541_add_job_template_to_template.rb
279
280
  - db/migrate/20150708133241_add_targeting.rb
@@ -321,6 +322,7 @@ files:
321
322
  - db/migrate/20210312074713_add_provider_inputs.rb
322
323
  - db/migrate/2021051713291621250977_add_host_proxy_invocations.rb
323
324
  - db/migrate/20210816100932_rex_setting_category_to_dsl.rb
325
+ - db/migrate/20220321101835_rename_ssh_provider_to_script.rb
324
326
  - db/seeds.d/100-assign_features_with_templates.rb
325
327
  - db/seeds.d/20-permissions.rb
326
328
  - db/seeds.d/50-notification_blueprints.rb
@@ -333,6 +335,7 @@ files:
333
335
  - extra/cockpit/foreman-cockpit.service
334
336
  - extra/cockpit/settings.yml.example
335
337
  - foreman_remote_execution.gemspec
338
+ - jsconfig.json
336
339
  - lib/foreman_remote_execution.rb
337
340
  - lib/foreman_remote_execution/engine.rb
338
341
  - lib/foreman_remote_execution/version.rb
@@ -475,6 +478,10 @@ files:
475
478
  - webpack/global_index.js
476
479
  - webpack/helpers.js
477
480
  - webpack/index.js
481
+ - webpack/react_app/components/FeaturesDropdown/actions.js
482
+ - webpack/react_app/components/FeaturesDropdown/constant.js
483
+ - webpack/react_app/components/FeaturesDropdown/index.js
484
+ - webpack/react_app/components/HostKebab/KebabItems.js
478
485
  - webpack/react_app/components/RecentJobsCard/JobStatusIcon.js
479
486
  - webpack/react_app/components/RecentJobsCard/RecentJobsCard.js
480
487
  - webpack/react_app/components/RecentJobsCard/RecentJobsTable.js
@@ -507,7 +514,9 @@ files:
507
514
  - webpack/react_app/components/jobInvocations/AggregateStatus/index.js
508
515
  - webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js
509
516
  - webpack/react_app/components/jobInvocations/index.js
517
+ - webpack/react_app/extend/fillKebabItems.js
510
518
  - webpack/react_app/extend/fillRecentJobsCard.js
519
+ - webpack/react_app/extend/fillRexFeaturesDropdown.js
511
520
  - webpack/react_app/extend/fillregistrationAdvanced.js
512
521
  - webpack/react_app/extend/reducers.js
513
522
  - webpack/react_app/redux/actions/jobInvocations/index.js
@@ -536,7 +545,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
536
545
  - !ruby/object:Gem::Version
537
546
  version: '0'
538
547
  requirements: []
539
- rubygems_version: 3.1.2
548
+ rubygems_version: 3.2.26
540
549
  signing_key:
541
550
  specification_version: 4
542
551
  summary: A plugin bringing remote execution to the Foreman, completing the config