feat: add RBAC permission system to admin cabinet frontend

- Permission store (Zustand) with wildcard matching and role level checks
- PermissionRoute guard for route-level access control
- PermissionGate component for element-level visibility
- Admin Roles page: CRUD, permission editor, role assignment
- Admin Policies page: ABAC policy management (time/IP conditions)
- Admin Audit Log page: filterable log viewer with CSV export
- AdminPanel sidebar navigation gated by permissions
- 4 locale files updated (en, ru, zh, fa) with RBAC translations
- API client module for all RBAC endpoints
This commit is contained in:
Fringg
2026-02-25 03:02:52 +03:00
parent 48be067d1b
commit 874ee2682e
15 changed files with 5320 additions and 121 deletions

215
src/api/rbac.ts Normal file
View File

@@ -0,0 +1,215 @@
import apiClient from './client';
// === Types ===
export interface AdminRole {
id: number;
name: string;
description: string | null;
level: number;
permissions: string[];
color: string | null;
icon: string | null;
is_system: boolean;
is_active: boolean;
created_at: string;
updated_at: string;
user_count?: number;
}
export interface CreateRolePayload {
name: string;
description?: string | null;
level: number;
permissions: string[];
color?: string | null;
icon?: string | null;
}
export interface UpdateRolePayload {
name?: string;
description?: string | null;
level?: number;
permissions?: string[];
color?: string | null;
icon?: string | null;
is_active?: boolean;
}
export interface UserRoleAssignment {
id: number;
user_id: number;
role_id: number;
role_name: string;
assigned_by: number | null;
assigned_at: string;
expires_at: string | null;
user_email: string | null;
user_first_name: string | null;
user_telegram_id: number | null;
}
export interface AssignRolePayload {
user_id: number;
role_id: number;
expires_at?: string | null;
}
export interface AccessPolicy {
id: number;
name: string;
description: string | null;
resource: string;
action: string;
effect: 'allow' | 'deny';
conditions: Record<string, unknown>;
priority: number;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface CreatePolicyPayload {
name: string;
description?: string | null;
resource: string;
action: string;
effect: 'allow' | 'deny';
conditions?: Record<string, unknown>;
priority?: number;
}
export interface UpdatePolicyPayload {
name?: string;
description?: string | null;
resource?: string;
action?: string;
effect?: 'allow' | 'deny';
conditions?: Record<string, unknown>;
priority?: number;
is_active?: boolean;
}
export interface AuditLogEntry {
id: number;
user_id: number;
action: string;
resource: string;
resource_id: string | null;
details: Record<string, unknown>;
ip_address: string | null;
user_agent: string | null;
created_at: string;
user_email: string | null;
user_first_name: string | null;
}
export interface AuditLogFilters {
user_id?: number;
action?: string;
resource?: string;
date_from?: string;
date_to?: string;
offset?: number;
limit?: number;
}
export interface AuditLogResponse {
items: AuditLogEntry[];
total: number;
offset: number;
limit: number;
}
export interface PermissionSection {
section: string;
actions: string[];
}
// === API ===
const BASE = '/cabinet/admin/rbac';
export const rbacApi = {
// --- Roles ---
getRoles: async (): Promise<AdminRole[]> => {
const response = await apiClient.get<AdminRole[]>(`${BASE}/roles`);
return response.data;
},
createRole: async (payload: CreateRolePayload): Promise<AdminRole> => {
const response = await apiClient.post<AdminRole>(`${BASE}/roles`, payload);
return response.data;
},
updateRole: async (roleId: number, payload: UpdateRolePayload): Promise<AdminRole> => {
const response = await apiClient.put<AdminRole>(`${BASE}/roles/${roleId}`, payload);
return response.data;
},
deleteRole: async (roleId: number): Promise<void> => {
await apiClient.delete(`${BASE}/roles/${roleId}`);
},
// --- Permission Registry ---
getPermissionRegistry: async (): Promise<PermissionSection[]> => {
const response = await apiClient.get<PermissionSection[]>(`${BASE}/permissions`);
return response.data;
},
// --- Role Users ---
getRoleUsers: async (roleId: number): Promise<UserRoleAssignment[]> => {
const response = await apiClient.get<UserRoleAssignment[]>(`${BASE}/roles/${roleId}/users`);
return response.data;
},
assignRole: async (payload: AssignRolePayload): Promise<UserRoleAssignment> => {
const response = await apiClient.post<UserRoleAssignment>(`${BASE}/assignments`, payload);
return response.data;
},
revokeRole: async (assignmentId: number): Promise<void> => {
await apiClient.delete(`${BASE}/assignments/${assignmentId}`);
},
// --- Access Policies ---
getPolicies: async (): Promise<AccessPolicy[]> => {
const response = await apiClient.get<AccessPolicy[]>(`${BASE}/policies`);
return response.data;
},
createPolicy: async (payload: CreatePolicyPayload): Promise<AccessPolicy> => {
const response = await apiClient.post<AccessPolicy>(`${BASE}/policies`, payload);
return response.data;
},
updatePolicy: async (policyId: number, payload: UpdatePolicyPayload): Promise<AccessPolicy> => {
const response = await apiClient.put<AccessPolicy>(`${BASE}/policies/${policyId}`, payload);
return response.data;
},
deletePolicy: async (policyId: number): Promise<void> => {
await apiClient.delete(`${BASE}/policies/${policyId}`);
},
// --- Audit Log ---
getAuditLog: async (filters?: AuditLogFilters): Promise<AuditLogResponse> => {
const response = await apiClient.get<AuditLogResponse>(`${BASE}/audit-log`, {
params: filters,
});
return response.data;
},
exportAuditLog: async (filters?: AuditLogFilters): Promise<Blob> => {
const response = await apiClient.get(`${BASE}/audit-log/export`, {
params: filters,
responseType: 'blob',
});
return response.data as Blob;
},
};