From 902b16134fe14da8ab1dd3782042a1d411a4d26c Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 17 Dec 2025 15:59:06 +0800 Subject: [PATCH] misc: added permission guards in the UI --- .../ProjectPermissionContext/index.tsx | 1 + frontend/src/context/index.tsx | 1 + .../MCPEndpointDetailPage.tsx | 39 +++++++++++--- .../MCPEndpointConnectedServersSection.tsx | 27 +++++++--- .../components/MCPEndpointDetailsSection.tsx | 24 ++++++--- .../MCPEndpointToolSelectionSection.tsx | 18 ++++++- .../MCPEndpointsTab/MCPEndpointRow.tsx | 52 +++++++++++++------ .../MCPEndpointsTab/MCPEndpointsTab.tsx | 24 ++++++--- .../components/MCPServersTab/MCPServerRow.tsx | 52 +++++++++++++------ .../MCPServersTab/MCPServersTab.tsx | 24 ++++++--- .../MCPServerDetailPage.tsx | 35 ++++++++++--- .../components/MCPServerDetailsSection.tsx | 23 +++++--- 12 files changed, 234 insertions(+), 86 deletions(-) diff --git a/frontend/src/context/ProjectPermissionContext/index.tsx b/frontend/src/context/ProjectPermissionContext/index.tsx index 9eb548c071..08d3600f6b 100644 --- a/frontend/src/context/ProjectPermissionContext/index.tsx +++ b/frontend/src/context/ProjectPermissionContext/index.tsx @@ -16,5 +16,6 @@ export { ProjectPermissionPkiSyncActions, ProjectPermissionPkiTemplateActions, ProjectPermissionSshHostActions, + ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "./types"; diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index 81feaf8e58..17c846ab97 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -27,6 +27,7 @@ export { ProjectPermissionPkiSyncActions, ProjectPermissionPkiTemplateActions, ProjectPermissionSshHostActions, + ProjectPermissionMcpEndpointActions, ProjectPermissionSub, useProjectPermission } from "./ProjectPermissionContext"; diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx index 3988ac7250..af5244d915 100644 --- a/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx +++ b/frontend/src/pages/ai/MCPEndpointDetailPage/MCPEndpointDetailPage.tsx @@ -10,6 +10,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, ContentLoader, @@ -20,6 +21,7 @@ import { DropdownMenuTrigger, EmptyState } from "@app/components/v2"; +import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context"; import { useDeleteAiMcpEndpoint, useGetAiMcpEndpointById } from "@app/hooks/api"; import { EditMCPEndpointModal } from "../MCPPage/components/MCPEndpointsTab/EditMCPEndpointModal"; @@ -98,7 +100,7 @@ const PageContent = () => { - - setIsEditModalOpen(true)}> - Edit Endpoint - - setIsDeleteModalOpen(true)} className="text-red-500"> - Delete Endpoint - + + + {(isAllowed) => ( + setIsEditModalOpen(true)} + isDisabled={!isAllowed} + > + Edit Endpoint + + )} + + + {(isAllowed) => ( + setIsDeleteModalOpen(true)} + className="text-red-500" + isDisabled={!isAllowed} + > + Delete Endpoint + + )} + diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx index cb2470baae..d3367bc8c4 100644 --- a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx +++ b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointConnectedServersSection.tsx @@ -3,7 +3,9 @@ import { faCheck, faPencil, faServer, faTimes } from "@fortawesome/free-solid-sv import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Checkbox, IconButton, Spinner } from "@app/components/v2"; +import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context"; import { useListAiMcpServers, useUpdateAiMcpEndpoint } from "@app/hooks/api"; type Props = { @@ -96,14 +98,25 @@ export const MCPEndpointConnectedServersSection = ({ endpointId, projectId, serv ) : ( - - - + {(isAllowed) => ( + + + + )} + )}
diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx index 8f9f6ebfed..e66fd033ed 100644 --- a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx +++ b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointDetailsSection.tsx @@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { GenericFieldLabel, IconButton, @@ -13,6 +14,7 @@ import { Switch, Tooltip } from "@app/components/v2"; +import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context"; import { TAiMcpEndpointWithServerIds, useUpdateAiMcpEndpoint } from "@app/hooks/api"; type Props = { @@ -122,14 +124,22 @@ export const MCPEndpointDetailsSection = ({ endpoint, onEdit }: Props) => {

Details

- - - + {(isAllowed) => ( + + + + )} +
{endpoint.name} diff --git a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx index 6344688f04..b52bfc2a8b 100644 --- a/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx +++ b/frontend/src/pages/ai/MCPEndpointDetailPage/components/MCPEndpointToolSelectionSection.tsx @@ -10,6 +10,11 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { Input, Switch, Tooltip } from "@app/components/v2"; +import { + ProjectPermissionMcpEndpointActions, + ProjectPermissionSub, + useProjectPermission +} from "@app/context"; import { TAiMcpEndpointToolConfig, useDisableEndpointTool, @@ -33,6 +38,7 @@ type ServerToolsSectionProps = { toolConfigs: TAiMcpEndpointToolConfig[]; onToolToggle: (serverToolId: string, isEnabled: boolean) => void; isUpdating: boolean; + canEdit: boolean; }; const ServerToolsSection = ({ @@ -42,7 +48,8 @@ const ServerToolsSection = ({ searchQuery, toolConfigs, onToolToggle, - isUpdating + isUpdating, + canEdit }: ServerToolsSectionProps) => { const [isExpanded, setIsExpanded] = useState(false); @@ -126,7 +133,7 @@ const ServerToolsSection = ({ id={`tool-${tool.id}`} isChecked={isToolEnabled(tool.id)} onCheckedChange={(checked) => onToolToggle(tool.id, checked)} - isDisabled={isUpdating} + isDisabled={isUpdating || !canEdit} />
))} @@ -146,6 +153,12 @@ const ServerToolsSection = ({ export const MCPEndpointToolSelectionSection = ({ endpointId, projectId, serverIds }: Props) => { const [searchQuery, setSearchQuery] = useState(""); + const { permission } = useProjectPermission(); + const canEdit = permission.can( + ProjectPermissionMcpEndpointActions.Edit, + ProjectPermissionSub.McpEndpoints + ); + const { data: serversData } = useListAiMcpServers({ projectId }); const { data: toolConfigs = [] } = useListEndpointTools({ endpointId }); const enableTool = useEnableEndpointTool(); @@ -212,6 +225,7 @@ export const MCPEndpointToolSelectionSection = ({ endpointId, projectId, serverI toolConfigs={toolConfigs} onToolToggle={handleToolToggle} isUpdating={enableTool.isPending || disableTool.isPending} + canEdit={canEdit} /> )) )} diff --git a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx index ef7ca54534..f40e774514 100644 --- a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx +++ b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointRow.tsx @@ -4,6 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { DropdownMenu, DropdownMenuContent, @@ -13,6 +14,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context"; import { useToggle } from "@app/hooks"; import { TAiMcpEndpoint } from "@app/hooks/api"; @@ -113,25 +115,41 @@ export const MCPEndpointRow = ({ endpoint, onEditEndpoint, onDeleteEndpoint }: P > Copy Endpoint ID - { - e.stopPropagation(); - onEditEndpoint(endpoint); - }} - icon={} + - Edit Endpoint - - { - e.stopPropagation(); - onDeleteEndpoint(endpoint); - }} - icon={} - className="text-red-500 hover:text-red-400" + {(isAllowed) => ( + { + e.stopPropagation(); + onEditEndpoint(endpoint); + }} + icon={} + isDisabled={!isAllowed} + > + Edit Endpoint + + )} + + - Delete Endpoint - + {(isAllowed) => ( + { + e.stopPropagation(); + onDeleteEndpoint(endpoint); + }} + icon={} + className="text-red-500 hover:text-red-400" + isDisabled={!isAllowed} + > + Delete Endpoint + + )} + diff --git a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx index 8bd86fb0bb..abcfeeb7a6 100644 --- a/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx +++ b/frontend/src/pages/ai/MCPPage/components/MCPEndpointsTab/MCPEndpointsTab.tsx @@ -3,7 +3,9 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; +import { ProjectPermissionMcpEndpointActions, ProjectPermissionSub } from "@app/context"; import { TAiMcpEndpoint, useDeleteAiMcpEndpoint } from "@app/hooks/api"; import { AddMCPEndpointModal } from "./AddMCPEndpointModal"; @@ -63,14 +65,22 @@ export const MCPEndpointsTab = () => {

- + {(isAllowed) => ( + + )} +
> Copy Server ID - { - e.stopPropagation(); - onEditServer(server); - }} - icon={} + - Edit Server - - { - e.stopPropagation(); - onDeleteServer(server); - }} - icon={} - className="text-red-500 hover:text-red-400" + {(isAllowed) => ( + { + e.stopPropagation(); + onEditServer(server); + }} + icon={} + isDisabled={!isAllowed} + > + Edit Server + + )} + + - Delete Server - + {(isAllowed) => ( + { + e.stopPropagation(); + onDeleteServer(server); + }} + icon={} + className="text-red-500 hover:text-red-400" + isDisabled={!isAllowed} + > + Delete Server + + )} +
diff --git a/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx b/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx index 35c50d987a..3692b17a61 100644 --- a/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx +++ b/frontend/src/pages/ai/MCPPage/components/MCPServersTab/MCPServersTab.tsx @@ -3,7 +3,9 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, DeleteActionModal } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { TAiMcpServer, useDeleteAiMcpServer } from "@app/hooks/api"; import { AddMCPServerModal } from "./AddMCPServerModal"; @@ -63,14 +65,22 @@ export const MCPServersTab = () => {

- + {(isAllowed) => ( + + )} + diff --git a/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx b/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx index 1055a3af73..fb5f6fefd5 100644 --- a/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx +++ b/frontend/src/pages/ai/MCPServerDetailPage/MCPServerDetailPage.tsx @@ -5,6 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useNavigate, useParams } from "@tanstack/react-router"; import { createNotification } from "@app/components/notifications"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { Button, ContentLoader, @@ -15,6 +16,7 @@ import { DropdownMenuTrigger, EmptyState } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { useDeleteAiMcpServer, useGetAiMcpServerById } from "@app/hooks/api"; import { EditMCPServerModal } from "../MCPPage/components/MCPServersTab/EditMCPServerModal"; @@ -118,12 +120,33 @@ const PageContent = () => { - setIsEditModalOpen(true)}> - Edit Server - - setIsDeleteModalOpen(true)} className="text-red-500"> - Delete Server - + + {(isAllowed) => ( + setIsEditModalOpen(true)} + isDisabled={!isAllowed} + > + Edit Server + + )} + + + {(isAllowed) => ( + setIsDeleteModalOpen(true)} + className="text-red-500" + isDisabled={!isAllowed} + > + Delete Server + + )} + diff --git a/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx b/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx index 4c2e8aefa3..89c588713a 100644 --- a/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx +++ b/frontend/src/pages/ai/MCPServerDetailPage/components/MCPServerDetailsSection.tsx @@ -2,7 +2,9 @@ import { faEdit } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format } from "date-fns"; +import { ProjectPermissionCan } from "@app/components/permissions"; import { GenericFieldLabel, IconButton } from "@app/components/v2"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { AiMcpServerStatus, TAiMcpServer } from "@app/hooks/api"; type Props = { @@ -33,14 +35,19 @@ export const MCPServerDetailsSection = ({ server, onEdit }: Props) => {

Details

- - - + + {(isAllowed) => ( + + + + )} +
{server.name}