fix 2
ci-front / build (push) Successful in 2m21s

This commit is contained in:
nikita
2026-04-05 10:34:33 +03:00
parent 255fe2eaf3
commit 7d2f3d0f3a
4 changed files with 234 additions and 113 deletions
@@ -42,14 +42,31 @@ export const Navigation: React.FC<NavigationProps> = ({
const currentTheme = getCurrentTheme(); const currentTheme = getCurrentTheme();
const navItems = [ const navItems = [
{ path: "/templates", label: "Шаблоны", icon: FaCode }, { path: "/templates", label: "Шаблоны", icon: FaCode, requireView: true },
{ path: "/add-agents", label: "Деплой", icon: FaRocket }, {
{ path: "/registration", label: "Регистрация", icon: FaKey }, path: "/add-agents",
{ path: "/logs", label: "Логи", icon: FaFileAlt }, 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; 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(() => { useEffect(() => {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if ( if (
@@ -77,42 +94,37 @@ export const Navigation: React.FC<NavigationProps> = ({
const renderNavItems = (showLabels: boolean, iconSize: number) => ( const renderNavItems = (showLabels: boolean, iconSize: number) => (
<div className="flex items-center gap-1 whitespace-nowrap"> <div className="flex items-center gap-1 whitespace-nowrap">
{navItems {filteredNavItems.map((item) => {
.filter((item) => { const Icon = item.icon;
if ((item as any).adminOnly && !user?.permission_admin) return false; const active = isActive(item.path);
return true; return (
}) <button
.map((item) => { key={item.path}
const Icon = item.icon; onClick={() => navigate(item.path)}
const active = isActive(item.path); className="flex items-center gap-1.5 px-3 py-2 rounded-lg font-medium transition-all flex-shrink-0"
return ( style={{
<button backgroundColor: active ? "var(--accent)" : "transparent",
key={item.path} color: active ? "var(--accent-text)" : "var(--text-secondary)",
onClick={() => navigate(item.path)} }}
className="flex items-center gap-1.5 px-3 py-2 rounded-lg font-medium transition-all flex-shrink-0" onMouseEnter={(e) => {
style={{ if (!active) {
backgroundColor: active ? "var(--accent)" : "transparent", e.currentTarget.style.backgroundColor = "var(--bg-secondary)";
color: active ? "var(--accent-text)" : "var(--text-secondary)", e.currentTarget.style.color = "var(--text-primary)";
}} }
onMouseEnter={(e) => { }}
if (!active) { onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = "var(--bg-secondary)"; if (!active) {
e.currentTarget.style.color = "var(--text-primary)"; e.currentTarget.style.backgroundColor = "transparent";
} e.currentTarget.style.color = "var(--text-secondary)";
}} }
onMouseLeave={(e) => { }}
if (!active) { title={item.label}
e.currentTarget.style.backgroundColor = "transparent"; >
e.currentTarget.style.color = "var(--text-secondary)"; <Icon size={iconSize} />
} {showLabels && <span className="text-xs">{item.label}</span>}
}} </button>
title={item.label} );
> })}
<Icon size={iconSize} />
{showLabels && <span className="text-xs">{item.label}</span>}
</button>
);
})}
</div> </div>
); );
@@ -375,14 +387,31 @@ export const BottomNav: React.FC = () => {
const { user } = useAuthStore(); const { user } = useAuthStore();
const navItems = [ const navItems = [
{ path: "/templates", label: "Шаблоны", icon: FaCode }, { path: "/templates", label: "Шаблоны", icon: FaCode, requireView: true },
{ path: "/add-agents", label: "Деплой", icon: FaRocket }, {
{ path: "/registration", label: "Регистрация", icon: FaKey }, path: "/add-agents",
{ path: "/logs", label: "Логи", icon: FaFileAlt }, 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; 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 ( return (
<div <div
className="flex-shrink-0 border-t" className="flex-shrink-0 border-t"
@@ -392,32 +421,24 @@ export const BottomNav: React.FC = () => {
}} }}
> >
<div className="flex items-center justify-around px-2 py-2"> <div className="flex items-center justify-around px-2 py-2">
{navItems {filteredNavItems.map((item) => {
.filter((item) => { const Icon = item.icon;
if ((item as any).adminOnly && !user?.permission_admin) const active = isActive(item.path);
return false; return (
return true; <button
}) key={item.path}
.map((item) => { onClick={() => navigate(item.path)}
const Icon = item.icon; className="flex items-center justify-center p-3 rounded-lg transition-all"
const active = isActive(item.path); style={{
return ( backgroundColor: active ? "var(--accent)" : "transparent",
<button color: active ? "var(--accent-text)" : "var(--text-secondary)",
key={item.path} }}
onClick={() => navigate(item.path)} title={item.label}
className="flex items-center justify-center p-3 rounded-lg transition-all" >
style={{ <Icon size={20} />
backgroundColor: active ? "var(--accent)" : "transparent", </button>
color: active );
? "var(--accent-text)" })}
: "var(--text-secondary)",
}}
title={item.label}
>
<Icon size={20} />
</button>
);
})}
</div> </div>
</div> </div>
); );
@@ -1,12 +1,42 @@
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => { interface ProtectedRouteProps {
const { isAuthenticated } = useAuthStore(); children: React.ReactNode;
requireView?: boolean;
requireManageAgent?: boolean;
requireAdmin?: boolean;
fallbackPath?: string;
}
// if (!isAuthenticated) { export const ProtectedRoute: React.FC<ProtectedRouteProps> = ({
// return <Navigate to="/auth" replace />; 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 <Navigate to="/auth" replace />;
}
if (requireView && !user.permission_view) {
return <Navigate to={fallbackPath} replace />;
}
if (requireManageAgent && !user.permission_manage_agent) {
return <Navigate to={fallbackPath} replace />;
}
if (requireAdmin && !user.permission_admin) {
return <Navigate to={fallbackPath} replace />;
}
return <>{children}</>; return <>{children}</>;
}; };
+75 -10
View File
@@ -15,6 +15,7 @@ import { LogsPage } from "@/pages/logs.page";
import { GraphsPage } from "@/pages/graphs.page"; import { GraphsPage } from "@/pages/graphs.page";
import { DashboardPage } from "@/pages/dashboard.page"; import { DashboardPage } from "@/pages/dashboard.page";
import { AgentDashboardPage } from "@/pages/agent-dashboard.page"; import { AgentDashboardPage } from "@/pages/agent-dashboard.page";
import { ProtectedRoute } from "./helper/protected.route";
export const mockGraphData: GraphData = { export const mockGraphData: GraphData = {
nodes: [ nodes: [
@@ -122,18 +123,82 @@ export const Routing = () => {
<Route path="/register" element={<RegisterPage />} /> <Route path="/register" element={<RegisterPage />} />
<Route element={<DefaultLayout />}> <Route element={<DefaultLayout />}>
<Route path="/" element={<TemplatesPage />} /> {/* Routes requiring 'view' permission */}
<Route path="/add-agents" element={<AddAgentsPage />} /> <Route
<Route path="/registration" element={<RegistrationTokenPage />} /> path="/"
<Route path="/logs" element={<LogsPage />} /> element={
<Route path="/admin" element={<AdminPage />} /> <ProtectedRoute requireView>
<Route path="/IDE" element={<IDEPage />} /> <TemplatesPage />
<Route path="/templates" element={<TemplatesPage />} /> </ProtectedRoute>
<Route path="/graphs" element={<GraphsPage />} /> }
{/* <Route path="/dashboard" element={<DashboardPage />} /> */} />
<Route
path="/logs"
element={
<ProtectedRoute requireView>
<LogsPage />
</ProtectedRoute>
}
/>
<Route
path="/graphs"
element={
<ProtectedRoute requireView>
<GraphsPage />
</ProtectedRoute>
}
/>
<Route <Route
path="/dashboard/:agentLabel" path="/dashboard/:agentLabel"
element={<AgentDashboardPage />} element={
<ProtectedRoute requireView>
<AgentDashboardPage />
</ProtectedRoute>
}
/>
{/* Routes requiring 'manage_agent' permission */}
<Route
path="/add-agents"
element={
<ProtectedRoute requireManageAgent>
<AddAgentsPage />
</ProtectedRoute>
}
/>
<Route
path="/registration"
element={
<ProtectedRoute requireManageAgent>
<RegistrationTokenPage />
</ProtectedRoute>
}
/>
<Route
path="/templates"
element={
<ProtectedRoute requireView>
<TemplatesPage />
</ProtectedRoute>
}
/>
<Route
path="/IDE"
element={
<ProtectedRoute requireView>
<IDEPage />
</ProtectedRoute>
}
/>
{/* Admin route requiring 'admin' permission */}
<Route
path="/admin"
element={
<ProtectedRoute requireAdmin>
<AdminPage />
</ProtectedRoute>
}
/> />
</Route> </Route>
+32 -27
View File
@@ -8,6 +8,7 @@ import { RunScriptModal } from "../modules/ide/components/RunScriptModal";
import { AddInterpreterModal } from "../modules/ide/components/AddInterpreterModal"; import { AddInterpreterModal } from "../modules/ide/components/AddInterpreterModal";
import type { FileNode } from "../modules/ide"; import type { FileNode } from "../modules/ide";
import { scriptsApi } from "../modules/ide/api/scripts.api"; import { scriptsApi } from "../modules/ide/api/scripts.api";
import { useAuthStore } from "@/modules/auth/store/useAuthStore";
const convertTreeToFileNode = (data: any[]): FileNode => { const convertTreeToFileNode = (data: any[]): FileNode => {
const convertItem = (item: any): FileNode => { const convertItem = (item: any): FileNode => {
@@ -43,6 +44,8 @@ const convertTreeToFileNode = (data: any[]): FileNode => {
export const TemplatesPage = () => { export const TemplatesPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useAuthStore();
const canManageAgent = user?.permission_manage_agent;
const [files, setFiles] = useState<FileNode | null>(null); const [files, setFiles] = useState<FileNode | null>(null);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [runModal, setRunModal] = useState<{ const [runModal, setRunModal] = useState<{
@@ -127,33 +130,35 @@ export const TemplatesPage = () => {
Add Interpreter Add Interpreter
</button> </button>
{/* Open in Editor button */} {/* Open in Editor button — только с правом manage_agent */}
<button {canManageAgent && (
onClick={() => navigate("/ide")} <button
style={{ onClick={() => navigate("/ide")}
display: "flex", style={{
alignItems: "center", display: "flex",
gap: "8px", alignItems: "center",
padding: "6px 16px", gap: "8px",
backgroundColor: "#0e639c", padding: "6px 16px",
border: "none", backgroundColor: "#0e639c",
borderRadius: "4px", border: "none",
color: "#ffffff", borderRadius: "4px",
cursor: "pointer", color: "#ffffff",
fontSize: "12px", cursor: "pointer",
fontWeight: 500, fontSize: "12px",
transition: "all 0.15s", fontWeight: 500,
}} transition: "all 0.15s",
onMouseEnter={(e) => { }}
e.currentTarget.style.backgroundColor = "#1177bb"; onMouseEnter={(e) => {
}} e.currentTarget.style.backgroundColor = "#1177bb";
onMouseLeave={(e) => { }}
e.currentTarget.style.backgroundColor = "#0e639c"; onMouseLeave={(e) => {
}} e.currentTarget.style.backgroundColor = "#0e639c";
> }}
<FiEdit3 size={14} /> >
Open Editor <FiEdit3 size={14} />
</button> Open Editor
</button>
)}
</div> </div>
{/* File Picker (terminal встроен внутрь) */} {/* File Picker (terminal встроен внутрь) */}