foreman_remote_execution 16.0.1 → 16.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/template_invocations_controller.rb +9 -1
- data/app/views/templates/script/package_action.erb +5 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/webpack/JobInvocationDetail/JobAdditionInfo.js +1 -1
- data/webpack/JobInvocationDetail/TemplateInvocation.js +15 -4
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/OutputCodeBlock.js +49 -8
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/TemplateActionButtons.js +2 -4
- data/webpack/JobInvocationDetail/TemplateInvocationComponents/index.scss +8 -2
- data/webpack/JobInvocationDetail/TemplateInvocationPage.js +2 -0
- data/webpack/JobInvocationDetail/__tests__/TemplateInvocation.test.js +7 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40f170b56effb3a3f7f415ac040d45242641bc3210266bf723d957a4337e8aa2
|
4
|
+
data.tar.gz: c6a8e5a792629049aa8e40fdc2a3b6b20cc182af3e0e3d1578e5e3614525e7d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b47defe2e23b8ce3ac1ebc08164749d7c2a03a133f78dfcb702c76cd4f434786b0dbeceb72c2d7ca47cedbd5d1f688175b9e0680245eac4e1064f9739d2b68c
|
7
|
+
data.tar.gz: c0d069ef4dde5b1a07cb38b60f0dcc7218487cc58a5a2961039f2603517a2fdc5060b603b4a13b5ff4c659a13b9b25eb836b59e5e3d6efbe1e69202364cc0f87
|
@@ -38,9 +38,17 @@ class TemplateInvocationsController < ApplicationController
|
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
41
|
+
smart_proxy = @template_invocation.smart_proxy
|
42
|
+
if smart_proxy
|
43
|
+
proxy = {
|
44
|
+
name: smart_proxy.name,
|
45
|
+
href: smart_proxy_path(smart_proxy),
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
41
49
|
auto_refresh = @job_invocation.task.try(:pending?)
|
42
50
|
finished = @job_invocation.status_label == 'failed' || @job_invocation.status_label == 'succeeded' || @job_invocation.status_label == 'cancelled'
|
43
|
-
render :json => { :output => lines, :preview => template_invocation_preview(@template_invocation, @host), :input_values => transformed_input_values, :job_invocation_description => @job_invocation.description, :task_id => @template_invocation_task.id, :task_cancellable => @template_invocation_task.cancellable?, :host_name => @host.name, :permissions => {
|
51
|
+
render :json => { :output => lines, :preview => template_invocation_preview(@template_invocation, @host), :proxy => proxy, :input_values => transformed_input_values, :job_invocation_description => @job_invocation.description, :task_id => @template_invocation_task.id, :task_cancellable => @template_invocation_task.cancellable?, :host_name => @host.name, :permissions => {
|
44
52
|
:view_foreman_tasks => User.current.allowed_to?(:view_foreman_tasks),
|
45
53
|
:cancel_job_invocations => User.current.allowed_to?(:cancel_job_invocations),
|
46
54
|
:execute_jobs => User.current.allowed_to?(:create_job_invocations) && (!@host.infrastructure_host? || User.current.can?(:execute_jobs_on_infrastructure_hosts)),
|
@@ -105,7 +105,11 @@ handle_zypp_res_codes () {
|
|
105
105
|
|
106
106
|
# Action
|
107
107
|
<% if package_manager == 'yum' -%>
|
108
|
-
|
108
|
+
<% if @host.respond_to?(:yum_or_yum_transient) -%>
|
109
|
+
<%= @host.yum_or_yum_transient %> -y <%= input("options") %> <%= action %> <%= input("package") %>
|
110
|
+
<% else -%>
|
111
|
+
yum -y <%= input("options") %> <%= action %> <%= input("package") %>
|
112
|
+
<% end -%>
|
109
113
|
<% elsif package_manager == 'apt' -%>
|
110
114
|
<%-
|
111
115
|
action = 'install' if action == 'group install'
|
@@ -169,7 +169,7 @@ const Inputs = ({ data }) => {
|
|
169
169
|
const inputs =
|
170
170
|
data?.pattern_template_invocations?.[0]?.template_invocation_input_values;
|
171
171
|
|
172
|
-
if (!inputs) return null;
|
172
|
+
if (!inputs || inputs.length === 0) return null;
|
173
173
|
return (
|
174
174
|
<ExpandableSection
|
175
175
|
toggleText={__('User Inputs')}
|
@@ -53,6 +53,7 @@ export const TemplateInvocation = ({
|
|
53
53
|
jobID,
|
54
54
|
isInTableView,
|
55
55
|
hostName,
|
56
|
+
hostProxy,
|
56
57
|
}) => {
|
57
58
|
const templateURL = showTemplateInvocationUrl(hostID, jobID);
|
58
59
|
const hostDetailsPageUrl = useForemanHostDetailsPageUrl();
|
@@ -150,10 +151,18 @@ export const TemplateInvocation = ({
|
|
150
151
|
permissions={permissions}
|
151
152
|
/>
|
152
153
|
{!isInTableView && (
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
154
|
+
<>
|
155
|
+
<div>
|
156
|
+
{__('Target:')}{' '}
|
157
|
+
<a href={`${hostDetailsPageUrl}${hostName}`}>{hostName}</a>{' '}
|
158
|
+
{!!hostProxy && (
|
159
|
+
<>
|
160
|
+
{__('using Smart Proxy')}{' '}
|
161
|
+
<a href={hostProxy.href}>{hostProxy.name}</a>
|
162
|
+
</>
|
163
|
+
)}
|
164
|
+
</div>
|
165
|
+
</>
|
157
166
|
)}
|
158
167
|
{showTemplatePreview && <PreviewTemplate inputValues={inputValues} />}
|
159
168
|
{showCommand && (
|
@@ -185,6 +194,7 @@ export const TemplateInvocation = ({
|
|
185
194
|
TemplateInvocation.propTypes = {
|
186
195
|
hostID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
187
196
|
hostName: PropTypes.string, // only used when isInTableView is false
|
197
|
+
hostProxy: PropTypes.object, // only used when isInTableView is false
|
188
198
|
jobID: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
189
199
|
isInTableView: PropTypes.bool,
|
190
200
|
};
|
@@ -192,6 +202,7 @@ TemplateInvocation.propTypes = {
|
|
192
202
|
TemplateInvocation.defaultProps = {
|
193
203
|
isInTableView: true,
|
194
204
|
hostName: '',
|
205
|
+
hostProxy: {},
|
195
206
|
};
|
196
207
|
|
197
208
|
CopyToClipboard.propTypes = {
|
@@ -1,4 +1,9 @@
|
|
1
|
-
import React
|
1
|
+
import React, {
|
2
|
+
useEffect,
|
3
|
+
useLayoutEffect,
|
4
|
+
useState,
|
5
|
+
useCallback,
|
6
|
+
} from 'react';
|
2
7
|
import PropTypes from 'prop-types';
|
3
8
|
import { Button } from '@patternfly/react-core';
|
4
9
|
import { translate as __ } from 'foremanReact/common/I18n';
|
@@ -55,6 +60,48 @@ export const OutputCodeBlock = ({ code, showOutputType, scrollElement }) => {
|
|
55
60
|
const filteredCode = code.filter(
|
56
61
|
({ output_type: outputType }) => showOutputType[outputType]
|
57
62
|
);
|
63
|
+
|
64
|
+
const scrollElementSelected = useCallback(
|
65
|
+
() => document.querySelector(scrollElement),
|
66
|
+
[scrollElement]
|
67
|
+
);
|
68
|
+
const onClickScrollToTop = useCallback(() => {
|
69
|
+
if (scrollElementSelected()) scrollElementSelected().scrollTo(0, 0);
|
70
|
+
}, [scrollElementSelected]);
|
71
|
+
|
72
|
+
const onClickScrollToBottom = useCallback(() => {
|
73
|
+
if (scrollElementSelected())
|
74
|
+
scrollElementSelected().scrollTo(0, scrollElementSelected().scrollHeight);
|
75
|
+
}, [scrollElementSelected]);
|
76
|
+
|
77
|
+
const [isScrolledBottom, setIsScrolledBottom] = useState(true);
|
78
|
+
useEffect(() => {
|
79
|
+
const element = scrollElementSelected();
|
80
|
+
const onScroll = () => {
|
81
|
+
if (
|
82
|
+
Math.abs(
|
83
|
+
element.scrollHeight - element.scrollTop - element.clientHeight
|
84
|
+
) < 1
|
85
|
+
)
|
86
|
+
setIsScrolledBottom(true);
|
87
|
+
else setIsScrolledBottom(false);
|
88
|
+
};
|
89
|
+
if (element) {
|
90
|
+
element.addEventListener('scroll', onScroll);
|
91
|
+
}
|
92
|
+
return () => {
|
93
|
+
if (element) {
|
94
|
+
element.removeEventListener('scroll', onScroll);
|
95
|
+
}
|
96
|
+
};
|
97
|
+
}, [scrollElementSelected]);
|
98
|
+
|
99
|
+
useLayoutEffect(() => {
|
100
|
+
if (isScrolledBottom) {
|
101
|
+
onClickScrollToBottom();
|
102
|
+
}
|
103
|
+
}, [code.length, isScrolledBottom, onClickScrollToBottom]);
|
104
|
+
|
58
105
|
if (!filteredCode.length) {
|
59
106
|
return <div>{__('No output for the selected filters')}</div>;
|
60
107
|
}
|
@@ -81,13 +128,7 @@ export const OutputCodeBlock = ({ code, showOutputType, scrollElement }) => {
|
|
81
128
|
);
|
82
129
|
});
|
83
130
|
});
|
84
|
-
|
85
|
-
const onClickScrollToTop = () => {
|
86
|
-
scrollElementSeleceted().scrollTo(0, 0);
|
87
|
-
};
|
88
|
-
const onClickScrollToBottom = () => {
|
89
|
-
scrollElementSeleceted().scrollTo(0, scrollElementSeleceted().scrollHeight);
|
90
|
-
};
|
131
|
+
|
91
132
|
return (
|
92
133
|
<div className="invocation-output">
|
93
134
|
<Button
|
@@ -102,12 +102,10 @@ export const RowActions = ({ hostID, jobID }) => {
|
|
102
102
|
.map(({ text, href, onClick, permission, isDisabled }) =>
|
103
103
|
permission
|
104
104
|
? {
|
105
|
-
title: text
|
106
|
-
component: 'a',
|
107
|
-
className: 'jobs-table-action-item',
|
108
|
-
href,
|
105
|
+
title: <a href={href}>{text}</a>,
|
109
106
|
onClick,
|
110
107
|
isDisabled,
|
108
|
+
className: 'jobs-table-action-item',
|
111
109
|
}
|
112
110
|
: null
|
113
111
|
)
|
@@ -15,6 +15,7 @@ const TemplateInvocationPage = ({
|
|
15
15
|
const {
|
16
16
|
job_invocation_description: jobDescription,
|
17
17
|
host_name: hostName,
|
18
|
+
proxy: hostProxy,
|
18
19
|
} = useSelector(selectTemplateInvocation);
|
19
20
|
const description = sprintf(__('Template Invocation for %s'), hostName);
|
20
21
|
const breadcrumbOptions = {
|
@@ -36,6 +37,7 @@ const TemplateInvocationPage = ({
|
|
36
37
|
jobID={jobID}
|
37
38
|
isInTableView={false}
|
38
39
|
hostName={hostName}
|
40
|
+
hostProxy={hostProxy}
|
39
41
|
/>
|
40
42
|
</PageLayout>
|
41
43
|
);
|
@@ -32,12 +32,16 @@ describe('TemplateInvocation', () => {
|
|
32
32
|
jobID="1"
|
33
33
|
isInTableView={false}
|
34
34
|
hostName="example-host"
|
35
|
+
hostProxy={{ name: 'example-proxy', href: '#' }}
|
35
36
|
/>
|
36
37
|
</Provider>
|
37
38
|
);
|
38
39
|
|
39
|
-
expect(screen.getByText('Target:')).toBeInTheDocument();
|
40
40
|
expect(screen.getByText('example-host')).toBeInTheDocument();
|
41
|
+
expect(screen.getByText('example-proxy')).toBeInTheDocument();
|
42
|
+
|
43
|
+
expect(screen.getByText(/using Smart Proxy/)).toBeInTheDocument();
|
44
|
+
expect(screen.getByText(/Target:/)).toBeInTheDocument();
|
41
45
|
|
42
46
|
expect(screen.getByText('This is red text')).toBeInTheDocument();
|
43
47
|
expect(screen.getByText('This is default text')).toBeInTheDocument();
|
@@ -50,6 +54,7 @@ describe('TemplateInvocation', () => {
|
|
50
54
|
jobID="1"
|
51
55
|
isInTableView={false}
|
52
56
|
hostName="example-host"
|
57
|
+
hostProxy={{ name: 'example-proxy', href: '#' }}
|
53
58
|
/>
|
54
59
|
</Provider>
|
55
60
|
);
|
@@ -101,6 +106,7 @@ describe('TemplateInvocation', () => {
|
|
101
106
|
jobID="1"
|
102
107
|
isInTableView={false}
|
103
108
|
hostName="example-host"
|
109
|
+
hostProxy={{ name: 'example-proxy', href: '#' }}
|
104
110
|
/>
|
105
111
|
);
|
106
112
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_remote_execution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 16.0.
|
4
|
+
version: 16.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Remote Execution team
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-05-13 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: deface
|
@@ -92,8 +92,8 @@ email:
|
|
92
92
|
executables: []
|
93
93
|
extensions: []
|
94
94
|
extra_rdoc_files:
|
95
|
-
- README.md
|
96
95
|
- LICENSE
|
96
|
+
- README.md
|
97
97
|
files:
|
98
98
|
- LICENSE
|
99
99
|
- README.md
|
@@ -605,7 +605,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
605
605
|
- !ruby/object:Gem::Version
|
606
606
|
version: '0'
|
607
607
|
requirements: []
|
608
|
-
rubygems_version: 3.6.
|
608
|
+
rubygems_version: 3.6.7
|
609
609
|
specification_version: 4
|
610
610
|
summary: A plugin bringing remote execution to the Foreman, completing the config
|
611
611
|
management functionality with remote management functionality.
|