From 641ad723f122d32d97235d515b0d74d79add8f66 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Thu, 7 May 2026 16:00:02 +0530 Subject: [PATCH 1/6] fix: RBAc checks the sidebar and hides the resource if it is not permitted --- .../enterprise/lib/contexts/rbacContext.tsx | 3 ++ ui/components/sidebar.tsx | 29 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx index 02a1c11ff2..5f5147bc77 100644 --- a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx +++ b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx @@ -26,6 +26,9 @@ export enum RbacResource { PromptRepository = "PromptRepository", PromptDeploymentStrategy = "PromptDeploymentStrategy", AccessProfiles = "AccessProfiles", + APIKeys = "APIKeys", + Inference = "Inference", + Metrics = "Metrics", } // RBAC Operation Names (must match backend definitions) diff --git a/ui/components/sidebar.tsx b/ui/components/sidebar.tsx index cf0bbad8f8..508cca0a9d 100644 --- a/ui/components/sidebar.tsx +++ b/ui/components/sidebar.tsx @@ -19,10 +19,8 @@ import { LogOut, Logs, Network, - PanelLeft, PanelLeftClose, PanelLeftOpen, - PanelRight, Plug, Puzzle, ScrollText, @@ -67,7 +65,6 @@ import { useGetVersionQuery, useLogoutMutation, } from "@/lib/store"; -import { cn } from "@/lib/utils"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import type { UserInfo } from "@enterprise/lib/store/utils/tokenManager"; import { getUserInfo } from "@enterprise/lib/store/utils/tokenManager"; @@ -583,6 +580,7 @@ export default function AppSidebar() { const hasClusterConfigAccess = useRbac(RbacResource.Cluster, RbacOperation.View); const isAdaptiveRoutingAllowed = useRbac(RbacResource.AdaptiveRouter, RbacOperation.View); const hasSettingsAccess = useRbac(RbacResource.Settings, RbacOperation.View); + const hasAPIKeyAccess = useRbac(RbacResource.APIKeys, RbacOperation.View); const hasPromptRepositoryAccess = useRbac(RbacResource.PromptRepository, RbacOperation.View); const hasAccessProfilesAccess = useRbac(RbacResource.AccessProfiles, RbacOperation.View); const hasAnyGovernanceAccess = @@ -625,7 +623,7 @@ export default function AppSidebar() { url: "/workspace/mcp-logs", icon: MCPIcon, description: "MCP tool execution logs", - hasAccess: hasLogsAccess, + hasAccess: hasMCPGatewayAccess, }, { title: "Connectors", @@ -910,7 +908,7 @@ export default function AppSidebar() { url: "/workspace/config/api-keys", icon: KeyRound, description: "API keys management", - hasAccess: hasSettingsAccess, + hasAccess: hasAPIKeyAccess, }, { title: "Performance Tuning", @@ -950,11 +948,26 @@ export default function AppSidebar() { ], ); + const accessibleItems: SidebarItem[] = useMemo(() => { + return items + .map((item) => { + const hadSubItems = !!item.subItems?.length; + if (hadSubItems) { + const visibleSubItems = item.subItems!.filter((sub) => sub.hasAccess !== false); + if (visibleSubItems.length === 0) return null; + return { ...item, subItems: visibleSubItems, hasAccess: true }; + } + if (item.hasAccess === false) return null; + return item; + }) + .filter(Boolean) as SidebarItem[]; + }, [items]); + const filteredItems: SidebarItem[] = useMemo(() => { const query = searchQuery.trim().toLowerCase(); - if (!query) return items; + if (!query) return accessibleItems; - return items + return accessibleItems .map((item) => { const parentMatches = item.title.toLowerCase().includes(query); if (parentMatches) return item; @@ -970,7 +983,7 @@ export default function AppSidebar() { return null; }) .filter(Boolean) as SidebarItem[]; - }, [items, searchQuery]); + }, [accessibleItems, searchQuery]); const { data: version } = useGetVersionQuery(); const { resolvedTheme } = useTheme(); From 9585ac653ac224f9f549f2d35ef396daef0233ac Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Thu, 7 May 2026 16:20:41 +0530 Subject: [PATCH 2/6] fix: RBAc checks for model provider page --- ui/app/workspace/config/layout.tsx | 14 ++-- ui/app/workspace/providers/page.tsx | 6 +- .../views/modelProviderKeysTableView.tsx | 64 ++++++++++--------- 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/ui/app/workspace/config/layout.tsx b/ui/app/workspace/config/layout.tsx index 04e68b03aa..93a2c2e291 100644 --- a/ui/app/workspace/config/layout.tsx +++ b/ui/app/workspace/config/layout.tsx @@ -1,4 +1,4 @@ -import { createFileRoute, Outlet, useChildMatches } from "@tanstack/react-router"; +import { createFileRoute, Outlet, useChildMatches, useLocation } from "@tanstack/react-router"; import FullPageLoader from "@/components/fullPageLoader"; import { NoPermissionView } from "@/components/noPermissionView"; import { useGetCoreConfigQuery } from "@/lib/store"; @@ -6,11 +6,17 @@ import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import ConfigPage from "./page"; function RouteComponent() { - const hasConfigAccess = useRbac(RbacResource.Settings, RbacOperation.View); - const { isLoading } = useGetCoreConfigQuery({ fromDB: true }, { skip: !hasConfigAccess }); + const pathname = useLocation({ select: (l) => l.pathname }); + const hasSettingsAccess = useRbac(RbacResource.Settings, RbacOperation.View); + const hasAPIKeysAccess = useRbac(RbacResource.APIKeys, RbacOperation.View); const childMatches = useChildMatches(); - if (!hasConfigAccess) { + const isAPIKeysRoute = pathname.startsWith("/workspace/config/api-keys"); + const requiredAccess = isAPIKeysRoute ? hasAPIKeysAccess : hasSettingsAccess; + + const { isLoading } = useGetCoreConfigQuery({ fromDB: true }, { skip: !requiredAccess }); + + if (!requiredAccess) { return ; } diff --git a/ui/app/workspace/providers/page.tsx b/ui/app/workspace/providers/page.tsx index cb2a2dc936..1dd6f6dad6 100644 --- a/ui/app/workspace/providers/page.tsx +++ b/ui/app/workspace/providers/page.tsx @@ -17,8 +17,8 @@ import { import { KnownProvider, ModelProviderName, ProviderStatus } from "@/lib/types/config"; import { cn } from "@/lib/utils"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; -import { AlertCircle } from "lucide-react"; import { useNavigate } from "@tanstack/react-router"; +import { AlertCircle } from "lucide-react"; import { useQueryState } from "nuqs"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; @@ -240,7 +240,7 @@ export default function Providers() { })} )} -
+ {hasProviderCreateAccess ?
setShowCustomProviderSheet(true)} /> -
+
: null} diff --git a/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx b/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx index 4cc49f5d91..ff8588eb85 100644 --- a/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx +++ b/ui/app/workspace/providers/views/modelProviderKeysTableView.tsx @@ -106,7 +106,7 @@ export default function ModelProviderKeysTableView({ provider, className, header
Configured {entityLabelPlural}
{headerActions} - {!isKeyless && ( + {!isKeyless && hasUpdateProviderAccess ? ( - )} + ) : null}
@@ -152,7 +152,7 @@ export default function ModelProviderKeysTableView({ provider, className, header key={key.id} data-testid={`key-row-${key.name}`} className="text-sm transition-colors hover:bg-white" - onClick={() => {}} + onClick={() => { }} >
@@ -258,34 +258,36 @@ export default function ModelProviderKeysTableView({ provider, className, header
- - - - - - { - setShowAddNewKeyDialog({ show: true, keyId: key.id }); - }} - disabled={!hasUpdateProviderAccess} - > - - Edit - - { - setShowDeleteKeyDialog({ show: true, keyId: key.id }); - }} - disabled={!hasDeleteProviderAccess} - > - - Delete - - - + {hasUpdateProviderAccess || hasDeleteProviderAccess ? + + + + + + { + setShowAddNewKeyDialog({ show: true, keyId: key.id }); + }} + disabled={!hasUpdateProviderAccess} + > + + Edit + + { + setShowDeleteKeyDialog({ show: true, keyId: key.id }); + }} + disabled={!hasDeleteProviderAccess} + > + + Delete + + + : null + }
From 630af54e74d3fff1a16223083ba09194cd339f33 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 8 May 2026 11:30:27 +0530 Subject: [PATCH 3/6] fix: logs and mcp logs RBAc checks --- ui/app/workspace/logs/views/columns.tsx | 45 +++++++++++--------- ui/app/workspace/mcp-logs/page.tsx | 4 +- ui/app/workspace/mcp-logs/views/columns.tsx | 47 +++++++++++---------- 3 files changed, 51 insertions(+), 45 deletions(-) diff --git a/ui/app/workspace/logs/views/columns.tsx b/ui/app/workspace/logs/views/columns.tsx index 4ad3650ac2..824733fd98 100644 --- a/ui/app/workspace/logs/views/columns.tsx +++ b/ui/app/workspace/logs/views/columns.tsx @@ -472,26 +472,29 @@ export const createColumns = ( }, })); - const actionsColumn: ColumnDef = { - id: "actions", - size: 72, - cell: ({ row }) => { - const log = row.original; - return ( - - ); - }, - }; + const actionsColumn: ColumnDef[] = hasDeleteAccess + ? [ + { + id: "actions", + size: 72, + cell: ({ row }) => { + const log = row.original; + return ( + + ); + }, + }, + ] + : []; - return [...baseColumns, ...metadataColumns, actionsColumn]; + return [...baseColumns, ...metadataColumns, ...actionsColumn]; }; diff --git a/ui/app/workspace/mcp-logs/page.tsx b/ui/app/workspace/mcp-logs/page.tsx index 98a00098c6..757423b225 100644 --- a/ui/app/workspace/mcp-logs/page.tsx +++ b/ui/app/workspace/mcp-logs/page.tsx @@ -24,7 +24,7 @@ export default function MCPLogsPage() { const [error, setError] = useState(null); const [showEmptyState, setShowEmptyState] = useState(false); const hasCheckedEmptyState = useRef(false); - const hasDeleteAccess = useRbac(RbacResource.Logs, RbacOperation.Delete); + const hasDeleteAccess = useRbac(RbacResource.MCPGateway, RbacOperation.Delete); const [deleteLogs] = useDeleteMCPLogsMutation(); // Lazy query kept only for handleLogNavigate (fetches adjacent pages on demand) @@ -448,4 +448,4 @@ export default function MCPLogsPage() { )}
); -} \ No newline at end of file +} diff --git a/ui/app/workspace/mcp-logs/views/columns.tsx b/ui/app/workspace/mcp-logs/views/columns.tsx index afd2a17dfb..c1f1a63146 100644 --- a/ui/app/workspace/mcp-logs/views/columns.tsx +++ b/ui/app/workspace/mcp-logs/views/columns.tsx @@ -2,7 +2,7 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Status, StatusBarColors, Statuses } from "@/lib/constants/logs"; import type { MCPToolLogEntry } from "@/lib/types/logs"; -import { ColumnDef } from "@tanstack/react-table"; +import { ColumnDef, Row } from "@tanstack/react-table"; import { format, isValid } from "date-fns"; import { ArrowUpDown, Trash2 } from "lucide-react"; @@ -95,24 +95,27 @@ export const createMCPColumns = ( return
{isValidNumber ? `${cost.toFixed(4)}` : "N/A"}
; }, }, - { - id: "actions", - size: 72, - cell: ({ row }) => { - const log = row.original; - return ( - - ); - }, - }, - ]; \ No newline at end of file + ...(hasDeleteAccess + ? [ + { + id: "actions", + size: 72, + cell: ({ row }: { row: Row }) => { + const log = row.original; + return ( + + ); + }, + }, + ] + : []), + ]; From 7b8dbfc7649ef100b5bb87091200f907a19a1866 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 8 May 2026 11:44:59 +0530 Subject: [PATCH 4/6] feat: added MCP logs as a dedicated RBAC resource --- .../enterprise/lib/contexts/rbacContext.tsx | 1 + ui/app/workspace/mcp-logs/layout.tsx | 12 +++++++++++- ui/app/workspace/mcp-logs/page.tsx | 2 +- ui/components/sidebar.tsx | 16 +++++++++------- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx index 5f5147bc77..4f89483ecb 100644 --- a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx +++ b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx @@ -15,6 +15,7 @@ export enum RbacResource { ModelProvider = "ModelProvider", Plugins = "Plugins", MCPGateway = "MCPGateway", + MCPLogs = "MCPLogs", AdaptiveRouter = "AdaptiveRouter", AuditLogs = "AuditLogs", Customers = "Customers", diff --git a/ui/app/workspace/mcp-logs/layout.tsx b/ui/app/workspace/mcp-logs/layout.tsx index 7f36337b7f..1cf679ac60 100644 --- a/ui/app/workspace/mcp-logs/layout.tsx +++ b/ui/app/workspace/mcp-logs/layout.tsx @@ -1,6 +1,16 @@ +import { NoPermissionView } from "@/components/noPermissionView"; +import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { createFileRoute } from "@tanstack/react-router"; import MCPLogsPage from "./page"; +function RouteComponent() { + const hasViewMCPLogsAccess = useRbac(RbacResource.MCPLogs, RbacOperation.View); + if (!hasViewMCPLogsAccess) { + return ; + } + return ; +} + export const Route = createFileRoute("/workspace/mcp-logs")({ - component: MCPLogsPage, + component: RouteComponent, }); \ No newline at end of file diff --git a/ui/app/workspace/mcp-logs/page.tsx b/ui/app/workspace/mcp-logs/page.tsx index 757423b225..360119c011 100644 --- a/ui/app/workspace/mcp-logs/page.tsx +++ b/ui/app/workspace/mcp-logs/page.tsx @@ -24,7 +24,7 @@ export default function MCPLogsPage() { const [error, setError] = useState(null); const [showEmptyState, setShowEmptyState] = useState(false); const hasCheckedEmptyState = useRef(false); - const hasDeleteAccess = useRbac(RbacResource.MCPGateway, RbacOperation.Delete); + const hasDeleteAccess = useRbac(RbacResource.MCPLogs, RbacOperation.Delete); const [deleteLogs] = useDeleteMCPLogsMutation(); // Lazy query kept only for handleLogNavigate (fetches adjacent pages on demand) diff --git a/ui/components/sidebar.tsx b/ui/components/sidebar.tsx index 508cca0a9d..ceec43945c 100644 --- a/ui/components/sidebar.tsx +++ b/ui/components/sidebar.tsx @@ -561,6 +561,7 @@ export default function AppSidebar() { const hasObservabilityAccess = useRbac(RbacResource.Observability, RbacOperation.View); const hasModelProvidersAccess = useRbac(RbacResource.ModelProvider, RbacOperation.View); const hasMCPGatewayAccess = useRbac(RbacResource.MCPGateway, RbacOperation.View); + const hasMCPLogsAccess = useRbac(RbacResource.MCPLogs, RbacOperation.View); const hasPluginsAccess = useRbac(RbacResource.Plugins, RbacOperation.View); const hasUsersAccess = useRbac(RbacResource.Users, RbacOperation.View); const hasUserProvisioningAccess = useRbac(RbacResource.UserProvisioning, RbacOperation.View); @@ -623,7 +624,7 @@ export default function AppSidebar() { url: "/workspace/mcp-logs", icon: MCPIcon, description: "MCP tool execution logs", - hasAccess: hasMCPGatewayAccess, + hasAccess: hasMCPLogsAccess, }, { title: "Connectors", @@ -921,11 +922,12 @@ export default function AppSidebar() { }, ], [ - hasLogsAccess, - hasObservabilityAccess, - hasModelProvidersAccess, - hasMCPGatewayAccess, - hasPluginsAccess, + hasLogsAccess, + hasObservabilityAccess, + hasModelProvidersAccess, + hasMCPGatewayAccess, + hasMCPLogsAccess, + hasPluginsAccess, hasUsersAccess, hasUserProvisioningAccess, hasAuditLogsAccess, @@ -1460,4 +1462,4 @@ export default function AppSidebar() { ); -} \ No newline at end of file +} From e062cb47a937b47ae08db89a57af54d23505e0aa Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 8 May 2026 12:09:51 +0530 Subject: [PATCH 5/6] feat: implemented the MCP group resource RBAC --- ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx | 3 ++- ui/app/workspace/mcp-tool-groups/layout.tsx | 4 ++-- ui/components/sidebar.tsx | 6 ++++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx index 4f89483ecb..8d89e25cb4 100644 --- a/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx +++ b/ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx @@ -15,6 +15,7 @@ export enum RbacResource { ModelProvider = "ModelProvider", Plugins = "Plugins", MCPGateway = "MCPGateway", + MCPToolGroups = "MCPToolGroups", MCPLogs = "MCPLogs", AdaptiveRouter = "AdaptiveRouter", AuditLogs = "AuditLogs", @@ -85,4 +86,4 @@ export function useRbacContext() { }; } return context; -} \ No newline at end of file +} diff --git a/ui/app/workspace/mcp-tool-groups/layout.tsx b/ui/app/workspace/mcp-tool-groups/layout.tsx index 5664583316..a5954962b3 100644 --- a/ui/app/workspace/mcp-tool-groups/layout.tsx +++ b/ui/app/workspace/mcp-tool-groups/layout.tsx @@ -4,8 +4,8 @@ import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import MCPToolGroupsPage from "./page"; function RouteComponent() { - const hasMCPGatewayAccess = useRbac(RbacResource.MCPGateway, RbacOperation.View); - if (!hasMCPGatewayAccess) { + const hasMCPToolGroupsAccess = useRbac(RbacResource.MCPToolGroups, RbacOperation.View); + if (!hasMCPToolGroupsAccess) { return ; } return ; diff --git a/ui/components/sidebar.tsx b/ui/components/sidebar.tsx index ceec43945c..c6042e044f 100644 --- a/ui/components/sidebar.tsx +++ b/ui/components/sidebar.tsx @@ -561,6 +561,7 @@ export default function AppSidebar() { const hasObservabilityAccess = useRbac(RbacResource.Observability, RbacOperation.View); const hasModelProvidersAccess = useRbac(RbacResource.ModelProvider, RbacOperation.View); const hasMCPGatewayAccess = useRbac(RbacResource.MCPGateway, RbacOperation.View); + const hasMCPToolGroupsAccess = useRbac(RbacResource.MCPToolGroups, RbacOperation.View); const hasMCPLogsAccess = useRbac(RbacResource.MCPLogs, RbacOperation.View); const hasPluginsAccess = useRbac(RbacResource.Plugins, RbacOperation.View); const hasUsersAccess = useRbac(RbacResource.Users, RbacOperation.View); @@ -698,7 +699,7 @@ export default function AppSidebar() { icon: MCPIcon, description: "MCP configuration", url: "/workspace/mcp-gateway", - hasAccess: hasMCPGatewayAccess, + hasAccess: hasMCPGatewayAccess || hasMCPToolGroupsAccess, subItems: [ { title: "MCP Catalog", @@ -712,7 +713,7 @@ export default function AppSidebar() { url: "/workspace/mcp-tool-groups", icon: ToolCase, description: "Tool Groups", - hasAccess: hasMCPGatewayAccess, + hasAccess: hasMCPToolGroupsAccess, }, { title: "MCP Settings", @@ -926,6 +927,7 @@ export default function AppSidebar() { hasObservabilityAccess, hasModelProvidersAccess, hasMCPGatewayAccess, + hasMCPToolGroupsAccess, hasMCPLogsAccess, hasPluginsAccess, hasUsersAccess, From 0bb104f6cb7adad5600b506548874f8e7320c6c8 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 8 May 2026 16:31:06 +0530 Subject: [PATCH 6/6] fix: remove force limit for teams api --- framework/configstore/rdb.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/framework/configstore/rdb.go b/framework/configstore/rdb.go index aa5e4f1c50..e79eb838fe 100644 --- a/framework/configstore/rdb.go +++ b/framework/configstore/rdb.go @@ -2830,8 +2830,6 @@ func (s *RDBConfigStore) GetTeamsPaginated(ctx context.Context, params TeamsQuer offset := params.Offset if limit <= 0 { limit = 25 - } else if limit > 100 { - limit = 100 } if offset < 0 { offset = 0