foreman_remote_execution 4.3.1 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +16 -0
  3. data/app/controllers/foreman_remote_execution/concerns/api/v2/registration_commands_controller_extensions.rb +19 -0
  4. data/app/helpers/remote_execution_helper.rb +27 -0
  5. data/app/lib/foreman_remote_execution/provider_input.rb +29 -0
  6. data/app/models/invocation_provider_input_value.rb +12 -0
  7. data/app/models/job_invocation.rb +4 -0
  8. data/app/models/job_invocation_composer.rb +13 -0
  9. data/app/models/remote_execution_provider.rb +17 -2
  10. data/app/models/setting/remote_execution.rb +10 -0
  11. data/app/models/template_invocation.rb +2 -0
  12. data/app/services/renderer_methods.rb +12 -0
  13. data/app/views/job_invocations/_form.html.erb +8 -0
  14. data/db/migrate/20210312074713_add_provider_inputs.rb +10 -0
  15. data/foreman_remote_execution.gemspec +1 -1
  16. data/lib/foreman_remote_execution/engine.rb +5 -6
  17. data/lib/foreman_remote_execution/version.rb +1 -1
  18. data/locale/action_names.rb +1 -0
  19. data/locale/de/foreman_remote_execution.po +77 -27
  20. data/locale/en/foreman_remote_execution.po +77 -27
  21. data/locale/en_GB/foreman_remote_execution.po +77 -27
  22. data/locale/es/foreman_remote_execution.po +77 -27
  23. data/locale/foreman_remote_execution.pot +241 -163
  24. data/locale/fr/foreman_remote_execution.po +77 -27
  25. data/locale/ja/foreman_remote_execution.po +77 -27
  26. data/locale/ko/foreman_remote_execution.po +77 -27
  27. data/locale/pt_BR/foreman_remote_execution.po +77 -27
  28. data/locale/ru/foreman_remote_execution.po +77 -27
  29. data/locale/zh_CN/foreman_remote_execution.po +77 -27
  30. data/locale/zh_TW/foreman_remote_execution.po +77 -27
  31. data/package.json +3 -2
  32. data/test/helpers/remote_execution_helper_test.rb +16 -0
  33. data/test/unit/job_invocation_composer_test.rb +41 -1
  34. data/test/unit/job_invocation_report_template_test.rb +57 -0
  35. data/webpack/JobWizard/JobWizard.js +30 -7
  36. data/webpack/JobWizard/JobWizard.scss +12 -0
  37. data/webpack/JobWizard/JobWizardConstants.js +5 -0
  38. data/webpack/JobWizard/JobWizardSelectors.js +21 -0
  39. data/webpack/JobWizard/__tests__/JobWizard.test.js +20 -0
  40. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +83 -0
  41. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +77 -0
  42. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +45 -0
  43. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +64 -0
  44. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +86 -0
  45. data/webpack/JobWizard/steps/form/GroupedSelectField.js +88 -0
  46. data/webpack/JobWizard/steps/form/SelectField.js +39 -0
  47. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +38 -0
  48. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +23 -0
  49. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +36 -0
  50. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +22 -0
  51. data/webpack/__mocks__/foremanReact/common/helpers.js +1 -0
  52. data/webpack/__mocks__/foremanReact/redux/API/index.js +5 -0
  53. data/webpack/__mocks__/foremanReact/routes/common/PageLayout/PageLayout.js +10 -0
  54. data/webpack/fills_index.js +11 -0
  55. data/webpack/global_index.js +4 -0
  56. data/webpack/index.js +0 -4
  57. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +87 -0
  58. data/webpack/react_app/components/RecentJobsCard/constants.js +1 -0
  59. data/webpack/react_app/components/RecentJobsCard/index.js +1 -0
  60. data/webpack/react_app/components/RecentJobsCard/styles.css +15 -0
  61. data/webpack/react_app/components/RegistrationExtension/RexInterface.js +50 -0
  62. data/webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js +9 -0
  63. data/webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap +35 -0
  64. data/webpack/react_app/extend/fills.js +10 -0
  65. data/webpack/react_app/extend/reducers.js +4 -0
  66. metadata +39 -5
  67. data/app/views/api/v2/registration/_form.html.erb +0 -12
