feat(web): create system-features vertical (#36894)

This commit is contained in:
yyh 2026-06-01 18:15:25 +08:00 committed by GitHub
parent fc7716704d
commit 8fc2807194
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
100 changed files with 260 additions and 290 deletions

View File

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { pluginInstallLimit } from '@/app/components/plugins/install-plugin/hooks/use-install-plugin-limit'
import { InstallationScope } from '@/types/feature'
import { InstallationScope } from '@/features/system-features/constants'
describe('Plugin Marketplace to Install Flow', () => {
describe('install permission validation pipeline', () => {

View File

@ -1,10 +1,10 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import type { RenderHookOptions, RenderHookResult, RenderOptions, RenderResult } from '@testing-library/react'
import type { ReactElement, ReactNode } from 'react'
import type { SystemFeatures } from '@/types/feature'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { render, renderHook } from '@testing-library/react'
import { defaultSystemFeatures } from '@/features/system-features/config'
import { consoleQuery } from '@/service/client'
import { defaultSystemFeatures } from '@/types/feature'
type QueryKeyProvider = {
queryKey: () => readonly unknown[]
@ -40,9 +40,9 @@ type DeepPartial<T> = T extends Array<infer U>
: T
const buildSystemFeatures = (
overrides: DeepPartial<SystemFeatures> = {},
): SystemFeatures => {
const o = overrides as Partial<SystemFeatures>
overrides: DeepPartial<GetSystemFeaturesResponse> = {},
): GetSystemFeaturesResponse => {
const o = overrides as Partial<GetSystemFeaturesResponse>
return {
...defaultSystemFeatures,
...o,
@ -65,6 +65,14 @@ const buildSystemFeatures = (
license: {
...defaultSystemFeatures.license,
...(o.license ?? {}),
workspaces: {
...defaultSystemFeatures.license.workspaces,
...(o.license?.workspaces ?? {}),
},
},
plugin_manager: {
...defaultSystemFeatures.plugin_manager,
...(o.plugin_manager ?? {}),
},
}
}
@ -90,10 +98,10 @@ export const createTestQueryClient = (): QueryClient =>
export const seedSystemFeatures = (
queryClient: QueryClient,
overrides: DeepPartial<SystemFeatures> = {},
): SystemFeatures => {
overrides: DeepPartial<GetSystemFeaturesResponse> = {},
): GetSystemFeaturesResponse => {
const data = buildSystemFeatures(overrides)
queryClient.setQueryData(consoleQuery.systemFeatures.queryKey(), data)
queryClient.setQueryData(consoleQuery.systemFeatures.get.queryKey(), data)
return data
}
@ -118,7 +126,7 @@ type SystemFeaturesTestOptions = {
* `useSuspenseQuery` resolve immediately. Pass `null` to skip seeding and
* keep the systemFeatures query in the pending state.
*/
systemFeatures?: DeepPartial<SystemFeatures> | null
systemFeatures?: DeepPartial<GetSystemFeaturesResponse> | null
trialModels?: readonly string[] | null
/**
* Seed the workflow clipboard DSL version query only for tests that need it.
@ -130,7 +138,7 @@ type SystemFeaturesTestOptions = {
type SystemFeaturesWrapper = {
queryClient: QueryClient
systemFeatures: SystemFeatures | null
systemFeatures: GetSystemFeaturesResponse | null
wrapper: (props: { children: ReactNode }) => ReactElement
}
@ -154,7 +162,7 @@ export const createSystemFeaturesWrapper = (
export const renderWithSystemFeatures = (
ui: ReactElement,
options: SystemFeaturesTestOptions & Omit<RenderOptions, 'wrapper'> = {},
): RenderResult & { queryClient: QueryClient, systemFeatures: SystemFeatures | null } => {
): RenderResult & { queryClient: QueryClient, systemFeatures: GetSystemFeaturesResponse | null } => {
const { systemFeatures: sf, trialModels, appDslVersion, queryClient: qc, ...renderOptions } = options
const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({
systemFeatures: sf,
@ -169,7 +177,7 @@ export const renderWithSystemFeatures = (
export const renderHookWithSystemFeatures = <Result, Props = void>(
callback: (props: Props) => Result,
options: SystemFeaturesTestOptions & Omit<RenderHookOptions<Props>, 'wrapper'> = {},
): RenderHookResult<Result, Props> & { queryClient: QueryClient, systemFeatures: SystemFeatures | null } => {
): RenderHookResult<Result, Props> & { queryClient: QueryClient, systemFeatures: GetSystemFeaturesResponse | null } => {
const { systemFeatures: sf, trialModels, appDslVersion, queryClient: qc, ...hookOptions } = options
const { wrapper, queryClient, systemFeatures } = createSystemFeaturesWrapper({
systemFeatures: sf,

View File

@ -51,7 +51,7 @@ vi.mock('@/service/server', () => ({
},
}))
vi.mock('@/service/server-system-features', () => ({
vi.mock('@/features/system-features/server', () => ({
serverSystemFeaturesQueryOptions: () => ({
queryKey: ['console', 'system-features'],
queryFn: mocks.systemFeaturesQueryFn,

View File

@ -2,10 +2,10 @@ import type { ReactNode } from 'react'
import { dehydrate, HydrationBoundary } from '@tanstack/react-query'
import { getQueryClientServer } from '@/context/query-client-server'
import { serverUserProfileQueryOptions } from '@/features/account-profile/server'
import { serverSystemFeaturesQueryOptions } from '@/features/system-features/server'
import { headers } from '@/next/headers'
import { redirect } from '@/next/navigation'
import { getServerConsoleClientContext, resolveServerConsoleApiUrl, serverConsoleQuery } from '@/service/server'
import { serverSystemFeaturesQueryOptions } from '@/service/server-system-features'
import { basePath } from '@/utils/var'
const CURRENT_PATHNAME_HEADER = 'x-dify-pathname'

View File

@ -3,7 +3,7 @@ import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import Header from '@/app/signin/_header'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
export default function SignInLayout({ children }: any) {
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())

View File

@ -5,10 +5,10 @@ import * as React from 'react'
import { useCallback, useEffect } from 'react'
import AppUnavailable from '@/app/components/base/app-unavailable'
import Loading from '@/app/components/base/loading'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { SSOProtocol } from '@/features/system-features/constants'
import { useRouter, useSearchParams } from '@/next/navigation'
import { fetchWebOAuth2SSOUrl, fetchWebOIDCSSOUrl, fetchWebSAMLSSOUrl } from '@/service/share'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { SSOProtocol } from '@/types/feature'
const ExternalMemberSSOAuth = () => {
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())

View File

@ -5,12 +5,12 @@ import { toast } from '@langgenius/dify-ui/toast'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import { SSOProtocol } from '@/features/system-features/constants'
import { useRouter, useSearchParams } from '@/next/navigation'
import { fetchMembersOAuth2SSOUrl, fetchMembersOIDCSSOUrl, fetchMembersSAMLSSOUrl } from '@/service/share'
import { SSOProtocol } from '@/types/feature'
type SSOAuthProps = {
protocol: SSOProtocol | ''
protocol: string
}
const SSOAuth: FC<SSOAuthProps> = ({

View File

@ -4,8 +4,8 @@ import type { PropsWithChildren } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { systemFeaturesQueryOptions } from '@/service/system-features'
export default function SignInLayout({ children }: PropsWithChildren) {
const { t } = useTranslation()

View File

@ -7,9 +7,9 @@ import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
import { IS_CE_EDITION } from '@/config'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { LicenseStatus } from '@/features/system-features/constants'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { LicenseStatus } from '@/types/feature'
import MailAndCodeAuth from './components/mail-and-code-auth'
import MailAndPasswordAuth from './components/mail-and-password-auth'
import SSOAuth from './components/sso-auth'

View File

@ -6,9 +6,9 @@ import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import AppUnavailable from '@/app/components/base/app-unavailable'
import { useWebAppStore } from '@/context/web-app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode } from '@/models/access-control'
import { useRouter, useSearchParams } from '@/next/navigation'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { webAppLogout } from '@/service/webapp-auth'
import ExternalMemberSsoAuth from './components/external-member-sso-auth'
import NormalForm from './normalForm'

View File

@ -17,9 +17,9 @@ import Collapse from '@/app/components/header/account-setting/collapse'
import { IS_CE_EDITION, validPassword } from '@/config'
import { useProviderContext } from '@/context/provider-context'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { consoleQuery } from '@/service/client'
import { updateUserProfile } from '@/service/common'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import DeleteAccount from '../delete-account'
import AvatarWithEdit from './AvatarWithEdit'

View File

@ -5,9 +5,9 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { useRouter } from '@/next/navigation'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import Avatar from './avatar'
const Header = () => {

View File

@ -6,8 +6,8 @@ import Loading from '@/app/components/base/loading'
import Header from '@/app/signin/_header'
import { AppContextProvider } from '@/context/app-context-provider'
import { isLegacyBase401, userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { systemFeaturesQueryOptions } from '@/service/system-features'
export default function SignInLayout({ children }: any) {
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())

View File

@ -2,7 +2,7 @@
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Header from '../signin/_header'
import ActivateForm from './activateForm'

View File

@ -8,9 +8,9 @@ import { RiBuildingLine, RiGlobalLine, RiVerifiedBadgeLine } from '@remixicon/re
import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode, SubjectType } from '@/models/access-control'
import { useUpdateAccessMode } from '@/service/access-control'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import useAccessControlStore from '../../../../context/access-control-store'
import AccessControlDialog from './access-control-dialog'
import AccessControlItem from './access-control-item'

View File

@ -35,13 +35,13 @@ import { collaborationManager } from '@/app/components/workflow/collaboration/co
import { webSocketClient } from '@/app/components/workflow/collaboration/core/websocket-manager'
import { WorkflowContext } from '@/app/components/workflow/context'
import { appDefaultIconBackground } from '@/config'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { AccessMode } from '@/models/access-control'
import { useAppWhiteListSubjects, useGetUserCanAccessApp } from '@/service/access-control'
import { fetchAppDetailDirect, publishToCreatorsPlatform } from '@/service/apps'
import { fetchInstalledAppList } from '@/service/explore'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useInvalidateAppWorkflow } from '@/service/use-workflow'
import { fetchPublishedWorkflow } from '@/service/workflow'
import { AppModeEnum } from '@/types/app'

View File

@ -15,11 +15,11 @@ import { useStore as useAppStore } from '@/app/components/app/store'
import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button'
import { useAppContext } from '@/context/app-context'
import { useDocLink } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode } from '@/models/access-control'
import { usePathname, useRouter } from '@/next/navigation'
import { useAppWhiteListSubjects } from '@/service/access-control'
import { fetchAppDetailDirect } from '@/service/apps'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useAppWorkflow } from '@/service/use-workflow'
import { AppModeEnum } from '@/types/app'
import { asyncRunSafe } from '@/utils'

View File

@ -22,7 +22,9 @@ vi.mock('@/next/navigation', () => ({
vi.mock('@/service/client', () => ({
consoleClient: {
systemFeatures: vi.fn(),
systemFeatures: {
get: vi.fn(),
},
},
consoleQuery: {
apps: {
@ -36,7 +38,9 @@ vi.mock('@/service/client', () => ({
},
},
systemFeatures: {
queryKey: () => ['console', 'systemFeatures'],
get: {
queryKey: () => ['console', 'systemFeatures', 'get'],
},
},
},
}))

View File

@ -39,6 +39,7 @@ import { UserAvatarList } from '@/app/components/base/user-avatar-list'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AppCardTags } from '@/features/tag-management/components/app-card-tags'
import { useAsyncWindowOpen } from '@/hooks/use-async-window-open'
import { AccessMode } from '@/models/access-control'
@ -47,7 +48,6 @@ import { useRouter } from '@/next/navigation'
import { useGetUserCanAccessApp } from '@/service/access-control'
import { copyApp, exportAppConfig, updateAppInfo } from '@/service/apps'
import { fetchInstalledAppList } from '@/service/explore'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useDeleteAppMutation } from '@/service/use-apps'
import { fetchWorkflowDraft } from '@/service/workflow'
import { AppModeEnum } from '@/types/app'

View File

@ -12,12 +12,12 @@ import Input from '@/app/components/base/input'
import TabSliderNew from '@/app/components/base/tab-slider-new'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { TagFilter } from '@/features/tag-management/components/tag-filter'
import { CheckModal } from '@/hooks/use-pay'
import dynamic from '@/next/dynamic'
import { usePathname, useRouter, useSearchParams } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { AppModeEnum } from '@/types/app'
import AppCard from './app-card'
import { AppCardSkeleton } from './app-card-skeleton'

View File

@ -22,7 +22,7 @@ import List from '@/app/components/base/chat/chat-with-history/sidebar/list'
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import MenuDropdown from '@/app/components/share/text-generation/menu-dropdown'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useChatWithHistoryContext } from '../context'
type Props = {

View File

@ -10,7 +10,7 @@ import ActionButton from '@/app/components/base/action-button'
import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
import Divider from '@/app/components/base/divider'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { isClient } from '@/utils/client'
import {
useEmbeddedChatbotContext,

View File

@ -11,10 +11,10 @@ import Header from '@/app/components/base/chat/embedded-chatbot/header'
import Loading from '@/app/components/base/loading'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import useDocumentTitle from '@/hooks/use-document-title'
import { AppSourceType } from '@/service/share'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import {
EmbeddedChatbotContext,
useEmbeddedChatbotContext,

View File

@ -1,6 +1,6 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import type { ChangeEvent } from 'react'
import type { AppContextValue } from '@/context/app-context'
import type { SystemFeatures } from '@/types/feature'
import { act } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createMockProviderContextValue } from '@/__mocks__/provider-context'
@ -18,7 +18,7 @@ import { useProviderContext } from '@/context/provider-context'
import { updateCurrentWorkspace } from '@/service/common'
import useWebAppBrand from '../use-web-app-brand'
let currentBrandingOverrides: Partial<SystemFeatures['branding']> = {}
let currentBrandingOverrides: Partial<GetSystemFeaturesResponse['branding']> = {}
const renderHook = <Result, Props = void>(callback: (props: Props) => Result) =>
renderHookWithSystemFeatures(callback, {
systemFeatures: {

View File

@ -7,8 +7,8 @@ import { getImageUploadErrorMessage, imageUpload } from '@/app/components/base/i
import { Plan } from '@/app/components/billing/type'
import { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { updateCurrentWorkspace } from '@/service/common'
import { systemFeaturesQueryOptions } from '@/service/system-features'
const MAX_LOGO_FILE_SIZE = 5 * 1024 * 1024
const CUSTOM_CONFIG_URL = '/workspaces/custom-config'

View File

@ -1,8 +1,8 @@
import { useSuspenseQuery } from '@tanstack/react-query'
import { useMemo } from 'react'
import { useLocale } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { LanguagesSupported } from '@/i18n-config/language'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { usePipelineTemplateList } from '@/service/use-pipeline'
import CreateCard from './create-card'
import TemplateCard from './template-card'

View File

@ -11,11 +11,11 @@ import Input from '@/app/components/base/input'
import CheckboxWithLabel from '@/app/components/datasets/create/website/base/checkbox-with-label'
import { useAppContext, useSelector as useAppContextSelector } from '@/context/app-context'
import { useExternalApiPanel } from '@/context/external-api-panel-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { TagFilter } from '@/features/tag-management/components/tag-filter'
import { TagManagementModal } from '@/features/tag-management/components/tag-management-modal'
import useDocumentTitle from '@/hooks/use-document-title'
import { useDatasetApiBaseUrl, useInvalidDatasetList } from '@/service/knowledge/use-dataset'
import { systemFeaturesQueryOptions } from '@/service/system-features'
// Components
import ExternalAPIPanel from '../external-api/external-api-panel'
import ServiceApi from '../extra-info/service-api'

View File

@ -20,12 +20,12 @@ import Banner from '@/app/components/explore/banner/banner'
import Category from '@/app/components/explore/category'
import CreateAppModal from '@/app/components/explore/create-app-modal'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useImportDSL } from '@/hooks/use-import-dsl'
import {
DSLImportMode,
} from '@/models/app'
import { fetchAppDetail } from '@/service/explore'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useMembers } from '@/service/use-common'
import { useExploreAppList } from '@/service/use-explore'
import { trackCreateApp } from '@/utils/create-app-tracking'

View File

@ -10,7 +10,7 @@ import { useState } from 'react'
import AppUnavailable from '@/app/components/base/app-unavailable'
import Loading from '@/app/components/base/loading'
import { IS_CLOUD_EDITION } from '@/config'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useGetTryAppInfo } from '@/service/use-try-app'
import App from './app'
import AppInfo from './app-info'

View File

@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import { IS_CE_EDITION } from '@/config'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
type IAccountSettingProps = {
langGeniusVersionInfo: LangGeniusVersionResponse

View File

@ -1,7 +1,7 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import type { AppContextValue } from '@/context/app-context'
import type { ModalContextState } from '@/context/modal-context'
import type { ProviderContextState } from '@/context/provider-context'
import type { SystemFeatures } from '@/types/feature'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
import { Plan } from '@/app/components/billing/type'
@ -144,7 +144,7 @@ describe('AccountDropdown', () => {
const renderWithRouter = (
ui: React.ReactElement,
options: { systemFeatures?: DeepPartial<SystemFeatures> } = {},
options: { systemFeatures?: DeepPartial<GetSystemFeaturesResponse> } = {},
) => {
return renderWithSystemFeatures(ui, {
systemFeatures: options.systemFeatures ?? { branding: { enabled: false } },

View File

@ -17,9 +17,9 @@ import { useDocLink } from '@/context/i18n'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { env } from '@/env'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { useRouter } from '@/next/navigation'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useLogout } from '@/service/use-common'
import AccountAbout from '../account-about'
import GithubStar from '../github-star'

View File

@ -1,6 +1,6 @@
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo } from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useGetDataSourceListAuth } from '@/service/use-datasource'
import Card from './card'
import InstallFromMarketplace from './install-from-marketplace'

View File

@ -11,9 +11,9 @@ import UpgradeBtn from '@/app/components/billing/upgrade-btn'
import { useAppContext } from '@/context/app-context'
import { useLocale } from '@/context/i18n'
import { useProviderContext } from '@/context/provider-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
import { LanguagesSupported } from '@/i18n-config/language'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useMembers } from '@/service/use-common'
import EditWorkspaceModal from './edit-workspace-modal'
import InviteButton from './invite-button'

View File

@ -4,7 +4,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useWorkspacePermissions } from '@/service/use-workspace'
type InviteButtonProps = {

View File

@ -13,7 +13,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import Loading from '@/app/components/base/loading'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useWorkspacePermissions } from '@/service/use-workspace'
type Props = {

View File

@ -10,8 +10,8 @@ import { useTranslation } from 'react-i18next'
import { usePluginsWithLatestVersion } from '@/app/components/plugins/hooks'
import { IS_CLOUD_EDITION } from '@/config'
import { useProviderContext } from '@/context/provider-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { consoleQuery } from '@/service/client'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import {
CustomConfigurationStatusEnum,
ModelTypeEnum,

View File

@ -13,9 +13,9 @@ import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/
import { IS_CLOUD_EDITION } from '@/config'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useSearchParams } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useInstallPackageFromMarketPlace } from '@/service/use-plugins'
import { CustomConfigurationStatusEnum, ModelFeatureEnum, ModelStatusEnum, ModelTypeEnum } from '../declarations'
import { useLanguage, useMarketplaceAllPlugins } from '../hooks'

View File

@ -8,9 +8,9 @@ import { useAppContext } from '@/context/app-context'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { WorkspaceProvider } from '@/context/workspace-context-provider'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { Plan } from '../billing/type'
import AccountDropdown from './account-dropdown'
import AppNav from './app-nav'

View File

@ -1,7 +1,7 @@
import { screen } from '@testing-library/react'
import dayjs from 'dayjs'
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
import { LicenseStatus } from '@/types/feature'
import { LicenseStatus } from '@/features/system-features/constants'
import LicenseNav from '../index'
describe('LicenseNav', () => {
@ -26,7 +26,7 @@ describe('LicenseNav', () => {
systemFeatures: {
license: {
status: LicenseStatus.ACTIVE,
expired_at: null,
expired_at: '',
},
},
})

View File

@ -4,8 +4,8 @@ import { RiHourglass2Fill } from '@remixicon/react'
import { useSuspenseQuery } from '@tanstack/react-query'
import dayjs from 'dayjs'
import { useTranslation } from 'react-i18next'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { LicenseStatus } from '@/types/feature'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { LicenseStatus } from '@/features/system-features/constants'
import PremiumBadge from '../../base/premium-badge'
const LicenseNav = () => {

View File

@ -1,6 +1,7 @@
import type { PluginInstallationScope } from '@dify/contracts/api/console/system-features/types.gen'
import { describe, expect, it } from 'vitest'
import { renderHookWithSystemFeatures as renderHook } from '@/__tests__/utils/mock-system-features'
import { InstallationScope } from '@/types/feature'
import { InstallationScope } from '@/features/system-features/constants'
import { pluginInstallLimit } from '../use-install-plugin-limit'
const basePlugin = {
@ -117,7 +118,7 @@ describe('pluginInstallLimit', () => {
const features = {
plugin_installation_permission: {
restrict_to_marketplace_only: false,
plugin_installation_scope: 'unknown-scope' as InstallationScope,
plugin_installation_scope: 'unknown-scope' as unknown as PluginInstallationScope,
},
}

View File

@ -1,12 +1,12 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import type { Plugin, PluginManifestInMarket } from '../../types'
import type { SystemFeatures } from '@/types/feature'
import { useSuspenseQuery } from '@tanstack/react-query'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { InstallationScope } from '@/types/feature'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { InstallationScope } from '@/features/system-features/constants'
type PluginProps = (Plugin | PluginManifestInMarket) & { from: 'github' | 'marketplace' | 'package' }
export function pluginInstallLimit(plugin: PluginProps, systemFeatures: SystemFeatures) {
export function pluginInstallLimit(plugin: PluginProps, systemFeatures: GetSystemFeaturesResponse) {
if (systemFeatures.plugin_installation_permission.restrict_to_marketplace_only) {
if (plugin.from === 'github' || plugin.from === 'package')
return { canInstall: false }

View File

@ -5,7 +5,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback, useEffect, useMemo, useState } from 'react'
import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
import { pluginInstallLimit } from '@/app/components/plugins/install-plugin/hooks/use-install-plugin-limit'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins'
type UseInstallMultiStateParams = {

View File

@ -4,7 +4,7 @@ import type { PluginDetail } from '../../../types'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useBoolean } from 'ahooks'
import { useCallback, useMemo, useState } from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useReferenceSetting from '../../../plugin-page/use-reference-setting'
import { AUTO_UPDATE_MODE } from '../../../reference-setting-modal/auto-update-setting/types'
import { PluginSource } from '../../../types'

View File

@ -12,7 +12,7 @@ import {
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { PluginSource } from '../types'
type Props = {

View File

@ -17,9 +17,9 @@ import { useTranslation } from 'react-i18next'
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
import { API_PREFIX } from '@/config'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useRenderI18nObject } from '@/hooks/use-i18n'
import useTheme from '@/hooks/use-theme'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { isEqualOrLaterThanVersion } from '@/utils/semver'
import { getMarketplaceUrl } from '@/utils/var'
import Badge from '../../base/badge'

View File

@ -10,7 +10,7 @@ import {
useRef,
useState,
} from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { PLUGIN_PAGE_TABS_MAP, usePluginPageTabs } from '../hooks'
import { PLUGIN_TYPE_SEARCH_MAP } from '../marketplace/constants'
import {

View File

@ -1,10 +1,10 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import type { ReactElement } from 'react'
import type { FilterState } from '../../filter-management'
import type { SystemFeatures } from '@/types/feature'
import { act, fireEvent, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features'
import { InstallationScope } from '@/types/feature'
import { InstallationScope } from '@/features/system-features/constants'
// ==================== Imports (after mocks) ====================
@ -32,7 +32,7 @@ const {
plugin_installation_scope: 'all' as const,
restrict_to_marketplace_only: false,
},
} as Partial<SystemFeatures>,
} as Partial<GetSystemFeaturesResponse>,
pluginList: { plugins: [] as Array<{ id: string }> } as { plugins: Array<{ id: string }> } | undefined,
}
return {
@ -105,7 +105,7 @@ const setMockFilters = (filters: Partial<FilterState>) => {
mockState.filters = { ...mockState.filters, ...filters }
}
const setMockSystemFeatures = (features: Partial<SystemFeatures>) => {
const setMockSystemFeatures = (features: Partial<GetSystemFeaturesResponse>) => {
mockState.systemFeatures = { ...mockState.systemFeatures, ...features }
}

View File

@ -12,7 +12,7 @@ import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndD
import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github'
import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package'
import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useInstalledPluginList } from '@/service/use-plugins'
import Line from '../../marketplace/empty/line'
import { usePluginPageContext } from '../context'

View File

@ -19,11 +19,11 @@ import TabSlider from '@/app/components/base/tab-slider'
import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal'
import { MARKETPLACE_API_PREFIX, SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config'
import { useDocLink } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { usePluginInstallation } from '@/hooks/use-query-params'
import Link from '@/next/link'
import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { sleep } from '@/utils'
import { PLUGIN_PAGE_TABS_MAP } from '../hooks'
import InstallFromLocalPackage from '../install-plugin/install-from-local-package'

View File

@ -18,7 +18,7 @@ import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndD
import InstallFromGitHub from '@/app/components/plugins/install-plugin/install-from-github'
import InstallFromLocalPackage from '@/app/components/plugins/install-plugin/install-from-local-package'
import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
type Props = {
onSwitchToMarketplaceTab: () => void

View File

@ -3,7 +3,7 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useInvalidateReferenceSettings, useMutationReferenceSettings, useReferenceSettings } from '@/service/use-plugins'
import { PermissionType } from '../types'

View File

@ -10,7 +10,7 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { PermissionType } from '@/app/components/plugins/types'
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import AutoUpdateSetting from './auto-update-setting'
import { defaultValue as autoUpdateDefaultValue } from './auto-update-setting/config'
import Label from './label'

View File

@ -3,9 +3,9 @@ import type { PromptConfig, SavedMessage } from '@/models/debug'
import type { SiteInfo } from '@/models/share'
import type { VisionSettings } from '@/types/app'
import { fireEvent, render, screen } from '@testing-library/react'
import { defaultSystemFeatures } from '@/features/system-features/config'
import { AccessMode } from '@/models/access-control'
import { Resolution, TransferMethod } from '@/types/app'
import { defaultSystemFeatures } from '@/types/feature'
import TextGenerationSidebar from '../text-generation-sidebar'
const runOncePropsSpy = vi.fn()

View File

@ -8,11 +8,11 @@ import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { getRawInputsFromUrlParams } from '@/app/components/base/chat/utils'
import { useWebAppStore } from '@/context/web-app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useAppFavicon } from '@/hooks/use-app-favicon'
import useDocumentTitle from '@/hooks/use-document-title'
import { changeLanguage } from '@/i18n-config/client'
import { AppSourceType, fetchSavedMessage as doFetchSavedMessage, removeMessage, saveMessage } from '@/service/share'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { Resolution, TransferMethod } from '@/types/app'
import { userInputsFormToPromptVariables } from '@/utils/model-config'

View File

@ -1,9 +1,9 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import type { FC, RefObject } from 'react'
import type { InputValueTypes, TextGenerationCustomConfig, TextGenerationRunControl } from './types'
import type { PromptConfig, SavedMessage, TextToSpeechConfig } from '@/models/debug'
import type { SiteInfo } from '@/models/share'
import type { VisionFile, VisionSettings } from '@/types/app'
import type { SystemFeatures } from '@/types/feature'
import { cn } from '@langgenius/dify-ui/cn'
import { useTranslation } from 'react-i18next'
import SavedItems from '@/app/components/app/text-generate/saved-items'
@ -38,7 +38,7 @@ type TextGenerationSidebarProps = {
runControl: TextGenerationRunControl | null
savedMessages: SavedMessage[]
siteInfo: SiteInfo
systemFeatures: SystemFeatures
systemFeatures: GetSystemFeaturesResponse
textToSpeechConfig: TextToSpeechConfig | null
visionConfig: VisionSettings
}

View File

@ -17,7 +17,7 @@ import CustomCreateCard from '@/app/components/tools/provider/custom-create-card
import ProviderDetail from '@/app/components/tools/provider/detail'
import WorkflowToolEmpty from '@/app/components/tools/provider/empty'
import ToolCardSkeletonGrid from '@/app/components/tools/provider/tool-card-skeleton'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useCheckInstalled, useInvalidateInstalledPluginList } from '@/service/use-plugins'
import { useAllToolProviders } from '@/service/use-tools'
import Marketplace from './marketplace'

View File

@ -10,8 +10,8 @@ import { useSerialAsyncCallback } from '@/app/components/workflow/hooks/use-seri
import { useNodesReadOnly } from '@/app/components/workflow/hooks/use-workflow'
import { useWorkflowStore } from '@/app/components/workflow/store'
import { API_PREFIX } from '@/config'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { postWithKeepalive } from '@/service/fetch'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { syncWorkflowDraft } from '@/service/workflow'
import { useWorkflowRefreshDraft } from '.'

View File

@ -19,8 +19,8 @@ import {
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useFeaturedTriggersRecommendations } from '@/service/use-plugins'
import { useAllTriggerPlugins, useInvalidateAllTriggerPlugins } from '@/service/use-triggers'
import { getMarketplaceUrl } from '@/utils/var'

View File

@ -21,8 +21,8 @@ import Divider from '@/app/components/base/divider'
import { SearchMenu } from '@/app/components/base/icons/src/vender/line/general'
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useGetLanguage } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { getMarketplaceUrl } from '@/utils/var'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { PluginCategoryEnum } from '../../plugins/types'

View File

@ -14,7 +14,7 @@ import {
} from 'react'
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { useGetLanguage } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useMarketplacePlugins } from '../../plugins/marketplace/hooks'
import { PluginCategoryEnum } from '../../plugins/types'
import { BlockEnum } from '../types'

View File

@ -10,7 +10,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/too
import { useSuspenseQuery } from '@tanstack/react-query'
import { memo, useEffect, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useFeaturedToolsRecommendations } from '@/service/use-plugins'
import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, useInvalidateAllBuiltInTools } from '@/service/use-tools'
import { basePath } from '@/utils/var'

View File

@ -20,7 +20,7 @@ import { useTranslation } from 'react-i18next'
import SearchBox from '@/app/components/plugins/marketplace/search-box'
import EditCustomToolModal from '@/app/components/tools/edit-custom-collection-modal'
import AllTools from '@/app/components/workflow/block-selector/all-tools'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import {
createCustomCollection,
} from '@/service/tools'

View File

@ -7,7 +7,7 @@ import type {
} from '../types/collaboration'
import { useSuspenseQuery } from '@tanstack/react-query'
import { useEffect, useRef, useState } from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { collaborationManager } from '../core/collaboration-manager'
import { CursorService } from '../services/cursor-service'

View File

@ -54,9 +54,11 @@ vi.mock('@/context/app-context', () => ({
vi.mock('@/service/client', () => ({
consoleClient: {
systemFeatures: () => ({
enable_collaboration_mode: globalFeatureState.enableCollaboration,
}),
systemFeatures: {
get: () => ({
enable_collaboration_mode: globalFeatureState.enableCollaboration,
}),
},
workflowComments: {
create: (...args: unknown[]) => mockCreateWorkflowComment(...args),
delete: (...args: unknown[]) => mockDeleteWorkflowComment(...args),
@ -73,7 +75,9 @@ vi.mock('@/service/client', () => ({
},
consoleQuery: {
systemFeatures: {
queryKey: () => ['console', 'systemFeatures'],
get: {
queryKey: () => ['console', 'systemFeatures', 'get'],
},
},
},
}))

View File

@ -4,9 +4,9 @@ import { useCallback, useEffect, useMemo, useRef } from 'react'
import { useReactFlow } from 'reactflow'
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
import { useAppContext } from '@/context/app-context'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useParams } from '@/next/navigation'
import { consoleClient } from '@/service/client'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useStore } from '../store'
import { ControlMode } from '../types'

View File

@ -1,6 +1,6 @@
import { useSuspenseQuery } from '@tanstack/react-query'
import { useCallback } from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { useStore, useWorkflowStore } from '../store'
import { ControlMode } from '../types'
import { useEdgesInteractionsWithoutSync } from './use-edges-interactions-without-sync'

View File

@ -19,7 +19,7 @@ vi.mock('@tanstack/react-query', () => ({
useSuspenseQuery: mocks.useSuspenseQuery,
}))
vi.mock('@/service/system-features', () => ({
vi.mock('@/features/system-features/client', () => ({
systemFeaturesQueryOptions: () => ({}),
}))

View File

@ -24,8 +24,8 @@ import { useMarketplacePlugins } from '@/app/components/plugins/marketplace/hook
import { PluginCategoryEnum } from '@/app/components/plugins/types'
import { CollectionType } from '@/app/components/tools/types'
import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useStrategyProviders } from '@/service/use-strategy'
import Tools from '../../../block-selector/tools'
import ViewTypeSelect, { ViewType } from '../../../block-selector/view-type-select'

View File

@ -16,7 +16,7 @@ import {
useReactFlow,
useViewport,
} from 'reactflow'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import {
useNodesSyncDraft,
useWorkflowReadOnly,

View File

@ -35,7 +35,7 @@ vi.mock('@/service/device-flow', () => ({
},
}))
vi.mock('@/service/system-features', () => ({
vi.mock('@/features/system-features/client', () => ({
systemFeaturesQueryOptions: () => ({ queryKey: ['sys'], queryFn: async () => ({}) }),
}))

View File

@ -3,10 +3,10 @@ import { useSuspenseQuery } from '@tanstack/react-query'
import Divider from '@/app/components/base/divider'
import LocaleMenu from '@/app/signin/_locale-menu'
import { useLocale } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { setLocaleOnClient } from '@/i18n-config'
import { languages } from '@/i18n-config/language'
import dynamic from '@/next/dynamic'
import { systemFeaturesQueryOptions } from '@/service/system-features'
const DifyLogo = dynamic(() => import('@/app/components/base/logo/dify-logo'), {
ssr: false,

View File

@ -1,8 +1,8 @@
'use client'
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import Header from './_header'
export default function DeviceLayout({ children }: { children: React.ReactNode }) {

View File

@ -5,10 +5,10 @@ import { useQuery } from '@tanstack/react-query'
import { useEffect, useState } from 'react'
import Divider from '@/app/components/base/divider'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { usePathname, useRouter, useSearchParams } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { deviceLookup } from '@/service/device-flow'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import AuthorizeAccount from './components/authorize-account'
import AuthorizeSSO from './components/authorize-sso'
import Chooser from './components/chooser'

View File

@ -3,9 +3,9 @@ import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
import ChangePasswordForm from '@/app/forgot-password/ChangePasswordForm'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { useSearchParams } from '@/next/navigation'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import Header from '../signin/_header'
import ForgotPasswordForm from './ForgotPasswordForm'

View File

@ -2,7 +2,7 @@
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Header from '../signin/_header'
import InstallForm from './installForm'

View File

@ -2,7 +2,7 @@
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Header from '../signin/_header'
export default function SignInLayout({ children }: any) {

View File

@ -19,7 +19,7 @@ vi.mock('@/next/dynamic', () => ({
default: () => () => null,
}))
vi.mock('@/service/system-features', () => ({
vi.mock('@/features/system-features/client', () => ({
systemFeaturesQueryOptions: () => ({}),
}))

View File

@ -2,10 +2,10 @@
import { useSuspenseQuery } from '@tanstack/react-query'
import Divider from '@/app/components/base/divider'
import { useLocale } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { setLocaleOnClient } from '@/i18n-config'
import { languages } from '@/i18n-config/language'
import dynamic from '@/next/dynamic'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import LocaleMenu from './_locale-menu'
// Avoid rendering the logo and theme selector on the server

View File

@ -5,12 +5,12 @@ import { toast } from '@langgenius/dify-ui/toast'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Lock01 } from '@/app/components/base/icons/src/vender/solid/security'
import { SSOProtocol } from '@/features/system-features/constants'
import { useRouter, useSearchParams } from '@/next/navigation'
import { getUserOAuth2SSOUrl, getUserOIDCSSOUrl, getUserSAMLSSOUrl } from '@/service/sso'
import { SSOProtocol } from '@/types/feature'
type SSOAuthProps = {
protocol: SSOProtocol | ''
protocol: string
}
const SSOAuth: FC<SSOAuthProps> = ({

View File

@ -12,13 +12,13 @@ import Input from '@/app/components/base/input'
import Loading from '@/app/components/base/loading'
import { LICENSE_LINK } from '@/constants/link'
import { useLocale } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { i18n, setLocaleOnClient } from '@/i18n-config'
import { languages } from '@/i18n-config/language'
import Link from '@/next/link'
import { useRouter, useSearchParams } from '@/next/navigation'
import { consoleQuery } from '@/service/client'
import { activateMember } from '@/service/common'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useInvitationCheck } from '@/service/use-common'
import { getBrowserTimezone, timezones } from '@/utils/timezone'
import { resolvePostLoginRedirect } from '../utils/post-login-redirect'

View File

@ -2,8 +2,8 @@
import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import Header from './_header'
export default function SignInLayout({ children }: any) {

View File

@ -7,11 +7,11 @@ import { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { IS_CE_EDITION } from '@/config'
import { isLegacyBase401, userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { LicenseStatus } from '@/features/system-features/constants'
import Link from '@/next/link'
import { useRouter, useSearchParams } from '@/next/navigation'
import { invitationCheck } from '@/service/common'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { LicenseStatus } from '@/types/feature'
import Loading from '../components/base/loading'
import MailAndCodeAuth from './components/mail-and-code-auth'
import MailAndPasswordAuth from './components/mail-and-password-auth'

View File

@ -9,8 +9,8 @@ import Input from '@/app/components/base/input'
import Split from '@/app/signin/split'
import { emailRegex } from '@/config'
import { useLocale } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import Link from '@/next/link'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useSendMail } from '@/service/use-common'
type Props = {

View File

@ -3,8 +3,8 @@ import { cn } from '@langgenius/dify-ui/cn'
import { useSuspenseQuery } from '@tanstack/react-query'
import Header from '@/app/signin/_header'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import useDocumentTitle from '@/hooks/use-document-title'
import { systemFeaturesQueryOptions } from '@/service/system-features'
export default function RegisterLayout({ children }: any) {
const { data: systemFeatures } = useSuspenseQuery(systemFeaturesQueryOptions())

View File

@ -18,8 +18,8 @@ import {
} from '@/context/app-context'
import { env } from '@/env'
import { userProfileQueryOptions } from '@/features/account-profile/client'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { consoleQuery } from '@/service/client'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import {
useLangGeniusVersion,
} from '@/service/use-common'

View File

@ -8,9 +8,9 @@ import { useEffect } from 'react'
import { create } from 'zustand'
import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils'
import Loading from '@/app/components/base/loading'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode } from '@/models/access-control'
import { usePathname, useSearchParams } from '@/next/navigation'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { useGetWebAppAccessModeByCode } from '@/service/use-share'
type WebAppStore = {

View File

@ -1,10 +0,0 @@
import type { SystemFeatures } from '@/types/feature'
import { type } from '@orpc/contract'
import { base } from '../base'
export const systemFeaturesContract = base
.route({
path: '/system-features',
method: 'GET',
})
.output(type<SystemFeatures>())

View File

@ -18,7 +18,6 @@ import {
import { changePreferredProviderTypeContract, modelProvidersModelsContract } from './console/model-providers'
import { notificationContract, notificationDismissContract } from './console/notification'
import { pluginCheckInstalledContract, pluginLatestVersionsContract } from './console/plugins'
import { systemFeaturesContract } from './console/system'
import {
tagBindingCreateContract,
tagBindingRemoveContract,
@ -80,7 +79,6 @@ export const consoleRouterContract = {
get: accountProfileContract,
},
},
systemFeatures: systemFeaturesContract,
apps: {
...communityContract.apps,
list: appListContract,

View File

@ -1,11 +1,11 @@
import type { SystemFeatures } from '@/types/feature'
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import { describe, expect, it, vi } from 'vitest'
import { defaultSystemFeatures } from '@/types/feature'
import { defaultSystemFeatures } from '../config'
type LoadOptions = {
cloudEnv?: Partial<typeof defaultCloudEnv>
isCloudEdition: boolean
systemFeaturesResult?: SystemFeatures
systemFeaturesResult?: GetSystemFeaturesResponse
systemFeaturesError?: Error
}
@ -52,18 +52,22 @@ const loadSystemFeaturesModule = async ({
...cloudEnv,
},
}))
vi.doMock('../client', () => ({
vi.doMock('@/service/client', () => ({
consoleClient: {
systemFeatures,
systemFeatures: {
get: systemFeatures,
},
},
consoleQuery: {
systemFeatures: {
queryKey: () => queryKey,
get: {
queryKey: () => queryKey,
},
},
},
}))
const module = await import('../system-features')
const module = await import('../client')
return {
module,
@ -93,19 +97,23 @@ const loadServerSystemFeaturesModule = async ({
...cloudEnv,
},
}))
vi.doMock('../server', () => ({
vi.doMock('@/service/server', () => ({
getServerConsoleClientContext,
serverConsoleClient: {
systemFeatures,
systemFeatures: {
get: systemFeatures,
},
},
serverConsoleQuery: {
systemFeatures: {
queryKey: () => queryKey,
get: {
queryKey: () => queryKey,
},
},
},
}))
const module = await import('../server-system-features')
const module = await import('../server')
return {
getServerConsoleClientContext,
@ -124,7 +132,7 @@ describe('systemFeaturesQueryOptions', () => {
const data = await options.queryFn?.(queryContext)
expect(systemFeatures).not.toHaveBeenCalled()
expect(options.staleTime).toBe(Infinity)
expect(options.staleTime).toBe('static')
expect(data).toMatchObject({
enable_marketplace: true,
enable_email_code_login: true,
@ -215,7 +223,7 @@ describe('serverSystemFeaturesQueryOptions', () => {
expect(getServerConsoleClientContext).not.toHaveBeenCalled()
expect(systemFeatures).not.toHaveBeenCalled()
expect(options.staleTime).toBe(Infinity)
expect(options.staleTime).toBe('static')
expect(data).toMatchObject({
enable_marketplace: false,
enable_email_password_login: true,

View File

@ -1,9 +1,8 @@
import type { SystemFeatures } from '@/types/feature'
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import { queryOptions } from '@tanstack/react-query'
import { IS_CLOUD_EDITION } from '@/config'
import { cloudSystemFeatures } from '@/config/cloud-system-features'
import { defaultSystemFeatures } from '@/types/feature'
import { consoleClient, consoleQuery } from './client'
import { consoleClient, consoleQuery } from '@/service/client'
import { cloudSystemFeatures, defaultSystemFeatures } from './config'
/**
* Soft-fallback to defaults so the dashboard stays usable when /system-features fails.
@ -15,26 +14,26 @@ import { consoleClient, consoleQuery } from './client'
* is a small, dependency-free endpoint in the community edition.
*
* For Cloud, this query is intentionally local-only and uses `staleTime:
* Infinity`: the payload comes from frontend config/defaults, so refetching
* would only re-run the same local merge. For non-Cloud, do not override
* 'static'`: the payload comes from frontend config/defaults, so invalidation
* should not re-run the same local merge. For non-Cloud, do not override
* `staleTime`: inherit the 5-minute default from query-client-server.ts.
*/
export const systemFeaturesQueryOptions = () => {
const queryKey = consoleQuery.systemFeatures.queryKey()
const queryKey = consoleQuery.systemFeatures.get.queryKey()
if (IS_CLOUD_EDITION) {
return queryOptions<SystemFeatures>({
return queryOptions<GetSystemFeaturesResponse>({
queryKey,
queryFn: async () => cloudSystemFeatures,
staleTime: Infinity,
staleTime: 'static',
})
}
return queryOptions<SystemFeatures>({
return queryOptions<GetSystemFeaturesResponse>({
queryKey,
queryFn: async () => {
try {
return await consoleClient.systemFeatures()
return await consoleClient.systemFeatures.get()
}
catch (err) {
console.error('[systemFeatures] fetch failed, using defaults', err)

View File

@ -1,8 +1,58 @@
import type { SystemFeatures } from '@/types/feature'
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import { env } from '@/env'
import { defaultSystemFeatures, InstallationScope, LicenseStatus } from '@/types/feature'
import { InstallationScope, LicenseStatus } from './constants'
export const cloudSystemFeatures: SystemFeatures = {
export const defaultSystemFeatures = {
sso_enforced_for_signin: false,
sso_enforced_for_signin_protocol: '',
enable_marketplace: false,
enable_email_code_login: false,
enable_email_password_login: true,
enable_social_oauth_login: false,
enable_collaboration_mode: true,
is_allow_create_workspace: false,
is_allow_register: false,
is_email_setup: false,
enable_change_email: true,
max_plugin_package_size: 15728640,
license: {
status: LicenseStatus.NONE,
expired_at: '',
workspaces: {
enabled: false,
size: 0,
limit: 0,
},
},
branding: {
enabled: false,
login_page_logo: '',
workspace_logo: '',
favicon: '',
application_title: '',
},
webapp_auth: {
enabled: false,
allow_sso: false,
sso_config: {
protocol: '',
},
allow_email_code_login: false,
allow_email_password_login: false,
},
plugin_installation_permission: {
plugin_installation_scope: InstallationScope.ALL,
restrict_to_marketplace_only: false,
},
plugin_manager: {
enabled: false,
},
enable_creators_platform: false,
enable_trial_app: false,
enable_explore_banner: false,
} satisfies GetSystemFeaturesResponse
export const cloudSystemFeatures = {
...defaultSystemFeatures,
sso_enforced_for_signin: false,
sso_enforced_for_signin_protocol: '',
@ -49,4 +99,4 @@ export const cloudSystemFeatures: SystemFeatures = {
enable_creators_platform: env.NEXT_PUBLIC_CREATORS_PLATFORM_FEATURES_ENABLED,
enable_trial_app: env.NEXT_PUBLIC_ENABLE_TRIAL_APP,
enable_explore_banner: env.NEXT_PUBLIC_ENABLE_EXPLORE_BANNER,
}
} satisfies GetSystemFeaturesResponse

View File

@ -0,0 +1,23 @@
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
export const SSOProtocol = {
SAML: 'saml',
OIDC: 'oidc',
OAuth2: 'oauth2',
} as const
export const LicenseStatus = {
NONE: 'none',
INACTIVE: 'inactive',
ACTIVE: 'active',
EXPIRING: 'expiring',
EXPIRED: 'expired',
LOST: 'lost',
} as const satisfies Record<string, GetSystemFeaturesResponse['license']['status']>
export const InstallationScope = {
ALL: 'all',
NONE: 'none',
OFFICIAL_ONLY: 'official_only',
OFFICIAL_AND_PARTNER: 'official_and_specific_partners',
} as const satisfies Record<string, GetSystemFeaturesResponse['plugin_installation_permission']['plugin_installation_scope']>

View File

@ -1,30 +1,29 @@
import type { SystemFeatures } from '@/types/feature'
import type { GetSystemFeaturesResponse } from '@dify/contracts/api/console/system-features/types.gen'
import { queryOptions } from '@tanstack/react-query'
import { IS_CLOUD_EDITION } from '@/config'
import { cloudSystemFeatures } from '@/config/cloud-system-features'
import { defaultSystemFeatures } from '@/types/feature'
import {
getServerConsoleClientContext,
serverConsoleClient,
serverConsoleQuery,
} from './server'
} from '@/service/server'
import { cloudSystemFeatures, defaultSystemFeatures } from './config'
export const serverSystemFeaturesQueryOptions = () => {
const queryKey = serverConsoleQuery.systemFeatures.queryKey()
const queryKey = serverConsoleQuery.systemFeatures.get.queryKey()
if (IS_CLOUD_EDITION) {
return queryOptions<SystemFeatures>({
return queryOptions<GetSystemFeaturesResponse>({
queryKey,
queryFn: async () => cloudSystemFeatures,
staleTime: Infinity,
staleTime: 'static',
})
}
return queryOptions<SystemFeatures>({
return queryOptions<GetSystemFeaturesResponse>({
queryKey,
queryFn: async () => {
try {
return await serverConsoleClient.systemFeatures(undefined, {
return await serverConsoleClient.systemFeatures.get(undefined, {
context: await getServerConsoleClientContext(),
})
}

View File

@ -2,8 +2,8 @@
import { useQuery } from '@tanstack/react-query'
import { useFavicon, useTitle } from 'ahooks'
import { useEffect } from 'react'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { defaultSystemFeatures } from '@/types/feature'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { defaultSystemFeatures } from '@/features/system-features/config'
import { basePath } from '@/utils/var'
export default function useDocumentTitle(title: string) {

View File

@ -61,7 +61,8 @@ describe('server console oRPC client', () => {
})
it('should call contracts with forwarded cookies, csrf header, and no-store cache', async () => {
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ feature: { billing: false } }), {
const { defaultSystemFeatures } = await import('@/features/system-features/config')
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify(defaultSystemFeatures), {
status: 200,
headers: {
'content-type': 'application/json',
@ -70,7 +71,7 @@ describe('server console oRPC client', () => {
vi.stubGlobal('fetch', fetchMock)
const { getServerConsoleClientContext, serverConsoleClient } = await import('../server')
await serverConsoleClient.systemFeatures(undefined, {
await serverConsoleClient.systemFeatures.get(undefined, {
context: await getServerConsoleClientContext(),
})

View File

@ -1,7 +1,7 @@
import type { AccessControlAccount, AccessControlGroup, AccessMode, Subject } from '@/models/access-control'
import type { App } from '@/types/app'
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { get, post } from './base'
import { getUserCanAccess } from './share'

View File

@ -1,8 +1,8 @@
import type { App, AppCategory } from '@/models/explore'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useLocale } from '@/context/i18n'
import { systemFeaturesQueryOptions } from '@/features/system-features/client'
import { AccessMode } from '@/models/access-control'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { consoleQuery } from './client'
import { fetchAppList, fetchBanners, fetchInstalledAppList, fetchInstalledAppMeta, fetchInstalledAppParams, getAppAccessModeByAppId, uninstallApp, updatePinStatus } from './explore'

View File

@ -1,115 +0,0 @@
export const SSOProtocol = {
SAML: 'saml',
OIDC: 'oidc',
OAuth2: 'oauth2',
} as const
export type SSOProtocol = typeof SSOProtocol[keyof typeof SSOProtocol]
export const LicenseStatus = {
NONE: 'none',
INACTIVE: 'inactive',
ACTIVE: 'active',
EXPIRING: 'expiring',
EXPIRED: 'expired',
LOST: 'lost',
} as const
export type LicenseStatus = typeof LicenseStatus[keyof typeof LicenseStatus]
export const InstallationScope = {
ALL: 'all',
NONE: 'none',
OFFICIAL_ONLY: 'official_only',
OFFICIAL_AND_PARTNER: 'official_and_specific_partners',
} as const
export type InstallationScope = typeof InstallationScope[keyof typeof InstallationScope]
type License = {
status: LicenseStatus
expired_at: string | null
}
export type SystemFeatures = {
plugin_installation_permission: {
plugin_installation_scope: InstallationScope
restrict_to_marketplace_only: boolean
}
sso_enforced_for_signin: boolean
sso_enforced_for_signin_protocol: SSOProtocol | ''
sso_enforced_for_web: boolean
sso_enforced_for_web_protocol: SSOProtocol | ''
enable_marketplace: boolean
enable_change_email: boolean
enable_email_code_login: boolean
enable_email_password_login: boolean
enable_social_oauth_login: boolean
enable_collaboration_mode: boolean
is_allow_create_workspace: boolean
is_allow_register: boolean
is_email_setup: boolean
license: License
branding: {
enabled: boolean
login_page_logo: string
workspace_logo: string
favicon: string
application_title: string
}
webapp_auth: {
enabled: boolean
allow_sso: boolean
sso_config: {
protocol: SSOProtocol | ''
}
allow_email_code_login: boolean
allow_email_password_login: boolean
}
enable_creators_platform: boolean
enable_trial_app: boolean
enable_explore_banner: boolean
}
export const defaultSystemFeatures: SystemFeatures = {
plugin_installation_permission: {
plugin_installation_scope: InstallationScope.ALL,
restrict_to_marketplace_only: false,
},
sso_enforced_for_signin: false,
sso_enforced_for_signin_protocol: '',
sso_enforced_for_web: false,
sso_enforced_for_web_protocol: '',
enable_marketplace: false,
enable_change_email: false,
enable_email_code_login: false,
enable_email_password_login: false,
enable_social_oauth_login: false,
enable_collaboration_mode: false,
is_allow_create_workspace: false,
is_allow_register: false,
is_email_setup: false,
license: {
status: LicenseStatus.NONE,
expired_at: '',
},
branding: {
enabled: false,
login_page_logo: '',
workspace_logo: '',
favicon: '',
application_title: 'test title',
},
webapp_auth: {
enabled: false,
allow_sso: false,
sso_config: {
protocol: '',
},
allow_email_code_login: false,
allow_email_password_login: false,
},
enable_creators_platform: false,
enable_trial_app: false,
enable_explore_banner: false,
}