feat: oauth 添加字段

This commit is contained in:
Gavan
2025-07-14 14:04:25 +08:00
parent 0776e5eb54
commit bede408e41
13 changed files with 564 additions and 347 deletions

232
ui/src/api/Admin.ts Normal file
View File

@@ -0,0 +1,232 @@
/* eslint-disable */
/* tslint:disable */
// @ts-nocheck
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import request, { ContentType, RequestParams } from "./httpClient";
import {
DeleteDeleteAdminParams,
DomainAdminUser,
DomainCreateAdminReq,
DomainListAdminLoginHistoryResp,
DomainListAdminUserResp,
DomainLoginReq,
DomainSetting,
DomainUpdateSettingReq,
GetAdminLoginHistoryParams,
GetListAdminUserParams,
WebResp,
} from "./types";
/**
* @description 创建管理员
*
* @tags Admin
* @name PostCreateAdmin
* @summary 创建管理员
* @request POST:/api/v1/admin/create
* @response `200` `(WebResp & {
data?: DomainAdminUser,
})` OK
*/
export const postCreateAdmin = (
param: DomainCreateAdminReq,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainAdminUser;
}
>({
path: `/api/v1/admin/create`,
method: "POST",
body: param,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 删除管理员
*
* @tags Admin
* @name DeleteDeleteAdmin
* @summary 删除管理员
* @request DELETE:/api/v1/admin/delete
* @response `200` `(WebResp & {
data?: Record<string, any>,
})` OK
*/
export const deleteDeleteAdmin = (
query: DeleteDeleteAdminParams,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: Record<string, any>;
}
>({
path: `/api/v1/admin/delete`,
method: "DELETE",
query: query,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 获取管理员用户列表
*
* @tags Admin
* @name GetListAdminUser
* @summary 获取管理员用户列表
* @request GET:/api/v1/admin/list
* @response `200` `(WebResp & {
data?: DomainListAdminUserResp,
})` OK
*/
export const getListAdminUser = (
query: GetListAdminUserParams,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainListAdminUserResp;
}
>({
path: `/api/v1/admin/list`,
method: "GET",
query: query,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 管理员登录
*
* @tags Admin
* @name PostAdminLogin
* @summary 管理员登录
* @request POST:/api/v1/admin/login
* @response `200` `(WebResp & {
data?: DomainAdminUser,
})` OK
*/
export const postAdminLogin = (
param: DomainLoginReq,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainAdminUser;
}
>({
path: `/api/v1/admin/login`,
method: "POST",
body: param,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 获取管理员登录历史
*
* @tags Admin
* @name GetAdminLoginHistory
* @summary 获取管理员登录历史
* @request GET:/api/v1/admin/login-history
* @response `200` `(WebResp & {
data?: DomainListAdminLoginHistoryResp,
})` OK
*/
export const getAdminLoginHistory = (
query: GetAdminLoginHistoryParams,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainListAdminLoginHistoryResp;
}
>({
path: `/api/v1/admin/login-history`,
method: "GET",
query: query,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 获取系统设置
*
* @tags Admin
* @name GetGetSetting
* @summary 获取系统设置
* @request GET:/api/v1/admin/setting
* @response `200` `(WebResp & {
data?: DomainSetting,
})` OK
*/
export const getGetSetting = (params: RequestParams = {}) =>
request<
WebResp & {
data?: DomainSetting;
}
>({
path: `/api/v1/admin/setting`,
method: "GET",
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 更新系统设置
*
* @tags Admin
* @name PutUpdateSetting
* @summary 更新系统设置
* @request PUT:/api/v1/admin/setting
* @response `200` `(WebResp & {
data?: DomainSetting,
})` OK
*/
export const putUpdateSetting = (
param: DomainUpdateSettingReq,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainSetting;
}
>({
path: `/api/v1/admin/setting`,
method: "PUT",
body: param,
type: ContentType.Json,
format: "json",
...params,
});

View File

@@ -12,25 +12,16 @@
import request, { ContentType, RequestParams } from "./httpClient";
import {
DeleteDeleteAdminParams,
DeleteDeleteUserParams,
DomainAdminUser,
DomainCreateAdminReq,
DomainInviteResp,
DomainListAdminLoginHistoryResp,
DomainListAdminUserResp,
DomainListLoginHistoryResp,
DomainListUserResp,
DomainLoginReq,
DomainLoginResp,
DomainOAuthURLResp,
DomainRegisterReq,
DomainSetting,
DomainUpdateSettingReq,
DomainUpdateUserReq,
DomainUser,
GetAdminLoginHistoryParams,
GetListAdminUserParams,
GetListUserParams,
GetLoginHistoryParams,
GetUserOauthCallbackParams,
@@ -38,212 +29,6 @@ import {
WebResp,
} from "./types";
/**
* @description 创建管理员
*
* @tags User
* @name PostCreateAdmin
* @summary 创建管理员
* @request POST:/api/v1/admin/create
* @response `200` `(WebResp & {
data?: DomainAdminUser,
})` OK
*/
export const postCreateAdmin = (
param: DomainCreateAdminReq,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainAdminUser;
}
>({
path: `/api/v1/admin/create`,
method: "POST",
body: param,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 删除管理员
*
* @tags User
* @name DeleteDeleteAdmin
* @summary 删除管理员
* @request DELETE:/api/v1/admin/delete
* @response `200` `(WebResp & {
data?: Record<string, any>,
})` OK
*/
export const deleteDeleteAdmin = (
query: DeleteDeleteAdminParams,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: Record<string, any>;
}
>({
path: `/api/v1/admin/delete`,
method: "DELETE",
query: query,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 获取管理员用户列表
*
* @tags User
* @name GetListAdminUser
* @summary 获取管理员用户列表
* @request GET:/api/v1/admin/list
* @response `200` `(WebResp & {
data?: DomainListAdminUserResp,
})` OK
*/
export const getListAdminUser = (
query: GetListAdminUserParams,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainListAdminUserResp;
}
>({
path: `/api/v1/admin/list`,
method: "GET",
query: query,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 管理员登录
*
* @tags User
* @name PostAdminLogin
* @summary 管理员登录
* @request POST:/api/v1/admin/login
* @response `200` `(WebResp & {
data?: DomainAdminUser,
})` OK
*/
export const postAdminLogin = (
param: DomainLoginReq,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainAdminUser;
}
>({
path: `/api/v1/admin/login`,
method: "POST",
body: param,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 获取管理员登录历史
*
* @tags User
* @name GetAdminLoginHistory
* @summary 获取管理员登录历史
* @request GET:/api/v1/admin/login-history
* @response `200` `(WebResp & {
data?: DomainListAdminLoginHistoryResp,
})` OK
*/
export const getAdminLoginHistory = (
query: GetAdminLoginHistoryParams,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainListAdminLoginHistoryResp;
}
>({
path: `/api/v1/admin/login-history`,
method: "GET",
query: query,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 获取系统设置
*
* @tags User
* @name GetGetSetting
* @summary 获取系统设置
* @request GET:/api/v1/admin/setting
* @response `200` `(WebResp & {
data?: DomainSetting,
})` OK
*/
export const getGetSetting = (params: RequestParams = {}) =>
request<
WebResp & {
data?: DomainSetting;
}
>({
path: `/api/v1/admin/setting`,
method: "GET",
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 更新系统设置
*
* @tags User
* @name PutUpdateSetting
* @summary 更新系统设置
* @request PUT:/api/v1/admin/setting
* @response `200` `(WebResp & {
data?: DomainSetting,
})` OK
*/
export const putUpdateSetting = (
param: DomainUpdateSettingReq,
params: RequestParams = {},
) =>
request<
WebResp & {
data?: DomainSetting;
}
>({
path: `/api/v1/admin/setting`,
method: "PUT",
body: param,
type: ContentType.Json,
format: "json",
...params,
});
/**
* @description 下载VSCode插件
*

View File

@@ -1,3 +1,4 @@
export * from './Admin'
export * from './Billing'
export * from './Dashboard'
export * from './Model'

View File

@@ -185,6 +185,40 @@ export interface DomainCreateModelReq {
provider?: string;
}
export interface DomainCustomOAuth {
/** 自定义OAuth访问令牌URL */
access_token_url?: string;
/** 自定义OAuth授权URL */
authorize_url?: string;
/** 用户信息回包中的头像URL字段名` */
avatar_field?: string;
/** 自定义客户端ID */
client_id?: string;
/** 自定义客户端密钥 */
client_secret?: string;
/** 用户信息回包中的邮箱字段名 */
email_field?: string;
/** 自定义OAuth开关 */
enable?: boolean;
/** 用户信息回包中的ID字段名 */
id_field?: string;
/** 用户信息回包中的用户名字段名` */
name_field?: string;
/** 自定义OAuth Scope列表 */
scopes?: string[];
/** 自定义OAuth用户信息URL */
userinfo_url?: string;
}
export interface DomainDingtalkOAuth {
/** 钉钉客户端ID */
client_id?: string;
/** 钉钉客户端密钥 */
client_secret?: string;
/** 钉钉OAuth开关 */
enable?: boolean;
}
export interface DomainIPInfo {
/** ASN */
asn?: string;
@@ -357,24 +391,12 @@ export interface DomainRegisterReq {
export interface DomainSetting {
/** 创建时间 */
created_at?: number;
/** 自定义OAuth访问令牌URL */
custom_oauth_access_token_url?: string;
/** 自定义OAuth授权URL */
custom_oauth_authorize_url?: string;
/** 自定义OAuth客户端ID */
custom_oauth_client_id?: string;
/** 自定义OAuth Scope列表 */
custom_oauth_scopes?: string[];
/** 自定义OAuth用户信息URL */
custom_oauth_userinfo_url?: string;
/** 钉钉客户端ID */
dingtalk_client_id?: string;
/** 自定义OAuth接入 */
custom_oauth?: DomainCustomOAuth;
/** 钉钉OAuth接入 */
dingtalk_oauth?: DomainDingtalkOAuth;
/** 是否禁用密码登录 */
disable_password_login?: boolean;
/** 是否开启自定义OAuth */
enable_custom_oauth?: boolean;
/** 是否开启钉钉OAuth */
enable_dingtalk_oauth?: boolean;
/** 是否开启SSO */
enable_sso?: boolean;
/** 是否强制两步验证 */
@@ -461,28 +483,12 @@ export interface DomainUpdateModelReq {
}
export interface DomainUpdateSettingReq {
/** 自定义OAuth访问令牌URL */
custom_oauth_access_token_url?: string;
/** 自定义OAuth授权URL */
custom_oauth_authorize_url?: string;
/** 自定义OAuth客户端ID */
custom_oauth_client_id?: string;
/** 自定义OAuth客户端密钥 */
custom_oauth_client_secret?: string;
/** 自定义OAuth Scope列表 */
custom_oauth_scopes?: string[];
/** 自定义OAuth用户信息URL */
custom_oauth_userinfo_url?: string;
/** 钉钉客户端ID */
dingtalk_client_id?: string;
/** 钉钉客户端密钥 */
dingtalk_client_secret?: string;
/** 自定义OAuth配置 */
custom_oauth?: DomainCustomOAuth;
/** 钉钉OAuth配置 */
dingtalk_oauth?: DomainDingtalkOAuth;
/** 是否禁用密码登录 */
disable_password_login?: boolean;
/** 是否开启自定义OAuth */
enable_custom_oauth?: boolean;
/** 是否开启钉钉OAuth */
enable_dingtalk_oauth?: boolean;
/** 是否开启SSO */
enable_sso?: boolean;
/** 是否强制两步验证 */
@@ -499,6 +505,8 @@ export interface DomainUpdateUserReq {
}
export interface DomainUser {
/** 头像URL */
avatar_url?: string;
/** 创建时间 */
created_at?: number;
/** 邮箱 */

View File

@@ -7,9 +7,9 @@ import {
TextField,
Paper,
} from '@mui/material';
import { postCreateAdmin } from '@/api/User';
import { postCreateAdmin } from '@/api/Admin';
import { CopyToClipboard } from 'react-copy-to-clipboard';
import { deleteDeleteAdmin, getListAdminUser } from '@/api/User';
import { deleteDeleteAdmin, getListAdminUser } from '@/api/Admin';
import { Table, Modal, message } from '@c-x/ui';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { useRequest } from 'ahooks';

View File

@@ -3,7 +3,7 @@ import { Stack, Box } from '@mui/material';
import { Table } from '@c-x/ui';
import dayjs from 'dayjs';
import { useRequest } from 'ahooks';
import { getAdminLoginHistory } from '@/api/User';
import { getAdminLoginHistory } from '@/api/Admin';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { DomainListAdminLoginHistoryResp } from '@/api/types';
import User from '@/components/user';

View File

@@ -21,11 +21,13 @@ import { Icon, message } from '@c-x/ui';
import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js';
import { useSearchParams } from 'react-router-dom';
import { postLogin, getUserOauthSignupOrIn, getGetSetting } from '@/api/User';
import { postLogin, getUserOauthSignupOrIn } from '@/api/User';
import { getGetSetting } from '@/api/Admin';
import { useForm, Controller } from 'react-hook-form';
import { styled } from '@mui/material/styles';
import { useRequest } from 'ahooks';
import { DomainSetting } from '@/api/types';
// 样式化组件
const StyledContainer = styled(Container)(({ theme }) => ({
@@ -114,8 +116,9 @@ const AuthPage = () => {
const [showPassword, setShowPassword] = useState(false);
const [searchParams] = useSearchParams();
const { data: loginSetting = {} } = useRequest(getGetSetting);
const { data: loginSetting = {} as DomainSetting } =
useRequest(getGetSetting);
const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting;
const {
control,
handleSubmit,
@@ -175,10 +178,8 @@ const AuthPage = () => {
}, []);
const oauthEnable = useMemo(() => {
return (
loginSetting.enable_custom_oauth || loginSetting.enable_dingtalk_oauth
);
}, [loginSetting]);
return custom_oauth.enable || dingtalk_oauth.enable;
}, [custom_oauth, dingtalk_oauth]);
// 渲染用户名输入框
const renderUsernameField = () => (
@@ -290,7 +291,7 @@ const AuthPage = () => {
<Divider sx={{ my: 3, fontSize: 12, borderColor: 'divider' }}>
使
</Divider>
{loginSetting.enable_dingtalk_oauth && (
{dingtalk_oauth.enable && (
<IconButton
sx={{ alignSelf: 'center' }}
onClick={() => onOauthLogin('dingtalk')}
@@ -298,7 +299,7 @@ const AuthPage = () => {
<Icon type='icon-dingding' sx={{ fontSize: 30 }} />
</IconButton>
)}
{loginSetting.enable_custom_oauth && (
{custom_oauth.enable && (
<IconButton
sx={{ alignSelf: 'center' }}
onClick={() => onOauthLogin('custom')}

View File

@@ -23,12 +23,10 @@ import {
Divider,
} from '@mui/material';
import { useRequest } from 'ahooks';
import {
postRegister,
getUserOauthSignupOrIn,
getGetSetting,
} from '@/api/User';
import { postRegister, getUserOauthSignupOrIn } from '@/api/User';
import { getGetSetting } from '@/api/Admin';
import { Icon } from '@c-x/ui';
import { DomainSetting } from '@/api/types';
import DownloadIcon from '@mui/icons-material/Download';
import MenuBookIcon from '@mui/icons-material/MenuBook';
@@ -92,7 +90,9 @@ const StyledTextField = styled(TextField)(({ theme }) => ({
const Invite = () => {
const { id, step } = useParams();
const [showPassword, setShowPassword] = useState(false);
const { data: loginSetting = {} } = useRequest(getGetSetting);
const { data: loginSetting = {} as DomainSetting } =
useRequest(getGetSetting);
const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting;
const {
control,
handleSubmit,
@@ -148,10 +148,8 @@ const Invite = () => {
};
const oauthEnable = useMemo(() => {
return (
loginSetting.enable_custom_oauth || loginSetting.enable_dingtalk_oauth
);
}, [loginSetting]);
return custom_oauth.enable || dingtalk_oauth.enable;
}, [custom_oauth, dingtalk_oauth]);
const oauthLogin = () => {
return (
@@ -159,7 +157,7 @@ const Invite = () => {
<Divider sx={{ my: 2, fontSize: 12, borderColor: 'divider' }}>
使
</Divider>
{loginSetting.enable_dingtalk_oauth && (
{dingtalk_oauth.enable && (
<Button
sx={{ alignSelf: 'center' }}
onClick={() => onOauthLogin('dingtalk')}
@@ -167,7 +165,7 @@ const Invite = () => {
<Icon type='icon-dingding' sx={{ fontSize: 30 }} />
</Button>
)}
{loginSetting.enable_custom_oauth && (
{custom_oauth.enable && (
<IconButton
sx={{ alignSelf: 'center' }}
onClick={() => onOauthLogin('custom')}

View File

@@ -12,7 +12,7 @@ import {
InputAdornment,
IconButton,
} from '@mui/material';
import { postAdminLogin } from '@/api/User';
import { postAdminLogin } from '@/api/Admin';
import { useForm, Controller } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { styled } from '@mui/material/styles';

View File

@@ -8,9 +8,8 @@ import {
Button,
Box,
} from '@mui/material';
import { Icon, Modal } from '@c-x/ui';
import { useRequest } from 'ahooks';
import { getGetSetting, putUpdateSetting } from '@/api/User';
import { getGetSetting, putUpdateSetting } from '@/api/Admin';
import MemberManage from './memberManage';
import LoginHistory from './loginHistory';
import { message } from '@c-x/ui';
@@ -30,11 +29,11 @@ const StyledLabel = styled('div')(({ theme }) => ({
color: theme.vars.palette.text.primary,
}));
const OAUTH_LOGIN_TYPE_KEYS = ['enable_custom_oauth', 'enable_dingtalk_oauth'];
const OAUTH_LOGIN_TYPE_KEYS = ['dingtalk_oauth', 'custom_oauth'];
const OAUTH_LOGIN_TYPE_LABELS = {
enable_custom_oauth: '已开启 OAuth 登录',
enable_dingtalk_oauth: '已开启钉钉登录',
custom_oauth: '已开启 OAuth 登录',
dingtalk_oauth: '已开启钉钉登录',
};
type OAUTH_LOGIN_TYPE_KEYS = keyof typeof OAUTH_LOGIN_TYPE_LABELS;
@@ -42,16 +41,7 @@ type OAUTH_LOGIN_TYPE_KEYS = keyof typeof OAUTH_LOGIN_TYPE_LABELS;
const User = () => {
const [thirdPartyLoginSettingModalOpen, setThirdPartyLoginSettingModalOpen] =
useState(false);
const {
data = {
enable_sso: false,
force_two_factor_auth: false,
disable_password_login: false,
enable_dingtalk_oauth: false,
enable_custom_oauth: false,
},
refresh,
} = useRequest(getGetSetting);
const { data, refresh } = useRequest(getGetSetting);
const { runAsync: updateSetting } = useRequest(putUpdateSetting, {
manual: true,
@@ -62,8 +52,9 @@ const User = () => {
});
const oauthLabel = useMemo(() => {
if (!data) return '未开启';
const key = OAUTH_LOGIN_TYPE_KEYS.find(
(key) => data[key as OAUTH_LOGIN_TYPE_KEYS]
(key) => data[key as OAUTH_LOGIN_TYPE_KEYS]?.enable
);
return key
? OAUTH_LOGIN_TYPE_LABELS[key as OAUTH_LOGIN_TYPE_KEYS]
@@ -127,7 +118,7 @@ const User = () => {
<ThirdPartyLoginSettingModal
open={thirdPartyLoginSettingModalOpen}
onCancel={() => setThirdPartyLoginSettingModalOpen(false)}
settingData={data}
settingData={data || {}}
onOk={() => {
refresh();
}}

View File

@@ -1,9 +1,16 @@
import { Button, Radio, Stack, Box, TextField } from '@mui/material';
import { Modal, Icon, message } from '@c-x/ui';
import {
Button,
Radio,
Stack,
TextField,
Autocomplete,
Chip,
} from '@mui/material';
import { Modal, message } from '@c-x/ui';
import { useState, useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { FormItem } from '@/components/form';
import { putUpdateSetting } from '@/api/User';
import { putUpdateSetting } from '@/api/Admin';
import { DomainSetting, DomainUpdateSettingReq } from '@/api/types';
type LoginType = 'dingding' | 'wechat' | 'feishu' | 'oauth' | 'none';
@@ -59,22 +66,33 @@ const ThirdPartyLoginSettingModal = ({
control,
handleSubmit,
reset,
watch,
formState: { errors },
} = useForm({
defaultValues: {
dingtalk_client_id: '',
dingtalk_client_secret: '',
custom_oauth_access_token_url: '',
custom_oauth_authorize_url: '',
custom_oauth_client_id: '',
custom_oauth_client_secret: '',
access_token_url: '',
authorize_url: '',
client_id: '',
client_secret: '',
id_field: '',
name_field: '',
scopes: [] as string[],
avatar_field: '',
userinfo_url: '',
email_field: '',
},
});
const [loginType, setLoginType] = useState<LoginType>(
settingData?.enable_dingtalk_oauth ? 'dingding' : 'none'
settingData?.dingtalk_oauth?.enable ? 'dingding' : 'none'
);
const [scopeInputValue, setScopeInputValue] = useState('');
const userInfoUrl = watch('userinfo_url');
useEffect(() => {
if (open) {
reset();
@@ -82,25 +100,31 @@ const ThirdPartyLoginSettingModal = ({
}, [open]);
useEffect(() => {
if (settingData?.enable_dingtalk_oauth) {
if (settingData?.dingtalk_oauth?.enable) {
setLoginType('dingding');
reset(
{
dingtalk_client_id: settingData.dingtalk_client_id,
dingtalk_client_id: settingData.dingtalk_oauth.client_id,
dingtalk_client_secret: settingData.dingtalk_oauth.client_secret,
},
{
keepValues: true,
}
);
}
if (settingData?.enable_custom_oauth) {
if (settingData?.custom_oauth?.enable) {
setLoginType('oauth');
reset(
{
custom_oauth_access_token_url:
settingData.custom_oauth_access_token_url,
custom_oauth_authorize_url: settingData.custom_oauth_authorize_url,
custom_oauth_client_id: settingData.custom_oauth_client_id,
access_token_url: settingData.custom_oauth.access_token_url,
authorize_url: settingData.custom_oauth.authorize_url,
client_id: settingData.custom_oauth.client_id,
id_field: settingData.custom_oauth.id_field,
name_field: settingData.custom_oauth.name_field,
scopes: settingData.custom_oauth.scopes || [],
avatar_field: settingData.custom_oauth.avatar_field,
userinfo_url: settingData.custom_oauth.userinfo_url,
email_field: settingData.custom_oauth.email_field,
},
{
keepValues: true,
@@ -113,24 +137,42 @@ const ThirdPartyLoginSettingModal = ({
let params: DomainUpdateSettingReq = {};
if (loginType === 'none') {
params = {
enable_dingtalk_oauth: false,
enable_custom_oauth: false,
dingtalk_oauth: {
enable: false,
},
custom_oauth: {
enable: false,
},
};
} else if (loginType === 'dingding') {
params = {
enable_dingtalk_oauth: true,
enable_custom_oauth: false,
dingtalk_client_id: data.dingtalk_client_id,
dingtalk_client_secret: data.dingtalk_client_secret,
dingtalk_oauth: {
enable: true,
client_id: data.dingtalk_client_id,
client_secret: data.dingtalk_client_secret,
},
custom_oauth: {
enable: false,
},
};
} else if (loginType === 'oauth') {
params = {
enable_custom_oauth: true,
enable_dingtalk_oauth: false,
custom_oauth_access_token_url: data.custom_oauth_access_token_url,
custom_oauth_authorize_url: data.custom_oauth_authorize_url,
custom_oauth_client_id: data.custom_oauth_client_id,
custom_oauth_client_secret: data.custom_oauth_client_secret,
dingtalk_oauth: {
enable: false,
},
custom_oauth: {
enable: true,
access_token_url: data.access_token_url,
authorize_url: data.authorize_url,
client_id: data.client_id,
client_secret: data.client_secret,
id_field: data.id_field,
name_field: data.name_field,
scopes: data.scopes,
avatar_field: data.avatar_field,
userinfo_url: data.userinfo_url,
email_field: data.email_field,
},
};
}
@@ -201,15 +243,15 @@ const ThirdPartyLoginSettingModal = ({
rules={{
required: 'Access Token URL 不能为空',
}}
name='custom_oauth_access_token_url'
name='access_token_url'
render={({ field }) => (
<TextField
{...field}
fullWidth
size='small'
placeholder='请输入'
error={!!errors.custom_oauth_access_token_url}
helperText={errors.custom_oauth_access_token_url?.message}
error={!!errors.access_token_url}
helperText={errors.access_token_url?.message}
/>
)}
/>
@@ -217,7 +259,7 @@ const ThirdPartyLoginSettingModal = ({
<FormItem label='Authorize URL' required>
<Controller
control={control}
name='custom_oauth_authorize_url'
name='authorize_url'
rules={{
required: 'Authorize URL 不能为空',
}}
@@ -227,8 +269,8 @@ const ThirdPartyLoginSettingModal = ({
fullWidth
size='small'
placeholder='请输入'
error={!!errors.custom_oauth_authorize_url}
helperText={errors.custom_oauth_authorize_url?.message}
error={!!errors.authorize_url}
helperText={errors.authorize_url?.message}
/>
)}
/>
@@ -236,7 +278,7 @@ const ThirdPartyLoginSettingModal = ({
<FormItem label='Client ID' required>
<Controller
control={control}
name='custom_oauth_client_id'
name='client_id'
rules={{
required: 'Client ID 不能为空',
}}
@@ -246,8 +288,8 @@ const ThirdPartyLoginSettingModal = ({
fullWidth
size='small'
placeholder='请输入'
error={!!errors.custom_oauth_client_id}
helperText={errors.custom_oauth_client_id?.message}
error={!!errors.client_id}
helperText={errors.client_id?.message}
/>
)}
/>
@@ -255,7 +297,7 @@ const ThirdPartyLoginSettingModal = ({
<FormItem label='Client Secret' required>
<Controller
control={control}
name='custom_oauth_client_secret'
name='client_secret'
rules={{
required: 'Client Secret 不能为空',
}}
@@ -265,12 +307,175 @@ const ThirdPartyLoginSettingModal = ({
fullWidth
size='small'
placeholder='请输入'
error={!!errors.custom_oauth_client_secret}
helperText={errors.custom_oauth_client_secret?.message}
error={!!errors.client_secret}
helperText={errors.client_secret?.message}
/>
)}
/>
</FormItem>
<FormItem label='Scope' required>
<Controller
name='scopes'
control={control}
rules={{
validate: (value) => {
if (value.length === 0) {
return 'Scope 不能为空';
}
return true;
},
}}
render={({ field }) => (
<Autocomplete
multiple
id='tags-filled'
options={[]}
value={field.value}
inputValue={scopeInputValue}
onChange={(_, value) => {
field.onChange(value);
}}
onInputChange={(_, value) => {
setScopeInputValue(value);
}}
size='small'
freeSolo
renderTags={(value: readonly string[], getTagProps) =>
value.map((option: string, index: number) => {
const { key, ...tagProps } = getTagProps({ index });
const label = `${option}`;
return (
<Chip
key={key}
label={label}
size='small'
{...tagProps}
/>
);
})
}
renderInput={(params) => (
<TextField
{...params}
required
placeholder='请输入(可多个, 回车键确认)'
error={Boolean(errors.scopes)}
helperText={errors.scopes?.message as string}
onBlur={() => {
// 失去焦点时自动添加当前输入的值
const trimmedValue = scopeInputValue.trim();
if (trimmedValue && !field.value.includes(trimmedValue)) {
field.onChange([...field.value, trimmedValue]);
// 清空输入框
setScopeInputValue('');
}
}}
/>
)}
/>
)}
/>
</FormItem>
<FormItem label='用户信息 URL' required>
<Controller
control={control}
name='userinfo_url'
rules={{
required: '用户信息 URL 不能为空',
}}
render={({ field }) => (
<TextField
{...field}
fullWidth
size='small'
placeholder='请输入'
error={!!errors.userinfo_url}
helperText={errors.userinfo_url?.message}
/>
)}
/>
</FormItem>
{userInfoUrl && (
<>
<FormItem label='ID 字段' required>
<Controller
control={control}
name='id_field'
rules={{
required: 'ID 字段 不能为空',
}}
render={({ field }) => (
<TextField
{...field}
fullWidth
size='small'
placeholder='请输入'
error={!!errors.id_field}
helperText={errors.id_field?.message}
/>
)}
/>
</FormItem>
<FormItem label='用户名字段' required>
<Controller
control={control}
name='name_field'
rules={{
required: '用户名字段不能为空',
}}
render={({ field }) => (
<TextField
{...field}
fullWidth
size='small'
placeholder='请输入'
error={!!errors.name_field}
helperText={errors.name_field?.message}
/>
)}
/>
</FormItem>
<FormItem label='头像字段' required>
<Controller
control={control}
name='avatar_field'
rules={{
required: '头像字段不能为空',
}}
render={({ field }) => (
<TextField
{...field}
fullWidth
size='small'
placeholder='请输入'
error={!!errors.avatar_field}
helperText={errors.avatar_field?.message}
/>
)}
/>
</FormItem>
<FormItem label='邮箱字段' required>
<Controller
control={control}
name='email_field'
rules={{
required: '邮箱字段不能为空',
}}
render={({ field }) => (
<TextField
{...field}
fullWidth
size='small'
placeholder='请输入'
error={!!errors.email_field}
helperText={errors.email_field?.message}
/>
)}
/>
</FormItem>
</>
)}
</>
);
};

View File

@@ -64,12 +64,7 @@ const Dashboard = () => {
</Select>
</Stack>
<MemberStatistic
memberData={memberData}
userList={userList}
onMemberChange={onMemberChange}
timeRange={timeRange}
/>
<MemberStatistic memberData={memberData} timeRange={timeRange} />
</Stack>
);
};

View File

@@ -22,11 +22,13 @@ import { getRedirectUrl } from '@/utils';
import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js';
import { useSearchParams } from 'react-router-dom';
import { postLogin, getUserOauthSignupOrIn, getGetSetting } from '@/api/User';
import { postLogin, getUserOauthSignupOrIn } from '@/api/User';
import { getGetSetting } from '@/api/Admin';
import { useForm, Controller } from 'react-hook-form';
import { styled } from '@mui/material/styles';
import { useRequest } from 'ahooks';
import { DomainSetting } from '@/api/types';
// 样式化组件
const StyledContainer = styled(Container)(({ theme }) => ({
@@ -115,8 +117,9 @@ const UserLogin = () => {
const [showPassword, setShowPassword] = useState(false);
const [searchParams] = useSearchParams();
const { data: loginSetting = {} } = useRequest(getGetSetting);
const { data: loginSetting = {} as DomainSetting } =
useRequest(getGetSetting);
const { custom_oauth = {}, dingtalk_oauth = {} } = loginSetting;
const {
control,
handleSubmit,
@@ -166,10 +169,8 @@ const UserLogin = () => {
}, []);
const oauthEnable = useMemo(() => {
return (
loginSetting.enable_custom_oauth || loginSetting.enable_dingtalk_oauth
);
}, [loginSetting]);
return custom_oauth.enable || dingtalk_oauth.enable;
}, [custom_oauth, dingtalk_oauth]);
// 渲染用户名输入框
const renderUsernameField = () => (
@@ -280,7 +281,7 @@ const UserLogin = () => {
<Divider sx={{ my: 3, fontSize: 12, borderColor: 'divider' }}>
使
</Divider>
{loginSetting.enable_dingtalk_oauth && (
{dingtalk_oauth.enable && (
<IconButton
sx={{ alignSelf: 'center' }}
onClick={() => onOauthLogin('dingtalk')}
@@ -288,7 +289,7 @@ const UserLogin = () => {
<Icon type='icon-dingding' sx={{ fontSize: 30 }} />
</IconButton>
)}
{loginSetting.enable_custom_oauth && (
{custom_oauth.enable && (
<IconButton
sx={{ alignSelf: 'center' }}
onClick={() => onOauthLogin('custom')}