mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-03 07:13:26 +08:00
增加了子管理员的功能
This commit is contained in:
@@ -16,9 +16,11 @@ import {
|
||||
DomainAdminUser,
|
||||
DomainCreateAdminReq,
|
||||
DomainExportCompletionDataResp,
|
||||
DomainGrantRoleReq,
|
||||
DomainListAdminLoginHistoryResp,
|
||||
DomainListAdminUserResp,
|
||||
DomainLoginReq,
|
||||
DomainRole,
|
||||
DomainSetting,
|
||||
DomainUpdateSettingReq,
|
||||
GetAdminLoginHistoryParams,
|
||||
@@ -244,6 +246,55 @@ export const getAdminProfile = (params: RequestParams = {}) =>
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 获取角色列表
|
||||
*
|
||||
* @tags Admin
|
||||
* @name GetListRole
|
||||
* @summary 获取角色列表
|
||||
* @request GET:/api/v1/admin/role
|
||||
* @response `200` `(WebResp & {
|
||||
data?: (DomainRole)[],
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const getListRole = (params: RequestParams = {}) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainRole[];
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/admin/role`,
|
||||
method: "GET",
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 授权角色
|
||||
*
|
||||
* @tags Admin
|
||||
* @name PostGrantRole
|
||||
* @summary 授权角色
|
||||
* @request POST:/api/v1/admin/role
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const postGrantRole = (
|
||||
param: DomainGrantRoleReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/role`,
|
||||
method: "POST",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 获取系统设置
|
||||
*
|
||||
|
||||
231
ui/src/api/UserGroup.ts
Normal file
231
ui/src/api/UserGroup.ts
Normal file
@@ -0,0 +1,231 @@
|
||||
/* 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 {
|
||||
DeleteDeleteGroupParams,
|
||||
DomainCreateUserGroupReq,
|
||||
DomainGrantGroupReq,
|
||||
DomainGroupAddUserReq,
|
||||
DomainGroupRemoveAdminReq,
|
||||
DomainGroupRemoveUserReq,
|
||||
DomainListUserGroupResp,
|
||||
DomainUpdateUserGroupReq,
|
||||
DomainUserGroup,
|
||||
GetListUserGroupParams,
|
||||
WebResp,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* @description 列出用户分组
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name GetListUserGroup
|
||||
* @summary 列出用户分组
|
||||
* @request GET:/api/v1/admin/user-group
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainListUserGroupResp,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const getListUserGroup = (
|
||||
query: GetListUserGroupParams,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainListUserGroupResp;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/admin/user-group`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 更新用户分组
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name PutUpdateUserGroup
|
||||
* @summary 更新用户分组
|
||||
* @request PUT:/api/v1/admin/user-group
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainUserGroup,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const putUpdateUserGroup = (
|
||||
param: DomainUpdateUserGroupReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainUserGroup;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/admin/user-group`,
|
||||
method: "PUT",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 创建用户分组
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name PostCreateGroup
|
||||
* @summary 创建用户分组
|
||||
* @request POST:/api/v1/admin/user-group
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainUserGroup,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const postCreateGroup = (
|
||||
param: DomainCreateUserGroupReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainUserGroup;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/admin/user-group`,
|
||||
method: "POST",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 删除用户分组
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name DeleteDeleteGroup
|
||||
* @summary 删除用户分组
|
||||
* @request DELETE:/api/v1/admin/user-group
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const deleteDeleteGroup = (
|
||||
query: DeleteDeleteGroupParams,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/user-group`,
|
||||
method: "DELETE",
|
||||
query: query,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 从分组中移除管理员
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name DeleteRemoveAdminFromGroup
|
||||
* @summary 从分组中移除管理员
|
||||
* @request DELETE:/api/v1/admin/user-group/admin
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const deleteRemoveAdminFromGroup = (
|
||||
param: DomainGroupRemoveAdminReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/user-group/admin`,
|
||||
method: "DELETE",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 授权分组给管理员
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name PostGrantGroup
|
||||
* @summary 授权分组给管理员
|
||||
* @request POST:/api/v1/admin/user-group/grant
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const postGrantGroup = (
|
||||
param: DomainGrantGroupReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/user-group/grant`,
|
||||
method: "POST",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 添加用户到分组
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name PostAddUserToGroup
|
||||
* @summary 添加用户到分组
|
||||
* @request POST:/api/v1/admin/user-group/user
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const postAddUserToGroup = (
|
||||
param: DomainGroupAddUserReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/user-group/user`,
|
||||
method: "POST",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 从分组中移除用户
|
||||
*
|
||||
* @tags UserGroup
|
||||
* @name DeleteRemoveUserFromGroup
|
||||
* @summary 从分组中移除用户
|
||||
* @request DELETE:/api/v1/admin/user-group/user
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const deleteRemoveUserFromGroup = (
|
||||
param: DomainGroupRemoveUserReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/api/v1/admin/user-group/user`,
|
||||
method: "DELETE",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
@@ -9,6 +9,7 @@ export * from './OpenAiv1'
|
||||
export * from './SecurityScanning'
|
||||
export * from './User'
|
||||
export * from './UserDashboard'
|
||||
export * from './UserGroup'
|
||||
export * from './UserManage'
|
||||
export * from './UserRecord'
|
||||
export * from './UserSecurityScanning'
|
||||
|
||||
@@ -166,6 +166,8 @@ export interface DomainAdminUser {
|
||||
id?: string;
|
||||
/** 最后活跃时间 */
|
||||
last_active_at?: number;
|
||||
/** 角色 */
|
||||
role?: DomainRole;
|
||||
/** 用户状态 active: 正常 inactive: 禁用 */
|
||||
status?: ConstsAdminStatus;
|
||||
/** 用户名 */
|
||||
@@ -243,7 +245,7 @@ export interface DomainCheckModelReq {
|
||||
api_base: string;
|
||||
api_header?: string;
|
||||
/** 接口密钥 */
|
||||
api_key: string;
|
||||
api_key?: string;
|
||||
api_version?: string;
|
||||
/** 模型名称 */
|
||||
model_name: string;
|
||||
@@ -366,9 +368,11 @@ export interface DomainCompletionRecord {
|
||||
|
||||
export interface DomainCreateAdminReq {
|
||||
/** 密码 */
|
||||
password?: string;
|
||||
password: string;
|
||||
/** 角色ID */
|
||||
role_id: number;
|
||||
/** 用户名 */
|
||||
username?: string;
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface DomainCreateModelReq {
|
||||
@@ -409,6 +413,11 @@ export interface DomainCreateSecurityScanningReq {
|
||||
workspace?: string;
|
||||
}
|
||||
|
||||
export interface DomainCreateUserGroupReq {
|
||||
/** 组名称 */
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DomainCreateWorkspaceFileReq {
|
||||
/** 文件内容 */
|
||||
content?: string;
|
||||
@@ -525,6 +534,41 @@ export interface DomainGetProviderModelListResp {
|
||||
models?: DomainProviderModelListItem[];
|
||||
}
|
||||
|
||||
export interface DomainGrantGroupReq {
|
||||
/** 管理员ID列表 */
|
||||
admin_ids: string[];
|
||||
/** 分组ID列表 */
|
||||
group_ids: string[];
|
||||
}
|
||||
|
||||
export interface DomainGrantRoleReq {
|
||||
/** 管理员ID */
|
||||
admin_id?: string;
|
||||
/** 角色ID列表 */
|
||||
role_ids?: number[];
|
||||
}
|
||||
|
||||
export interface DomainGroupAddUserReq {
|
||||
/** 分组ID列表 */
|
||||
group_ids: string[];
|
||||
/** 用户ID列表 */
|
||||
user_ids: string[];
|
||||
}
|
||||
|
||||
export interface DomainGroupRemoveAdminReq {
|
||||
/** 管理员ID列表 */
|
||||
admin_ids: string[];
|
||||
/** 分组ID */
|
||||
group_id: string;
|
||||
}
|
||||
|
||||
export interface DomainGroupRemoveUserReq {
|
||||
/** 分组ID */
|
||||
group_id: string;
|
||||
/** 用户ID列表 */
|
||||
user_ids: string[];
|
||||
}
|
||||
|
||||
export interface DomainIPInfo {
|
||||
/** ASN */
|
||||
asn?: string;
|
||||
@@ -641,6 +685,13 @@ export interface DomainListSecurityScanningResp {
|
||||
total_count?: number;
|
||||
}
|
||||
|
||||
export interface DomainListUserGroupResp {
|
||||
groups?: DomainUserGroup[];
|
||||
has_next_page?: boolean;
|
||||
next_token?: string;
|
||||
total_count?: number;
|
||||
}
|
||||
|
||||
export interface DomainListUserResp {
|
||||
has_next_page?: boolean;
|
||||
next_token?: string;
|
||||
@@ -832,6 +883,12 @@ export interface DomainReportReq {
|
||||
user_input?: string;
|
||||
}
|
||||
|
||||
export interface DomainRole {
|
||||
description?: string;
|
||||
id?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningBrief {
|
||||
/** 创建时间 */
|
||||
created_at?: number;
|
||||
@@ -1049,6 +1106,12 @@ export interface DomainUpdateSettingReq {
|
||||
force_two_factor_auth?: boolean;
|
||||
}
|
||||
|
||||
export interface DomainUpdateUserGroupReq {
|
||||
/** 分组id */
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface DomainUpdateUserReq {
|
||||
/** 用户ID */
|
||||
id: string;
|
||||
@@ -1108,6 +1171,16 @@ export interface DomainUserEvent {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface DomainUserGroup {
|
||||
/** 关联的管理员 */
|
||||
admins?: DomainAdminUser[];
|
||||
id?: string;
|
||||
/** 组名 */
|
||||
name?: string;
|
||||
/** 关联的用户 */
|
||||
users?: DomainUser[];
|
||||
}
|
||||
|
||||
export interface DomainUserHeatmap {
|
||||
count?: number;
|
||||
date?: number;
|
||||
@@ -1265,6 +1338,20 @@ export interface GetAdminLoginHistoryParams {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetListUserGroupParams {
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/** 分页 */
|
||||
page?: number;
|
||||
/** 每页多少条记录 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface DeleteDeleteGroupParams {
|
||||
/** 分组id */
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface GetChatInfoParams {
|
||||
/** 对话记录ID */
|
||||
id: string;
|
||||
|
||||
@@ -6,10 +6,15 @@ import {
|
||||
IconButton,
|
||||
TextField,
|
||||
Paper,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from '@mui/material';
|
||||
import { AdminPanelSettings, Person } from '@mui/icons-material';
|
||||
import { postCreateAdmin } from '@/api/Admin';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { deleteDeleteAdmin, getListAdminUser } from '@/api/Admin';
|
||||
import { deleteDeleteAdmin, getAdminProfile, getListAdminUser, postGrantRole } from '@/api/Admin';
|
||||
import { Table, Modal, message } from '@c-x/ui';
|
||||
import { ColumnsType } from '@c-x/ui/dist/Table';
|
||||
import { useRequest } from 'ahooks';
|
||||
@@ -44,6 +49,7 @@ const AddAdminModal = ({
|
||||
postCreateAdmin({
|
||||
username: data.username,
|
||||
password,
|
||||
role_id: data.roleId, // 添加 role_id 参数
|
||||
}).then(() => {
|
||||
setIsSuccess(true);
|
||||
message.success('添加成功');
|
||||
@@ -53,7 +59,9 @@ const AddAdminModal = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
reset();
|
||||
reset({
|
||||
roleId: 2, // 设置默认角色为普通管理员
|
||||
});
|
||||
} else {
|
||||
setIsSuccess(false);
|
||||
setPassword(Math.random().toString(36).substring(2, 15));
|
||||
@@ -85,6 +93,29 @@ const AddAdminModal = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='roleId'
|
||||
rules={{ required: '请选择角色' }}
|
||||
render={({ field }) => (
|
||||
<FormControl fullWidth required error={!!errors.roleId}>
|
||||
<InputLabel>角色</InputLabel>
|
||||
<Select
|
||||
label='角色'
|
||||
{...field}
|
||||
value={field.value || ''} // 确保有默认值
|
||||
>
|
||||
<MenuItem value={1}>超级管理员</MenuItem>
|
||||
<MenuItem value={2}>普通管理员</MenuItem>
|
||||
</Select>
|
||||
{errors.roleId && (
|
||||
<Box sx={{ color: 'error.main', fontSize: '0.75rem', mt: 0.5 }}>
|
||||
{errors.roleId?.message as string}
|
||||
</Box>
|
||||
)}
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Stack
|
||||
direction='row'
|
||||
justifyContent='flex-end'
|
||||
@@ -154,6 +185,20 @@ const AddAdminModal = ({
|
||||
const AdminUser = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { data, loading, refresh } = useRequest(() => getListAdminUser({}));
|
||||
const { data: currentAdmin } = useRequest(() => getAdminProfile({}));
|
||||
|
||||
const handleRoleChange = (adminId: string, newRoleId: number) => {
|
||||
postGrantRole({
|
||||
admin_id: adminId,
|
||||
role_ids: [newRoleId],
|
||||
}).then(() => {
|
||||
message.success('角色更新成功');
|
||||
refresh();
|
||||
}).catch(() => {
|
||||
message.error('角色更新失败');
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteAdmin = (data: DomainAdminUser) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
@@ -187,21 +232,65 @@ const AdminUser = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '加入时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 140,
|
||||
render: (text) => {
|
||||
return dayjs.unix(text).fromNow();
|
||||
title: '角色',
|
||||
dataIndex: 'last_active_at',
|
||||
render: (text, record) => {
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<Select
|
||||
size='small'
|
||||
value={record.role?.id || ''}
|
||||
onChange={(e) => handleRoleChange(record.id!, Number(e.target.value))}
|
||||
displayEmpty
|
||||
disabled={record.id === currentAdmin?.id}
|
||||
renderValue={(value) => {
|
||||
if (value === 1) {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<AdminPanelSettings fontSize="small" />
|
||||
<span>超级管理员</span>
|
||||
</Stack>
|
||||
);
|
||||
} else if (value === 2) {
|
||||
return (
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<Person fontSize="small" />
|
||||
<span>普通管理员</span>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
return "请选择角色";
|
||||
}}
|
||||
sx={{
|
||||
mr: 1
|
||||
}}
|
||||
>
|
||||
<MenuItem value={1}>
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<AdminPanelSettings fontSize="small" />
|
||||
<span>超级管理员</span>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
<MenuItem value={2}>
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<Person fontSize="small" />
|
||||
<span>普通管理员</span>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最近活跃',
|
||||
dataIndex: 'last_active_at',
|
||||
width: 140,
|
||||
title: '活跃时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 120,
|
||||
render: (text, record) => {
|
||||
return record.last_active_at === 0
|
||||
? '从未使用'
|
||||
: dayjs.unix(text).fromNow();
|
||||
return <Stack>
|
||||
<Box>{dayjs.unix(text).fromNow()}</Box>
|
||||
<Box>{record.last_active_at === 0 ? '从未使用' : dayjs.unix(text).fromNow()}</Box>
|
||||
</Stack>
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
58
ui/src/pages/memberManage/createGroupModal.tsx
Normal file
58
ui/src/pages/memberManage/createGroupModal.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, message } from '@c-x/ui';
|
||||
import { Box, TextField } from '@mui/material';
|
||||
import { postCreateGroup } from '@/api/UserGroup';
|
||||
|
||||
const CreateGroupModal = ({
|
||||
open,
|
||||
onClose,
|
||||
onCreated, // 添加一个回调函数,用于在创建成功后刷新列表
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onCreated?: () => void; // 可选的回调函数
|
||||
}) => {
|
||||
const [groupName, setGroupName] = useState('');
|
||||
|
||||
const handleCreate = async () => {
|
||||
if (!groupName.trim()) {
|
||||
message.error('请输入成员组名称');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await postCreateGroup({ name: groupName });
|
||||
message.success('成员组创建成功');
|
||||
setGroupName(''); // 清空输入框
|
||||
onCreated?.(); // 调用回调函数刷新列表
|
||||
onClose(); // 关闭弹窗
|
||||
} catch (error) {
|
||||
console.error('创建成员组失败:', error);
|
||||
message.error('创建成员组失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='创建成员组'
|
||||
width={500}
|
||||
open={open}
|
||||
onOk={handleCreate}
|
||||
onCancel={onClose}
|
||||
okText='创建'
|
||||
cancelText='取消'
|
||||
>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
label='成员组名称'
|
||||
fullWidth
|
||||
value={groupName}
|
||||
onChange={(e) => setGroupName(e.target.value)}
|
||||
variant='outlined'
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateGroupModal;
|
||||
309
ui/src/pages/memberManage/groupList.tsx
Normal file
309
ui/src/pages/memberManage/groupList.tsx
Normal file
@@ -0,0 +1,309 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Card from '@/components/card';
|
||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
||||
import { useRequest } from 'ahooks';
|
||||
import { getListUser } from '@/api/User';
|
||||
import {
|
||||
Stack,
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
Chip,
|
||||
SelectChangeEvent,
|
||||
} from '@mui/material';
|
||||
import { Table, Modal, message } from '@c-x/ui';
|
||||
import { ColumnsType } from '@c-x/ui/dist/Table';
|
||||
import { DomainAdminUser, DomainUser, DomainUserGroup } from '@/api/types';
|
||||
import { deleteDeleteGroup, getListAdminUser, getListUserGroup } from '@/api';
|
||||
import { deleteRemoveAdminFromGroup, postGrantGroup, postAddUserToGroup, deleteRemoveUserFromGroup, putUpdateUserGroup } from '@/api/UserGroup';
|
||||
import CreateGroupModal from './createGroupModal';
|
||||
import UpdateGroupModal from './updateGroupModal';
|
||||
import { Check } from '@mui/icons-material';
|
||||
|
||||
|
||||
const GroupList = () => {
|
||||
const [openCreateGroupModal, setOpenCreateGroupModal] = useState(false);
|
||||
const [openUpdateGroupModal, setOpenUpdateGroupModal] = useState(false);
|
||||
const [currentGroup, setCurrentGroup] = useState<DomainUserGroup | null>(null);
|
||||
const groupData = useRequest(() => getListUserGroup({}));
|
||||
const userData = useRequest(() => getListUser({}));
|
||||
const adminData = useRequest(() => getListAdminUser({}));
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
|
||||
const handleClick = (
|
||||
event: React.MouseEvent<HTMLButtonElement>,
|
||||
record: DomainUserGroup
|
||||
) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
setCurrentGroup(record);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const onDeleteGroup = () => {
|
||||
handleClose();
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
okText: '删除',
|
||||
okButtonProps: {
|
||||
color: 'error',
|
||||
},
|
||||
content: (
|
||||
<>
|
||||
确定要删除该成员组{' '}
|
||||
<Box component='span' sx={{ fontWeight: 700, color: 'text.primary' }}>
|
||||
{currentGroup!.name}
|
||||
</Box>{' '}
|
||||
吗?
|
||||
</>
|
||||
),
|
||||
onOk: () => {
|
||||
deleteDeleteGroup({ id: currentGroup!.id! }).then(() => {
|
||||
message.success('删除成功');
|
||||
groupData.refresh();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateGroup = () => {
|
||||
handleClose();
|
||||
setOpenUpdateGroupModal(true);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<DomainUserGroup> = [
|
||||
{
|
||||
title: '成员组',
|
||||
dataIndex: 'name',
|
||||
width: 120,
|
||||
render: (text) => {
|
||||
return text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '成员',
|
||||
dataIndex: 'users',
|
||||
render: (users, record) => {
|
||||
return <FormControl sx={{ width: '100%' }}>
|
||||
<InputLabel size='small'>成员</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={(users || []).map((u: DomainUser) => u.id)}
|
||||
label='成员'
|
||||
size='small'
|
||||
renderValue={(selectedIds: string[]) => {
|
||||
if (!Array.isArray(selectedIds)) return null;
|
||||
const selectedUsers = (userData.data?.users || []).filter((user: DomainUser) =>
|
||||
user.id && selectedIds.includes(user.id)
|
||||
);
|
||||
return selectedUsers.map((u: DomainUser) => (
|
||||
<Chip size='small' key={u.id} label={u.username}></Chip>
|
||||
));
|
||||
}}
|
||||
onChange={(event: SelectChangeEvent<string[]>) => {
|
||||
console.log(event.target)
|
||||
// 获取当前分组的成员ID列表
|
||||
const currentUserIds = (users || []).map((user: DomainUser) => user.id!);
|
||||
// 获取选择的成员ID列表
|
||||
const selectedUserIds = event.target.value as string[];
|
||||
|
||||
// 计算新增的成员ID
|
||||
const addedUserIds = selectedUserIds.filter(id => !currentUserIds.includes(id));
|
||||
// 计算移除的成员ID
|
||||
const removedUserIds = currentUserIds.filter((id: string) => !selectedUserIds.includes(id));
|
||||
|
||||
// 调用API添加成员
|
||||
if (addedUserIds.length > 0) {
|
||||
postAddUserToGroup({
|
||||
user_ids: addedUserIds,
|
||||
group_ids: [record.id!]
|
||||
}).then(() => {
|
||||
message.success('成员添加成功');
|
||||
groupData.refresh();
|
||||
}).catch(() => {
|
||||
message.error('成员添加失败');
|
||||
});
|
||||
}
|
||||
|
||||
// 调用API移除成员
|
||||
if (removedUserIds.length > 0) {
|
||||
deleteRemoveUserFromGroup({
|
||||
user_ids: removedUserIds,
|
||||
group_id: record.id!
|
||||
}).then(() => {
|
||||
message.success('成员移除成功');
|
||||
groupData.refresh();
|
||||
}).catch(() => {
|
||||
message.error('成员移除失败');
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{userData.data?.users?.map((user) => (
|
||||
<MenuItem key={user.username} value={user.id}>
|
||||
{(users.some((u: DomainUser) => {
|
||||
return u.id === user.id
|
||||
})) ? <>
|
||||
{user.username}<Check sx={{ ml: 2, width: '16px' }} />
|
||||
</> : <>
|
||||
{user.username}
|
||||
</>}
|
||||
</MenuItem>)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '管理员',
|
||||
dataIndex: 'admins',
|
||||
render: (admins, record) => {
|
||||
return <FormControl sx={{ width: '100%' }}>
|
||||
<InputLabel size='small'>管理员</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={(admins || []).map((u: DomainAdminUser) => u.id)}
|
||||
label='管理员'
|
||||
size='small'
|
||||
renderValue={(selectedIds: string[]) => {
|
||||
if (!Array.isArray(selectedIds)) return null;
|
||||
const selectedAdmins = (adminData.data?.users || []).filter((user: DomainAdminUser) =>
|
||||
user.id && selectedIds.includes(user.id)
|
||||
);
|
||||
return selectedAdmins.map((u: DomainAdminUser) => (
|
||||
<Chip size='small' key={u.id} label={u.username}></Chip>
|
||||
));
|
||||
}}
|
||||
onChange={(event: SelectChangeEvent<string[]>) => {
|
||||
console.log(event.target)
|
||||
// 获取当前分组的管理员ID列表
|
||||
const currentAdminIds = (admins || []).map((user: DomainAdminUser) => user.id!);
|
||||
// 获取选择的管理员ID列表
|
||||
const selectedAdminIds = event.target.value as string[];
|
||||
|
||||
// 计算新增的管理员ID
|
||||
const addedAdminIds = selectedAdminIds.filter(id => !currentAdminIds.includes(id));
|
||||
// 计算移除的管理员ID
|
||||
const removedAdminIds = currentAdminIds.filter((id: string) => !selectedAdminIds.includes(id));
|
||||
|
||||
// 调用API添加管理员
|
||||
if (addedAdminIds.length > 0) {
|
||||
postGrantGroup({
|
||||
admin_ids: addedAdminIds,
|
||||
group_ids: [record.id!]
|
||||
}).then(() => {
|
||||
message.success('管理员添加成功');
|
||||
groupData.refresh();
|
||||
}).catch(() => {
|
||||
message.error('管理员添加失败');
|
||||
});
|
||||
}
|
||||
|
||||
// 调用API移除管理员
|
||||
if (removedAdminIds.length > 0) {
|
||||
deleteRemoveAdminFromGroup({
|
||||
admin_ids: removedAdminIds,
|
||||
group_id: record.id!
|
||||
}).then(() => {
|
||||
message.success('管理员移除成功');
|
||||
groupData.refresh();
|
||||
}).catch(() => {
|
||||
message.error('管理员移除失败');
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{adminData.data?.users?.map((user) => (
|
||||
<MenuItem key={user.username} value={user.id}>
|
||||
{(admins.some((u: DomainAdminUser) => {
|
||||
return u.id === user.id
|
||||
})) ? <>
|
||||
{user.username}<Check sx={{ ml: 2, width: '16px' }} />
|
||||
</> : <>
|
||||
{user.username}
|
||||
</>}
|
||||
</MenuItem>)
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: 80,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<IconButton
|
||||
onClick={(e) => handleClick(e, record)}
|
||||
sx={{
|
||||
'&:hover': {
|
||||
color: 'info.main',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<MoreHorizIcon />
|
||||
</IconButton>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card sx={{ maxHeight: '500px' }}>
|
||||
<Menu anchorEl={anchorEl} open={!!anchorEl} onClose={handleClose}>
|
||||
<MenuItem onClick={onUpdateGroup}>
|
||||
修改成员组名称
|
||||
</MenuItem>
|
||||
<MenuItem sx={{ color: 'error.main' }} onClick={onDeleteGroup}>
|
||||
删除成员组
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Stack
|
||||
direction='row'
|
||||
justifyContent='space-between'
|
||||
alignItems='center'
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<Box sx={{ fontWeight: 700 }}>成员组</Box>
|
||||
<Stack direction='row' gap={1}>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
onClick={() => setOpenCreateGroupModal(true)}
|
||||
>创建成员组</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Table
|
||||
columns={columns}
|
||||
height='calc(100% - 53px)'
|
||||
dataSource={groupData.data?.groups || []}
|
||||
sx={{ mx: -2 }}
|
||||
pagination={false}
|
||||
rowKey='id'
|
||||
loading={groupData.loading}
|
||||
/>
|
||||
<CreateGroupModal
|
||||
open={openCreateGroupModal}
|
||||
onClose={() => setOpenCreateGroupModal(false)}
|
||||
onCreated={() => groupData.refresh()} // 添加 onCreated 回调函数
|
||||
/>
|
||||
<UpdateGroupModal
|
||||
open={openUpdateGroupModal}
|
||||
onClose={() => setOpenUpdateGroupModal(false)}
|
||||
onUpdated={() => groupData.refresh()}
|
||||
group={currentGroup}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupList;
|
||||
@@ -14,6 +14,7 @@ import MemberManage from './memberManage';
|
||||
import LoginHistory from './loginHistory';
|
||||
import { message } from '@c-x/ui';
|
||||
import ThirdPartyLoginSettingModal from './thirdPartyLoginSettingModal';
|
||||
import GroupList from './groupList';
|
||||
|
||||
const StyledCard = styled(Card)({
|
||||
display: 'flex',
|
||||
@@ -65,14 +66,17 @@ const User = () => {
|
||||
<Stack gap={2} sx={{ height: '100%' }}>
|
||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||
<Grid size={6} sx={{ height: '100%' }}>
|
||||
<MemberManage />
|
||||
<Stack gap={2} sx={{ height: '100%' }}>
|
||||
<GroupList />
|
||||
<MemberManage />
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid size={6} container sx={{ height: '100%' }}>
|
||||
<Stack gap={2} sx={{ height: '100%' }}>
|
||||
<StyledCard>
|
||||
<StyledLabel>强制启用两步认证</StyledLabel>
|
||||
<Switch
|
||||
checked={data?.force_two_factor_auth}
|
||||
checked={!!data?.force_two_factor_auth}
|
||||
onChange={(e) => {
|
||||
updateSetting({ force_two_factor_auth: e.target.checked });
|
||||
}}
|
||||
@@ -81,7 +85,7 @@ const User = () => {
|
||||
<StyledCard>
|
||||
<StyledLabel>禁止使用密码登录</StyledLabel>
|
||||
<Switch
|
||||
checked={data?.disable_password_login}
|
||||
checked={!!data?.disable_password_login}
|
||||
onChange={(e) =>
|
||||
updateSetting({ disable_password_login: e.target.checked })
|
||||
}
|
||||
@@ -90,7 +94,7 @@ const User = () => {
|
||||
<StyledCard>
|
||||
<StyledLabel>开放自主注册(无需邀请)</StyledLabel>
|
||||
<Switch
|
||||
checked={data?.enable_auto_login}
|
||||
checked={!!data?.enable_auto_login}
|
||||
onChange={(e) =>
|
||||
updateSetting({ enable_auto_login: e.target.checked })
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ const LoginHistory = () => {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card sx={{ flex: 1, height: 'calc(100% - 358px)' }}>
|
||||
<Card sx={{ flex: 1, height: '500px' }}>
|
||||
<Stack
|
||||
direction='row'
|
||||
justifyContent='space-between'
|
||||
|
||||
@@ -282,7 +282,7 @@ const MemberManage = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
width: 80,
|
||||
render: (_, record) => {
|
||||
@@ -302,7 +302,7 @@ const MemberManage = () => {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card sx={{ flex: 1, height: '100%' }}>
|
||||
<Card sx={{ flex: 1, height: '500px' }}>
|
||||
<Menu anchorEl={anchorEl} open={!!anchorEl} onClose={handleClose}>
|
||||
{currentUser?.status === ConstsUserStatus.UserStatusActive && (
|
||||
<MenuItem onClick={onLockUser}>锁定成员</MenuItem>
|
||||
|
||||
70
ui/src/pages/memberManage/updateGroupModal.tsx
Normal file
70
ui/src/pages/memberManage/updateGroupModal.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Modal, message } from '@c-x/ui';
|
||||
import { Box, TextField } from '@mui/material';
|
||||
import { putUpdateUserGroup } from '@/api/UserGroup';
|
||||
import { DomainUserGroup } from '@/api/types';
|
||||
|
||||
interface UpdateGroupModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onUpdated?: () => void;
|
||||
group: DomainUserGroup | null;
|
||||
}
|
||||
|
||||
const UpdateGroupModal = ({ open, onClose, onUpdated, group }: UpdateGroupModalProps) => {
|
||||
const [groupName, setGroupName] = useState('');
|
||||
|
||||
// 当group变化时,更新表单中的组名
|
||||
useEffect(() => {
|
||||
if (group) {
|
||||
setGroupName(group.name || '');
|
||||
}
|
||||
}, [group]);
|
||||
|
||||
const handleUpdate = async () => {
|
||||
if (!groupName.trim()) {
|
||||
message.error('请输入成员组名称');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!group?.id) {
|
||||
message.error('无效的成员组');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await putUpdateUserGroup({ id: group.id, name: groupName });
|
||||
message.success('成员组名称更新成功');
|
||||
setGroupName(''); // 清空输入框
|
||||
onUpdated?.(); // 调用回调函数刷新列表
|
||||
onClose(); // 关闭弹窗
|
||||
} catch (error) {
|
||||
console.error('更新成员组名称失败:', error);
|
||||
message.error('更新成员组名称失败');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='修改成员组名称'
|
||||
width={500}
|
||||
open={open}
|
||||
onOk={handleUpdate}
|
||||
onCancel={onClose}
|
||||
okText='确定'
|
||||
cancelText='取消'
|
||||
>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
label='成员组名称'
|
||||
fullWidth
|
||||
value={groupName}
|
||||
onChange={(e) => setGroupName(e.target.value)}
|
||||
variant='outlined'
|
||||
/>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpdateGroupModal;
|
||||
Reference in New Issue
Block a user