Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ui/app/_fallbacks/enterprise/lib/contexts/rbacContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 10 additions & 4 deletions ui/app/workspace/config/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
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";
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 <NoPermissionView entity="configuration" />;
}
Comment thread
impoiler marked this conversation as resolved.

Expand Down
6 changes: 3 additions & 3 deletions ui/app/workspace/providers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -240,15 +240,15 @@ export default function Providers() {
})}
</div>
)}
<div className="pb-4">
{hasProviderCreateAccess ? <div className="pb-4">
<AddProviderDropdown
disabled={!hasProviderCreateAccess}
existingInSidebar={existingInSidebarNames}
knownProviders={knownProviders}
onSelectKnownProvider={handleSelectKnownProvider}
onAddCustomProvider={() => setShowCustomProviderSheet(true)}
/>
</div>
</div> : null}
Comment thread
impoiler marked this conversation as resolved.
</div>
</div>
</TooltipProvider>
Expand Down
64 changes: 33 additions & 31 deletions ui/app/workspace/providers/views/modelProviderKeysTableView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export default function ModelProviderKeysTableView({ provider, className, header
<div className="flex items-center gap-2">Configured {entityLabelPlural}</div>
<div className="flex items-center gap-2">
{headerActions}
{!isKeyless && (
{!isKeyless && hasUpdateProviderAccess ? (
<Button
disabled={!hasUpdateProviderAccess}
data-testid="add-key-btn"
Comment thread
impoiler marked this conversation as resolved.
Expand All @@ -117,7 +117,7 @@ export default function ModelProviderKeysTableView({ provider, className, header
<PlusIcon className="h-4 w-4" />
Add new {entityLabel}
</Button>
)}
) : null}
</div>
</CardTitle>
</CardHeader>
Expand Down Expand Up @@ -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={() => { }}
>
<TableCell>
<div className="flex items-center space-x-2">
Expand Down Expand Up @@ -258,34 +258,36 @@ export default function ModelProviderKeysTableView({ provider, className, header
</TableCell>
<TableCell className="text-right">
<div className="flex items-center justify-end space-x-2">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button onClick={(e) => e.stopPropagation()} variant="ghost">
<EllipsisIcon className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
setShowAddNewKeyDialog({ show: true, keyId: key.id });
}}
disabled={!hasUpdateProviderAccess}
>
<PencilIcon className="mr-1 h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
onClick={() => {
setShowDeleteKeyDialog({ show: true, keyId: key.id });
}}
disabled={!hasDeleteProviderAccess}
>
<TrashIcon className="mr-1 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{hasUpdateProviderAccess || hasDeleteProviderAccess ?
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button onClick={(e) => e.stopPropagation()} variant="ghost">
<EllipsisIcon className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
setShowAddNewKeyDialog({ show: true, keyId: key.id });
}}
disabled={!hasUpdateProviderAccess}
>
<PencilIcon className="mr-1 h-4 w-4" />
Edit
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
onClick={() => {
setShowDeleteKeyDialog({ show: true, keyId: key.id });
}}
disabled={!hasDeleteProviderAccess}
>
<TrashIcon className="mr-1 h-4 w-4" />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu> : null
}
</div>
</TableCell>
</TableRow>
Expand Down
29 changes: 21 additions & 8 deletions ui/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@ import {
LogOut,
Logs,
Network,
PanelLeft,
PanelLeftClose,
PanelLeftOpen,
PanelRight,
Plug,
Puzzle,
ScrollText,
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand Down
Loading