foreman_ansible 6.4.1 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/ansible_inventories_controller.rb +1 -1
  3. data/app/graphql/mutations/ansible_variable_overrides/create.rb +26 -0
  4. data/app/graphql/mutations/ansible_variable_overrides/delete.rb +38 -0
  5. data/app/graphql/mutations/ansible_variable_overrides/update.rb +26 -0
  6. data/app/graphql/mutations/hosts/assign_ansible_roles.rb +37 -0
  7. data/app/graphql/presenters/ansible_role_presenter.rb +12 -0
  8. data/app/graphql/presenters/overriden_ansible_variable_presenter.rb +19 -0
  9. data/app/graphql/types/ansible_role.rb +9 -0
  10. data/app/graphql/types/ansible_variable.rb +23 -0
  11. data/app/graphql/types/ansible_variable_override.rb +9 -0
  12. data/app/graphql/types/inherited_ansible_role.rb +13 -0
  13. data/app/graphql/types/overriden_ansible_variable.rb +27 -0
  14. data/app/helpers/foreman_ansible/ansible_roles_data_preparations.rb +22 -22
  15. data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
  16. data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +2 -1
  17. data/app/models/foreman_ansible/ansible_provider.rb +7 -5
  18. data/app/services/foreman_ansible/ansible_report_importer.rb +2 -2
  19. data/app/services/foreman_ansible/inventory_creator.rb +1 -1
  20. data/app/services/foreman_ansible/override_resolver.rb +22 -0
  21. data/app/views/api/v2/ansible_override_values/index.json.rabl +3 -0
  22. data/app/views/api/v2/ansible_variables/show.json.rabl +1 -1
  23. data/app/views/foreman_ansible/ansible_roles/_hostgroup_ansible_roles_button.erb +3 -0
  24. data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
  25. data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
  26. data/config/routes.rb +3 -0
  27. data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
  28. data/lib/foreman_ansible/engine.rb +0 -17
  29. data/lib/foreman_ansible/register.rb +115 -4
  30. data/lib/foreman_ansible/version.rb +1 -1
  31. data/package.json +4 -2
  32. data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
  33. data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
  34. data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
  35. data/test/unit/ansible_provider_test.rb +3 -6
  36. data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
  37. data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
  38. data/test/unit/hostgroup_ansible_role_test.rb +13 -0
  39. data/test/unit/services/override_resolver_test.rb +34 -0
  40. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +51 -27
  41. data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +12 -6
  42. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
  43. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
  44. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
  45. data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
  46. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
  47. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
  48. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
  49. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
  50. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
  51. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
  52. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
  53. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
  54. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
  55. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
  56. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
  57. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
  58. data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
  59. data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
  60. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
  61. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
  62. data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
  63. data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
  64. data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
  65. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
  66. data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
  67. data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
  68. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
  69. data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
  70. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
  71. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
  72. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
  73. data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
  74. data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
  75. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
  76. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
  77. data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
  78. data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
  79. data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
  80. data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
  81. data/webpack/components/AnsibleHostDetail/constants.js +9 -0
  82. data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
  83. data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
  84. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
  85. data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
  86. data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
  87. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
  88. data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +4 -0
  89. data/webpack/components/DualList/DualList.scss +3 -0
  90. data/webpack/components/DualList/ListControls.js +65 -0
  91. data/webpack/components/DualList/ListHeader.js +16 -0
  92. data/webpack/components/DualList/ListItem.js +69 -0
  93. data/webpack/components/DualList/ListPane.js +95 -0
  94. data/webpack/components/DualList/SelectedStatus.js +21 -0
  95. data/webpack/components/DualList/index.js +103 -0
  96. data/webpack/components/ErrorState.js +16 -0
  97. data/webpack/components/withLoading.js +135 -0
  98. data/webpack/components/withPagination.js +0 -0
  99. data/webpack/formHelper.js +131 -0
  100. data/webpack/globalIdHelper.js +13 -0
  101. data/webpack/global_index.js +7 -1
  102. data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
  103. data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
  104. data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
  105. data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
  106. data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
  107. data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
  108. data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
  109. data/webpack/graphql/queries/ansibleRoles.gql +13 -0
  110. data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
  111. data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
  112. data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
  113. data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
  114. data/webpack/graphql/queries/recurringJobs.gql +28 -0
  115. data/webpack/helpers/pageParamsHelper.js +40 -0
  116. data/webpack/helpers/paginationHelper.js +9 -0
  117. data/webpack/permissionsHelper.js +58 -0
  118. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
  119. data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
  120. data/webpack/routes/HostgroupJobs/index.js +26 -0
  121. data/webpack/routes/routes.js +10 -0
  122. data/webpack/testHelper.js +165 -0
  123. data/webpack/toastHelper.js +4 -0
  124. metadata +127 -54
  125. data/app/assets/images/foreman_ansible/Ansible.png +0 -0
  126. data/app/models/foreman_ansible/fact_name.rb +0 -16
  127. data/app/models/setting/ansible.rb +0 -106
  128. data/app/services/foreman_ansible/fact_importer.rb +0 -99
  129. data/app/services/foreman_ansible/fact_parser.rb +0 -126
  130. data/app/services/foreman_ansible/fact_sparser.rb +0 -37
  131. data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
  132. data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
  133. data/test/unit/services/fact_importer_test.rb +0 -52
  134. data/test/unit/services/fact_parser_test.rb +0 -281
  135. data/test/unit/services/fact_sparser_test.rb +0 -24
  136. data/test/unit/services/structured_fact_importer_test.rb +0 -30
  137. data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import PropTypes from 'prop-types';
