mirror of
https://github.com/ansible/awx.git
synced 2026-05-14 21:07:39 -02:30
Reapply prompt on launch for job template fields after rebasing.
This commit is contained in:
@@ -1,18 +1,32 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { string, bool } from 'prop-types';
|
import { string, bool } from 'prop-types';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
import { Split, SplitItem } from '@patternfly/react-core';
|
import { Split, SplitItem } from '@patternfly/react-core';
|
||||||
|
import { CheckboxField } from '@components/FormField';
|
||||||
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
|
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
|
||||||
import CodeMirrorInput from './CodeMirrorInput';
|
import CodeMirrorInput from './CodeMirrorInput';
|
||||||
import YamlJsonToggle from './YamlJsonToggle';
|
import YamlJsonToggle from './YamlJsonToggle';
|
||||||
import { JSON_MODE, YAML_MODE } from './constants';
|
import { JSON_MODE, YAML_MODE } from './constants';
|
||||||
|
|
||||||
function VariablesField({ id, name, label, readOnly }) {
|
const FieldHeader = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCheckboxField = styled(CheckboxField)`
|
||||||
|
--pf-c-check__label--FontSize: var(--pf-c-form__label--FontSize);
|
||||||
|
`;
|
||||||
|
|
||||||
|
function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
|
||||||
const [field, meta, helpers] = useField(name);
|
const [field, meta, helpers] = useField(name);
|
||||||
const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
|
const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="pf-c-form__group">
|
||||||
|
<FieldHeader>
|
||||||
<Split gutter="sm">
|
<Split gutter="sm">
|
||||||
<SplitItem>
|
<SplitItem>
|
||||||
<label htmlFor={id} className="pf-c-form__label">
|
<label htmlFor={id} className="pf-c-form__label">
|
||||||
@@ -37,6 +51,14 @@ function VariablesField({ id, name, label, readOnly }) {
|
|||||||
/>
|
/>
|
||||||
</SplitItem>
|
</SplitItem>
|
||||||
</Split>
|
</Split>
|
||||||
|
{promptId && (
|
||||||
|
<StyledCheckboxField
|
||||||
|
id="template-ask-variables-on-launch"
|
||||||
|
label={i18n._(t`Prompt On Launch`)}
|
||||||
|
name="ask_variables_on_launch"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FieldHeader>
|
||||||
<CodeMirrorInput
|
<CodeMirrorInput
|
||||||
mode={mode}
|
mode={mode}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
@@ -51,108 +73,6 @@ function VariablesField({ id, name, label, readOnly }) {
|
|||||||
{meta.error}
|
{meta.error}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
VariablesField.propTypes = {
|
|
||||||
id: string.isRequired,
|
|
||||||
name: string.isRequired,
|
|
||||||
label: string.isRequired,
|
|
||||||
readOnly: bool,
|
|
||||||
};
|
|
||||||
VariablesField.defaultProps = {
|
|
||||||
readOnly: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default VariablesField;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { string, bool } from 'prop-types';
|
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { Field, useFormikContext } from 'formik';
|
|
||||||
import { Split, SplitItem } from '@patternfly/react-core';
|
|
||||||
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
|
|
||||||
import { CheckboxField } from '@components/FormField';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import CodeMirrorInput from './CodeMirrorInput';
|
|
||||||
import YamlJsonToggle from './YamlJsonToggle';
|
|
||||||
import { JSON_MODE, YAML_MODE } from './constants';
|
|
||||||
|
|
||||||
const FieldHeader = styled.div`
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCheckboxField = styled(CheckboxField)`
|
|
||||||
--pf-c-check__label--FontSize: var(--pf-c-form__label--FontSize);
|
|
||||||
`;
|
|
||||||
|
|
||||||
function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
|
|
||||||
const { values, setFieldError, setFieldValue } = useFormikContext();
|
|
||||||
const value = values[name];
|
|
||||||
const [mode, setMode] = useState(isJson(value) ? JSON_MODE : YAML_MODE);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="pf-c-form__group">
|
|
||||||
<FieldHeader>
|
|
||||||
<Split gutter="sm">
|
|
||||||
<SplitItem>
|
|
||||||
<label htmlFor={id} className="pf-c-form__label">
|
|
||||||
<span className="pf-c-form__label-text">{label}</span>
|
|
||||||
</label>
|
|
||||||
</SplitItem>
|
|
||||||
<SplitItem>
|
|
||||||
<YamlJsonToggle
|
|
||||||
mode={mode}
|
|
||||||
onChange={newMode => {
|
|
||||||
try {
|
|
||||||
const newVal =
|
|
||||||
newMode === YAML_MODE
|
|
||||||
? jsonToYaml(value)
|
|
||||||
: yamlToJson(value);
|
|
||||||
setFieldValue(name, newVal);
|
|
||||||
setMode(newMode);
|
|
||||||
} catch (err) {
|
|
||||||
setFieldError(name, err.message);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SplitItem>
|
|
||||||
</Split>
|
|
||||||
{promptId && (
|
|
||||||
<StyledCheckboxField
|
|
||||||
id="template-ask-variables-on-launch"
|
|
||||||
label={i18n._(t`Prompt On Launch`)}
|
|
||||||
name="ask_variables_on_launch"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</FieldHeader>
|
|
||||||
<Field name={name}>
|
|
||||||
{({ field, form }) => (
|
|
||||||
<>
|
|
||||||
<CodeMirrorInput
|
|
||||||
mode={mode}
|
|
||||||
readOnly={readOnly}
|
|
||||||
{...field}
|
|
||||||
onChange={newVal => {
|
|
||||||
form.setFieldValue(name, newVal);
|
|
||||||
}}
|
|
||||||
hasErrors={!!form.errors[field.name]}
|
|
||||||
/>
|
|
||||||
{form.errors[field.name] ? (
|
|
||||||
<div
|
|
||||||
className="pf-c-form__helper-text pf-m-error"
|
|
||||||
aria-live="polite"
|
|
||||||
>
|
|
||||||
{form.errors[field.name]}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -161,10 +81,11 @@ VariablesField.propTypes = {
|
|||||||
name: string.isRequired,
|
name: string.isRequired,
|
||||||
label: string.isRequired,
|
label: string.isRequired,
|
||||||
readOnly: bool,
|
readOnly: bool,
|
||||||
|
promptId: string,
|
||||||
};
|
};
|
||||||
VariablesField.defaultProps = {
|
VariablesField.defaultProps = {
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
|
promptId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(VariablesField);
|
export default withI18n()(VariablesField);
|
||||||
*/
|
|
||||||
|
|||||||
@@ -3,210 +3,10 @@ import { withRouter } from 'react-router-dom';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { FormGroup, ToolbarItem } from '@patternfly/react-core';
|
|
||||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
|
||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
|
||||||
import { FieldTooltip } from '@components/FormField';
|
|
||||||
import CredentialChip from '@components/CredentialChip';
|
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
|
||||||
import Lookup from './Lookup';
|
|
||||||
import OptionsList from './shared/OptionsList';
|
|
||||||
|
|
||||||
const QS_CONFIG = getQSConfig('credentials', {
|
|
||||||
page: 1,
|
|
||||||
page_size: 5,
|
|
||||||
order_by: 'name',
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadCredentialTypes() {
|
|
||||||
const { data } = await CredentialTypesAPI.read();
|
|
||||||
const acceptableTypes = ['machine', 'cloud', 'net', 'ssh', 'vault'];
|
|
||||||
return data.results.filter(type => acceptableTypes.includes(type.kind));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadCredentials(params, selectedCredentialTypeId) {
|
|
||||||
params.credential_type = selectedCredentialTypeId || 1;
|
|
||||||
const { data } = await CredentialsAPI.read(params);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MultiCredentialsLookup(props) {
|
|
||||||
const { tooltip, value, onChange, onError, history, i18n } = props;
|
|
||||||
const [credentialTypes, setCredentialTypes] = useState([]);
|
|
||||||
const [selectedType, setSelectedType] = useState(null);
|
|
||||||
const [credentials, setCredentials] = useState([]);
|
|
||||||
const [credentialsCount, setCredentialsCount] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const types = await loadCredentialTypes();
|
|
||||||
setCredentialTypes(types);
|
|
||||||
const match = types.find(type => type.kind === 'ssh') || types[0];
|
|
||||||
setSelectedType(match);
|
|
||||||
} catch (err) {
|
|
||||||
onError(err);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [onError]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
if (!selectedType) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const params = parseQueryString(QS_CONFIG, history.location.search);
|
|
||||||
const { results, count } = await loadCredentials(
|
|
||||||
params,
|
|
||||||
selectedType.id
|
|
||||||
);
|
|
||||||
setCredentials(results);
|
|
||||||
setCredentialsCount(count);
|
|
||||||
} catch (err) {
|
|
||||||
onError(err);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [selectedType, history.location.search, onError]);
|
|
||||||
|
|
||||||
const renderChip = ({ item, removeItem, canDelete }) => (
|
|
||||||
<CredentialChip
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => removeItem(item)}
|
|
||||||
isReadOnly={!canDelete}
|
|
||||||
credential={item}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const isMultiple = selectedType && selectedType.kind === 'vault';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormGroup label={i18n._(t`Credentials`)} fieldId="multiCredential">
|
|
||||||
{tooltip && <FieldTooltip content={tooltip} />}
|
|
||||||
<Lookup
|
|
||||||
id="multiCredential"
|
|
||||||
header={i18n._(t`Credentials`)}
|
|
||||||
value={value}
|
|
||||||
multiple
|
|
||||||
onChange={onChange}
|
|
||||||
qsConfig={QS_CONFIG}
|
|
||||||
renderItemChip={renderChip}
|
|
||||||
renderOptionsList={({ state, dispatch, canDelete }) => {
|
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
{credentialTypes && credentialTypes.length > 0 && (
|
|
||||||
<ToolbarItem css=" display: flex; align-items: center;">
|
|
||||||
<div css="flex: 0 0 25%; margin-right: 32px">
|
|
||||||
{i18n._(t`Selected Category`)}
|
|
||||||
</div>
|
|
||||||
<AnsibleSelect
|
|
||||||
css="flex: 1 1 75%;"
|
|
||||||
id="multiCredentialsLookUp-select"
|
|
||||||
label={i18n._(t`Selected Category`)}
|
|
||||||
data={credentialTypes.map(type => ({
|
|
||||||
key: type.id,
|
|
||||||
value: type.id,
|
|
||||||
label: type.name,
|
|
||||||
isDisabled: false,
|
|
||||||
}))}
|
|
||||||
value={selectedType && selectedType.id}
|
|
||||||
onChange={(e, id) => {
|
|
||||||
setSelectedType(
|
|
||||||
credentialTypes.find(o => o.id === parseInt(id, 10))
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ToolbarItem>
|
|
||||||
)}
|
|
||||||
<OptionsList
|
|
||||||
value={state.selectedItems}
|
|
||||||
options={credentials}
|
|
||||||
optionCount={credentialsCount}
|
|
||||||
searchColumns={[
|
|
||||||
{
|
|
||||||
name: i18n._(t`Name`),
|
|
||||||
key: 'name',
|
|
||||||
isDefault: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18n._(t`Created By (Username)`),
|
|
||||||
key: 'created_by__username',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: i18n._(t`Modified By (Username)`),
|
|
||||||
key: 'modified_by__username',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
sortColumns={[
|
|
||||||
{
|
|
||||||
name: i18n._(t`Name`),
|
|
||||||
key: 'name',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
multiple={isMultiple}
|
|
||||||
header={i18n._(t`Credentials`)}
|
|
||||||
name="credentials"
|
|
||||||
qsConfig={QS_CONFIG}
|
|
||||||
readOnly={!canDelete}
|
|
||||||
selectItem={item => {
|
|
||||||
if (isMultiple) {
|
|
||||||
return dispatch({ type: 'SELECT_ITEM', item });
|
|
||||||
}
|
|
||||||
const selectedItems = state.selectedItems.filter(
|
|
||||||
i => i.kind !== item.kind
|
|
||||||
);
|
|
||||||
selectedItems.push(item);
|
|
||||||
return dispatch({
|
|
||||||
type: 'SET_SELECTED_ITEMS',
|
|
||||||
selectedItems,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
deselectItem={item => dispatch({ type: 'DESELECT_ITEM', item })}
|
|
||||||
renderItemChip={renderChip}
|
|
||||||
/>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
MultiCredentialsLookup.propTypes = {
|
|
||||||
tooltip: PropTypes.string,
|
|
||||||
value: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
id: PropTypes.number,
|
|
||||||
name: PropTypes.string,
|
|
||||||
description: PropTypes.string,
|
|
||||||
kind: PropTypes.string,
|
|
||||||
clound: PropTypes.bool,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onError: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
MultiCredentialsLookup.defaultProps = {
|
|
||||||
tooltip: '',
|
|
||||||
value: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
|
||||||
export default withI18n()(withRouter(MultiCredentialsLookup));
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
import React, { Fragment, useState, useEffect } from 'react';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { ToolbarItem } from '@patternfly/react-core';
|
import { ToolbarItem } from '@patternfly/react-core';
|
||||||
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
import { CredentialsAPI, CredentialTypesAPI } from '@api';
|
||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import CredentialChip from '@components/CredentialChip';
|
import CredentialChip from '@components/CredentialChip';
|
||||||
import VerticalSeperator from '@components/VerticalSeparator';
|
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
import Lookup from './Lookup';
|
import Lookup from './Lookup';
|
||||||
import OptionsList from './shared/OptionsList';
|
import OptionsList from './shared/OptionsList';
|
||||||
@@ -293,8 +93,9 @@ function MultiCredentialsLookup(props) {
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
{credentialTypes && credentialTypes.length > 0 && (
|
{credentialTypes && credentialTypes.length > 0 && (
|
||||||
<ToolbarItem css=" display: flex; align-items: center;">
|
<ToolbarItem css=" display: flex; align-items: center;">
|
||||||
<div css="flex: 0 0 25%;">{i18n._(t`Selected Category`)}</div>
|
<div css="flex: 0 0 25%; margin-right: 32px">
|
||||||
<VerticalSeperator />
|
{i18n._(t`Selected Category`)}
|
||||||
|
</div>
|
||||||
<AnsibleSelect
|
<AnsibleSelect
|
||||||
css="flex: 1 1 75%;"
|
css="flex: 1 1 75%;"
|
||||||
id="multiCredentialsLookUp-select"
|
id="multiCredentialsLookUp-select"
|
||||||
@@ -387,4 +188,3 @@ MultiCredentialsLookup.defaultProps = {
|
|||||||
|
|
||||||
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
export { MultiCredentialsLookup as _MultiCredentialsLookup };
|
||||||
export default withI18n()(withRouter(MultiCredentialsLookup));
|
export default withI18n()(withRouter(MultiCredentialsLookup));
|
||||||
*/
|
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ import { Formik, useField } from 'formik';
|
|||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form, FormGroup } from '@patternfly/react-core';
|
||||||
|
|
||||||
import FormField, { FormSubmitError } from '@components/FormField';
|
import FormField, {
|
||||||
|
FormSubmitError,
|
||||||
|
FieldTooltip,
|
||||||
|
} from '@components/FormField';
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
||||||
import { VariablesField } from '@components/CodeMirrorInput';
|
import { VariablesField } from '@components/CodeMirrorInput';
|
||||||
import { required } from '@util/validators';
|
import { required } from '@util/validators';
|
||||||
@@ -23,7 +26,7 @@ function HostFormFields({ host, i18n }) {
|
|||||||
const hostAddMatch = useRouteMatch('/hosts/add');
|
const hostAddMatch = useRouteMatch('/hosts/add');
|
||||||
const inventoryFieldArr = useField({
|
const inventoryFieldArr = useField({
|
||||||
name: 'inventory',
|
name: 'inventory',
|
||||||
validate: required(i18n._(t`Select aå value for this field`), i18n),
|
validate: required(i18n._(t`Select a value for this field`), i18n),
|
||||||
});
|
});
|
||||||
const inventoryMeta = inventoryFieldArr[1];
|
const inventoryMeta = inventoryFieldArr[1];
|
||||||
const inventoryHelpers = inventoryFieldArr[2];
|
const inventoryHelpers = inventoryFieldArr[2];
|
||||||
@@ -45,6 +48,18 @@ function HostFormFields({ host, i18n }) {
|
|||||||
label={i18n._(t`Description`)}
|
label={i18n._(t`Description`)}
|
||||||
/>
|
/>
|
||||||
{hostAddMatch && (
|
{hostAddMatch && (
|
||||||
|
<FormGroup
|
||||||
|
label={i18n._(t`Inventory`)}
|
||||||
|
isRequired
|
||||||
|
fieldId="inventory-lookup"
|
||||||
|
isValid={!inventoryMeta.touched || !inventoryMeta.error}
|
||||||
|
helperTextInvalid={inventoryMeta.error}
|
||||||
|
>
|
||||||
|
<FieldTooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Select the inventory that this host will belong to.`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<InventoryLookup
|
<InventoryLookup
|
||||||
value={inventory}
|
value={inventory}
|
||||||
onBlur={() => inventoryHelpers.setTouched()}
|
onBlur={() => inventoryHelpers.setTouched()}
|
||||||
@@ -54,13 +69,14 @@ function HostFormFields({ host, i18n }) {
|
|||||||
isValid={!inventoryMeta.touched || !inventoryMeta.error}
|
isValid={!inventoryMeta.touched || !inventoryMeta.error}
|
||||||
helperTextInvalid={inventoryMeta.error}
|
helperTextInvalid={inventoryMeta.error}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
inventoryHelpers.setValuealue(value.id);
|
inventoryHelpers.setValue(value.id);
|
||||||
setInventory(value);
|
setInventory(value);
|
||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
touched={inventoryMeta.touched}
|
touched={inventoryMeta.touched}
|
||||||
error={inventoryMeta.error}
|
error={inventoryMeta.error}
|
||||||
/>
|
/>
|
||||||
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
<FormFullWidthLayout>
|
<FormFullWidthLayout>
|
||||||
<VariablesField
|
<VariablesField
|
||||||
@@ -122,139 +138,3 @@ HostForm.defaultProps = {
|
|||||||
|
|
||||||
export { HostForm as _HostForm };
|
export { HostForm as _HostForm };
|
||||||
export default withI18n()(HostForm);
|
export default withI18n()(HostForm);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
import React, { useState } from 'react';
|
|
||||||
import { func, shape } from 'prop-types';
|
|
||||||
|
|
||||||
import { useRouteMatch } from 'react-router-dom';
|
|
||||||
import { Formik, Field } from 'formik';
|
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
|
|
||||||
import { Form, FormGroup } from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import FormRow from '@components/FormRow';
|
|
||||||
import FormField, {
|
|
||||||
FormSubmitError,
|
|
||||||
FieldTooltip,
|
|
||||||
} from '@components/FormField';
|
|
||||||
import FormActionGroup from '@components/FormActionGroup/FormActionGroup';
|
|
||||||
import { VariablesField } from '@components/CodeMirrorInput';
|
|
||||||
import { required } from '@util/validators';
|
|
||||||
import { InventoryLookup } from '@components/Lookup';
|
|
||||||
|
|
||||||
function HostForm({ handleSubmit, handleCancel, host, submitError, i18n }) {
|
|
||||||
const [inventory, setInventory] = useState(
|
|
||||||
host ? host.summary_fields.inventory : ''
|
|
||||||
);
|
|
||||||
|
|
||||||
const hostAddMatch = useRouteMatch('/hosts/add');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Formik
|
|
||||||
initialValues={{
|
|
||||||
name: host.name,
|
|
||||||
description: host.description,
|
|
||||||
inventory: host.inventory || '',
|
|
||||||
variables: host.variables,
|
|
||||||
}}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
>
|
|
||||||
{formik => (
|
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
|
||||||
<FormRow>
|
|
||||||
<FormField
|
|
||||||
id="host-name"
|
|
||||||
name="name"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Name`)}
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="host-description"
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
/>
|
|
||||||
{hostAddMatch && (
|
|
||||||
<Field
|
|
||||||
name="inventory"
|
|
||||||
validate={required(
|
|
||||||
i18n._(t`Select a value for this field`),
|
|
||||||
i18n
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{({ form }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={i18n._(t`Inventory`)}
|
|
||||||
isRequired
|
|
||||||
fieldId="inventory-lookup"
|
|
||||||
isValid={!form.touched.inventory || !form.errors.inventory}
|
|
||||||
helperTextInvalid={form.errors.inventory}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(
|
|
||||||
t`Select the inventory that this host will belong to.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<InventoryLookup
|
|
||||||
value={inventory}
|
|
||||||
onBlur={() => form.setFieldTouched('inventory')}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('inventory', value.id);
|
|
||||||
setInventory(value);
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
touched={form.touched.inventory}
|
|
||||||
error={form.errors.inventory}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<VariablesField
|
|
||||||
id="host-variables"
|
|
||||||
name="variables"
|
|
||||||
label={i18n._(t`Variables`)}
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
<FormSubmitError error={submitError} />
|
|
||||||
<FormActionGroup
|
|
||||||
onCancel={handleCancel}
|
|
||||||
onSubmit={formik.handleSubmit}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
HostForm.propTypes = {
|
|
||||||
handleSubmit: func.isRequired,
|
|
||||||
handleCancel: func.isRequired,
|
|
||||||
host: shape({}),
|
|
||||||
submitError: shape({}),
|
|
||||||
};
|
|
||||||
|
|
||||||
HostForm.defaultProps = {
|
|
||||||
host: {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
inventory: undefined,
|
|
||||||
variables: '---\n',
|
|
||||||
summary_fields: {
|
|
||||||
inventory: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
submitError: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export { HostForm as _HostForm };
|
|
||||||
export default withI18n()(HostForm);
|
|
||||||
*/
|
|
||||||
|
|||||||
@@ -27,677 +27,19 @@ import {
|
|||||||
FormFullWidthLayout,
|
FormFullWidthLayout,
|
||||||
FormCheckboxLayout,
|
FormCheckboxLayout,
|
||||||
} from '@components/FormLayout';
|
} from '@components/FormLayout';
|
||||||
import CollapsibleSection from '@components/CollapsibleSection';
|
|
||||||
import { required } from '@util/validators';
|
|
||||||
import { JobTemplate } from '@types';
|
|
||||||
import {
|
|
||||||
InventoryLookup,
|
|
||||||
InstanceGroupsLookup,
|
|
||||||
ProjectLookup,
|
|
||||||
MultiCredentialsLookup,
|
|
||||||
} from '@components/Lookup';
|
|
||||||
import { JobTemplatesAPI, ProjectsAPI } from '@api';
|
|
||||||
import LabelSelect from './LabelSelect';
|
|
||||||
import PlaybookSelect from './PlaybookSelect';
|
|
||||||
|
|
||||||
class JobTemplateForm extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
template: JobTemplate,
|
|
||||||
handleCancel: PropTypes.func.isRequired,
|
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
|
||||||
submitError: PropTypes.shape({}),
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
template: {
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
job_type: 'run',
|
|
||||||
inventory: undefined,
|
|
||||||
project: undefined,
|
|
||||||
playbook: '',
|
|
||||||
summary_fields: {
|
|
||||||
inventory: null,
|
|
||||||
labels: { results: [] },
|
|
||||||
project: null,
|
|
||||||
credentials: [],
|
|
||||||
},
|
|
||||||
isNew: true,
|
|
||||||
},
|
|
||||||
submitError: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
hasContentLoading: true,
|
|
||||||
contentError: false,
|
|
||||||
project: props.template.summary_fields.project,
|
|
||||||
inventory: props.template.summary_fields.inventory,
|
|
||||||
allowCallbacks: !!props.template.host_config_key,
|
|
||||||
};
|
|
||||||
this.handleProjectValidation = this.handleProjectValidation.bind(this);
|
|
||||||
this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this);
|
|
||||||
this.handleProjectUpdate = this.handleProjectUpdate.bind(this);
|
|
||||||
this.setContentError = this.setContentError.bind(this);
|
|
||||||
this.fetchProject = this.fetchProject.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { validateField } = this.props;
|
|
||||||
this.setState({ contentError: null, hasContentLoading: true });
|
|
||||||
// TODO: determine when LabelSelect has finished loading labels
|
|
||||||
Promise.all([this.loadRelatedInstanceGroups(), this.fetchProject()]).then(
|
|
||||||
() => {
|
|
||||||
this.setState({ hasContentLoading: false });
|
|
||||||
validateField('project');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetchProject() {
|
|
||||||
const { project } = this.state;
|
|
||||||
if (project && project.id) {
|
|
||||||
try {
|
|
||||||
const { data: projectData } = await ProjectsAPI.readDetail(project.id);
|
|
||||||
this.setState({ project: projectData });
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ contentError: err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadRelatedInstanceGroups() {
|
|
||||||
const { setFieldValue, template } = this.props;
|
|
||||||
if (!template.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const { data } = await JobTemplatesAPI.readInstanceGroups(template.id);
|
|
||||||
setFieldValue('initialInstanceGroups', data.results);
|
|
||||||
setFieldValue('instanceGroups', [...data.results]);
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ contentError: err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleProjectValidation() {
|
|
||||||
const { i18n, touched } = this.props;
|
|
||||||
const { project } = this.state;
|
|
||||||
return () => {
|
|
||||||
if (!project && touched.project) {
|
|
||||||
return i18n._(t`Select a value for this field`);
|
|
||||||
}
|
|
||||||
if (project && project.status === 'never updated') {
|
|
||||||
return i18n._(t`This project needs to be updated`);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleProjectUpdate(project) {
|
|
||||||
const { setFieldValue } = this.props;
|
|
||||||
setFieldValue('project', project.id);
|
|
||||||
setFieldValue('playbook', 0);
|
|
||||||
setFieldValue('scm_branch', '');
|
|
||||||
this.setState({ project });
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentError(contentError) {
|
|
||||||
this.setState({ contentError });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
contentError,
|
|
||||||
hasContentLoading,
|
|
||||||
inventory,
|
|
||||||
project,
|
|
||||||
allowCallbacks,
|
|
||||||
} = this.state;
|
|
||||||
const {
|
|
||||||
handleCancel,
|
|
||||||
handleSubmit,
|
|
||||||
handleBlur,
|
|
||||||
setFieldValue,
|
|
||||||
template,
|
|
||||||
submitError,
|
|
||||||
i18n,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const jobTypeOptions = [
|
|
||||||
{
|
|
||||||
value: '',
|
|
||||||
key: '',
|
|
||||||
label: i18n._(t`Choose a job type`),
|
|
||||||
isDisabled: true,
|
|
||||||
},
|
|
||||||
{ value: 'run', key: 'run', label: i18n._(t`Run`), isDisabled: false },
|
|
||||||
{
|
|
||||||
value: 'check',
|
|
||||||
key: 'check',
|
|
||||||
label: i18n._(t`Check`),
|
|
||||||
isDisabled: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const verbosityOptions = [
|
|
||||||
{ value: '0', key: '0', label: i18n._(t`0 (Normal)`) },
|
|
||||||
{ value: '1', key: '1', label: i18n._(t`1 (Verbose)`) },
|
|
||||||
{ value: '2', key: '2', label: i18n._(t`2 (More Verbose)`) },
|
|
||||||
{ value: '3', key: '3', label: i18n._(t`3 (Debug)`) },
|
|
||||||
{ value: '4', key: '4', label: i18n._(t`4 (Connection Debug)`) },
|
|
||||||
];
|
|
||||||
let callbackUrl;
|
|
||||||
if (template && template.related) {
|
|
||||||
const { origin } = document.location;
|
|
||||||
const path = template.related.callback || `${template.url}callback`;
|
|
||||||
callbackUrl = `${origin}${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasContentLoading) {
|
|
||||||
return <ContentLoading />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contentError) {
|
|
||||||
return <ContentError error={contentError} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AdvancedFieldsWrapper = template.isNew ? CollapsibleSection : 'div';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form autoComplete="off" onSubmit={handleSubmit}>
|
|
||||||
<FormColumnLayout>
|
|
||||||
<FormField
|
|
||||||
id="template-name"
|
|
||||||
name="name"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Name`)}
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
isRequired
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="template-description"
|
|
||||||
name="description"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Description`)}
|
|
||||||
/>
|
|
||||||
<FieldWithPrompt
|
|
||||||
fieldId="template-job-type"
|
|
||||||
isRequired
|
|
||||||
label={i18n._(t`Job Type`)}
|
|
||||||
promptId="template-ask-job-type-on-launch"
|
|
||||||
promptName="ask_job_type_on_launch"
|
|
||||||
tooltip={i18n._(t`For job templates, select run to execute
|
|
||||||
the playbook. Select check to only check playbook syntax,
|
|
||||||
test environment setup, and report problems without
|
|
||||||
executing the playbook.`)}
|
|
||||||
>
|
|
||||||
<Field
|
|
||||||
name="job_type"
|
|
||||||
validate={required(null, i18n)}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
>
|
|
||||||
{({ form, field }) => {
|
|
||||||
const isValid = !form.touched.job_type || !form.errors.job_type;
|
|
||||||
return (
|
|
||||||
<AnsibleSelect
|
|
||||||
isValid={isValid}
|
|
||||||
id="template-job-type"
|
|
||||||
data={jobTypeOptions}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Field>
|
|
||||||
</FieldWithPrompt>
|
|
||||||
<Field
|
|
||||||
name="inventory"
|
|
||||||
validate={required(i18n._(t`Select a value for this field`), i18n)}
|
|
||||||
>
|
|
||||||
{({ form }) => (
|
|
||||||
<InventoryLookup
|
|
||||||
value={inventory}
|
|
||||||
onBlur={() => form.setFieldTouched('inventory')}
|
|
||||||
tooltip={i18n._(t`Select the inventory containing the hosts
|
|
||||||
you want this job to manage.`)}
|
|
||||||
isValid={!form.touched.inventory || !form.errors.inventory}
|
|
||||||
helperTextInvalid={form.errors.inventory}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('inventory', value.id);
|
|
||||||
form.setFieldValue('organizationId', value.organization);
|
|
||||||
this.setState({ inventory: value });
|
|
||||||
}}
|
|
||||||
required
|
|
||||||
touched={form.touched.inventory}
|
|
||||||
error={form.errors.inventory}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="project" validate={this.handleProjectValidation()}>
|
|
||||||
{({ form }) => (
|
|
||||||
<ProjectLookup
|
|
||||||
value={project}
|
|
||||||
onBlur={() => form.setFieldTouched('project')}
|
|
||||||
tooltip={i18n._(t`Select the project containing the playbook
|
|
||||||
you want this job to execute.`)}
|
|
||||||
isValid={!form.touched.project || !form.errors.project}
|
|
||||||
helperTextInvalid={form.errors.project}
|
|
||||||
onChange={this.handleProjectUpdate}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
{project && project.allow_override && (
|
|
||||||
<FormField
|
|
||||||
id="scm_branch"
|
|
||||||
name="scm_branch"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`SCM Branch`)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Field
|
|
||||||
name="playbook"
|
|
||||||
validate={required(i18n._(t`Select a value for this field`), i18n)}
|
|
||||||
onBlur={handleBlur}
|
|
||||||
>
|
|
||||||
{({ field, form }) => {
|
|
||||||
const isValid = !form.touched.playbook || !form.errors.playbook;
|
|
||||||
return (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="template-playbook"
|
|
||||||
helperTextInvalid={form.errors.playbook}
|
|
||||||
isRequired
|
|
||||||
isValid={isValid}
|
|
||||||
label={i18n._(t`Playbook`)}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(
|
|
||||||
t`Select the playbook to be executed by this job.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<PlaybookSelect
|
|
||||||
projectId={form.values.project}
|
|
||||||
isValid={isValid}
|
|
||||||
form={form}
|
|
||||||
field={field}
|
|
||||||
onBlur={() => form.setFieldTouched('playbook')}
|
|
||||||
onError={this.setContentError}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Field>
|
|
||||||
<FormFullWidthLayout>
|
|
||||||
<Field name="labels">
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Optional labels that describe this job template,
|
|
||||||
such as 'dev' or 'test'. Labels can be used to group and filter
|
|
||||||
job templates and completed jobs.`)}
|
|
||||||
/>
|
|
||||||
<LabelSelect
|
|
||||||
value={field.value}
|
|
||||||
onChange={labels => setFieldValue('labels', labels)}
|
|
||||||
onError={this.setContentError}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="credentials" fieldId="template-credentials">
|
|
||||||
{({ field }) => (
|
|
||||||
<MultiCredentialsLookup
|
|
||||||
value={field.value}
|
|
||||||
onChange={newCredentials =>
|
|
||||||
setFieldValue('credentials', newCredentials)
|
|
||||||
}
|
|
||||||
onError={this.setContentError}
|
|
||||||
tooltip={i18n._(
|
|
||||||
t`Select credentials that allow Tower to access the nodes this job will be ran against. You can only select one credential of each type. For machine credentials (SSH), checking "Prompt on launch" without selecting credentials will require you to select a machine credential at run time. If you select credentials and check "Prompt on launch", the selected credential(s) become the defaults that can be updated at run time.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<AdvancedFieldsWrapper label="Advanced">
|
|
||||||
<FormColumnLayout>
|
|
||||||
<FormField
|
|
||||||
id="template-forks"
|
|
||||||
name="forks"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
label={i18n._(t`Forks`)}
|
|
||||||
tooltip={
|
|
||||||
<span>
|
|
||||||
{i18n._(t`The number of parallel or simultaneous
|
|
||||||
processes to use while executing the playbook. An empty value,
|
|
||||||
or a value less than 1 will use the Ansible default which is
|
|
||||||
usually 5. The default number of forks can be overwritten
|
|
||||||
with a change to`)}{' '}
|
|
||||||
<code>ansible.cfg</code>.{' '}
|
|
||||||
{i18n._(t`Refer to the Ansible documentation for details
|
|
||||||
about the configuration file.`)}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="template-limit"
|
|
||||||
name="limit"
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Limit`)}
|
|
||||||
tooltip={i18n._(t`Provide a host pattern to further constrain
|
|
||||||
the list of hosts that will be managed or affected by the
|
|
||||||
playbook. Multiple patterns are allowed. Refer to Ansible
|
|
||||||
documentation for more information and examples on patterns.`)}
|
|
||||||
/>
|
|
||||||
<Field name="verbosity">
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="template-verbosity"
|
|
||||||
label={i18n._(t`Verbosity`)}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Control the level of output ansible will
|
|
||||||
produce as the playbook executes.`)}
|
|
||||||
/>
|
|
||||||
<AnsibleSelect
|
|
||||||
id="template-verbosity"
|
|
||||||
data={verbosityOptions}
|
|
||||||
{...field}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<FormField
|
|
||||||
id="template-job-slicing"
|
|
||||||
name="job_slice_count"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
label={i18n._(t`Job Slicing`)}
|
|
||||||
tooltip={i18n._(t`Divide the work done by this job template
|
|
||||||
into the specified number of job slices, each running the
|
|
||||||
same tasks against a portion of the inventory.`)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
id="template-timeout"
|
|
||||||
name="timeout"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
label={i18n._(t`Timeout`)}
|
|
||||||
tooltip={i18n._(t`The amount of time (in seconds) to run
|
|
||||||
before the task is canceled. Defaults to 0 for no job
|
|
||||||
timeout.`)}
|
|
||||||
/>
|
|
||||||
<Field name="diff_mode">
|
|
||||||
{({ field, form }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="template-show-changes"
|
|
||||||
label={i18n._(t`Show Changes`)}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`If enabled, show the changes made by
|
|
||||||
Ansible tasks, where supported. This is equivalent
|
|
||||||
to Ansible’s --diff mode.`)}
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<Switch
|
|
||||||
id="template-show-changes"
|
|
||||||
label={field.value ? i18n._(t`On`) : i18n._(t`Off`)}
|
|
||||||
isChecked={field.value}
|
|
||||||
onChange={checked =>
|
|
||||||
form.setFieldValue(field.name, checked)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<FormFullWidthLayout>
|
|
||||||
<Field name="instanceGroups">
|
|
||||||
{({ field, form }) => (
|
|
||||||
<InstanceGroupsLookup
|
|
||||||
value={field.value}
|
|
||||||
onChange={value =>
|
|
||||||
form.setFieldValue(field.name, value)
|
|
||||||
}
|
|
||||||
tooltip={i18n._(t`Select the Instance Groups for this Organization
|
|
||||||
to run on.`)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="job_tags">
|
|
||||||
{({ field, form }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={i18n._(t`Job Tags`)}
|
|
||||||
fieldId="template-job-tags"
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Tags are useful when you have a large
|
|
||||||
playbook, and you want to run a specific part of a
|
|
||||||
play or task. Use commas to separate multiple tags.
|
|
||||||
Refer to Ansible Tower documentation for details on
|
|
||||||
the usage of tags.`)}
|
|
||||||
/>
|
|
||||||
<TagMultiSelect
|
|
||||||
value={field.value}
|
|
||||||
onChange={value =>
|
|
||||||
form.setFieldValue(field.name, value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<Field name="skip_tags">
|
|
||||||
{({ field, form }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={i18n._(t`Skip Tags`)}
|
|
||||||
fieldId="template-skip-tags"
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Skip tags are useful when you have a
|
|
||||||
large playbook, and you want to skip specific parts of a
|
|
||||||
play or task. Use commas to separate multiple tags. Refer
|
|
||||||
to Ansible Tower documentation for details on the usage
|
|
||||||
of tags.`)}
|
|
||||||
/>
|
|
||||||
<TagMultiSelect
|
|
||||||
value={field.value}
|
|
||||||
onChange={value =>
|
|
||||||
form.setFieldValue(field.name, value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<FormGroup
|
|
||||||
fieldId="template-option-checkboxes"
|
|
||||||
label={i18n._(t`Options`)}
|
|
||||||
>
|
|
||||||
<FormCheckboxLayout>
|
|
||||||
<CheckboxField
|
|
||||||
id="option-privilege-escalation"
|
|
||||||
name="become_enabled"
|
|
||||||
label={i18n._(t`Privilege Escalation`)}
|
|
||||||
tooltip={i18n._(t`If enabled, run this playbook as an
|
|
||||||
administrator.`)}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
aria-label={i18n._(t`Provisioning Callbacks`)}
|
|
||||||
label={
|
|
||||||
<span>
|
|
||||||
{i18n._(t`Provisioning Callbacks`)}
|
|
||||||
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Enables creation of a provisioning
|
|
||||||
callback URL. Using the URL a host can contact BRAND_NAME
|
|
||||||
and request a configuration update using this job
|
|
||||||
template.`)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
id="option-callbacks"
|
|
||||||
isChecked={allowCallbacks}
|
|
||||||
onChange={checked => {
|
|
||||||
this.setState({ allowCallbacks: checked });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<CheckboxField
|
|
||||||
id="option-concurrent"
|
|
||||||
name="allow_simultaneous"
|
|
||||||
label={i18n._(t`Concurrent Jobs`)}
|
|
||||||
tooltip={i18n._(t`If enabled, simultaneous runs of this job
|
|
||||||
template will be allowed.`)}
|
|
||||||
/>
|
|
||||||
<CheckboxField
|
|
||||||
id="option-fact-cache"
|
|
||||||
name="use_fact_cache"
|
|
||||||
label={i18n._(t`Fact Cache`)}
|
|
||||||
tooltip={i18n._(t`If enabled, use cached facts if available
|
|
||||||
and store discovered facts in the cache.`)}
|
|
||||||
/>
|
|
||||||
</FormCheckboxLayout>
|
|
||||||
</FormGroup>
|
|
||||||
</FormFullWidthLayout>
|
|
||||||
{allowCallbacks && (
|
|
||||||
<>
|
|
||||||
{callbackUrl && (
|
|
||||||
<FormGroup
|
|
||||||
label={i18n._(t`Provisioning Callback URL`)}
|
|
||||||
fieldId="template-callback-url"
|
|
||||||
>
|
|
||||||
<TextInput
|
|
||||||
id="template-callback-url"
|
|
||||||
isDisabled
|
|
||||||
value={callbackUrl}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
<FormField
|
|
||||||
id="template-host-config-key"
|
|
||||||
name="host_config_key"
|
|
||||||
label={i18n._(t`Host Config Key`)}
|
|
||||||
validate={allowCallbacks ? required(null, i18n) : null}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</FormColumnLayout>
|
|
||||||
</AdvancedFieldsWrapper>
|
|
||||||
</FormFullWidthLayout>
|
|
||||||
<FormSubmitError error={submitError} />
|
|
||||||
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
|
||||||
</FormColumnLayout>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormikApp = withFormik({
|
|
||||||
mapPropsToValues(props) {
|
|
||||||
const { template = {} } = props;
|
|
||||||
const {
|
|
||||||
summary_fields = {
|
|
||||||
labels: { results: [] },
|
|
||||||
inventory: { organization: null },
|
|
||||||
},
|
|
||||||
} = template;
|
|
||||||
const hasInventory = summary_fields.inventory
|
|
||||||
? summary_fields.inventory.organization_id
|
|
||||||
: null;
|
|
||||||
return {
|
|
||||||
ask_job_type_on_launch: template.ask_job_type_on_launch || false,
|
|
||||||
name: template.name || '',
|
|
||||||
description: template.description || '',
|
|
||||||
job_type: template.job_type || 'run',
|
|
||||||
inventory: template.inventory || '',
|
|
||||||
project: template.project || '',
|
|
||||||
scm_branch: template.scm_branch || '',
|
|
||||||
playbook: template.playbook || '',
|
|
||||||
labels: summary_fields.labels.results || [],
|
|
||||||
forks: template.forks || 0,
|
|
||||||
limit: template.limit || '',
|
|
||||||
verbosity: template.verbosity || '0',
|
|
||||||
job_slice_count: template.job_slice_count || 1,
|
|
||||||
timeout: template.timeout || 0,
|
|
||||||
diff_mode: template.diff_mode || false,
|
|
||||||
job_tags: template.job_tags || '',
|
|
||||||
skip_tags: template.skip_tags || '',
|
|
||||||
become_enabled: template.become_enabled || false,
|
|
||||||
allow_callbacks: template.allow_callbacks || false,
|
|
||||||
allow_simultaneous: template.allow_simultaneous || false,
|
|
||||||
use_fact_cache: template.use_fact_cache || false,
|
|
||||||
host_config_key: template.host_config_key || '',
|
|
||||||
organizationId: hasInventory,
|
|
||||||
initialInstanceGroups: [],
|
|
||||||
instanceGroups: [],
|
|
||||||
credentials: summary_fields.credentials || [],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
handleSubmit: async (values, { props, setErrors }) => {
|
|
||||||
try {
|
|
||||||
await props.handleSubmit(values);
|
|
||||||
} catch (errors) {
|
|
||||||
setErrors(errors);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})(JobTemplateForm);
|
|
||||||
|
|
||||||
export { JobTemplateForm as _JobTemplateForm };
|
|
||||||
export default withI18n()(withRouter(FormikApp));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
import { withI18n } from '@lingui/react';
|
|
||||||
import { t } from '@lingui/macro';
|
|
||||||
import { withFormik, Field } from 'formik';
|
|
||||||
import {
|
|
||||||
Form,
|
|
||||||
FormGroup,
|
|
||||||
Switch,
|
|
||||||
Checkbox,
|
|
||||||
TextInput,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
import ContentError from '@components/ContentError';
|
|
||||||
import ContentLoading from '@components/ContentLoading';
|
|
||||||
import AnsibleSelect from '@components/AnsibleSelect';
|
|
||||||
import { TagMultiSelect } from '@components/MultiSelect';
|
|
||||||
import FormActionGroup from '@components/FormActionGroup';
|
|
||||||
import FormField, {
|
|
||||||
CheckboxField,
|
|
||||||
FieldTooltip,
|
|
||||||
FormSubmitError,
|
|
||||||
} from '@components/FormField';
|
|
||||||
import FieldWithPrompt from '@components/FieldWithPrompt';
|
|
||||||
import FormRow from '@components/FormRow';
|
|
||||||
import { required } from '@util/validators';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { JobTemplate } from '@types';
|
|
||||||
import {
|
|
||||||
InventoryLookup,
|
|
||||||
InstanceGroupsLookup,
|
|
||||||
ProjectLookup,
|
|
||||||
MultiCredentialsLookup,
|
|
||||||
} from '@components/Lookup';
|
|
||||||
import { VariablesField } from '@components/CodeMirrorInput';
|
import { VariablesField } from '@components/CodeMirrorInput';
|
||||||
|
import { required } from '@util/validators';
|
||||||
|
import { JobTemplate } from '@types';
|
||||||
|
import {
|
||||||
|
InventoryLookup,
|
||||||
|
InstanceGroupsLookup,
|
||||||
|
ProjectLookup,
|
||||||
|
MultiCredentialsLookup,
|
||||||
|
} from '@components/Lookup';
|
||||||
import { JobTemplatesAPI, ProjectsAPI } from '@api';
|
import { JobTemplatesAPI, ProjectsAPI } from '@api';
|
||||||
import LabelSelect from './LabelSelect';
|
import LabelSelect from './LabelSelect';
|
||||||
import PlaybookSelect from './PlaybookSelect';
|
import PlaybookSelect from './PlaybookSelect';
|
||||||
|
|
||||||
const GridFormGroup = styled(FormGroup)`
|
|
||||||
& > label {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&& {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
class JobTemplateForm extends Component {
|
class JobTemplateForm extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
template: JobTemplate,
|
template: JobTemplate,
|
||||||
@@ -862,7 +204,7 @@ class JobTemplateForm extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form autoComplete="off" onSubmit={handleSubmit}>
|
<Form autoComplete="off" onSubmit={handleSubmit}>
|
||||||
<FormRow>
|
<FormColumnLayout>
|
||||||
<FormField
|
<FormField
|
||||||
id="template-name"
|
id="template-name"
|
||||||
name="name"
|
name="name"
|
||||||
@@ -1020,26 +362,7 @@ class JobTemplateForm extends Component {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
</FormRow>
|
<FormFullWidthLayout>
|
||||||
<FormRow>
|
|
||||||
<Field name="labels">
|
|
||||||
{({ field }) => (
|
|
||||||
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Optional labels that describe this job template,
|
|
||||||
such as 'dev' or 'test'. Labels can be used to group and filter
|
|
||||||
job templates and completed jobs.`)}
|
|
||||||
/>
|
|
||||||
<LabelSelect
|
|
||||||
value={field.value}
|
|
||||||
onChange={labels => setFieldValue('labels', labels)}
|
|
||||||
onError={this.setContentError}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormRow>
|
|
||||||
<FormRow>
|
|
||||||
<FieldWithPrompt
|
<FieldWithPrompt
|
||||||
fieldId="template-credentials"
|
fieldId="template-credentials"
|
||||||
label={i18n._(t`Credentials`)}
|
label={i18n._(t`Credentials`)}
|
||||||
@@ -1063,8 +386,29 @@ class JobTemplateForm extends Component {
|
|||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
</FieldWithPrompt>
|
</FieldWithPrompt>
|
||||||
</FormRow>
|
<Field name="labels">
|
||||||
<FormRow>
|
{({ field }) => (
|
||||||
|
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
||||||
|
<FieldTooltip
|
||||||
|
content={i18n._(t`Optional labels that describe this job template,
|
||||||
|
such as 'dev' or 'test'. Labels can be used to group and filter
|
||||||
|
job templates and completed jobs.`)}
|
||||||
|
/>
|
||||||
|
<LabelSelect
|
||||||
|
value={field.value}
|
||||||
|
onChange={labels => setFieldValue('labels', labels)}
|
||||||
|
onError={this.setContentError}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<VariablesField
|
||||||
|
id="template-variables"
|
||||||
|
name="extra_vars"
|
||||||
|
label={i18n._(t`Variables`)}
|
||||||
|
promptId="template-ask-variables-on-launch"
|
||||||
|
/>
|
||||||
|
<FormColumnLayout>
|
||||||
<FormField
|
<FormField
|
||||||
id="template-forks"
|
id="template-forks"
|
||||||
name="forks"
|
name="forks"
|
||||||
@@ -1100,7 +444,9 @@ class JobTemplateForm extends Component {
|
|||||||
<TextInput
|
<TextInput
|
||||||
id="template-limit"
|
id="template-limit"
|
||||||
{...field}
|
{...field}
|
||||||
isValid={!form.touched.job_type || !form.errors.job_type}
|
isValid={
|
||||||
|
!form.touched.job_type || !form.errors.job_type
|
||||||
|
}
|
||||||
onChange={(value, event) => {
|
onChange={(value, event) => {
|
||||||
field.onChange(event);
|
field.onChange(event);
|
||||||
}}
|
}}
|
||||||
@@ -1171,7 +517,7 @@ class JobTemplateForm extends Component {
|
|||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
</FieldWithPrompt>
|
</FieldWithPrompt>
|
||||||
</FormRow>
|
<FormFullWidthLayout>
|
||||||
<Field name="instanceGroups">
|
<Field name="instanceGroups">
|
||||||
{({ field, form }) => (
|
{({ field, form }) => (
|
||||||
<InstanceGroupsLookup
|
<InstanceGroupsLookup
|
||||||
@@ -1197,7 +543,9 @@ class JobTemplateForm extends Component {
|
|||||||
{({ field, form }) => (
|
{({ field, form }) => (
|
||||||
<TagMultiSelect
|
<TagMultiSelect
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={value => form.setFieldValue(field.name, value)}
|
onChange={value =>
|
||||||
|
form.setFieldValue(field.name, value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
@@ -1217,17 +565,18 @@ class JobTemplateForm extends Component {
|
|||||||
{({ field, form }) => (
|
{({ field, form }) => (
|
||||||
<TagMultiSelect
|
<TagMultiSelect
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={value => form.setFieldValue(field.name, value)}
|
onChange={value =>
|
||||||
|
form.setFieldValue(field.name, value)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Field>
|
</Field>
|
||||||
</FieldWithPrompt>
|
</FieldWithPrompt>
|
||||||
<GridFormGroup
|
<FormGroup
|
||||||
fieldId="template-option-checkboxes"
|
fieldId="template-option-checkboxes"
|
||||||
isInline
|
|
||||||
label={i18n._(t`Options`)}
|
label={i18n._(t`Options`)}
|
||||||
css="margin-top: 20px"
|
|
||||||
>
|
>
|
||||||
|
<FormCheckboxLayout>
|
||||||
<CheckboxField
|
<CheckboxField
|
||||||
id="option-privilege-escalation"
|
id="option-privilege-escalation"
|
||||||
name="become_enabled"
|
name="become_enabled"
|
||||||
@@ -1269,14 +618,11 @@ class JobTemplateForm extends Component {
|
|||||||
tooltip={i18n._(t`If enabled, use cached facts if available
|
tooltip={i18n._(t`If enabled, use cached facts if available
|
||||||
and store discovered facts in the cache.`)}
|
and store discovered facts in the cache.`)}
|
||||||
/>
|
/>
|
||||||
</GridFormGroup>
|
</FormCheckboxLayout>
|
||||||
<div
|
</FormGroup>
|
||||||
css={`
|
</FormFullWidthLayout>
|
||||||
${allowCallbacks ? '' : 'display: none'}
|
{allowCallbacks && (
|
||||||
margin-top: 20px;
|
<>
|
||||||
`}
|
|
||||||
>
|
|
||||||
<FormRow>
|
|
||||||
{callbackUrl && (
|
{callbackUrl && (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={i18n._(t`Provisioning Callback URL`)}
|
label={i18n._(t`Provisioning Callback URL`)}
|
||||||
@@ -1295,18 +641,13 @@ class JobTemplateForm extends Component {
|
|||||||
label={i18n._(t`Host Config Key`)}
|
label={i18n._(t`Host Config Key`)}
|
||||||
validate={allowCallbacks ? required(null, i18n) : null}
|
validate={allowCallbacks ? required(null, i18n) : null}
|
||||||
/>
|
/>
|
||||||
</FormRow>
|
</>
|
||||||
</div>
|
)}
|
||||||
<FormRow>
|
</FormColumnLayout>
|
||||||
<VariablesField
|
</FormFullWidthLayout>
|
||||||
id="template-variables"
|
|
||||||
name="extra_vars"
|
|
||||||
label={i18n._(t`Variables`)}
|
|
||||||
promptId="template-ask-variables-on-launch"
|
|
||||||
/>
|
|
||||||
</FormRow>
|
|
||||||
<FormSubmitError error={submitError} />
|
<FormSubmitError error={submitError} />
|
||||||
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
||||||
|
</FormColumnLayout>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1388,4 +729,3 @@ const FormikApp = withFormik({
|
|||||||
|
|
||||||
export { JobTemplateForm as _JobTemplateForm };
|
export { JobTemplateForm as _JobTemplateForm };
|
||||||
export default withI18n()(withRouter(FormikApp));
|
export default withI18n()(withRouter(FormikApp));
|
||||||
*/
|
|
||||||
|
|||||||
Reference in New Issue
Block a user