mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(console,phrases): integrate monaco editor (#5460)
* feat(console,phrases): integrate monaco editor integrate monaco editor * chore: update pnpm lock file update pnpm lock file * fix(console): remove unused styles remove unused styles * feat(console): update font styles update font styles
This commit is contained in:
parent
e1def81ed5
commit
2f72f8ffd7
28 changed files with 574 additions and 36 deletions
|
@ -36,6 +36,7 @@
|
||||||
"@logto/schemas": "workspace:^1.13.1",
|
"@logto/schemas": "workspace:^1.13.1",
|
||||||
"@logto/shared": "workspace:^3.1.0",
|
"@logto/shared": "workspace:^3.1.0",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@parcel/compressor-brotli": "2.9.3",
|
"@parcel/compressor-brotli": "2.9.3",
|
||||||
"@parcel/compressor-gzip": "2.9.3",
|
"@parcel/compressor-gzip": "2.9.3",
|
||||||
"@parcel/core": "2.9.3",
|
"@parcel/core": "2.9.3",
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { type EditorProps } from '@monaco-editor/react';
|
||||||
|
|
||||||
|
import type { IStandaloneThemeData } from './type';
|
||||||
|
|
||||||
|
// Logto dark theme extends vs-dark theme
|
||||||
|
export const logtoDarkTheme: IStandaloneThemeData = {
|
||||||
|
base: 'vs-dark',
|
||||||
|
inherit: true,
|
||||||
|
rules: [],
|
||||||
|
colors: {
|
||||||
|
'editor.background': '#181133', // :token/code/code-bg
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// @see {@link https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html}
|
||||||
|
export const defaultOptions: EditorProps['options'] = {
|
||||||
|
minimap: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
wordWrap: 'on',
|
||||||
|
renderLineHighlight: 'none',
|
||||||
|
fontFamily: 'Roboto Mono, monospace',
|
||||||
|
fontSize: 14,
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
@use '@/scss/underscore' as _;
|
||||||
|
@use '@/scss/code-editor' as codeEditor;
|
||||||
|
|
||||||
|
.codeEditor {
|
||||||
|
@include codeEditor.color;
|
||||||
|
@include codeEditor.font;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: var(--color-code-bg);
|
||||||
|
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: _.unit(4);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.tabList {
|
||||||
|
display: flex;
|
||||||
|
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;
|
||||||
|
|
||||||
|
&.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);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorContainer {
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-bottom: _.unit(4);
|
||||||
|
}
|
||||||
|
}
|
126
packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx
Normal file
126
packages/console/src/pages/JwtClaims/MonacoCodeEditor/index.tsx
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
import { Editor, type BeforeMount, type OnMount, useMonaco } from '@monaco-editor/react';
|
||||||
|
import { type Nullable } from '@silverhand/essentials';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import Copy from '@/assets/icons/copy.svg';
|
||||||
|
import IconButton from '@/ds-components/IconButton';
|
||||||
|
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';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
actions?: React.ReactNode;
|
||||||
|
models: Model[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function MonacoCodeEditor({ className, actions, models }: Props) {
|
||||||
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
const monaco = useMonaco();
|
||||||
|
const editorRef = useRef<Nullable<IStandaloneCodeEditor>>(null);
|
||||||
|
|
||||||
|
const [activeModelName, setActiveModelName] = useState<string>();
|
||||||
|
const activeModel = useMemo(
|
||||||
|
() => models.find((model) => model.name === activeModelName),
|
||||||
|
[activeModelName, models]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the first model as the active model
|
||||||
|
useEffect(() => {
|
||||||
|
setActiveModelName(models[0]?.name);
|
||||||
|
}, [models]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Add global declarations
|
||||||
|
// monaco will be ready after the editor is mounted, useEffect will be called after the monaco is ready
|
||||||
|
if (!monaco || !activeModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the global declarations for the active model
|
||||||
|
// @see {@link https://microsoft.github.io/monaco-editor/typedoc/interfaces/languages.typescript.LanguageServiceDefaults.html#setExtraLibs}
|
||||||
|
if (activeModel.globalDeclarations) {
|
||||||
|
monaco.languages.typescript.typescriptDefaults.setExtraLibs([
|
||||||
|
{
|
||||||
|
content: activeModel.globalDeclarations,
|
||||||
|
filePath: `file:///global.d.ts`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}, [activeModel, monaco]);
|
||||||
|
|
||||||
|
const handleCodeCopy = useCallback(async () => {
|
||||||
|
const editor = editorRef.current;
|
||||||
|
|
||||||
|
if (editor) {
|
||||||
|
const code = editor.getValue();
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
toast.success(t('general.copied'));
|
||||||
|
}
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
const handleEditorWillMount = useCallback<BeforeMount>((monaco) => {
|
||||||
|
// Register the new logto theme
|
||||||
|
monaco.editor.defineTheme('logto-dark', logtoDarkTheme);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleEditorDidMount = useCallback<OnMount>((editor) => {
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||||
|
editorRef.current = editor;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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={() => {
|
||||||
|
setActiveModelName(name);
|
||||||
|
}}
|
||||||
|
onKeyDown={onKeyDownHandler(() => {
|
||||||
|
setActiveModelName(name);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className={styles.title}>{activeModel?.title}</div>
|
||||||
|
)}
|
||||||
|
<div className={styles.actions}>
|
||||||
|
{actions}
|
||||||
|
<IconButton size="small" onClick={handleCodeCopy}>
|
||||||
|
<Copy />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div className={styles.editorContainer}>
|
||||||
|
<Editor
|
||||||
|
height="100%"
|
||||||
|
language="typescript"
|
||||||
|
// TODO: need to check on the usage of value and defaultValue
|
||||||
|
defaultValue={activeModel?.defaultValue}
|
||||||
|
path={activeModel?.name}
|
||||||
|
theme="logto-dark"
|
||||||
|
options={defaultOptions}
|
||||||
|
beforeMount={handleEditorWillMount}
|
||||||
|
onMount={handleEditorDidMount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MonacoCodeEditor;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { type Monaco, type OnMount } from '@monaco-editor/react';
|
||||||
|
|
||||||
|
export type IStandaloneThemeData = Parameters<Monaco['editor']['defineTheme']>[1];
|
||||||
|
|
||||||
|
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 title of the model */
|
||||||
|
title: string;
|
||||||
|
defaultValue: string;
|
||||||
|
language: string;
|
||||||
|
globalDeclarations?: string;
|
||||||
|
};
|
118
packages/console/src/pages/JwtClaims/config.ts
Normal file
118
packages/console/src/pages/JwtClaims/config.ts
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import type { Model } from './MonacoCodeEditor/type.js';
|
||||||
|
|
||||||
|
const userJwtGlobalDeclarations = `
|
||||||
|
declare global {
|
||||||
|
export interface CustomJwtClaims extends Record<string, any> {}
|
||||||
|
|
||||||
|
/** The user info associated with the token.
|
||||||
|
*
|
||||||
|
* @param {string} id - The user id
|
||||||
|
* @param {string} [primaryEmail] - The user email
|
||||||
|
* @param {string} [primaryPhone] - The user phone
|
||||||
|
* @param {string} [username] - The user username
|
||||||
|
* @param {string} [name] - The user name
|
||||||
|
* @param {string} [avatar] - The user avatar
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = {
|
||||||
|
id: string;
|
||||||
|
primaryEmail?: string;
|
||||||
|
primaryPhone?: string;
|
||||||
|
username?: string;
|
||||||
|
name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logto internal data that can be used to pass additional information
|
||||||
|
* @param {User} user - The user info associated with the token.
|
||||||
|
*/
|
||||||
|
export type Data = {
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Exports {
|
||||||
|
/**
|
||||||
|
* This function is called to get custom claims for the JWT token.
|
||||||
|
*
|
||||||
|
* @param {string} token -The JWT token.
|
||||||
|
* @param {Data} data - Logto internal data that can be used to pass additional information
|
||||||
|
* @param {User} data.user - The user info associated with the token.
|
||||||
|
*
|
||||||
|
* @returns The custom claims.
|
||||||
|
*/
|
||||||
|
getCustomJwtClaims: (token: string, data: Data) => Promise<CustomJwtClaims>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exports: Exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { exports as default };
|
||||||
|
`;
|
||||||
|
|
||||||
|
const machineToMachineJwtGlobalDeclarations = `
|
||||||
|
declare global {
|
||||||
|
export interface CustomJwtClaims extends Record<string, any> {}
|
||||||
|
|
||||||
|
export interface Exports {
|
||||||
|
/**
|
||||||
|
* This function is called to get custom claims for the JWT token.
|
||||||
|
*
|
||||||
|
* @param {string} token -The JWT token.
|
||||||
|
*
|
||||||
|
* @returns The custom claims.
|
||||||
|
*/
|
||||||
|
getCustomJwtClaims: (token: string) => Promise<CustomJwtClaims>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exports: Exports;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { exports as default };
|
||||||
|
`;
|
||||||
|
|
||||||
|
const defaultUserJwtClaimsCode = `/**
|
||||||
|
* This function is called to get custom claims for the JWT token.
|
||||||
|
*
|
||||||
|
* @param {string} token -The JWT token.
|
||||||
|
* @param {Data} data - Logto internal data that can be used to pass additional information
|
||||||
|
* @param {User} data.user - The user info associated with the token.
|
||||||
|
*
|
||||||
|
* @returns The custom claims.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.getCustomJwtClaims = async (token, data) => {
|
||||||
|
return {};
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const defaultMachineToMachineJwtClaimsCode = `/**
|
||||||
|
* This function is called to get custom claims for the JWT token.
|
||||||
|
*
|
||||||
|
* @param {string} token -The JWT token.
|
||||||
|
*
|
||||||
|
* @returns The custom claims.
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.getCustomJwtClaims = async (token) => {
|
||||||
|
return {};
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export const userJwtFile: Model = {
|
||||||
|
name: 'user-jwt.ts',
|
||||||
|
title: 'TypeScript',
|
||||||
|
language: 'typescript',
|
||||||
|
defaultValue: defaultUserJwtClaimsCode,
|
||||||
|
globalDeclarations: userJwtGlobalDeclarations,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const machineToMachineJwtFile: Model = {
|
||||||
|
name: 'machine-to-machine-jwt.ts',
|
||||||
|
title: 'TypeScript',
|
||||||
|
language: 'typescript',
|
||||||
|
defaultValue: defaultMachineToMachineJwtClaimsCode,
|
||||||
|
globalDeclarations: machineToMachineJwtGlobalDeclarations,
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum JwtTokenType {
|
||||||
|
UserAccessToken = 'user-access-token',
|
||||||
|
MachineToMachineAccessToken = 'm2m-access-token',
|
||||||
|
}
|
|
@ -5,8 +5,39 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.cardTitle {
|
.header {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-bottom: _.unit(4);
|
margin-bottom: _.unit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabNav {
|
||||||
|
margin-bottom: _.unit(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: _.unit(2);
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.codePanel {
|
||||||
|
margin-bottom: _.unit(6);
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.cardTitle {
|
||||||
|
font: var(--font-title-2);
|
||||||
|
margin-bottom: _.unit(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexGrow {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,73 @@
|
||||||
import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
|
import { withAppInsights } from '@logto/app-insights/react/AppInsightsReact';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import type { TFuncKey } from 'i18next';
|
||||||
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import Card from '@/ds-components/Card';
|
||||||
import CardTitle from '@/ds-components/CardTitle';
|
import CardTitle from '@/ds-components/CardTitle';
|
||||||
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
import TabNav, { TabNavItem } from '@/ds-components/TabNav';
|
||||||
|
|
||||||
|
import MonacoCodeEditor from './MonacoCodeEditor';
|
||||||
|
import { type Model } from './MonacoCodeEditor/type';
|
||||||
|
import { userJwtFile, machineToMachineJwtFile, JwtTokenType } from './config';
|
||||||
import * as styles from './index.module.scss';
|
import * as styles from './index.module.scss';
|
||||||
import { JwtTokenType } from './type';
|
|
||||||
|
|
||||||
export * from './type';
|
export { JwtTokenType } from './config';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
tab: JwtTokenType;
|
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}`;
|
const getPath = (tab: JwtTokenType) => `/jwt-claims/${tab}`;
|
||||||
|
|
||||||
function JwtClaims({ tab }: Props) {
|
function JwtClaims({ tab }: Props) {
|
||||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||||
|
|
||||||
|
// TODO: API integration, read/write the custom claims code value
|
||||||
|
const activeModel = useMemo<Model>(() => {
|
||||||
|
return tab === JwtTokenType.UserAccessToken ? userJwtFile : machineToMachineJwtFile;
|
||||||
|
}, [tab]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<CardTitle
|
<CardTitle
|
||||||
title="jwt_claims.title"
|
title="jwt_claims.title"
|
||||||
subtitle="jwt_claims.description"
|
subtitle="jwt_claims.description"
|
||||||
className={styles.cardTitle}
|
className={styles.header}
|
||||||
/>
|
/>
|
||||||
<TabNav>
|
<TabNav className={styles.tabNav}>
|
||||||
<TabNavItem
|
{Object.values(JwtTokenType).map((tokenType) => (
|
||||||
href={getPath(JwtTokenType.UserAccessToken)}
|
<TabNavItem key={tokenType} href={getPath(tokenType)} isActive={tokenType === tab}>
|
||||||
isActive={tab === JwtTokenType.UserAccessToken}
|
{t(`jwt_claims.${phrases.tab[tokenType]}`)}
|
||||||
>
|
</TabNavItem>
|
||||||
{t('jwt_claims.user_jwt_tab')}
|
))}
|
||||||
</TabNavItem>
|
|
||||||
<TabNavItem
|
|
||||||
href={getPath(JwtTokenType.MachineToMachineAccessToken)}
|
|
||||||
isActive={tab === JwtTokenType.MachineToMachineAccessToken}
|
|
||||||
>
|
|
||||||
{t('jwt_claims.machine_to_machine_jwt_tab')}
|
|
||||||
</TabNavItem>
|
|
||||||
</TabNav>
|
</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>
|
||||||
|
<div>Form Panel</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
export enum JwtTokenType {
|
|
||||||
UserAccessToken = 'user-access-token',
|
|
||||||
MachineToMachineAccessToken = 'm2m-access-token',
|
|
||||||
}
|
|
10
packages/console/src/scss/_code-editor.scss
Normal file
10
packages/console/src/scss/_code-editor.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
@mixin color {
|
||||||
|
--color-code-bg: #181133;
|
||||||
|
--color-code-white: var(--color-base);
|
||||||
|
--color-code-grey: #adaab4;
|
||||||
|
--color-code-tab-active-bg: rgba(255, 255, 255, 24%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin font {
|
||||||
|
--font-code: 500 14px/20px 'Roboto Mono', monospace;
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ body {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: auto;
|
-moz-osx-font-smoothing: auto;
|
||||||
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
|
@ -39,6 +39,14 @@ export default function koaSecurityHeaders<StateT, ContextT, ResponseBodyT>(
|
||||||
const developmentOrigins = conditionalArray(!isProduction && 'ws:');
|
const developmentOrigins = conditionalArray(!isProduction && 'ws:');
|
||||||
const appInsightsOrigins = ['https://*.applicationinsights.azure.com'];
|
const appInsightsOrigins = ['https://*.applicationinsights.azure.com'];
|
||||||
|
|
||||||
|
// We use react-monaco-editor for code editing in the admin console. It loads the monaco editor asynchronously from a CDN.
|
||||||
|
// Allow the CDN src in the CSP.
|
||||||
|
// Allow blob: for monaco editor to load worker scripts
|
||||||
|
const monacoEditorCDNSource = [
|
||||||
|
'https://cdn.jsdelivr.net/npm/monaco-editor@0.43.0/min/vs/',
|
||||||
|
'blob:',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Applied rules:
|
* Default Applied rules:
|
||||||
* - crossOriginOpenerPolicy: https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#cross-origin-opener-policy-coop
|
* - crossOriginOpenerPolicy: https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#cross-origin-opener-policy-coop
|
||||||
|
@ -107,6 +115,7 @@ export default function koaSecurityHeaders<StateT, ContextT, ResponseBodyT>(
|
||||||
scriptSrc: [
|
scriptSrc: [
|
||||||
"'self'",
|
"'self'",
|
||||||
...conditionalArray(!isProduction && ["'unsafe-eval'", "'unsafe-inline'"]),
|
...conditionalArray(!isProduction && ["'unsafe-eval'", "'unsafe-inline'"]),
|
||||||
|
...monacoEditorCDNSource,
|
||||||
],
|
],
|
||||||
connectSrc: [
|
connectSrc: [
|
||||||
"'self'",
|
"'self'",
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -3,7 +3,10 @@ const jwt_claims = {
|
||||||
description:
|
description:
|
||||||
'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
|
'Set up custom JWT claims to include in the access token. These claims can be used to pass additional information to your application.',
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -7,7 +7,13 @@ const jwt_claims = {
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
user_jwt_tab: 'User JWT',
|
user_jwt_tab: 'User JWT',
|
||||||
/** UNTRANSLATED */
|
/** UNTRANSLATED */
|
||||||
machine_to_machine_jwt_tab: 'Machine-to-Machine JWT',
|
machine_to_machine_jwt_tab: 'Machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
user_jwt: 'user JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
machine_to_machine_jwt: 'machine-to-machine JWT',
|
||||||
|
/** UNTRANSLATED */
|
||||||
|
code_editor_title: 'Customize the {{token}} claims',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Object.freeze(jwt_claims);
|
export default Object.freeze(jwt_claims);
|
||||||
|
|
|
@ -2948,6 +2948,9 @@ importers:
|
||||||
'@mdx-js/react':
|
'@mdx-js/react':
|
||||||
specifier: ^1.6.22
|
specifier: ^1.6.22
|
||||||
version: 1.6.22(react@18.2.0)
|
version: 1.6.22(react@18.2.0)
|
||||||
|
'@monaco-editor/react':
|
||||||
|
specifier: ^4.6.0
|
||||||
|
version: 4.6.0(monaco-editor@0.46.0)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@parcel/compressor-brotli':
|
'@parcel/compressor-brotli':
|
||||||
specifier: 2.9.3
|
specifier: 2.9.3
|
||||||
version: 2.9.3(@parcel/core@2.9.3)
|
version: 2.9.3(@parcel/core@2.9.3)
|
||||||
|
@ -7933,6 +7936,28 @@ packages:
|
||||||
json5: 2.2.1
|
json5: 2.2.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@monaco-editor/loader@1.4.0(monaco-editor@0.46.0):
|
||||||
|
resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==}
|
||||||
|
peerDependencies:
|
||||||
|
monaco-editor: '>= 0.21.0 < 1'
|
||||||
|
dependencies:
|
||||||
|
monaco-editor: 0.46.0
|
||||||
|
state-local: 1.0.7
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@monaco-editor/react@4.6.0(monaco-editor@0.46.0)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==}
|
||||||
|
peerDependencies:
|
||||||
|
monaco-editor: '>= 0.25.0 < 1'
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@monaco-editor/loader': 1.4.0(monaco-editor@0.46.0)
|
||||||
|
monaco-editor: 0.46.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2:
|
/@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2:
|
||||||
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
|
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
|
@ -17200,6 +17225,10 @@ packages:
|
||||||
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
|
resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/monaco-editor@0.46.0:
|
||||||
|
resolution: {integrity: sha512-ADwtLIIww+9FKybWscd7OCfm9odsFYHImBRI1v9AviGce55QY8raT+9ihH8jX/E/e6QVSGM+pKj4jSUSRmALNQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ms@2.1.2:
|
/ms@2.1.2:
|
||||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
|
|
||||||
|
@ -20081,6 +20110,10 @@ packages:
|
||||||
escape-string-regexp: 2.0.0
|
escape-string-regexp: 2.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/state-local@1.0.7:
|
||||||
|
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/state-toggle@1.0.3:
|
/state-toggle@1.0.3:
|
||||||
resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==}
|
resolution: {integrity: sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
Loading…
Reference in a new issue