FastAPI backend with auth, user, instance, ticket, query, dashboard APIs. React + TypeScript frontend with mock data UI (10 pages). SQLite + async SQLAlchemy, JWT auth, role-based access control. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
158 lines
4.7 KiB
TypeScript
158 lines
4.7 KiB
TypeScript
import { createContext, useContext, useState, useCallback, useMemo } from 'react';
|
|
import { menuItems, currentUser as defaultUser } from '@/lib/mock';
|
|
|
|
export type UserRole = 'admin' | 'dba' | 'developer' | 'guest';
|
|
|
|
export interface AuthUser {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
role: UserRole;
|
|
roleName: string;
|
|
avatar: string;
|
|
}
|
|
|
|
interface AuthContextType {
|
|
user: AuthUser;
|
|
isLoggedIn: boolean;
|
|
login: (username: string, password: string) => Promise<boolean>;
|
|
logout: () => void;
|
|
switchRole: (role: UserRole) => void;
|
|
hasPermission: (menuId: string) => boolean;
|
|
visibleMenuItems: typeof menuItems;
|
|
isAdmin: boolean;
|
|
isDBA: boolean;
|
|
isDeveloper: boolean;
|
|
isGuest: boolean;
|
|
}
|
|
|
|
const roleNameMap: Record<UserRole, string> = {
|
|
admin: '超级管理员',
|
|
dba: '数据库管理员',
|
|
developer: '开发人员',
|
|
guest: '访客',
|
|
};
|
|
|
|
const routePermissionMap: Record<string, UserRole[]> = {
|
|
dashboard: ['admin', 'dba', 'developer', 'guest'],
|
|
mysql: ['admin', 'dba'],
|
|
postgres: ['admin', 'dba'],
|
|
redis: ['admin', 'dba'],
|
|
mongo: ['admin', 'dba'],
|
|
clickhouse: ['admin', 'dba'],
|
|
tickets: ['admin', 'dba', 'developer'],
|
|
queries: ['admin', 'dba', 'developer', 'guest'],
|
|
roles: ['admin'],
|
|
users: ['admin'],
|
|
'data-permission': ['admin', 'dba'],
|
|
'audit-rules': ['admin', 'dba'],
|
|
workflow: ['admin', 'dba'],
|
|
'auto-task': ['admin'],
|
|
announcement: ['admin'],
|
|
'config-platform': ['admin'],
|
|
notifications: ['admin'],
|
|
};
|
|
|
|
// 页面内操作权限
|
|
export const actionPermissionMap: Record<string, UserRole[]> = {
|
|
'ticket.create': ['admin', 'dba', 'developer'],
|
|
'ticket.approve': ['admin', 'dba'],
|
|
'ticket.execute': ['admin', 'dba'],
|
|
'ticket.rollback': ['admin', 'dba'],
|
|
'ticket.delete': ['admin'],
|
|
'query.create': ['admin', 'dba', 'developer'],
|
|
'query.export': ['admin', 'dba', 'developer'],
|
|
'instance.add': ['admin'],
|
|
'instance.edit': ['admin', 'dba'],
|
|
'instance.delete': ['admin'],
|
|
'user.manage': ['admin'],
|
|
'role.manage': ['admin'],
|
|
'rule.manage': ['admin', 'dba'],
|
|
'workflow.manage': ['admin', 'dba'],
|
|
'config.manage': ['admin'],
|
|
'announcement.manage': ['admin'],
|
|
'auto-task.manage': ['admin'],
|
|
};
|
|
|
|
const AuthContext = createContext<AuthContextType | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
const [user, setUser] = useState<AuthUser>({
|
|
id: defaultUser.id,
|
|
username: defaultUser.username,
|
|
email: defaultUser.email,
|
|
role: defaultUser.role,
|
|
roleName: defaultUser.roleName,
|
|
avatar: defaultUser.avatar,
|
|
});
|
|
const [isLoggedIn, setIsLoggedIn] = useState(true);
|
|
|
|
const login = useCallback(async (username: string, _password: string): Promise<boolean> => {
|
|
await new Promise(r => setTimeout(r, 800));
|
|
setIsLoggedIn(true);
|
|
setUser({
|
|
id: 1,
|
|
username,
|
|
email: `${username}@company.com`,
|
|
role: username === 'admin' ? 'admin' : username === 'dba' ? 'dba' : 'developer',
|
|
roleName: username === 'admin' ? '超级管理员' : username === 'dba' ? '数据库管理员' : '开发人员',
|
|
avatar: username.charAt(0).toUpperCase(),
|
|
});
|
|
return true;
|
|
}, []);
|
|
|
|
const logout = useCallback(() => {
|
|
setIsLoggedIn(false);
|
|
}, []);
|
|
|
|
const switchRole = useCallback((role: UserRole) => {
|
|
setUser(prev => ({
|
|
...prev,
|
|
role,
|
|
roleName: roleNameMap[role],
|
|
username: role === 'admin' ? 'admin' : role === 'dba' ? 'zhangsan' : role === 'developer' ? 'wangwu' : 'visitor',
|
|
email: `${role}@company.com`,
|
|
avatar: role.charAt(0).toUpperCase(),
|
|
}));
|
|
}, []);
|
|
|
|
const hasPermission = useCallback((menuId: string): boolean => {
|
|
const allowedRoles = routePermissionMap[menuId];
|
|
if (!allowedRoles) return false;
|
|
return allowedRoles.includes(user.role);
|
|
}, [user.role]);
|
|
|
|
const visibleMenuItems = useMemo(() => {
|
|
return menuItems.filter(item => item.roles.includes(user.role));
|
|
}, [user.role]);
|
|
|
|
const value = useMemo(() => ({
|
|
user,
|
|
isLoggedIn,
|
|
login,
|
|
logout,
|
|
switchRole,
|
|
hasPermission,
|
|
visibleMenuItems,
|
|
isAdmin: user.role === 'admin',
|
|
isDBA: user.role === 'dba',
|
|
isDeveloper: user.role === 'developer',
|
|
isGuest: user.role === 'guest',
|
|
}), [user, isLoggedIn, login, logout, switchRole, hasPermission, visibleMenuItems]);
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
}
|
|
|
|
export function useAuth(): AuthContextType {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
|
return ctx;
|
|
}
|
|
|
|
export function useActionPermission(action: string): boolean {
|
|
const { user } = useAuth();
|
|
const allowedRoles = actionPermissionMap[action];
|
|
if (!allowedRoles) return false;
|
|
return allowedRoles.includes(user.role);
|
|
}
|