mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
feat: Hide n8n-connect usage when aiGatewayCloudUbb is enabled (#35978)
This commit is contained in:
parent
88efdb975e
commit
e7fa25217e
|
|
@ -247,6 +247,7 @@ export interface FrontendSettings {
|
|||
aiGateway?: {
|
||||
enabled: boolean;
|
||||
budget: number;
|
||||
cloudUbbEnabled: boolean;
|
||||
};
|
||||
ai: {
|
||||
allowSendingParameterValues: boolean;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,10 @@ export class LicenseState {
|
|||
return this.isLicensed('feat:aiGateway');
|
||||
}
|
||||
|
||||
isAiGatewayCloudUbbLicensed() {
|
||||
return this.isLicensed('feat:aiGatewayCloudUbb');
|
||||
}
|
||||
|
||||
isAdvancedExecutionFiltersLicensed() {
|
||||
return this.isLicensed('feat:advancedExecutionFilters');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export const LICENSE_FEATURES = {
|
|||
COMMUNITY_NODES_CUSTOM_REGISTRY: 'feat:communityNodes:customRegistry',
|
||||
AI_CREDITS: 'feat:aiCredits',
|
||||
AI_GATEWAY: 'feat:aiGateway',
|
||||
AI_GATEWAY_CLOUD_UBB: 'feat:aiGatewayCloudUbb',
|
||||
FOLDERS: 'feat:folders',
|
||||
INSIGHTS_VIEW_SUMMARY: 'feat:insights:viewSummary',
|
||||
INSIGHTS_VIEW_DASHBOARD: 'feat:insights:viewDashboard',
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export class E2EController {
|
|||
[LICENSE_FEATURES.ASK_AI]: false,
|
||||
[LICENSE_FEATURES.AI_CREDITS]: false,
|
||||
[LICENSE_FEATURES.AI_GATEWAY]: false,
|
||||
[LICENSE_FEATURES.AI_GATEWAY_CLOUD_UBB]: false,
|
||||
[LICENSE_FEATURES.FOLDERS]: false,
|
||||
[LICENSE_FEATURES.INSIGHTS_VIEW_SUMMARY]: false,
|
||||
[LICENSE_FEATURES.INSIGHTS_VIEW_DASHBOARD]: false,
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ describe('FrontendService', () => {
|
|||
globalConfig.aiAssistant.baseUrl = '';
|
||||
globalConfig.aiGateway.enabled = false;
|
||||
licenseState.isAiGatewayLicensed.mockReturnValue(false);
|
||||
licenseState.isAiGatewayCloudUbbLicensed.mockReturnValue(false);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -293,6 +294,18 @@ describe('FrontendService', () => {
|
|||
expect(settings.aiGateway).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should enable Cloud UBB mode when the AI Gateway is also licensed', async () => {
|
||||
globalConfig.aiAssistant.baseUrl = 'https://ai-assistant.n8n.io';
|
||||
globalConfig.aiGateway.enabled = true;
|
||||
licenseState.isAiGatewayLicensed.mockReturnValue(true);
|
||||
licenseState.isAiGatewayCloudUbbLicensed.mockReturnValue(true);
|
||||
const { service } = createMockService();
|
||||
|
||||
const settings = await service.getSettings();
|
||||
|
||||
expect(settings.aiGateway).toMatchObject({ enabled: true, cloudUbbEnabled: true });
|
||||
});
|
||||
|
||||
it('should normalize configured postMessage origins', async () => {
|
||||
securityConfig.postMessageAllowedOrigins =
|
||||
'HTTPS://Example.COM/, https://app.example.com:443, http://localhost:5678/path, not a url, data:text/html;foo';
|
||||
|
|
|
|||
|
|
@ -607,6 +607,7 @@ export class FrontendService {
|
|||
this.settings.aiGateway = {
|
||||
enabled: true,
|
||||
budget: this.license.getValue(LICENSE_QUOTAS.AI_GATEWAY_BUDGET) ?? 0,
|
||||
cloudUbbEnabled: this.licenseState.isAiGatewayCloudUbbLicensed(),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,10 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||
|
||||
const isAiGatewayEnabled = computed(() => settings.value.aiGateway?.enabled ?? false);
|
||||
|
||||
const isAiGatewayCloudUbbEnabled = computed(
|
||||
() => settings.value.aiGateway?.cloudUbbEnabled ?? false,
|
||||
);
|
||||
|
||||
const aiGatewayBudget = computed(() => settings.value.aiGateway?.budget ?? 0);
|
||||
|
||||
const isSmtpSetup = computed(() => userManagement.value.smtpSetup);
|
||||
|
|
@ -498,6 +502,7 @@ export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
|||
aiCreditsQuota,
|
||||
isAiDataSharingEnabled,
|
||||
isAiGatewayEnabled,
|
||||
isAiGatewayCloudUbbEnabled,
|
||||
aiGatewayBudget,
|
||||
reset,
|
||||
getTimezones,
|
||||
|
|
|
|||
|
|
@ -30,9 +30,13 @@ vi.mock('@n8n/stores/useRootStore', () => ({
|
|||
}));
|
||||
|
||||
const mockIsAiGatewayEnabled = ref(false);
|
||||
const mockIsAiGatewayCloudUbbEnabled = ref(false);
|
||||
|
||||
vi.mock('@n8n/stores/settings.store', () => ({
|
||||
useSettingsStore: vi.fn(() => ({ isAiGatewayEnabled: mockIsAiGatewayEnabled.value })),
|
||||
useSettingsStore: vi.fn(() => ({
|
||||
isAiGatewayEnabled: mockIsAiGatewayEnabled.value,
|
||||
isAiGatewayCloudUbbEnabled: mockIsAiGatewayCloudUbbEnabled.value,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('useAiGateway', () => {
|
||||
|
|
@ -40,6 +44,7 @@ describe('useAiGateway', () => {
|
|||
setActivePinia(createPinia());
|
||||
vi.clearAllMocks();
|
||||
mockIsAiGatewayEnabled.value = false;
|
||||
mockIsAiGatewayCloudUbbEnabled.value = false;
|
||||
mockGetGatewayConfig.mockResolvedValue({ nodes: [], credentialTypes: [], providerConfig: {} });
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useSettingsItems } from './useSettingsItems';
|
||||
|
||||
const isAiGatewayCloudUbbEnabled = ref(false);
|
||||
|
||||
vi.mock('vue-router', () => ({ useRouter: vi.fn(() => ({})) }));
|
||||
vi.mock('./useUserHelpers', () => ({
|
||||
useUserHelpers: vi.fn(() => ({ canUserAccessRouteByName: vi.fn(() => true) })),
|
||||
}));
|
||||
vi.mock('./useAiGateway', () => ({
|
||||
useAiGateway: vi.fn(() => ({ balance: computed(() => undefined) })),
|
||||
}));
|
||||
vi.mock('@n8n/i18n', () => ({ useI18n: vi.fn(() => ({ baseText: (key: string) => key })) }));
|
||||
vi.mock('../stores/ui.store', () => ({ useUIStore: vi.fn(() => ({ settingsSidebarItems: [] })) }));
|
||||
vi.mock('@n8n/stores/settings.store', () => ({
|
||||
useSettingsStore: vi.fn(() => ({
|
||||
isAiAssistantEnabled: false,
|
||||
isAiGatewayEnabled: true,
|
||||
isAiGatewayCloudUbbEnabled: isAiGatewayCloudUbbEnabled.value,
|
||||
isPublicApiEnabled: false,
|
||||
isQueueModeEnabled: false,
|
||||
isModuleActive: vi.fn(() => false),
|
||||
})),
|
||||
}));
|
||||
vi.mock('../utils/rbac/permissions', () => ({ hasPermission: vi.fn(() => false) }));
|
||||
vi.mock('@/features/shared/envFeatureFlag/useEnvFeatureFlag', () => ({
|
||||
useEnvFeatureFlag: vi.fn(() => ({ check: computed(() => () => false) })),
|
||||
}));
|
||||
|
||||
describe('useSettingsItems', () => {
|
||||
it.each([
|
||||
[false, true],
|
||||
[true, false],
|
||||
])('shows n8n Connect only when Cloud UBB is %s', (cloudUbbEnabled, visible) => {
|
||||
isAiGatewayCloudUbbEnabled.value = cloudUbbEnabled;
|
||||
|
||||
const item = useSettingsItems().settingsItems.value.find(
|
||||
({ id }) => id === 'settings-n8n-connect',
|
||||
);
|
||||
|
||||
expect(item !== undefined).toBe(visible);
|
||||
});
|
||||
});
|
||||
|
|
@ -61,7 +61,9 @@ export function useSettingsItems() {
|
|||
label: i18n.baseText('settings.n8nConnect'),
|
||||
position: 'top',
|
||||
available:
|
||||
settingsStore.isAiGatewayEnabled && canUserAccessRouteByName(VIEWS.AI_GATEWAY_SETTINGS),
|
||||
settingsStore.isAiGatewayEnabled &&
|
||||
!settingsStore.isAiGatewayCloudUbbEnabled &&
|
||||
canUserAccessRouteByName(VIEWS.AI_GATEWAY_SETTINGS),
|
||||
route: { to: { name: VIEWS.AI_GATEWAY_SETTINGS } },
|
||||
creditsTag:
|
||||
balance.value !== undefined
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ describe('router', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
settingsStore = useSettingsStore();
|
||||
settingsStore.settings.aiGateway = undefined;
|
||||
const usersStore = useUsersStore();
|
||||
initializeAuthenticatedFeaturesSpy = vi
|
||||
.spyOn(init, 'initializeAuthenticatedFeatures')
|
||||
|
|
@ -201,6 +202,14 @@ describe('router', () => {
|
|||
expect(router.currentRoute.value.name).toBe(name);
|
||||
});
|
||||
|
||||
test('should block n8n Connect settings for Cloud UBB', async () => {
|
||||
settingsStore.settings.aiGateway = { enabled: true, budget: 0, cloudUbbEnabled: true };
|
||||
|
||||
await router.push('/settings/n8n-connect');
|
||||
|
||||
expect(router.currentRoute.value.name).toBe(VIEWS.WORKFLOWS);
|
||||
});
|
||||
|
||||
describe('resource center route guard', () => {
|
||||
beforeEach(async () => {
|
||||
// Reset to a neutral route so each test's push('/resource-center')
|
||||
|
|
|
|||
|
|
@ -768,7 +768,7 @@ export const routes: RouteRecordRaw[] = [
|
|||
middlewareOptions: {
|
||||
custom: () => {
|
||||
const settingsStore = useSettingsStore();
|
||||
return settingsStore.isAiGatewayEnabled;
|
||||
return settingsStore.isAiGatewayEnabled && !settingsStore.isAiGatewayCloudUbbEnabled;
|
||||
},
|
||||
},
|
||||
telemetry: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user