foreman_remote_execution 3.2.2 → 3.3.4

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc +5 -30
  3. data/.github/workflows/ci.yml +101 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop_todo.yml +3 -0
  6. data/Gemfile +1 -0
  7. data/app/assets/stylesheets/foreman_remote_execution/job_invocations.scss +6 -5
  8. data/app/controllers/api/v2/job_invocations_controller.rb +17 -0
  9. data/app/helpers/remote_execution_helper.rb +38 -33
  10. data/app/lib/actions/remote_execution/run_host_job.rb +3 -2
  11. data/app/models/remote_execution_provider.rb +5 -0
  12. data/app/services/default_proxy_proxy_selector.rb +20 -0
  13. data/app/views/api/v2/job_invocations/main.json.rabl +6 -0
  14. data/app/views/job_invocations/_card_target_hosts.html.erb +1 -1
  15. data/app/views/job_invocations/_tab_hosts.html.erb +3 -23
  16. data/app/views/job_invocations/index.html.erb +2 -1
  17. data/app/views/job_invocations/show.html.erb +0 -6
  18. data/app/views/job_invocations/show.json.erb +4 -0
  19. data/app/views/templates/ssh/package_action.erb +1 -0
  20. data/app/views/templates/ssh/puppet_agent_disable.erb +3 -0
  21. data/app/views/templates/ssh/puppet_agent_enable.erb +3 -0
  22. data/app/views/templates/ssh/puppet_install_modules_from_forge.erb +3 -0
  23. data/app/views/templates/ssh/puppet_run_once.erb +3 -0
  24. data/db/seeds.d/70-job_templates.rb +1 -1
  25. data/foreman_remote_execution.gemspec +4 -5
  26. data/lib/foreman_remote_execution/version.rb +1 -1
  27. data/locale/action_names.rb +0 -1
  28. data/package.json +16 -33
  29. data/webpack/__mocks__/foremanReact/common/I18n.js +1 -0
  30. data/webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js +3 -0
  31. data/webpack/__mocks__/foremanReact/constants.js +3 -0
  32. data/webpack/index.js +9 -7
  33. data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +52 -0
  34. data/webpack/react_app/components/TargetingHosts/TargetingHostsActions.js +8 -0
  35. data/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +1 -0
  36. data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +12 -0
  37. data/webpack/react_app/components/TargetingHosts/__tests__/HostItem.test.js +6 -0
  38. data/webpack/react_app/components/TargetingHosts/__tests__/HostStatus.test.js +6 -0
  39. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHosts.test.js +6 -0
  40. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostItem.test.js.snap +31 -0
  41. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostStatus.test.js.snap +12 -0
  42. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +81 -0
  43. data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +43 -0
  44. data/webpack/react_app/components/TargetingHosts/components/HostItem.js +39 -0
  45. data/webpack/react_app/components/TargetingHosts/components/HostStatus.js +54 -0
  46. data/webpack/react_app/components/TargetingHosts/index.js +37 -0
  47. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.js +10 -0
  48. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +6 -3
  49. data/webpack/react_app/components/jobInvocations/index.js +19 -7
  50. data/webpack/react_app/redux/actions/jobInvocations/index.js +12 -8
  51. data/webpack/react_app/redux/consts.js +1 -2
  52. data/webpack/react_app/redux/reducers/jobInvocations/index.fixtures.js +8 -40
  53. data/webpack/react_app/redux/reducers/jobInvocations/index.test.js +17 -11
  54. data/webpack/test_setup.js +2 -1
  55. metadata +26 -12
  56. data/.hound.yml +0 -19
  57. data/.travis.yml +0 -6
  58. data/app/views/job_invocations/_host_actions_td.html.erb +0 -3
  59. data/app/views/job_invocations/_host_name_td.html.erb +0 -8
  60. data/app/views/job_invocations/_host_status_td.html.erb +0 -1
  61. data/app/views/job_invocations/show.js.erb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19c80592b90b1ba20ac213bae2ff36e9608202161e29718a063d3ac52ed249b3
4
- data.tar.gz: daa58fc0cfe1a9b788a49372c1d200579502331ef9e1f1424fa4a81270898a8b
3
+ metadata.gz: af3cd3861fdf9ca798dff28f5da9441110f6442b9cb156604325c0c535dd7d27
4
+ data.tar.gz: 24188a80f5231e8d15826d85322ff410a23bb62cded5a6ab5d30a87063eb8b85
5
5
  SHA512:
