0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-03 22:15:32 -05:00

feat(console): add xml text file reader (#4936)

This commit is contained in:
Darcy Ye 2023-11-23 11:34:15 +08:00 committed by GitHub
parent a376b71ae7
commit 979bf08966
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 245 additions and 24 deletions

View file

@ -0,0 +1,7 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M9 0C6.79086 0 5 1.79086 5 4V36C5 38.2091 6.79086 40 9 40H31C33.2091 40 35 38.2091 35 36V10L25 0H9Z" fill="#CABEFF"/>
<path d="M25 0L35 10H27C25.8954 10 25 9.10457 25 8V0Z" fill="#AF9EFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.3165 16.5516C22.8404 16.7262 23.1236 17.2926 22.949 17.8165L19.449 28.3165C19.2743 28.8404 18.708 29.1236 18.184 28.949C17.6601 28.7743 17.3769 28.208 17.5516 27.684L21.0516 17.184C21.2262 16.6601 21.7926 16.3769 22.3165 16.5516Z" fill="#947DFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.6332 16.7261C16.0607 17.0758 16.1237 17.7058 15.774 18.1333L11.8174 22.969L15.7433 27.3311C16.1128 27.7416 16.0795 28.3739 15.669 28.7433C15.2585 29.1128 14.6262 29.0795 14.2567 28.669L9.75671 23.669C9.42642 23.302 9.4134 22.7489 9.72604 22.3668L14.226 16.8668C14.5758 16.4393 15.2058 16.3763 15.6332 16.7261Z" fill="#947DFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.3668 16.7261C23.9393 17.0758 23.8763 17.7058 24.226 18.1333L28.1826 22.969L24.2567 27.3311C23.8872 27.7416 23.9205 28.3739 24.331 28.7433C24.7415 29.1128 25.3738 29.0795 25.7433 28.669L30.2433 23.669C30.5736 23.302 30.5866 22.7489 30.274 22.3668L25.774 16.8668C25.4242 16.4393 24.7942 16.3763 24.3668 16.7261Z" fill="#947DFF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,6 +1,6 @@
import { type SsoProviderName } from '@logto/schemas';
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useFormContext, Controller } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import FormField from '@/ds-components/FormField';
@ -13,6 +13,8 @@ import {
type SsoConnectorConfig,
} from '@/pages/EnterpriseSso/types.js';
import XmlFileReader from '../XmlFileReader';
import ParsedConfigPreview from './ParsedConfigPreview';
import SwitchFormatButton, { FormFormat } from './SwitchFormatButton';
import * as styles from './index.module.scss';
@ -37,6 +39,7 @@ function SamlMetadataFormFields({
}: SamlMetadataFormFieldsProps) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
control,
register,
formState: { errors },
} = useFormContext<SamlGuideFormType>();
@ -79,14 +82,13 @@ function SamlMetadataFormFields({
case FormFormat.Xml: {
return (
<>
<FormField
isRequired={isFieldCheckRequired}
title="enterprise_sso.metadata.saml.metadata_xml_field_name"
>
<Textarea
rows={5}
{...register('metadata', { required: isFieldCheckRequired })}
error={Boolean(errors.metadata)}
<FormField title="enterprise_sso.metadata.saml.metadata_xml_field_name">
<Controller
control={control}
name="metadata"
render={({ field: { onChange, value } }) => (
<XmlFileReader value={value} onChange={onChange} />
)}
/>
</FormField>
{/* Since we will reset all other fields except for the current one when switching to another configuration form format, we only show parsed config preview when the result comes from the current form format. */}

View file

@ -0,0 +1,6 @@
// In Bytes.
export const getXmlFileSize = (xmlContent: string): number => {
const blob = new Blob([xmlContent], { type: 'application/xml' });
const file = new File([blob], 'identity provider metadata.xml');
return file.size;
};

View file

@ -0,0 +1,60 @@
@use '@/scss/underscore' as _;
.fileInput {
display: none;
}
.preview {
display: flex;
max-width: 400px;
align-items: center;
flex-direction: row;
padding: _.unit(5) _.unit(5) _.unit(5) _.unit(4);
background: var(--color-layer-1);
color: var(--color-text);
border: 1px solid var(--color-border);
border-radius: _.unit(3);
.fileInfo {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: _.unit(0.5);
flex: 1;
margin-left: _.unit(2);
.fileName {
font: var(--font-label-2);
}
.fileSize {
font: var(--font-label-3);
color: var(--color-text-secondary);
}
}
.delete {
display: flex;
width: 28px;
height: 28px;
border-radius: _.unit(1.5);
border: none;
padding: _.unit(1);
background-color: transparent;
.icon {
color: var(--color-text-secondary);
}
&:hover {
background: var(--color-hover);
border-radius: 6px;
}
}
}
.error {
font: var(--font-body-2);
color: var(--color-error);
margin-top: _.unit(1);
}

View file

@ -0,0 +1,117 @@
import { conditional } from '@silverhand/essentials';
import { useCallback, useState } from 'react';
import { useDropzone, type FileRejection } from 'react-dropzone';
import { useFormContext } from 'react-hook-form';
import Delete from '@/assets/icons/delete.svg';
import FileIcon from '@/assets/icons/file-icon.svg';
import UploaderIcon from '@/assets/icons/upload.svg';
import Button from '@/ds-components/Button';
import IconButton from '@/ds-components/IconButton';
import { type SamlGuideFormType } from '../../types';
import { getXmlFileSize } from '../SamlMetadataForm/utils';
import * as styles from './index.module.scss';
const xmlMimeTypes = ['application/xml', 'text/xml'];
const xmlFileName = 'identity provider metadata.xml'; // Real file name does not matter, use a generic name.
const xmlFileSizeLimit = 500 * 1024; // 500 KB
type Props = {
onChange: (xmlContent?: string) => void;
value?: string;
};
function XmlFileReader({ onChange, value }: Props) {
/**
* It's ok to use 0 as a fallback file size because it will not be displayed if the value is empty.
*/
const [fileSize, setFileSize] = useState<number>(
conditional(value && getXmlFileSize(value)) ?? 0
);
const {
setError,
formState: {
errors: { metadata: metadataError },
},
} = useFormContext<SamlGuideFormType>();
/**
* As you can see, per `useDropzone` hook's config, there are at most one file, if file is rejected, then we can return as long as we get the error message.
*/
const onDrop = useCallback(
async (acceptedFiles: File[] = [], fileRejection: FileRejection[] = []) => {
if (fileRejection.length > 0) {
const fileErrors = fileRejection[0]?.errors;
if (fileErrors?.[0]?.message) {
setError('metadata', {
type: 'custom',
message: fileErrors[0]?.message,
});
}
return;
}
const acceptedFile = acceptedFiles[0];
if (!acceptedFile) {
return;
}
setFileSize(acceptedFile.size);
const xmlContent = await acceptedFile.text();
onChange(xmlContent);
},
[onChange, setError]
);
const handleRemove = () => {
onChange(undefined);
};
const { getRootProps, getInputProps } = useDropzone({
onDrop,
noDrag: true, // Only allow file selection via the file input.
maxFiles: 1,
maxSize: xmlFileSizeLimit,
multiple: false, // Upload only one file at a time.
accept: Object.fromEntries(xmlMimeTypes.map((mimeType) => [mimeType, []])),
});
return (
<div>
{value ? (
<div className={styles.preview}>
<FileIcon />
<div className={styles.fileInfo}>
<span className={styles.fileName}>{xmlFileName}</span>
<span className={styles.fileSize}>{`${(fileSize / 1024).toFixed(2)} KB`}</span>
</div>
<IconButton
className={styles.delete}
onClick={() => {
handleRemove();
}}
>
<Delete className={styles.icon} />
</IconButton>
</div>
) : (
<>
<div {...getRootProps()}>
<Button
icon={<UploaderIcon />}
title="enterprise_sso_details.upload_idp_metadata_button_text"
size="large"
/>
<input {...getInputProps({ className: styles.fileInput })} />
</div>
{Boolean(metadataError) && <div className={styles.error}>{metadataError?.message}</div>}
</>
)}
</div>
);
}
export default XmlFileReader;

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -35,6 +35,7 @@ const enterprise_sso_details = {
'Are you sure you want to delete this enterprise connector? Users from identity providers will not utilize Single Sign-On.',
upload_idp_metadata_title: 'Upload IdP metadata',
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
upload_idp_metadata_button_text: 'Upload metadata XML file',
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
upload_saml_idp_metadata_info_text_xml:

View file

@ -55,7 +55,7 @@ const enterprise_sso = {
metadata_url_field_name: 'Metadata URL',
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
metadata_xml_uploader_text: 'Upload metadata XML file',
sign_in_endpoint_field_name: 'Sign on URL',
idp_entity_id_field_name: 'IdP entity ID (Issuer)',

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -77,7 +77,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -78,7 +78,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -77,7 +77,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -77,7 +77,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */

View file

@ -67,6 +67,8 @@ const enterprise_sso_details = {
/** UNTRANSLATED */
upload_idp_metadata_description: 'Configure the metadata copied from the identity provider.',
/** UNTRANSLATED */
upload_idp_metadata_button_text: 'Upload metadata XML file',
/** UNTRANSLATED */
upload_saml_idp_metadata_info_text_url:
'Paste the metadata URL from the identity provider to connect.',
/** UNTRANSLATED */

View file

@ -77,7 +77,7 @@ const enterprise_sso = {
metadata_url_description:
'Dynamically fetch data from the metadata URL and keep certificate up to date.',
/** UNTRANSLATED */
metadata_xml_field_name: 'Metadata XML file',
metadata_xml_field_name: 'IdP metadata XML file',
/** UNTRANSLATED */
metadata_xml_uploader_text: 'Upload metadata XML file',
/** UNTRANSLATED */