@@ -0,0 +1,39 @@
1
+ import React, { useState } from 'react';
2
+ import { FormGroup, Select, SelectOption } from '@patternfly/react-core';
3
+ import PropTypes from 'prop-types';
4
+
5
+ export const SelectField = ({ label, fieldId, options, value, setValue }) => {
6
+ const onSelect = (event, selection) => {
7
+ setValue(selection);
8
+ setIsOpen(false);
9
+ };
10
+ const [isOpen, setIsOpen] = useState(false);
11
+ return (
12
+ <FormGroup label={label} fieldId={fieldId}>
13
+ <Select
14
+ selections={value}
15
+ onSelect={onSelect}
16
+ onToggle={setIsOpen}
17
+ isOpen={isOpen}
18
+ className="without_select2"
19
+ maxHeight="45vh"
20
+ >
21
+ {options.map((option, index) => (
22
+ <SelectOption key={index} value={option} />
23
+ ))}
24
+ </Select>
25
+ </FormGroup>
26
+ );
27
+ };
28
+ SelectField.propTypes = {
29
+ label: PropTypes.string.isRequired,
30
+ fieldId: PropTypes.string.isRequired,
31
+ options: PropTypes.array,
32
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
33
+ setValue: PropTypes.func.isRequired,
34
+ };
35
+
36
+ SelectField.defaultProps = {
37
+ options: [],
38
+ value: null,
39
+ };
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import * as patternfly from '@patternfly/react-core';
3
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
+ import { GroupedSelectField } from '../GroupedSelectField';
5
+
6
+ jest.spyOn(patternfly, 'Select');
7
+ jest.spyOn(patternfly, 'SelectOption');
8
+ patternfly.Select.mockImplementation(props => <div>{props}</div>);
9
+ patternfly.SelectOption.mockImplementation(props => <div>{props}</div>);
10
+
11
+ const fixtures = {
12
+ 'renders with props': {
13
+ label: 'grouped select',
14
+ fieldId: 'field-id',
15
+ groups: [
16
+ {
17
+ groupLabel: 'Ansible',
18
+ options: [
19
+ {
20
+ label: 'Ansible Roles - Ansible Default',
21
+ value: 168,
22
+ },
23
+ {
24
+ label: 'Ansible Roles - Install from git',
25
+ value: 170,
26
+ },
27
+ ],
28
+ },
29
+ ],
30
+ selected: 170,
31
+ setSelected: jest.fn(),
32
+ },
33
+ };
34
+
35
+ describe('GroupedSelectField', () => {
36
+ describe('rendering', () =>
37
+ testComponentSnapshotsWithFixtures(GroupedSelectField, fixtures));
38
+ });
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import * as patternfly from '@patternfly/react-core';
3
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
+ import { SelectField } from '../SelectField';
5
+
6
+ jest.spyOn(patternfly, 'Select');
7
+ jest.spyOn(patternfly, 'SelectOption');
8
+ patternfly.Select.mockImplementation(props => <div>{props}</div>);
9
+ patternfly.SelectOption.mockImplementation(props => <div>{props}</div>);
10
+ const fixtures = {
11
+ 'renders with props': {
12
+ label: 'grouped select',
13
+ fieldId: 'field-id',
14
+ options: ['Commands'],
15
+ value: 'Commands',
16
+ setValue: jest.fn(),
17
+ },
18
+ };
19
+
20
+ describe('SelectField', () => {
21
+ describe('rendering', () =>
22
+ testComponentSnapshotsWithFixtures(SelectField, fixtures));
23
+ });
@@ -0,0 +1,36 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`GroupedSelectField rendering renders with props 1`] = `
4
+ <FormGroup
5
+ fieldId="field-id"
6
+ label="grouped select"
7
+ >
8
+ <mockConstructor
9
+ className="without_select2"
10
+ isGrouped={true}
11
+ isOpen={false}
12
+ onClear={[Function]}
13
+ onFilter={[Function]}
14
+ onSelect={[Function]}
15
+ onToggle={[Function]}
16
+ selections={170}
17
+ variant="typeahead"
18
+ >
19
+ <SelectGroup
20
+ key="0"
21
+ label="Ansible"
22
+ >
23
+ <mockConstructor
24
+ key="0"
25
+ onClick={[Function]}
26
+ value="Ansible Roles - Ansible Default"
27
+ />
28
+ <mockConstructor
29
+ key="1"
30
+ onClick={[Function]}
31
+ value="Ansible Roles - Install from git"
32
+ />
33
+ </SelectGroup>
34
+ </mockConstructor>
35
+ </FormGroup>
36
+ `;
@@ -0,0 +1,22 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`SelectField rendering renders with props 1`] = `
4
+ <FormGroup
5
+ fieldId="field-id"
6
+ label="grouped select"
7
+ >
8
+ <mockConstructor
9
+ className="without_select2"
10
+ isOpen={false}
11
+ maxHeight="45vh"
12
+ onSelect={[Function]}
13
+ onToggle={[Function]}
14
+ selections="Commands"
15
+ >
16
+ <mockConstructor
17
+ key="0"
18
+ value="Commands"
19
+ />
20
+ </mockConstructor>
21
+ </FormGroup>
22
+ `;
@@ -0,0 +1 @@
1
+ export const foremanUrl = path => `foreman${path}`;
@@ -0,0 +1,5 @@
1
+ export const API = {
2
+ get: jest.fn(),
3
+ };
4
+
5
+ export const get = data => ({ type: 'get-some-type', ...data });
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const PageLayout = ({ children }) => <div>{children}</div>;
5
+
6
+ PageLayout.propTypes = {
7
+ children: PropTypes.node.isRequired,
8
+ };
9
+
10
+ export default PageLayout;
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
+
4
+ import RexInterface from './react_app/components/RegistrationExtension/RexInterface';
5
+
6
+ addGlobalFill(
7
+ 'registrationAdvanced',
8
+ 'foreman-remote-exectuion-rex-interface',
9
+ <RexInterface key="registration-rex-interface" />,
10
+ 100
11
+ );
@@ -1,4 +1,8 @@
1
1
  import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
