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.
         |