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,104 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen } from '@testing-library/react';
|
3
|
+
import '@testing-library/jest-dom';
|
4
|
+
import AnsibleHostInventory from './AnsibleHostInventory';
|
5
|
+
|
6
|
+
const inventoryData = {
|
7
|
+
all: {
|
8
|
+
hosts: ['jim-dorsay.tlv.redhat.com'],
|
9
|
+
vars: {},
|
10
|
+
},
|
11
|
+
_meta: {
|
12
|
+
hostvars: {
|
13
|
+
'jim-dorsay.tlv.redhat.com': {
|
14
|
+
foreman: {
|
15
|
+
hostname: 'jim-dorsay',
|
16
|
+
fqdn: 'jim-dorsay.tlv.redhat.com',
|
17
|
+
hostgroup: 'libvirt',
|
18
|
+
foreman_subnets: [
|
19
|
+
{
|
20
|
+
name: 'subnet',
|
21
|
+
network: '192.168.122.0',
|
22
|
+
mask: '255.255.255.0',
|
23
|
+
gateway: '',
|
24
|
+
dns_primary: '',
|
25
|
+
dns_secondary: '',
|
26
|
+
from: '',
|
27
|
+
to: '',
|
28
|
+
boot_mode: 'DHCP',
|
29
|
+
ipam: 'DHCP',
|
30
|
+
vlanid: null,
|
31
|
+
mtu: 1500,
|
32
|
+
nic_delay: null,
|
33
|
+
network_type: 'IPv4',
|
34
|
+
description: '',
|
35
|
+
},
|
36
|
+
],
|
37
|
+
foreman_interfaces: [
|
38
|
+
{
|
39
|
+
ip: '192.168.122.139',
|
40
|
+
ip6: '',
|
41
|
+
mac: '52:54:00:bd:01:0d',
|
42
|
+
name: 'jim-dorsay.tlv.redhat.com',
|
43
|
+
attrs: {},
|
44
|
+
virtual: false,
|
45
|
+
link: true,
|
46
|
+
identifier: '',
|
47
|
+
managed: true,
|
48
|
+
primary: true,
|
49
|
+
provision: true,
|
50
|
+
subnet: {},
|
51
|
+
subnet6: null,
|
52
|
+
tag: null,
|
53
|
+
attached_to: null,
|
54
|
+
type: 'Interface',
|
55
|
+
},
|
56
|
+
],
|
57
|
+
location: 'Default Location',
|
58
|
+
location_title: 'Default Location',
|
59
|
+
organization: 'Default Organization',
|
60
|
+
organization_title: 'Default Organization',
|
61
|
+
domainname: 'tlv.redhat.com',
|
62
|
+
foreman_domain_description: '',
|
63
|
+
owner_name: 'Admin User',
|
64
|
+
owner_email: 'root@localdomain',
|
65
|
+
ssh_authorized_keys: [],
|
66
|
+
foreman_users: {
|
67
|
+
admin: {
|
68
|
+
firstname: 'Admin',
|
69
|
+
lastname: 'User',
|
70
|
+
mail: 'root@localdomain',
|
71
|
+
description: '',
|
72
|
+
fullname: 'Admin User',
|
73
|
+
name: 'admin',
|
74
|
+
ssh_authorized_keys: [],
|
75
|
+
},
|
76
|
+
},
|
77
|
+
root_pw:
|
78
|
+
'$5$I6cmWY8Uy4NaTfqW$wkKsQzinnd2iNTHvYPYPxO/YkHlvT/sgVF1n6paZvd8',
|
79
|
+
foreman_config_groups: [],
|
80
|
+
puppetmaster: '',
|
81
|
+
},
|
82
|
+
foreman_ansible_roles: ['foreman_ansible_test'],
|
83
|
+
ansible_roles_check_mode: false,
|
84
|
+
host_packages: '',
|
85
|
+
host_registration_insights: false,
|
86
|
+
host_registration_remote_execution: true,
|
87
|
+
remote_execution_ssh_keys: [
|
88
|
+
'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6ERZ9H22Di8mHRcxoTyIc+kcKCpGYq6G4/Ub9WK+DZlZZNMXCcO0WydV9GyJX9eIGrStDxZvxtJQQoOvYRS8d92RZPy3wIR4GtingmVNpwehNePHAcF1Aj9nqm+A4V49R3PqC28ctzBaJAbrvnNh/xDCsTW/ogexu8yN4iKkt0ZijUTnAZ2w1dXit23iITm0I8NPYqxRFNQOYtLYBZDKX+rPanjkyYGEyvi/RkhhHOimMjhAR0Qo9Vqm438LZWrZzGAffiE3AVYBWd3Eh2B5nW1q4cmS7CYOfsQCmO9u9x9lFzZCfdIFVWBhSyqR7cw/M7rTmixUNoX3QvSM0A+z/pBx++SgB+LhV2Zmek68NvcG/9LElD3pVESyvqtRbQQtb7Y6e553QUBn4lOg/N/p67TzfySCm0QhU5dKyj52Bvg6yhrwvqbVw1lwpjf6CNLERcRsTomIUDLVzDdcxo+u5GZ2J/hi5xH7NNMT89oPErwChm8wUDQyAdKGntCFP4D8= yifatfanimakias@localhost.localdomain',
|
89
|
+
],
|
90
|
+
remote_execution_ssh_user: 'root',
|
91
|
+
remote_execution_effective_user_method: 'sudo',
|
92
|
+
remote_execution_connect_by_ip: false,
|
93
|
+
},
|
94
|
+
},
|
95
|
+
},
|
96
|
+
};
|
97
|
+
|
98
|
+
describe('AnsibleHostInventory', () => {
|
99
|
+
it('should show inventory', () => {
|
100
|
+
render(<AnsibleHostInventory inventoryData={inventoryData} />);
|
101
|
+
expect(screen.getByText('_meta:')).toBeInTheDocument();
|
102
|
+
expect(screen.getByText('all:')).toBeInTheDocument();
|
103
|
+
});
|
104
|
+
});
|
@@ -0,0 +1,38 @@
|
|
1
|
+
import React, { useMemo } from 'react';
|
2
|
+
import { foremanUrl } from 'foremanReact/common/helpers';
|
3
|
+
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
|
4
|
+
import Skeleton from 'react-loading-skeleton';
|
5
|
+
import { STATUS } from 'foremanReact/constants';
|
6
|
+
|
7
|
+
import PropTypes from 'prop-types';
|
8
|
+
import AnsibleHostInventory from './AnsibleHostInventory';
|
9
|
+
import ErrorState from '../../../ErrorState';
|
10
|
+
|
11
|
+
const WrappedAnsibleHostInventory = ({ hostId }) => {
|
12
|
+
const params = useMemo(() => ({ params: { host_ids: [hostId] } }), [hostId]);
|
13
|
+
|
14
|
+
const url = hostId && foremanUrl('/ansible/api/ansible_inventories/hosts');
|
15
|
+
const { response: inventory, status } = useAPI('get', url, params);
|
16
|
+
|
17
|
+
if (status === STATUS.PENDING) {
|
18
|
+
return <Skeleton count={5} />;
|
19
|
+
}
|
20
|
+
|
21
|
+
if (status === STATUS.ERROR) {
|
22
|
+
return (
|
23
|
+
<ErrorState description={inventory?.response?.data?.error?.message} />
|
24
|
+
);
|
25
|
+
}
|
26
|
+
|
27
|
+
return <AnsibleHostInventory inventoryData={inventory} />;
|
28
|
+
};
|
29
|
+
|
30
|
+
WrappedAnsibleHostInventory.propTypes = {
|
31
|
+
hostId: PropTypes.number,
|
32
|
+
};
|
33
|
+
|
34
|
+
WrappedAnsibleHostInventory.defaultProps = {
|
35
|
+
hostId: undefined,
|
36
|
+
};
|
37
|
+
|
38
|
+
export default WrappedAnsibleHostInventory;
|
@@ -0,0 +1,238 @@
|
|
1
|
+
import React, { useReducer } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { useDispatch } from 'react-redux';
|
4
|
+
import { useMutation } from '@apollo/client';
|
5
|
+
|
6
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
7
|
+
import { usePaginationOptions } from 'foremanReact/components/Pagination/PaginationHooks';
|
8
|
+
import { openConfirmModal } from 'foremanReact/components/ConfirmModal';
|
9
|
+
import {
|
10
|
+
TableComposable,
|
11
|
+
Thead,
|
12
|
+
Tbody,
|
13
|
+
Tr,
|
14
|
+
Th,
|
15
|
+
Td,
|
16
|
+
} from '@patternfly/react-table';
|
17
|
+
import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
|
18
|
+
|
19
|
+
import deleteAnsibleVariableOverride from '../../../../graphql/mutations/deleteAnsibleVariableOverride.gql';
|
20
|
+
import EditableAction from './EditableAction';
|
21
|
+
import EditableValue from './EditableValue';
|
22
|
+
import { decodeModelId } from '../../../../globalIdHelper';
|
23
|
+
import {
|
24
|
+
formatSourceAttr,
|
25
|
+
findOverride,
|
26
|
+
validateValue,
|
27
|
+
onCompleted,
|
28
|
+
onError,
|
29
|
+
} from './AnsibleVariableOverridesTableHelper';
|
30
|
+
|
31
|
+
import withLoading from '../../../withLoading';
|
32
|
+
import {
|
33
|
+
preparePerPageOptions,
|
34
|
+
refreshPage,
|
35
|
+
} from '../../../../helpers/paginationHelper';
|
36
|
+
|
37
|
+
const reducer = (state, action) =>
|
38
|
+
state.map((item, index) => {
|
39
|
+
if (action.idx === index) {
|
40
|
+
return { ...item, ...action.payload };
|
41
|
+
}
|
42
|
+
return item;
|
43
|
+
});
|
44
|
+
|
45
|
+
const initState = vars =>
|
46
|
+
vars.map((variable, idx) => ({
|
47
|
+
open: false,
|
48
|
+
value: variable.currentValue
|
49
|
+
? variable.currentValue.value
|
50
|
+
: variable.defaultValue,
|
51
|
+
validation: { key: 'noval', msg: '' },
|
52
|
+
working: false,
|
53
|
+
}));
|
54
|
+
|
55
|
+
const AnsibleVariableOverridesTable = ({
|
56
|
+
variables,
|
57
|
+
hostAttrs,
|
58
|
+
hostId,
|
59
|
+
hostGlobalId,
|
60
|
+
totalCount,
|
61
|
+
pagination,
|
62
|
+
history,
|
63
|
+
}) => {
|
64
|
+
const columns = [
|
65
|
+
__('Name'),
|
66
|
+
__('Ansible Role'),
|
67
|
+
__('Type'),
|
68
|
+
__('Value'),
|
69
|
+
__('Source attribute'),
|
70
|
+
];
|
71
|
+
|
72
|
+
const handlePerPageSelected = (event, perPage) => {
|
73
|
+
refreshPage(history, { page: 1, perPage });
|
74
|
+
};
|
75
|
+
|
76
|
+
const handlePageSelected = (event, page) => {
|
77
|
+
refreshPage(history, { ...pagination, page });
|
78
|
+
};
|
79
|
+
|
80
|
+
const perPageOptions = preparePerPageOptions(usePaginationOptions());
|
81
|
+
|
82
|
+
const [editableState, innerDispatch] = useReducer(
|
83
|
+
reducer,
|
84
|
+
variables,
|
85
|
+
initState
|
86
|
+
);
|
87
|
+
|
88
|
+
const toggleWorking = idx => flag => {
|
89
|
+
innerDispatch({ idx, payload: { working: flag } });
|
90
|
+
};
|
91
|
+
|
92
|
+
const setEditable = (idx, flag) => () => {
|
93
|
+
innerDispatch({ idx, payload: { open: flag } });
|
94
|
+
};
|
95
|
+
|
96
|
+
const onValueChange = (idx, variable) => value => {
|
97
|
+
const payload = {
|
98
|
+
value,
|
99
|
+
validation: validateValue(variable, value),
|
100
|
+
};
|
101
|
+
innerDispatch({ idx, payload });
|
102
|
+
};
|
103
|
+
|
104
|
+
const onSubmitSuccess = (idx, variable) => newValue => {
|
105
|
+
innerDispatch({
|
106
|
+
idx,
|
107
|
+
payload: {
|
108
|
+
open: false,
|
109
|
+
working: false,
|
110
|
+
value: newValue,
|
111
|
+
validation: validateValue(variable, newValue),
|
112
|
+
},
|
113
|
+
});
|
114
|
+
};
|
115
|
+
|
116
|
+
const onValidationError = idx => error => {
|
117
|
+
innerDispatch({
|
118
|
+
idx,
|
119
|
+
payload: {
|
120
|
+
working: false,
|
121
|
+
validation: { key: 'error', msg: error },
|
122
|
+
},
|
123
|
+
});
|
124
|
+
};
|
125
|
+
|
126
|
+
const dispatch = useDispatch();
|
127
|
+
const [callMutation] = useMutation(deleteAnsibleVariableOverride);
|
128
|
+
|
129
|
+
const deleteAction = (variable, idx) => ({
|
130
|
+
title: __('Delete'),
|
131
|
+
onClick: () => {
|
132
|
+
dispatch(
|
133
|
+
openConfirmModal({
|
134
|
+
title: __('Delete Ansible Variable Override'),
|
135
|
+
message:
|
136
|
+
variable &&
|
137
|
+
sprintf(
|
138
|
+
__('Are you sure you want to delete override for %s?'),
|
139
|
+
variable.key
|
140
|
+
),
|
141
|
+
onConfirm: () => {
|
142
|
+
callMutation({
|
143
|
+
variables: {
|
144
|
+
id: findOverride(variable, hostAttrs.name).id,
|
145
|
+
hostId,
|
146
|
+
variableId: decodeModelId(variable),
|
147
|
+
},
|
148
|
+
}).then(onCompleted(onValueChange(idx, variable)), onError); // eslint-disable-line
|
149
|
+
},
|
150
|
+
})
|
151
|
+
);
|
152
|
+
},
|
153
|
+
});
|
154
|
+
|
155
|
+
const actionsResolver = (variable, idx) => {
|
156
|
+
const actions = [];
|
157
|
+
if (variable.currentValue?.element === 'fqdn' && variable.meta.canEdit) {
|
158
|
+
actions.push(deleteAction(variable, idx));
|
159
|
+
}
|
160
|
+
return actions;
|
161
|
+
};
|
162
|
+
|
163
|
+
return (
|
164
|
+
<React.Fragment>
|
165
|
+
<Flex>
|
166
|
+
<FlexItem align={{ default: 'alignRight' }}>
|
167
|
+
<Pagination
|
168
|
+
itemCount={totalCount}
|
169
|
+
page={pagination.page}
|
170
|
+
perPage={pagination.perPage}
|
171
|
+
onSetPage={handlePageSelected}
|
172
|
+
onPerPageSelect={handlePerPageSelected}
|
173
|
+
perPageOptions={perPageOptions}
|
174
|
+
variant="top"
|
175
|
+
/>
|
176
|
+
</FlexItem>
|
177
|
+
</Flex>
|
178
|
+
<TableComposable variant="compact">
|
179
|
+
<Thead>
|
180
|
+
<Tr>
|
181
|
+
{columns.map(col => (
|
182
|
+
<Th key={col}>{col}</Th>
|
183
|
+
))}
|
184
|
+
<Th />
|
185
|
+
</Tr>
|
186
|
+
</Thead>
|
187
|
+
<Tbody>
|
188
|
+
{variables.map((variable, idx) => (
|
189
|
+
<Tr key={idx}>
|
190
|
+
<Td>{variable.key}</Td>
|
191
|
+
<Td>{variable.ansibleRoleName}</Td>
|
192
|
+
<Td>{variable.parameterType}</Td>
|
193
|
+
<Td>
|
194
|
+
<EditableValue
|
195
|
+
variable={variable}
|
196
|
+
editing={editableState[idx].open}
|
197
|
+
onChange={onValueChange(idx, variable)}
|
198
|
+
value={editableState[idx].value}
|
199
|
+
validation={editableState[idx].validation}
|
200
|
+
working={editableState[idx].working}
|
201
|
+
/>
|
202
|
+
</Td>
|
203
|
+
<Td>{formatSourceAttr(variable)}</Td>
|
204
|
+
<Td>
|
205
|
+
<EditableAction
|
206
|
+
open={editableState[idx].open}
|
207
|
+
onClose={setEditable(idx, false)}
|
208
|
+
onOpen={setEditable(idx, true)}
|
209
|
+
toggleWorking={toggleWorking(idx)}
|
210
|
+
variable={variable}
|
211
|
+
state={editableState[idx]}
|
212
|
+
hostId={hostId}
|
213
|
+
hostName={hostAttrs.name}
|
214
|
+
hostGlobalId={hostGlobalId}
|
215
|
+
onSubmitSuccess={onSubmitSuccess(idx, variable)}
|
216
|
+
onValidationError={onValidationError(idx)}
|
217
|
+
/>
|
218
|
+
</Td>
|
219
|
+
<Td actions={{ items: actionsResolver(variable, idx) }} />
|
220
|
+
</Tr>
|
221
|
+
))}
|
222
|
+
</Tbody>
|
223
|
+
</TableComposable>
|
224
|
+
</React.Fragment>
|
225
|
+
);
|
226
|
+
};
|
227
|
+
|
228
|
+
AnsibleVariableOverridesTable.propTypes = {
|
229
|
+
variables: PropTypes.array.isRequired,
|
230
|
+
hostAttrs: PropTypes.object.isRequired,
|
231
|
+
hostId: PropTypes.number.isRequired,
|
232
|
+
hostGlobalId: PropTypes.string.isRequired,
|
233
|
+
totalCount: PropTypes.number.isRequired,
|
234
|
+
pagination: PropTypes.object.isRequired,
|
235
|
+
history: PropTypes.object.isRequired,
|
236
|
+
};
|
237
|
+
|
238
|
+
export default withLoading(AnsibleVariableOverridesTable);
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { TimesIcon, CheckIcon } from '@patternfly/react-icons';
|
3
|
+
import { sprintf, translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
|
5
|
+
import { showToast } from '../../../../toastHelper';
|
6
|
+
|
7
|
+
const formatSourceLink = currentValue =>
|
8
|
+
`${__(currentValue.element)}: ${currentValue.elementName}`;
|
9
|
+
|
10
|
+
export const formatSourceAttr = variable =>
|
11
|
+
variable.currentValue
|
12
|
+
? formatSourceLink(variable.currentValue)
|
13
|
+
: __('Default value');
|
14
|
+
|
15
|
+
export const formatValue = variable => {
|
16
|
+
const value = variable.currentValue
|
17
|
+
? variable.currentValue.value
|
18
|
+
: variable.defaultValue;
|
19
|
+
|
20
|
+
switch (variable.parameterType) {
|
21
|
+
case 'boolean':
|
22
|
+
return value ? <CheckIcon /> : <TimesIcon />;
|
23
|
+
case 'yaml':
|
24
|
+
case 'hash':
|
25
|
+
case 'array':
|
26
|
+
return JSON.stringify(value);
|
27
|
+
default:
|
28
|
+
return value;
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
const joinErrors = errors => errors.map(err => err.message).join(', ');
|
33
|
+
|
34
|
+
export const onCompleted = onSubmitSuccess => response => {
|
35
|
+
const {
|
36
|
+
errors,
|
37
|
+
overridenAnsibleVariable,
|
38
|
+
} = response.data.deleteAnsibleVariableOverride;
|
39
|
+
if (Array.isArray(errors) && errors.length > 0) {
|
40
|
+
showToast({
|
41
|
+
type: 'error',
|
42
|
+
message: formatError(joinErrors(errors)),
|
43
|
+
});
|
44
|
+
} else {
|
45
|
+
onSubmitSuccess(overridenAnsibleVariable.currentValue.value);
|
46
|
+
showToast({
|
47
|
+
type: 'success',
|
48
|
+
message: __('Ansible variable override was successfully deleted.'),
|
49
|
+
});
|
50
|
+
}
|
51
|
+
};
|
52
|
+
|
53
|
+
export const findOverride = (variable, hostname) =>
|
54
|
+
variable.lookupValues.nodes.find(
|
55
|
+
item =>
|
56
|
+
item.value === variable.currentValue.value &&
|
57
|
+
item.match === `fqdn=${hostname}`
|
58
|
+
);
|
59
|
+
|
60
|
+
const formatError = error =>
|
61
|
+
sprintf(
|
62
|
+
__(
|
63
|
+
'There was a following error when deleting Ansible variable override: %s'
|
64
|
+
),
|
65
|
+
error
|
66
|
+
);
|
67
|
+
|
68
|
+
export const onError = response => {
|
69
|
+
showToast({ type: 'error', message: formatError(response.error) });
|
70
|
+
};
|
71
|
+
|
72
|
+
const validationSuccess = { key: 'success', msg: '' };
|
73
|
+
|
74
|
+
const validateRegexp = (variable, value) => {
|
75
|
+
if (new RegExp(variable.validatorRule).test(value)) {
|
76
|
+
return validationSuccess;
|
77
|
+
}
|
78
|
+
return {
|
79
|
+
key: 'error',
|
80
|
+
msg: sprintf(
|
81
|
+
__('Invalid, expected to match a regex: %s'),
|
82
|
+
variable.validatorRule
|
83
|
+
),
|
84
|
+
};
|
85
|
+
};
|
86
|
+
|
87
|
+
const validateList = (variable, value) => {
|
88
|
+
if (variable.validatorRule.split(',').find(item => item.trim() === value)) {
|
89
|
+
return validationSuccess;
|
90
|
+
}
|
91
|
+
return {
|
92
|
+
key: 'error',
|
93
|
+
msg: sprintf(__('Invalid, expected one of: %s'), variable.validatorRule),
|
94
|
+
};
|
95
|
+
};
|
96
|
+
|
97
|
+
export const validateValue = (variable, value) => {
|
98
|
+
if (variable.required && !value) {
|
99
|
+
return { key: 'error', msg: __('is required') };
|
100
|
+
}
|
101
|
+
|
102
|
+
if (variable.validatorType === 'regexp') {
|
103
|
+
return validateRegexp(variable, value);
|
104
|
+
}
|
105
|
+
|
106
|
+
if (variable.validatorType === 'list') {
|
107
|
+
return validateList(variable, value);
|
108
|
+
}
|
109
|
+
|
110
|
+
return { key: 'noval', msg: '' };
|
111
|
+
};
|
data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/EditableAction.js
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { useMutation } from '@apollo/client';
|
4
|
+
import classNames from 'classnames';
|
5
|
+
|
6
|
+
import { Button, Spinner } from '@patternfly/react-core';
|
7
|
+
import { TimesIcon, CheckIcon, PencilAltIcon } from '@patternfly/react-icons';
|
8
|
+
|
9
|
+
import './EditableAction.scss';
|
10
|
+
|
11
|
+
import { decodeModelId } from '../../../../globalIdHelper';
|
12
|
+
import updateAnsibleVariableOverrideMutation from '../../../../graphql/mutations/updateAnsibleVariableOverride.gql';
|
13
|
+
import createAnsibleVariableOverrideMutation from '../../../../graphql/mutations/createAnsibleVariableOverride.gql';
|
14
|
+
import {
|
15
|
+
onCompleted,
|
16
|
+
onError,
|
17
|
+
hasError,
|
18
|
+
createMatcher,
|
19
|
+
} from './EditableActionHelper';
|
20
|
+
|
21
|
+
const EditableAction = ({
|
22
|
+
onValidationError,
|
23
|
+
toggleWorking,
|
24
|
+
onSubmitSuccess,
|
25
|
+
open,
|
26
|
+
onClose,
|
27
|
+
onOpen,
|
28
|
+
state,
|
29
|
+
variable,
|
30
|
+
hostId,
|
31
|
+
hostName,
|
32
|
+
}) => {
|
33
|
+
const [callUpdateMutation] = useMutation(
|
34
|
+
updateAnsibleVariableOverrideMutation,
|
35
|
+
{
|
36
|
+
onCompleted: onCompleted(
|
37
|
+
'updateAnsibleVariableOverride',
|
38
|
+
onValidationError,
|
39
|
+
toggleWorking,
|
40
|
+
onSubmitSuccess
|
41
|
+
),
|
42
|
+
onError: onError(toggleWorking),
|
43
|
+
}
|
44
|
+
);
|
45
|
+
|
46
|
+
const [callCreateMutation] = useMutation(
|
47
|
+
createAnsibleVariableOverrideMutation,
|
48
|
+
{
|
49
|
+
onCompleted: onCompleted(
|
50
|
+
'createAnsibleVariableOverride',
|
51
|
+
onValidationError,
|
52
|
+
toggleWorking,
|
53
|
+
onSubmitSuccess
|
54
|
+
),
|
55
|
+
onError: onError(toggleWorking),
|
56
|
+
}
|
57
|
+
);
|
58
|
+
|
59
|
+
const onSubmit = event => {
|
60
|
+
if (!variable.currentValue || variable.currentValue.element !== 'fqdn') {
|
61
|
+
return createOverride();
|
62
|
+
}
|
63
|
+
return updateOverride();
|
64
|
+
};
|
65
|
+
|
66
|
+
const updateOverride = () => {
|
67
|
+
const match = createMatcher(variable.currentValue.elementName);
|
68
|
+
const lookupValue = variable.lookupValues.nodes.find(
|
69
|
+
item => item.match === match
|
70
|
+
);
|
71
|
+
toggleWorking(true);
|
72
|
+
callUpdateMutation({
|
73
|
+
variables: {
|
74
|
+
id: lookupValue.id,
|
75
|
+
value: state.value,
|
76
|
+
hostId,
|
77
|
+
ansibleVariableId: decodeModelId(variable),
|
78
|
+
match,
|
79
|
+
},
|
80
|
+
});
|
81
|
+
};
|
82
|
+
|
83
|
+
const createOverride = () => {
|
84
|
+
const match = createMatcher(hostName);
|
85
|
+
toggleWorking(true);
|
86
|
+
callCreateMutation({
|
87
|
+
variables: {
|
88
|
+
hostId,
|
89
|
+
lookupKeyId: decodeModelId(variable),
|
90
|
+
value: state.value,
|
91
|
+
match,
|
92
|
+
},
|
93
|
+
});
|
94
|
+
};
|
95
|
+
|
96
|
+
if (!variable.meta.canEdit) {
|
97
|
+
return null;
|
98
|
+
}
|
99
|
+
|
100
|
+
return (
|
101
|
+
<React.Fragment>
|
102
|
+
<div>
|
103
|
+
<div
|
104
|
+
className={classNames({
|
105
|
+
hideElement: !open,
|
106
|
+
editableActionItem: true,
|
107
|
+
})}
|
108
|
+
>
|
109
|
+
<Button
|
110
|
+
variant="plain"
|
111
|
+
onClick={onClose}
|
112
|
+
isDisabled={state.working}
|
113
|
+
aria-label="Cancel editing override button"
|
114
|
+
>
|
115
|
+
<TimesIcon />
|
116
|
+
</Button>
|
117
|
+
<Button
|
118
|
+
variant="plain"
|
119
|
+
onClick={onSubmit}
|
120
|
+
isDisabled={state.working || hasError(state)}
|
121
|
+
aria-label="Submit editing override button"
|
122
|
+
>
|
123
|
+
<CheckIcon />
|
124
|
+
</Button>
|
125
|
+
<span className={!state.working ? 'hideElement' : ''}>
|
126
|
+
<Spinner size="md" />
|
127
|
+
</span>
|
128
|
+
</div>
|
129
|
+
<div
|
130
|
+
className={classNames({
|
131
|
+
hideElement: open,
|
132
|
+
editableActionItem: true,
|
133
|
+
})}
|
134
|
+
>
|
135
|
+
<Button
|
136
|
+
onClick={onOpen}
|
137
|
+
variant="plain"
|
138
|
+
aria-label="Edit override button"
|
139
|
+
>
|
140
|
+
<PencilAltIcon />
|
141
|
+
</Button>
|
142
|
+
</div>
|
143
|
+
</div>
|
144
|
+
</React.Fragment>
|
145
|
+
);
|
146
|
+
};
|
147
|
+
|
148
|
+
EditableAction.propTypes = {
|
149
|
+
onValidationError: PropTypes.func.isRequired,
|
150
|
+
toggleWorking: PropTypes.func.isRequired,
|
151
|
+
onSubmitSuccess: PropTypes.func.isRequired,
|
152
|
+
onClose: PropTypes.func.isRequired,
|
153
|
+
onOpen: PropTypes.func.isRequired,
|
154
|
+
variable: PropTypes.object.isRequired,
|
155
|
+
state: PropTypes.object.isRequired,
|
156
|
+
open: PropTypes.bool.isRequired,
|
157
|
+
hostId: PropTypes.number.isRequired,
|
158
|
+
hostName: PropTypes.string.isRequired,
|
159
|
+
};
|
160
|
+
|
161
|
+
export default EditableAction;
|