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.
- checksums.yaml +4 -4
- data/app/controllers/api/v2/ansible_inventories_controller.rb +1 -1
- data/app/graphql/mutations/ansible_variable_overrides/create.rb +26 -0
- data/app/graphql/mutations/ansible_variable_overrides/delete.rb +38 -0
- data/app/graphql/mutations/ansible_variable_overrides/update.rb +26 -0
- data/app/graphql/mutations/hosts/assign_ansible_roles.rb +37 -0
- data/app/graphql/presenters/ansible_role_presenter.rb +12 -0
- data/app/graphql/presenters/overriden_ansible_variable_presenter.rb +19 -0
- data/app/graphql/types/ansible_role.rb +9 -0
- data/app/graphql/types/ansible_variable.rb +23 -0
- data/app/graphql/types/ansible_variable_override.rb +9 -0
- data/app/graphql/types/inherited_ansible_role.rb +13 -0
- data/app/graphql/types/overriden_ansible_variable.rb +27 -0
- data/app/helpers/foreman_ansible/ansible_roles_data_preparations.rb +22 -22
- data/app/models/concerns/foreman_ansible/host_managed_extensions.rb +23 -4
- data/app/models/concerns/foreman_ansible/hostgroup_extensions.rb +2 -1
- data/app/models/foreman_ansible/ansible_provider.rb +7 -5
- data/app/services/foreman_ansible/ansible_report_importer.rb +2 -2
- data/app/services/foreman_ansible/inventory_creator.rb +1 -1
- data/app/services/foreman_ansible/override_resolver.rb +22 -0
- data/app/views/api/v2/ansible_override_values/index.json.rabl +3 -0
- data/app/views/api/v2/ansible_variables/show.json.rabl +1 -1
- data/app/views/foreman_ansible/ansible_roles/_hostgroup_ansible_roles_button.erb +3 -0
- data/app/views/foreman_ansible/job_templates/convert_to_rhel.erb +6 -2
- data/app/views/foreman_ansible/job_templates/run_openscap_scans_-_ansible_default.erb +20 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20210818083407_fix_ansible_setting_category_to_dsl.rb +5 -0
- data/lib/foreman_ansible/engine.rb +0 -17
- data/lib/foreman_ansible/register.rb +115 -4
- data/lib/foreman_ansible/version.rb +1 -1
- data/package.json +4 -2
- data/test/functional/api/v2/ansible_inventories_controller_test.rb +1 -2
- data/test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb +96 -0
- data/test/graphql/queries/ansible_roles_query_test.rb +35 -0
- data/test/unit/ansible_provider_test.rb +3 -6
- data/test/unit/concerns/host_managed_extensions_test.rb +8 -0
- data/test/unit/concerns/hostgroup_extensions_test.rb +6 -0
- data/test/unit/hostgroup_ansible_role_test.rb +13 -0
- data/test/unit/services/override_resolver_test.rb +34 -0
- data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +51 -27
- data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.test.js +12 -6
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.js +22 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.scss +4 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/AnsibleHostInventory.test.js +104 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleHostInventory/index.js +38 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverrides.scss +3 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +238 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTableHelper.js +111 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js +161 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.scss +7 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableActionHelper.js +49 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValue.js +70 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableValueHelper.js +35 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +429 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.test.js +71 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +74 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesUpdate.test.js +188 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +58 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +79 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper.js +106 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js +129 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss +7 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +103 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +96 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +184 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +195 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +88 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js +89 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +80 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesForm.js +90 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModal.scss +3 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js +40 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +82 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +129 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +85 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +180 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +75 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +51 -0
- data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +60 -0
- data/webpack/components/AnsibleHostDetail/components/TabLayout.js +12 -0
- data/webpack/components/AnsibleHostDetail/constants.js +9 -0
- data/webpack/components/AnsibleHostDetail/helpers.js +4 -0
- data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js +15 -10
- data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.js +29 -0
- data/webpack/components/AnsibleRolesSwitcher/components/AnsibleRole.test.js +3 -0
- data/webpack/components/AnsibleRolesSwitcher/components/AvailableRolesList.js +2 -1
- data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap +3 -3
- data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AvailableRolesList.test.js.snap +4 -0
- data/webpack/components/DualList/DualList.scss +3 -0
- data/webpack/components/DualList/ListControls.js +65 -0
- data/webpack/components/DualList/ListHeader.js +16 -0
- data/webpack/components/DualList/ListItem.js +69 -0
- data/webpack/components/DualList/ListPane.js +95 -0
- data/webpack/components/DualList/SelectedStatus.js +21 -0
- data/webpack/components/DualList/index.js +103 -0
- data/webpack/components/ErrorState.js +16 -0
- data/webpack/components/withLoading.js +135 -0
- data/webpack/components/withPagination.js +0 -0
- data/webpack/formHelper.js +131 -0
- data/webpack/globalIdHelper.js +13 -0
- data/webpack/global_index.js +7 -1
- data/webpack/graphql/mutations/assignAnsibleRoles.gql +17 -0
- data/webpack/graphql/mutations/cancelRecurringLogic.gql +12 -0
- data/webpack/graphql/mutations/createAnsibleVariableOverride.gql +28 -0
- data/webpack/graphql/mutations/createJobInvocation.gql +11 -0
- data/webpack/graphql/mutations/deleteAnsibleVariableOverride.gql +17 -0
- data/webpack/graphql/mutations/updateAnsibleVariableOverride.gql +29 -0
- data/webpack/graphql/queries/allAnsibleRoles.gql +13 -0
- data/webpack/graphql/queries/ansibleRoles.gql +13 -0
- data/webpack/graphql/queries/currentUserAttributes.gql +11 -0
- data/webpack/graphql/queries/hostAnsibleRoles.gql +17 -0
- data/webpack/graphql/queries/hostAvailableAnsibleRoles.gql +11 -0
- data/webpack/graphql/queries/hostVariableOverrides.gql +39 -0
- data/webpack/graphql/queries/recurringJobs.gql +28 -0
- data/webpack/helpers/pageParamsHelper.js +40 -0
- data/webpack/helpers/paginationHelper.js +9 -0
- data/webpack/permissionsHelper.js +58 -0
- data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.fixtures.js +63 -0
- data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +112 -0
- data/webpack/routes/HostgroupJobs/index.js +26 -0
- data/webpack/routes/routes.js +10 -0
- data/webpack/testHelper.js +165 -0
- data/webpack/toastHelper.js +4 -0
- metadata +127 -54
- data/app/assets/images/foreman_ansible/Ansible.png +0 -0
- data/app/models/foreman_ansible/fact_name.rb +0 -16
- data/app/models/setting/ansible.rb +0 -106
- data/app/services/foreman_ansible/fact_importer.rb +0 -99
- data/app/services/foreman_ansible/fact_parser.rb +0 -126
- data/app/services/foreman_ansible/fact_sparser.rb +0 -37
- data/app/services/foreman_ansible/operating_system_parser.rb +0 -102
- data/app/services/foreman_ansible/structured_fact_importer.rb +0 -25
- data/test/unit/services/fact_importer_test.rb +0 -52
- data/test/unit/services/fact_parser_test.rb +0 -281
- data/test/unit/services/fact_sparser_test.rb +0 -24
- data/test/unit/services/structured_fact_importer_test.rb +0 -30
- data/webpack/components/AnsibleRolesAndVariables/__test__/__snapshots__/AnsibleRolesAndVariablesImport.test.js.snap +0 -177
@@ -0,0 +1,89 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
|
5
|
+
|
6
|
+
import {
|
7
|
+
TableComposable,
|
8
|
+
Thead,
|
9
|
+
Tbody,
|
10
|
+
Tr,
|
11
|
+
Th,
|
12
|
+
Td,
|
13
|
+
} from '@patternfly/react-table';
|
14
|
+
|
15
|
+
import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
|
16
|
+
import withLoading from '../../../../withLoading';
|
17
|
+
import {
|
18
|
+
preparePerPageOptions,
|
19
|
+
refreshPage,
|
20
|
+
} from '../../../../../helpers/paginationHelper';
|
21
|
+
|
22
|
+
const AllRolesTable = ({
|
23
|
+
allAnsibleRoles,
|
24
|
+
totalCount,
|
25
|
+
pagination,
|
26
|
+
history,
|
27
|
+
}) => {
|
28
|
+
const columns = [__('Name'), __('Source')];
|
29
|
+
|
30
|
+
const handlePerPageSelected = (event, allPerPage) => {
|
31
|
+
refreshPage(history, { allPage: 1, allPerPage });
|
32
|
+
};
|
33
|
+
|
34
|
+
const handlePageSelected = (event, allPage) => {
|
35
|
+
refreshPage(history, { ...pagination, allPage });
|
36
|
+
};
|
37
|
+
|
38
|
+
const perPageOptions = preparePerPageOptions(usePaginationOptions());
|
39
|
+
|
40
|
+
return (
|
41
|
+
<React.Fragment>
|
42
|
+
<Flex className="pf-u-pt-md">
|
43
|
+
<FlexItem align={{ default: 'alignRight' }}>
|
44
|
+
<Pagination
|
45
|
+
itemCount={totalCount}
|
46
|
+
page={pagination.allPage}
|
47
|
+
perPage={pagination.allPerPage}
|
48
|
+
onSetPage={handlePageSelected}
|
49
|
+
onPerPageSelect={handlePerPageSelected}
|
50
|
+
perPageOptions={perPageOptions}
|
51
|
+
variant="top"
|
52
|
+
/>
|
53
|
+
</FlexItem>
|
54
|
+
</Flex>
|
55
|
+
<TableComposable variant="compact">
|
56
|
+
<Thead>
|
57
|
+
<Tr>
|
58
|
+
<Th />
|
59
|
+
{columns.map(col => (
|
60
|
+
<Th key={`${col}-all`}>{col}</Th>
|
61
|
+
))}
|
62
|
+
</Tr>
|
63
|
+
</Thead>
|
64
|
+
<Tbody>
|
65
|
+
{allAnsibleRoles.map(role => (
|
66
|
+
<Tr key={`${role.id}-all`} id={role.id}>
|
67
|
+
<Td />
|
68
|
+
<Td>{role.name}</Td>
|
69
|
+
<Td>
|
70
|
+
{role.inherited
|
71
|
+
? __('Inherited from Hostgroup')
|
72
|
+
: __('Directly assigned to Host')}
|
73
|
+
</Td>
|
74
|
+
</Tr>
|
75
|
+
))}
|
76
|
+
</Tbody>
|
77
|
+
</TableComposable>
|
78
|
+
</React.Fragment>
|
79
|
+
);
|
80
|
+
};
|
81
|
+
|
82
|
+
AllRolesTable.propTypes = {
|
83
|
+
allAnsibleRoles: PropTypes.array.isRequired,
|
84
|
+
totalCount: PropTypes.number.isRequired,
|
85
|
+
pagination: PropTypes.object.isRequired,
|
86
|
+
history: PropTypes.object.isRequired,
|
87
|
+
};
|
88
|
+
|
89
|
+
export default withLoading(AllRolesTable);
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { useQuery } from '@apollo/client';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
|
6
|
+
import { Modal, Button, ModalVariant } from '@patternfly/react-core';
|
7
|
+
|
8
|
+
import allAnsibleRolesQuery from '../../../../../graphql/queries/allAnsibleRoles.gql';
|
9
|
+
import AllRolesTable from './AllRolesTable';
|
10
|
+
|
11
|
+
import {
|
12
|
+
useParamsToVars,
|
13
|
+
useCurrentPagination,
|
14
|
+
} from '../../../../../helpers/pageParamsHelper';
|
15
|
+
|
16
|
+
const AllRolesModal = ({ hostGlobalId, onClose, history }) => {
|
17
|
+
const baseModalProps = {
|
18
|
+
variant: ModalVariant.large,
|
19
|
+
isOpen: true,
|
20
|
+
className: 'foreman-modal',
|
21
|
+
showClose: false,
|
22
|
+
title: __('All Ansible Roles'),
|
23
|
+
disableFocusTrap: true,
|
24
|
+
};
|
25
|
+
|
26
|
+
const paginationKeys = { page: 'allPage', perPage: 'allPerPage' };
|
27
|
+
|
28
|
+
const actions = [
|
29
|
+
<Button variant="link" onClick={onClose} key="close">
|
30
|
+
{__('Close')}
|
31
|
+
</Button>,
|
32
|
+
];
|
33
|
+
|
34
|
+
const wrapper = child => (
|
35
|
+
<Modal {...baseModalProps} actions={actions}>
|
36
|
+
{child}
|
37
|
+
</Modal>
|
38
|
+
);
|
39
|
+
|
40
|
+
const loadingWrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
|
41
|
+
|
42
|
+
const useFetchFn = () =>
|
43
|
+
useQuery(allAnsibleRolesQuery, {
|
44
|
+
variables: {
|
45
|
+
id: hostGlobalId,
|
46
|
+
...useParamsToVars(history, paginationKeys),
|
47
|
+
},
|
48
|
+
fetchPolicy: 'network-only',
|
49
|
+
});
|
50
|
+
|
51
|
+
const renameData = data => ({
|
52
|
+
allAnsibleRoles: data.host.allAnsibleRoles.nodes,
|
53
|
+
totalCount: data.host.allAnsibleRoles.totalCount,
|
54
|
+
});
|
55
|
+
|
56
|
+
const pagination = useCurrentPagination(history, paginationKeys);
|
57
|
+
|
58
|
+
return (
|
59
|
+
<AllRolesTable
|
60
|
+
wrapper={wrapper}
|
61
|
+
loadingWrapper={loadingWrapper}
|
62
|
+
emptyWrapper={loadingWrapper}
|
63
|
+
fetchFn={useFetchFn}
|
64
|
+
renameData={renameData}
|
65
|
+
renamedDataPath="allAnsibleRoles"
|
66
|
+
hostGlobalId={hostGlobalId}
|
67
|
+
emptyStateTitle={__('No Ansible roles assigned')}
|
68
|
+
history={history}
|
69
|
+
pagination={pagination}
|
70
|
+
/>
|
71
|
+
);
|
72
|
+
};
|
73
|
+
|
74
|
+
AllRolesModal.propTypes = {
|
75
|
+
hostGlobalId: PropTypes.string.isRequired,
|
76
|
+
onClose: PropTypes.func.isRequired,
|
77
|
+
history: PropTypes.object.isRequired,
|
78
|
+
};
|
79
|
+
|
80
|
+
export default AllRolesModal;
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
|
5
|
+
import { useMutation } from '@apollo/client';
|
6
|
+
|
7
|
+
import { Button, Modal, Spinner } from '@patternfly/react-core';
|
8
|
+
import { encodeId } from '../../../../../globalIdHelper';
|
9
|
+
import assignAnsibleRoles from '../../../../../graphql/mutations/assignAnsibleRoles.gql';
|
10
|
+
import withLoading from '../../../../withLoading';
|
11
|
+
import { onCompleted, onError, roleNamesToIds } from './EditRolesModalHelper';
|
12
|
+
import DualList from '../../../../DualList';
|
13
|
+
|
14
|
+
const EditRolesForm = props => {
|
15
|
+
const {
|
16
|
+
assignedRoles,
|
17
|
+
availableRoles,
|
18
|
+
closeModal,
|
19
|
+
hostId,
|
20
|
+
baseModalProps,
|
21
|
+
actions,
|
22
|
+
} = props;
|
23
|
+
|
24
|
+
const [formState, setFormState] = useState({
|
25
|
+
availableOptions: [],
|
26
|
+
chosenOptions: [],
|
27
|
+
});
|
28
|
+
|
29
|
+
useEffect(() => {
|
30
|
+
setFormState({
|
31
|
+
availableOptions: availableRoles.map(item => item.name),
|
32
|
+
chosenOptions: assignedRoles.map(item => item.name) || [],
|
33
|
+
});
|
34
|
+
}, [availableRoles, assignedRoles]);
|
35
|
+
|
36
|
+
const [callMutation, { loading }] = useMutation(assignAnsibleRoles, {
|
37
|
+
onCompleted: onCompleted(closeModal),
|
38
|
+
onError,
|
39
|
+
});
|
40
|
+
|
41
|
+
const allRoles = (availableRoles || []).concat(assignedRoles || []);
|
42
|
+
|
43
|
+
const variables = {
|
44
|
+
id: encodeId('Host', hostId),
|
45
|
+
ansibleRoleIds: roleNamesToIds(allRoles, formState.chosenOptions),
|
46
|
+
};
|
47
|
+
|
48
|
+
const formActions = [
|
49
|
+
<Button
|
50
|
+
key="confirm"
|
51
|
+
variant="primary"
|
52
|
+
onClick={() => callMutation({ variables })}
|
53
|
+
isDisabled={loading}
|
54
|
+
aria-label="submit ansible roles"
|
55
|
+
>
|
56
|
+
{__('Confirm')}
|
57
|
+
</Button>,
|
58
|
+
...actions,
|
59
|
+
];
|
60
|
+
|
61
|
+
if (loading) {
|
62
|
+
formActions.push(<Spinner key="spinner" size="lg" />);
|
63
|
+
}
|
64
|
+
|
65
|
+
return (
|
66
|
+
<Modal {...baseModalProps} actions={formActions}>
|
67
|
+
<DualList
|
68
|
+
availableOptions={formState.availableOptions}
|
69
|
+
chosenOptions={formState.chosenOptions}
|
70
|
+
onListChange={(newAvailable, newChosen) =>
|
71
|
+
setFormState({
|
72
|
+
availableOptions: newAvailable,
|
73
|
+
chosenOptions: newChosen,
|
74
|
+
})
|
75
|
+
}
|
76
|
+
/>
|
77
|
+
</Modal>
|
78
|
+
);
|
79
|
+
};
|
80
|
+
|
81
|
+
EditRolesForm.propTypes = {
|
82
|
+
closeModal: PropTypes.func.isRequired,
|
83
|
+
assignedRoles: PropTypes.array.isRequired,
|
84
|
+
availableRoles: PropTypes.array.isRequired,
|
85
|
+
actions: PropTypes.array.isRequired,
|
86
|
+
hostId: PropTypes.number.isRequired,
|
87
|
+
baseModalProps: PropTypes.object.isRequired,
|
88
|
+
};
|
89
|
+
|
90
|
+
export default withLoading(EditRolesForm);
|
data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/EditRolesModalHelper.js
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
2
|
+
import { decodeModelId } from '../../../../../globalIdHelper';
|
3
|
+
import { showToast } from '../../../../../toastHelper';
|
4
|
+
|
5
|
+
export const roleNamesToIds = (roles, names) =>
|
6
|
+
names.reduce((memo, name) => {
|
7
|
+
const role = roles.find(item => item.name === name);
|
8
|
+
if (role) {
|
9
|
+
memo.push(decodeModelId(role));
|
10
|
+
}
|
11
|
+
return memo;
|
12
|
+
}, []);
|
13
|
+
|
14
|
+
const joinErrors = errors => errors.map(err => err.message).join(', ');
|
15
|
+
|
16
|
+
const formatError = error =>
|
17
|
+
sprintf(
|
18
|
+
__('There was a following error when assigning Ansible Roles: %s'),
|
19
|
+
error
|
20
|
+
);
|
21
|
+
|
22
|
+
export const onCompleted = closeModal => data => {
|
23
|
+
const { errors } = data.assignAnsibleRoles;
|
24
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
25
|
+
showToast({
|
26
|
+
type: 'error',
|
27
|
+
message: formatError(joinErrors(errors)),
|
28
|
+
});
|
29
|
+
} else {
|
30
|
+
closeModal();
|
31
|
+
showToast({
|
32
|
+
type: 'success',
|
33
|
+
message: __('Ansible Roles were successfully assigned.'),
|
34
|
+
});
|
35
|
+
}
|
36
|
+
};
|
37
|
+
|
38
|
+
export const onError = error => {
|
39
|
+
showToast({ type: 'error', message: formatError(error) });
|
40
|
+
};
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
3
|
+
import PropTypes from 'prop-types';
|
4
|
+
|
5
|
+
import { Modal, Button, ModalVariant } from '@patternfly/react-core';
|
6
|
+
import { useQuery } from '@apollo/client';
|
7
|
+
|
8
|
+
import EditRolesForm from './EditRolesForm';
|
9
|
+
|
10
|
+
import availableAnsibleRoles from '../../../../../graphql/queries/hostAvailableAnsibleRoles.gql';
|
11
|
+
import { encodeId } from '../../../../../globalIdHelper';
|
12
|
+
|
13
|
+
import './EditRolesModal.scss';
|
14
|
+
|
15
|
+
const EditRolesModal = ({
|
16
|
+
assignedRoles,
|
17
|
+
isOpen,
|
18
|
+
closeModal,
|
19
|
+
hostId,
|
20
|
+
canEditHost,
|
21
|
+
}) => {
|
22
|
+
const baseModalProps = {
|
23
|
+
variant: ModalVariant.large,
|
24
|
+
isOpen,
|
25
|
+
className: 'foreman-modal',
|
26
|
+
showClose: false,
|
27
|
+
title: __('Edit Ansible Roles'),
|
28
|
+
disableFocusTrap: true,
|
29
|
+
};
|
30
|
+
|
31
|
+
const actions = [
|
32
|
+
<Button variant="link" onClick={event => closeModal()} key="close">
|
33
|
+
{__('Close')}
|
34
|
+
</Button>,
|
35
|
+
];
|
36
|
+
|
37
|
+
const emptyWrapper = child => (
|
38
|
+
<Modal {...baseModalProps} actions={actions}>
|
39
|
+
{child}
|
40
|
+
</Modal>
|
41
|
+
);
|
42
|
+
|
43
|
+
const loadingWrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
|
44
|
+
|
45
|
+
const variables = {
|
46
|
+
id: encodeId('Host', hostId),
|
47
|
+
};
|
48
|
+
|
49
|
+
const useFetchFn = () =>
|
50
|
+
useQuery(availableAnsibleRoles, { variables, fetchPolicy: 'network-only' });
|
51
|
+
|
52
|
+
const renameData = data => ({
|
53
|
+
availableRoles: data.host.availableAnsibleRoles.nodes,
|
54
|
+
});
|
55
|
+
|
56
|
+
return (
|
57
|
+
<EditRolesForm
|
58
|
+
emptyWrapper={emptyWrapper}
|
59
|
+
loadingWrapper={loadingWrapper}
|
60
|
+
actions={actions}
|
61
|
+
baseModalProps={baseModalProps}
|
62
|
+
fetchFn={useFetchFn}
|
63
|
+
renameData={renameData}
|
64
|
+
renamedDataPath="availableRoles"
|
65
|
+
assignedRoles={assignedRoles}
|
66
|
+
closeModal={closeModal}
|
67
|
+
hostId={hostId}
|
68
|
+
allowed={canEditHost}
|
69
|
+
requiredPermissions={['edit_hosts']}
|
70
|
+
/>
|
71
|
+
);
|
72
|
+
};
|
73
|
+
|
74
|
+
EditRolesModal.propTypes = {
|
75
|
+
assignedRoles: PropTypes.array.isRequired,
|
76
|
+
isOpen: PropTypes.bool.isRequired,
|
77
|
+
closeModal: PropTypes.func.isRequired,
|
78
|
+
hostId: PropTypes.number.isRequired,
|
79
|
+
canEditHost: PropTypes.bool.isRequired,
|
80
|
+
};
|
81
|
+
|
82
|
+
export default EditRolesModal;
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
import { Route, Link } from 'react-router-dom';
|
5
|
+
import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
|
6
|
+
|
7
|
+
import {
|
8
|
+
TableComposable,
|
9
|
+
Thead,
|
10
|
+
Tbody,
|
11
|
+
Tr,
|
12
|
+
Th,
|
13
|
+
Td,
|
14
|
+
} from '@patternfly/react-table';
|
15
|
+
import { Flex, FlexItem, Button, Pagination } from '@patternfly/react-core';
|
16
|
+
|
17
|
+
import EditRolesModal from './EditRolesModal';
|
18
|
+
|
19
|
+
import withLoading from '../../../withLoading';
|
20
|
+
import AllRolesModal from './AllRolesModal';
|
21
|
+
import {
|
22
|
+
preparePerPageOptions,
|
23
|
+
refreshPage,
|
24
|
+
} from '../../../../helpers/paginationHelper';
|
25
|
+
|
26
|
+
const RolesTable = ({
|
27
|
+
totalCount,
|
28
|
+
pagination,
|
29
|
+
history,
|
30
|
+
ansibleRoles,
|
31
|
+
hostId,
|
32
|
+
hostGlobalId,
|
33
|
+
canEditHost,
|
34
|
+
}) => {
|
35
|
+
const columns = [__('Name')];
|
36
|
+
|
37
|
+
const handlePerPageSelected = (event, perPage) => {
|
38
|
+
refreshPage(history, { page: 1, perPage });
|
39
|
+
};
|
40
|
+
|
41
|
+
const handlePageSelected = (event, page) => {
|
42
|
+
refreshPage(history, { ...pagination, page });
|
43
|
+
};
|
44
|
+
|
45
|
+
const perPageOptions = preparePerPageOptions(usePaginationOptions());
|
46
|
+
|
47
|
+
const editBtn = canEditHost ? (
|
48
|
+
<FlexItem>
|
49
|
+
<Link to="/Ansible/roles/edit">
|
50
|
+
<Button aria-label="edit ansible roles">
|
51
|
+
{__('Edit Ansible Roles')}
|
52
|
+
</Button>
|
53
|
+
</Link>
|
54
|
+
</FlexItem>
|
55
|
+
) : null;
|
56
|
+
|
57
|
+
return (
|
58
|
+
<React.Fragment>
|
59
|
+
<Flex>
|
60
|
+
<FlexItem>
|
61
|
+
<h3>
|
62
|
+
<span>{__('Ansible roles assigned directly to host')}</span>
|
63
|
+
<span>{' - '}</span>
|
64
|
+
<Link to="/Ansible/roles/all">{__('view all assigned roles')}</Link>
|
65
|
+
</h3>
|
66
|
+
</FlexItem>
|
67
|
+
</Flex>
|
68
|
+
<Flex>
|
69
|
+
<FlexItem>{editBtn}</FlexItem>
|
70
|
+
<FlexItem align={{ default: 'alignRight' }}>
|
71
|
+
<Pagination
|
72
|
+
itemCount={totalCount}
|
73
|
+
page={pagination.page}
|
74
|
+
perPage={pagination.perPage}
|
75
|
+
onSetPage={handlePageSelected}
|
76
|
+
onPerPageSelect={handlePerPageSelected}
|
77
|
+
perPageOptions={perPageOptions}
|
78
|
+
variant="top"
|
79
|
+
/>
|
80
|
+
</FlexItem>
|
81
|
+
</Flex>
|
82
|
+
<TableComposable variant="compact">
|
83
|
+
<Thead>
|
84
|
+
<Tr>
|
85
|
+
{columns.map(col => (
|
86
|
+
<Th key={col}>{col}</Th>
|
87
|
+
))}
|
88
|
+
</Tr>
|
89
|
+
</Thead>
|
90
|
+
<Tbody>
|
91
|
+
{ansibleRoles.map(role => (
|
92
|
+
<Tr key={role.id}>
|
93
|
+
<Td>{role.name}</Td>
|
94
|
+
</Tr>
|
95
|
+
))}
|
96
|
+
</Tbody>
|
97
|
+
</TableComposable>
|
98
|
+
<Route path="/Ansible/roles/edit">
|
99
|
+
<EditRolesModal
|
100
|
+
closeModal={() => history.goBack()}
|
101
|
+
isOpen
|
102
|
+
assignedRoles={ansibleRoles}
|
103
|
+
hostId={hostId}
|
104
|
+
canEditHost={canEditHost}
|
105
|
+
/>
|
106
|
+
</Route>
|
107
|
+
<Route path="/Ansible/roles/all">
|
108
|
+
<AllRolesModal
|
109
|
+
onClose={() => history.push('/Ansible/roles')}
|
110
|
+
isOpen
|
111
|
+
hostGlobalId={hostGlobalId}
|
112
|
+
history={history}
|
113
|
+
/>
|
114
|
+
</Route>
|
115
|
+
</React.Fragment>
|
116
|
+
);
|
117
|
+
};
|
118
|
+
|
119
|
+
RolesTable.propTypes = {
|
120
|
+
ansibleRoles: PropTypes.array.isRequired,
|
121
|
+
hostId: PropTypes.number.isRequired,
|
122
|
+
hostGlobalId: PropTypes.string.isRequired,
|
123
|
+
history: PropTypes.object.isRequired,
|
124
|
+
pagination: PropTypes.object.isRequired,
|
125
|
+
totalCount: PropTypes.number.isRequired,
|
126
|
+
canEditHost: PropTypes.bool.isRequired,
|
127
|
+
};
|
128
|
+
|
129
|
+
export default withLoading(RolesTable);
|
@@ -0,0 +1,85 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
3
|
+
import '@testing-library/jest-dom';
|
4
|
+
import userEvent from '@testing-library/user-event';
|
5
|
+
|
6
|
+
import RolesTab from '../';
|
7
|
+
|
8
|
+
import * as toasts from '../../../../../toastHelper';
|
9
|
+
|
10
|
+
import {
|
11
|
+
tick,
|
12
|
+
withMockedProvider,
|
13
|
+
withRedux,
|
14
|
+
withReactRouter,
|
15
|
+
} from '../../../../../testHelper';
|
16
|
+
import {
|
17
|
+
mocks,
|
18
|
+
hostId,
|
19
|
+
editModalOpenMocks,
|
20
|
+
assignRolesSuccessMock,
|
21
|
+
assignRolesErrorMock,
|
22
|
+
} from './RolesTab.fixtures';
|
23
|
+
|
24
|
+
const TestComponent = withReactRouter(withRedux(withMockedProvider(RolesTab)));
|
25
|
+
|
26
|
+
describe('assigning Ansible roles', () => {
|
27
|
+
it('should assign Ansible roles', async () => {
|
28
|
+
const showToast = jest.fn();
|
29
|
+
jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
|
30
|
+
|
31
|
+
render(
|
32
|
+
<TestComponent
|
33
|
+
hostId={hostId}
|
34
|
+
mocks={mocks.concat(editModalOpenMocks).concat(assignRolesSuccessMock)}
|
35
|
+
canEditHost
|
36
|
+
/>
|
37
|
+
);
|
38
|
+
await waitFor(tick);
|
39
|
+
userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
|
40
|
+
await waitFor(tick);
|
41
|
+
await waitFor(tick);
|
42
|
+
expect(screen.getByText('Available options')).toBeInTheDocument();
|
43
|
+
userEvent.click(screen.getAllByText('another.role')[1]);
|
44
|
+
userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
|
45
|
+
userEvent.click(screen.getByText('geerlingguy.ceylon'));
|
46
|
+
userEvent.click(screen.getByRole('button', { name: 'Add selected' }));
|
47
|
+
userEvent.click(
|
48
|
+
screen.getByRole('button', { name: 'submit ansible roles' })
|
49
|
+
);
|
50
|
+
await waitFor(tick);
|
51
|
+
expect(showToast).toHaveBeenCalledWith({
|
52
|
+
type: 'success',
|
53
|
+
message: 'Ansible Roles were successfully assigned.',
|
54
|
+
});
|
55
|
+
}, 8000);
|
56
|
+
it('should show errors', async () => {
|
57
|
+
const showToast = jest.fn();
|
58
|
+
jest.spyOn(toasts, 'showToast').mockImplementation(showToast);
|
59
|
+
|
60
|
+
render(
|
61
|
+
<TestComponent
|
62
|
+
hostId={hostId}
|
63
|
+
mocks={mocks.concat(editModalOpenMocks).concat(assignRolesErrorMock)}
|
64
|
+
canEditHost
|
65
|
+
/>
|
66
|
+
);
|
67
|
+
await waitFor(tick);
|
68
|
+
userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
|
69
|
+
await waitFor(tick);
|
70
|
+
expect(screen.getByText('Available options')).toBeInTheDocument();
|
71
|
+
userEvent.click(screen.getAllByText('another.role')[1]);
|
72
|
+
userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
|
73
|
+
userEvent.click(screen.getByText('geerlingguy.ceylon'));
|
74
|
+
userEvent.click(screen.getByRole('button', { name: 'Add selected' }));
|
75
|
+
userEvent.click(
|
76
|
+
screen.getByRole('button', { name: 'submit ansible roles' })
|
77
|
+
);
|
78
|
+
await waitFor(tick);
|
79
|
+
expect(showToast).toHaveBeenCalledWith({
|
80
|
+
type: 'error',
|
81
|
+
message:
|
82
|
+
'There was a following error when assigning Ansible Roles: is invalid',
|
83
|
+
});
|
84
|
+
}, 8000);
|
85
|
+
});
|