You've already forked DataMate
feat(auth): 为数据管理和RAG服务增加资源访问控制
- 在DatasetApplicationService中注入ResourceAccessService并添加所有权验证 - 在KnowledgeSetApplicationService中注入ResourceAccessService并添加所有权验证 - 修改DatasetRepository接口和实现类,增加按创建者过滤的方法 - 修改KnowledgeSetRepository接口和实现类,增加按创建者过滤的方法 - 在RAG索引器服务中添加知识库访问权限检查和作用域过滤 - 更新实体元对象处理器以使用请求用户上下文获取当前用户 - 在前端设置页面添加用户权限管理功能和角色权限控制 - 为Python标注服务增加用户上下文和数据集访问权限验证
This commit is contained in:
@@ -1,12 +1,51 @@
|
||||
import { useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Menu } from "antd";
|
||||
import { SettingOutlined } from "@ant-design/icons";
|
||||
import { SettingOutlined, TeamOutlined } from "@ant-design/icons";
|
||||
import { Component } from "lucide-react";
|
||||
import SystemConfig from "./SystemConfig";
|
||||
import ModelAccess from "./ModelAccess";
|
||||
import UserPermissionManagement from "./UserPermissionManagement";
|
||||
import { useAppSelector } from "@/store/hooks";
|
||||
import { hasPermission, PermissionCodes } from "@/auth/permissions";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [activeTab, setActiveTab] = useState("model-access");
|
||||
const permissions = useAppSelector((state) => state.auth.permissions);
|
||||
const canManageUsers = hasPermission(permissions, PermissionCodes.userManage);
|
||||
const canViewRoles = hasPermission(permissions, PermissionCodes.roleManage);
|
||||
const canViewPermissions = hasPermission(
|
||||
permissions,
|
||||
PermissionCodes.permissionManage
|
||||
);
|
||||
const tabs = useMemo(() => {
|
||||
const nextTabs = [
|
||||
{
|
||||
key: "model-access",
|
||||
icon: <Component className="w-4 h-4" />,
|
||||
label: "模型接入",
|
||||
},
|
||||
{
|
||||
key: "system-config",
|
||||
icon: <SettingOutlined />,
|
||||
label: "参数配置",
|
||||
},
|
||||
];
|
||||
if (canManageUsers || canViewRoles || canViewPermissions) {
|
||||
nextTabs.push({
|
||||
key: "user-permission",
|
||||
icon: <TeamOutlined />,
|
||||
label: "用户与权限",
|
||||
});
|
||||
}
|
||||
return nextTabs;
|
||||
}, [canManageUsers, canViewPermissions, canViewRoles]);
|
||||
const [activeTab, setActiveTab] = useState<string>(tabs[0]?.key ?? "model-access");
|
||||
|
||||
useEffect(() => {
|
||||
const hasActiveTab = tabs.some((tab) => tab.key === activeTab);
|
||||
if (!hasActiveTab && tabs.length > 0) {
|
||||
setActiveTab(tabs[0].key);
|
||||
}
|
||||
}, [activeTab, tabs]);
|
||||
|
||||
return (
|
||||
<div className="h-screen flex">
|
||||
@@ -18,21 +57,10 @@ export default function SettingsPage() {
|
||||
<div className="h-full">
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={[
|
||||
{
|
||||
key: "model-access",
|
||||
icon: <Component className="w-4 h-4" />,
|
||||
label: "模型接入",
|
||||
},
|
||||
{
|
||||
key: "system-config",
|
||||
icon: <SettingOutlined />,
|
||||
label: "参数配置",
|
||||
},
|
||||
]}
|
||||
items={tabs}
|
||||
selectedKeys={[activeTab]}
|
||||
onClick={({ key }) => {
|
||||
setActiveTab(key);
|
||||
setActiveTab(String(key));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -41,6 +69,13 @@ export default function SettingsPage() {
|
||||
{/* 内容区域,根据 activeTab 渲染不同的组件 */}
|
||||
{activeTab === "system-config" && <SystemConfig />}
|
||||
{activeTab === "model-access" && <ModelAccess />}
|
||||
{activeTab === "user-permission" && (
|
||||
<UserPermissionManagement
|
||||
canManageUsers={canManageUsers}
|
||||
canViewRoles={canViewRoles}
|
||||
canViewPermissions={canViewPermissions}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
321
frontend/src/pages/SettingsPage/UserPermissionManagement.tsx
Normal file
321
frontend/src/pages/SettingsPage/UserPermissionManagement.tsx
Normal file
@@ -0,0 +1,321 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Empty,
|
||||
message,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import {
|
||||
assignUserRolesUsingPut,
|
||||
listAuthPermissionsUsingGet,
|
||||
listAuthRolesUsingGet,
|
||||
listAuthUsersUsingGet,
|
||||
} from "./settings.apis";
|
||||
import type {
|
||||
AuthPermissionInfo,
|
||||
AuthRoleInfo,
|
||||
AuthUserWithRoles,
|
||||
} from "./settings.apis";
|
||||
|
||||
interface ApiResponse<T> {
|
||||
code: string;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface UserPermissionManagementProps {
|
||||
canManageUsers: boolean;
|
||||
canViewRoles: boolean;
|
||||
canViewPermissions: boolean;
|
||||
}
|
||||
|
||||
export default function UserPermissionManagement({
|
||||
canManageUsers,
|
||||
canViewRoles,
|
||||
canViewPermissions,
|
||||
}: UserPermissionManagementProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [users, setUsers] = useState<AuthUserWithRoles[]>([]);
|
||||
const [roles, setRoles] = useState<AuthRoleInfo[]>([]);
|
||||
const [permissions, setPermissions] = useState<AuthPermissionInfo[]>([]);
|
||||
const [editingUser, setEditingUser] = useState<AuthUserWithRoles | null>(null);
|
||||
const [selectedRoleCodes, setSelectedRoleCodes] = useState<string[]>([]);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
const canShowAnything = canManageUsers || canViewRoles || canViewPermissions;
|
||||
const canAssignRoles = canManageUsers && roles.length > 0;
|
||||
|
||||
const roleNameMap = useMemo(
|
||||
() => new Map(roles.map((role) => [role.roleCode, role.roleName || role.roleCode])),
|
||||
[roles]
|
||||
);
|
||||
const roleCodeToIdMap = useMemo(
|
||||
() => new Map(roles.map((role) => [role.roleCode, role.id])),
|
||||
[roles]
|
||||
);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const requestTasks: Array<Promise<unknown>> = [];
|
||||
if (canManageUsers || canViewRoles || canViewPermissions) {
|
||||
requestTasks.push(listAuthUsersUsingGet());
|
||||
}
|
||||
if (canManageUsers || canViewRoles) {
|
||||
requestTasks.push(listAuthRolesUsingGet());
|
||||
}
|
||||
if (canViewPermissions) {
|
||||
requestTasks.push(listAuthPermissionsUsingGet());
|
||||
}
|
||||
const responses = await Promise.all(requestTasks);
|
||||
let index = 0;
|
||||
if (canManageUsers || canViewRoles || canViewPermissions) {
|
||||
const userResponse = responses[index++] as ApiResponse<AuthUserWithRoles[]>;
|
||||
setUsers(userResponse?.data ?? []);
|
||||
}
|
||||
if (canManageUsers || canViewRoles) {
|
||||
const roleResponse = responses[index++] as ApiResponse<AuthRoleInfo[]>;
|
||||
setRoles(roleResponse?.data ?? []);
|
||||
} else {
|
||||
setRoles([]);
|
||||
}
|
||||
if (canViewPermissions) {
|
||||
const permissionResponse = responses[index++] as ApiResponse<AuthPermissionInfo[]>;
|
||||
setPermissions(permissionResponse?.data ?? []);
|
||||
} else {
|
||||
setPermissions([]);
|
||||
}
|
||||
} catch (error) {
|
||||
message.error("加载用户权限信息失败");
|
||||
console.error("加载用户权限信息失败:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [canManageUsers, canViewPermissions, canViewRoles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canShowAnything) {
|
||||
return;
|
||||
}
|
||||
void loadData();
|
||||
}, [canShowAnything, loadData]);
|
||||
|
||||
const userColumns: ColumnsType<AuthUserWithRoles> = [
|
||||
{
|
||||
title: "用户名",
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: "姓名",
|
||||
dataIndex: "fullName",
|
||||
key: "fullName",
|
||||
width: 180,
|
||||
render: (value?: string) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "邮箱",
|
||||
dataIndex: "email",
|
||||
key: "email",
|
||||
render: (value?: string) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "enabled",
|
||||
key: "enabled",
|
||||
width: 120,
|
||||
render: (enabled?: boolean) =>
|
||||
enabled ? <Tag color="green">启用</Tag> : <Tag color="default">禁用</Tag>,
|
||||
},
|
||||
{
|
||||
title: "角色",
|
||||
dataIndex: "roleCodes",
|
||||
key: "roleCodes",
|
||||
render: (roleCodes: string[]) => (
|
||||
<Space wrap>
|
||||
{(roleCodes ?? []).map((roleCode) => (
|
||||
<Tag key={roleCode}>{roleNameMap.get(roleCode) || roleCode}</Tag>
|
||||
))}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
width: 120,
|
||||
render: (_, record) => (
|
||||
<Button
|
||||
type="link"
|
||||
disabled={!canAssignRoles}
|
||||
onClick={() => {
|
||||
setEditingUser(record);
|
||||
setSelectedRoleCodes(record.roleCodes ?? []);
|
||||
}}
|
||||
>
|
||||
分配角色
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const roleColumns: ColumnsType<AuthRoleInfo> = [
|
||||
{ title: "角色编码", dataIndex: "roleCode", key: "roleCode", width: 220 },
|
||||
{ title: "角色名称", dataIndex: "roleName", key: "roleName", width: 180 },
|
||||
{
|
||||
title: "状态",
|
||||
dataIndex: "enabled",
|
||||
key: "enabled",
|
||||
width: 120,
|
||||
render: (enabled?: boolean) =>
|
||||
enabled ? <Tag color="green">启用</Tag> : <Tag color="default">禁用</Tag>,
|
||||
},
|
||||
{
|
||||
title: "描述",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
render: (value?: string) => value || "-",
|
||||
},
|
||||
];
|
||||
|
||||
const permissionColumns: ColumnsType<AuthPermissionInfo> = [
|
||||
{
|
||||
title: "权限编码",
|
||||
dataIndex: "permissionCode",
|
||||
key: "permissionCode",
|
||||
width: 260,
|
||||
},
|
||||
{
|
||||
title: "权限名称",
|
||||
dataIndex: "permissionName",
|
||||
key: "permissionName",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: "模块",
|
||||
dataIndex: "module",
|
||||
key: "module",
|
||||
width: 140,
|
||||
render: (value?: string) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "动作",
|
||||
dataIndex: "action",
|
||||
key: "action",
|
||||
width: 120,
|
||||
render: (value?: string) => value || "-",
|
||||
},
|
||||
{
|
||||
title: "接口",
|
||||
key: "api",
|
||||
render: (_, record) =>
|
||||
record.pathPattern ? `${record.method || "ALL"} ${record.pathPattern}` : "-",
|
||||
},
|
||||
];
|
||||
|
||||
const handleAssignRoles = async () => {
|
||||
if (!editingUser) {
|
||||
return;
|
||||
}
|
||||
if (selectedRoleCodes.length === 0) {
|
||||
message.warning("请至少选择一个角色");
|
||||
return;
|
||||
}
|
||||
const roleIds = selectedRoleCodes
|
||||
.map((roleCode) => roleCodeToIdMap.get(roleCode))
|
||||
.filter((roleId): roleId is string => Boolean(roleId));
|
||||
if (roleIds.length !== selectedRoleCodes.length) {
|
||||
message.error("角色映射失败,请刷新后重试");
|
||||
return;
|
||||
}
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await assignUserRolesUsingPut(editingUser.id, roleIds);
|
||||
message.success("角色分配成功");
|
||||
setEditingUser(null);
|
||||
setSelectedRoleCodes([]);
|
||||
await loadData();
|
||||
} catch (error) {
|
||||
message.error("角色分配失败");
|
||||
console.error("角色分配失败:", error);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!canShowAnything) {
|
||||
return <Empty description="当前账号无用户与权限管理权限" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size={16} className="w-full">
|
||||
<Card title="用户管理">
|
||||
<Table
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
dataSource={users}
|
||||
columns={userColumns}
|
||||
pagination={{ pageSize: 10, showSizeChanger: false }}
|
||||
/>
|
||||
</Card>
|
||||
{canViewRoles && (
|
||||
<Card title="角色列表">
|
||||
<Table
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
dataSource={roles}
|
||||
columns={roleColumns}
|
||||
pagination={{ pageSize: 8, showSizeChanger: false }}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
{canViewPermissions && (
|
||||
<Card title="权限列表">
|
||||
<Table
|
||||
loading={loading}
|
||||
rowKey="id"
|
||||
dataSource={permissions}
|
||||
columns={permissionColumns}
|
||||
pagination={{ pageSize: 10, showSizeChanger: false }}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
<Modal
|
||||
title={`分配角色 - ${editingUser?.username || ""}`}
|
||||
open={Boolean(editingUser)}
|
||||
confirmLoading={submitting}
|
||||
onOk={() => {
|
||||
void handleAssignRoles();
|
||||
}}
|
||||
onCancel={() => {
|
||||
setEditingUser(null);
|
||||
setSelectedRoleCodes([]);
|
||||
}}
|
||||
>
|
||||
{roles.length === 0 ? (
|
||||
<Typography.Text type="secondary">暂无可分配角色</Typography.Text>
|
||||
) : (
|
||||
<Select
|
||||
mode="multiple"
|
||||
className="w-full"
|
||||
placeholder="请选择角色"
|
||||
value={selectedRoleCodes}
|
||||
onChange={(values) => setSelectedRoleCodes(values)}
|
||||
options={roles.map((role) => ({
|
||||
value: role.roleCode,
|
||||
label: `${role.roleName} (${role.roleCode})`,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { get, post, put, del } from "@/utils/request";
|
||||
|
||||
// 模型相关接口
|
||||
export function queryModelProvidersUsingGet(params?: any) {
|
||||
export function queryModelProvidersUsingGet(params?: Record<string, unknown>) {
|
||||
return get("/api/models/providers", params);
|
||||
}
|
||||
|
||||
export function queryModelListUsingGet(data: any) {
|
||||
export function queryModelListUsingGet(data: Record<string, unknown>) {
|
||||
return get("/api/models/list", data);
|
||||
}
|
||||
|
||||
@@ -15,12 +15,12 @@ export function queryModelDetailByIdUsingGet(id: string | number) {
|
||||
|
||||
export function updateModelByIdUsingPut(
|
||||
id: string | number,
|
||||
data: any
|
||||
data: Record<string, unknown>
|
||||
) {
|
||||
return put(`/api/models/${id}`, data);
|
||||
}
|
||||
|
||||
export function createModelUsingPost(data: any) {
|
||||
export function createModelUsingPost(data: Record<string, unknown>) {
|
||||
return post("/api/models/create", data);
|
||||
}
|
||||
|
||||
@@ -28,13 +28,60 @@ export function deleteModelByIdUsingDelete(id: string | number) {
|
||||
return del(`/api/models/${id}`);
|
||||
}
|
||||
|
||||
|
||||
// 获取系统参数列表
|
||||
export function getSysParamList() {
|
||||
return get('/api/sys-param/list');
|
||||
return get("/api/sys-param/list");
|
||||
}
|
||||
|
||||
// 更新系统参数值
|
||||
export const updateSysParamValue = async (params: { id: string; paramValue: string }) => {
|
||||
export const updateSysParamValue = async (params: {
|
||||
id: string;
|
||||
paramValue: string;
|
||||
}) => {
|
||||
return put(`/api/sys-param/${params.id}`, params);
|
||||
};
|
||||
};
|
||||
|
||||
export interface AuthUserWithRoles {
|
||||
id: number;
|
||||
username: string;
|
||||
fullName?: string;
|
||||
email?: string;
|
||||
enabled?: boolean;
|
||||
roleCodes: string[];
|
||||
}
|
||||
|
||||
export interface AuthRoleInfo {
|
||||
id: string;
|
||||
roleCode: string;
|
||||
roleName: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface AuthPermissionInfo {
|
||||
id: string;
|
||||
permissionCode: string;
|
||||
permissionName: string;
|
||||
module?: string;
|
||||
action?: string;
|
||||
pathPattern?: string;
|
||||
method?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
// 用户与权限管理接口
|
||||
export function listAuthUsersUsingGet() {
|
||||
return get("/api/auth/users");
|
||||
}
|
||||
|
||||
export function listAuthRolesUsingGet() {
|
||||
return get("/api/auth/roles");
|
||||
}
|
||||
|
||||
export function listAuthPermissionsUsingGet() {
|
||||
return get("/api/auth/permissions");
|
||||
}
|
||||
|
||||
export function assignUserRolesUsingPut(userId: number, roleIds: string[]) {
|
||||
return put(`/api/auth/users/${userId}/roles`, { roleIds });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user