diff --git a/app/client/src/components/formControls/BaseControl.tsx b/app/client/src/components/formControls/BaseControl.tsx index d0f1f7bf82..d23c417c26 100644 --- a/app/client/src/components/formControls/BaseControl.tsx +++ b/app/client/src/components/formControls/BaseControl.tsx @@ -83,6 +83,7 @@ export interface ControlData { disabled?: boolean; staticDependencyPathList?: string[]; validator?: (value: string) => { isValid: boolean; message: string }; + isSecretExistsPath?: string; } export type FormConfigType = Omit & { configProperty?: string; diff --git a/app/client/src/components/formControls/InputTextControl.tsx b/app/client/src/components/formControls/InputTextControl.tsx index d16b6cb74b..af0e0d3b0d 100644 --- a/app/client/src/components/formControls/InputTextControl.tsx +++ b/app/client/src/components/formControls/InputTextControl.tsx @@ -2,6 +2,7 @@ import React from "react"; import BaseControl, { ControlProps } from "./BaseControl"; import { ControlType } from "constants/PropertyControlConstants"; import { TextInput } from "design-system"; +import { AppState } from "@appsmith/reducers"; import { Colors } from "constants/Colors"; import styled from "styled-components"; import { InputType } from "components/constants"; @@ -9,7 +10,9 @@ import { Field, WrappedFieldMetaProps, WrappedFieldInputProps, + formValueSelector, } from "redux-form"; +import { connect } from "react-redux"; export const StyledInfo = styled.span` font-weight: normal; @@ -19,42 +22,34 @@ export const StyledInfo = styled.span` margin-left: 1px; `; -export function InputText(props: { - label: string; - value: string; - isValid: boolean; - subtitle?: string; - validationMessage?: string; - placeholder?: string; - dataType?: string; - isRequired?: boolean; - name: string; - encrypted?: boolean; - disabled?: boolean; - customStyles?: Record; - validator?: (value: string) => { isValid: boolean; message: string }; -}) { - const { dataType, disabled, name, placeholder } = props; +const FieldWrapper = styled.div` + width: 35vw; + position: relative; +`; - return ( -
- -
- ); -} +const SecretDisplayIndicator = styled.input` + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + padding: 0px var(--ads-spaces-6); + z-index: 1; + cursor: text; + border: none; + background: none; +`; + +const PASSWORD_EXISTS_INDICATOR = "······"; function renderComponent( props: { placeholder: string; dataType?: InputType; disabled?: boolean; + reference: any; validator?: (value: string) => { isValid: boolean; message: string }; } & { meta: Partial; @@ -68,6 +63,7 @@ function renderComponent( name={props.input?.name} onChange={props.input.onChange} placeholder={props.placeholder} + ref={props.reference} value={props.input.value} {...props.input} validator={props.validator} @@ -75,10 +71,59 @@ function renderComponent( /> ); } + class InputTextControl extends BaseControl { + fieldRef: any; + + state = { + secretDisplayVisible: false, + }; + + constructor(props: InputControlProps) { + super(props); + this.fieldRef = React.createRef(); + } + + onClickSecretDisplayIndicator = () => { + if (!this.state.secretDisplayVisible) return; + this.setState({ + secretDisplayVisible: false, + }); + + if (this.fieldRef.current) this.fieldRef.current?.focus(); + }; + + checkForSecretOverlayIndicator = () => { + return ( + this.props.dataType === "PASSWORD" && + this.props.isSecretExistsPath && + this.props.isSecretExistsData + ); + }; + + onBlur = () => { + if ( + this.checkForSecretOverlayIndicator() && + this.fieldRef.current?.value?.length === 0 + ) { + this.setState({ + secretDisplayVisible: true, + }); + } + }; + + componentDidMount() { + if (this.checkForSecretOverlayIndicator()) { + this.setState({ + secretDisplayVisible: true, + }); + } + } + render() { const { configProperty, + customStyles, dataType, disabled, encrypted, @@ -92,19 +137,34 @@ class InputTextControl extends BaseControl { } = this.props; return ( - + + {this.state.secretDisplayVisible && ( + + )} + + ); } @@ -145,6 +205,18 @@ export interface InputControlProps extends ControlProps { encrypted?: boolean; disabled?: boolean; validator?: (value: string) => { isValid: boolean; message: string }; + isSecretExistsData?: boolean; } -export default InputTextControl; +const mapStateToProps = (state: AppState, props: InputControlProps) => { + const valueSelector = formValueSelector(props.formName); + let isSecretExistsData; + if (props.isSecretExistsPath) { + isSecretExistsData = valueSelector(state, props.isSecretExistsPath); + } + return { + isSecretExistsData, + }; +}; + +export default connect(mapStateToProps)(InputTextControl); diff --git a/app/client/src/entities/Datasource/RestAPIForm.ts b/app/client/src/entities/Datasource/RestAPIForm.ts index bc875bb082..ee7cec626d 100644 --- a/app/client/src/entities/Datasource/RestAPIForm.ts +++ b/app/client/src/entities/Datasource/RestAPIForm.ts @@ -91,6 +91,7 @@ export interface Basic { authenticationType: AuthType.basic; username: string; password: string; + secretExists?: Record; } export interface ApiKey { diff --git a/app/client/src/entities/Datasource/index.ts b/app/client/src/entities/Datasource/index.ts index 95bbea170c..b8bce27d78 100644 --- a/app/client/src/entities/Datasource/index.ts +++ b/app/client/src/entities/Datasource/index.ts @@ -23,6 +23,7 @@ export interface DatasourceAuthentication { bearerToken?: string; authenticationStatus?: string; authenticationType?: string; + secretExists?: Record; } export interface DatasourceColumns { diff --git a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx index 81f63fe245..a035118e0b 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/RestAPIDatasourceForm.tsx @@ -452,15 +452,15 @@ class DatasourceRestAPIEditor extends React.Component< return (
- {this.renderInputTextControlViaFormControl( - "url", - "URL", - "https://example.com", - "TEXT", - false, - true, - this.urlValidator, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "url", + label: "URL", + placeholderText: "https://example.com", + dataType: "TEXT", + encrypted: false, + isRequired: true, + fieldValidator: this.urlValidator, + })} {formData.isSendSessionEnabled && ( - {this.renderInputTextControlViaFormControl( - "sessionSignatureKey", - "Session Details Signature Key", - "", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "sessionSignatureKey", + label: "Session Details Signature Key", + placeholderText: "", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} )} @@ -591,24 +591,24 @@ class DatasourceRestAPIEditor extends React.Component< return ( <> - {this.renderInputTextControlViaFormControl( - "authentication.label", - "Key", - "api_key", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.label", + label: "Key", + placeholderText: "api_key", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} - {this.renderInputTextControlViaFormControl( - "authentication.value", - "Value", - "value", - "TEXT", - true, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.value", + label: "Value", + placeholderText: "value", + dataType: "TEXT", + encrypted: true, + isRequired: false, + })} {this.renderDropdownControlViaFormControl( @@ -633,14 +633,14 @@ class DatasourceRestAPIEditor extends React.Component< - {this.renderInputTextControlViaFormControl( - "authentication.headerPrefix", - "Header Prefix", - "eg: Bearer ", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.headerPrefix", + label: "Header Prefix", + placeholderText: "eg: Bearer ", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} )} @@ -650,14 +650,14 @@ class DatasourceRestAPIEditor extends React.Component< renderBearerToken = () => { return ( - {this.renderInputTextControlViaFormControl( - "authentication.bearerToken", - "Bearer Token", - "Bearer Token", - "TEXT", - true, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.bearerToken", + label: "Bearer Token", + placeholderText: "Bearer Token", + dataType: "TEXT", + encrypted: true, + isRequired: false, + })} ); }; @@ -666,24 +666,25 @@ class DatasourceRestAPIEditor extends React.Component< return ( <> - {this.renderInputTextControlViaFormControl( - "authentication.username", - "Username", - "Username", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.username", + label: "Username", + placeholderText: "Username", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} - {this.renderInputTextControlViaFormControl( - "authentication.password", - "Password", - "Password", - "PASSWORD", - true, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.password", + label: "Password", + placeholderText: "Password", + dataType: "PASSWORD", + encrypted: true, + isRequired: false, + isSecretExistsPath: "authentication.secretExists.password", + })} ); @@ -757,60 +758,61 @@ class DatasourceRestAPIEditor extends React.Component< - {this.renderInputTextControlViaFormControl( - "authentication.headerPrefix", - "Header Prefix", - "eg: Bearer ", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.headerPrefix", + label: "Header Prefix", + placeholderText: "eg: Bearer ", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} )} - {this.renderInputTextControlViaFormControl( - "authentication.accessTokenUrl", - "Access Token URL", - "https://example.com/login/oauth/access_token", - "TEXT", - false, - false, - this.urlValidator, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.accessTokenUrl", + label: "Access Token URL", + placeholderText: "https://example.com/login/oauth/access_token", + dataType: "TEXT", + encrypted: false, + isRequired: false, + fieldValidator: this.urlValidator, + })} - {this.renderInputTextControlViaFormControl( - "authentication.clientId", - "Client ID", - "Client ID", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.clientId", + label: "Client ID", + placeholderText: "Client ID", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} - {this.renderInputTextControlViaFormControl( - "authentication.clientSecret", - "Client Secret", - "Client Secret", - "PASSWORD", - true, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.clientSecret", + label: "Client Secret", + placeholderText: "Client Secret", + dataType: "PASSWORD", + encrypted: true, + isRequired: false, + isSecretExistsPath: "authentication.secretExists.clientSecret", + })} - {this.renderInputTextControlViaFormControl( - "authentication.scopeString", - "Scope(s)", - "e.g. read, write", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.scopeString", + label: "Scope(s)", + placeholderText: "e.g. read, write", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} - {this.renderInputTextControlViaFormControl( - "authentication.audience", - "Audience", - "https://example.com/oauth/audience", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.audience", + label: "Audience", + placeholderText: "https://example.com/oauth/audience", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} - {this.renderInputTextControlViaFormControl( - "authentication.resource", - "Resource", - "https://example.com/oauth/resource", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.resource", + label: "Resource", + placeholderText: "https://example.com/oauth/resource", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })} ); @@ -976,14 +978,14 @@ class DatasourceRestAPIEditor extends React.Component< - {this.renderInputTextControlViaFormControl( - "authentication.authorizationUrl", - "Authorization URL", - "https://example.com/login/oauth/authorize", - "TEXT", - false, - false, - )} + {this.renderInputTextControlViaFormControl({ + configProperty: "authentication.authorizationUrl", + label: "Authorization URL", + placeholderText: "https://example.com/login/oauth/authorize", + dataType: "TEXT", + encrypted: false, + isRequired: false, + })}
@@ -1016,15 +1018,25 @@ class DatasourceRestAPIEditor extends React.Component< // All components in formControls must be rendered via FormControl. // FormControl is the common wrapper for all formcontrol components and contains common elements i.e. label, subtitle, helpertext - renderInputTextControlViaFormControl( - configProperty: string, - label: string, - placeholderText: string, - dataType: "TEXT" | "PASSWORD" | "NUMBER", - encrypted: boolean, - isRequired: boolean, - fieldValidator?: (value: string) => { isValid: boolean; message: string }, - ) { + renderInputTextControlViaFormControl({ + configProperty, + dataType, + encrypted, + fieldValidator, + isRequired, + isSecretExistsPath, + label, + placeholderText, + }: { + configProperty: string; + label: string; + placeholderText: string; + dataType: "TEXT" | "PASSWORD" | "NUMBER"; + encrypted: boolean; + isRequired: boolean; + fieldValidator?: (value: string) => { isValid: boolean; message: string }; + isSecretExistsPath?: string; + }) { return (