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/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",
+ }}>
- {props.viewFn(childrenProps, comp.dispatch)}
+ {props.viewFn(
+ childrenProps,
+ comp.dispatch
+ )}
-
+
);
}
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;
+ 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..f88ec0628
--- /dev/null
+++ b/client/packages/lowcoder/src/pages/setting/idSource/OAuthForms/GenericOAuthForm.tsx
@@ -0,0 +1,314 @@
+import { useState } from "react";
+import { messageInstance, CloseEyeIcon } from "lowcoder-design";
+import { i18nObjs, trans } from "i18n";
+import {
+ FormStyled,
+ PasswordLabel,
+ StyledSteps
+} from "../styledComponents";
+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";
+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";
+import { IconPicker } from "@lowcoder-ee/comps/controls/iconControl";
+
+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;
+ 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);
+ }
+ })
+ }
+
+ 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;
+ const isIcon = valueObject && valueObject.isIcon;
+ return (
+
+
+ {label}:
+
+
+ ) : (
+
+ {label}:
+
+ )
+ }
+ >
+ {isPassword ? (
+
+ ) : isIcon ? (
+ form1.setFieldValue("sourceIcon", value)}
+ label={'Source Icon'}
+ value={form1.getFieldValue('sourceIcon')}
+ />
+ ) : (
+
+ )}
+
+
+ );
+ })}
+ {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/detail/index.tsx b/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx
index 291797178..6fa5cc34e 100644
--- a/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx
+++ b/client/packages/lowcoder/src/pages/setting/idSource/detail/index.tsx
@@ -37,6 +37,7 @@ import { validateResponse } from "api/apiUtils";
import { ItemType } from "pages/setting/idSource/idSourceConstants";
import _ from "lodash";
import { messageInstance } from "lowcoder-design/src/components/GlobalInstances";
+import { IconPicker } from "@lowcoder-ee/comps/controls/iconControl";
type IdSourceDetailProps = {
location: Location & { state: ConfigItem };
@@ -163,6 +164,7 @@ export const IdSourceDetail = (props: IdSourceDetailProps) => {
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')}
+ />
) : (
{
return !FreeTypes.includes(type);
@@ -83,6 +105,7 @@ export type ItemType = {
isList?: boolean;
isRequire?: boolean;
isPassword?: boolean;
+ isIcon?: boolean;
hasLock?: boolean;
tip?: string;
}
diff --git a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx
index 14fd3a43a..87a1071ca 100644
--- a/client/packages/lowcoder/src/pages/setting/idSource/list.tsx
+++ b/client/packages/lowcoder/src/pages/setting/idSource/list.tsx
@@ -91,18 +91,20 @@ export const IdSourceList = (props: any) => {
<>
- {trans("idSource.title")}
- {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) && (
.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
diff --git a/client/packages/lowcoder/src/pages/setting/styled.tsx b/client/packages/lowcoder/src/pages/setting/styled.tsx
index 06f951857..72cc4f5e7 100644
--- a/client/packages/lowcoder/src/pages/setting/styled.tsx
+++ b/client/packages/lowcoder/src/pages/setting/styled.tsx
@@ -69,9 +69,9 @@ export const ModalNameDiv = styled.div`
`;
export const CustomModalStyled = styled(CustomModal)`
- button {
- margin-top: 20px;
- }
+ // button {
+ // margin-top: 20px;
+ // }
`;
export const TacoInputStyled = styled(TacoInput)`