mirror of
https://github.com/logto-io/logto.git
synced 2025-03-17 22:31:28 -05:00
feat(console,phrases): add test sample code editor (#5475)
* feat(console,phrases): add test sample code editor add test sample code editor * fix(console): remove unused styles remove unused styles * refactor(console): refactor the components structure erfactor the components structure * refactor(console): clean up the root component clean up the root component
This commit is contained in:
parent
4e27e3465e
commit
2d98982588
35 changed files with 571 additions and 301 deletions
5
packages/console/src/assets/icons/token-file-icon.svg
Normal file
5
packages/console/src/assets/icons/token-file-icon.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6666 1.93677H3.33325C2.80282 1.93677 2.29411 2.14748 1.91904 2.52255C1.54397 2.89763 1.33325 3.40633 1.33325 3.93677V13.2701C1.33325 13.8005 1.54397 14.3092 1.91904 14.6843C2.29411 15.0594 2.80282 15.2701 3.33325 15.2701H12.6666C13.197 15.2701 13.7057 15.0594 14.0808 14.6843C14.4559 14.3092 14.6666 13.8005 14.6666 13.2701V3.93677C14.6666 3.40633 14.4559 2.89763 14.0808 2.52255C13.7057 2.14748 13.197 1.93677 12.6666 1.93677ZM13.3333 13.2701C13.3333 13.4469 13.263 13.6165 13.138 13.7415C13.013 13.8665 12.8434 13.9368 12.6666 13.9368H3.33325C3.15644 13.9368 2.98687 13.8665 2.86185 13.7415C2.73682 13.6165 2.66659 13.4469 2.66659 13.2701V3.93677C2.66659 3.75996 2.73682 3.59039 2.86185 3.46536C2.98687 3.34034 3.15644 3.2701 3.33325 3.2701H12.6666C12.8434 3.2701 13.013 3.34034 13.138 3.46536C13.263 3.59039 13.3333 3.75996 13.3333 3.93677V13.2701Z" fill="currentcolor"/>
|
||||
<path d="M7.91675 12.6561C7.48412 12.6561 7.13341 12.3053 7.13341 11.8727V6.65977H5.3615C4.9778 6.65977 4.66675 6.34872 4.66675 5.96502C4.66675 5.58132 4.9778 5.27026 5.3615 5.27026H10.4899C10.8736 5.27026 11.1846 5.58132 11.1846 5.96502C11.1846 6.34872 10.8736 6.65977 10.4899 6.65977H8.70008V11.8727C8.70008 12.3053 8.34937 12.6561 7.91675 12.6561Z" fill="currentcolor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
4
packages/console/src/assets/icons/user-file-icon.svg
Normal file
4
packages/console/src/assets/icons/user-file-icon.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="17" viewBox="0 0 16 17" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99995 1.93677C6.70694 1.93922 5.44254 2.31764 4.36077 3.02593C3.279 3.73421 2.42655 4.74179 1.90725 5.92595C1.38794 7.1101 1.2242 8.41971 1.43597 9.69527C1.64774 10.9708 2.22587 12.1573 3.09995 13.1101C3.72423 13.7868 4.48191 14.3269 5.32524 14.6962C6.16857 15.0656 7.07927 15.2563 7.99995 15.2563C8.92063 15.2563 9.83133 15.0656 10.6747 14.6962C11.518 14.3269 12.2757 13.7868 12.9 13.1101C13.774 12.1573 14.3522 10.9708 14.5639 9.69527C14.7757 8.41971 14.612 7.1101 14.0927 5.92595C13.5734 4.74179 12.7209 3.73421 11.6391 3.02593C10.5574 2.31764 9.29297 1.93922 7.99995 1.93677ZM7.99995 13.9368C6.61893 13.9347 5.29256 13.397 4.29995 12.4368C4.60131 11.7031 5.11397 11.0756 5.77279 10.634C6.4316 10.1924 7.20682 9.95664 7.99995 9.95664C8.79308 9.95664 9.5683 10.1924 10.2271 10.634C10.8859 11.0756 11.3986 11.7031 11.7 12.4368C10.7073 13.397 9.38097 13.9347 7.99995 13.9368ZM6.66662 7.2701C6.66662 7.00639 6.74482 6.74861 6.89132 6.52934C7.03783 6.31008 7.24607 6.13918 7.48971 6.03826C7.73334 5.93734 8.00143 5.91094 8.26007 5.96239C8.51871 6.01383 8.75629 6.14082 8.94276 6.32729C9.12923 6.51376 9.25622 6.75134 9.30766 7.00998C9.35911 7.26862 9.33271 7.53671 9.23179 7.78035C9.13087 8.02398 8.95998 8.23222 8.74071 8.37873C8.52145 8.52524 8.26366 8.60343 7.99995 8.60343C7.64633 8.60343 7.30719 8.46296 7.05714 8.21291C6.80709 7.96286 6.66662 7.62372 6.66662 7.2701ZM12.6066 11.2701C12.011 10.2513 11.0942 9.45879 9.99995 9.01677C10.3394 8.63187 10.5606 8.15721 10.6369 7.64973C10.7133 7.14225 10.6416 6.62351 10.4305 6.15577C10.2193 5.68802 9.87767 5.29114 9.44656 5.01274C9.01544 4.73435 8.51314 4.58627 7.99995 4.58627C7.48676 4.58627 6.98446 4.73435 6.55335 5.01274C6.12223 5.29114 5.7806 5.68802 5.56945 6.15577C5.3583 6.62351 5.28661 7.14225 5.36297 7.64973C5.43933 8.15721 5.66051 8.63187 5.99995 9.01677C4.90568 9.45879 3.98893 10.2513 3.39328 11.2701C2.91858 10.4615 2.66776 9.54108 2.66662 8.60343C2.66662 7.18895 3.22852 5.83239 4.22872 4.8322C5.22891 3.832 6.58546 3.2701 7.99995 3.2701C9.41444 3.2701 10.771 3.832 11.7712 4.8322C12.7714 5.83239 13.3333 7.18895 13.3333 8.60343C13.3321 9.54108 13.0813 10.4615 12.6066 11.2701Z" fill="currentcolor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -1,3 +1,4 @@
|
|||
<svg width="26" height="28" viewBox="0 0 26 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M12.9999 0C9.97432 0 7.52162 2.4527 7.52162 5.47826V6.69565C7.52162 9.72121 9.97432 12.1739 12.9999 12.1739C16.0254 12.1739 18.4781 9.72122 18.4781 6.69566V5.47826C18.4781 2.4527 16.0254 0 12.9999 0ZM13 28C25.1739 28 25.1739 24.0427 25.1739 24.0427C25.1739 18.9098 22.6263 14.1252 16.0434 14C16.0155 13.9995 15.8255 14.1844 15.5499 14.4524C14.8266 15.156 13.5144 16.4324 13 16.4324C12.4855 16.4324 11.1733 15.156 10.45 14.4524C10.1745 14.1844 9.98439 13.9995 9.95648 14C3.37365 14.1252 0.82605 18.9098 0.82605 24.0427C0.82605 24.0427 0.82605 28 13 28Z" fill="currentColor"/>
|
||||
<svg width="26" height="28" viewBox="0 0 26 28" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M12.9999 0C9.97432 0 7.52162 2.4527 7.52162 5.47826V6.69565C7.52162 9.72121 9.97432 12.1739 12.9999 12.1739C16.0254 12.1739 18.4781 9.72122 18.4781 6.69566V5.47826C18.4781 2.4527 16.0254 0 12.9999 0ZM13 28C25.1739 28 25.1739 24.0427 25.1739 24.0427C25.1739 18.9098 22.6263 14.1252 16.0434 14C16.0155 13.9995 15.8255 14.1844 15.5499 14.4524C14.8266 15.156 13.5144 16.4324 13 16.4324C12.4855 16.4324 11.1733 15.156 10.45 14.4524C10.1745 14.1844 9.98439 13.9995 9.95648 14C3.37365 14.1252 0.82605 18.9098 0.82605 24.0427C0.82605 24.0427 0.82605 28 13 28Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 738 B After Width: | Height: | Size: 742 B |
|
@ -21,4 +21,6 @@ export const defaultOptions: EditorProps['options'] = {
|
|||
renderLineHighlight: 'none',
|
||||
fontFamily: 'Roboto Mono, monospace',
|
||||
fontSize: 14,
|
||||
automaticLayout: true,
|
||||
tabSize: 2,
|
||||
};
|
||||
|
|
|
@ -22,27 +22,28 @@
|
|||
gap: _.unit(2);
|
||||
|
||||
.tab {
|
||||
padding: _.unit(1.5) _.unit(3);
|
||||
font: var(--font-label-2);
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
color: var(--color-code-grey);
|
||||
cursor: pointer;
|
||||
padding: _.unit(1.5) _.unit(3);
|
||||
color: var(--color-code-white);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: _.unit(1);
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
color: var(--color-code-white);
|
||||
background-color: var(--color-code-tab-active-bg);
|
||||
border-radius: 8px;
|
||||
&.tabButton {
|
||||
color: var(--color-code-grey);
|
||||
cursor: pointer;
|
||||
|
||||
&.active,
|
||||
&:hover {
|
||||
color: var(--color-code-white);
|
||||
background-color: var(--color-code-tab-active-bg);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font: var(--font-label-2);
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
color: var(--color-code-white);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: _.unit(2);
|
||||
|
@ -53,6 +54,5 @@
|
|||
.editorContainer {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
padding-bottom: _.unit(4);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ import { onKeyDownHandler } from '@/utils/a11y';
|
|||
import { logtoDarkTheme, defaultOptions } from './config.js';
|
||||
import * as styles from './index.module.scss';
|
||||
import type { IStandaloneCodeEditor, Model } from './type.js';
|
||||
import useEditorHeight from './use-editor-height.js';
|
||||
|
||||
export type { Model } from './type.js';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
|
@ -25,11 +28,16 @@ function MonacoCodeEditor({ className, actions, models }: Props) {
|
|||
const editorRef = useRef<Nullable<IStandaloneCodeEditor>>(null);
|
||||
|
||||
const [activeModelName, setActiveModelName] = useState<string>();
|
||||
|
||||
const activeModel = useMemo(
|
||||
() => models.find((model) => model.name === activeModelName),
|
||||
[activeModelName, models]
|
||||
);
|
||||
|
||||
const isMultiModals = useMemo(() => models.length > 1, [models]);
|
||||
|
||||
const { containerRef, editorHeight } = useEditorHeight();
|
||||
|
||||
// Set the first model as the active model
|
||||
useEffect(() => {
|
||||
setActiveModelName(models[0]?.name);
|
||||
|
@ -77,28 +85,31 @@ function MonacoCodeEditor({ className, actions, models }: Props) {
|
|||
return (
|
||||
<div className={classNames(className, styles.codeEditor)}>
|
||||
<header>
|
||||
{models.length > 1 ? (
|
||||
<div className={styles.tabList}>
|
||||
{models.map(({ name }) => (
|
||||
<div
|
||||
key={name}
|
||||
className={classNames(styles.tab, name === activeModelName && styles.active)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
<div className={styles.tabList}>
|
||||
{models.map(({ name, title, icon }) => (
|
||||
<div
|
||||
key={name}
|
||||
className={classNames(
|
||||
styles.tab,
|
||||
isMultiModals && styles.tabButton,
|
||||
name === activeModelName && styles.active
|
||||
)}
|
||||
{...(isMultiModals && {
|
||||
role: 'button',
|
||||
tabIndex: 0,
|
||||
onClick: () => {
|
||||
setActiveModelName(name);
|
||||
}}
|
||||
onKeyDown={onKeyDownHandler(() => {
|
||||
},
|
||||
onKeyDown: onKeyDownHandler(() => {
|
||||
setActiveModelName(name);
|
||||
})}
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.title}>{activeModel?.title}</div>
|
||||
)}
|
||||
}),
|
||||
})}
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.actions}>
|
||||
{actions}
|
||||
<IconButton size="small" onClick={handleCodeCopy}>
|
||||
|
@ -106,10 +117,10 @@ function MonacoCodeEditor({ className, actions, models }: Props) {
|
|||
</IconButton>
|
||||
</div>
|
||||
</header>
|
||||
<div className={styles.editorContainer}>
|
||||
<div ref={containerRef} className={styles.editorContainer}>
|
||||
<Editor
|
||||
height="100%"
|
||||
language="typescript"
|
||||
height={editorHeight}
|
||||
language={activeModel?.language ?? 'typescript'}
|
||||
// TODO: need to check on the usage of value and defaultValue
|
||||
defaultValue={activeModel?.defaultValue}
|
||||
path={activeModel?.name}
|
||||
|
|
|
@ -7,6 +7,8 @@ export type IStandaloneCodeEditor = Parameters<OnMount>[0];
|
|||
export type Model = {
|
||||
/** Used as the unique key for the monaco editor model @see {@link https://github.com/suren-atoyan/monaco-react?tab=readme-ov-file#multi-model-editor} */
|
||||
name: string;
|
||||
/** The icon of the model, will be displayed on the tab */
|
||||
icon?: React.ReactNode;
|
||||
/** The title of the model */
|
||||
title: string;
|
||||
defaultValue: string;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { useRef, useState, useLayoutEffect } from 'react';
|
||||
|
||||
// Recalculate the height of the editor when the container size changes
|
||||
// This is to avoid the code editor's height shaking when the content is updated.
|
||||
// @see {@link https://github.com/react-monaco-editor/react-monaco-editor/issues/391}
|
||||
const useEditorHeight = () => {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [editorHeight, setEditorHeight] = useState<number | string>('100%');
|
||||
const safeArea = 16;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (containerRef.current) {
|
||||
setEditorHeight(containerRef.current.clientHeight - safeArea);
|
||||
}
|
||||
};
|
||||
|
||||
handleResize();
|
||||
|
||||
const resizeObserver = new ResizeObserver(handleResize);
|
||||
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { containerRef, editorHeight };
|
||||
};
|
||||
|
||||
export default useEditorHeight;
|
|
@ -1,129 +0,0 @@
|
|||
import { Editor } from '@monaco-editor/react';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import BookIcon from '@/assets/icons/book.svg';
|
||||
import StartIcon from '@/assets/icons/start.svg';
|
||||
import Button from '@/ds-components/Button';
|
||||
import Table from '@/ds-components/Table';
|
||||
|
||||
import {
|
||||
JwtTokenType,
|
||||
userDataDescription,
|
||||
tokenDataDescription,
|
||||
fetchExternalDataEditorOptions,
|
||||
fetchExternalDataCodeExample,
|
||||
} from '../config';
|
||||
|
||||
import EnvironmentVariablesField from './EnvironmentVariablesField';
|
||||
import GuideCard, { CardType } from './GuideCard';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
enum Tab {
|
||||
DataSource = 'data_source_tab',
|
||||
Test = 'test_tab',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
tokenType: JwtTokenType;
|
||||
};
|
||||
|
||||
function RightPanel({ tokenType }: Props) {
|
||||
const [activeTab, setActiveTab] = useState<Tab>(Tab.DataSource);
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const getDataColumns = useCallback(
|
||||
(valueColSpan = 10) => [
|
||||
{
|
||||
title: t('domain.custom.dns_table.value_field'),
|
||||
dataIndex: 'value',
|
||||
colSpan: valueColSpan,
|
||||
render: ({ value }: (typeof userDataDescription)[0]) => (
|
||||
<span className={styles.value}>{value}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('general.description'),
|
||||
dataIndex: 'description',
|
||||
colSpan: 24 - valueColSpan,
|
||||
render: ({ description }: (typeof userDataDescription)[0]) => (
|
||||
<span className={styles.description}>{description}</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.tabs}>
|
||||
{Object.values(Tab).map((tab) => (
|
||||
<Button
|
||||
key={tab}
|
||||
type="primary"
|
||||
icon={tab === Tab.DataSource ? <BookIcon /> : <StartIcon />}
|
||||
title={`jwt_claims.${tab}`}
|
||||
className={classNames(styles.tab, activeTab === tab && styles.active)}
|
||||
onClick={() => {
|
||||
setActiveTab(tab);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className={classNames(styles.tabContent, activeTab === Tab.DataSource && styles.active)}>
|
||||
<div className={styles.description}>{t('jwt_claims.jwt_claims_description')}</div>
|
||||
{tokenType === JwtTokenType.UserAccessToken && (
|
||||
<GuideCard name={CardType.UserData}>
|
||||
<Table
|
||||
hasBorder
|
||||
isRowHoverEffectDisabled
|
||||
className={styles.table}
|
||||
rowIndexKey="value"
|
||||
columns={getDataColumns()}
|
||||
rowGroups={[{ key: 'user_data', data: userDataDescription }]}
|
||||
/>
|
||||
</GuideCard>
|
||||
)}
|
||||
<GuideCard name={CardType.TokenData}>
|
||||
<Table
|
||||
hasBorder
|
||||
isRowHoverEffectDisabled
|
||||
className={styles.table}
|
||||
rowIndexKey="value"
|
||||
columns={getDataColumns(6)}
|
||||
rowGroups={[{ key: 'token_data', data: tokenDataDescription }]}
|
||||
/>
|
||||
</GuideCard>
|
||||
<GuideCard name={CardType.FetchExternalData}>
|
||||
<div className={styles.description}>
|
||||
{t('jwt_claims.fetch_external_data.description')}
|
||||
</div>
|
||||
<Editor
|
||||
language="typescript"
|
||||
className={styles.editor}
|
||||
value={fetchExternalDataCodeExample}
|
||||
height="300px"
|
||||
theme="logto-dark"
|
||||
options={fetchExternalDataEditorOptions}
|
||||
/>
|
||||
</GuideCard>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export default RightPanel;
|
41
packages/console/src/pages/JwtClaims/ScriptPanel.tsx
Normal file
41
packages/console/src/pages/JwtClaims/ScriptPanel.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* Code Editor for the custom JWT claims script. */
|
||||
import { useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Card from '@/ds-components/Card';
|
||||
|
||||
import MonacoCodeEditor, { type Model } from './MonacoCodeEditor';
|
||||
import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config';
|
||||
import * as styles from './index.module.scss';
|
||||
import { type JwtClaimsFormType } from './type';
|
||||
|
||||
const titlePhrases = Object.freeze({
|
||||
[JwtTokenType.UserAccessToken]: 'user_jwt',
|
||||
[JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt',
|
||||
});
|
||||
|
||||
function ScriptPanel() {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { watch } = useFormContext<JwtClaimsFormType>();
|
||||
const tokenType = watch('tokenType');
|
||||
|
||||
// TODO: API integration, read/write the custom claims code value
|
||||
const activeModel = useMemo<Model>(() => {
|
||||
return tokenType === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile;
|
||||
}, [tokenType]);
|
||||
|
||||
return (
|
||||
<Card className={styles.codePanel}>
|
||||
<div className={styles.cardTitle}>
|
||||
{t('jwt_claims.code_editor_title', {
|
||||
token: t(`jwt_claims.${titlePhrases[tokenType]}`),
|
||||
})}
|
||||
</div>
|
||||
<MonacoCodeEditor className={styles.flexGrow} models={[activeModel]} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default ScriptPanel;
|
|
@ -25,7 +25,7 @@ function GuideCard({ name, children }: GuardCardProps) {
|
|||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.jwt_claims' });
|
||||
|
||||
return (
|
||||
<Card className={classNames(styles.card, expanded && styles.expanded)}>
|
||||
<Card className={classNames(styles.card, styles.collapsible, expanded && styles.expanded)}>
|
||||
<div
|
||||
className={styles.headerRow}
|
||||
role="button"
|
|
@ -0,0 +1,108 @@
|
|||
import { Editor } from '@monaco-editor/react';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Table from '@/ds-components/Table';
|
||||
|
||||
import {
|
||||
JwtTokenType,
|
||||
userDataDescription,
|
||||
tokenDataDescription,
|
||||
fetchExternalDataEditorOptions,
|
||||
fetchExternalDataCodeExample,
|
||||
} from '../config';
|
||||
import { type JwtClaimsFormType } from '../type';
|
||||
|
||||
import EnvironmentVariablesField from './EnvironmentVariablesField';
|
||||
import GuideCard, { CardType } from './GuideCard';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
/* Instructions and environment variable settings for the custom JWT claims script. */
|
||||
function InstructionTab({ isActive }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const { watch } = useFormContext<JwtClaimsFormType>();
|
||||
const tokenType = watch('tokenType');
|
||||
|
||||
const getDataColumns = useCallback(
|
||||
(valueColSpan = 10) => [
|
||||
{
|
||||
title: t('domain.custom.dns_table.value_field'),
|
||||
dataIndex: 'value',
|
||||
colSpan: valueColSpan,
|
||||
render: ({ value }: (typeof userDataDescription)[0]) => (
|
||||
<span className={styles.value}>{value}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('general.description'),
|
||||
dataIndex: 'description',
|
||||
colSpan: 24 - valueColSpan,
|
||||
render: ({ description }: (typeof userDataDescription)[0]) => (
|
||||
<span className={styles.description}>{description}</span>
|
||||
),
|
||||
},
|
||||
],
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.tabContent, isActive && styles.active)}>
|
||||
<div className={styles.description}>{t('jwt_claims.jwt_claims_description')}</div>
|
||||
{tokenType === JwtTokenType.UserAccessToken && (
|
||||
<GuideCard name={CardType.UserData}>
|
||||
<Table
|
||||
hasBorder
|
||||
isRowHoverEffectDisabled
|
||||
className={styles.table}
|
||||
rowIndexKey="value"
|
||||
columns={getDataColumns()}
|
||||
rowGroups={[{ key: 'user_data', data: userDataDescription }]}
|
||||
/>
|
||||
</GuideCard>
|
||||
)}
|
||||
<GuideCard name={CardType.TokenData}>
|
||||
<Table
|
||||
hasBorder
|
||||
isRowHoverEffectDisabled
|
||||
className={styles.table}
|
||||
rowIndexKey="value"
|
||||
columns={getDataColumns(6)}
|
||||
rowGroups={[{ key: 'token_data', data: tokenDataDescription }]}
|
||||
/>
|
||||
</GuideCard>
|
||||
<GuideCard name={CardType.FetchExternalData}>
|
||||
<div className={styles.description}>{t('jwt_claims.fetch_external_data.description')}</div>
|
||||
<Editor
|
||||
language="typescript"
|
||||
className={styles.sampleCode}
|
||||
value={fetchExternalDataCodeExample}
|
||||
height="300px"
|
||||
theme="logto-dark"
|
||||
options={fetchExternalDataEditorOptions}
|
||||
/>
|
||||
</GuideCard>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export default InstructionTab;
|
|
@ -0,0 +1,56 @@
|
|||
import classNames from 'classnames';
|
||||
import { useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Button from '@/ds-components/Button';
|
||||
import Card from '@/ds-components/Card';
|
||||
|
||||
import MonacoCodeEditor from '../MonacoCodeEditor/index.js';
|
||||
import {
|
||||
userTokenPayloadTestModel,
|
||||
machineToMachineTokenPayloadTestModel,
|
||||
userTokenContextTestModel,
|
||||
JwtTokenType,
|
||||
} from '../config.js';
|
||||
import { type JwtClaimsFormType } from '../type.js';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
function TestTab({ isActive }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.jwt_claims' });
|
||||
|
||||
const { watch } = useFormContext<JwtClaimsFormType>();
|
||||
const tokenType = watch('tokenType');
|
||||
|
||||
const editorModels = useMemo(
|
||||
() =>
|
||||
tokenType === JwtTokenType.UserAccessToken
|
||||
? [userTokenPayloadTestModel, userTokenContextTestModel]
|
||||
: [machineToMachineTokenPayloadTestModel],
|
||||
[tokenType]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(styles.tabContent, isActive && styles.active)}>
|
||||
<Card className={classNames(styles.card, styles.flexGrow, styles.flexColumn)}>
|
||||
<div className={styles.headerRow}>
|
||||
<div className={styles.cardHeader}>
|
||||
<div className={styles.cardTitle}>{t('tester.title')}</div>
|
||||
<div className={styles.cardSubtitle}>{t('tester.subtitle')}</div>
|
||||
</div>
|
||||
<Button title="jwt_claims.tester.run_button" type="primary" />
|
||||
</div>
|
||||
<div className={classNames(styles.cardContent, styles.flexColumn, styles.flexGrow)}>
|
||||
<MonacoCodeEditor models={editorModels} className={styles.flexGrow} />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TestTab;
|
|
@ -39,6 +39,7 @@
|
|||
display: none;
|
||||
|
||||
&.active {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: _.unit(4);
|
||||
|
@ -48,47 +49,54 @@
|
|||
font: var(--font-body-2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
.card {
|
||||
.headerRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: _.unit(4);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text);
|
||||
margin-bottom: _.unit(1);
|
||||
}
|
||||
|
||||
.cardSubtitle {
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
> *:first-child {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transition: transform 0.3s ease;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
&.collapsible {
|
||||
.headerRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: _.unit(4);
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.cardHeader {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text);
|
||||
margin-bottom: _.unit(1);
|
||||
}
|
||||
|
||||
.cardSubtitle {
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transition: transform 0.3s ease;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease;
|
||||
|
||||
> *:first-child {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
|
@ -100,37 +108,46 @@
|
|||
max-height: 1000;
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
.value {
|
||||
display: inline-block;
|
||||
padding: _.unit(0.5) _.unit(2);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg-info-tag);
|
||||
border-radius: 4px;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
margin-top: _.unit(4);
|
||||
.table {
|
||||
.value {
|
||||
display: inline-block;
|
||||
padding: _.unit(0.5) _.unit(2);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg-info-tag);
|
||||
border-radius: 4px;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
}
|
||||
|
||||
.sampleCode {
|
||||
margin-top: _.unit(4);
|
||||
|
||||
:global {
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.monaco-editor {
|
||||
border-radius: 8px;
|
||||
|
||||
:global {
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.monaco-editor {
|
||||
.overflow-guard {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.overflow-guard {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.lines-content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
/* stylelint-disable-next-line selector-class-pattern */
|
||||
.lines-content {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flexColumn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flexGrow {
|
||||
flex-grow: 1;
|
||||
}
|
42
packages/console/src/pages/JwtClaims/SettingsPanel/index.tsx
Normal file
42
packages/console/src/pages/JwtClaims/SettingsPanel/index.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
|
||||
import BookIcon from '@/assets/icons/book.svg';
|
||||
import StartIcon from '@/assets/icons/start.svg';
|
||||
import Button from '@/ds-components/Button';
|
||||
|
||||
import InstructionTab from './InstructionTab';
|
||||
import TestTab from './TestTab';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
enum Tab {
|
||||
DataSource = 'data_source_tab',
|
||||
Test = 'test_tab',
|
||||
}
|
||||
|
||||
function SettingsPanel() {
|
||||
const [activeTab, setActiveTab] = useState<Tab>(Tab.DataSource);
|
||||
|
||||
return (
|
||||
<div className={styles.flexColumn}>
|
||||
<div className={styles.tabs}>
|
||||
{Object.values(Tab).map((tab) => (
|
||||
<Button
|
||||
key={tab}
|
||||
type="primary"
|
||||
icon={tab === Tab.DataSource ? <BookIcon /> : <StartIcon />}
|
||||
title={`jwt_claims.${tab}`}
|
||||
className={classNames(styles.tab, activeTab === tab && styles.active)}
|
||||
onClick={() => {
|
||||
setActiveTab(tab);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<InstructionTab isActive={activeTab === Tab.DataSource} />
|
||||
<TestTab isActive={activeTab === Tab.Test} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SettingsPanel;
|
|
@ -1,5 +1,8 @@
|
|||
import { type EditorProps } from '@monaco-editor/react';
|
||||
|
||||
import TokenFileIcon from '@/assets/icons/token-file-icon.svg';
|
||||
import UserFileIcon from '@/assets/icons/user-file-icon.svg';
|
||||
|
||||
import type { Model } from './MonacoCodeEditor/type.js';
|
||||
|
||||
/**
|
||||
|
@ -171,33 +174,34 @@ export const userDataDescription: GuideTableData[] = [
|
|||
|
||||
export const tokenDataDescription: GuideTableData[] = [
|
||||
{
|
||||
value: 'iss',
|
||||
description: '(issuer) Issuer of the JWT.',
|
||||
},
|
||||
{
|
||||
value: 'sub',
|
||||
description: '(subject) Subject of the JWT.',
|
||||
},
|
||||
{
|
||||
value: 'aud',
|
||||
description: '(audience) Recipient for which the JWT is intended.',
|
||||
},
|
||||
{
|
||||
value: 'exp',
|
||||
description: '(expiration) Time after which the JWT expires.',
|
||||
},
|
||||
{
|
||||
value: 'nbf',
|
||||
description: '(not before) Time before which the JWT must not be accepted for processing.',
|
||||
value: 'jti',
|
||||
description:
|
||||
'(JWT ID) Unique identifier for the JWT. Useful for tracking and preventing reuse of the token.',
|
||||
},
|
||||
{
|
||||
value: 'iat',
|
||||
description: '(issued at) Time at which the JWT was issued.',
|
||||
},
|
||||
{
|
||||
value: 'jti',
|
||||
value: 'exp',
|
||||
description: '(expiration) Time after which the JWT expires.',
|
||||
},
|
||||
{
|
||||
value: 'client_id',
|
||||
description: 'Client ID of the application that requested the JWT.',
|
||||
},
|
||||
{
|
||||
value: 'kind',
|
||||
description:
|
||||
'(JWT ID) Unique identifier for the JWT. Useful for tracking and preventing reuse of the token.',
|
||||
'Type of the token. `AccessToken` for user access tokens and `ClientCredentials` for machine-to-machine access tokens.',
|
||||
},
|
||||
{
|
||||
value: 'scope',
|
||||
description: 'Scopes requested by the client joint by space.',
|
||||
},
|
||||
{
|
||||
value: 'aud',
|
||||
description: '(audience) Audience for which the JWT is intended.',
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -226,3 +230,62 @@ const data = await response.json();
|
|||
return {
|
||||
externalData: data,
|
||||
};`;
|
||||
|
||||
/**
|
||||
* Tester Code Editor configs
|
||||
*/
|
||||
const standardTokenPayloadData = {
|
||||
jti: '1234567890',
|
||||
iat: 1_516_239_022,
|
||||
exp: 1_516_239_022,
|
||||
client_id: 'my_app',
|
||||
scope: 'read write',
|
||||
aud: 'http://localhost:3000/api',
|
||||
};
|
||||
|
||||
const defaultUserTokenPayloadData = {
|
||||
...standardTokenPayloadData,
|
||||
kind: 'AccessToken',
|
||||
};
|
||||
|
||||
const defaultMachineToMachineTokenPayloadData = {
|
||||
...standardTokenPayloadData,
|
||||
kind: 'ClientCredentials',
|
||||
};
|
||||
|
||||
const defaultUserTokenContextData = {
|
||||
user: {
|
||||
id: '123',
|
||||
primaryEmail: 'foo@logto.io',
|
||||
primaryPhone: '+1234567890',
|
||||
username: 'foo',
|
||||
name: 'Foo Bar',
|
||||
avatar: 'https://example.com/avatar.png',
|
||||
identities: {},
|
||||
customData: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const userTokenPayloadTestModel: Model = {
|
||||
language: 'json',
|
||||
icon: <TokenFileIcon />,
|
||||
name: 'user-token-payload.json',
|
||||
title: 'Token',
|
||||
defaultValue: JSON.stringify(defaultUserTokenPayloadData, null, '\t'),
|
||||
};
|
||||
|
||||
export const machineToMachineTokenPayloadTestModel: Model = {
|
||||
language: 'json',
|
||||
icon: <TokenFileIcon />,
|
||||
name: 'machine-to-machine-token-payload.json',
|
||||
title: 'Token',
|
||||
defaultValue: JSON.stringify(defaultMachineToMachineTokenPayloadData, null, '\t'),
|
||||
};
|
||||
|
||||
export const userTokenContextTestModel: Model = {
|
||||
language: 'json',
|
||||
icon: <UserFileIcon />,
|
||||
name: 'user-token-context.json',
|
||||
title: 'User Context',
|
||||
defaultValue: JSON.stringify(defaultUserTokenContextData, null, '\t'),
|
||||
};
|
|
@ -18,22 +18,25 @@
|
|||
.tabContent {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: _.unit(3);
|
||||
flex-grow: 1;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
margin-bottom: _.unit(6);
|
||||
|
||||
&:first-child {
|
||||
margin-right: _.unit(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.codePanel {
|
||||
margin-bottom: _.unit(6);
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.cardTitle {
|
||||
font: var(--font-title-2);
|
||||
font: var(--font-label-2);
|
||||
margin-bottom: _.unit(3);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,63 +1,47 @@
|
|||
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';
|
||||
import CardTitle from '@/ds-components/CardTitle';
|
||||
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
||||
|
||||
import MonacoCodeEditor from './MonacoCodeEditor';
|
||||
import { type Model } from './MonacoCodeEditor/type';
|
||||
import RightPanel from './RightPanel';
|
||||
import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config';
|
||||
import ScriptPanel from './ScriptPanel';
|
||||
import SettingsPanel from './SettingsPanel';
|
||||
import { JwtTokenType } from './config';
|
||||
import * as styles from './index.module.scss';
|
||||
import { type JwtClaimsFormType } from './type';
|
||||
|
||||
export { JwtTokenType } from './config';
|
||||
|
||||
const tabPhrases = Object.freeze({
|
||||
[JwtTokenType.UserAccessToken]: 'user_jwt_tab',
|
||||
[JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt_tab',
|
||||
});
|
||||
|
||||
const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`;
|
||||
|
||||
type Props = {
|
||||
tab: JwtTokenType;
|
||||
};
|
||||
|
||||
const phrases = Object.freeze({
|
||||
tab: {
|
||||
[JwtTokenType.UserAccessToken]: 'user_jwt_tab',
|
||||
[JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt_tab',
|
||||
},
|
||||
token: {
|
||||
[JwtTokenType.UserAccessToken]: 'user_jwt',
|
||||
[JwtTokenType.MachineToMachineAccessToken]: 'machine_to_machine_jwt',
|
||||
},
|
||||
} satisfies Record<
|
||||
string,
|
||||
Record<JwtTokenType, TFuncKey<'translation', 'admin_console.jwt_claims'>>
|
||||
>);
|
||||
|
||||
const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`;
|
||||
|
||||
function JwtClaims({ tab }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
||||
const userJwtClaimsForm = useForm<JwtClaimsFormType>({
|
||||
defaultValues: {
|
||||
tokenType: JwtTokenType.UserAccessToken,
|
||||
environmentVariables: [{ key: '', value: '' }],
|
||||
},
|
||||
});
|
||||
|
||||
const machineToMachineJwtClaimsForm = useForm<JwtClaimsFormType>({
|
||||
defaultValues: {
|
||||
tokenType: JwtTokenType.MachineToMachineAccessToken,
|
||||
environmentVariables: [{ key: '', value: '' }],
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: API integration, read/write the custom claims code value
|
||||
const activeModel = useMemo<Model>(() => {
|
||||
return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile;
|
||||
}, [tab]);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<CardTitle
|
||||
|
@ -68,7 +52,7 @@ function JwtClaims({ tab }: Props) {
|
|||
<TabNav className={styles.tabNav}>
|
||||
{Object.values(JwtTokenType).map((tokenType) => (
|
||||
<TabNavItem key={tokenType} href={getPath(tokenType)} isActive={tokenType === tab}>
|
||||
{t(`jwt_claims.${phrases.tab[tokenType]}`)}
|
||||
{t(`jwt_claims.${tabPhrases[tokenType]}`)}
|
||||
</TabNavItem>
|
||||
))}
|
||||
</TabNav>
|
||||
|
@ -78,15 +62,8 @@ function JwtClaims({ tab }: Props) {
|
|||
: 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} />
|
||||
<ScriptPanel />
|
||||
<SettingsPanel />
|
||||
</form>
|
||||
</FormProvider>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { type JwtTokenType } from './config.js';
|
||||
|
||||
export type JwtClaimsFormType = {
|
||||
tokenType: JwtTokenType;
|
||||
script?: string;
|
||||
environmentVariables?: Array<{ key: string; value: string }>;
|
||||
contextSample?: string;
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -35,9 +35,10 @@ const jwt_claims = {
|
|||
},
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
title: 'Test',
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -56,11 +56,13 @@ const jwt_claims = {
|
|||
/** UNTRANSLATED */
|
||||
jwt_claims_hint:
|
||||
'Limit custom claims to under 50KB. Default JWT claims are automatically included in the token and can not be overridden.',
|
||||
test_data: {
|
||||
tester: {
|
||||
/** UNTRANSLATED */
|
||||
title: 'Test',
|
||||
/** UNTRANSLATED */
|
||||
subtitle: "Edit the context to adjust the token's request states and test your custom claims.",
|
||||
/** UNTRANSLATED */
|
||||
run_button: 'Run',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue