This commit is contained in:
hermes-$(hostname) committed 2026-07-08 08:53:48 +00:00
1 parent f5cf3f4440
commit 0fcdbc5f6c
29 files changed
+773 -264

No files matched your search

+61 -2
View File
@@ -1,5 +1,6 @@
import { createContext, useContext, useState, useCallback, useMemo, useRef, useEffect } from 'react';
import { menuItems } from '@/constants/menu';
import { instanceApi } from '@/api/instance';
const defaultUser = {
id: 0,
@@ -131,6 +132,8 @@ const permissionAliasMap: Record<string, string[]> = {
settings: ['config-platform'],
};
const dataChangePermissionIds = new Set(['tickets', 'workflow']);
export const actionPermissionMap: Record<string, UserRole[]> = {
'ticket.create': ['admin', 'dba', 'developer'],
'ticket.approve': ['admin', 'dba', 'developer'],
@@ -191,6 +194,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} catch { /* ignore */ }
return true;
});
const [hasWritableDataSourceAccess, setHasWritableDataSourceAccess] = useState(false);
/** 权限缓存引用 */
const permissionCacheRef = useRef<PermissionCache>({
@@ -216,6 +220,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
permissionCacheRef.current.timestamp = 0;
}, []);
const hasWritableAccessInUserPayload = useCallback((u: AuthUser): boolean => {
return Boolean(u.dataSourcePermissions?.some(ds =>
ds.actions.some(action => ['write', 'delete', 'readwrite'].includes(String(action)))
));
}, []);
/** 触发权限变更事件 */
const emitPermissionChange = useCallback((updatedUser: AuthUser) => {
listenersRef.current.forEach(listener => listener(updatedUser));
@@ -239,6 +249,43 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
return () => window.removeEventListener('storage', handleStorageChange);
}, [clearPermissionCache]);
useEffect(() => {
if (!isLoggedIn) {
setHasWritableDataSourceAccess(false);
clearPermissionCache();
return;
}
if (user.role === 'admin' || user.role === 'dba') {
setHasWritableDataSourceAccess(true);
clearPermissionCache();
return;
}
if (user.role !== 'developer') {
setHasWritableDataSourceAccess(false);
clearPermissionCache();
return;
}
let cancelled = false;
instanceApi.accessible('write')
.then((instances) => {
if (cancelled) return;
setHasWritableDataSourceAccess((instances || []).length > 0);
clearPermissionCache();
})
.catch(() => {
if (cancelled) return;
setHasWritableDataSourceAccess(hasWritableAccessInUserPayload(user));
clearPermissionCache();
});
return () => {
cancelled = true;
};
}, [isLoggedIn, user.id, user.role, user.permissionsUpdatedAt, hasWritableAccessInUserPayload, clearPermissionCache]);
const login = useCallback(async (username: string, password: string): Promise<boolean> => {
try {
const res = await authApi.login(username, password);
@@ -327,6 +374,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
cache.menu.set(menuId, false);
return false;
}
const targetMenuId = menuItem?.id || menuId;
if (user.role === 'developer' && dataChangePermissionIds.has(targetMenuId) && !hasWritableDataSourceAccess) {
cache.menu.set(menuId, false);
cache.timestamp = Date.now();
return false;
}
const permissionKeys = [
menuId,
@@ -340,7 +393,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
cache.menu.set(menuId, result);
cache.timestamp = Date.now();
return result;
}, [user.role, user.customPermissions, isLoggedIn, isCacheValid]);
}, [user.role, user.customPermissions, hasWritableDataSourceAccess, isLoggedIn, isCacheValid]);
/** 操作权限检查(带缓存,支持自定义权限覆盖) */
const hasActionPermission = useCallback((action: PermissionAction | string): boolean => {
@@ -351,6 +404,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
return cache.action.get(action)!;
}
if (user.role === 'developer' && ['ticket.create', 'ticket.approve'].includes(String(action)) && !hasWritableDataSourceAccess) {
cache.action.set(action, false);
cache.timestamp = Date.now();
return false;
}
// 优先检查自定义权限
if (user.customPermissions && action in user.customPermissions) {
const customResult = user.customPermissions[action];
@@ -369,7 +428,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
cache.action.set(action, result);
cache.timestamp = Date.now();
return result;
}, [user.role, user.customPermissions, isLoggedIn, isCacheValid]);
}, [user.role, user.customPermissions, hasWritableDataSourceAccess, isLoggedIn, isCacheValid]);
/** 数据源级权限检查 */
const hasDataSourcePermission = useCallback((
+1 -1
View File
@@ -45,7 +45,7 @@ export function useLogin(): LoginState {
}
if (prefs?.login_style) localStorage.setItem('login-style', prefs.login_style);
} catch { /* ignore */ }
navigate('/dashboard');
navigate('/queries');
} else {
setError('用户名或密码错误');
}