mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
feat(console): show app secret (#1723)
This commit is contained in:
parent
dec607315d
commit
01dfeed19b
8 changed files with 72 additions and 3 deletions
|
@ -25,6 +25,12 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
cursor: text;
|
cursor: text;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.copyIcon {
|
.copyIcon {
|
||||||
margin-left: _.unit(3);
|
margin-left: _.unit(3);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react';
|
import { MouseEventHandler, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { TFuncKey, useTranslation } from 'react-i18next';
|
import { TFuncKey, useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import Copy from '@/icons/Copy';
|
import Copy from '@/icons/Copy';
|
||||||
|
import Eye from '@/icons/Eye';
|
||||||
|
import EyeClosed from '@/icons/EyeClosed';
|
||||||
|
|
||||||
import IconButton from '../IconButton';
|
import IconButton from '../IconButton';
|
||||||
import Tooltip from '../Tooltip';
|
import Tooltip from '../Tooltip';
|
||||||
|
@ -12,14 +14,29 @@ type Props = {
|
||||||
value: string;
|
value: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
variant?: 'text' | 'contained' | 'border' | 'icon';
|
variant?: 'text' | 'contained' | 'border' | 'icon';
|
||||||
|
hasVisibilityToggle?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CopyState = TFuncKey<'translation', 'admin_console.general'>;
|
type CopyState = TFuncKey<'translation', 'admin_console.general'>;
|
||||||
|
|
||||||
const CopyToClipboard = ({ value, className, variant = 'contained' }: Props) => {
|
const CopyToClipboard = ({
|
||||||
|
value,
|
||||||
|
className,
|
||||||
|
hasVisibilityToggle,
|
||||||
|
variant = 'contained',
|
||||||
|
}: Props) => {
|
||||||
const copyIconReference = useRef<HTMLDivElement>(null);
|
const copyIconReference = useRef<HTMLDivElement>(null);
|
||||||
const [copyState, setCopyState] = useState<CopyState>('copy');
|
const [copyState, setCopyState] = useState<CopyState>('copy');
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.general' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.general' });
|
||||||
|
const [showHiddenContent, setShowHiddenContent] = useState(false);
|
||||||
|
|
||||||
|
const displayValue = useMemo(() => {
|
||||||
|
if (!hasVisibilityToggle || showHiddenContent) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '*'.repeat(value.length);
|
||||||
|
}, [hasVisibilityToggle, showHiddenContent, value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
copyIconReference.current?.addEventListener('mouseleave', () => {
|
copyIconReference.current?.addEventListener('mouseleave', () => {
|
||||||
|
@ -33,6 +50,10 @@ const CopyToClipboard = ({ value, className, variant = 'contained' }: Props) =>
|
||||||
setCopyState('copied');
|
setCopyState('copied');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleHiddenContent = () => {
|
||||||
|
setShowHiddenContent((previous) => !previous);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(styles.container, styles[variant], className)}
|
className={classNames(styles.container, styles[variant], className)}
|
||||||
|
@ -41,7 +62,14 @@ const CopyToClipboard = ({ value, className, variant = 'contained' }: Props) =>
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
{variant === 'icon' ? null : value}
|
{variant !== 'icon' && <div className={styles.content}>{displayValue}</div>}
|
||||||
|
{hasVisibilityToggle && (
|
||||||
|
<div className={styles.eye}>
|
||||||
|
<IconButton onClick={toggleHiddenContent}>
|
||||||
|
{showHiddenContent ? <EyeClosed /> : <Eye />}
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div ref={copyIconReference} className={styles.copyIcon}>
|
<div ref={copyIconReference} className={styles.copyIcon}>
|
||||||
<IconButton onClick={copy}>
|
<IconButton onClick={copy}>
|
||||||
<Copy />
|
<Copy />
|
||||||
|
|
21
packages/console/src/icons/EyeClosed.tsx
Normal file
21
packages/console/src/icons/EyeClosed.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { SVGProps } from 'react';
|
||||||
|
|
||||||
|
const EyeClosed = (props: SVGProps<SVGSVGElement>) => (
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M3.66988 2.79338C3.27935 3.18391 3.27935 3.81707 3.66988 4.2076L5.30804 5.84576C3.10993 7.40492 1.66699 9.55153 1.66699 10.0003C1.66699 10.722 5.39795 15.8337 10.0003 15.8337C11.468 15.8337 12.847 15.3139 14.0447 14.5824L16.3978 16.9355C16.7883 17.326 17.4215 17.326 17.812 16.9355C18.2025 16.545 18.2025 15.9118 17.812 15.5213L5.08409 2.79338C4.69356 2.40286 4.0604 2.40286 3.66988 2.79338ZM12.0732 12.6109L7.38973 7.92745C6.9373 8.4965 6.66699 9.21685 6.66699 10.0003C6.66699 11.8413 8.15938 13.3337 10.0003 13.3337C10.7838 13.3337 11.5042 13.0634 12.0732 12.6109ZM18.3337 10.0003C18.3337 10.2663 17.8268 11.1287 16.9563 12.1117L9.07884 4.23427C9.38142 4.1904 9.68888 4.16699 10.0003 4.16699C14.6027 4.16699 18.3337 9.27866 18.3337 10.0003Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default EyeClosed;
|
|
@ -64,6 +64,16 @@ const Settings = ({ applicationType, oidcConfig, defaultData, isDeleted }: Props
|
||||||
placeholder={t('application_details.description_placeholder')}
|
placeholder={t('application_details.description_placeholder')}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
{applicationType === ApplicationType.Traditional && (
|
||||||
|
<FormField title="application_details.application_secret" className={styles.textField}>
|
||||||
|
<CopyToClipboard
|
||||||
|
hasVisibilityToggle
|
||||||
|
className={styles.textField}
|
||||||
|
value={defaultData.secret}
|
||||||
|
variant="border"
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
<FormField
|
<FormField
|
||||||
title="application_details.authorization_endpoint"
|
title="application_details.authorization_endpoint"
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
|
|
|
@ -9,6 +9,7 @@ const application_details = {
|
||||||
authorization_endpoint: 'Authorization endpoint',
|
authorization_endpoint: 'Authorization endpoint',
|
||||||
authorization_endpoint_tip:
|
authorization_endpoint_tip:
|
||||||
"The endpoint to perform authentication and authorization. It's used for OpenID Connect Authentication.",
|
"The endpoint to perform authentication and authorization. It's used for OpenID Connect Authentication.",
|
||||||
|
application_secret: 'App Secret',
|
||||||
redirect_uri: 'Redirect URI',
|
redirect_uri: 'Redirect URI',
|
||||||
redirect_uris: 'Redirect URIs',
|
redirect_uris: 'Redirect URIs',
|
||||||
redirect_uri_placeholder: 'https://your.website.com/app',
|
redirect_uri_placeholder: 'https://your.website.com/app',
|
||||||
|
|
|
@ -9,6 +9,7 @@ const application_details = {
|
||||||
authorization_endpoint: '인증 End-Point',
|
authorization_endpoint: '인증 End-Point',
|
||||||
authorization_endpoint_tip:
|
authorization_endpoint_tip:
|
||||||
'인증 및 권한 부여를 진행할 End-Point예요. OpenID Connect 인증에서 사용되던 값 이에요.',
|
'인증 및 권한 부여를 진행할 End-Point예요. OpenID Connect 인증에서 사용되던 값 이에요.',
|
||||||
|
application_secret: 'App Secret',
|
||||||
redirect_uri: 'Redirect URI',
|
redirect_uri: 'Redirect URI',
|
||||||
redirect_uris: 'Redirect URIs',
|
redirect_uris: 'Redirect URIs',
|
||||||
redirect_uri_placeholder: 'https://your.website.com/app',
|
redirect_uri_placeholder: 'https://your.website.com/app',
|
||||||
|
|
|
@ -9,6 +9,7 @@ const application_details = {
|
||||||
authorization_endpoint: 'Yetkilendirme bitiş noktası',
|
authorization_endpoint: 'Yetkilendirme bitiş noktası',
|
||||||
authorization_endpoint_tip:
|
authorization_endpoint_tip:
|
||||||
'Kimlik doğrulama ve yetkilendirme gerçekleştirmek için bitiş noktası. OpenID Connect Authentication için kullanılır.',
|
'Kimlik doğrulama ve yetkilendirme gerçekleştirmek için bitiş noktası. OpenID Connect Authentication için kullanılır.',
|
||||||
|
application_secret: 'Uygulama Sırrı',
|
||||||
redirect_uri: 'Yönlendirme URIı',
|
redirect_uri: 'Yönlendirme URIı',
|
||||||
redirect_uris: 'Yönlendirme URIları',
|
redirect_uris: 'Yönlendirme URIları',
|
||||||
redirect_uri_placeholder: 'https://your.website.com/app',
|
redirect_uri_placeholder: 'https://your.website.com/app',
|
||||||
|
|
|
@ -8,6 +8,7 @@ const application_details = {
|
||||||
description_placeholder: '请输入应用描述',
|
description_placeholder: '请输入应用描述',
|
||||||
authorization_endpoint: 'Authorization Endpoint',
|
authorization_endpoint: 'Authorization Endpoint',
|
||||||
authorization_endpoint_tip: '进行鉴权与授权的端点 endpoint。用于 OpenID Connect 中的鉴权流程。',
|
authorization_endpoint_tip: '进行鉴权与授权的端点 endpoint。用于 OpenID Connect 中的鉴权流程。',
|
||||||
|
application_secret: 'App Secret',
|
||||||
redirect_uri: 'Redirect URI',
|
redirect_uri: 'Redirect URI',
|
||||||
redirect_uris: 'Redirect URIs',
|
redirect_uris: 'Redirect URIs',
|
||||||
redirect_uri_placeholder: 'https://your.website.com/app',
|
redirect_uri_placeholder: 'https://your.website.com/app',
|
||||||
|
|
Loading…
Reference in a new issue