selectedCredentialHelper.setValue(credential)}
+ onDeselect={() => selectedCredentialHelper.setValue(null)}
+ isRadio
+ />
+ )}
+ renderToolbar={props => }
+ showPageSizeOptions={false}
+ toolbarSearchColumns={[
+ {
+ 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',
+ },
+ ]}
+ toolbarSortColumns={[
+ {
+ name: i18n._(t`Name`),
+ key: 'name',
+ },
+ ]}
+ />
+ );
+}
+
+export default withI18n()(CredentialsStep);
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx
new file mode 100644
index 0000000000..2114904ef2
--- /dev/null
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/MetadataStep.jsx
@@ -0,0 +1,159 @@
+import React, { useCallback, useEffect } from 'react';
+import { withI18n } from '@lingui/react';
+import { t } from '@lingui/macro';
+import { useField, useFormikContext } from 'formik';
+import styled from 'styled-components';
+import { Button, Form, FormGroup, Tooltip } from '@patternfly/react-core';
+import { QuestionCircleIcon as PFQuestionCircleIcon } from '@patternfly/react-icons';
+import { CredentialTypesAPI } from '../../../../../api';
+import AnsibleSelect from '../../../../../components/AnsibleSelect';
+import ContentError from '../../../../../components/ContentError';
+import ContentLoading from '../../../../../components/ContentLoading';
+import FormField from '../../../../../components/FormField';
+import { FormFullWidthLayout } from '../../../../../components/FormLayout';
+import useRequest from '../../../../../util/useRequest';
+import { required } from '../../../../../util/validators';
+
+const QuestionCircleIcon = styled(PFQuestionCircleIcon)`
+ margin-left: 10px;
+`;
+
+const TestButton = styled(Button)`
+ margin-top: 20px;
+`;
+
+function MetadataStep({ i18n }) {
+ const form = useFormikContext();
+ const [selectedCredential] = useField('credential');
+ const [inputValues] = useField('inputs');
+
+ const {
+ result: fields,
+ error,
+ isLoading,
+ request: fetchMetadataOptions,
+ } = useRequest(
+ useCallback(async () => {
+ const {
+ data: {
+ inputs: { required, metadata },
+ },
+ } = await CredentialTypesAPI.readDetail(
+ selectedCredential.value.credential_type ||
+ selectedCredential.value.credential_type_id
+ );
+ metadata.forEach(field => {
+ if (inputValues.value[field.id]) {
+ form.initialValues.inputs[field.id] = inputValues.value[field.id];
+ } else {
+ if (field.type === 'string' && field.choices) {
+ form.initialValues.inputs[field.id] =
+ field.default || field.choices[0];
+ } else {
+ form.initialValues.inputs[field.id] = '';
+ }
+ }
+ if (required && required.includes(field.id)) {
+ field.required = true;
+ }
+ });
+ return metadata;
+ /* eslint-disable-next-line react-hooks/exhaustive-deps */
+ }, []),
+ []
+ );
+
+ useEffect(() => {
+ fetchMetadataOptions();
+ }, [fetchMetadataOptions]);
+
+ const testMetadata = () => {
+ alert('not implemented');
+ };
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (error) {
+ return ;
+ }
+
+ return (
+ <>
+ {fields.length > 0 && (
+
+ )}
+
+ testMetadata()}
+ >
+ {i18n._(t`Test`)}
+
+
+ >
+ );
+}
+
+export default withI18n()(MetadataStep);
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js
new file mode 100644
index 0000000000..467b3f3936
--- /dev/null
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginPrompt/index.js
@@ -0,0 +1,3 @@
+export { default as CredentialPluginPrompt } from './CredentialPluginPrompt';
+export { default as CredentialsStep } from './CredentialsStep';
+export { default as MetadataStep } from './MetadataStep';
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.jsx
new file mode 100644
index 0000000000..2ea3ca84d0
--- /dev/null
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/CredentialPluginSelected.jsx
@@ -0,0 +1,54 @@
+import React from 'react';
+import { withI18n } from '@lingui/react';
+import { t, Trans } from '@lingui/macro';
+import styled from 'styled-components';
+import { Button, ButtonVariant, Tooltip } from '@patternfly/react-core';
+import { KeyIcon } from '@patternfly/react-icons';
+import CredentialChip from '../../../../components/CredentialChip';
+
+const SelectedCredential = styled.div`
+ display: flex;
+ justify-content: space-between;
+ margin-top: 10px;
+ background-color: white;
+ border-bottom-color: var(--pf-global--BorderColor--200);
+`;
+
+const SpacedCredentialChip = styled(CredentialChip)`
+ margin: 5px 8px;
+`;
+
+function CredentialPluginSelected({
+ i18n,
+ credential,
+ onEditPlugin,
+ onClearPlugin,
+}) {
+ return (
+ <>
+
+
+ This field will be retrieved from an external secret management system
+ using the following credential:
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default withI18n()(CredentialPluginSelected);
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/index.js b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/index.js
new file mode 100644
index 0000000000..033586567f
--- /dev/null
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialPlugins/index.js
@@ -0,0 +1,2 @@
+export { default as CredentialPluginSelected } from './CredentialPluginSelected';
+export { default as CredentialPluginField } from './CredentialPluginField';
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/GoogleComputeEngineSubForm.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/GoogleComputeEngineSubForm.jsx
index 89584b956c..2622106afb 100644
--- a/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/GoogleComputeEngineSubForm.jsx
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/GoogleComputeEngineSubForm.jsx
@@ -2,13 +2,18 @@ import React, { useState } from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { useField } from 'formik';
-import { FileUpload, FormGroup } from '@patternfly/react-core';
-import FormField from '../../../../components/FormField';
+import {
+ FileUpload,
+ FormGroup,
+ TextArea,
+ TextInput,
+} from '@patternfly/react-core';
import {
FormColumnLayout,
FormFullWidthLayout,
} from '../../../../components/FormLayout';
import { required } from '../../../../util/validators';
+import { CredentialPluginField } from '../CredentialPlugins';
const GoogleComputeEngineSubForm = ({ i18n }) => {
const [fileError, setFileError] = useState(null);
@@ -91,30 +96,38 @@ const GoogleComputeEngineSubForm = ({ i18n }) => {
}}
/>
-
-
+
+
+
+ >
+
+
-
+ >
+
+
);
diff --git a/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/SharedFields.jsx b/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/SharedFields.jsx
index 51a95292bd..d2d4ee20fc 100644
--- a/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/SharedFields.jsx
+++ b/awx/ui_next/src/screens/Credential/shared/CredentialSubForms/SharedFields.jsx
@@ -1,39 +1,50 @@
import React from 'react';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import FormField, { PasswordField } from '../../../../components/FormField';
+import { TextArea, TextInput } from '@patternfly/react-core';
+import { CredentialPluginField } from '../CredentialPlugins';
+import { PasswordInput } from '../../../../components/FormField';
export const UsernameFormField = withI18n()(({ i18n }) => (
-
+ >
+
+
));
export const PasswordFormField = withI18n()(({ i18n }) => (
-
+ >
+
+
));
export const SSHKeyDataField = withI18n()(({ i18n }) => (
-
+ >
+
+
));
export const SSHKeyUnlockField = withI18n()(({ i18n }) => (
-
+ >
+
+
));