From 27a3eeb83510d0244f1403b2d5403b661cde76b7 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Mon, 6 May 2024 16:44:03 +0500 Subject: [PATCH 1/4] reduce div wrappers around UICompBuilder --- .../src/comps/generators/uiCompBuilder.tsx | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx b/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx index c3c189c27..f5808ef38 100644 --- a/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx +++ b/client/packages/lowcoder/src/comps/generators/uiCompBuilder.tsx @@ -54,22 +54,6 @@ export function HidableView(props: { } } -export function ExtendedComponentView(props: { - children: JSX.Element | React.ReactNode; - className: string; - dataTestId: string; -}) { - if (!props.className && !props.dataTestId) { - return <>{props.children}; - } - - return ( -
- {props.children} -
- ); -} - export function ExtendedPropertyView< ChildrenCompMap extends Record>, >(props: { @@ -196,7 +180,13 @@ export class UICompBuilder< } override getView(): ViewReturn { - return (
); + return ( + + ); } override getPropertyView(): ReactNode { @@ -223,7 +213,11 @@ export const DisabledContext = React.createContext(false); /** * Guaranteed to be in a react component, so that react hooks can be used internally */ -function UIView(props: { comp: any; viewFn: any }) { +function UIView(props: { + innerRef: React.RefObject; + comp: any; + viewFn: any; +}) { const comp = props.comp; const childrenProps = childrenToProps(comp.children); @@ -243,13 +237,22 @@ function UIView(props: { comp: any; viewFn: any }) { //END ADD BY FRED return ( - + data-testid={childrenProps.dataTestId as string} + style={{ + width: "100%", + height: "100%", + margin: "0px", + padding: "0px", + }}> - + ); } From abaf81f0449a80c7db5b2637b50e2f104342a44a Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 8 May 2024 11:25:05 +0500 Subject: [PATCH 2/4] added generic oauth modal --- .../setting/idSource/AddGenericOauthModal.tsx | 432 ++++++++++++++++++ .../genericOauthForms/IssuerEndpoint.tsx | 0 .../setting/idSource/idSourceConstants.ts | 21 + .../src/pages/setting/idSource/list.tsx | 53 ++- 4 files changed, 494 insertions(+), 12 deletions(-) create mode 100644 client/packages/lowcoder/src/pages/setting/idSource/AddGenericOauthModal.tsx create mode 100644 client/packages/lowcoder/src/pages/setting/idSource/genericOauthForms/IssuerEndpoint.tsx diff --git a/client/packages/lowcoder/src/pages/setting/idSource/AddGenericOauthModal.tsx b/client/packages/lowcoder/src/pages/setting/idSource/AddGenericOauthModal.tsx new file mode 100644 index 000000000..337cde38c --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/idSource/AddGenericOauthModal.tsx @@ -0,0 +1,432 @@ +import { useEffect, useMemo, useState } from "react"; +import { messageInstance, CustomSelect, CloseEyeIcon } from "lowcoder-design"; +import { + CustomModalStyled, +} from "../styled"; +import { trans } from "i18n"; +import { + FormStyled, + CheckboxStyled, + SpanStyled, + PasswordLabel +} from "./styledComponents"; +import { default as Form } from "antd/es/form"; +import { default as Input } from "antd/es/input"; +import { default as Select } from "antd/es/select"; +import { default as Tooltip } from "antd/es/tooltip"; +import IdSourceApi, { ConfigItem } from "api/idSourceApi"; +import { validateResponse } from "api/apiUtils"; +import { authConfig, AuthType, clientIdandSecretConfig, ItemType } from "./idSourceConstants"; +import { ServerAuthTypeInfo } from "constants/authConstants"; +import { GeneralLoginIcon } from "assets/icons"; +import _ from "lodash"; +import Steps from "antd/es/steps"; +import Flex from "antd/es/flex"; +import Button from "antd/es/button"; +import axios from "axios"; + +const steps = [ + { + title: 'Step 1', + description: 'Provider endpoint', + }, + { + title: 'Step 2', + description: 'Provider details', + }, + { + title: 'Step 3', + description: 'Mapping', + }, +]; + +interface OpenIdProvider { + issuer: string, + authorization_endpoint: string, + token_endpoint: string, + userinfo_endpoint: string, + jwks_uri?: string, + scopes_supported: string[], +} + +interface ConfigProvider { + authType: string, + source: string, + sourceName: string, + sourceDescription?: string, + sourceIcon?: string, + sourceCategory?: string, + issuerUri: string, + authorizationEndpoint: string, + tokenEndpoint: string, + userInfoEndpoint: string, + jwksUri?: string, + scope: string, +} + +type AddGenericOauthModalProp = { + modalVisible: boolean; + oauthProvidersList: AuthType[], + closeModal: () => void; + onConfigCreate: () => void; +}; + +function AddGenericOauthModal(props: AddGenericOauthModalProp) { + const { + modalVisible, + oauthProvidersList, + closeModal, + onConfigCreate + } = props; + const [form1] = Form.useForm(); + const [form2] = Form.useForm(); + const [form3] = Form.useForm(); + const [saveLoading, setSaveLoading] = useState(false); + const [currentStep, setCurrentStep] = useState(2); + const [issuerDetails, setIssuerDetails] = useState(); + + function saveAuthProvider(values: ConfigItem) { + setSaveLoading(true); + const config = { + ...values, + enableRegister: true, + } + IdSourceApi.saveConfig(config) + .then((resp) => { + if (validateResponse(resp)) { + messageInstance.success(trans("idSource.saveSuccess")); + } + }) + .catch((e) => messageInstance.error(e.message)) + .finally(() => { + setSaveLoading(false); + // onConfigCreate(); + }); + } + + const handleStep1Save = () => { + form1.validateFields().then(async (values) => { + setSaveLoading(true); + const { issuerEndpoint } = values; + const res = await axios.get(`${issuerEndpoint}/.well-known/openid-configuration`); + setSaveLoading(false); + + if (res.status >= 400) { + return null; + } + setIssuerDetails(() => { + const issuer = { + authType: AuthType.Generic, + source: '', + sourceName: '', + issuerUri: res.data.issuer, + authorizationEndpoint: res.data.authorization_endpoint, + tokenEndpoint: res.data.token_endpoint, + userInfoEndpoint: res.data.userinfo_endpoint, + jwksUri: res.data.jwks_uri, + scope: res.data.scopes_supported.join(','), + }; + form2.setFieldsValue(issuer); + return issuer; + }) + setCurrentStep(currentStep => currentStep + 1); + }) + } + + const handleStep2Save = () => { + form2.validateFields().then(values => { + // saveAuthProvider(values) + setIssuerDetails(issuerDetails => ({ + ...issuerDetails, + ...values, + })) + setCurrentStep(currentStep => currentStep + 1); + }) + } + + const handleStep3Save = () => { + form3.validateFields().then(values => { + setIssuerDetails((issuerDetails: any) => { + const updatedDetails = { + ...issuerDetails, + sourceMappings: { + ...values, + } + // ...values, + }; + // saveAuthProvider(updatedDetails); + console.log('save details', updatedDetails); + return updatedDetails; + }); + }) + } + + const handleSave = () => { + if (currentStep === 0) { + return handleStep1Save(); + } + if (currentStep === 1) { + return handleStep2Save(); + } + if (currentStep === 2) { + return handleStep3Save(); + } + } + + function handleCancel() { + if (currentStep === 0) { + closeModal(); + return form1.resetFields(); + } + setCurrentStep(currentStep => currentStep - 1); + } + + const authConfigOptions = Object.values(authConfig) + .filter(config => { + return !(oauthProvidersList.indexOf(config.sourceValue) > -1) + }) + .map(config => ({ + label: config.sourceName, + value: config.sourceValue, + })); + + const selectedAuthType = Form.useWatch('authType', form1);; + + const authConfigForm = authConfig[AuthType.Generic].form; + + // useEffect(() => { + // if (!issuerDetails) return; + + // console.log(issuerDetails); + // form2.setFieldsValue({ + // ...issuerDetails, + // }); + // }, [issuerDetails]); + + return ( + { + setCurrentStep(0); + form1.resetFields(); + form2.resetFields(); + }} + > + + + {currentStep === 0 && ( + + + + + + + + + + )} + {currentStep === 1 && ( + <> + + {Object.entries(authConfigForm).map(([key, value]) => { + const valueObject = _.isObject(value) ? (value as ItemType) : false; + const required = true; + const label = valueObject ? valueObject.label : value as string; + const tip = valueObject && valueObject.tip; + const isPassword = valueObject && valueObject.isPassword; + return ( +
+ + {label}: + + + ) : ( + + {label}: + + ) + } + > + {isPassword ? ( + + ) : ( + + )} + +
+ ); + })} +
+ + + + + + )} + { currentStep === 2 && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
+ ); +} + +export default AddGenericOauthModal; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/genericOauthForms/IssuerEndpoint.tsx b/client/packages/lowcoder/src/pages/setting/idSource/genericOauthForms/IssuerEndpoint.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts b/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts index 2dd789041..252e29a1d 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts +++ b/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts @@ -7,6 +7,7 @@ export enum AuthType { Github = "GITHUB", Ory = "ORY", KeyCloak = "KEYCLOAK", + Generic = "GENERIC", } export const IdSource = [ @@ -62,6 +63,26 @@ export const authConfig = { scope: "Scope", }, }, + [AuthType.Generic]: { + sourceName: "Generic", + sourceValue: AuthType.Generic, + form: { + source: { label: "Source", isRequire: true }, + sourceName: { label: "Source Name", isRequire: true }, + sourceDescription: { label: "Source Description" }, + sourceIcon: { label: "Source Icon" }, + sourceCategory: { label: "Source Category" }, + ...clientIdandSecretConfig, + issuerUri: { label: 'Issuer URI', isRequired: true }, + authorizationEndpoint: { label: 'Authorization Endpoint', isRequired: true }, + tokenEndpoint: { label: 'Token Endpoint', isRequired: true }, + userInfoEndpoint: { label: 'UserInfo Endpoint', isRequired: true }, + // jwks: { label: 'Authorize URL', isRequired: true }, + scope: "Scope", + // baseUrl: "Base URL", + // realm: "Realm", + }, + }, } as { [key: string]: { sourceName: string; sourceValue: AuthType, form: FormItemType } }; export const FreeTypes = [AuthType.Google, AuthType.Github, AuthType.Form, AuthType.Ory, AuthType.KeyCloak]; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx index 14fd3a43a..c7e6b243b 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx @@ -35,6 +35,8 @@ import { currentOrgAdmin } from "../../../util/permissionUtils"; import CreateModal from "./createModal"; import _ from "lodash"; import { HelpText } from "components/HelpText"; +import Flex from "antd/es/flex"; +import AddGenericOauthModal from "./AddGenericOauthModal"; export const IdSourceList = (props: any) => { const user = useSelector(getUser); @@ -43,6 +45,7 @@ export const IdSourceList = (props: any) => { const [configs, setConfigs] = useState([]); const [fetching, setFetching] = useState(false); const [modalVisible, setModalVisible] = useState(false); + const [genericModalVisible, setGenericModalVisible] = useState(false); const enableEnterpriseLogin = useSelector(selectSystemConfig)?.featureFlag?.enableEnterpriseLogin; let protocol = window.location.protocol; @@ -91,18 +94,35 @@ export const IdSourceList = (props: any) => { <> - {trans("idSource.title")} - {currentOrgAdmin(user) && ( - } - onClick={() => - setModalVisible(true) - } - > - {"Add OAuth Provider"} - - )} + <> +
+ {trans("idSource.title")} +
+ + {currentOrgAdmin(user) && ( + } + onClick={() => + setGenericModalVisible(true) + } + > + {"Add Generic OAuth Provider"} + + )} + {currentOrgAdmin(user) && ( + } + onClick={() => + setModalVisible(true) + } + > + {"Add OAuth Provider"} + + )} + +
{ getConfigs(); }} /> + config.authType)} + closeModal={() => setGenericModalVisible(false)} + onConfigCreate={() => { + setGenericModalVisible(false); + // getConfigs(); + }} + /> ); }; From 32d2bb6f6506486c6c0c069c975fef86b852f9a9 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 8 May 2024 17:38:38 +0500 Subject: [PATCH 3/4] added generic oauth steps --- .../lowcoder/src/constants/authConstants.ts | 8 +- .../setting/idSource/AddGenericOauthModal.tsx | 432 ------------------ .../idSource/OAuthForms/GeneralOAuthForm.tsx | 147 ++++++ .../idSource/OAuthForms/GenericOAuthForm.tsx | 305 +++++++++++++ .../pages/setting/idSource/createModal.tsx | 119 ++--- .../genericOauthForms/IssuerEndpoint.tsx | 0 .../setting/idSource/idSourceConstants.ts | 5 +- .../src/pages/setting/idSource/list.tsx | 60 +-- .../setting/idSource/styledComponents.tsx | 20 +- 9 files changed, 524 insertions(+), 572 deletions(-) delete mode 100644 client/packages/lowcoder/src/pages/setting/idSource/AddGenericOauthModal.tsx create mode 100644 client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GeneralOAuthForm.tsx create mode 100644 client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx delete mode 100644 client/packages/lowcoder/src/pages/setting/idSource/genericOauthForms/IssuerEndpoint.tsx diff --git a/client/packages/lowcoder/src/constants/authConstants.ts b/client/packages/lowcoder/src/constants/authConstants.ts index 0207d6abf..6c19a0bf8 100644 --- a/client/packages/lowcoder/src/constants/authConstants.ts +++ b/client/packages/lowcoder/src/constants/authConstants.ts @@ -90,9 +90,9 @@ export const AuthRoutes: Array<{ path: string; component: React.ComponentType void; - onConfigCreate: () => void; -}; - -function AddGenericOauthModal(props: AddGenericOauthModalProp) { - const { - modalVisible, - oauthProvidersList, - closeModal, - onConfigCreate - } = props; - const [form1] = Form.useForm(); - const [form2] = Form.useForm(); - const [form3] = Form.useForm(); - const [saveLoading, setSaveLoading] = useState(false); - const [currentStep, setCurrentStep] = useState(2); - const [issuerDetails, setIssuerDetails] = useState(); - - function saveAuthProvider(values: ConfigItem) { - setSaveLoading(true); - const config = { - ...values, - enableRegister: true, - } - IdSourceApi.saveConfig(config) - .then((resp) => { - if (validateResponse(resp)) { - messageInstance.success(trans("idSource.saveSuccess")); - } - }) - .catch((e) => messageInstance.error(e.message)) - .finally(() => { - setSaveLoading(false); - // onConfigCreate(); - }); - } - - const handleStep1Save = () => { - form1.validateFields().then(async (values) => { - setSaveLoading(true); - const { issuerEndpoint } = values; - const res = await axios.get(`${issuerEndpoint}/.well-known/openid-configuration`); - setSaveLoading(false); - - if (res.status >= 400) { - return null; - } - setIssuerDetails(() => { - const issuer = { - authType: AuthType.Generic, - source: '', - sourceName: '', - issuerUri: res.data.issuer, - authorizationEndpoint: res.data.authorization_endpoint, - tokenEndpoint: res.data.token_endpoint, - userInfoEndpoint: res.data.userinfo_endpoint, - jwksUri: res.data.jwks_uri, - scope: res.data.scopes_supported.join(','), - }; - form2.setFieldsValue(issuer); - return issuer; - }) - setCurrentStep(currentStep => currentStep + 1); - }) - } - - const handleStep2Save = () => { - form2.validateFields().then(values => { - // saveAuthProvider(values) - setIssuerDetails(issuerDetails => ({ - ...issuerDetails, - ...values, - })) - setCurrentStep(currentStep => currentStep + 1); - }) - } - - const handleStep3Save = () => { - form3.validateFields().then(values => { - setIssuerDetails((issuerDetails: any) => { - const updatedDetails = { - ...issuerDetails, - sourceMappings: { - ...values, - } - // ...values, - }; - // saveAuthProvider(updatedDetails); - console.log('save details', updatedDetails); - return updatedDetails; - }); - }) - } - - const handleSave = () => { - if (currentStep === 0) { - return handleStep1Save(); - } - if (currentStep === 1) { - return handleStep2Save(); - } - if (currentStep === 2) { - return handleStep3Save(); - } - } - - function handleCancel() { - if (currentStep === 0) { - closeModal(); - return form1.resetFields(); - } - setCurrentStep(currentStep => currentStep - 1); - } - - const authConfigOptions = Object.values(authConfig) - .filter(config => { - return !(oauthProvidersList.indexOf(config.sourceValue) > -1) - }) - .map(config => ({ - label: config.sourceName, - value: config.sourceValue, - })); - - const selectedAuthType = Form.useWatch('authType', form1);; - - const authConfigForm = authConfig[AuthType.Generic].form; - - // useEffect(() => { - // if (!issuerDetails) return; - - // console.log(issuerDetails); - // form2.setFieldsValue({ - // ...issuerDetails, - // }); - // }, [issuerDetails]); - - return ( - { - setCurrentStep(0); - form1.resetFields(); - form2.resetFields(); - }} - > - - - {currentStep === 0 && ( - - - - - - - - - - )} - {currentStep === 1 && ( - <> - - {Object.entries(authConfigForm).map(([key, value]) => { - const valueObject = _.isObject(value) ? (value as ItemType) : false; - const required = true; - const label = valueObject ? valueObject.label : value as string; - const tip = valueObject && valueObject.tip; - const isPassword = valueObject && valueObject.isPassword; - return ( -
- - {label}: - - - ) : ( - - {label}: - - ) - } - > - {isPassword ? ( - - ) : ( - - )} - -
- ); - })} -
- - - - - - )} - { currentStep === 2 && ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - )} -
- ); -} - -export default AddGenericOauthModal; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GeneralOAuthForm.tsx b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GeneralOAuthForm.tsx new file mode 100644 index 000000000..968fcef67 --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GeneralOAuthForm.tsx @@ -0,0 +1,147 @@ +import { useMemo, useState } from "react"; +import { messageInstance, CloseEyeIcon } from "lowcoder-design"; +import { trans } from "i18n"; +import { + FormStyled, + PasswordLabel +} from "../styledComponents"; +import { default as Form } from "antd/es/form"; +import { default as Input } from "antd/es/input"; +import { default as Tooltip } from "antd/es/tooltip"; +import IdSourceApi, { ConfigItem } from "api/idSourceApi"; +import { validateResponse } from "api/apiUtils"; +import { authConfig, AuthType, clientIdandSecretConfig, ItemType } from "../idSourceConstants"; +import _ from "lodash"; +import Flex from "antd/es/flex"; +import Button from "antd/es/button"; + +type GeneralOAuthFormProp = { + authType: AuthType, + onSave: () => void; + onCancel: () => void; +}; + +function GeneralOAuthForm(props: GeneralOAuthFormProp) { + const { + authType, + onSave, + onCancel, + } = props; + const [form1] = Form.useForm(); + const [saveLoading, setSaveLoading] = useState(false); + + function saveAuthProvider(values: ConfigItem) { + setSaveLoading(true); + const config = { + ...values, + authType, + enableRegister: true, + } + IdSourceApi.saveConfig(config) + .then((resp) => { + if (validateResponse(resp)) { + messageInstance.success(trans("idSource.saveSuccess")); + } + }) + .catch((e) => messageInstance.error(e.message)) + .finally(() => { + setSaveLoading(false); + onSave(); + }); + } + + const handleSave = () => { + form1.validateFields().then(values => { + console.log(values); + saveAuthProvider(values); + }); + } + + function handleCancel() { + onCancel(); + } + + const authConfigForm = useMemo(() => { + if(!authConfig[authType]) return clientIdandSecretConfig; + return authConfig[authType].form; + }, [authType]) + + return ( + + {Object.entries(authConfigForm).map(([key, value]) => { + const valueObject = _.isObject(value) ? (value as ItemType) : false; + const required = true; + const label = valueObject ? valueObject.label : value; + const tip = valueObject && valueObject.tip; + const isPassword = valueObject && valueObject.isPassword; + return ( +
+ + {label}: + + + ) : ( + + {label}: + + ) + } + > + {isPassword ? ( + + ) : ( + + )} + +
+ ); + })} + + + + +
+ ); +} + +export default GeneralOAuthForm; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx new file mode 100644 index 000000000..04a217dc4 --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx @@ -0,0 +1,305 @@ +import { useState } from "react"; +import { messageInstance, CloseEyeIcon } from "lowcoder-design"; +import { trans } from "i18n"; +import { + FormStyled, + PasswordLabel, + StyledSteps +} from "../styledComponents"; +import { default as Form } from "antd/es/form"; +import { default as Input } from "antd/es/input"; +import { default as Tooltip } from "antd/es/tooltip"; +import IdSourceApi, { ConfigItem } from "api/idSourceApi"; +import { validateResponse } from "api/apiUtils"; +import { authConfig, AuthType, ItemType } from "../idSourceConstants"; +import _ from "lodash"; +import Flex from "antd/es/flex"; +import Button from "antd/es/button"; +import axios from "axios"; + +const sourceMappingKeys = [ + 'uid', + 'email', + 'username', + 'avatar', +]; + +const steps = [ + { + title: 'Step 1', + description: 'Issuer endpoint', + }, + { + title: 'Step 2', + description: 'Provider details', + }, + { + title: 'Step 3', + description: 'Mapping', + }, +]; + +interface OpenIdProvider { + issuer: string, + authorization_endpoint: string, + token_endpoint: string, + userinfo_endpoint: string, + jwks_uri?: string, + scopes_supported: string[], +} + +interface ConfigProvider { + authType: string, + source: string, + sourceName: string, + sourceDescription?: string, + sourceIcon?: string, + sourceCategory?: string, + issuer: string, + authorizationEndpoint: string, + tokenEndpoint: string, + userInfoEndpoint: string, + jwksUri?: string, + scope: string, + sourceMappings: any, +} + +type GenericOAuthFormProp = { + authType: AuthType, + onSave: () => void; + onCancel: () => void; +}; + +function GenericOAuthForm(props: GenericOAuthFormProp) { + const { + authType, + onSave, + onCancel + } = props; + const [form1] = Form.useForm(); + + const [saveLoading, setSaveLoading] = useState(false); + const [currentStep, setCurrentStep] = useState(0); + const [issuerDetails, setIssuerDetails] = useState(); + + function saveAuthProvider(values: ConfigItem) { + setSaveLoading(true); + const config = { + ...values, + enableRegister: true, + } + IdSourceApi.saveConfig(config) + .then((resp) => { + if (validateResponse(resp)) { + messageInstance.success(trans("idSource.saveSuccess")); + onSave(); + } + }) + .catch((e) => messageInstance.error(e.message)) + .finally(() => { + setSaveLoading(false); + }); + } + + const handleStep1Save = () => { + form1.validateFields().then(async (values) => { + setSaveLoading(true); + const { issuer } = values; + const res = await axios.get(`${issuer}/.well-known/openid-configuration`); + setSaveLoading(false); + + if (res.status >= 400) { + return null; + } + setIssuerDetails(() => { + const issuer = { + authType: AuthType.Generic, + source: '', + sourceName: '', + issuer: res.data.issuer, + authorizationEndpoint: res.data.authorization_endpoint, + tokenEndpoint: res.data.token_endpoint, + userInfoEndpoint: res.data.userinfo_endpoint, + jwksUri: res.data.jwks_uri, + scope: res.data.scopes_supported.join(','), + sourceMappings: sourceMappingKeys.map(sourceKey => ({ + [sourceKey]: sourceKey, + })) + }; + form1.setFieldsValue(issuer); + return issuer; + }) + setCurrentStep(currentStep => currentStep + 1); + }) + } + + const handleStep2Save = () => { + form1.validateFields().then(values => { + setIssuerDetails(issuerDetails => ({ + ...issuerDetails, + ...values, + })) + setCurrentStep(currentStep => currentStep + 1); + }) + } + + const handleStep3Save = () => { + form1.validateFields().then(values => { + setIssuerDetails((issuerDetails: any) => { + const updatedDetails = { + ...issuerDetails, + sourceMappings: { + ...values, + } + }; + saveAuthProvider(updatedDetails); + console.log('save details', updatedDetails); + return updatedDetails; + }); + }) + } + + const handleSave = () => { + if (currentStep === 0) { + return handleStep1Save(); + } + if (currentStep === 1) { + return handleStep2Save(); + } + if (currentStep === 2) { + return handleStep3Save(); + } + } + + function handleCancel() { + if (currentStep === 0) { + onCancel(); + return form1.resetFields(); + } + setCurrentStep(currentStep => currentStep - 1); + } + + const authConfigForm = authConfig[AuthType.Generic].form; + + return ( + <> + + + + {currentStep === 0 && ( + + + + )} + {currentStep === 1 && Object.entries(authConfigForm).map(([key, value]) => { + const valueObject = _.isObject(value) ? (value as ItemType) : false; + const required = true; + const label = valueObject ? valueObject.label : value as string; + const tip = valueObject && valueObject.tip; + const isPassword = valueObject && valueObject.isPassword; + return ( +
+ + {label}: + + + ) : ( + + {label}: + + ) + } + > + {isPassword ? ( + + ) : ( + + )} + +
+ ); + })} + {currentStep === 2 && sourceMappingKeys.map(sourceKey => ( + + + + + + + + ))} + + + + +
+ + ); +} + +export default GenericOAuthForm; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/createModal.tsx b/client/packages/lowcoder/src/pages/setting/idSource/createModal.tsx index 8b2106568..d154bfec0 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/createModal.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/createModal.tsx @@ -1,25 +1,20 @@ -import { useEffect, useMemo, useState } from "react"; -import { messageInstance, CustomSelect, CloseEyeIcon } from "lowcoder-design"; +import { CustomSelect } from "lowcoder-design"; import { CustomModalStyled, } from "../styled"; import { trans } from "i18n"; import { FormStyled, - CheckboxStyled, SpanStyled, - PasswordLabel } from "./styledComponents"; import { default as Form } from "antd/es/form"; -import { default as Input } from "antd/es/input"; import { default as Select } from "antd/es/select"; -import { default as Tooltip } from "antd/es/tooltip"; -import IdSourceApi, { ConfigItem } from "api/idSourceApi"; -import { validateResponse } from "api/apiUtils"; -import { authConfig, AuthType, clientIdandSecretConfig, ItemType } from "./idSourceConstants"; +import { authConfig, AuthType } from "./idSourceConstants"; import { ServerAuthTypeInfo } from "constants/authConstants"; import { GeneralLoginIcon } from "assets/icons"; import _ from "lodash"; +import GenericOAuthForm from "./OAuthForms/GenericOAuthForm"; +import GeneralOAuthForm from "./OAuthForms/GeneralOAuthForm"; type CreateModalProp = { modalVisible: boolean; @@ -36,31 +31,9 @@ function CreateModal(props: CreateModalProp) { onConfigCreate } = props; const [form] = Form.useForm(); - const [saveLoading, setSaveLoading] = useState(false); - const handleOk = () => { - form.validateFields().then(values => { - // console.log(values) - saveAuthProvider(values) - }) - } - function saveAuthProvider(values: ConfigItem) { - setSaveLoading(true); - const config = { - ...values, - enableRegister: true, - } - IdSourceApi.saveConfig(config) - .then((resp) => { - if (validateResponse(resp)) { - messageInstance.success(trans("idSource.saveSuccess")); - } - }) - .catch((e) => messageInstance.error(e.message)) - .finally(() => { - setSaveLoading(false); - onConfigCreate(); - }); + const handleSave = () => { + onConfigCreate(); } function handleCancel() { @@ -71,6 +44,7 @@ function CreateModal(props: CreateModalProp) { const authConfigOptions = Object.values(authConfig) .filter(config => { return !(oauthProvidersList.indexOf(config.sourceValue) > -1) + || config.sourceValue === AuthType.Generic }) .map(config => ({ label: config.sourceName, @@ -79,22 +53,14 @@ function CreateModal(props: CreateModalProp) { const selectedAuthType = Form.useWatch('authType', form);; - const authConfigForm = useMemo(() => { - if(!authConfig[selectedAuthType]) return clientIdandSecretConfig; - return authConfig[selectedAuthType].form; - }, [selectedAuthType]) - return ( form.resetFields()} > @@ -102,7 +68,7 @@ function CreateModal(props: CreateModalProp) { form={form} name="basic" layout="vertical" - style={{ maxWidth: 440 }} + style={{ maxWidth: '100%' }} autoComplete="off" > - {Object.entries(authConfigForm).map(([key, value]) => { - const valueObject = _.isObject(value) ? (value as ItemType) : false; - const required = true; - const label = valueObject ? valueObject.label : value; - const tip = valueObject && valueObject.tip; - const isPassword = valueObject && valueObject.isPassword; - return ( -
- - {label}: - - - ) : ( - - {label}: - - ) - } - > - {isPassword ? ( - - ) : ( - - )} - -
- ); - })} + + {selectedAuthType === AuthType.Generic && ( + + )} + + {selectedAuthType !== AuthType.Generic && ( + + )}
); } diff --git a/client/packages/lowcoder/src/pages/setting/idSource/genericOauthForms/IssuerEndpoint.tsx b/client/packages/lowcoder/src/pages/setting/idSource/genericOauthForms/IssuerEndpoint.tsx deleted file mode 100644 index e69de29bb..000000000 diff --git a/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts b/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts index 252e29a1d..069dfce28 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts +++ b/client/packages/lowcoder/src/pages/setting/idSource/idSourceConstants.ts @@ -16,6 +16,7 @@ export const IdSource = [ AuthType.Form, AuthType.Ory, AuthType.KeyCloak, + AuthType.Generic, ]; export const validatorOptions = []; @@ -73,7 +74,7 @@ export const authConfig = { sourceIcon: { label: "Source Icon" }, sourceCategory: { label: "Source Category" }, ...clientIdandSecretConfig, - issuerUri: { label: 'Issuer URI', isRequired: true }, + issuer: { label: 'Issuer URI', isRequired: true }, authorizationEndpoint: { label: 'Authorization Endpoint', isRequired: true }, tokenEndpoint: { label: 'Token Endpoint', isRequired: true }, userInfoEndpoint: { label: 'UserInfo Endpoint', isRequired: true }, @@ -85,7 +86,7 @@ export const authConfig = { }, } as { [key: string]: { sourceName: string; sourceValue: AuthType, form: FormItemType } }; -export const FreeTypes = [AuthType.Google, AuthType.Github, AuthType.Form, AuthType.Ory, AuthType.KeyCloak]; +export const FreeTypes = [AuthType.Google, AuthType.Github, AuthType.Form, AuthType.Ory, AuthType.KeyCloak, AuthType.Generic]; export const authTypeDisabled = (type: AuthType, enableEnterpriseLogin?: boolean) => { return !FreeTypes.includes(type); diff --git a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx index c7e6b243b..87a1071ca 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx @@ -35,8 +35,6 @@ import { currentOrgAdmin } from "../../../util/permissionUtils"; import CreateModal from "./createModal"; import _ from "lodash"; import { HelpText } from "components/HelpText"; -import Flex from "antd/es/flex"; -import AddGenericOauthModal from "./AddGenericOauthModal"; export const IdSourceList = (props: any) => { const user = useSelector(getUser); @@ -45,7 +43,6 @@ export const IdSourceList = (props: any) => { const [configs, setConfigs] = useState([]); const [fetching, setFetching] = useState(false); const [modalVisible, setModalVisible] = useState(false); - const [genericModalVisible, setGenericModalVisible] = useState(false); const enableEnterpriseLogin = useSelector(selectSystemConfig)?.featureFlag?.enableEnterpriseLogin; let protocol = window.location.protocol; @@ -95,33 +92,18 @@ export const IdSourceList = (props: any) => { <> -
- {trans("idSource.title")} -
- - {currentOrgAdmin(user) && ( - } - onClick={() => - setGenericModalVisible(true) - } - > - {"Add Generic OAuth Provider"} - - )} - {currentOrgAdmin(user) && ( - } - onClick={() => - setModalVisible(true) - } - > - {"Add OAuth Provider"} - - )} - + {trans("idSource.title")} + {currentOrgAdmin(user) && ( + } + onClick={() => + setModalVisible(true) + } + > + {"Add OAuth Provider"} + + )}
{ title={trans("idSource.loginType")} dataIndex="authType" key="authType" - render={(value, record: ConfigItem) => ( + render={(value: AuthType, record: ConfigItem) => ( { { alt={value} /> } - {authConfig[value as AuthType].sourceName} + + {value === AuthType.Generic + ? record.sourceName + : authConfig[value as AuthType].sourceName + } + {!FreeTypes.includes(value) && ( { getConfigs(); }} /> - config.authType)} - closeModal={() => setGenericModalVisible(false)} - onConfigCreate={() => { - setGenericModalVisible(false); - // getConfigs(); - }} - /> ); }; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx b/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx index 577684f98..092108b30 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/styledComponents.tsx @@ -4,6 +4,7 @@ import { default as Button } from "antd/es/button"; import { default as Checkbox} from "antd/es/checkbox"; import { default as Form } from "antd/es/form"; import { UnderlineCss } from "lowcoder-design"; +import Steps from "antd/es/steps"; const btnLoadingCss = css` > .ant-btn-loading-icon .anticon { @@ -62,8 +63,8 @@ export const SpanStyled = styled.div<{ disabled: boolean }>` height: 100%; img { - width: 32px; - height: 32px; + width: 25px; + height: 25px; margin-right: 12px; opacity: ${(props) => props.disabled && "0.4"}; } @@ -328,3 +329,18 @@ export const CreateButton = styled(Button)` height: 12px; } `; + +export const StyledSteps = styled(Steps)` + .ant-steps-item-icon { + width: 25px; + height: 25px; + font-size: 10px; + line-height: 26px; + } + + .ant-steps-item-title { + font-size: 14px; + line-height: 22px; + font-weight: bold; + } +`; \ No newline at end of file From a92a113dedc769078903f3fb1a89ea50fa49f85f Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Wed, 8 May 2024 19:30:21 +0500 Subject: [PATCH 4/4] added icon selector for sourceIcon --- .../src/components/iconSelect/index.tsx | 2 +- .../src/comps/controls/iconControl.tsx | 2 +- .../idSource/OAuthForms/GenericOAuthForm.tsx | 63 +++++++++++-------- .../pages/setting/idSource/detail/index.tsx | 10 ++- .../setting/idSource/idSourceConstants.ts | 3 +- .../lowcoder/src/pages/setting/styled.tsx | 6 +- 6 files changed, 52 insertions(+), 34 deletions(-) diff --git a/client/packages/lowcoder-design/src/components/iconSelect/index.tsx b/client/packages/lowcoder-design/src/components/iconSelect/index.tsx index 14b1f577f..85643d344 100644 --- a/client/packages/lowcoder-design/src/components/iconSelect/index.tsx +++ b/client/packages/lowcoder-design/src/components/iconSelect/index.tsx @@ -398,7 +398,7 @@ export const IconSelect = (props: { visible={visible} setVisible={setVisible} trigger="click" - leftOffset={-96} + leftOffset={-30} searchKeywords={props.searchKeywords} /> ); diff --git a/client/packages/lowcoder/src/comps/controls/iconControl.tsx b/client/packages/lowcoder/src/comps/controls/iconControl.tsx index 51311e1b5..15b5f9dc2 100644 --- a/client/packages/lowcoder/src/comps/controls/iconControl.tsx +++ b/client/packages/lowcoder/src/comps/controls/iconControl.tsx @@ -70,7 +70,7 @@ const Wrapper = styled.div` } `; -const IconPicker = (props: { +export const IconPicker = (props: { value: string; onChange: (value: string) => void; label?: ReactNode; diff --git a/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx index 04a217dc4..f88ec0628 100644 --- a/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx +++ b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx @@ -1,12 +1,12 @@ import { useState } from "react"; import { messageInstance, CloseEyeIcon } from "lowcoder-design"; -import { trans } from "i18n"; +import { i18nObjs, trans } from "i18n"; import { FormStyled, PasswordLabel, StyledSteps } from "../styledComponents"; -import { default as Form } from "antd/es/form"; +import { default as Form, FormInstance } from "antd/es/form"; import { default as Input } from "antd/es/input"; import { default as Tooltip } from "antd/es/tooltip"; import IdSourceApi, { ConfigItem } from "api/idSourceApi"; @@ -16,6 +16,7 @@ import _ from "lodash"; import Flex from "antd/es/flex"; import Button from "antd/es/button"; import axios from "axios"; +import { IconPicker } from "@lowcoder-ee/comps/controls/iconControl"; const sourceMappingKeys = [ 'uid', @@ -80,7 +81,7 @@ function GenericOAuthForm(props: GenericOAuthFormProp) { const [saveLoading, setSaveLoading] = useState(false); const [currentStep, setCurrentStep] = useState(0); - const [issuerDetails, setIssuerDetails] = useState(); + const [issuerDetails, setIssuerDetails] = useState({}); function saveAuthProvider(values: ConfigItem) { setSaveLoading(true); @@ -105,31 +106,32 @@ function GenericOAuthForm(props: GenericOAuthFormProp) { form1.validateFields().then(async (values) => { setSaveLoading(true); const { issuer } = values; - const res = await axios.get(`${issuer}/.well-known/openid-configuration`); - setSaveLoading(false); - - if (res.status >= 400) { - return null; + try { + const res = await axios.get(`${issuer}/.well-known/openid-configuration`); + setIssuerDetails(() => { + const issuer = { + authType: AuthType.Generic, + source: '', + sourceName: '', + issuer: res.data.issuer, + authorizationEndpoint: res.data.authorization_endpoint, + tokenEndpoint: res.data.token_endpoint, + userInfoEndpoint: res.data.userinfo_endpoint, + jwksUri: res.data.jwks_uri, + scope: res.data.scopes_supported.join(','), + sourceMappings: sourceMappingKeys.map(sourceKey => ({ + [sourceKey]: sourceKey, + })) + }; + form1.setFieldsValue(issuer); + return issuer; + }) + } catch (e) { + setIssuerDetails({}); + } finally { + setSaveLoading(false); + setCurrentStep(currentStep => currentStep + 1); } - setIssuerDetails(() => { - const issuer = { - authType: AuthType.Generic, - source: '', - sourceName: '', - issuer: res.data.issuer, - authorizationEndpoint: res.data.authorization_endpoint, - tokenEndpoint: res.data.token_endpoint, - userInfoEndpoint: res.data.userinfo_endpoint, - jwksUri: res.data.jwks_uri, - scope: res.data.scopes_supported.join(','), - sourceMappings: sourceMappingKeys.map(sourceKey => ({ - [sourceKey]: sourceKey, - })) - }; - form1.setFieldsValue(issuer); - return issuer; - }) - setCurrentStep(currentStep => currentStep + 1); }) } @@ -215,6 +217,7 @@ function GenericOAuthForm(props: GenericOAuthFormProp) { const label = valueObject ? valueObject.label : value as string; const tip = valueObject && valueObject.tip; const isPassword = valueObject && valueObject.isPassword; + const isIcon = valueObject && valueObject.isIcon; return (
+ ) : isIcon ? ( + form1.setFieldValue("sourceIcon", value)} + label={'Source Icon'} + value={form1.getFieldValue('sourceIcon')} + /> ) : ( { const label = valueObject ? valueObject.label : value as string; const isList = valueObject && valueObject.isList; const isPassword = valueObject && valueObject.isPassword; + const isIcon = valueObject && valueObject.isIcon; return (
{ } autoComplete={"one-time-code"} /> - ) : !isPassword && !isList ? ( + ) : !isPassword && !isList && !isIcon ? ( { (lock ? handleLockClick()} /> : ) } /> + ) : !isPassword && !isList && isIcon ? ( + form.setFieldValue("sourceIcon", value)} + label={'Source Icon'} + value={form.getFieldValue('sourceIcon')} + /> ) : (