foreman_remote_execution 4.4.0 → 4.5.3
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/job_invocations_controller.rb +13 -24
- data/app/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/job_templates_controller.rb +4 -4
- data/app/controllers/ui_job_wizard_controller.rb +19 -0
- data/app/helpers/job_invocations_helper.rb +2 -2
- data/app/helpers/remote_execution_helper.rb +13 -9
- data/app/lib/actions/remote_execution/run_host_job.rb +36 -6
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
- data/app/models/host_proxy_invocation.rb +4 -0
- data/app/models/host_status/execution_status.rb +5 -5
- data/app/models/job_invocation.rb +31 -12
- data/app/models/job_invocation_composer.rb +61 -19
- data/app/models/remote_execution_provider.rb +1 -1
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/ssh_execution_provider.rb +4 -4
- data/app/models/targeting.rb +5 -1
- data/app/overrides/execution_interface.rb +8 -8
- data/app/overrides/subnet_proxies.rb +6 -6
- data/app/views/job_invocations/index.html.erb +1 -1
- data/app/views/templates/ssh/module_action.erb +1 -0
- data/app/views/templates/ssh/puppet_run_once.erb +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
- data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
- data/extra/cockpit/foreman-cockpit-session +6 -6
- data/lib/foreman_remote_execution/engine.rb +11 -8
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +2 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
- data/test/unit/job_invocation_composer_test.rb +59 -2
- data/test/unit/job_invocation_test.rb +1 -1
- data/webpack/JobWizard/JobWizard.js +80 -19
- data/webpack/JobWizard/JobWizard.scss +42 -1
- data/webpack/JobWizard/JobWizardConstants.js +11 -0
- data/webpack/JobWizard/JobWizardSelectors.js +27 -1
- data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
- data/webpack/JobWizard/__tests__/fixtures.js +128 -0
- data/webpack/JobWizard/__tests__/integration.test.js +84 -0
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +110 -0
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +195 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +144 -0
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +34 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -44
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +9 -1
- data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
- data/webpack/JobWizard/steps/Schedule/index.js +41 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +20 -0
- data/webpack/JobWizard/steps/form/Formatter.js +149 -0
- data/webpack/JobWizard/steps/form/GroupedSelectField.js +3 -0
- data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
- data/webpack/JobWizard/steps/form/SelectField.js +24 -3
- data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
- data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
- data/webpack/global_index.js +5 -3
- data/webpack/index.js +3 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +1 -5
- data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
- data/webpack/react_app/extend/{fills.js → fillRecentJobsCard.js} +7 -6
- data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
- data/webpack/react_app/extend/reducers.js +2 -1
- metadata +24 -14
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
- data/test/models/orchestration/ssh_test.rb +0 -56
- data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -20
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -83
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -64
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -36
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -22
- data/webpack/fills_index.js +0 -11
@@ -0,0 +1,84 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { Provider } from 'react-redux';
|
3
|
+
import { mount } from '@theforeman/test';
|
4
|
+
import { render, fireEvent, screen, act } from '@testing-library/react';
|
5
|
+
import * as api from 'foremanReact/redux/API';
|
6
|
+
import { JobWizard } from '../JobWizard';
|
7
|
+
import * as selectors from '../JobWizardSelectors';
|
8
|
+
import {
|
9
|
+
testSetup,
|
10
|
+
mockApi,
|
11
|
+
jobCategories,
|
12
|
+
jobTemplateResponse as jobTemplate,
|
13
|
+
} from './fixtures';
|
14
|
+
|
15
|
+
const store = testSetup(selectors, api);
|
16
|
+
|
17
|
+
selectors.selectJobTemplate.mockImplementation(() => {});
|
18
|
+
|
19
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
20
|
+
if (action.key === 'JOB_CATEGORIES') {
|
21
|
+
handleSuccess && handleSuccess({ data: { job_categories: jobCategories } });
|
22
|
+
}
|
23
|
+
return { type: 'get', ...action };
|
24
|
+
});
|
25
|
+
describe('Job wizard fill', () => {
|
26
|
+
it('should select template', async () => {
|
27
|
+
const wrapper = mount(
|
28
|
+
<Provider store={store}>
|
29
|
+
<JobWizard advancedValues={{}} setAdvancedValues={jest.fn()} />
|
30
|
+
</Provider>
|
31
|
+
);
|
32
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
33
|
+
4
|
34
|
+
);
|
35
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
36
|
+
expect(store.getActions()).toMatchSnapshot('initial');
|
37
|
+
|
38
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
39
|
+
wrapper.find('.pf-c-button.pf-c-select__toggle-button').simulate('click');
|
40
|
+
await act(async () => {
|
41
|
+
await wrapper
|
42
|
+
.find('.pf-c-select__menu-item')
|
43
|
+
.first()
|
44
|
+
.simulate('click');
|
45
|
+
await wrapper.update();
|
46
|
+
});
|
47
|
+
expect(store.getActions().slice(-1)).toMatchSnapshot('select template');
|
48
|
+
expect(wrapper.find('.pf-c-wizard__nav-link.pf-m-disabled')).toHaveLength(
|
49
|
+
0
|
50
|
+
);
|
51
|
+
});
|
52
|
+
|
53
|
+
it('have all steps', async () => {
|
54
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => null);
|
55
|
+
selectors.selectJobTemplate.mockRestore();
|
56
|
+
selectors.selectJobTemplates.mockRestore();
|
57
|
+
selectors.selectJobCategories.mockRestore();
|
58
|
+
mockApi(api);
|
59
|
+
|
60
|
+
render(
|
61
|
+
<Provider store={store}>
|
62
|
+
<JobWizard />
|
63
|
+
</Provider>
|
64
|
+
);
|
65
|
+
const steps = [
|
66
|
+
'Target Hosts',
|
67
|
+
'Advanced Fields',
|
68
|
+
'Schedule',
|
69
|
+
'Review Details',
|
70
|
+
'Category and Template',
|
71
|
+
];
|
72
|
+
// eslint-disable-next-line no-unused-vars
|
73
|
+
for await (const step of steps) {
|
74
|
+
const stepSelector = screen.getByText(step);
|
75
|
+
const stepTitle = screen.getAllByText(step);
|
76
|
+
expect(stepTitle).toHaveLength(1);
|
77
|
+
await act(async () => {
|
78
|
+
await fireEvent.click(stepSelector);
|
79
|
+
});
|
80
|
+
const stepTitles = screen.getAllByText(step);
|
81
|
+
expect(stepTitles).toHaveLength(3);
|
82
|
+
}
|
83
|
+
});
|
84
|
+
});
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { useSelector } from 'react-redux';
|
4
|
+
import { Title, Form } from '@patternfly/react-core';
|
5
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
6
|
+
import {
|
7
|
+
selectEffectiveUser,
|
8
|
+
selectAdvancedTemplateInputs,
|
9
|
+
selectTemplateInputs,
|
10
|
+
} from '../../JobWizardSelectors';
|
11
|
+
import {
|
12
|
+
EffectiveUserField,
|
13
|
+
TimeoutToKillField,
|
14
|
+
PasswordField,
|
15
|
+
KeyPassphraseField,
|
16
|
+
EffectiveUserPasswordField,
|
17
|
+
ConcurrencyLevelField,
|
18
|
+
TimeSpanLevelField,
|
19
|
+
TemplateInputsFields,
|
20
|
+
} from './Fields';
|
21
|
+
import { DescriptionField } from './DescriptionField';
|
22
|
+
|
23
|
+
export const AdvancedFields = ({ advancedValues, setAdvancedValues }) => {
|
24
|
+
const effectiveUser = useSelector(selectEffectiveUser);
|
25
|
+
const advancedTemplateInputs = useSelector(selectAdvancedTemplateInputs);
|
26
|
+
const templateInputs = useSelector(selectTemplateInputs);
|
27
|
+
return (
|
28
|
+
<>
|
29
|
+
<Title headingLevel="h2" className="advanced-fields-title">
|
30
|
+
{__('Advanced Fields')}
|
31
|
+
</Title>
|
32
|
+
<Form id="advanced-fields-job-template" autoComplete="off">
|
33
|
+
<TemplateInputsFields
|
34
|
+
inputs={advancedTemplateInputs}
|
35
|
+
value={advancedValues.templateValues}
|
36
|
+
setValue={newValue => setAdvancedValues({ templateValues: newValue })}
|
37
|
+
/>
|
38
|
+
{effectiveUser?.overridable && (
|
39
|
+
<EffectiveUserField
|
40
|
+
value={advancedValues.effectiveUserValue}
|
41
|
+
setValue={newValue =>
|
42
|
+
setAdvancedValues({
|
43
|
+
effectiveUserValue: newValue,
|
44
|
+
})
|
45
|
+
}
|
46
|
+
/>
|
47
|
+
)}
|
48
|
+
<DescriptionField
|
49
|
+
inputs={templateInputs}
|
50
|
+
value={advancedValues.description}
|
51
|
+
setValue={newValue => setAdvancedValues({ description: newValue })}
|
52
|
+
/>
|
53
|
+
<TimeoutToKillField
|
54
|
+
value={advancedValues.timeoutToKill}
|
55
|
+
setValue={newValue =>
|
56
|
+
setAdvancedValues({
|
57
|
+
timeoutToKill: newValue,
|
58
|
+
})
|
59
|
+
}
|
60
|
+
/>
|
61
|
+
<PasswordField
|
62
|
+
value={advancedValues.password}
|
63
|
+
setValue={newValue =>
|
64
|
+
setAdvancedValues({
|
65
|
+
password: newValue,
|
66
|
+
})
|
67
|
+
}
|
68
|
+
/>
|
69
|
+
<KeyPassphraseField
|
70
|
+
value={advancedValues.keyPassphrase}
|
71
|
+
setValue={newValue =>
|
72
|
+
setAdvancedValues({
|
73
|
+
keyPassphrase: newValue,
|
74
|
+
})
|
75
|
+
}
|
76
|
+
/>
|
77
|
+
<EffectiveUserPasswordField
|
78
|
+
value={advancedValues.effectiveUserPassword}
|
79
|
+
setValue={newValue =>
|
80
|
+
setAdvancedValues({
|
81
|
+
effectiveUserPassword: newValue,
|
82
|
+
})
|
83
|
+
}
|
84
|
+
/>
|
85
|
+
<ConcurrencyLevelField
|
86
|
+
value={advancedValues.concurrencyLevel}
|
87
|
+
setValue={newValue =>
|
88
|
+
setAdvancedValues({
|
89
|
+
concurrencyLevel: newValue,
|
90
|
+
})
|
91
|
+
}
|
92
|
+
/>
|
93
|
+
<TimeSpanLevelField
|
94
|
+
value={advancedValues.timeSpan}
|
95
|
+
setValue={newValue =>
|
96
|
+
setAdvancedValues({
|
97
|
+
timeSpan: newValue,
|
98
|
+
})
|
99
|
+
}
|
100
|
+
/>
|
101
|
+
</Form>
|
102
|
+
</>
|
103
|
+
);
|
104
|
+
};
|
105
|
+
|
106
|
+
AdvancedFields.propTypes = {
|
107
|
+
advancedValues: PropTypes.object.isRequired,
|
108
|
+
setAdvancedValues: PropTypes.func.isRequired,
|
109
|
+
};
|
110
|
+
export default AdvancedFields;
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import React, { useState } from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { FormGroup, TextInput, Button } from '@patternfly/react-core';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
|
6
|
+
export const DescriptionField = ({ inputs, value, setValue }) => {
|
7
|
+
const generateDesc = () => {
|
8
|
+
let newDesc = value;
|
9
|
+
if (value) {
|
10
|
+
const re = new RegExp('%\\{([^\\}]+)\\}', 'gm');
|
11
|
+
const results = [...newDesc.matchAll(re)].map(result => ({
|
12
|
+
name: result[1],
|
13
|
+
text: result[0],
|
14
|
+
}));
|
15
|
+
results.forEach(result => {
|
16
|
+
newDesc = newDesc.replace(
|
17
|
+
result.text,
|
18
|
+
// TODO: Replace with the value of the input from Target Hosts step
|
19
|
+
inputs.find(input => input.name === result.name)?.name || result.text
|
20
|
+
);
|
21
|
+
});
|
22
|
+
}
|
23
|
+
return newDesc;
|
24
|
+
};
|
25
|
+
const [generatedDesc, setGeneratedDesc] = useState(generateDesc());
|
26
|
+
const [isPreview, setIsPreview] = useState(true);
|
27
|
+
|
28
|
+
const togglePreview = () => {
|
29
|
+
setGeneratedDesc(generateDesc());
|
30
|
+
setIsPreview(v => !v);
|
31
|
+
};
|
32
|
+
|
33
|
+
return (
|
34
|
+
<FormGroup
|
35
|
+
label={__('Description')}
|
36
|
+
fieldId="description"
|
37
|
+
helperText={
|
38
|
+
<Button variant="link" isInline onClick={togglePreview}>
|
39
|
+
{isPreview
|
40
|
+
? __('Edit job description template')
|
41
|
+
: __('Preview job description')}
|
42
|
+
</Button>
|
43
|
+
}
|
44
|
+
>
|
45
|
+
{isPreview ? (
|
46
|
+
<TextInput id="description-preview" value={generatedDesc} isDisabled />
|
47
|
+
) : (
|
48
|
+
<TextInput
|
49
|
+
type="text"
|
50
|
+
autoComplete="description"
|
51
|
+
id="description"
|
52
|
+
value={value}
|
53
|
+
onChange={newValue => setValue(newValue)}
|
54
|
+
/>
|
55
|
+
)}
|
56
|
+
</FormGroup>
|
57
|
+
);
|
58
|
+
};
|
59
|
+
|
60
|
+
DescriptionField.propTypes = {
|
61
|
+
inputs: PropTypes.array.isRequired,
|
62
|
+
value: PropTypes.string,
|
63
|
+
setValue: PropTypes.func.isRequired,
|
64
|
+
};
|
65
|
+
DescriptionField.defaultProps = {
|
66
|
+
value: '',
|
67
|
+
};
|
@@ -0,0 +1,195 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
3
|
+
import { FormGroup, TextInput } from '@patternfly/react-core';
|
4
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import { helpLabel } from '../form/FormHelpers';
|
6
|
+
import { formatter } from '../form/Formatter';
|
7
|
+
import { NumberInput } from '../form/NumberInput';
|
8
|
+
|
9
|
+
export const EffectiveUserField = ({ value, setValue }) => (
|
10
|
+
<FormGroup
|
11
|
+
label={__('Effective user')}
|
12
|
+
labelIcon={helpLabel(
|
13
|
+
__(
|
14
|
+
'A user to be used for executing the script. If it differs from the SSH user, su or sudo is used to switch the accounts.'
|
15
|
+
),
|
16
|
+
'effective-user'
|
17
|
+
)}
|
18
|
+
fieldId="effective-user"
|
19
|
+
>
|
20
|
+
<TextInput
|
21
|
+
autoComplete="effective-user"
|
22
|
+
id="effective-user"
|
23
|
+
type="text"
|
24
|
+
value={value}
|
25
|
+
onChange={newValue => setValue(newValue)}
|
26
|
+
/>
|
27
|
+
</FormGroup>
|
28
|
+
);
|
29
|
+
|
30
|
+
export const TimeoutToKillField = ({ value, setValue }) => (
|
31
|
+
<NumberInput
|
32
|
+
formProps={{
|
33
|
+
label: __('Timeout to kill'),
|
34
|
+
labelIcon: helpLabel(
|
35
|
+
__(
|
36
|
+
'Time in seconds from the start on the remote host after which the job should be killed.'
|
37
|
+
),
|
38
|
+
'timeout-to-kill'
|
39
|
+
),
|
40
|
+
fieldId: 'timeout-to-kill',
|
41
|
+
}}
|
42
|
+
inputProps={{
|
43
|
+
value,
|
44
|
+
placeholder: __('For example: 1, 2, 3, 4, 5...'),
|
45
|
+
autoComplete: 'timeout-to-kill',
|
46
|
+
id: 'timeout-to-kill',
|
47
|
+
onChange: newValue => setValue(newValue),
|
48
|
+
}}
|
49
|
+
/>
|
50
|
+
);
|
51
|
+
|
52
|
+
export const PasswordField = ({ value, setValue }) => (
|
53
|
+
<FormGroup
|
54
|
+
label={__('Password')}
|
55
|
+
labelIcon={helpLabel(
|
56
|
+
__(
|
57
|
+
'Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.'
|
58
|
+
),
|
59
|
+
'password'
|
60
|
+
)}
|
61
|
+
fieldId="job-password"
|
62
|
+
>
|
63
|
+
<TextInput
|
64
|
+
autoComplete="new-password" // to prevent firefox from autofilling the user password
|
65
|
+
id="job-password"
|
66
|
+
type="password"
|
67
|
+
placeholder="*****"
|
68
|
+
value={value}
|
69
|
+
onChange={newValue => setValue(newValue)}
|
70
|
+
/>
|
71
|
+
</FormGroup>
|
72
|
+
);
|
73
|
+
|
74
|
+
export const KeyPassphraseField = ({ value, setValue }) => (
|
75
|
+
<FormGroup
|
76
|
+
label={__('Private key passphrase')}
|
77
|
+
labelIcon={helpLabel(
|
78
|
+
__(
|
79
|
+
'Key passphrase is only applicable for SSH provider. Other providers ignore this field. Passphrase is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.'
|
80
|
+
),
|
81
|
+
'key-passphrase'
|
82
|
+
)}
|
83
|
+
fieldId="key-passphrase"
|
84
|
+
>
|
85
|
+
<TextInput
|
86
|
+
autoComplete="key-passphrase"
|
87
|
+
id="key-passphrase"
|
88
|
+
type="password"
|
89
|
+
placeholder="*****"
|
90
|
+
value={value}
|
91
|
+
onChange={newValue => setValue(newValue)}
|
92
|
+
/>
|
93
|
+
</FormGroup>
|
94
|
+
);
|
95
|
+
|
96
|
+
export const EffectiveUserPasswordField = ({ value, setValue }) => (
|
97
|
+
<FormGroup
|
98
|
+
label={__('Effective user password')}
|
99
|
+
labelIcon={helpLabel(
|
100
|
+
__(
|
101
|
+
'Effective user password is only applicable for SSH provider. Other providers ignore this field. Password is stored encrypted in DB until the job finishes. For future or recurring executions, it is removed after the last execution.'
|
102
|
+
),
|
103
|
+
'effective-user-password'
|
104
|
+
)}
|
105
|
+
fieldId="effective-user-password"
|
106
|
+
>
|
107
|
+
<TextInput
|
108
|
+
autoComplete="effective-user-password"
|
109
|
+
id="effective-user-password"
|
110
|
+
type="password"
|
111
|
+
placeholder="*****"
|
112
|
+
value={value}
|
113
|
+
onChange={newValue => setValue(newValue)}
|
114
|
+
/>
|
115
|
+
</FormGroup>
|
116
|
+
);
|
117
|
+
|
118
|
+
export const ConcurrencyLevelField = ({ value, setValue }) => (
|
119
|
+
<NumberInput
|
120
|
+
formProps={{
|
121
|
+
label: __('Concurrency level'),
|
122
|
+
labelIcon: helpLabel(
|
123
|
+
__(
|
124
|
+
'Run at most N tasks at a time. If this is set and proxy batch triggering is enabled, then tasks are triggered on the smart proxy in batches of size 1.'
|
125
|
+
),
|
126
|
+
'concurrency-level'
|
127
|
+
),
|
128
|
+
fieldId: 'concurrency-level',
|
129
|
+
}}
|
130
|
+
inputProps={{
|
131
|
+
min: 1,
|
132
|
+
autoComplete: 'concurrency-level',
|
133
|
+
id: 'concurrency-level',
|
134
|
+
placeholder: __('For example: 1, 2, 3, 4, 5...'),
|
135
|
+
value,
|
136
|
+
onChange: newValue => setValue(newValue),
|
137
|
+
}}
|
138
|
+
/>
|
139
|
+
);
|
140
|
+
|
141
|
+
export const TimeSpanLevelField = ({ value, setValue }) => (
|
142
|
+
<NumberInput
|
143
|
+
formProps={{
|
144
|
+
label: __('Time span'),
|
145
|
+
labelIcon: helpLabel(
|
146
|
+
__(
|
147
|
+
'Distribute execution over N seconds. If this is set and proxy batch triggering is enabled, then tasks are triggered on the smart proxy in batches of size 1.'
|
148
|
+
),
|
149
|
+
'time-span'
|
150
|
+
),
|
151
|
+
fieldId: 'time-span',
|
152
|
+
}}
|
153
|
+
inputProps={{
|
154
|
+
min: 1,
|
155
|
+
autoComplete: 'time-span',
|
156
|
+
id: 'time-span',
|
157
|
+
placeholder: __('For example: 1, 2, 3, 4, 5...'),
|
158
|
+
value,
|
159
|
+
onChange: newValue => setValue(newValue),
|
160
|
+
}}
|
161
|
+
/>
|
162
|
+
);
|
163
|
+
|
164
|
+
export const TemplateInputsFields = ({ inputs, value, setValue }) => (
|
165
|
+
<>{inputs?.map(input => formatter(input, value, setValue))}</>
|
166
|
+
);
|
167
|
+
EffectiveUserField.propTypes = {
|
168
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
169
|
+
setValue: PropTypes.func.isRequired,
|
170
|
+
};
|
171
|
+
EffectiveUserField.defaultProps = {
|
172
|
+
value: '',
|
173
|
+
};
|
174
|
+
|
175
|
+
TimeoutToKillField.propTypes = EffectiveUserField.propTypes;
|
176
|
+
TimeoutToKillField.defaultProps = EffectiveUserField.defaultProps;
|
177
|
+
PasswordField.propTypes = EffectiveUserField.propTypes;
|
178
|
+
PasswordField.defaultProps = EffectiveUserField.defaultProps;
|
179
|
+
KeyPassphraseField.propTypes = EffectiveUserField.propTypes;
|
180
|
+
KeyPassphraseField.defaultProps = EffectiveUserField.defaultProps;
|
181
|
+
EffectiveUserPasswordField.propTypes = EffectiveUserField.propTypes;
|
182
|
+
EffectiveUserPasswordField.defaultProps = EffectiveUserField.defaultProps;
|
183
|
+
ConcurrencyLevelField.propTypes = EffectiveUserField.propTypes;
|
184
|
+
ConcurrencyLevelField.defaultProps = EffectiveUserField.defaultProps;
|
185
|
+
TimeSpanLevelField.propTypes = EffectiveUserField.propTypes;
|
186
|
+
TimeSpanLevelField.defaultProps = EffectiveUserField.defaultProps;
|
187
|
+
TemplateInputsFields.propTypes = {
|
188
|
+
inputs: PropTypes.array.isRequired,
|
189
|
+
value: PropTypes.object,
|
190
|
+
setValue: PropTypes.func.isRequired,
|
191
|
+
};
|
192
|
+
|
193
|
+
TemplateInputsFields.defaultProps = {
|
194
|
+
value: {},
|
195
|
+
};
|