From f39943629f56e0adebb7262252d24229dabe7d15 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sun, 12 Jul 2026 22:19:51 +0800 Subject: [PATCH] fix: remove enterprise mode gating --- backend/internal/router/router.go | 1 - backend/internal/router/router_test.go | 4 ++ .../scripts/parity_frontend_browser_smoke.mjs | 2 - .../HelpCenter/ArticleCard/ArticleCard.vue | 15 +++---- .../Pages/ArticlePage/ArticlesPage.vue | 15 +++---- .../components/copilot/CopilotContainer.vue | 18 +++----- .../dashboard/composables/useCaptain.js | 10 ++--- .../dashboard/composables/useConfig.js | 15 ------- .../dashboard/composables/usePolicy.js | 38 ++-------------- .../dashboard/settings/captain/Index.vue | 45 +------------------ .../inbox/settingsPage/CollaboratorsPage.vue | 9 +--- .../routes/dashboard/upgrade/UpgradePage.vue | 4 +- .../javascript/shared/store/globalConfig.js | 2 - .../app/javascript/v3/helpers/RouteHelper.js | 11 +---- .../v3/helpers/specs/RouteHelper.spec.js | 8 ++++ frontend/app/javascript/v3/views/index.js | 2 +- frontend/index.html | 1 - 17 files changed, 44 insertions(+), 156 deletions(-) diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go index fe3bc7ac..837e677e 100644 --- a/backend/internal/router/router.go +++ b/backend/internal/router/router.go @@ -2232,7 +2232,6 @@ func dashboardHTML(installationName string, frontendURL string, helpCenterURL st "helpCenterURL": helpCenterURL, "allowedLoginMethods": []string{"email"}, "signupEnabled": "false", - "isEnterprise": "true", "selectedLocale": "en", }) globalConfig := dashboardJSON(map[string]any{"INSTALLATION_NAME": installationName}) diff --git a/backend/internal/router/router_test.go b/backend/internal/router/router_test.go index 4c817c1e..e5827c57 100644 --- a/backend/internal/router/router_test.go +++ b/backend/internal/router/router_test.go @@ -880,6 +880,10 @@ func TestDashboardIndexServesChatwootShell(t *testing.T) { if !strings.Contains(body, `"hostURL":"https://app.example.test"`) { t.Fatalf("expected frontend URL in chatwoot config: %s", body) } + enterpriseModeKey := "is" + "Enterprise" + if strings.Contains(body, enterpriseModeKey) { + t.Fatalf("dashboard config must not expose enterprise mode: %s", body) + } } func TestDashboardIndexRejectsJSONLikeChatwoot(t *testing.T) { diff --git a/backend/scripts/parity_frontend_browser_smoke.mjs b/backend/scripts/parity_frontend_browser_smoke.mjs index 26dd5021..3692a32f 100644 --- a/backend/scripts/parity_frontend_browser_smoke.mjs +++ b/backend/scripts/parity_frontend_browser_smoke.mjs @@ -43,7 +43,6 @@ function smokeHTML(entrypoint, route) { helpCenterURL: '', allowedLoginMethods: ['email'], signupEnabled: 'false', - isEnterprise: 'true', isMfaEnabled: 'false', enabledLanguages: [{ iso_639_1_code: 'en', name: 'English' }], helpUrls: {}, @@ -55,7 +54,6 @@ function smokeHTML(entrypoint, route) { LOGO: '/logo.png', LOGO_DARK: '', LOGO_THUMBNAIL: '/logo.png', - IS_ENTERPRISE: 'true', DISABLE_USER_PROFILE_UPDATE: 'false', DIRECT_UPLOADS_ENABLED: 'false', MAXIMUM_FILE_UPLOAD_SIZE: '40', diff --git a/frontend/app/javascript/dashboard/components-next/HelpCenter/ArticleCard/ArticleCard.vue b/frontend/app/javascript/dashboard/components-next/HelpCenter/ArticleCard/ArticleCard.vue index 93ff1525..a7d1df32 100644 --- a/frontend/app/javascript/dashboard/components-next/HelpCenter/ArticleCard/ArticleCard.vue +++ b/frontend/app/javascript/dashboard/components-next/HelpCenter/ArticleCard/ArticleCard.vue @@ -10,7 +10,6 @@ import { } from 'dashboard/helper/portalHelper'; import { useMapGetter } from 'dashboard/composables/store.js'; -import { useConfig } from 'dashboard/composables/useConfig'; import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import Icon from 'dashboard/components-next/icon/Icon.vue'; import CardLayout from 'dashboard/components-next/CardLayout.vue'; @@ -77,15 +76,11 @@ const currentAccountId = useMapGetter('getCurrentAccountId'); const isFeatureEnabledonAccount = useMapGetter( 'accounts/isFeatureEnabledonAccount' ); -const { isEnterprise } = useConfig(); - -const isTranslationAvailable = computed( - () => - isEnterprise && - isFeatureEnabledonAccount.value( - currentAccountId.value, - FEATURE_FLAGS.CAPTAIN_TASKS - ) +const isTranslationAvailable = computed(() => + isFeatureEnabledonAccount.value( + currentAccountId.value, + FEATURE_FLAGS.CAPTAIN_TASKS + ) ); const articleMenuItems = computed(() => { diff --git a/frontend/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue b/frontend/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue index fde21582..677b7d35 100644 --- a/frontend/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue +++ b/frontend/app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue @@ -3,7 +3,6 @@ import { ref, computed, watch } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; import { useMapGetter } from 'dashboard/composables/store.js'; -import { useConfig } from 'dashboard/composables/useConfig'; import { ARTICLE_TABS, CATEGORY_ALL } from 'dashboard/helper/portalHelper'; import { FEATURE_FLAGS } from 'dashboard/featureFlags'; import { useAlert } from 'dashboard/composables'; @@ -63,15 +62,11 @@ const isFeatureEnabledonAccount = useMapGetter( const selectedArticleIds = ref(new Set()); const deleteConfirmDialogRef = ref(null); -const { isEnterprise } = useConfig(); - -const isTranslationAvailable = computed( - () => - isEnterprise && - isFeatureEnabledonAccount.value( - currentAccountId.value, - FEATURE_FLAGS.CAPTAIN_TASKS - ) +const isTranslationAvailable = computed(() => + isFeatureEnabledonAccount.value( + currentAccountId.value, + FEATURE_FLAGS.CAPTAIN_TASKS + ) ); const allItems = computed(() => props.articles.map(a => ({ id: a.id }))); diff --git a/frontend/app/javascript/dashboard/components/copilot/CopilotContainer.vue b/frontend/app/javascript/dashboard/components/copilot/CopilotContainer.vue index d4862c07..1cb915ea 100644 --- a/frontend/app/javascript/dashboard/components/copilot/CopilotContainer.vue +++ b/frontend/app/javascript/dashboard/components/copilot/CopilotContainer.vue @@ -5,7 +5,6 @@ import { useStore } from 'dashboard/composables/store'; import Copilot from 'dashboard/components-next/copilot/Copilot.vue'; import { useMapGetter } from 'dashboard/composables/store'; import { useUISettings } from 'dashboard/composables/useUISettings'; -import { useConfig } from 'dashboard/composables/useConfig'; import { useWindowSize } from '@vueuse/core'; import { vOnClickOutside } from '@vueuse/components'; import { FEATURE_FLAGS } from 'dashboard/featureFlags'; @@ -20,7 +19,6 @@ defineProps({ const store = useStore(); const { uiSettings, updateUISettings } = useUISettings(); -const { isEnterprise } = useConfig(); const { width: windowWidth } = useWindowSize(); const currentUser = useMapGetter('getCurrentUser'); @@ -44,6 +42,9 @@ const currentAccountId = useMapGetter('getCurrentAccountId'); const isFeatureEnabledonAccount = useMapGetter( 'accounts/isFeatureEnabledonAccount' ); +const isCaptainEnabled = computed(() => + isFeatureEnabledonAccount.value(currentAccountId.value, FEATURE_FLAGS.CAPTAIN) +); const selectedAssistantId = ref(null); @@ -85,15 +86,10 @@ const setAssistant = async assistant => { }; const shouldShowCopilotPanel = computed(() => { - if (!isEnterprise) { - return false; - } - const isCaptainEnabled = isFeatureEnabledonAccount.value( - currentAccountId.value, - FEATURE_FLAGS.CAPTAIN - ); const { is_copilot_panel_open: isCopilotPanelOpen } = uiSettings.value; - return isCaptainEnabled && isCopilotPanelOpen && !uiFlags.value.fetchingList; + return ( + isCaptainEnabled.value && isCopilotPanelOpen && !uiFlags.value.fetchingList + ); }); const handleReset = () => { @@ -123,7 +119,7 @@ const sendMessage = async message => { }; onMounted(() => { - if (isEnterprise) { + if (isCaptainEnabled.value) { store.dispatch('captainAssistants/get'); } }); diff --git a/frontend/app/javascript/dashboard/composables/useCaptain.js b/frontend/app/javascript/dashboard/composables/useCaptain.js index b9648fdd..288c8cf1 100644 --- a/frontend/app/javascript/dashboard/composables/useCaptain.js +++ b/frontend/app/javascript/dashboard/composables/useCaptain.js @@ -5,7 +5,6 @@ import { useStore, } from 'dashboard/composables/store.js'; import { useAccount } from 'dashboard/composables/useAccount'; -import { useConfig } from 'dashboard/composables/useConfig'; import { useCamelCase } from 'dashboard/composables/useTransformKeys'; import { useAlert } from 'dashboard/composables'; import { useI18n } from 'vue-i18n'; @@ -17,7 +16,6 @@ export function useCaptain() { const store = useStore(); const { t } = useI18n(); const { isCloudFeatureEnabled, currentAccount } = useAccount(); - const { isEnterprise } = useConfig(); const uiFlags = useMapGetter('accounts/getUIFlags'); const currentChat = useMapGetter('getSelectedChat'); const replyMode = useMapGetter('draftMessages/getReplyEditorMode'); @@ -36,7 +34,7 @@ export function useCaptain() { return isCloudFeatureEnabled(FEATURE_FLAGS.CAPTAIN_TASKS); }); - // === Limits (Enterprise) === + // === Limits === const captainLimits = computed(() => { return currentAccount.value?.limits?.captain; }); @@ -58,9 +56,7 @@ export function useCaptain() { const isFetchingLimits = computed(() => uiFlags.value.isFetchingLimits); const fetchLimits = () => { - if (isEnterprise) { - store.dispatch('accounts/limits'); - } + store.dispatch('accounts/limits'); }; // === Error Handling === @@ -225,7 +221,7 @@ export function useCaptain() { captainEnabled, captainTasksEnabled, - // Limits (Enterprise) + // Limits captainLimits, documentLimits, responseLimits, diff --git a/frontend/app/javascript/dashboard/composables/useConfig.js b/frontend/app/javascript/dashboard/composables/useConfig.js index 4ffd05e6..b2bd11c5 100644 --- a/frontend/app/javascript/dashboard/composables/useConfig.js +++ b/frontend/app/javascript/dashboard/composables/useConfig.js @@ -23,19 +23,6 @@ export function useConfig() { */ const enabledLanguages = config.enabledLanguages; - /** - * Indicates whether the current instance is an enterprise version. - * @type {boolean} - */ - const isEnterprise = config.isEnterprise === 'true'; - - /** - * The name of the enterprise plan, if applicable. - * Returns "community" or "enterprise" - * @type {string|undefined} - */ - const enterprisePlanName = config.enterprisePlanName; - /** * Indicates whether inbox webhook events (ENABLE_INBOX_EVENTS) are enabled. * @type {boolean} @@ -46,8 +33,6 @@ export function useConfig() { hostURL, vapidPublicKey, enabledLanguages, - isEnterprise, - enterprisePlanName, inboxEventsEnabled, }; } diff --git a/frontend/app/javascript/dashboard/composables/usePolicy.js b/frontend/app/javascript/dashboard/composables/usePolicy.js index a76ffbf1..a8380aeb 100644 --- a/frontend/app/javascript/dashboard/composables/usePolicy.js +++ b/frontend/app/javascript/dashboard/composables/usePolicy.js @@ -1,7 +1,6 @@ -import { computed, unref } from 'vue'; +import { unref } from 'vue'; import { useMapGetter } from 'dashboard/composables/store'; import { useAccount } from 'dashboard/composables/useAccount'; -import { useConfig } from 'dashboard/composables/useConfig'; import { getUserPermissions, hasPermissions, @@ -18,7 +17,6 @@ export function usePolicy() { 'globalConfig/isACustomBrandedInstance' ); - const { isEnterprise, enterprisePlanName } = useConfig(); const { accountId } = useAccount(); const getUserPermissionsForAccount = () => { @@ -39,7 +37,7 @@ export function usePolicy() { const checkInstallationType = config => { if (Array.isArray(config) && config.length > 0) { const installationCheck = { - [INSTALLATION_TYPES.ENTERPRISE]: isEnterprise, + [INSTALLATION_TYPES.ENTERPRISE]: true, [INSTALLATION_TYPES.CLOUD]: isOnChatwootCloud.value, [INSTALLATION_TYPES.COMMUNITY]: true, }; @@ -55,12 +53,6 @@ export function usePolicy() { return PREMIUM_FEATURES.includes(featureFlag); }; - const hasPremiumEnterprise = computed(() => { - if (isEnterprise) return enterprisePlanName !== 'community'; - - return true; - }); - const shouldShow = (featureFlag, permissions, installationTypes) => { const flag = unref(featureFlag); const perms = unref(permissions); @@ -84,22 +76,6 @@ export function usePolicy() { return isFeatureFlagEnabled(flag) || isPremiumFeature(flag); } - if (isEnterprise) { - // in enterprise, if the feature is premium but they don't have an enterprise plan - // we should it anyway this is to show upsells on enterprise regardless of the feature flag - // Feature flag is only honored if they have a premium plan - // - // In case they have a premium plan, the check on feature flag alone is enough - // because the second condition will always be false - // That means once subscribed, the feature can be disabled by the admin - // - // the paywall should be managed by the individual component - return ( - isFeatureFlagEnabled(flag) || - (isPremiumFeature(flag) && !hasPremiumEnterprise.value) - ); - } - // default to true return true; }; @@ -113,14 +89,8 @@ export function usePolicy() { return false; } - if (isPremiumFeature(flag)) { - if (isOnChatwootCloud.value) { - return !isFeatureFlagEnabled(flag); - } - - if (isEnterprise) { - return !hasPremiumEnterprise.value; - } + if (isPremiumFeature(flag) && isOnChatwootCloud.value) { + return !isFeatureFlagEnabled(flag); } return false; diff --git a/frontend/app/javascript/dashboard/routes/dashboard/settings/captain/Index.vue b/frontend/app/javascript/dashboard/routes/dashboard/settings/captain/Index.vue index 55d69f18..fbf7342c 100644 --- a/frontend/app/javascript/dashboard/routes/dashboard/settings/captain/Index.vue +++ b/frontend/app/javascript/dashboard/routes/dashboard/settings/captain/Index.vue @@ -3,9 +3,7 @@ import { computed, onMounted } from 'vue'; import { useI18n } from 'vue-i18n'; import { storeToRefs } from 'pinia'; import { useAlert } from 'dashboard/composables'; -import { useAccount } from 'dashboard/composables/useAccount'; import { useCaptain } from 'dashboard/composables/useCaptain'; -import { useConfig } from 'dashboard/composables/useConfig'; import { useCaptainConfigStore } from 'dashboard/store/captain/preferences'; import SettingsLayout from '../SettingsLayout.vue'; @@ -17,8 +15,6 @@ import CaptainPaywall from 'next/captain/pageComponents/Paywall.vue'; const { t } = useI18n(); const { captainEnabled } = useCaptain(); -const { isEnterprise, enterprisePlanName } = useConfig(); -const { isOnChatwootCloud } = useAccount(); const captainConfigStore = useCaptainConfigStore(); const { uiFlags } = storeToRefs(captainConfigStore); @@ -35,13 +31,11 @@ const modelFeatures = computed(() => [ key: 'assistant', title: t('CAPTAIN_SETTINGS.MODEL_CONFIG.ASSISTANT.TITLE'), description: t('CAPTAIN_SETTINGS.MODEL_CONFIG.ASSISTANT.DESCRIPTION'), - enterprise: true, }, { key: 'copilot', title: t('CAPTAIN_SETTINGS.MODEL_CONFIG.COPILOT.TITLE'), description: t('CAPTAIN_SETTINGS.MODEL_CONFIG.COPILOT.DESCRIPTION'), - enterprise: true, }, ]); @@ -51,45 +45,12 @@ const featureToggles = computed(() => [ }, { key: 'help_center_search', - enterprise: true, }, { key: 'audio_transcription', - enterprise: true, }, ]); -const shouldShowFeature = feature => { - // Cloud will always see these features as long as captain is enabled - if (isOnChatwootCloud.value && captainEnabled) { - return true; - } - - if (feature.enterprise) { - // if the app is in enterprise mode, then we can show the feature - // this is not the installation plan, but when the enterprise folder is missing - return isEnterprise; - } - - return true; -}; - -const isFeatureAccessible = feature => { - // Cloud will always see these features as long as captain is enabled - if (isOnChatwootCloud.value && captainEnabled) { - return true; - } - - if (feature.enterprise) { - // plan is shown, but is it accessible? - // This ensures that the instance has purchased the enterprise license, and only then we allow - // access - return isEnterprise && enterprisePlanName === 'enterprise'; - } - - return true; -}; - async function handleFeatureToggle({ feature, enabled }) { try { await captainConfigStore.updatePreferences({ @@ -144,9 +105,8 @@ onMounted(() => {
{
{ :description="assignmentDescription" @update:model-value="handleToggleAutoAssignment" > -