mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(console): should mask confidential connector config values by default (#3645)
This commit is contained in:
parent
e4fc54000e
commit
6bb4098409
4 changed files with 50 additions and 5 deletions
|
@ -6,6 +6,12 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideTextContainerContent {
|
||||||
|
input {
|
||||||
|
-webkit-text-security: disc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -9,21 +9,52 @@ import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
type Ref,
|
type Ref,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
import EyeClosed from '@/assets/images/eye-closed.svg';
|
||||||
|
import Eye from '@/assets/images/eye.svg';
|
||||||
|
import IconButton from '@/components/IconButton';
|
||||||
|
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
|
|
||||||
type Props = Omit<HTMLProps<HTMLInputElement>, 'size'> & {
|
type Props = Omit<HTMLProps<HTMLInputElement>, 'size'> & {
|
||||||
error?: string | boolean;
|
error?: string | boolean;
|
||||||
icon?: ReactElement;
|
icon?: ReactElement;
|
||||||
suffix?: ReactElement;
|
suffix?: ReactElement;
|
||||||
|
isConfidential?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function TextInput(
|
function TextInput(
|
||||||
{ error, icon, suffix, disabled, className, readOnly, type = 'text', ...rest }: Props,
|
{
|
||||||
|
error,
|
||||||
|
icon,
|
||||||
|
suffix,
|
||||||
|
disabled,
|
||||||
|
className,
|
||||||
|
readOnly,
|
||||||
|
type = 'text',
|
||||||
|
isConfidential = false,
|
||||||
|
...rest
|
||||||
|
}: Props,
|
||||||
reference: Ref<Nullable<HTMLInputElement>>
|
reference: Ref<Nullable<HTMLInputElement>>
|
||||||
) {
|
) {
|
||||||
const innerRef = useRef<HTMLInputElement>(null);
|
const innerRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [isContentHidden, setIsContentHidden] = useState(true);
|
||||||
|
|
||||||
|
const toggleHiddenContent = () => {
|
||||||
|
setIsContentHidden((previous) => !previous);
|
||||||
|
};
|
||||||
|
|
||||||
|
const suffixIcon =
|
||||||
|
isConfidential && type === 'text' ? (
|
||||||
|
<IconButton onClick={toggleHiddenContent}>
|
||||||
|
{isContentHidden ? <EyeClosed /> : <Eye />}
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
suffix
|
||||||
|
);
|
||||||
|
|
||||||
useImperativeHandle(reference, () => innerRef.current);
|
useImperativeHandle(reference, () => innerRef.current);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -54,6 +85,7 @@ function TextInput(
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.container,
|
styles.container,
|
||||||
error && styles.error,
|
error && styles.error,
|
||||||
|
isConfidential && isContentHidden && type === 'text' && styles.hideTextContainerContent,
|
||||||
icon && styles.withIcon,
|
icon && styles.withIcon,
|
||||||
disabled && styles.disabled,
|
disabled && styles.disabled,
|
||||||
readOnly && styles.readOnly
|
readOnly && styles.readOnly
|
||||||
|
@ -61,9 +93,9 @@ function TextInput(
|
||||||
>
|
>
|
||||||
{icon && <span className={styles.icon}>{icon}</span>}
|
{icon && <span className={styles.icon}>{icon}</span>}
|
||||||
<input type={type} {...rest} ref={innerRef} disabled={disabled} readOnly={readOnly} />
|
<input type={type} {...rest} ref={innerRef} disabled={disabled} readOnly={readOnly} />
|
||||||
{suffix &&
|
{suffixIcon &&
|
||||||
cloneElement(suffix, {
|
cloneElement(suffixIcon, {
|
||||||
className: classNames([suffix.props.className, styles.suffix]),
|
className: classNames([suffixIcon.props.className, styles.suffix]),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{Boolean(error) && typeof error === 'string' && (
|
{Boolean(error) && typeof error === 'string' && (
|
||||||
|
|
|
@ -60,7 +60,13 @@ function ConfigForm({ formItems }: Props) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (item.type === ConnectorConfigFormItemType.Text) {
|
if (item.type === ConnectorConfigFormItemType.Text) {
|
||||||
return <TextInput {...buildCommonProperties()} />;
|
return (
|
||||||
|
<TextInput
|
||||||
|
{...buildCommonProperties()}
|
||||||
|
// TODO: update connectors form config and remove RegExp check
|
||||||
|
isConfidential={item.isConfidential ?? /(Key|Secret)$/.test(item.key)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === ConnectorConfigFormItemType.MultilineText) {
|
if (item.type === ConnectorConfigFormItemType.MultilineText) {
|
||||||
|
|
|
@ -114,6 +114,7 @@ const baseConfigFormItem = {
|
||||||
.optional(),
|
.optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
tooltip: z.string().optional(),
|
tooltip: z.string().optional(),
|
||||||
|
isConfidential: z.boolean().optional(), // For `Text` type only.
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectorConfigFormItemGuard = z.discriminatedUnion('type', [
|
const connectorConfigFormItemGuard = z.discriminatedUnion('type', [
|
||||||
|
|
Loading…
Reference in a new issue