dify/web/app/components/main-nav/routes.ts
Xiyuan Chen b56ac4af4d
feat: gate agent management behind the agent.manage permission (#39330)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-07-23 07:34:07 +00:00

185 lines
5.8 KiB
TypeScript

import { buildIntegrationPath } from '@/app/components/integrations/routes'
type MainNavRouteVisibility = (options: MainNavRouteVisibilityOptions) => boolean
const DATASET_COLLECTION_ROUTES = new Set(['create', 'create-from-pipeline', 'connect'])
const DATASET_DOCUMENT_CREATION_ROUTES = new Set(['create', 'create-from-pipeline'])
const DEPLOYMENT_COLLECTION_ROUTES = new Set(['create'])
export type MainNavRouteConfig = {
key: string
href: string
active: (pathname: string) => boolean
icon: string
activeIcon: string
visibility: MainNavRouteVisibility
feature?: 'agentV2' | 'marketplace'
} & ({ label: string; labelKey?: never } | { label?: never; labelKey: string })
export type MainNavRouteVisibilityOptions = {
agentV2Enabled: boolean
canManageAgents: boolean
canUseAppDeploy: boolean
isCurrentWorkspaceDatasetOperator: boolean
marketplaceEnabled: boolean
}
export type DetailSidebarVisibilityOptions = Pick<
MainNavRouteVisibilityOptions,
'agentV2Enabled' | 'canUseAppDeploy' | 'isCurrentWorkspaceDatasetOperator'
>
const VISIBLE_TO_ALL: MainNavRouteVisibility = () => true
const CAN_MANAGE_AGENTS: MainNavRouteVisibility = (options) => options.canManageAgents
const CAN_USE_APP_DEPLOY: MainNavRouteVisibility = (options) => options.canUseAppDeploy
function isPathUnderRoute(pathname: string, route: string) {
return pathname === route || pathname.startsWith(`${route}/`)
}
export const MAIN_NAV_ROUTES = [
{
key: 'home',
href: '/',
labelKey: 'mainNav.home',
active: (path: string) => path === '/' || path === '/explore/apps',
icon: 'i-custom-vender-main-nav-home',
activeIcon: 'i-custom-vender-main-nav-home-active',
visibility: VISIBLE_TO_ALL,
},
{
key: 'apps',
href: '/apps',
labelKey: 'menus.apps',
active: (path: string) =>
isPathUnderRoute(path, '/apps') ||
isPathUnderRoute(path, '/app') ||
isPathUnderRoute(path, '/snippets'),
icon: 'i-custom-vender-main-nav-studio',
activeIcon: 'i-custom-vender-main-nav-studio-active',
visibility: VISIBLE_TO_ALL,
},
{
key: 'roster',
href: '/agents',
label: 'Agents',
active: (path: string) => isPathUnderRoute(path, '/agents'),
icon: 'i-custom-vender-main-nav-roster',
activeIcon: 'i-custom-vender-main-nav-roster-active',
visibility: CAN_MANAGE_AGENTS,
feature: 'agentV2',
},
{
key: 'datasets',
href: '/datasets',
labelKey: 'menus.datasets',
active: (path: string) => isPathUnderRoute(path, '/datasets'),
icon: 'i-custom-vender-main-nav-knowledge',
activeIcon: 'i-custom-vender-main-nav-knowledge-active',
visibility: VISIBLE_TO_ALL,
},
{
key: 'integrations',
href: buildIntegrationPath('provider'),
labelKey: 'mainNav.integrations',
active: (path: string) =>
isPathUnderRoute(path, '/integrations') || isPathUnderRoute(path, '/tools'),
icon: 'i-custom-vender-main-nav-integrations',
activeIcon: 'i-custom-vender-main-nav-integrations-active',
visibility: VISIBLE_TO_ALL,
},
{
key: 'marketplace',
href: '/marketplace',
labelKey: 'mainNav.marketplace',
active: (path: string) =>
isPathUnderRoute(path, '/marketplace') || isPathUnderRoute(path, '/plugins'),
icon: 'i-custom-vender-main-nav-marketplace',
activeIcon: 'i-custom-vender-main-nav-marketplace-active',
visibility: VISIBLE_TO_ALL,
feature: 'marketplace',
},
{
key: 'deployments',
href: '/deployments',
labelKey: 'menus.deployments',
active: (path: string) => isPathUnderRoute(path, '/deployments'),
icon: 'i-ri-rocket-line',
activeIcon: 'i-ri-rocket-fill',
visibility: CAN_USE_APP_DEPLOY,
},
] as const satisfies readonly MainNavRouteConfig[]
export function isMainNavRouteVisible(
route: MainNavRouteConfig,
options: MainNavRouteVisibilityOptions,
) {
if (route.feature === 'agentV2' && !options.agentV2Enabled) return false
if (route.feature === 'marketplace' && !options.marketplaceEnabled) return false
return route.visibility(options)
}
function isAppDetailPathname(pathname: string) {
return pathname.startsWith('/app/')
}
function isDatasetDetailPathname(pathname: string) {
const [section, datasetId, subSection, action] = pathname.split('/').filter(Boolean)
if (section !== 'datasets' || !datasetId) return false
if (DATASET_COLLECTION_ROUTES.has(datasetId)) return false
if (datasetId === 'new' && subSection === 'create') return false
if (subSection === 'documents' && action && DATASET_DOCUMENT_CREATION_ROUTES.has(action))
return false
return true
}
export function shouldHideMainNavigation(pathname: string) {
const [section, namespace, knowledgeSpaceId] = pathname.split('/').filter(Boolean)
return (
section === 'datasets' &&
namespace === 'new' &&
!!knowledgeSpaceId &&
knowledgeSpaceId !== 'create'
)
}
function isAgentDetailPathname(pathname: string) {
const [section, agentId] = pathname.split('/').filter(Boolean)
return section === 'agents' && !!agentId
}
function isDeploymentDetailPathname(pathname: string) {
const [section, appInstanceId] = pathname.split('/').filter(Boolean)
return (
section === 'deployments' && !!appInstanceId && !DEPLOYMENT_COLLECTION_ROUTES.has(appInstanceId)
)
}
function isSnippetDetailPathname(pathname: string) {
const [section, snippetId] = pathname.split('/').filter(Boolean)
return section === 'snippets' && !!snippetId
}
export function shouldUseDetailSidebar(pathname: string, options: DetailSidebarVisibilityOptions) {
if (isDatasetDetailPathname(pathname) || isSnippetDetailPathname(pathname)) return true
if (options.isCurrentWorkspaceDatasetOperator) return false
if (isAppDetailPathname(pathname)) return true
if (options.agentV2Enabled && isAgentDetailPathname(pathname)) return true
return options.canUseAppDeploy && isDeploymentDetailPathname(pathname)
}