6
- metadata.gz: c3b9ee1922a5ece712f8f431eb84de7ab5793d5108fe4959ce690a0acd6edb14f21ab5135cc1b7bcf37d3fbe81af45731fe1900e1803683c131bc3dba13aa931
7
- data.tar.gz: 5db356770b3017319ef2e4f0d1a16598025c9227b540661b2f3e6104f2bd522dbbfb351266eae0bc63d19667a2edc079de0136b50e648e6abddf973bbbe58ead
6
+ metadata.gz: bdb4f63c31a63b4a54268f7fa36174cdf11ba90393159862370b29679d90a46ea5e0b8f86a0d98708166fa89fc0af0223dd4f93990fd0bc6e12c9dac473ab6e2
7
+ data.tar.gz: 4c03fa3ead970d7817e00e884a32857a5a17ae1e41462e40db4057a57f4def05f04d6ad1a16829807a883482d3a684fe344b06e367c72a99cc13c1faacb7b673
data/.eslintrc CHANGED
@@ -1,32 +1,7 @@
1
1
  {
2
- "root": true,
3
- "extends": ["airbnb-base", "./node_modules/@theforeman/vendor-dev/eslint.extends.js"],
4
- "plugins": [
5
- "react"
6
- ],
7
- "env": {
8
- "browser": true,
9
- "es6": true,
10
- "node": true,
11
- "jasmine": true,
12
- "jest": true
13
- },
14
- "parser": "babel-eslint",
15
- "rules": {
16
- "react/jsx-uses-vars": "error",
17
- "react/jsx-uses-react": "error",
18
- "no-unused-vars": [
19
- "error",
20
- {
21
- "vars": "all",
22
- "args": "none"
23
- }
24
- ],
25
- "no-underscore-dangle": "off",
26
- "no-use-before-define": "off",
27
- "import/prefer-default-export": "off",
28
- // Import rules off for now due to HoundCI issue
29
- "import/no-unresolved": "off",
30
- "import/extensions": "off"
31
- }
2
+ "plugins": ["@theforeman/foreman"],
3
+ "extends": [
4
+ "plugin:@theforeman/foreman/core",
5
+ "plugin:@theforeman/foreman/plugins"
6
+ ]
32
7
  }
