- {createCredentialHeader}
- <>
- {' '}
-
- >
+ {headerTitle}
+
+ {isBeta ? (
+ <>
+
+ >
+ ) : null}
-
-
- username-password-credential,
- first: os-service-log,
- second: os-application-log,
- }}
- />
-
-
-
-
-
-
+
+ {isCreateCredential ? (
+ <>
+
+
+ username-password-credential,
+ first: os-service-log,
+ second: os-application-log,
+ }}
+ />
+
+
+
+
+
+
+
+ >
+ ) : null}
+
{prompt ? (
<>
diff --git a/src/plugins/credential_management/public/components/common/text_content/text_content.ts b/src/plugins/credential_management/public/components/common/text_content/text_content.ts
index f9543cee8243..1cae69955668 100644
--- a/src/plugins/credential_management/public/components/common/text_content/text_content.ts
+++ b/src/plugins/credential_management/public/components/common/text_content/text_content.ts
@@ -54,3 +54,24 @@ export const confirmButtonOnDeleteComfirmText = i18n.translate(
defaultMessage: 'Delete',
}
);
+
+export const credentialEditPageAuthType = i18n.translate(
+ 'credentialManagement.textContent.credentialEditPageAuthType',
+ {
+ defaultMessage: 'Username & password',
+ }
+);
+
+export const credentialEditPageAuthTitle = i18n.translate(
+ 'credentialManagement.textContent.credentialEditPageAuthTitle',
+ {
+ defaultMessage: 'Authentication Details',
+ }
+);
+
+export const credentialEditPageInfoTitle = i18n.translate(
+ 'credentialManagement.textContent.credentialEditPageInfoTitle',
+ {
+ defaultMessage: 'Saved Credential Information',
+ }
+);
diff --git a/src/plugins/credential_management/public/components/credential_table/credentials_table.tsx b/src/plugins/credential_management/public/components/credential_table/credentials_table.tsx
index 9d51bbe21d64..38c7cb208d25 100644
--- a/src/plugins/credential_management/public/components/credential_table/credentials_table.tsx
+++ b/src/plugins/credential_management/public/components/credential_table/credentials_table.tsx
@@ -22,13 +22,11 @@ import {
EuiBadgeGroup,
EuiPageContent,
EuiTitle,
- EuiSearchBar,
EuiConfirmModal,
EuiLoadingSpinner,
EuiOverlayMask,
EuiGlobalToastList,
EuiGlobalToastListToast,
- Query,
} from '@elastic/eui';
import {
@@ -66,23 +64,13 @@ interface Props extends RouteComponentProps {
export const CredentialsTable = ({ canSave, history }: Props) => {
const [credentials, setCredentials] = React.useState
([]);
const [selectedCredentials, setSelectedCredentials] = React.useState([]);
-
const { setBreadcrumbs } = useOpenSearchDashboards().services;
-
- /* Update breadcrumb*/
- useEffectOnce(() => {
- setBreadcrumbs(getListBreadcrumbs());
- });
-
const [isLoading, setIsLoading] = React.useState(false);
const [isDeleting, setIsDeleting] = React.useState(false);
const [confirmDeleteVisible, setConfirmDeleteVisible] = React.useState(false);
const [toasts, setToasts] = React.useState([]);
- const [searchText, setSearchText] = React.useState('');
- const { savedObjects, uiSettings } = useOpenSearchDashboards<
- CredentialManagementContext
- >().services;
+ const { savedObjects } = useOpenSearchDashboards().services;
const columns = [
{
@@ -140,28 +128,6 @@ export const CredentialsTable = ({ canSave, history }: Props) => {
onSelectionChange,
};
- const renderDeleteButton = () => {
- let deleteButtonMsg = 'Delete';
-
- if (selectedCredentials.length === 1) {
- deleteButtonMsg = `${deleteButtonMsg} ${selectedCredentials.length} Credential`;
- } else if (selectedCredentials.length > 1) {
- deleteButtonMsg = `${deleteButtonMsg} ${selectedCredentials.length} Credentials`;
- }
- return (
- {
- setConfirmDeleteVisible(true);
- }}
- disabled={selectedCredentials.length === 0}
- >
- {deleteButtonMsg}
-
- );
- };
-
const onClickDelete = async () => {
try {
setIsDeleting(true);
@@ -193,25 +159,72 @@ export const CredentialsTable = ({ canSave, history }: Props) => {
}
};
- const deleteButton = renderDeleteButton();
+ const removeToast = (id: string) => {
+ setToasts(toasts.filter((toast) => toast.id !== id));
+ };
+
+ const createButton = canSave ? : <>>;
+
+ const renderDeleteButton = () => {
+ let deleteButtonMsg = 'Delete';
+
+ if (selectedCredentials.length === 1) {
+ deleteButtonMsg = `${deleteButtonMsg} ${selectedCredentials.length} Credential`;
+ } else if (selectedCredentials.length > 1) {
+ deleteButtonMsg = `${deleteButtonMsg} ${selectedCredentials.length} Credentials`;
+ }
+ return (
+ {
+ setConfirmDeleteVisible(true);
+ }}
+ disabled={selectedCredentials.length === 0}
+ >
+ {deleteButtonMsg}
+
+ );
+ };
+
+ /* create a button to the right of search bar*/
+ const renderToolsRight = () => {
+ return (
+
+ {renderDeleteButton()}
+
+ );
+ };
- React.useEffect(() => {
+ const search = {
+ toolsRight: renderToolsRight(),
+ box: {
+ incremental: true,
+ schema: {
+ fields: { title: { type: 'string' } },
+ },
+ },
+ };
+
+ /* Update breadcrumb*/
+ useEffectOnce(() => {
+ setBreadcrumbs(getListBreadcrumbs());
+ });
+
+ /* fetch credential*/
+ useEffectOnce(() => {
(async () => {
setIsLoading(true);
const fetchedCredentials: CredentialsTableItem[] = await getCredentials(savedObjects.client);
- const fetchedCredentialsResults = fetchedCredentials.filter((row) => {
- return row.title.includes(searchText);
- });
+ setCredentials(fetchedCredentials);
- setCredentials(fetchedCredentialsResults);
setIsLoading(false);
})();
- }, [history.push, credentials.length, uiSettings, savedObjects.client, searchText]);
-
- const createButton = canSave ? : <>>;
+ });
- const tableRenderDeleteModal = () => {
+ /* render delete modal*/
+ const renderTableDeleteModal = () => {
return confirmDeleteVisible ? (
{
) : null;
};
- const onSearchChange = ({
- query,
- }: {
- query: Query | null;
- error: { message: string } | null;
- }) => {
- setSearchText(query!.text);
- };
-
- const removeToast = (id: string) => {
- setToasts(toasts.filter((toast) => toast.id !== id));
- };
-
const renderContent = () => {
return (
@@ -272,17 +272,7 @@ export const CredentialsTable = ({ canSave, history }: Props) => {
{createButton}
- onSearchChange(e)}
- toolsRight={[
-
- {deleteButton}
- ,
- ]}
- />
-
- {tableRenderDeleteModal()}
+ {renderTableDeleteModal()}
@@ -295,6 +285,7 @@ export const CredentialsTable = ({ canSave, history }: Props) => {
columns={columns}
pagination={pagination}
sorting={sorting}
+ search={search}
loading={isLoading}
/>
>
diff --git a/src/plugins/credential_management/public/components/edit_credential_wizard/components/edit_credential.tsx b/src/plugins/credential_management/public/components/edit_credential_wizard/components/edit_credential.tsx
index 4b036d7d2e87..6a75e6785f3e 100644
--- a/src/plugins/credential_management/public/components/edit_credential_wizard/components/edit_credential.tsx
+++ b/src/plugins/credential_management/public/components/edit_credential_wizard/components/edit_credential.tsx
@@ -14,8 +14,6 @@ import {
EuiDescribedFormGroup,
EuiFormRow,
EuiFieldText,
- EuiSelect,
- EuiLink,
EuiButton,
EuiPageContent,
EuiFieldPassword,
@@ -25,6 +23,16 @@ import {
EuiConfirmModal,
EuiLoadingSpinner,
EuiOverlayMask,
+ EuiFlexGroup,
+ EuiText,
+ EuiSpacer,
+ EuiBottomBar,
+ EuiButtonEmpty,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
} from '@elastic/eui';
import { DocLinksStart } from 'src/core/public';
import {
@@ -34,20 +42,22 @@ import {
import { getCreateBreadcrumbs } from '../../breadcrumbs';
import { CredentialManagmentContextValue } from '../../../types';
-// TODO: Add Header https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2051
import { context as contextType } from '../../../../../opensearch_dashboards_react/public';
import { EditCredentialItem } from '../../types';
import { LocalizedContent } from '../../common/text_content';
+import { Header } from '../../common/components/header';
interface EditCredentialState {
credentialName: string;
credentialMaterialsType: string;
+ credentialDescription: string;
username?: string;
password?: string;
dual: boolean;
toasts: EuiGlobalToastListToast[];
docLinks: DocLinksStart;
- isVisible: boolean;
+ isDeleteModalVisible: boolean;
+ isUpdateModalVisible: boolean;
isLoading: boolean;
}
@@ -63,76 +73,42 @@ export class EditCredentialComponent extends React.Component<
public readonly context!: CredentialManagmentContextValue;
constructor(props: EditCredentialProps, context: CredentialManagmentContextValue) {
super(props, context);
-
context.services.setBreadcrumbs(getCreateBreadcrumbs());
this.state = {
credentialName: props.credential.title,
credentialMaterialsType: CredentialMaterialsType.UsernamePasswordType,
- username: undefined,
+ credentialDescription: props.credential.description || '',
+ username: props.credential.username || '',
password: undefined,
dual: true,
toasts: [],
docLinks: context.services.docLinks,
- isVisible: false,
+ isDeleteModalVisible: false,
+ isUpdateModalVisible: false,
isLoading: false,
};
}
-
- confirmDelete = async () => {
- const { savedObjects } = this.context.services;
- this.setState({ isLoading: true });
- try {
- await savedObjects.client.delete(CREDENTIAL_SAVED_OBJECT_TYPE, this.props.credential.id);
- this.props.history.push('');
- } catch (e) {
- const deleteCredentialFailMsg = (
-
- );
- this.setState((prevState) => ({
- toasts: prevState.toasts.concat([
- {
- title: deleteCredentialFailMsg,
- id: deleteCredentialFailMsg.props.id,
- color: 'warning',
- iconType: 'alert',
- },
- ]),
- }));
- }
- this.setState({ isLoading: false });
- };
-
- delelteButtonRender() {
+ /* Render methods */
+ renderDelelteButton() {
return (
<>
-
-
-
-
-
-
-
+
+
+
+
+
- {this.state.isVisible ? (
+ {this.state.isDeleteModalVisible ? (
{
- this.setState({ isVisible: false });
+ this.setState({ isDeleteModalVisible: false });
}}
onConfirm={this.confirmDelete}
cancelButtonText={LocalizedContent.cancelButtonOnDeleteCancelText}
@@ -147,93 +123,155 @@ export class EditCredentialComponent extends React.Component<
>
);
}
+ renderHeader() {
+ return ;
+ }
+
+ renderUpdatePasswordModal() {
+ const closeModal = () => this.setState({ isUpdateModalVisible: false });
+ return (
+ <>
+ Update Password
+
+ {this.state.isUpdateModalVisible ? (
+
+
+
+ Update password
+
+
+
+
+ this.setState({ password: e.target.value })}
+ />
+
+
+
+ Cancel
+
+ Update
+
+
+
+ ) : null}
+ >
+ );
+ }
- renderContent() {
- const options = [
- {
- value: CredentialMaterialsType.UsernamePasswordType,
- text: 'Username and Password Credential',
- },
- ];
+ renderContent = () => {
+ const header = this.renderHeader();
+ const deleteButton = this.renderDelelteButton();
return (
-
- {this.delelteButtonRender()}
-
-
+
+
+
+ {header}
+ {deleteButton}
+
+
+
+
+
+
+
+ {LocalizedContent.credentialEditPageInfoTitle}
+
+
+
+
+
Credential Name}
- description={The name of credential that you want to create
}
+ title={Credential Details
}
+ description={
+
+ The credential information is used for reference in tables and when adding to a data
+ source connection
+
+ }
>
-
+
this.setState({ credentialName: e.target.value })}
/>
+
+ this.setState({ credentialDescription: e.target.value })}
+ />
+
+
+
+
+
+
+
+ {LocalizedContent.credentialEditPageAuthTitle}
+
+
+
+
+
Credential Type}
+ title={Authentication Details
}
description={
-
-
- The type of credential that you want to create{' '}
-
- Credential Types Supported
-
-
-
- -
- For username_password_credential type: this type can be used for{' '}
- credentials in format of username, password.{' '}
-
- - Ex: OpenSearch basic auth
-
-
- -
- For aws_iam_credential type: this type can only be used for aws iam
- credential, with aws_access_key_id, aws_secret_access_key, and region (optional)
-
-
-
+ Modify these to update the authentication type and associated details
}
>
-
- this.setState({ credentialMaterialsType: e.target.value })}
- options={options}
- />
+
+ {LocalizedContent.credentialEditPageAuthType}
-
+
+
+
this.setState({ username: e.target.value })}
/>
- this.setState({ password: e.target.value })}
- />
+
+
+ *************
+
+ {this.renderUpdatePasswordModal()}
+
-
- Update
-
-
-
- );
- }
- removeToast = (id: string) => {
- this.setState((prevState) => ({
- toasts: prevState.toasts.filter((toast) => toast.id !== id),
- }));
+
+
+
+
+ Cancel changes
+
+
+
+
+ Save changes
+
+
+
+
+
+
+ );
};
render() {
@@ -256,31 +294,85 @@ export class EditCredentialComponent extends React.Component<
>
);
}
+ /* Events */
+ removeToast = (id: string) => {
+ this.setState((prevState) => ({
+ toasts: prevState.toasts.filter((toast) => toast.id !== id),
+ }));
+ };
removeCredential = async () => {
- this.setState({ isVisible: true });
+ this.setState({ isDeleteModalVisible: true });
+ };
+
+ updateCredentialPassword = async () => {
+ this.setState({ isUpdateModalVisible: true });
};
- updateCredential = async () => {
+ updateCredentialInformation = async () => {
const { savedObjects } = this.context.services;
this.setState({ isLoading: true });
+
try {
- await savedObjects.client.update('credential', this.props.credential.id, {
+ const credentialAttributes = {
title: this.state.credentialName,
+ description: this.state.credentialDescription,
credentialMaterials: {
credentialMaterialsType: this.state.credentialMaterialsType,
credentialMaterialsContent: {
username: this.state.username,
+ },
+ },
+ };
+ await savedObjects.client.update(
+ CREDENTIAL_SAVED_OBJECT_TYPE,
+ this.props.credential.id,
+ credentialAttributes
+ );
+ this.props.history.push('');
+ } catch (e) {
+ const editCredentialFailMsg = (
+
+ );
+ this.setState((prevState) => ({
+ toasts: prevState.toasts.concat([
+ {
+ title: editCredentialFailMsg,
+ id: editCredentialFailMsg.props.id,
+ color: 'warning',
+ iconType: 'alert',
+ },
+ ]),
+ }));
+ }
+ this.setState({ isLoading: false });
+ };
+
+ updateCredentialPasswordField = async () => {
+ const { savedObjects } = this.context.services;
+ this.setState({ isLoading: true, isUpdateModalVisible: false });
+ try {
+ const credentialAttributes = {
+ credentialMaterials: {
+ credentialMaterialsContent: {
password: this.state.password,
},
},
- });
+ };
+ await savedObjects.client.update(
+ CREDENTIAL_SAVED_OBJECT_TYPE,
+ this.props.credential.id,
+ credentialAttributes
+ );
this.props.history.push('');
} catch (e) {
const editCredentialFailMsg = (
);
this.setState((prevState) => ({
@@ -296,6 +388,33 @@ export class EditCredentialComponent extends React.Component<
}
this.setState({ isLoading: false });
};
+
+ confirmDelete = async () => {
+ const { savedObjects } = this.context.services;
+ this.setState({ isLoading: true });
+ try {
+ await savedObjects.client.delete(CREDENTIAL_SAVED_OBJECT_TYPE, this.props.credential.id);
+ this.props.history.push('');
+ } catch (e) {
+ const deleteCredentialFailMsg = (
+
+ );
+ this.setState((prevState) => ({
+ toasts: prevState.toasts.concat([
+ {
+ title: deleteCredentialFailMsg,
+ id: deleteCredentialFailMsg.props.id,
+ color: 'warning',
+ iconType: 'alert',
+ },
+ ]),
+ }));
+ }
+ this.setState({ isLoading: false });
+ };
}
export const EditCredential = withRouter(EditCredentialComponent);
diff --git a/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts b/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts
index 4e116ce61023..b9edbb5221ef 100644
--- a/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts
+++ b/src/plugins/data_source/server/saved_objects/credential_saved_objects_client_wrapper.ts
@@ -138,15 +138,27 @@ export class CredentialSavedObjectsClientWrapper {
}
private async validateAndEncryptPartialAttributes(attributes: T) {
- this.validateCredentialMaterials(attributes.credentialMaterials);
+ const { credentialMaterials } = attributes;
+ const { credentialMaterialsContent } = credentialMaterials;
+
+ if ('password' in credentialMaterialsContent) {
+ this.validatePassword(credentialMaterialsContent.password);
+ return {
+ ...attributes,
+ credentialMaterials: await this.encryptUsernamePasswordTypedCredentialMaterials(
+ credentialMaterials
+ ),
+ };
+ } else {
+ this.validateAttributes(attributes);
+ }
return await this.encryptCredentialMaterials(attributes);
}
private validateAttributes(attributes: T) {
const { title, credentialMaterials } = attributes;
-
- if (title === undefined) {
+ if (!title) {
throw SavedObjectsErrorHelpers.createBadRequestError('attribute "title" required');
}
@@ -175,33 +187,24 @@ export class CredentialSavedObjectsClientWrapper {
}
}
- private validateUsernamePasswordTypedContent(credentialMaterialsContent: T) {
- const { username, password } = credentialMaterialsContent;
-
- if (username === undefined) {
- throw SavedObjectsErrorHelpers.createBadRequestError('attribute "username" required');
- }
-
- if (password === undefined) {
- throw SavedObjectsErrorHelpers.createBadRequestError('attribute "password" required');
- }
-
- return;
- }
-
private async encryptCredentialMaterials(attributes: T) {
const { credentialMaterials } = attributes;
const { credentialMaterialsType, credentialMaterialsContent } = credentialMaterials;
+ const { username } = credentialMaterialsContent;
switch (credentialMaterialsType) {
case CredentialMaterialsType.UsernamePasswordType:
- this.validateUsernamePasswordTypedContent(credentialMaterialsContent);
+ this.validateUsername(username);
+
return {
...attributes,
- credentialMaterials: await this.encryptUsernamePasswordTypedCredentialMaterials(
- credentialMaterials
- ),
+ credentialMaterials: {
+ credentialMaterialsType,
+ credentialMaterialsContent: {
+ username,
+ },
+ },
};
default:
throw SavedObjectsErrorHelpers.createBadRequestError(
@@ -210,6 +213,20 @@ export class CredentialSavedObjectsClientWrapper {
}
}
+ private validateUsername(username: T) {
+ if (!username) {
+ throw SavedObjectsErrorHelpers.createBadRequestError('attribute "username" required');
+ }
+ return;
+ }
+
+ private validatePassword(password: T) {
+ if (!password) {
+ throw SavedObjectsErrorHelpers.createBadRequestError('attribute "password" required');
+ }
+ return;
+ }
+
private async encryptUsernamePasswordTypedCredentialMaterials(
credentialMaterials: T
) {