4
+
5
+ const ListItem = props => {
6
+ const draggableBtn = (
7
+ <div className="pf-c-dual-list-selector__draggable">
8
+ <button
9
+ className="pf-c-button pf-m-plain"
10
+ type="button"
11
+ aria-pressed="false"
12
+ aria-label="Reorder"
13
+ id="draggable-list-item-2-draggable-button"
14
+ aria-labelledby="draggable-list-item-2-draggable-button draggable-list-item-2-item-text"
15
+ aria-describedby="draggable-help"
16
+ >
17
+ <i className="fas fa-grip-vertical" aria-hidden="true" />
18
+ </button>
19
+ </div>
20
+ );
21
+
22
+ const orderBtn = (
23
+ <span className="foreman-dual-list-order">{`${props.index + 1}.`}</span>
24
+ );
25
+
26
+ return (
27
+ <li className="pf-c-dual-list-selector__list-item">
28
+ <div
29
+ className={classNames('pf-c-dual-list-selector__list-item-row ', {
30
+ 'pf-m-selected': props.selected,
31
+ 'pf-m-ghost-row': props.dragging,
32
+ })}
33
+ >
34
+ {props.draggable && draggableBtn}
35
+ <button
36
+ className="pf-c-dual-list-selector__item"
37
+ type="button"
38
+ onClick={props.onClick}
39
+ >
40
+ <span className="pf-c-dual-list-selector__item-main">
41
+ <span className="pf-c-dual-list-selector__item-text">
42
+ {props.draggable && orderBtn}
43
+ <span>{props.name}</span>
44
+ </span>
45
+ </span>
46
+ <span className="pf-c-dual-list-selector__item-count">
47
+ <span className="pf-c-badge pf-m-read" />
48
+ </span>
49
+ </button>
50
+ </div>
51
+ </li>
52
+ );
53
+ };
54
+
55
+ ListItem.propTypes = {
56
+ selected: PropTypes.bool.isRequired,
57
+ dragging: PropTypes.bool,
58
+ draggable: PropTypes.bool,
59
+ onClick: PropTypes.func.isRequired,
60
+ name: PropTypes.string.isRequired,
61
+ index: PropTypes.number.isRequired,
62
+ };
63
+
64
+ ListItem.defaultProps = {
65
+ draggable: false,
66
+ dragging: false,
67
+ };
68
+
69
+ export default ListItem;
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import { DndProvider } from 'react-dnd';
3
+ import HTML5Backend from 'react-dnd-html5-backend';
4
+ import PropTypes from 'prop-types';
5
+
6
+ import {
7
+ orderable,
8
+ orderDragged,
9
+ } from 'foremanReact/components/common/forms/OrderableSelect/helpers';
10
+
11
+ import ListItem from './ListItem';
12
+ import ListHeader from './ListHeader';
13
+ import SelectedStatus from './SelectedStatus';
14
+
15
+ const OrderableListItem = orderable(ListItem, {
16
+ type: 'orderableItem',
17
+ getItem: props => ({ id: props.name }),
18
+ direction: 'vertical',
19
+ });
20
+
21
+ const ListPane = ({
22
+ items,
23
+ isItemSelected,
24
+ draggable,
25
+ onItemClick,
26
+ paneClass,
27
+ title,
28
+ selected,
29
+ onSetOrderedItems,
30
+ }) => {
31
+ const moveValue = (dragIndex, hoverIndex) => {
32
+ onSetOrderedItems(orderDragged(items, dragIndex, hoverIndex));
33
+ };
34
+
35
+ const renderOrderableItem = (item, idx) => (
36
+ <OrderableListItem
37
+ key={item}
38
+ name={item}
39
+ index={idx}
40
+ moveValue={moveValue}
41
+ onClick={onItemClick(item)}
42
+ selected={isItemSelected(item)}
43
+ draggable={draggable}
44
+ />
45
+ );
46
+
47
+ const renderListItem = (item, idx) => (
48
+ <ListItem
49
+ key={item}
50
+ name={item}
51
+ index={idx}
52
+ onClick={onItemClick(item)}
53
+ selected={isItemSelected(item)}
54
+ />
55
+ );
56
+
57
+ const renderItem = draggable ? renderOrderableItem : renderListItem;
58
+
59
+ return (
60
+ <div
61
+ className={`pf-c-dual-list-selector__pane pf-m-available ${paneClass}`}
62
+ >
63
+ <ListHeader title={title} />
64
+ <SelectedStatus
65
+ selectedCount={selected.length}
66
+ totalCount={items.length}
67
+ />
68
+ <div className="pf-c-dual-list-selector__menu">
69
+ <DndProvider backend={HTML5Backend}>
70
+ <ul className="pf-c-dual-list-selector__list">
71
+ {items.map(renderItem)}
72
+ </ul>
73
+ </DndProvider>
74
+ </div>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ ListPane.propTypes = {
80
+ items: PropTypes.array.isRequired,
81
+ isItemSelected: PropTypes.func.isRequired,
82
+ draggable: PropTypes.bool,
83
+ onItemClick: PropTypes.func.isRequired,
84
+ paneClass: PropTypes.string.isRequired,
85
+ title: PropTypes.string.isRequired,
86
+ selected: PropTypes.array.isRequired,
87
+ onSetOrderedItems: PropTypes.func,
88
+ };
89
+
90
+ ListPane.defaultProps = {
91
+ draggable: false,
92
+ onSetOrderedItems: () => {},
93
+ };
94
+
95
+ export default ListPane;
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __, sprintf } from 'foremanReact/common/I18n';
4
+
5
+ const SelectedStatus = ({ selectedCount, totalCount }) => (
6
+ <div className="pf-c-dual-list-selector__status">
7
+ <span className="pf-c-dual-list-selector__status-text">
8
+ {sprintf(__('%(selectedCount)s of %(totalCount)s items selected'), {
9
+ selectedCount,
10
+ totalCount,
11
+ })}
12
+ </span>
13
+ </div>
14
+ );
15
+
16
+ SelectedStatus.propTypes = {
17
+ selectedCount: PropTypes.number.isRequired,
18
+ totalCount: PropTypes.number.isRequired,
19
+ };
20
+
21
+ export default SelectedStatus;
@@ -0,0 +1,103 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { translate as __ } from 'foremanReact/common/I18n';
4
+
5
+ import ListControls from './ListControls';
6
+ import ListPane from './ListPane';
7
+ import './DualList.scss';
8
+
9
+ const DualList = props => {
10
+ const defaultSelectState = { availableSelected: [], chosenSelected: [] };
11
+ const [selectState, setSelectState] = useState(defaultSelectState);
12
+
13
+ const onItemClick = stateName => itemName => (...args) => {
14
+ if (isItemSelected(stateName)(itemName)) {
15
+ setSelectState({
16
+ ...selectState,
17
+ [stateName]: selectState[stateName].filter(item => item !== itemName),
18
+ });
19
+ } else {
20
+ setSelectState({
21
+ ...selectState,
22
+ [stateName]: [...selectState[stateName], itemName],
23
+ });
24
+ }
25
+ };
26
+
27
+ const isItemSelected = stateName => item =>
28
+ selectState[stateName].includes(item);
29
+
30
+ const onAddAll = () =>
31
+ props.onListChange([], [...props.chosenOptions, ...props.availableOptions]);
32
+ const onRemoveAll = () =>
33
+ props.onListChange([...props.availableOptions, ...props.chosenOptions], []);
34
+
35
+ const onAddSelected = () => {
36
+ const [newAvailable, newChosen] = moveItems(
37
+ props.availableOptions,
38
+ props.chosenOptions,
39
+ selectState.availableSelected
40
+ );
41
+ setSelectState(defaultSelectState);
42
+ props.onListChange(newAvailable, newChosen);
43
+ };
44
+
45
+ const onRemoveSelected = () => {
46
+ const [newChosen, newAvailable] = moveItems(
47
+ props.chosenOptions,
48
+ props.availableOptions,
49
+ selectState.chosenSelected
50
+ );
51
+ setSelectState(defaultSelectState);
52
+ props.onListChange(newAvailable, newChosen);
53
+ };
54
+
55
+ const moveItems = (removeFrom, addTo, selected) => {
56
+ const newRemoveFrom = removeFrom.filter(item => !selected.includes(item));
57
+ return [newRemoveFrom, [...addTo, ...selected]];
58
+ };
59
+
60
+ const onChosenSetOrder = items =>
61
+ props.onListChange(props.availableOptions, items);
62
+
63
+ return (
64
+ <div className="pf-c-dual-list-selector">
65
+ <ListPane
66
+ title={__('Available options')}
67
+ items={props.availableOptions}
68
+ paneClass="pf-m-available"
69
+ onItemClick={onItemClick('availableSelected')}
70
+ selected={selectState.availableSelected}
71
+ isItemSelected={isItemSelected('availableSelected')}
72
+ />
73
+ <ListControls
74
+ onAddSelected={onAddSelected}
75
+ onRemoveSelected={onRemoveSelected}
76
+ onRemoveAll={onRemoveAll}
77
+ onAddAll={onAddAll}
78
+ addAllDisabled={props.availableOptions.length === 0}
79
+ removeAllDisabled={props.chosenOptions.length === 0}
80
+ addSelectedDisabled={selectState.availableSelected.length === 0}
81
+ removeSelectedDisabled={selectState.chosenSelected.length === 0}
82
+ />
83
+ <ListPane
84
+ title={__('Chosen options')}
85
+ items={props.chosenOptions}
86
+ paneClass="pf-m-chosen"
87
+ draggable
88
+ onItemClick={onItemClick('chosenSelected')}
89
+ selected={selectState.chosenSelected}
90
+ isItemSelected={isItemSelected('chosenSelected')}
91
+ onSetOrderedItems={onChosenSetOrder}
92
+ />
93
+ </div>
94
+ );
95
+ };
96
+
97
+ DualList.propTypes = {
98
+ onListChange: PropTypes.func.isRequired,
99
+ chosenOptions: PropTypes.array.isRequired,
100
+ availableOptions: PropTypes.array.isRequired,
101
+ };
102
+
103
+ export default DualList;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import EmptyState from 'foremanReact/components/common/EmptyState/EmptyStatePattern';
3
+
4
+ import { EmptyStateIcon } from '@patternfly/react-core';
5
+ import { ExclamationCircleIcon } from '@patternfly/react-icons';
6
+ import { global_danger_color_200 as dangerColor } from '@patternfly/react-tokens';
7
+ import { translate as __ } from 'foremanReact/common/I18n';
8
+
9
+ const ErrorState = props => {
10
+ const icon = (
11
+ <EmptyStateIcon icon={ExclamationCircleIcon} color={dangerColor.value} />
12
+ );
13
+ return <EmptyState header={__('Error!')} icon={icon} {...props} />;
14
+ };
15
+
16
+ export default ErrorState;
@@ -0,0 +1,135 @@
1
+ import React from 'react';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+ import PropTypes from 'prop-types';
4
+
5
+ import Skeleton from 'react-loading-skeleton';
6
+ import EmptyState from 'foremanReact/components/common/EmptyState/EmptyStatePattern';
7
+ import { LockIcon } from '@patternfly/react-icons';
8
+ import { EmptyStateIcon } from '@patternfly/react-core';
9
+ import {
10
+ permissionCheck,
11
+ permissionDeniedMsg,
12
+ allowPrimaryAction,
13
+ } from '../permissionsHelper';
14
+ import ErrorState from './ErrorState';
15
+
16
+ const pluckData = (data, path) => {
17
+ const split = path.split('.');
18
+ return split.reduce((memo, item) => {
19
+ if (item) {
20
+ return memo[item];
21
+ }
22
+ throw new Error('Unexpected empty segment in response data path');
23
+ }, data);
24
+ };
25
+
26
+ const withLoading = Component => {
27
+ const defaultEmptyStateProps = {
28
+ header: __('Nothing Found!'),
29
+ description: '',
30
+ };
31
+
32
+ const Subcomponent = ({
33
+ fetchFn,
34
+ renameData,
35
+ renamedDataPath,
36
+ showEmptyState,
37
+ emptyWrapper,
38
+ loadingWrapper,
39
+ wrapper,
40
+ emptyStateProps,
41
+ permissions,
42
+ allowed,
43
+ requiredPermissions,
44
+ primaryActionPermissions,
45
+ ...rest
46
+ }) => {
47
+ const { loading, error, data } = fetchFn(rest);
48
+
49
+ if (loading) {
50
+ return loadingWrapper(<Skeleton count={5} />);
51
+ }
52
+
53
+ if (error) {
54
+ return emptyWrapper(<ErrorState description={error.message} />);
55
+ }
56
+
57
+ const check = permissionCheck(data.currentUser, permissions);
58
+
59
+ if (!check.allowed) {
60
+ return emptyWrapper(
61
+ <EmptyState
62
+ icon={<EmptyStateIcon icon={LockIcon} />}
63
+ header={__('Permission denied')}
64
+ description={permissionDeniedMsg(
65
+ check.permissions.map(item => item.name)
66
+ )}
67
+ />
68
+ );
69
+ }
70
+
71
+ if (!allowed) {
72
+ return emptyWrapper(
73
+ <EmptyState
74
+ icon={<EmptyStateIcon icon={LockIcon} />}
75
+ header={__('Permission denied')}
76
+ description={permissionDeniedMsg(requiredPermissions)}
77
+ />
78
+ );
79
+ }
80
+
81
+ const renamedData = renameData(data);
82
+ const result = pluckData(renamedData, renamedDataPath);
83
+
84
+ if (
85
+ showEmptyState &&
86
+ ((Array.isArray(result) && result.length === 0) || !result)
87
+ ) {
88
+ return emptyWrapper(
89
+ <EmptyState
90
+ {...{
91
+ ...defaultEmptyStateProps,
92
+ ...allowPrimaryAction(
93
+ emptyStateProps,
94
+ data.currentUser,
95
+ primaryActionPermissions
96
+ ),
97
+ }}
98
+ />
99
+ );
100
+ }
101
+
102
+ return wrapper(<Component {...rest} {...renamedData} />);
103
+ };
104
+
105
+ Subcomponent.propTypes = {
106
+ fetchFn: PropTypes.func.isRequired,
107
+ renamedDataPath: PropTypes.string.isRequired,
108
+ renameData: PropTypes.func,
109
+ showEmptyState: PropTypes.bool,
110
+ loadingWrapper: PropTypes.func,
111
+ emptyWrapper: PropTypes.func,
112
+ emptyStateProps: PropTypes.object,
113
+ wrapper: PropTypes.func,
114
+ permissions: PropTypes.array,
115
+ allowed: PropTypes.bool,
116
+ primaryActionPermissions: PropTypes.array,
117
+ };
118
+
119
+ Subcomponent.defaultProps = {
120
+ renameData: data => data,
121
+ showEmptyState: true,
122
+ loadingWrapper: child => child,
123
+ emptyWrapper: child => child,
124
+ wrapper: child => child,
125
+ emptyStateProps: defaultEmptyStateProps,
126
+ permissions: [],
127
+ requiredPermissions: [],
128
+ primaryActionPermissions: [],
129
+ allowed: true,
130
+ };
131
+
132
+ return Subcomponent;
133
+ };
134
+
135
+ export default withLoading;
File without changes
@@ -0,0 +1,131 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import {
4
+ FormGroup,
5
+ FormSelect,
6
+ FormSelectOption,
7
+ DatePicker,
8
+ TimePicker,
9
+ } from '@patternfly/react-core';
10
+ import ExclamationCircleIcon from '@patternfly/react-icons/dist/js/icons/exclamation-circle-icon';
11
+
12
+ const wrapFieldProps = fieldProps => {
13
+ const { onChange } = fieldProps;
14
+ // modify onChange args to correctly wire formik with pf4 input handlers
15
+ const wrappedOnChange = (value, event) => {
16
+ onChange(event);
17
+ };
18
+
19
+ return { ...fieldProps, onChange: wrappedOnChange };
20
+ };
21
+
22
+ const wrapPickerProps = fieldProps => {
23
+ const { onChange } = fieldProps;
24
+ // because pf4 does not provide consistent handlers for its inputs
25
+ const wrappedOnChange = value => {
26
+ onChange({ target: { name: fieldProps.name, value } });
27
+ };
28
+
29
+ return { ...fieldProps, onChange: wrappedOnChange };
30
+ };
31
+
32
+ const shouldValidate = (form, fieldName) => {
33
+ if (form.touched[fieldName]) {
34
+ return form.errors[fieldName] ? 'error' : 'success';
35
+ }
36
+
37
+ return 'noval';
38
+ };
39
+
40
+ export const SelectField = ({
41
+ selectItems,
42
+ field,
43
+ form,
44
+ label,
45
+ isRequired,
46
+ blankLabel,
47
+ }) => {
48
+ const fieldProps = wrapFieldProps(field);
49
+
50
+ const valid = shouldValidate(form, field.name);
51
+
52
+ return (
53
+ <FormGroup
54
+ label={label}
55
+ isRequired={isRequired}
56
+ helperTextInvalid={form.errors[field.name]}
57
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
58
+ validated={valid}
59
+ >
60
+ <FormSelect
61
+ {...fieldProps}
62
+ className="without_select2"
63
+ aria-label={fieldProps.name}
64
+ validated={valid}
65
+ isDisabled={form.isSubmitting}
66
+ >
67
+ <FormSelectOption key={0} value="" label={blankLabel} />
68
+ {selectItems.map((item, idx) => (
69
+ <FormSelectOption key={idx + 1} value={item.id} label={item.name} />
70
+ ))}
71
+ </FormSelect>
72
+ </FormGroup>
73
+ );
74
+ };
75
+
76
+ SelectField.propTypes = {
77
+ selectItems: PropTypes.array,
78
+ field: PropTypes.object.isRequired,
79
+ form: PropTypes.object.isRequired,
80
+ label: PropTypes.string.isRequired,
81
+ blankLabel: PropTypes.string,
82
+ isRequired: PropTypes.bool,
83
+ };
84
+
85
+ SelectField.defaultProps = {
86
+ selectItems: [],
87
+ isRequired: false,
88
+ blankLabel: '',
89
+ };
90
+
91
+ const pickerWithHandlers = Component => {
92
+ const Subcomponent = ({ form, field, isRequired, label, ...rest }) => {
93
+ const { onChange, onBlur } = wrapPickerProps(field);
94
+ const valid = shouldValidate(form, field.name);
95
+
96
+ return (
97
+ <FormGroup
98
+ label={label}
99
+ isRequired={isRequired}
100
+ helperTextInvalid={form.errors[field.name]}
101
+ helperTextInvalidIcon={<ExclamationCircleIcon />}
102
+ validated={valid}
103
+ >
104
+ <Component
105
+ aria-label={field.name}
106
+ onChange={onChange}
107
+ onBlur={onBlur}
108
+ {...rest}
109
+ validated={valid}
110
+ isDisabled={form.isSubmitting}
111
+ />
112
+ </FormGroup>
113
+ );
114
+ };
115
+
116
+ Subcomponent.propTypes = {
117
+ form: PropTypes.object.isRequired,
118
+ field: PropTypes.object.isRequired,
119
+ isRequired: PropTypes.bool,
120
+ label: PropTypes.string.isRequired,
121
+ };
122
+
123
+ Subcomponent.defaultProps = {
124
+ isRequired: false,
125
+ };
126
+
127
+ return Subcomponent;
128
+ };
129
+
130
+ export const DatePickerField = pickerWithHandlers(DatePicker);
131
+ export const TimePickerField = pickerWithHandlers(TimePicker);
@@ -0,0 +1,13 @@
1
+ const idSeparator = '-';
2
+ const versionSeparator = ':';
3
+ const defaultVersion = '01';
4
+
5
+ export const decodeModelId = model => decodeId(model.id);
6
+
7
+ export const decodeId = id => {
8
+ const split = atob(id).split(idSeparator);
9
+ return parseInt(split[split.length - 1], 10);
10
+ };
11
+
12
+ export const encodeId = (typename, id) =>
13
+ btoa([defaultVersion, versionSeparator, typename, idSeparator, id].join(''));
@@ -1,12 +1,18 @@
1
1
  import React from 'react';
2
+ import { registerRoutes } from 'foremanReact/routes/RoutingService';
2
3
 
3
4
  import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
4
5
 
6
+ import routes from './routes/routes';
5
7
  import AnsibleHostDetail from './components/AnsibleHostDetail';
6
8
 
9
+ import { ANSIBLE_KEY } from './components/AnsibleHostDetail/constants';
10
+
7
11
  addGlobalFill(
8
12
  'host-details-page-tabs',
9
- 'Ansible',
13
+ ANSIBLE_KEY,
10
14
  <AnsibleHostDetail key="ansible-host-detail" />,
11
15
  500
12
16
  );
17
+
18
+ registerRoutes('foreman_ansible', routes);
@@ -0,0 +1,17 @@
1
+ mutation AssignAnsibleRoles($id: ID!, $ansibleRoleIds: [Int!]!) {
2
+ assignAnsibleRoles(input: { id: $id, ansibleRoleIds: $ansibleRoleIds }) {
3
+ host {
4
+ id
5
+ ownAnsibleRoles {
6
+ nodes {
7
+ id
8
+ name
9
+ }
10
+ }
11
+ }
12
+ errors {
13
+ path
14
+ message
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,12 @@
1
+ mutation CancelRecurringLogic($id:ID!) {
2
+ cancelRecurringLogic(input: { id: $id }){
3
+ recurringLogic {
4
+ id
5
+ cronLine
6
+ }
7
+ errors {
8
+ message
9
+ path
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,28 @@
1
+ mutation CreateAnsibleVariableOverride(
2
+ $hostId: Int!,
3
+ $value: RawJson!,
4
+ $lookupKeyId: Int!,
5
+ $match: String!) {
6
+ createAnsibleVariableOverride(input: { hostId: $hostId, lookupKeyId: $lookupKeyId, value: $value, match: $match }) {
7
+ overridenAnsibleVariable {
8
+ id
9
+ lookupValues(match: $match) {
10
+ nodes {
11
+ id
12
+ match
13
+ value
14
+ omit
15
+ }
16
+ }
17
+ currentValue {
18
+ element
19
+ value
20
+ elementName
21
+ }
22
+ }
23
+ errors {
24
+ path
25
+ message
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,11 @@
1
+ mutation CreateJobInvocation($jobInvocation: JobInvocationInput!) {
2
+ createJobInvocation(input: { jobInvocation: $jobInvocation }) {
3
+ jobInvocation {
4
+ id
5
+ }
6
+ errors {
7
+ path
8
+ message
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,17 @@
1
+ mutation DeleteAnsibleVariableOverride($id: ID!, $hostId: Int!, $variableId: Int!) {
2
+ deleteAnsibleVariableOverride(input: { id: $id, hostId: $hostId, variableId: $variableId }) {
3
+ id
4
+ overridenAnsibleVariable {
5
+ id
6
+ currentValue {
7
+ value
8
+ element
9
+ elementName
10
+ }
11
+ }
12
+ errors {
13
+ path
14
+ message
15
+ }
16
+ }
17
+ }