mirror of
https://github.com/logto-io/logto.git
synced 2025-04-14 23:11:31 -05:00
feat(console,phrases): implement environment variables input field (2/2) (#5473)
feat(console,phrases): implement environment variables input field implement environment variables input field
This commit is contained in:
parent
fa3577e491
commit
4e27e3465e
19 changed files with 194 additions and 12 deletions
|
@ -0,0 +1,114 @@
|
|||
import { useCallback, useMemo } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FormField from '@/ds-components/FormField';
|
||||
import KeyValueInputField from '@/ds-components/KeyValueInputField';
|
||||
|
||||
import { type JwtClaimsFormType } from '../type';
|
||||
|
||||
const isValidKey = (key: string) => {
|
||||
return /^\w+$/.test(key);
|
||||
};
|
||||
|
||||
function EnvironmentVariablesField() {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const {
|
||||
register,
|
||||
getValues,
|
||||
trigger,
|
||||
formState: {
|
||||
errors: { environmentVariables: envVariableErrors },
|
||||
submitCount,
|
||||
},
|
||||
} = useFormContext<JwtClaimsFormType>();
|
||||
|
||||
// Read the form controller from the context @see {@link https://react-hook-form.com/docs/usefieldarray}
|
||||
const { fields, remove, append } = useFieldArray<JwtClaimsFormType>({
|
||||
name: 'environmentVariables',
|
||||
});
|
||||
|
||||
const keyValidator = useCallback(
|
||||
(key: string, index: number) => {
|
||||
const envVariables = getValues('environmentVariables');
|
||||
|
||||
if (!envVariables) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unique key validation
|
||||
if (envVariables.filter(({ key: _key }) => _key.length > 0 && _key === key).length > 1) {
|
||||
// Reuse the same error phrase key from webhook settings
|
||||
return t('webhook_details.settings.key_duplicated_error');
|
||||
}
|
||||
|
||||
// Empty key validation (if value is present)
|
||||
const correspondValue = getValues(`environmentVariables.${index}.value`);
|
||||
if (correspondValue) {
|
||||
// Reuse the same error phrase key from webhook settings
|
||||
return Boolean(key) || t('webhook_details.settings.key_missing_error');
|
||||
}
|
||||
|
||||
// Key format validation
|
||||
if (Boolean(key) && !isValidKey(key)) {
|
||||
// Reuse the same error phrase key from webhook settings
|
||||
return t('webhook_details.settings.invalid_key_error');
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[getValues, t]
|
||||
);
|
||||
|
||||
const valueValidator = useCallback(
|
||||
(value: string, index: number) => {
|
||||
return getValues(`environmentVariables.${index}.value`)
|
||||
? Boolean(value) || t('webhook_details.settings.value_missing_error')
|
||||
: true;
|
||||
},
|
||||
[getValues, t]
|
||||
);
|
||||
|
||||
const revalidate = useCallback(() => {
|
||||
for (const [index] of fields.entries()) {
|
||||
void trigger(`environmentVariables.${index}.key`);
|
||||
|
||||
// Only trigger value validation if the form has been submitted
|
||||
if (submitCount > 0) {
|
||||
void trigger(`environmentVariables.${index}.value`);
|
||||
}
|
||||
}
|
||||
}, [fields, submitCount, trigger]);
|
||||
|
||||
const getInputFieldProps = useMemo(
|
||||
() => ({
|
||||
key: (index: number) =>
|
||||
register(`environmentVariables.${index}.key`, {
|
||||
validate: (key) => keyValidator(key, index),
|
||||
onChange: revalidate,
|
||||
}),
|
||||
value: (index: number) =>
|
||||
register(`environmentVariables.${index}.value`, {
|
||||
validate: (value) => valueValidator(value, index),
|
||||
onChange: revalidate,
|
||||
}),
|
||||
}),
|
||||
[register, revalidate, keyValidator, valueValidator]
|
||||
);
|
||||
|
||||
return (
|
||||
<FormField title="jwt_claims.environment_variables.input_field_title">
|
||||
<KeyValueInputField
|
||||
fields={fields}
|
||||
// Force envVariableErrors to be an array, otherwise return undefined
|
||||
errors={envVariableErrors?.map?.((error) => error)}
|
||||
getInputFieldProps={getInputFieldProps}
|
||||
onAppend={append}
|
||||
onRemove={remove}
|
||||
/>
|
||||
</FormField>
|
||||
);
|
||||
}
|
||||
|
||||
export default EnvironmentVariablesField;
|
|
@ -16,6 +16,7 @@ import {
|
|||
fetchExternalDataCodeExample,
|
||||
} from '../config';
|
||||
|
||||
import EnvironmentVariablesField from './EnvironmentVariablesField';
|
||||
import GuideCard, { CardType } from './GuideCard';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
|
@ -107,7 +108,19 @@ function RightPanel({ tokenType }: Props) {
|
|||
options={fetchExternalDataEditorOptions}
|
||||
/>
|
||||
</GuideCard>
|
||||
<GuideCard name={CardType.EnvironmentVariables} />
|
||||
<GuideCard name={CardType.EnvironmentVariables}>
|
||||
{/**
|
||||
* We use useFieldArray hook to manage the list of environment variables in the EnvironmentVariablesField component.
|
||||
* useFieldArray will read the form context and return the necessary methods and values to manage the list.
|
||||
* The form context will mutate when the tokenType changes. It will provide different form state and methods based on the tokenType. (@see JwtClaims component.)
|
||||
* However, the form context/controller updates did not trigger a re-render of the useFieldArray hook. (@see {@link https://github.com/react-hook-form/react-hook-form/blob/master/src/useFieldArray.ts#L95})
|
||||
*
|
||||
* This cause issues when the tokenType changes and the environment variables list is not rerendered. The form state will be stale.
|
||||
* In order to fix this, we need to re-render the EnvironmentVariablesField component when the tokenType changes.
|
||||
* Achieve this by adding a key to the EnvironmentVariablesField component. Force a re-render when the tokenType changes.
|
||||
*/}
|
||||
<EnvironmentVariablesField key={tokenType} />
|
||||
</GuideCard>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -2,6 +2,7 @@ import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
|
|||
import classNames from 'classnames';
|
||||
import type { TFuncKey } from 'i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { useForm, FormProvider } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/ds-components/Card';
|
||||
|
@ -13,6 +14,7 @@ import { type Model } from './MonacoCodeEditor/type';
|
|||
import RightPanel from './RightPanel';
|
||||
import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config';
|
||||
import * as styles from './index.module.scss';
|
||||
import { type JwtClaimsFormType } from './type';
|
||||
|
||||
export { JwtTokenType } from './config';
|
||||
|
||||
|
@ -39,6 +41,18 @@ const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`;
|
|||
function JwtClaims({ tab }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const userJwtClaimsForm = useForm<JwtClaimsFormType>({
|
||||
defaultValues: {
|
||||
environmentVariables: [{ key: '', value: '' }],
|
||||
},
|
||||
});
|
||||
|
||||
const machineToMachineJwtClaimsForm = useForm<JwtClaimsFormType>({
|
||||
defaultValues: {
|
||||
environmentVariables: [{ key: '', value: '' }],
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: API integration, read/write the custom claims code value
|
||||
const activeModel = useMemo<Model>(() => {
|
||||
return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile;
|
||||
|
@ -58,17 +72,23 @@ function JwtClaims({ tab }: Props) {
|
|||
</TabNavItem>
|
||||
))}
|
||||
</TabNav>
|
||||
<form className={classNames(styles.tabContent)}>
|
||||
<Card className={styles.codePanel}>
|
||||
<div className={styles.cardTitle}>
|
||||
{t('jwt_claims.code_editor_title', {
|
||||
token: t(`jwt_claims.${phrases.token[tab]}`),
|
||||
})}
|
||||
</div>
|
||||
<MonacoCodeEditor className={styles.flexGrow} models={[activeModel]} />
|
||||
</Card>
|
||||
<RightPanel tokenType={tab} />
|
||||
</form>
|
||||
<FormProvider
|
||||
{...(tab === JwtTokenType.UserAccessToken
|
||||
? userJwtClaimsForm
|
||||
: machineToMachineJwtClaimsForm)}
|
||||
>
|
||||
<form className={classNames(styles.tabContent)}>
|
||||
<Card className={styles.codePanel}>
|
||||
<div className={styles.cardTitle}>
|
||||
{t('jwt_claims.code_editor_title', {
|
||||
token: t(`jwt_claims.${phrases.token[tab]}`),
|
||||
})}
|
||||
</div>
|
||||
<MonacoCodeEditor className={styles.flexGrow} models={[activeModel]} />
|
||||
</Card>
|
||||
<RightPanel tokenType={tab} />
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
6
packages/console/src/pages/JwtClaims/type.ts
Normal file
6
packages/console/src/pages/JwtClaims/type.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export type JwtClaimsFormType = {
|
||||
script?: string;
|
||||
environmentVariables?: Array<{ key: string; value: string }>;
|
||||
contextSample?: string;
|
||||
tokenSample?: string;
|
||||
};
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -31,6 +31,7 @@ const jwt_claims = {
|
|||
title: 'Set environment variables',
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
|
@ -50,6 +50,8 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
subtitle:
|
||||
'Use environment variables to store sensitive information and access them in your custom claims handler.',
|
||||
/** UNTRANSLATED */
|
||||
input_field_title: 'Add environment variables',
|
||||
},
|
||||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
|
|
Loading…
Add table
Reference in a new issue