foreman_ansible 7.0.0 → 7.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/graphql/resolvers/ansible_role/path.rb +11 -0
- data/app/graphql/resolvers/ansible_variable/path.rb +11 -0
- data/app/graphql/types/ansible_role.rb +1 -0
- data/app/graphql/types/ansible_variable.rb +1 -0
- data/app/models/foreman_ansible/ansible_provider.rb +3 -2
- data/app/services/foreman_ansible/ansible_report_importer.rb +0 -4
- data/lib/foreman_ansible/engine.rb +1 -0
- data/lib/foreman_ansible/version.rb +1 -1
- data/test/unit/ansible_provider_test.rb +12 -0
- data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.js +1 -0
- data/webpack/components/AnsibleHostDetail/AnsibleHostDetail.scss +4 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/AnsibleVariableOverridesTable.js +5 -3
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverrides.fixtures.js +9 -0
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/__test__/AnsibleVariableOverridesDelete.test.js +2 -2
- data/webpack/components/AnsibleHostDetail/components/AnsibleVariableOverrides/index.js +4 -1
- data/webpack/components/AnsibleHostDetail/components/JobsTab/JobsTabHelper.js +49 -7
- data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js +4 -1
- data/webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js.orig +151 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js +19 -3
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js +3 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js +8 -3
- data/webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTabHelper.test.js +11 -0
- data/webpack/components/AnsibleHostDetail/components/JobsTab/index.js +8 -1
- data/webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js +8 -14
- data/webpack/components/AnsibleHostDetail/components/RolesTab/EditRolesModal/index.js +5 -2
- data/webpack/components/AnsibleHostDetail/components/RolesTab/RolesTable.js +4 -2
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/EditRoles.test.js +2 -2
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.fixtures.js +11 -0
- data/webpack/components/AnsibleHostDetail/components/RolesTab/__test__/RolesTab.test.js +7 -3
- data/webpack/components/AnsibleHostDetail/components/RolesTab/index.js +38 -14
- data/webpack/components/AnsibleHostDetail/components/SecondaryTabRoutes.js +1 -0
- data/webpack/components/DualList/index.js +2 -2
- data/webpack/formHelper.js +1 -1
- data/webpack/graphql/queries/hostAnsibleRoles.gql +1 -0
- data/webpack/graphql/queries/hostVariableOverrides.gql +1 -0
- data/webpack/graphql/queries/recurringJobs.gql +1 -0
- data/webpack/routes/HostgroupJobs/__test__/HostgroupJobs.test.js +5 -2
- metadata +30 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a8c4f263992d803c31386bc301ce1e032c680ada845db8bf07e71162c003384
|
4
|
+
data.tar.gz: 628c7b6952499ef5dfe912370bec8d3e936926a004acf5b7693d7706fe461ea9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '09f3fd7b961edcb7abee1f6dd6de4fbf0bb80130668c39724d6c8658c2ca1570109ab6b7d172d2f9b963c6e4e5aebaa3280ebf199f9fe5ff86580ab1d8f06c7e'
|
7
|
+
data.tar.gz: 924b85ee81562a848c07417fd0788520c000292901f63ef45f8adbfc99ec32dae21858589c83688f461d19e49a4c6491801d53f611032a1c291ba941e9068133
|
@@ -88,7 +88,7 @@ if defined? ForemanRemoteExecution
|
|
88
88
|
:children => [
|
89
89
|
{
|
90
90
|
:name => :tags,
|
91
|
-
:type =>
|
91
|
+
:type => String,
|
92
92
|
:opts => { :required => false, :desc => N_('A comma separated list of tags to use for Ansible run') }
|
93
93
|
},
|
94
94
|
{
|
@@ -115,7 +115,8 @@ if defined? ForemanRemoteExecution
|
|
115
115
|
end
|
116
116
|
|
117
117
|
def proxy_batch_size
|
118
|
-
Setting['foreman_ansible_proxy_batch_size']
|
118
|
+
value = Setting['foreman_ansible_proxy_batch_size']
|
119
|
+
value.presence && value.to_i
|
119
120
|
end
|
120
121
|
|
121
122
|
private
|
@@ -18,10 +18,6 @@ module ForemanAnsible
|
|
18
18
|
partial_hostname_match(hostname)
|
19
19
|
end
|
20
20
|
|
21
|
-
def self.authorized_smart_proxy_features
|
22
|
-
super + ['Ansible']
|
23
|
-
end
|
24
|
-
|
25
21
|
def partial_hostname_match(hostname)
|
26
22
|
return @host unless @host.new_record?
|
27
23
|
hosts = Host.where(Host.arel_table[:name].matches("#{hostname}.%"))
|
@@ -75,6 +75,7 @@ module ForemanAnsible
|
|
75
75
|
::Api::V2::HostgroupsController.include ForemanAnsible::Api::V2::HostgroupsControllerExtensions
|
76
76
|
::Api::V2::HostgroupsController.include ForemanAnsible::Api::V2::HostgroupsParamGroupExtensions
|
77
77
|
::ConfigReportImporter.include ForemanAnsible::AnsibleReportImporter
|
78
|
+
ReportImporter.register_smart_proxy_feature('Ansible')
|
78
79
|
rescue StandardError => e
|
79
80
|
Rails.logger.warn "Foreman Ansible: skipping engine hook (#{e})"
|
80
81
|
end
|
@@ -47,4 +47,16 @@ class AnsibleProviderTest < ActiveSupport::TestCase
|
|
47
47
|
proxy_command_options(template_invocation, dummyhost)
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
describe '#proxy_batch_size' do
|
52
|
+
it 'returns integer if setting is string' do
|
53
|
+
Setting.expects(:[]).with('foreman_ansible_proxy_batch_size').returns('10')
|
54
|
+
_(ForemanAnsible::AnsibleProvider.proxy_batch_size).must_equal(10)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns nil if setting is empty' do
|
58
|
+
Setting.expects(:[]).with('foreman_ansible_proxy_batch_size').returns('')
|
59
|
+
_(ForemanAnsible::AnsibleProvider.proxy_batch_size).must_equal(nil)
|
60
|
+
end
|
61
|
+
end
|
50
62
|
end
|
@@ -63,7 +63,7 @@ const AnsibleVariableOverridesTable = ({
|
|
63
63
|
}) => {
|
64
64
|
const columns = [
|
65
65
|
__('Name'),
|
66
|
-
__('Ansible
|
66
|
+
__('Ansible role'),
|
67
67
|
__('Type'),
|
68
68
|
__('Value'),
|
69
69
|
__('Source attribute'),
|
@@ -131,7 +131,7 @@ const AnsibleVariableOverridesTable = ({
|
|
131
131
|
onClick: () => {
|
132
132
|
dispatch(
|
133
133
|
openConfirmModal({
|
134
|
-
title: __('Delete Ansible
|
134
|
+
title: __('Delete Ansible variable override'),
|
135
135
|
message:
|
136
136
|
variable &&
|
137
137
|
sprintf(
|
@@ -187,7 +187,9 @@ const AnsibleVariableOverridesTable = ({
|
|
187
187
|
<Tbody>
|
188
188
|
{variables.map((variable, idx) => (
|
189
189
|
<Tr key={idx}>
|
190
|
-
<Td>
|
190
|
+
<Td>
|
191
|
+
<a href={variable.path}>{variable.key}</a>
|
192
|
+
</Td>
|
191
193
|
<Td>{variable.ansibleRoleName}</Td>
|
192
194
|
<Td>{variable.parameterType}</Td>
|
193
195
|
<Td>
|
@@ -29,6 +29,7 @@ const withFqdnOverride = canEdit => ({
|
|
29
29
|
},
|
30
30
|
id: ansibleVariableId,
|
31
31
|
key: 'rectangle',
|
32
|
+
path: '/ansible/ansible_variables/1/edit',
|
32
33
|
defaultValue: 17,
|
33
34
|
parameterType: 'integer',
|
34
35
|
ansibleRoleName: 'test.role',
|
@@ -62,6 +63,7 @@ const withDomainOverride = canEdit => ({
|
|
62
63
|
},
|
63
64
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTc4',
|
64
65
|
key: 'circle',
|
66
|
+
path: '/ansible/ansible_variables/2/edit',
|
65
67
|
defaultValue: 'd',
|
66
68
|
parameterType: 'string',
|
67
69
|
ansibleRoleName: 'test.role',
|
@@ -133,6 +135,7 @@ export const mocks = [
|
|
133
135
|
},
|
134
136
|
id: barVariableGlobalId,
|
135
137
|
key: 'bar',
|
138
|
+
path: '/ansible/ansible_variables/11/edit',
|
136
139
|
defaultValue: 'a',
|
137
140
|
parameterType: 'string',
|
138
141
|
ansibleRoleName: 'test.role',
|
@@ -160,6 +163,7 @@ export const mocks = [
|
|
160
163
|
},
|
161
164
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY1',
|
162
165
|
key: 'square',
|
166
|
+
path: '/ansible/ansible_variables/12/edit',
|
163
167
|
defaultValue: true,
|
164
168
|
parameterType: 'boolean',
|
165
169
|
ansibleRoleName: 'test.role',
|
@@ -179,6 +183,7 @@ export const mocks = [
|
|
179
183
|
},
|
180
184
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTc4',
|
181
185
|
key: 'circle',
|
186
|
+
path: '/ansible/ansible_variables/13/edit',
|
182
187
|
defaultValue: 'd',
|
183
188
|
parameterType: 'string',
|
184
189
|
ansibleRoleName: 'test.role',
|
@@ -203,6 +208,7 @@ export const mocks = [
|
|
203
208
|
},
|
204
209
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTc5',
|
205
210
|
key: 'ellipse',
|
211
|
+
path: '/ansible/ansible_variables/14/edit',
|
206
212
|
defaultValue: ['seven', 'eight'],
|
207
213
|
parameterType: 'array',
|
208
214
|
ansibleRoleName: 'test.role',
|
@@ -227,6 +233,7 @@ export const mocks = [
|
|
227
233
|
},
|
228
234
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY2Ng==',
|
229
235
|
key: 'spiral',
|
236
|
+
path: '/ansible/ansible_variables/15/edit',
|
230
237
|
defaultValue: { one: 'one', two: 'two' },
|
231
238
|
parameterType: 'hash',
|
232
239
|
ansibleRoleName: 'test.role',
|
@@ -246,6 +253,7 @@ export const mocks = [
|
|
246
253
|
},
|
247
254
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY3Mg==',
|
248
255
|
key: 'sun',
|
256
|
+
path: '/ansible/ansible_variables/16/edit',
|
249
257
|
defaultValue: "{ one: 'one', two: 'two' }",
|
250
258
|
parameterType: 'json',
|
251
259
|
ansibleRoleName: 'test.role',
|
@@ -265,6 +273,7 @@ export const mocks = [
|
|
265
273
|
},
|
266
274
|
id: 'MDE6QW5zaWJsZVZhcmlhYmxlLTY3Mw==',
|
267
275
|
key: 'moon',
|
276
|
+
path: '/ansible/ansible_variables/17/edit',
|
268
277
|
defaultValue: [
|
269
278
|
{ hosts: 'all', become: 'true', roles: ['foo'] },
|
270
279
|
],
|
@@ -37,12 +37,12 @@ describe('AnsibleVariableOverrides', () => {
|
|
37
37
|
userEvent.click(screen.getByText('Delete'));
|
38
38
|
await waitFor(tick);
|
39
39
|
expect(
|
40
|
-
screen.getByText('Delete Ansible
|
40
|
+
screen.getByText('Delete Ansible variable override')
|
41
41
|
).toBeInTheDocument();
|
42
42
|
userEvent.click(screen.getByText('Cancel'));
|
43
43
|
await waitFor(tick);
|
44
44
|
expect(
|
45
|
-
screen.queryByText('Delete Ansible
|
45
|
+
screen.queryByText('Delete Ansible variable override')
|
46
46
|
).not.toBeInTheDocument();
|
47
47
|
});
|
48
48
|
it('should delete override', async () => {
|
@@ -41,7 +41,10 @@ const AnsibleVariableOverrides = ({ hostId, hostAttrs, history }) => {
|
|
41
41
|
renameData={renameData}
|
42
42
|
fetchFn={useFetchFn}
|
43
43
|
renamedDataPath="variables"
|
44
|
-
|
44
|
+
emptyStateProps={{
|
45
|
+
header: __('No Ansible variables found for Host'),
|
46
|
+
description: __('Only variables marked to Override are shown here.'),
|
47
|
+
}}
|
45
48
|
permissions={['view_ansible_variables']}
|
46
49
|
pagination={pagination}
|
47
50
|
history={history}
|
@@ -7,21 +7,36 @@ import { showToast } from '../../../../toastHelper';
|
|
7
7
|
export const ansiblePurpose = (resourceName, resourceId) =>
|
8
8
|
`ansible-${resourceName}-${resourceId}`;
|
9
9
|
|
10
|
-
const jobSearch = (resourceName, resourceId,
|
11
|
-
`recurring = true && pattern_template_name = "Ansible Roles - Ansible Default"
|
10
|
+
const jobSearch = (resourceName, resourceId, status, hostGroupId) => {
|
11
|
+
const search = `recurring = true && pattern_template_name = "Ansible Roles - Ansible Default"`;
|
12
|
+
const searchStatus = ` && ${status}`;
|
13
|
+
const searchHost = ` && recurring_logic.purpose = ${ansiblePurpose(
|
12
14
|
resourceName,
|
13
15
|
resourceId
|
14
16
|
)}`;
|
17
|
+
const searchHostGroup = hostGroupId
|
18
|
+
? ` or recurring_logic.purpose = ${ansiblePurpose(
|
19
|
+
'hostgroup',
|
20
|
+
hostGroupId
|
21
|
+
)}`
|
22
|
+
: '';
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
24
|
+
return search + searchStatus + searchHost + searchHostGroup;
|
25
|
+
};
|
26
|
+
|
27
|
+
export const scheduledJobsSearch = (resourceName, resourceId, hostGroupId) =>
|
28
|
+
jobSearch(resourceName, resourceId, 'status = queued', hostGroupId);
|
29
|
+
export const previousJobsSearch = (resourceName, resourceId, hostGroupId) =>
|
30
|
+
jobSearch(resourceName, resourceId, 'status != queued', hostGroupId);
|
20
31
|
|
21
32
|
const fetchJobsFn = (searchFn, pagination = {}) => componentProps =>
|
22
33
|
useQuery(jobsQuery, {
|
23
34
|
variables: {
|
24
|
-
search: searchFn(
|
35
|
+
search: searchFn(
|
36
|
+
componentProps.resourceName,
|
37
|
+
componentProps.resourceId,
|
38
|
+
componentProps.hostGroupId
|
39
|
+
),
|
25
40
|
...pagination,
|
26
41
|
},
|
27
42
|
});
|
@@ -77,3 +92,30 @@ export const useCancelMutation = (resourceName, resourceId) =>
|
|
77
92
|
},
|
78
93
|
],
|
79
94
|
});
|
95
|
+
|
96
|
+
export const readableCron = (cron = '') => {
|
97
|
+
if (cron.match(/(\d+ \* \* \* \*)/)) {
|
98
|
+
return 'hourly';
|
99
|
+
}
|
100
|
+
|
101
|
+
if (cron.match(/(\d+ \d+ \* \* \*)/)) {
|
102
|
+
return 'daily';
|
103
|
+
}
|
104
|
+
|
105
|
+
if (cron.match(/(\d+ \d+ \* \* \d+)/)) {
|
106
|
+
return 'weekly';
|
107
|
+
}
|
108
|
+
|
109
|
+
if (cron.match(/(\d+ \d+ \d+ \* \*)/)) {
|
110
|
+
return 'monthly';
|
111
|
+
}
|
112
|
+
|
113
|
+
return 'custom';
|
114
|
+
};
|
115
|
+
|
116
|
+
export const readablePurpose = (purpose = '') => {
|
117
|
+
if (window.location.href.match(/ansible\/hostgroup/)) {
|
118
|
+
return '';
|
119
|
+
}
|
120
|
+
return purpose.match(/hostgroup/) ? __('(from host group)') : '';
|
121
|
+
};
|
@@ -17,6 +17,7 @@ import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
|
|
17
17
|
|
18
18
|
import { decodeId } from '../../../../globalIdHelper';
|
19
19
|
import withLoading from '../../../withLoading';
|
20
|
+
import { readableCron, readablePurpose } from './JobsTabHelper';
|
20
21
|
import {
|
21
22
|
preparePerPageOptions,
|
22
23
|
refreshPage,
|
@@ -78,13 +79,15 @@ const PreviousJobsTable = ({ history, totalCount, jobs, pagination }) => {
|
|
78
79
|
>
|
79
80
|
{job.description}
|
80
81
|
</a>
|
82
|
+
|
83
|
+
{readablePurpose(job.recurringLogic.purpose)}
|
81
84
|
</Td>
|
82
85
|
<Td>{job.task.result}</Td>
|
83
86
|
<Td>{job.task.state}</Td>
|
84
87
|
<Td>
|
85
88
|
<RelativeDateTime date={job.startAt} />
|
86
89
|
</Td>
|
87
|
-
<Td>{job.recurringLogic.cronLine}</Td>
|
90
|
+
<Td>{readableCron(job.recurringLogic.cronLine)}</Td>
|
88
91
|
</Tr>
|
89
92
|
))}
|
90
93
|
</Tbody>
|
@@ -0,0 +1,151 @@
|
|
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 RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
|
7
|
+
|
8
|
+
import {
|
9
|
+
TableComposable,
|
10
|
+
Thead,
|
11
|
+
Tbody,
|
12
|
+
Tr,
|
13
|
+
Th,
|
14
|
+
Td,
|
15
|
+
} from '@patternfly/react-table';
|
16
|
+
import { Flex, FlexItem, Pagination } from '@patternfly/react-core';
|
17
|
+
|
18
|
+
import { decodeId } from '../../../../globalIdHelper';
|
19
|
+
import withLoading from '../../../withLoading';
|
20
|
+
<<<<<<< HEAD
|
21
|
+
import { readableCron } from './JobsTabHelper';
|
22
|
+
import {
|
23
|
+
preparePerPageOptions,
|
24
|
+
refreshPage,
|
25
|
+
} from '../../../../helpers/paginationHelper';
|
26
|
+
=======
|
27
|
+
import { readableCron, readablePurpose } from './JobsTabHelper';
|
28
|
+
>>>>>>> 5b01704 (Fixes #34458 - Show Hostgroup jobs on the Host Detail page)
|
29
|
+
|
30
|
+
const PreviousJobsTable = ({ history, totalCount, jobs, pagination }) => {
|
31
|
+
const columns = [
|
32
|
+
__('Description'),
|
33
|
+
__('Result'),
|
34
|
+
__('State'),
|
35
|
+
__('Executed at'),
|
36
|
+
__('Schedule'),
|
37
|
+
];
|
38
|
+
|
39
|
+
const handlePerPageSelected = (event, perPage) => {
|
40
|
+
refreshPage(history, { page: 1, perPage });
|
41
|
+
};
|
42
|
+
|
43
|
+
const handlePageSelected = (event, page) => {
|
44
|
+
refreshPage(history, { ...pagination, page });
|
45
|
+
};
|
46
|
+
|
47
|
+
const perPageOptions = preparePerPageOptions(usePaginationOptions());
|
48
|
+
|
49
|
+
return (
|
50
|
+
<React.Fragment>
|
51
|
+
<h3>{__('Previously executed jobs')}</h3>
|
52
|
+
<<<<<<< HEAD
|
53
|
+
<Flex className="pf-u-pt-md">
|
54
|
+
=======
|
55
|
+
<Flex direction={{ default: 'column' }} className="pf-u-pt-md">
|
56
|
+
<FlexItem align={{ default: 'alignRight' }}>
|
57
|
+
<Pagination updateParamsByUrl itemCount={totalCount} variant="top" />
|
58
|
+
</FlexItem>
|
59
|
+
<FlexItem>
|
60
|
+
<TableComposable variant="compact">
|
61
|
+
<Thead>
|
62
|
+
<Tr>
|
63
|
+
{columns.map(col => (
|
64
|
+
<Th key={col}>{col}</Th>
|
65
|
+
))}
|
66
|
+
</Tr>
|
67
|
+
</Thead>
|
68
|
+
<Tbody>
|
69
|
+
{jobs.map(job => (
|
70
|
+
<Tr key={job.id}>
|
71
|
+
<Td>
|
72
|
+
<a
|
73
|
+
onClick={() =>
|
74
|
+
window.tfm.nav.pushUrl(
|
75
|
+
`/job_invocations/${decodeId(job.id)}`
|
76
|
+
)
|
77
|
+
}
|
78
|
+
>
|
79
|
+
{job.description}
|
80
|
+
</a>
|
81
|
+
|
82
|
+
{readablePurpose(job.recurringLogic.purpose)}
|
83
|
+
</Td>
|
84
|
+
<Td>{job.task.result}</Td>
|
85
|
+
<Td>{job.task.state}</Td>
|
86
|
+
<Td>
|
87
|
+
<RelativeDateTime date={job.startAt} />
|
88
|
+
</Td>
|
89
|
+
<Td>{readableCron(job.recurringLogic.cronLine)}</Td>
|
90
|
+
</Tr>
|
91
|
+
))}
|
92
|
+
</Tbody>
|
93
|
+
</TableComposable>
|
94
|
+
</FlexItem>
|
95
|
+
>>>>>>> 5b01704 (Fixes #34458 - Show Hostgroup jobs on the Host Detail page)
|
96
|
+
<FlexItem align={{ default: 'alignRight' }}>
|
97
|
+
<Pagination
|
98
|
+
itemCount={totalCount}
|
99
|
+
page={pagination.page}
|
100
|
+
perPage={pagination.perPage}
|
101
|
+
onSetPage={handlePageSelected}
|
102
|
+
onPerPageSelect={handlePerPageSelected}
|
103
|
+
perPageOptions={perPageOptions}
|
104
|
+
variant="top"
|
105
|
+
/>
|
106
|
+
</FlexItem>
|
107
|
+
</Flex>
|
108
|
+
<TableComposable variant="compact">
|
109
|
+
<Thead>
|
110
|
+
<Tr>
|
111
|
+
{columns.map(col => (
|
112
|
+
<Th key={col}>{col}</Th>
|
113
|
+
))}
|
114
|
+
</Tr>
|
115
|
+
</Thead>
|
116
|
+
<Tbody>
|
117
|
+
{jobs.map(job => (
|
118
|
+
<Tr key={job.id}>
|
119
|
+
<Td>
|
120
|
+
<a
|
121
|
+
onClick={() =>
|
122
|
+
window.tfm.nav.pushUrl(
|
123
|
+
`/job_invocations/${decodeId(job.id)}`
|
124
|
+
)
|
125
|
+
}
|
126
|
+
>
|
127
|
+
{job.description}
|
128
|
+
</a>
|
129
|
+
</Td>
|
130
|
+
<Td>{job.task.result}</Td>
|
131
|
+
<Td>{job.task.state}</Td>
|
132
|
+
<Td>
|
133
|
+
<RelativeDateTime date={job.startAt} />
|
134
|
+
</Td>
|
135
|
+
<Td>{readableCron(job.recurringLogic.cronLine)}</Td>
|
136
|
+
</Tr>
|
137
|
+
))}
|
138
|
+
</Tbody>
|
139
|
+
</TableComposable>
|
140
|
+
</React.Fragment>
|
141
|
+
);
|
142
|
+
};
|
143
|
+
|
144
|
+
PreviousJobsTable.propTypes = {
|
145
|
+
jobs: PropTypes.array.isRequired,
|
146
|
+
history: PropTypes.object.isRequired,
|
147
|
+
totalCount: PropTypes.number.isRequired,
|
148
|
+
pagination: PropTypes.object.isRequired,
|
149
|
+
};
|
150
|
+
|
151
|
+
export default withLoading(PreviousJobsTable);
|
@@ -14,11 +14,20 @@ import {
|
|
14
14
|
Td,
|
15
15
|
} from '@patternfly/react-table';
|
16
16
|
|
17
|
-
import {
|
17
|
+
import {
|
18
|
+
useCancelMutation,
|
19
|
+
readableCron,
|
20
|
+
readablePurpose,
|
21
|
+
} from './JobsTabHelper';
|
18
22
|
import withLoading from '../../../withLoading';
|
19
23
|
import { decodeId } from '../../../../globalIdHelper';
|
20
24
|
|
21
|
-
const RecurringJobsTable = ({
|
25
|
+
const RecurringJobsTable = ({
|
26
|
+
jobs,
|
27
|
+
resourceName,
|
28
|
+
resourceId,
|
29
|
+
hostGroupId,
|
30
|
+
}) => {
|
22
31
|
const columns = [__('Description'), __('Schedule'), __('Next Run')];
|
23
32
|
const dispatch = useDispatch();
|
24
33
|
|
@@ -73,8 +82,10 @@ const RecurringJobsTable = ({ jobs, resourceName, resourceId }) => {
|
|
73
82
|
>
|
74
83
|
{job.description}
|
75
84
|
</a>
|
85
|
+
|
86
|
+
{readablePurpose(job.recurringLogic.purpose)}
|
76
87
|
</Td>
|
77
|
-
<Td>{job.recurringLogic.cronLine}</Td>
|
88
|
+
<Td>{readableCron(job.recurringLogic.cronLine)}</Td>
|
78
89
|
<Td>
|
79
90
|
<RelativeDateTime date={job.startAt} />
|
80
91
|
</Td>
|
@@ -91,6 +102,11 @@ RecurringJobsTable.propTypes = {
|
|
91
102
|
jobs: PropTypes.array.isRequired,
|
92
103
|
resourceId: PropTypes.number.isRequired,
|
93
104
|
resourceName: PropTypes.string.isRequired,
|
105
|
+
hostGroupId: PropTypes.number,
|
106
|
+
};
|
107
|
+
|
108
|
+
RecurringJobsTable.defaultProps = {
|
109
|
+
hostGroupId: undefined,
|
94
110
|
};
|
95
111
|
|
96
112
|
export default withLoading(RecurringJobsTable);
|
@@ -23,10 +23,12 @@ const viewer = userFactory('viewer', [
|
|
23
23
|
|
24
24
|
const firstRecurringLogicGlobalId =
|
25
25
|
'MDE6Rm9yZW1hblRhc2tzOjpSZWN1cnJpbmdMb2dpYy0x';
|
26
|
+
|
26
27
|
const firstRecurringLogic = {
|
27
28
|
__typename: 'ForemanTasks::RecurringLogic',
|
28
29
|
id: firstRecurringLogicGlobalId,
|
29
30
|
cronLine: toCron(futureDate, 'weekly'),
|
31
|
+
purpose: '',
|
30
32
|
meta: {
|
31
33
|
canEdit: true,
|
32
34
|
},
|
@@ -66,6 +68,7 @@ export const secondJob = {
|
|
66
68
|
__typename: 'ForemanTasks::RecurringLogic',
|
67
69
|
id: 'MDE6Rm9yZW1hblRhc2tzOjpSZWN1cnJpbmdMb2dpYy0yMw==',
|
68
70
|
cronLine: '54 10 15 * *',
|
71
|
+
purpose: '',
|
69
72
|
meta: {
|
70
73
|
canEdit: true,
|
71
74
|
},
|
@@ -18,6 +18,7 @@ import {
|
|
18
18
|
import * as toasts from '../../../../../toastHelper';
|
19
19
|
|
20
20
|
import { toCron } from '../NewRecurringJobHelper';
|
21
|
+
import { readableCron } from '../JobsTabHelper';
|
21
22
|
|
22
23
|
import {
|
23
24
|
tick,
|
@@ -49,8 +50,10 @@ describe('JobsTab', () => {
|
|
49
50
|
.map(element => expect(element).toBeInTheDocument());
|
50
51
|
expect(screen.getByText('Scheduled recurring jobs')).toBeInTheDocument();
|
51
52
|
expect(screen.getByText('Previously executed jobs')).toBeInTheDocument();
|
52
|
-
expect(
|
53
|
-
|
53
|
+
expect(
|
54
|
+
screen.getByText(readableCron(toCron(futureDate, 'weekly')))
|
55
|
+
).toBeInTheDocument();
|
56
|
+
expect(screen.getByText('monthly')).toBeInTheDocument();
|
54
57
|
});
|
55
58
|
it('should show empty state', async () => {
|
56
59
|
render(
|
@@ -129,7 +132,9 @@ describe('JobsTab', () => {
|
|
129
132
|
message: 'Ansible job was successfully created.',
|
130
133
|
});
|
131
134
|
await waitFor(tick);
|
132
|
-
expect(
|
135
|
+
expect(
|
136
|
+
screen.getByText(readableCron(toCron(futureDate, 'weekly')))
|
137
|
+
).toBeInTheDocument();
|
133
138
|
expect(screen.getByText('in 3 days')).toBeInTheDocument();
|
134
139
|
expect(
|
135
140
|
screen.queryByText('No config job for Ansible roles scheduled')
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { readableCron } from '../JobsTabHelper';
|
2
|
+
|
3
|
+
describe('JobTabsHelper', () => {
|
4
|
+
it('readableCron', () => {
|
5
|
+
expect(readableCron('01 * * * *')).toBe('hourly');
|
6
|
+
expect(readableCron('01 01 * * *')).toBe('daily');
|
7
|
+
expect(readableCron('01 01 * * 01')).toBe('weekly');
|
8
|
+
expect(readableCron('01 01 01 * *')).toBe('monthly');
|
9
|
+
expect(readableCron()).toBe('custom');
|
10
|
+
});
|
11
|
+
});
|
@@ -15,7 +15,7 @@ import RecurringJobsTable from './RecurringJobsTable';
|
|
15
15
|
import PreviousJobsTable from './PreviousJobsTable';
|
16
16
|
import NewRecurringJobModal from './NewRecurringJobModal';
|
17
17
|
|
18
|
-
const JobsTab = ({ resourceName, resourceId, history }) => {
|
18
|
+
const JobsTab = ({ resourceName, resourceId, hostGroupId, history }) => {
|
19
19
|
const [modalOpen, setModalOpen] = useState(false);
|
20
20
|
const toggleModal = () => setModalOpen(!modalOpen);
|
21
21
|
|
@@ -44,6 +44,7 @@ const JobsTab = ({ resourceName, resourceId, history }) => {
|
|
44
44
|
<RecurringJobsTable
|
45
45
|
resourceId={resourceId}
|
46
46
|
resourceName={resourceName}
|
47
|
+
hostGroupId={hostGroupId}
|
47
48
|
fetchFn={fetchRecurringFn}
|
48
49
|
renameData={renameData}
|
49
50
|
renamedDataPath="jobs"
|
@@ -59,6 +60,7 @@ const JobsTab = ({ resourceName, resourceId, history }) => {
|
|
59
60
|
<PreviousJobsTable
|
60
61
|
resourceId={resourceId}
|
61
62
|
resourceName={resourceName}
|
63
|
+
hostGroupId={hostGroupId}
|
62
64
|
fetchFn={fetchPreviousFn(useParamsToVars(history))}
|
63
65
|
renameData={renameData}
|
64
66
|
emptyWrapper={() => null}
|
@@ -81,8 +83,13 @@ const JobsTab = ({ resourceName, resourceId, history }) => {
|
|
81
83
|
|
82
84
|
JobsTab.propTypes = {
|
83
85
|
resourceName: PropTypes.string.isRequired,
|
86
|
+
hostGroupId: PropTypes.number,
|
84
87
|
resourceId: PropTypes.number.isRequired,
|
85
88
|
history: PropTypes.object.isRequired,
|
86
89
|
};
|
87
90
|
|
91
|
+
JobsTab.defaultProps = {
|
92
|
+
hostGroupId: undefined,
|
93
|
+
};
|
94
|
+
|
88
95
|
export default JobsTab;
|
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|
3
3
|
import { useQuery } from '@apollo/client';
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
5
|
|
6
|
-
import { Modal,
|
6
|
+
import { Modal, ModalVariant } from '@patternfly/react-core';
|
7
7
|
|
8
8
|
import allAnsibleRolesQuery from '../../../../../graphql/queries/allAnsibleRoles.gql';
|
9
9
|
import AllRolesTable from './AllRolesTable';
|
@@ -17,25 +17,19 @@ const AllRolesModal = ({ hostGlobalId, onClose, history }) => {
|
|
17
17
|
const baseModalProps = {
|
18
18
|
variant: ModalVariant.large,
|
19
19
|
isOpen: true,
|
20
|
+
onClose,
|
20
21
|
className: 'foreman-modal',
|
21
|
-
showClose:
|
22
|
-
title: __('All Ansible
|
22
|
+
showClose: true,
|
23
|
+
title: __('All assigned Ansible roles'),
|
23
24
|
disableFocusTrap: true,
|
25
|
+
description: __(
|
26
|
+
'This list consists of host assigned roles and group assigned roles. Group assigned roles will always be executed prior to host assigned roles'
|
27
|
+
),
|
24
28
|
};
|
25
29
|
|
26
30
|
const paginationKeys = { page: 'allPage', perPage: 'allPerPage' };
|
27
31
|
|
28
|
-
const
|
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
|
-
);
|
32
|
+
const wrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
|
39
33
|
|
40
34
|
const loadingWrapper = child => <Modal {...baseModalProps}>{child}</Modal>;
|
41
35
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
2
2
|
import { translate as __ } from 'foremanReact/common/I18n';
|
3
3
|
import PropTypes from 'prop-types';
|
4
4
|
|
5
|
-
import { Modal, Button
|
5
|
+
import { Modal, Button } from '@patternfly/react-core';
|
6
6
|
import { useQuery } from '@apollo/client';
|
7
7
|
|
8
8
|
import EditRolesForm from './EditRolesForm';
|
@@ -20,12 +20,15 @@ const EditRolesModal = ({
|
|
20
20
|
canEditHost,
|
21
21
|
}) => {
|
22
22
|
const baseModalProps = {
|
23
|
-
|
23
|
+
width: '70%',
|
24
24
|
isOpen,
|
25
25
|
className: 'foreman-modal',
|
26
26
|
showClose: false,
|
27
27
|
title: __('Edit Ansible Roles'),
|
28
28
|
disableFocusTrap: true,
|
29
|
+
description: __(
|
30
|
+
'Add, remove or reorder host assigned Ansible roles. This host has also group assigned roles that are not displayed here and will always be executed prior to host assigned roles'
|
31
|
+
),
|
29
32
|
};
|
30
33
|
|
31
34
|
const actions = [
|
@@ -48,7 +48,7 @@ const RolesTable = ({
|
|
48
48
|
<FlexItem>
|
49
49
|
<Link to="/Ansible/roles/edit">
|
50
50
|
<Button aria-label="edit ansible roles">
|
51
|
-
{__('Edit Ansible
|
51
|
+
{__('Edit Ansible roles')}
|
52
52
|
</Button>
|
53
53
|
</Link>
|
54
54
|
</FlexItem>
|
@@ -90,7 +90,9 @@ const RolesTable = ({
|
|
90
90
|
<Tbody>
|
91
91
|
{ansibleRoles.map(role => (
|
92
92
|
<Tr key={role.id}>
|
93
|
-
<Td>
|
93
|
+
<Td>
|
94
|
+
<a href={role.path}>{role.name}</a>
|
95
|
+
</Td>
|
94
96
|
</Tr>
|
95
97
|
))}
|
96
98
|
</Tbody>
|
@@ -39,7 +39,7 @@ describe('assigning Ansible roles', () => {
|
|
39
39
|
userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
|
40
40
|
await waitFor(tick);
|
41
41
|
await waitFor(tick);
|
42
|
-
expect(screen.getByText('Available
|
42
|
+
expect(screen.getByText('Available Ansible roles')).toBeInTheDocument();
|
43
43
|
userEvent.click(screen.getAllByText('another.role')[1]);
|
44
44
|
userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
|
45
45
|
userEvent.click(screen.getByText('geerlingguy.ceylon'));
|
@@ -67,7 +67,7 @@ describe('assigning Ansible roles', () => {
|
|
67
67
|
await waitFor(tick);
|
68
68
|
userEvent.click(screen.getByRole('button', { name: 'edit ansible roles' }));
|
69
69
|
await waitFor(tick);
|
70
|
-
expect(screen.getByText('Available
|
70
|
+
expect(screen.getByText('Available Ansible roles')).toBeInTheDocument();
|
71
71
|
userEvent.click(screen.getAllByText('another.role')[1]);
|
72
72
|
userEvent.click(screen.getByRole('button', { name: 'Remove selected' }));
|
73
73
|
userEvent.click(screen.getByText('geerlingguy.ceylon'));
|
@@ -34,24 +34,28 @@ const role1 = {
|
|
34
34
|
__typename: 'AnsibleRole',
|
35
35
|
id: 'MDE6QW5zaWJsZVJvbGUtMw==',
|
36
36
|
name: 'aardvaark.cube',
|
37
|
+
path: '/ansible/ansible_roles/search="name = aardvaark.cube"',
|
37
38
|
};
|
38
39
|
|
39
40
|
const role2 = {
|
40
41
|
__typename: 'AnsibleRole',
|
41
42
|
id: 'MDE6QW5zaWJsZVJvbGUtNQ==',
|
42
43
|
name: 'aardvaark.sphere',
|
44
|
+
path: '/ansible/ansible_roles/search="name = aardvaark.sphere"',
|
43
45
|
};
|
44
46
|
|
45
47
|
const role3 = {
|
46
48
|
__typename: 'AnsibleRole',
|
47
49
|
id: 'MDE6QW5zaWJsZVJvbGUtMzA=',
|
48
50
|
name: 'another.role',
|
51
|
+
path: '/ansible/ansible_roles/search="name = another.role"',
|
49
52
|
};
|
50
53
|
|
51
54
|
const role4 = {
|
52
55
|
__typename: 'AnsibleRole',
|
53
56
|
id: 'MDE6QW5zaWJsZVJvbGUtMzk=',
|
54
57
|
name: 'geerlingguy.ceylon',
|
58
|
+
path: '/ansible/ansible_roles/search="name = geerlingguy.ceylon"',
|
55
59
|
};
|
56
60
|
|
57
61
|
const ansibleRolesMock = {
|
@@ -71,26 +75,32 @@ const availableRoles = {
|
|
71
75
|
__typename: 'AnsibleRole',
|
72
76
|
id: 'MDE6QW5zaWJsZVJvbGUtMQ==',
|
73
77
|
name: 'theforeman.foreman_scap_client',
|
78
|
+
path:
|
79
|
+
'/ansible/ansible_roles/search="name = theforeman.foreman_scap_client"',
|
74
80
|
},
|
75
81
|
{
|
76
82
|
__typename: 'AnsibleRole',
|
77
83
|
id: 'MDE6QW5zaWJsZVJvbGUtMg==',
|
78
84
|
name: 'adriagalin.motd',
|
85
|
+
path: '/ansible/ansible_roles/search="name = adriagalin.motd"',
|
79
86
|
},
|
80
87
|
{
|
81
88
|
__typename: 'AnsibleRole',
|
82
89
|
id: 'MDE6QW5zaWJsZVJvbGUtMjI=',
|
83
90
|
name: 'geerlingguy.php',
|
91
|
+
path: '/ansible/ansible_roles/search="name = geerlingguy.php"',
|
84
92
|
},
|
85
93
|
{
|
86
94
|
__typename: 'AnsibleRole',
|
87
95
|
id: 'MDE6QW5zaWJsZVJvbGUtNTc=',
|
88
96
|
name: 'robertdebock.epel',
|
97
|
+
path: '/ansible/ansible_roles/search="name = robertdebock.epel"',
|
89
98
|
},
|
90
99
|
{
|
91
100
|
__typename: 'AnsibleRole',
|
92
101
|
id: 'MDE6QW5zaWJsZVJvbGUtNTg=',
|
93
102
|
name: 'geerlingguy.nfs',
|
103
|
+
path: '/ansible/ansible_roles/search="name = geerlingguy.nfs"',
|
94
104
|
},
|
95
105
|
],
|
96
106
|
};
|
@@ -106,6 +116,7 @@ export const allRolesMocks = allAnsibleRolesMockFactory(
|
|
106
116
|
{
|
107
117
|
id: 'MDE6QW5zaWJsZVJvbGUtMg==',
|
108
118
|
name: 'adriagalin.motd',
|
119
|
+
path: '/ansible/ansible_roles/search="name = adriagalin.motd"',
|
109
120
|
inherited: true,
|
110
121
|
},
|
111
122
|
{ ...role1, inherited: false },
|
@@ -38,14 +38,18 @@ describe('RolesTab', () => {
|
|
38
38
|
);
|
39
39
|
await waitFor(tick);
|
40
40
|
expect(screen.getByText('view all assigned roles')).toBeInTheDocument();
|
41
|
-
expect(
|
41
|
+
expect(
|
42
|
+
screen.queryByText('All assigned Ansible roles')
|
43
|
+
).not.toBeInTheDocument();
|
42
44
|
userEvent.click(screen.getByText('view all assigned roles'));
|
43
45
|
await waitFor(tick);
|
44
|
-
expect(screen.getByText('All Ansible
|
46
|
+
expect(screen.getByText('All assigned Ansible roles')).toBeInTheDocument();
|
45
47
|
expect(screen.getByText('Inherited from Hostgroup')).toBeInTheDocument();
|
46
48
|
userEvent.click(screen.getByRole('button', { name: 'Close' }));
|
47
49
|
await waitFor(tick);
|
48
|
-
expect(
|
50
|
+
expect(
|
51
|
+
screen.queryByText('All assigned Ansible roles')
|
52
|
+
).not.toBeInTheDocument();
|
49
53
|
});
|
50
54
|
it('should load Ansible Roles as viewer', async () => {
|
51
55
|
render(
|
@@ -1,6 +1,7 @@
|
|
1
|
-
import React from 'react';
|
1
|
+
import React, { useState } from 'react';
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import { useQuery } from '@apollo/client';
|
4
|
+
import { Button } from '@patternfly/react-core';
|
4
5
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
6
|
|
6
7
|
import ansibleRolesQuery from '../../../../graphql/queries/hostAnsibleRoles.gql';
|
@@ -10,11 +11,12 @@ import {
|
|
10
11
|
useParamsToVars,
|
11
12
|
useCurrentPagination,
|
12
13
|
} from '../../../../helpers/pageParamsHelper';
|
14
|
+
import EditRolesModal from './EditRolesModal';
|
13
15
|
|
14
16
|
const RolesTab = ({ hostId, history, canEditHost }) => {
|
15
17
|
const hostGlobalId = encodeId('Host', hostId);
|
16
18
|
const pagination = useCurrentPagination(history);
|
17
|
-
|
19
|
+
const [assignModal, setAssignModal] = useState(false);
|
18
20
|
const renameData = data => ({
|
19
21
|
ansibleRoles: data.host.ownAnsibleRoles.nodes,
|
20
22
|
totalCount: data.host.ownAnsibleRoles.totalCount,
|
@@ -26,19 +28,41 @@ const RolesTab = ({ hostId, history, canEditHost }) => {
|
|
26
28
|
fetchPolicy: 'network-only',
|
27
29
|
});
|
28
30
|
|
31
|
+
const editBtn = canEditHost ? (
|
32
|
+
<Button
|
33
|
+
onClick={() => setAssignModal(true)}
|
34
|
+
aria-label="edit ansible roles"
|
35
|
+
>
|
36
|
+
{__('Assign Ansible roles')}
|
37
|
+
</Button>
|
38
|
+
) : null;
|
29
39
|
return (
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
<>
|
41
|
+
<RolesTable
|
42
|
+
fetchFn={useFetchFn}
|
43
|
+
renamedDataPath="ansibleRoles"
|
44
|
+
renameData={renameData}
|
45
|
+
permissions={['view_ansible_roles']}
|
46
|
+
history={history}
|
47
|
+
hostGlobalId={hostGlobalId}
|
48
|
+
emptyStateProps={{
|
49
|
+
header: __('No Ansible roles assigned'),
|
50
|
+
action: editBtn,
|
51
|
+
}}
|
52
|
+
pagination={pagination}
|
53
|
+
canEditHost={canEditHost}
|
54
|
+
hostId={hostId}
|
55
|
+
/>
|
56
|
+
{assignModal && (
|
57
|
+
<EditRolesModal
|
58
|
+
closeModal={() => setAssignModal(false)}
|
59
|
+
isOpen={assignModal}
|
60
|
+
assignedRoles={[]}
|
61
|
+
hostId={hostId}
|
62
|
+
canEditHost={canEditHost}
|
63
|
+
/>
|
64
|
+
)}
|
65
|
+
</>
|
42
66
|
);
|
43
67
|
};
|
44
68
|
|
@@ -63,7 +63,7 @@ const DualList = props => {
|
|
63
63
|
return (
|
64
64
|
<div className="pf-c-dual-list-selector">
|
65
65
|
<ListPane
|
66
|
-
title={__('Available
|
66
|
+
title={__('Available Ansible roles')}
|
67
67
|
items={props.availableOptions}
|
68
68
|
paneClass="pf-m-available"
|
69
69
|
onItemClick={onItemClick('availableSelected')}
|
@@ -81,7 +81,7 @@ const DualList = props => {
|
|
81
81
|
removeSelectedDisabled={selectState.chosenSelected.length === 0}
|
82
82
|
/>
|
83
83
|
<ListPane
|
84
|
-
title={__('
|
84
|
+
title={__('Host assigned Ansible roles')}
|
85
85
|
items={props.chosenOptions}
|
86
86
|
paneClass="pf-m-chosen"
|
87
87
|
draggable
|
data/webpack/formHelper.js
CHANGED
@@ -7,7 +7,7 @@ import {
|
|
7
7
|
DatePicker,
|
8
8
|
TimePicker,
|
9
9
|
} from '@patternfly/react-core';
|
10
|
-
import ExclamationCircleIcon from '@patternfly/react-icons
|
10
|
+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
|
11
11
|
|
12
12
|
const wrapFieldProps = fieldProps => {
|
13
13
|
const { onChange } = fieldProps;
|
@@ -24,6 +24,7 @@ import {
|
|
24
24
|
} from '../../../testHelper';
|
25
25
|
|
26
26
|
import { toCron } from '../../../components/AnsibleHostDetail/components/JobsTab/NewRecurringJobHelper';
|
27
|
+
import { readableCron } from '../../../components/AnsibleHostDetail/components/JobsTab/JobsTabHelper';
|
27
28
|
|
28
29
|
const TestComponent = withRedux(withRouter(withMockedProvider(HostgroupJobs)));
|
29
30
|
|
@@ -46,7 +47,7 @@ describe('HostgroupJobs', () => {
|
|
46
47
|
.map(element => expect(element).toBeInTheDocument());
|
47
48
|
expect(screen.getByText('Scheduled recurring jobs')).toBeInTheDocument();
|
48
49
|
expect(screen.getByText('Previously executed jobs')).toBeInTheDocument();
|
49
|
-
expect(screen.getByText('54 10 15 * *')).toBeInTheDocument();
|
50
|
+
expect(screen.getByText(readableCron('54 10 15 * *'))).toBeInTheDocument();
|
50
51
|
});
|
51
52
|
it('should show empty state', async () => {
|
52
53
|
render(
|
@@ -103,7 +104,9 @@ describe('HostgroupJobs', () => {
|
|
103
104
|
type: 'success',
|
104
105
|
message: 'Ansible job was successfully created.',
|
105
106
|
});
|
106
|
-
expect(
|
107
|
+
expect(
|
108
|
+
screen.getByText(readableCron(toCron(futureDate, 'weekly')))
|
109
|
+
).toBeInTheDocument();
|
107
110
|
expect(screen.getByText('in 3 days')).toBeInTheDocument();
|
108
111
|
expect(
|
109
112
|
screen.queryByText('No config job for Ansible roles scheduled')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_ansible
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.0.
|
4
|
+
version: 7.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Lobato Garcia
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acts_as_list
|
@@ -101,6 +101,8 @@ files:
|
|
101
101
|
- app/graphql/mutations/hosts/assign_ansible_roles.rb
|
102
102
|
- app/graphql/presenters/ansible_role_presenter.rb
|
103
103
|
- app/graphql/presenters/overriden_ansible_variable_presenter.rb
|
104
|
+
- app/graphql/resolvers/ansible_role/path.rb
|
105
|
+
- app/graphql/resolvers/ansible_variable/path.rb
|
104
106
|
- app/graphql/types/ansible_role.rb
|
105
107
|
- app/graphql/types/ansible_variable.rb
|
106
108
|
- app/graphql/types/ansible_variable_override.rb
|
@@ -325,9 +327,11 @@ files:
|
|
325
327
|
- webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.js
|
326
328
|
- webpack/components/AnsibleHostDetail/components/JobsTab/NewRecurringJobModal.scss
|
327
329
|
- webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js
|
330
|
+
- webpack/components/AnsibleHostDetail/components/JobsTab/PreviousJobsTable.js.orig
|
328
331
|
- webpack/components/AnsibleHostDetail/components/JobsTab/RecurringJobsTable.js
|
329
332
|
- webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.fixtures.js
|
330
333
|
- webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTab.test.js
|
334
|
+
- webpack/components/AnsibleHostDetail/components/JobsTab/__test__/JobsTabHelper.test.js
|
331
335
|
- webpack/components/AnsibleHostDetail/components/JobsTab/index.js
|
332
336
|
- webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/AllRolesTable.js
|
333
337
|
- webpack/components/AnsibleHostDetail/components/RolesTab/AllRolesModal/index.js
|
@@ -445,50 +449,50 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
445
449
|
- !ruby/object:Gem::Version
|
446
450
|
version: '0'
|
447
451
|
requirements: []
|
448
|
-
rubygems_version: 3.
|
452
|
+
rubygems_version: 3.3.7
|
449
453
|
signing_key:
|
450
454
|
specification_version: 4
|
451
455
|
summary: Ansible integration with Foreman (theforeman.org)
|
452
456
|
test_files:
|
453
|
-
- test/functional/
|
454
|
-
- test/functional/ansible_roles_controller_test.rb
|
455
|
-
- test/functional/api/v2/ansible_variables_controller_test.rb
|
457
|
+
- test/functional/api/v2/hosts_controller_test.rb
|
456
458
|
- test/functional/api/v2/ansible_roles_controller_test.rb
|
457
459
|
- test/functional/api/v2/hostgroups_controller_test.rb
|
460
|
+
- test/functional/api/v2/ansible_variables_controller_test.rb
|
458
461
|
- test/functional/api/v2/ansible_inventories_controller_test.rb
|
459
|
-
- test/functional/api/v2/hosts_controller_test.rb
|
460
|
-
- test/functional/ui_ansible_roles_controller_test.rb
|
461
462
|
- test/functional/hosts_controller_test.rb
|
462
|
-
- test/
|
463
|
-
- test/
|
464
|
-
- test/
|
465
|
-
- test/unit/
|
466
|
-
- test/unit/actions/run_proxy_ansible_command_test.rb
|
467
|
-
- test/unit/services/api_roles_importer_test.rb
|
468
|
-
- test/unit/services/ui_roles_importer_test.rb
|
463
|
+
- test/functional/ansible_roles_controller_test.rb
|
464
|
+
- test/functional/ansible_variables_controller_test.rb
|
465
|
+
- test/functional/ui_ansible_roles_controller_test.rb
|
466
|
+
- test/unit/services/inventory_creator_test.rb
|
469
467
|
- test/unit/services/ansible_report_importer_test.rb
|
470
|
-
- test/unit/services/
|
471
|
-
- test/unit/services/override_resolver_test.rb
|
468
|
+
- test/unit/services/ui_roles_importer_test.rb
|
472
469
|
- test/unit/services/roles_importer_test.rb
|
473
|
-
- test/unit/services/inventory_creator_test.rb
|
474
470
|
- test/unit/services/ansible_variables_importer_test.rb
|
475
|
-
- test/unit/
|
471
|
+
- test/unit/services/override_resolver_test.rb
|
472
|
+
- test/unit/services/api_roles_importer_test.rb
|
473
|
+
- test/unit/services/insights_plan_runner_test.rb
|
476
474
|
- test/unit/ansible_role_test.rb
|
477
|
-
- test/unit/hostgroup_ansible_role_test.rb
|
478
|
-
- test/unit/ansible_variable_test.rb
|
479
475
|
- test/unit/host_ansible_role_test.rb
|
476
|
+
- test/unit/hostgroup_ansible_role_test.rb
|
480
477
|
- test/unit/helpers/ansible_reports_helper_test.rb
|
481
478
|
- test/unit/ansible_provider_test.rb
|
482
|
-
- test/unit/ignore_roles_test.rb
|
483
|
-
- test/unit/import_roles_and_variables.rb
|
484
|
-
- test/unit/concerns/config_reports_extensions_test.rb
|
485
479
|
- test/unit/concerns/host_managed_extensions_test.rb
|
480
|
+
- test/unit/concerns/config_reports_extensions_test.rb
|
486
481
|
- test/unit/concerns/hostgroup_extensions_test.rb
|
482
|
+
- test/unit/ignore_roles_test.rb
|
483
|
+
- test/unit/ansible_variable_test.rb
|
484
|
+
- test/unit/import_roles_and_variables.rb
|
485
|
+
- test/unit/lib/proxy_api/ansible_test.rb
|
486
|
+
- test/unit/actions/run_ansible_job_test.rb
|
487
|
+
- test/unit/actions/run_proxy_ansible_command_test.rb
|
488
|
+
- test/foreman_ansible/helpers/ansible_roles_helper_test.rb
|
489
|
+
- test/test_plugin_helper.rb
|
487
490
|
- test/factories/ansible_proxy.rb
|
488
|
-
- test/factories/host_ansible_enhancements.rb
|
489
491
|
- test/factories/ansible_variables.rb
|
492
|
+
- test/factories/host_ansible_enhancements.rb
|
490
493
|
- test/factories/ansible_roles.rb
|
491
494
|
- test/fixtures/report.json
|
492
495
|
- test/fixtures/sample_facts.json
|
493
496
|
- test/fixtures/insights_playbook.yaml
|
494
|
-
- test/
|
497
|
+
- test/graphql/mutations/hosts/assign_ansible_roles_mutation_test.rb
|
498
|
+
- test/graphql/queries/ansible_roles_query_test.rb
|