2
  import routes from './Routes/routes';
3
+ import registerReducers from './react_app/extend/reducers';
4
+ import registerFills from './react_app/extend/fills';
3
5
 
4
6
  registerRoutes('foreman_remote_execution', routes);
7
+ registerReducers();
8
+ registerFills();
data/webpack/index.js CHANGED
@@ -1,8 +1,6 @@
1
- import { registerReducer } from 'foremanReact/common/MountingService';
2
1
  import componentRegistry from 'foremanReact/components/componentRegistry';
3
2
  import JobInvocationContainer from './react_app/components/jobInvocations';
4
3
  import TargetingHosts from './react_app/components/TargetingHosts';
5
- import rootReducer from './react_app/redux/reducers';
6
4
 
7
5
  const components = [
8
6
  { name: 'JobInvocationContainer', type: JobInvocationContainer },
@@ -12,5 +10,3 @@ const components = [
12
10
  components.forEach(component => {
13
11
  componentRegistry.register(component);
14
12
  });
15
-
16
- registerReducer('foremanRemoteExecutionReducers', rootReducer);
@@ -0,0 +1,87 @@
1
+ /* eslint-disable camelcase */
2
+
3
+ import PropTypes from 'prop-types';
4
+ import React from 'react';
5
+ import Skeleton from 'react-loading-skeleton';
6
+ import ElipsisWithTooltip from 'react-ellipsis-with-tooltip';
7
+
8
+ import { Grid, GridItem } from '@patternfly/react-core';
9
+ import {
10
+ OkIcon,
11
+ ErrorCircleOIcon,
12
+ } from '@patternfly/react-icons/dist/js/icons';
13
+ import {
14
+ PropertiesSidePanel,
15
+ PropertyItem,
16
+ } from '@patternfly/react-catalog-view-extension';
17
+ import { ArrowIcon } from '@patternfly/react-icons';
18
+
19
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
20
+ import CardItem from 'foremanReact/components/HostDetails/Templates/CardItem/CardTemplate';
21
+ import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
22
+ import { translate as __ } from 'foremanReact/common/I18n';
23
+ import './styles.css';
24
+
25
+ const RecentJobsCard = ({ hostDetails: { name } }) => {
26
+ const jobsUrl =
27
+ name && `/api/job_invocations?search=host%3D${name}&per_page=3`;
28
+ const {
29
+ response: { results: jobs },
30
+ } = useAPI('get', jobsUrl);
31
+
32
+ const iconMarkup = status => {
33
+ if (status === 1) return <ErrorCircleOIcon color="#C9190B" />;
34
+ return <OkIcon color="#3E8635" />;
35
+ };
36
+
37
+ return (
38
+ <CardItem
39
+ header={
40
+ <span>
41
+ {__('Recent Jobs')}{' '}
42
+ <a href={`/job_invocations?search=host+%3D+${name}`}>
43
+ <ArrowIcon />
44
+ </a>
45
+ </span>
46
+ }
47
+ >
48
+ <PropertiesSidePanel>
49
+ {jobs?.map(({ status, status_label, id, start_at, description }) => (
50
+ <PropertyItem
51
+ key={id}
52
+ label={
53
+ description ? (
54
+ <Grid>
55
+ <GridItem span={8}>
56
+ <ElipsisWithTooltip>{description}</ElipsisWithTooltip>
57
+ </GridItem>
58
+ <GridItem span={1}>{iconMarkup(status)}</GridItem>
59
+ <GridItem span={3}>{status_label}</GridItem>
60
+ </Grid>
61
+ ) : (
62
+ <Skeleton />
63
+ )
64
+ }
65
+ value={
66
+ start_at ? (
67
+ <a href={`/job_invocations/${id}`}>
68
+ <RelativeDateTime date={start_at} />
69
+ </a>
70
+ ) : (
71
+ <Skeleton />
72
+ )
73
+ }
74
+ />
75
+ ))}
76
+ </PropertiesSidePanel>
77
+ </CardItem>
78
+ );
79
+ };
80
+
81
+ export default RecentJobsCard;
82
+
83
+ RecentJobsCard.propTypes = {
84
+ hostDetails: PropTypes.shape({
85
+ name: PropTypes.string,
86
+ }).isRequired,
87
+ };
@@ -0,0 +1 @@
1
+ export const HOST_DETAILS_JOBS = 'HOST_DETAILS_JOBS';
@@ -0,0 +1 @@
1
+ export { default } from './RecentJobsCard';
@@ -0,0 +1,15 @@
1
+ .properties-side-panel-pf-property-value {
2
+ font-size: 14px !important;
3
+ margin-top: 8px;
4
+ word-break: break-word;
5
+ }
6
+
7
+ .properties-side-panel-pf-property-label {
8
+ font-weight: 700 !important;
9
+ font-size: 14px !important;
10
+ margin: 0 !important;
11
+ }
12
+
13
+ .properties-side-panel-pf-property {
14
+ margin-top: 24px;
15
+ }
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+
6
+ import { FormGroup, TextInput, Popover } from '@patternfly/react-core';
7
+
8
+ import { HelpIcon } from '@patternfly/react-icons';
9
+
10
+ const RexInterface = ({ isLoading, onChange }) => (
11
+ <FormGroup
12
+ label={__('Remote Execution Interface')}
13
+ fieldId="reg_rex_interface"
14
+ labelIcon={
15
+ <Popover
16
+ bodyContent={
17
+ <div>
18
+ {__('Identifier of the Host interface for Remote execution')}
19
+ </div>
20
+ }
21
+ >
22
+ <button
23
+ className="pf-c-form__group-label-help"
24
+ onClick={e => e.preventDefault()}
25
+ >
26
+ <HelpIcon noVerticalAlign />
27
+ </button>
28
+ </Popover>
29
+ }
30
+ >
31
+ <TextInput
32
+ type="text"
33
+ onBlur={e => onChange({ remoteExecutionInterface: e.target.value })}
34
+ id="reg_rex_interface_input"
35
+ isDisabled={isLoading}
36
+ />
37
+ </FormGroup>
38
+ );
39
+
40
+ RexInterface.propTypes = {
41
+ onChange: PropTypes.func,
42
+ isLoading: PropTypes.bool,
43
+ };
44
+
45
+ RexInterface.defaultProps = {
46
+ onChange: undefined,
47
+ isLoading: false,
48
+ };
49
+
50
+ export default RexInterface;
@@ -0,0 +1,9 @@
1
+ import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
2
+ import RexInterface from '../RexInterface';
3
+
4
+ const fixtures = {
5
+ renders: { isLoading: false, onChange: () => {} },
6
+ };
7
+
8
+ describe('RexInterface', () =>
9
+ testComponentSnapshotsWithFixtures(RexInterface, fixtures));
@@ -0,0 +1,35 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`RexInterface renders 1`] = `
4
+ <FormGroup
5
+ fieldId="reg_rex_interface"
6
+ label="Remote Execution Interface"
7
+ labelIcon={
8
+ <Popover
9
+ bodyContent={
10
+ <div>
11
+ Identifier of the Host interface for Remote execution
12
+ </div>
13
+ }
14
+ >
15
+ <button
16
+ className="pf-c-form__group-label-help"
17
+ onClick={[Function]}
18
+ >
19
+ <HelpIcon
20
+ color="currentColor"
21
+ noVerticalAlign={true}
22
+ size="sm"
23
+ />
24
+ </button>
25
+ </Popover>
26
+ }
27
+ >
28
+ <TextInput
29
+ id="reg_rex_interface_input"
30
+ isDisabled={false}
31
+ onBlur={[Function]}
32
+ type="text"
33
+ />
34
+ </FormGroup>
35
+ `;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
+ import RecentJobsCard from '../components/RecentJobsCard';
4
+
5
+ export default addGlobalFill(
6
+ 'details-cards',
7
+ 'rex-host-details-latest-jobs',
8
+ <RecentJobsCard />,
9
+ 1000
10
+ );