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,180 @@
|
|
1
|
+
import {
|
2
|
+
mockFactory,
|
3
|
+
advancedMockFactory,
|
4
|
+
admin,
|
5
|
+
intruder,
|
6
|
+
userFactory,
|
7
|
+
} from '../../../../../testHelper';
|
8
|
+
import ansibleRolesQuery from '../../../../../graphql/queries/hostAnsibleRoles.gql';
|
9
|
+
import allAnsibleRolesQuery from '../../../../../graphql/queries/allAnsibleRoles.gql';
|
10
|
+
import availableAnsibleRolesQuery from '../../../../../graphql/queries/hostAvailableAnsibleRoles.gql';
|
11
|
+
import assignAnsibleRolesMutation from '../../../../../graphql/mutations/assignAnsibleRoles.gql';
|
12
|
+
import { decodeModelId } from '../../../../../globalIdHelper';
|
13
|
+
|
14
|
+
export const hostId = 3;
|
15
|
+
const hostGlobalId = 'MDE6SG9zdC0z';
|
16
|
+
|
17
|
+
const ansibleRolesMockFactory = mockFactory('host', ansibleRolesQuery);
|
18
|
+
const allAnsibleRolesMockFactory = mockFactory('host', allAnsibleRolesQuery);
|
19
|
+
const assignRolesMockFactory = mockFactory(
|
20
|
+
'assignAnsibleRoles',
|
21
|
+
assignAnsibleRolesMutation
|
22
|
+
);
|
23
|
+
const editModalDataFactory = advancedMockFactory(availableAnsibleRolesQuery);
|
24
|
+
|
25
|
+
const viewer = userFactory('roles_viewer', [
|
26
|
+
{
|
27
|
+
__typename: 'Permission',
|
28
|
+
id: 'MDE6UGVybWlzc2lvbi0x',
|
29
|
+
name: 'view_ansible_roles',
|
30
|
+
},
|
31
|
+
]);
|
32
|
+
|
33
|
+
const role1 = {
|
34
|
+
__typename: 'AnsibleRole',
|
35
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMw==',
|
36
|
+
name: 'aardvaark.cube',
|
37
|
+
};
|
38
|
+
|
39
|
+
const role2 = {
|
40
|
+
__typename: 'AnsibleRole',
|
41
|
+
id: 'MDE6QW5zaWJsZVJvbGUtNQ==',
|
42
|
+
name: 'aardvaark.sphere',
|
43
|
+
};
|
44
|
+
|
45
|
+
const role3 = {
|
46
|
+
__typename: 'AnsibleRole',
|
47
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMzA=',
|
48
|
+
name: 'another.role',
|
49
|
+
};
|
50
|
+
|
51
|
+
const role4 = {
|
52
|
+
__typename: 'AnsibleRole',
|
53
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMzk=',
|
54
|
+
name: 'geerlingguy.ceylon',
|
55
|
+
};
|
56
|
+
|
57
|
+
const ansibleRolesMock = {
|
58
|
+
totalCount: 3,
|
59
|
+
nodes: [role1, role2, role3],
|
60
|
+
};
|
61
|
+
|
62
|
+
const ansibleRolesUpdatedMock = {
|
63
|
+
totalCount: 3,
|
64
|
+
nodes: [role1, role2, role4],
|
65
|
+
};
|
66
|
+
|
67
|
+
const availableRoles = {
|
68
|
+
nodes: [
|
69
|
+
role4,
|
70
|
+
{
|
71
|
+
__typename: 'AnsibleRole',
|
72
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMQ==',
|
73
|
+
name: 'theforeman.foreman_scap_client',
|
74
|
+
},
|
75
|
+
{
|
76
|
+
__typename: 'AnsibleRole',
|
77
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMg==',
|
78
|
+
name: 'adriagalin.motd',
|
79
|
+
},
|
80
|
+
{
|
81
|
+
__typename: 'AnsibleRole',
|
82
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMjI=',
|
83
|
+
name: 'geerlingguy.php',
|
84
|
+
},
|
85
|
+
{
|
86
|
+
__typename: 'AnsibleRole',
|
87
|
+
id: 'MDE6QW5zaWJsZVJvbGUtNTc=',
|
88
|
+
name: 'robertdebock.epel',
|
89
|
+
},
|
90
|
+
{
|
91
|
+
__typename: 'AnsibleRole',
|
92
|
+
id: 'MDE6QW5zaWJsZVJvbGUtNTg=',
|
93
|
+
name: 'geerlingguy.nfs',
|
94
|
+
},
|
95
|
+
],
|
96
|
+
};
|
97
|
+
|
98
|
+
export const allRolesMocks = allAnsibleRolesMockFactory(
|
99
|
+
{ id: hostGlobalId, first: 20, last: 20 },
|
100
|
+
{
|
101
|
+
__typename: 'Host',
|
102
|
+
id: hostGlobalId,
|
103
|
+
allAnsibleRoles: {
|
104
|
+
totalCount: 4,
|
105
|
+
nodes: [
|
106
|
+
{
|
107
|
+
id: 'MDE6QW5zaWJsZVJvbGUtMg==',
|
108
|
+
name: 'adriagalin.motd',
|
109
|
+
inherited: true,
|
110
|
+
},
|
111
|
+
{ ...role1, inherited: false },
|
112
|
+
{ ...role2, inherited: false },
|
113
|
+
{ ...role3, inherited: false },
|
114
|
+
],
|
115
|
+
},
|
116
|
+
}
|
117
|
+
);
|
118
|
+
|
119
|
+
const editModalData = {
|
120
|
+
host: {
|
121
|
+
__typename: 'Host',
|
122
|
+
id: hostGlobalId,
|
123
|
+
availableAnsibleRoles: availableRoles,
|
124
|
+
},
|
125
|
+
};
|
126
|
+
|
127
|
+
export const mocks = ansibleRolesMockFactory(
|
128
|
+
{ id: hostGlobalId, first: 20, last: 20 },
|
129
|
+
{ __typename: 'Host', id: hostGlobalId, ownAnsibleRoles: ansibleRolesMock },
|
130
|
+
{ currentUser: admin }
|
131
|
+
);
|
132
|
+
|
133
|
+
export const unauthorizedMocks = ansibleRolesMockFactory(
|
134
|
+
{ id: hostGlobalId, first: 20, last: 20 },
|
135
|
+
{ __typename: 'Host', id: hostGlobalId, ownAnsibleRoles: ansibleRolesMock },
|
136
|
+
{ currentUser: intruder }
|
137
|
+
);
|
138
|
+
|
139
|
+
export const authorizedMocks = ansibleRolesMockFactory(
|
140
|
+
{ id: hostGlobalId, first: 20, last: 20 },
|
141
|
+
{ __typename: 'Host', id: hostGlobalId, ownAnsibleRoles: ansibleRolesMock },
|
142
|
+
{ currentUser: viewer }
|
143
|
+
);
|
144
|
+
|
145
|
+
export const editModalOpenMocks = editModalDataFactory(
|
146
|
+
{
|
147
|
+
id: hostGlobalId,
|
148
|
+
},
|
149
|
+
editModalData
|
150
|
+
);
|
151
|
+
|
152
|
+
export const assignRolesSuccessMock = assignRolesMockFactory(
|
153
|
+
{
|
154
|
+
id: hostGlobalId,
|
155
|
+
ansibleRoleIds: [role1, role2, role4].map(decodeModelId),
|
156
|
+
},
|
157
|
+
{
|
158
|
+
host: {
|
159
|
+
__typename: 'Host',
|
160
|
+
id: hostGlobalId,
|
161
|
+
ownAnsibleRoles: ansibleRolesUpdatedMock,
|
162
|
+
},
|
163
|
+
errors: [],
|
164
|
+
}
|
165
|
+
);
|
166
|
+
|
167
|
+
export const assignRolesErrorMock = assignRolesMockFactory(
|
168
|
+
{
|
169
|
+
id: hostGlobalId,
|
170
|
+
ansibleRoleIds: [role1, role2, role4].map(decodeModelId),
|
171
|
+
},
|
172
|
+
{
|
173
|
+
host: {
|
174
|
+
__typename: 'Host',
|
175
|
+
id: hostGlobalId,
|
176
|
+
ownAnsibleRoles: ansibleRolesMock,
|
177
|
+
},
|
178
|
+
errors: [{ path: ['attributes', 'base'], message: 'is invalid' }],
|
179
|
+
}
|
180
|
+
);
|
@@ -0,0 +1,75 @@
|
|
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
|
+
import {
|
6
|
+
tick,
|
7
|
+
withMockedProvider,
|
8
|
+
withReactRouter,
|
9
|
+
} from '../../../../../testHelper';
|
10
|
+
|
11
|
+
import {
|
12
|
+
mocks,
|
13
|
+
hostId,
|
14
|
+
allRolesMocks,
|
15
|
+
unauthorizedMocks,
|
16
|
+
authorizedMocks,
|
17
|
+
} from './RolesTab.fixtures';
|
18
|
+
|
19
|
+
import RolesTab from '../';
|
20
|
+
|
21
|
+
const TestComponent = withReactRouter(withMockedProvider(RolesTab));
|
22
|
+
|
23
|
+
describe('RolesTab', () => {
|
24
|
+
it('should load Ansible Roles as admin', async () => {
|
25
|
+
render(<TestComponent hostId={hostId} mocks={mocks} canEditHost />);
|
26
|
+
await waitFor(tick);
|
27
|
+
expect(screen.getByText('aardvaark.cube')).toBeInTheDocument();
|
28
|
+
expect(screen.getByText('aardvaark.sphere')).toBeInTheDocument();
|
29
|
+
expect(screen.getByText('another.role')).toBeInTheDocument();
|
30
|
+
});
|
31
|
+
it('should show all Ansible roles modal', async () => {
|
32
|
+
render(
|
33
|
+
<TestComponent
|
34
|
+
hostId={hostId}
|
35
|
+
mocks={mocks.concat(allRolesMocks)}
|
36
|
+
canEditHost
|
37
|
+
/>
|
38
|
+
);
|
39
|
+
await waitFor(tick);
|
40
|
+
expect(screen.getByText('view all assigned roles')).toBeInTheDocument();
|
41
|
+
expect(screen.queryByText('All Ansible Roles')).not.toBeInTheDocument();
|
42
|
+
userEvent.click(screen.getByText('view all assigned roles'));
|
43
|
+
await waitFor(tick);
|
44
|
+
expect(screen.getByText('All Ansible Roles')).toBeInTheDocument();
|
45
|
+
expect(screen.getByText('Inherited from Hostgroup')).toBeInTheDocument();
|
46
|
+
userEvent.click(screen.getByRole('button', { name: 'Close' }));
|
47
|
+
await waitFor(tick);
|
48
|
+
expect(screen.queryByText('All Ansible Roles')).not.toBeInTheDocument();
|
49
|
+
});
|
50
|
+
it('should load Ansible Roles as viewer', async () => {
|
51
|
+
render(
|
52
|
+
<TestComponent
|
53
|
+
hostId={hostId}
|
54
|
+
mocks={authorizedMocks}
|
55
|
+
canEditHost={false}
|
56
|
+
/>
|
57
|
+
);
|
58
|
+
await waitFor(tick);
|
59
|
+
expect(screen.getByText('aardvaark.cube')).toBeInTheDocument();
|
60
|
+
expect(screen.queryByText('Edit Ansible Roles')).not.toBeInTheDocument();
|
61
|
+
});
|
62
|
+
it('should not load Ansible Roles for unauthorized user', async () => {
|
63
|
+
render(
|
64
|
+
<TestComponent hostId={hostId} mocks={unauthorizedMocks} canEditHost />
|
65
|
+
);
|
66
|
+
await waitFor(tick);
|
67
|
+
expect(screen.queryByText('aardvaark.cube')).not.toBeInTheDocument();
|
68
|
+
expect(screen.getByText('Permission denied')).toBeInTheDocument();
|
69
|
+
expect(
|
70
|
+
screen.getByText(
|
71
|
+
'You are not authorized to view the page. Request the following permissions from administrator: view_ansible_roles.'
|
72
|
+
)
|
73
|
+
).toBeInTheDocument();
|
74
|
+
});
|
75
|
+
});
|
@@ -0,0 +1,51 @@
|
|
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 ansibleRolesQuery from '../../../../graphql/queries/hostAnsibleRoles.gql';
|
7
|
+
import { encodeId } from '../../../../globalIdHelper';
|
8
|
+
import RolesTable from './RolesTable';
|
9
|
+
import {
|
10
|
+
useParamsToVars,
|
11
|
+
useCurrentPagination,
|
12
|
+
} from '../../../../helpers/pageParamsHelper';
|
13
|
+
|
14
|
+
const RolesTab = ({ hostId, history, canEditHost }) => {
|
15
|
+
const hostGlobalId = encodeId('Host', hostId);
|
16
|
+
const pagination = useCurrentPagination(history);
|
17
|
+
|
18
|
+
const renameData = data => ({
|
19
|
+
ansibleRoles: data.host.ownAnsibleRoles.nodes,
|
20
|
+
totalCount: data.host.ownAnsibleRoles.totalCount,
|
21
|
+
});
|
22
|
+
|
23
|
+
const useFetchFn = () =>
|
24
|
+
useQuery(ansibleRolesQuery, {
|
25
|
+
variables: { id: hostGlobalId, ...useParamsToVars(history) },
|
26
|
+
fetchPolicy: 'network-only',
|
27
|
+
});
|
28
|
+
|
29
|
+
return (
|
30
|
+
<RolesTable
|
31
|
+
fetchFn={useFetchFn}
|
32
|
+
renamedDataPath="ansibleRoles"
|
33
|
+
renameData={renameData}
|
34
|
+
permissions={['view_ansible_roles']}
|
35
|
+
history={history}
|
36
|
+
hostGlobalId={hostGlobalId}
|
37
|
+
emptyStateProps={{ title: __('No Ansible roles assigned') }}
|
38
|
+
pagination={pagination}
|
39
|
+
canEditHost={canEditHost}
|
40
|
+
hostId={hostId}
|
41
|
+
/>
|
42
|
+
);
|
43
|
+
};
|
44
|
+
|
45
|
+
RolesTab.propTypes = {
|
46
|
+
hostId: PropTypes.number.isRequired,
|
47
|
+
history: PropTypes.object.isRequired,
|
48
|
+
canEditHost: PropTypes.bool.isRequired,
|
49
|
+
};
|
50
|
+
|
51
|
+
export default RolesTab;
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { Route, Switch, Redirect } from 'react-router-dom';
|
4
|
+
|
5
|
+
import AnsibleVariableOverrides from './AnsibleVariableOverrides';
|
6
|
+
import RolesTab from './RolesTab';
|
7
|
+
import JobsTab from './JobsTab';
|
8
|
+
import TabLayout from './TabLayout';
|
9
|
+
|
10
|
+
import WrappedAnsibleHostInventory from './AnsibleHostInventory';
|
11
|
+
import { ANSIBLE_KEY } from '../constants';
|
12
|
+
import { route } from '../helpers';
|
13
|
+
|
14
|
+
const SecondaryTabRoutes = ({ response, router, history }) => (
|
15
|
+
<Switch>
|
16
|
+
<Route exact path={`/${ANSIBLE_KEY}`}>
|
17
|
+
<Redirect to={route('roles')} />
|
18
|
+
</Route>
|
19
|
+
<Route path={route('roles')}>
|
20
|
+
<TabLayout>
|
21
|
+
<RolesTab
|
22
|
+
hostId={response.id}
|
23
|
+
history={history}
|
24
|
+
canEditHost={response.permissions.edit_hosts}
|
25
|
+
/>
|
26
|
+
</TabLayout>
|
27
|
+
</Route>
|
28
|
+
<Route path={route('variables')}>
|
29
|
+
<TabLayout>
|
30
|
+
<AnsibleVariableOverrides
|
31
|
+
hostId={response.id}
|
32
|
+
hostAttrs={response}
|
33
|
+
history={history}
|
34
|
+
/>
|
35
|
+
</TabLayout>
|
36
|
+
</Route>
|
37
|
+
<Route path={route('inventory')}>
|
38
|
+
<TabLayout>
|
39
|
+
<WrappedAnsibleHostInventory hostId={response.id} />
|
40
|
+
</TabLayout>
|
41
|
+
</Route>
|
42
|
+
<Route path={route('jobs')}>
|
43
|
+
<TabLayout>
|
44
|
+
<JobsTab
|
45
|
+
resourceId={response.id}
|
46
|
+
resourceName="host"
|
47
|
+
history={history}
|
48
|
+
/>
|
49
|
+
</TabLayout>
|
50
|
+
</Route>
|
51
|
+
</Switch>
|
52
|
+
);
|
53
|
+
|
54
|
+
SecondaryTabRoutes.propTypes = {
|
55
|
+
response: PropTypes.object.isRequired,
|
56
|
+
router: PropTypes.object.isRequired,
|
57
|
+
history: PropTypes.object.isRequired,
|
58
|
+
};
|
59
|
+
|
60
|
+
export default SecondaryTabRoutes;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
|
4
|
+
const TabLayout = props => (
|
5
|
+
<div className="ansible-host-detail">{props.children}</div>
|
6
|
+
);
|
7
|
+
|
8
|
+
TabLayout.propTypes = {
|
9
|
+
children: PropTypes.object.isRequired,
|
10
|
+
};
|
11
|
+
|
12
|
+
export default TabLayout;
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
2
|
+
|
3
|
+
export const ANSIBLE_KEY = 'Ansible';
|
4
|
+
export const SECONDARY_TABS = [
|
5
|
+
{ key: 'roles', title: __('Roles') },
|
6
|
+
{ key: 'variables', title: __('Variables') },
|
7
|
+
{ key: 'inventory', title: __('Inventory') },
|
8
|
+
{ key: 'jobs', title: __('Jobs') },
|
9
|
+
];
|
data/webpack/components/AnsibleRolesAndVariables/__test__/AnsibleRolesAndVariablesImport.test.js
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
import
|
1
|
+
import React from 'react';
|
2
|
+
import { render, screen } from '@testing-library/react';
|
3
|
+
import '@testing-library/jest-dom';
|
2
4
|
import ImportRolesAndVariablesTable from '../AnsibleRolesAndVariables';
|
3
5
|
|
4
6
|
const rowsData = [
|
@@ -28,12 +30,15 @@ const columnsData = [
|
|
28
30
|
{ title: 'Hostgroups count' },
|
29
31
|
];
|
30
32
|
|
31
|
-
|
32
|
-
'should render'
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
}
|
37
|
-
|
38
|
-
|
39
|
-
|
33
|
+
describe('ImportRolesAndVariablesTable', () => {
|
34
|
+
it('should render', () => {
|
35
|
+
render(
|
36
|
+
<ImportRolesAndVariablesTable
|
37
|
+
rowsData={rowsData}
|
38
|
+
columnsData={columnsData}
|
39
|
+
/>
|
40
|
+
);
|
41
|
+
expect(screen.getByText('bennojoy.ntp')).toBeInTheDocument();
|
42
|
+
expect(screen.getByText('0ta2.git_role')).toBeInTheDocument();
|
43
|
+
});
|
44
|
+
});
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
2
3
|
import { ListView, Tooltip, OverlayTrigger } from 'patternfly-react';
|
3
4
|
import classNames from 'classnames';
|
4
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
@@ -57,4 +58,32 @@ const AnsibleRole = ({ role, icon, onClick, resourceName, index }) => {
|
|
57
58
|
return listItem(clickHandler);
|
58
59
|
};
|
59
60
|
|
61
|
+
AnsibleRole.propTypes = {
|
62
|
+
icon: PropTypes.string,
|
63
|
+
index: PropTypes.number,
|
64
|
+
onClick: PropTypes.func,
|
65
|
+
resourceName: PropTypes.string,
|
66
|
+
role: PropTypes.shape({
|
67
|
+
id: PropTypes.number,
|
68
|
+
inherited: PropTypes.bool,
|
69
|
+
name: PropTypes.string,
|
70
|
+
}),
|
71
|
+
};
|
72
|
+
|
73
|
+
AnsibleRole.defaultProps = {
|
74
|
+
icon: undefined,
|
75
|
+
index: undefined,
|
76
|
+
onClick: undefined,
|
77
|
+
resourceName: undefined,
|
78
|
+
role: {
|
79
|
+
id: undefined,
|
80
|
+
inherited: false,
|
81
|
+
name: '',
|
82
|
+
},
|
83
|
+
};
|
84
|
+
|
85
|
+
AnsibleRole.defaultProps = {
|
86
|
+
resourceName: 'host',
|
87
|
+
};
|
88
|
+
|
60
89
|
export default AnsibleRole;
|
@@ -9,16 +9,19 @@ const fixtures = {
|
|
9
9
|
role: { name: 'test.role', id: 5 },
|
10
10
|
icon: 'fa fa-plus-circle',
|
11
11
|
onClick: noop,
|
12
|
+
index: 0,
|
12
13
|
},
|
13
14
|
'should render a role to remove': {
|
14
15
|
role: { name: 'test.role', id: 5 },
|
15
16
|
icon: 'fa fa-minus-circle',
|
16
17
|
onClick: noop,
|
18
|
+
index: 0,
|
17
19
|
},
|
18
20
|
'should render inherited role to remove': {
|
19
21
|
role: { name: 'test.role', id: 5, inherited: true },
|
20
22
|
icon: 'fa fa-minus-circle',
|
21
23
|
onClick: noop,
|
24
|
+
index: 0,
|
22
25
|
},
|
23
26
|
};
|
24
27
|
|
@@ -25,12 +25,13 @@ const AvailableRolesList = ({
|
|
25
25
|
/>
|
26
26
|
</div>
|
27
27
|
<LoadingState loading={loading}>
|
28
|
-
{unassignedRoles.map(role => (
|
28
|
+
{unassignedRoles.map((role, index) => (
|
29
29
|
<AnsibleRole
|
30
30
|
key={role.id}
|
31
31
|
role={role}
|
32
32
|
icon="fa fa-plus-circle"
|
33
33
|
onClick={onAddRole}
|
34
|
+
index={index}
|
34
35
|
/>
|
35
36
|
))}
|
36
37
|
</LoadingState>
|
data/webpack/components/AnsibleRolesSwitcher/components/__snapshots__/AnsibleRole.test.js.snap
CHANGED
@@ -19,7 +19,7 @@ exports[`AnsibleRole should render a role to add 1`] = `
|
|
19
19
|
compoundExpand={false}
|
20
20
|
compoundExpanded={false}
|
21
21
|
description={null}
|
22
|
-
heading="test.role"
|
22
|
+
heading="1. test.role"
|
23
23
|
hideCloseIcon={false}
|
24
24
|
id={5}
|
25
25
|
initExpanded={false}
|
@@ -51,7 +51,7 @@ exports[`AnsibleRole should render a role to remove 1`] = `
|
|
51
51
|
compoundExpand={false}
|
52
52
|
compoundExpanded={false}
|
53
53
|
description={null}
|
54
|
-
heading="test.role"
|
54
|
+
heading="1. test.role"
|
55
55
|
hideCloseIcon={false}
|
56
56
|
id={5}
|
57
57
|
initExpanded={false}
|
@@ -94,7 +94,7 @@ exports[`AnsibleRole should render inherited role to remove 1`] = `
|
|
94
94
|
compoundExpand={false}
|
95
95
|
compoundExpanded={false}
|
96
96
|
description={null}
|
97
|
-
heading="test.role"
|
97
|
+
heading="1. test.role"
|
98
98
|
hideCloseIcon={false}
|
99
99
|
id={5}
|
100
100
|
initExpanded={false}
|
@@ -34,8 +34,10 @@ exports[`AvailableRolesList should render 1`] = `
|
|
34
34
|
>
|
35
35
|
<AnsibleRole
|
36
36
|
icon="fa fa-plus-circle"
|
37
|
+
index={0}
|
37
38
|
key="1"
|
38
39
|
onClick={[Function]}
|
40
|
+
resourceName="host"
|
39
41
|
role={
|
40
42
|
Object {
|
41
43
|
"id": 1,
|
@@ -45,8 +47,10 @@ exports[`AvailableRolesList should render 1`] = `
|
|
45
47
|
/>
|
46
48
|
<AnsibleRole
|
47
49
|
icon="fa fa-plus-circle"
|
50
|
+
index={1}
|
48
51
|
key="2"
|
49
52
|
onClick={[Function]}
|
53
|
+
resourceName="host"
|
50
54
|
role={
|
51
55
|
Object {
|
52
56
|
"id": 2,
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
4
|
+
|
5
|
+
const ListControls = props => (
|
6
|
+
<div className="pf-c-dual-list-selector__controls">
|
7
|
+
<div className="pf-c-dual-list-selector__controls-item">
|
8
|
+
<button
|
9
|
+
className="pf-c-button pf-m-plain"
|
10
|
+
type="button"
|
11
|
+
disabled={props.addSelectedDisabled}
|
12
|
+
onClick={props.onAddSelected}
|
13
|
+
aria-label={__('Add selected')}
|
14
|
+
>
|
15
|
+
<i className="fas fa-fw fa-angle-right" />
|
16
|
+
</button>
|
17
|
+
</div>
|
18
|
+
<div className="pf-c-dual-list-selector__controls-item">
|
19
|
+
<button
|
20
|
+
className="pf-c-button pf-m-plain"
|
21
|
+
type="button"
|
22
|
+
disabled={props.addAllDisabled}
|
23
|
+
onClick={props.onAddAll}
|
24
|
+
aria-label={__('Add all')}
|
25
|
+
>
|
26
|
+
<i className="fas fa-fw fa-angle-double-right" />
|
27
|
+
</button>
|
28
|
+
</div>
|
29
|
+
<div className="pf-c-dual-list-selector__controls-item">
|
30
|
+
<button
|
31
|
+
className="pf-c-button pf-m-plain"
|
32
|
+
type="button"
|
33
|
+
disabled={props.removeAllDisabled}
|
34
|
+
onClick={props.onRemoveAll}
|
35
|
+
aria-label={__('Remove all')}
|
36
|
+
>
|
37
|
+
<i className="fas fa-fw fa-angle-double-left" />
|
38
|
+
</button>
|
39
|
+
</div>
|
40
|
+
<div className="pf-c-dual-list-selector__controls-item">
|
41
|
+
<button
|
42
|
+
className="pf-c-button pf-m-plain"
|
43
|
+
type="button"
|
44
|
+
disabled={props.removeSelectedDisabled}
|
45
|
+
onClick={props.onRemoveSelected}
|
46
|
+
aria-label={__('Remove selected')}
|
47
|
+
>
|
48
|
+
<i className="fas fa-fw fa-angle-left" />
|
49
|
+
</button>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
);
|
53
|
+
|
54
|
+
ListControls.propTypes = {
|
55
|
+
addSelectedDisabled: PropTypes.bool.isRequired,
|
56
|
+
onAddSelected: PropTypes.func.isRequired,
|
57
|
+
addAllDisabled: PropTypes.bool.isRequired,
|
58
|
+
onAddAll: PropTypes.func.isRequired,
|
59
|
+
removeAllDisabled: PropTypes.bool.isRequired,
|
60
|
+
onRemoveAll: PropTypes.func.isRequired,
|
61
|
+
removeSelectedDisabled: PropTypes.bool.isRequired,
|
62
|
+
onRemoveSelected: PropTypes.func.isRequired,
|
63
|
+
};
|
64
|
+
|
65
|
+
export default ListControls;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
|
4
|
+
const ListHeader = props => (
|
5
|
+
<div className="pf-c-dual-list-selector__header">
|
6
|
+
<div className="pf-c-dual-list-selector__title">
|
7
|
+
<div className="pf-c-dual-list-selector__title-text">{props.title}</div>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
);
|
11
|
+
|
12
|
+
ListHeader.propTypes = {
|
13
|
+
title: PropTypes.string.isRequired,
|
14
|
+
};
|
15
|
+
|
16
|
+
export default ListHeader;
|