@@ -0,0 +1,101 @@
1
+ name: CI
2
+ on: [pull_request]
3
+ env:
4
+ RAILS_ENV: test
5
+ DATABASE_URL: postgresql://postgres:@localhost/test
6
+ DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: true
7
+ jobs:
8
+ rubocop:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - name: Setup Ruby
13
+ uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.6
16
+ - name: Setup
17
+ run: |
18
+ gem install bundler
19
+ bundle install --jobs=3 --retry=3
20
+ - name: Run rubocop
21
+ run: bundle exec rubocop
22
+ test_ruby:
23
+ runs-on: ubuntu-latest
24
+ needs: rubocop
25
+ services:
26
+ postgres:
27
+ image: postgres:12.1
28
+ ports: ['5432:5432']
29
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ foreman-core-branch: [2.1-stable, develop]
34
+ ruby-version: [2.5, 2.6]
35
+ node-version: [12]
36
+ steps:
37
+ - run: sudo apt-get update
38
+ - run: sudo apt-get install build-essential libcurl4-openssl-dev zlib1g-dev libpq-dev
39
+ - uses: actions/checkout@v2
40
+ with:
41
+ repository: theforeman/foreman
42
+ ref: ${{ matrix.foreman-core-branch }}
43
+ - uses: actions/checkout@v2
44
+ with:
45
+ path: foreman_remote_execution
46
+ - name: Setup Ruby
47
+ uses: ruby/setup-ruby@v1
48
+ with:
49
+ ruby-version: ${{ matrix.ruby-version }}
50
+ - name: Setup Node
51
+ uses: actions/setup-node@v1
52
+ with:
53
+ node-version: ${{ matrix.node-version }}
54
+ - uses: actions/cache@v1
55
+ with:
56
+ path: vendor/bundle
57
+ key: ${{ runner.os }}-fgems-${{ matrix.ruby-version }}-${{ hashFiles('Gemfile.lock') }}
58
+ restore-keys: |
59
+ ${{ runner.os }}-fgems-${{ matrix.ruby-version }}-
60
+ - name: Setup Bundler
61
+ run: |
62
+ echo "gem 'foreman_remote_execution', path: './foreman_remote_execution'" > bundler.d/foreman_remote_execution.local.rb
63
+ gem install bundler
64
+ bundle config set without journald development console libvirt
65
+ bundle config set path vendor/bundle
66
+ - name: Prepare test env
67
+ run: |
68
+ bundle install --jobs=3 --retry=3
69
+ bundle exec rake db:create
70
+ bundle exec rake db:migrate
71
+ - name: Run plugin tests
72
+ run: |
73
+ bundle exec rake test:foreman_remote_execution
74
+ bundle exec rake test TEST="test/unit/foreman/access_permissions_test.rb"
75
+ test_js:
76
+ runs-on: ubuntu-latest
77
+ needs: rubocop
78
+ strategy:
79
+ fail-fast: false
80
+ matrix:
81
+ ruby-version: [2.6]
82
+ node-version: [10, 12]
83
+ steps:
84
+ - uses: actions/checkout@v2
85
+ - name: Setup Ruby
86
+ uses: ruby/setup-ruby@v1
87
+ with:
88
+ ruby-version: ${{ matrix.ruby-version }}
89
+ - name: Setup Node
90
+ uses: actions/setup-node@v1
91
+ with:
92
+ node-version: ${{ matrix.node-version }}
93
+ - name: Nmp install
94
+ run: |
95
+ npm install
96
+ - name: Run plugin linter
97
+ run: |
98
+ npm run lint
99
+ - name: Run plugin tests
100
+ run: |
101
+ npm run test
data/.gitignore CHANGED
@@ -14,3 +14,4 @@ locale/*/*.po.time_stamp
14
14
  Gemfile.lock
15
15
  node_modules/
16
16
  package-lock.json
17
+ coverage/
@@ -6,6 +6,9 @@
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ Minitest/GlobalExpectations:
10
+ Enabled: false
11
+
9
12
  # Offense count: 2
10
13
  # Cop supports --auto-correct.
11
14
  # Configuration parameters: TreatCommentsAsGroupSeparators, Include.
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec :name => 'foreman_remote_execution'
4
4
 
5
+ gem 'rubocop', '~> 0.80.0'
5
6
  gem 'rubocop-minitest'
6
7
  gem 'rubocop-performance'
7
8
  gem 'rubocop-rails'
@@ -1,9 +1,6 @@
1
- div.infoblock {
2
- margin-bottom: 20px;
3
- line-height: 2;
4
-
1
+ .target-hosts-card {
5
2
  pre {
6
- margin-top: 5px;
3
+ white-space:pre-line;
7
4
  }
8
5
  }
9
6
 
@@ -29,3 +26,7 @@ div.infoblock {
29
26
  }
30
27
  }
31
28
  }
29
+
30
+ .text_warp{
31
+ word-wrap: break-word;
32
+ }
@@ -18,11 +18,17 @@ module Api
18
18
 
19
19
  api :GET, '/job_invocations/:id', N_('Show job invocation')
20
20
  param :id, :identifier, :required => true
21
+ param :host_status, :bool, required: false, desc: N_('Show Job status for the hosts')
21
22
  def show
22
23
  @hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
23
24
  @template_invocations = @job_invocation.template_invocations
24
25
  .where(host: @hosts)
25
26
  .includes(:input_values)
27
+
28
+ if params[:host_status] == 'true'
29
+ template_invocations = @template_invocations.includes(:run_host_job_task).to_a
30
+ @host_statuses = Hash[template_invocations.map { |ti| [ti.host_id, template_invocation_status(ti)] }]
31
+ end
26
32
  end
27
33
 
28
34
  def_param_group :job_invocation do
@@ -208,6 +214,17 @@ module Api
208
214
  def parent_scope
209
215
  resource_class.where(nil)
210
216
  end
217
+
218
+ def template_invocation_status(template_invocation)
219
+ task = template_invocation.try(:run_host_job_task)
220
+ parent_task = @job_invocation.task
221
+
222
+ return(parent_task.result == 'cancelled' ? 'cancelled' : 'N/A') if task.nil?
223
+ return task.state if task.state == 'running' || task.state == 'planned'
224
+ return 'error' if task.result == 'warning'
225
+
226
+ task.result
227
+ end
211
228
  end
212
229
  end
213
230
  end
@@ -14,43 +14,35 @@ module RemoteExecutionHelper
14
14
  end
15
15
 
16
16
  def template_invocation_status(task, parent_task)
17
- if task.nil?
18
- if parent_task.result == 'cancelled'
19
- icon_text('warning-triangle-o', _('cancelled'), :kind => 'pficon')
20
- else
21
- icon_text('question', 'N/A', :kind => 'fa')
22
- end
23
- elsif task.state == 'running'
24
- icon_text('running', _('running'), :kind => 'pficon')
25
- elsif task.state == 'planned'
26
- icon_text('build', _('planned'), :kind => 'pficon')
27
- else
28
- case task.result
29
- when 'warning', 'error'
30
- icon_text('error-circle-o', _('failed'), :kind => 'pficon')
31
- when 'cancelled'
32
- icon_text('warning-triangle-o', _('cancelled'), :kind => 'pficon')
33
- when 'success'
34
- icon_text('ok', _('success'), :kind => 'pficon')
35
- else
36
- task.result
37
- end
38
- end
17
+ return(parent_task.result == 'cancelled' ? _('cancelled') : 'N/A') if task.nil?
18
+ return task.state if task.state == 'running' || task.state == 'planned'
19
+ return _('error') if task.result == 'warning'
20
+
21
+ task.result
39
22
  end
40
23
 
41
24
  def template_invocation_actions(task, host, job_invocation, template_invocation)
25
+ links = []
42
26
  host_task = template_invocation.try(:run_host_job_task)
43
- [
44
- display_link_if_authorized(_('Host detail'), hash_for_host_path(host).merge(:auth_object => host, :permission => :view_hosts, :authorizer => job_hosts_authorizer)),
45
- display_link_if_authorized(_('Rerun on %s') % host.name, hash_for_rerun_job_invocation_path(:id => job_invocation, :host_ids => [ host.id ], :authorizer => job_hosts_authorizer)),
46
- if host_task.present?
47
- display_link_if_authorized(
48
- _('Host task'),
49
- hash_for_foreman_tasks_task_path(host_task)
50
- .merge(:auth_object => host_task, :permission => :view_foreman_tasks)
51
- )
52
- end,
53
- ]
27
+
28
+ if authorized_for(hash_for_host_path(host).merge(auth_object: host, permission: :view_hosts, authorizer: job_hosts_authorizer))
29
+ links << { title: _('Host detail'),
30
+ action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
31
+ end
32
+
33
+ if authorized_for(hash_for_rerun_job_invocation_path(id: job_invocation, host_ids: [ host.id ], authorizer: job_hosts_authorizer))
34
+ links << { title: (_('Rerun on %s') % host.name),
35
+ action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
36
+ 'data-method': 'get', id: "#{host.name}-actions-rerun" } }
37
+ end
38
+
39
+ if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
40
+ links << { title: _('Host task'),
41
+ action: { href: foreman_tasks_task_path(host_task),
42
+ 'data-method': 'get', id: "#{host.name}-actions-task" } }
43
+ end
44
+
45
+ links
54
46
  end
55
47
 
56
48
  def remote_execution_provider_for(template_invocation)
@@ -237,4 +229,17 @@ module RemoteExecutionHelper
237
229
 
238
230
  task.execution_plan.actions[1].try(:input).try(:[], 'script')
239
231
  end
232
+
233
+ def targeting_hosts(job_invocation, hosts)
234
+ hosts.map do |host|
235
+ template_invocation = job_invocation.template_invocations.find { |template_inv| template_inv.host_id == host.id }
236
+ task = template_invocation.try(:run_host_job_task)
237
+ link_authorized = !task.nil? && authorized_for(hash_for_template_invocation_path(:id => template_invocation).merge(:auth_object => host, :permission => :view_hosts, :authorizer => job_hosts_authorizer))
238
+
239
+ { name: host.name,
240
+ link: link_authorized ? template_invocation_path(:id => template_invocation) : '',
241
+ status: template_invocation_status(task, job_invocation.task),
242
+ actions: template_invocation_actions(task, host, job_invocation, template_invocation) }
243
+ end
244
+ end
240
245
  end
@@ -29,6 +29,9 @@ module Actions
29
29
 
30
30
  raise _('Could not use any template used in the job invocation') if template_invocation.blank?
31
31
 
32
+ provider = template_invocation.template.provider
33
+ proxy_selector = provider.required_proxy_selector_for(template_invocation.template) || proxy_selector
34
+
32
35
  provider_type = template_invocation.template.provider_type.to_s
33
36
  proxy = determine_proxy!(proxy_selector, provider_type, host)
34
37
 
@@ -36,8 +39,6 @@ module Actions
36
39
  script = renderer.render
37
40
  raise _('Failed rendering template: %s') % renderer.error_message unless script
38
41
 
39
- provider = template_invocation.template.provider
40
-
41
42
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
42
43
  :script => script,
43
44
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
@@ -98,5 +98,10 @@ class RemoteExecutionProvider
98
98
  def proxy_action_class
99
99
  ForemanRemoteExecutionCore::Actions::RunScript
100
100
  end
101
+
102
+ # Return a specific proxy selector to use for running a given template
103
+ # Returns either nil to use the default selector or an instance of a (sub)class of ::ForemanTasks::ProxySelector
104
+ def required_proxy_selector_for(_template)
105
+ end
101
106
  end
102
107
  end
@@ -0,0 +1,20 @@
1
+ class DefaultProxyProxySelector < ::RemoteExecutionProxySelector
2
+ def initialize
3
+ # TODO: Remove this once we have a reliable way of determining the internal proxy without katello
4
+ # Tracked as https://projects.theforeman.org/issues/29840
5
+ raise _('Internal proxy selector can only be used if Katello is enabled') unless defined?(::Katello)
6
+
7
+ super
8
+ end
9
+
10
+ def available_proxies(host, provider)
11
+ # TODO: Once we have a internal proxy marker/feature on the proxy, we can
12
+ # swap the implementation
13
+ raise _('default_capsule method missing from SmartProxy') unless ::SmartProxy.respond_to?(:default_capsule)
14
+
15
+ internal_proxy = ::SmartProxy.default_capsule
16
+ super.reduce({}) do |acc, (key, proxies)|
17
+ acc.merge(key => proxies.select { |proxy| proxy == internal_proxy })
18
+ end
19
+ end
20
+ end
@@ -21,6 +21,12 @@ child :targeting do
21
21
 
22
22
  child @hosts do
23
23
  extends 'api/v2/hosts/base'
24
+
25
+ if params[:host_status] == 'true'
26
+ node :job_status do |host|
27
+ @host_statuses[host.id]
28
+ end
29
+ end
24
30
  end
25
31
  end
26
32
 
@@ -1,5 +1,5 @@
1
1
  <% template_invocations = job_invocation.pattern_template_invocations %>
2
- <div class="card-pf card-pf-accented">
2
+ <div class="card-pf card-pf-accented target-hosts-card">
3
3
  <div class="card-pf-title">
4
4
  <h2 style="height: 18px;" class="card-pf-title">
5
5
  <%= _('Target hosts') %>
@@ -15,29 +15,9 @@
15
15
  <% end %>
16
16
  <br>
17
17
 
18
- <table class="<%= table_css_classes('table-condensed') %>">
19
- <thead>
20
- <tr>
21
- <th><%= sort :name, :as => _('Host') %></th>
22
- <th><%= _('Status') %></th>
23
- <th><%= _('Actions') %></th>
24
- </tr>
25
- </thead>
26
-
27
- <tbody>
28
- <% hosts.each do |host| %>
29
- <% template_invocation = job_invocation.template_invocations.find { |template_invocation| template_invocation.host_id == host.id } %>
30
- <% task = template_invocation.try(:run_host_job_task) %>
31
- <tr>
32
- <% options = { :host => host, :task => task, :job_invocation => job_invocation, :template_invocation => template_invocation } %>
33
- <%= render 'host_name_td', options %>
34
- <%= render 'host_status_td', options %>
35
- <%= render 'host_actions_td', options %>
36
- </tr>
37
- <% end %>
38
- </tbody>
39
- </table>
40
-
18
+ <div id="targeting_hosts">
19
+ <%= mount_react_component('TargetingHosts', '#targeting_hosts') %>
20
+ </div>
41
21
  <%= will_paginate_with_info @hosts, :container => true %>
42
22
  <% else %>
43
23
  <div class="alert alert-warning">
@@ -1,3 +1,4 @@
1
+ <% stylesheet 'foreman_remote_execution/foreman_remote_execution' %>
1
2
  <% title _('Job invocations') %>
2
3
 
3
4
  <% title_actions(job_invocations_buttons) %>
@@ -19,7 +20,7 @@
19
20
  <tbody>
20
21
  <% @job_invocations.each do |invocation| %>
21
22
  <tr>
22
- <td><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
23
+ <td class="text_warp"><%= link_to_if_authorized invocation_description(invocation), hash_for_job_invocation_path(invocation).merge(:auth_object => invocation, :permission => :view_job_invocations, :authorizer => authorizer) %></td>
23
24
  <td><%= trunc_with_tooltip(invocation&.targeting&.search_query, 15) %></td>
24
25
  <td><%= link_to_invocation_task_if_authorized(invocation) %></td>
25
26
  <td><%= invocation_result(invocation, :success_count) %></td>
@@ -41,9 +41,3 @@
41
41
  <% end %>
42
42
  <%= render_tab_content_for(:main_tabs, subject: @job_invocation) %>
43
43
  </div>
44
-
45
- <script id="job_invocation_refresh" data-refresh-url="<%= job_invocation_path(@job_invocation) %>">
46
- <% if @auto_refresh %>
47
- delayed_refresh($('script#job_invocation_refresh').data('refresh-url'), job_invocation_refresh_data());
48
- <% end %>
49
- </script>