diff --git a/frontend/src/app/providers/layout/navigation/navigation.tsx b/frontend/src/app/providers/layout/navigation/navigation.tsx index a5deef1..37f7223 100644 --- a/frontend/src/app/providers/layout/navigation/navigation.tsx +++ b/frontend/src/app/providers/layout/navigation/navigation.tsx @@ -42,14 +42,31 @@ export const Navigation: React.FC = ({ const currentTheme = getCurrentTheme(); const navItems = [ - { path: "/templates", label: "Шаблоны", icon: FaCode }, - { path: "/add-agents", label: "Деплой", icon: FaRocket }, - { path: "/registration", label: "Регистрация", icon: FaKey }, - { path: "/logs", label: "Логи", icon: FaFileAlt }, + { path: "/templates", label: "Шаблоны", icon: FaCode, requireView: true }, + { + path: "/add-agents", + label: "Деплой", + icon: FaRocket, + requireManageAgent: true, + }, + { + path: "/registration", + label: "Регистрация", + icon: FaKey, + requireManageAgent: true, + }, + { path: "/logs", label: "Логи", icon: FaFileAlt, requireView: true }, ]; const isActive = (path: string) => location.pathname === path; + // Filter nav items based on user permissions + const filteredNavItems = navItems.filter((item) => { + if (item.requireView && !user?.permission_view) return false; + if (item.requireManageAgent && !user?.permission_manage_agent) return false; + return true; + }); + useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if ( @@ -77,42 +94,37 @@ export const Navigation: React.FC = ({ const renderNavItems = (showLabels: boolean, iconSize: number) => (
- {navItems - .filter((item) => { - if ((item as any).adminOnly && !user?.permission_admin) return false; - return true; - }) - .map((item) => { - const Icon = item.icon; - const active = isActive(item.path); - return ( - - ); - })} + {filteredNavItems.map((item) => { + const Icon = item.icon; + const active = isActive(item.path); + return ( + + ); + })}
); @@ -375,14 +387,31 @@ export const BottomNav: React.FC = () => { const { user } = useAuthStore(); const navItems = [ - { path: "/templates", label: "Шаблоны", icon: FaCode }, - { path: "/add-agents", label: "Деплой", icon: FaRocket }, - { path: "/registration", label: "Регистрация", icon: FaKey }, - { path: "/logs", label: "Логи", icon: FaFileAlt }, + { path: "/templates", label: "Шаблоны", icon: FaCode, requireView: true }, + { + path: "/add-agents", + label: "Деплой", + icon: FaRocket, + requireManageAgent: true, + }, + { + path: "/registration", + label: "Регистрация", + icon: FaKey, + requireManageAgent: true, + }, + { path: "/logs", label: "Логи", icon: FaFileAlt, requireView: true }, ]; const isActive = (path: string) => location.pathname === path; + // Filter nav items based on user permissions + const filteredNavItems = navItems.filter((item) => { + if (item.requireView && !user?.permission_view) return false; + if (item.requireManageAgent && !user?.permission_manage_agent) return false; + return true; + }); + return (
{ }} >
- {navItems - .filter((item) => { - if ((item as any).adminOnly && !user?.permission_admin) - return false; - return true; - }) - .map((item) => { - const Icon = item.icon; - const active = isActive(item.path); - return ( - - ); - })} + {filteredNavItems.map((item) => { + const Icon = item.icon; + const active = isActive(item.path); + return ( + + ); + })}
); diff --git a/frontend/src/app/providers/routing/helper/protected.route.tsx b/frontend/src/app/providers/routing/helper/protected.route.tsx index 8eb41a6..02cdd63 100644 --- a/frontend/src/app/providers/routing/helper/protected.route.tsx +++ b/frontend/src/app/providers/routing/helper/protected.route.tsx @@ -1,12 +1,42 @@ -import { useAuthStore } from "@/modules/auth/store/useAuthStore"; import { Navigate } from "react-router-dom"; +import { useAuthStore } from "@/modules/auth/store/useAuthStore"; -export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { - const { isAuthenticated } = useAuthStore(); +interface ProtectedRouteProps { + children: React.ReactNode; + requireView?: boolean; + requireManageAgent?: boolean; + requireAdmin?: boolean; + fallbackPath?: string; +} - // if (!isAuthenticated) { - // return ; - // } +export const ProtectedRoute: React.FC = ({ + children, + requireView = false, + requireManageAgent = false, + requireAdmin = false, + fallbackPath = "/", +}) => { + const { user, isAuthenticated } = useAuthStore(); + + if (!isAuthenticated && user?.token) { + // User is authenticated based on token + } + + if (!user) { + return ; + } + + if (requireView && !user.permission_view) { + return ; + } + + if (requireManageAgent && !user.permission_manage_agent) { + return ; + } + + if (requireAdmin && !user.permission_admin) { + return ; + } return <>{children}; }; diff --git a/frontend/src/app/providers/routing/routing.tsx b/frontend/src/app/providers/routing/routing.tsx index 5ffab50..2e8d07c 100644 --- a/frontend/src/app/providers/routing/routing.tsx +++ b/frontend/src/app/providers/routing/routing.tsx @@ -15,6 +15,7 @@ import { LogsPage } from "@/pages/logs.page"; import { GraphsPage } from "@/pages/graphs.page"; import { DashboardPage } from "@/pages/dashboard.page"; import { AgentDashboardPage } from "@/pages/agent-dashboard.page"; +import { ProtectedRoute } from "./helper/protected.route"; export const mockGraphData: GraphData = { nodes: [ @@ -122,18 +123,82 @@ export const Routing = () => { } /> }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - {/* } /> */} + {/* Routes requiring 'view' permission */} + + + + } + /> + + + + } + /> + + + + } + /> } + element={ + + + + } + /> + + {/* Routes requiring 'manage_agent' permission */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + {/* Admin route requiring 'admin' permission */} + + + + } /> diff --git a/frontend/src/pages/templates.page.tsx b/frontend/src/pages/templates.page.tsx index 9b9150b..69a8fa3 100644 --- a/frontend/src/pages/templates.page.tsx +++ b/frontend/src/pages/templates.page.tsx @@ -8,6 +8,7 @@ import { RunScriptModal } from "../modules/ide/components/RunScriptModal"; import { AddInterpreterModal } from "../modules/ide/components/AddInterpreterModal"; import type { FileNode } from "../modules/ide"; import { scriptsApi } from "../modules/ide/api/scripts.api"; +import { useAuthStore } from "@/modules/auth/store/useAuthStore"; const convertTreeToFileNode = (data: any[]): FileNode => { const convertItem = (item: any): FileNode => { @@ -43,6 +44,8 @@ const convertTreeToFileNode = (data: any[]): FileNode => { export const TemplatesPage = () => { const navigate = useNavigate(); + const { user } = useAuthStore(); + const canManageAgent = user?.permission_manage_agent; const [files, setFiles] = useState(null); const [loading, setLoading] = useState(true); const [runModal, setRunModal] = useState<{ @@ -127,33 +130,35 @@ export const TemplatesPage = () => { Add Interpreter - {/* Open in Editor button */} - + {/* Open in Editor button — только с правом manage_agent */} + {canManageAgent && ( + + )} {/* File Picker (terminal встроен внутрь) */}