mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor(editor): Migrate settings.store to composition API (no-changelog) (#10022)
Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
parent
062633ec9b
commit
ba27c987dc
|
@ -23,7 +23,7 @@ export const defaultSettings: IN8nUISettings = {
|
||||||
logStreaming: false,
|
logStreaming: false,
|
||||||
debugInEditor: false,
|
debugInEditor: false,
|
||||||
advancedExecutionFilters: false,
|
advancedExecutionFilters: false,
|
||||||
variables: true,
|
variables: false,
|
||||||
sourceControl: false,
|
sourceControl: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
showNonProdBanner: false,
|
showNonProdBanner: false,
|
||||||
|
|
|
@ -2,9 +2,14 @@ import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import Assignment from '../Assignment.vue';
|
import Assignment from '../Assignment.vue';
|
||||||
|
import { defaultSettings } from '@/__tests__/defaults';
|
||||||
|
import { STORES } from '@/constants';
|
||||||
|
import { merge } from 'lodash-es';
|
||||||
|
|
||||||
const DEFAULT_SETUP = {
|
const DEFAULT_SETUP = {
|
||||||
pinia: createTestingPinia(),
|
pinia: createTestingPinia({
|
||||||
|
initialState: { [STORES.SETTINGS]: { settings: merge({}, defaultSettings) } },
|
||||||
|
}),
|
||||||
props: {
|
props: {
|
||||||
path: 'parameters.fields.0',
|
path: 'parameters.fields.0',
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
|
|
@ -5,10 +5,16 @@ import userEvent from '@testing-library/user-event';
|
||||||
import { fireEvent, within } from '@testing-library/vue';
|
import { fireEvent, within } from '@testing-library/vue';
|
||||||
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
|
import * as workflowHelpers from '@/composables/useWorkflowHelpers';
|
||||||
import AssignmentCollection from '../AssignmentCollection.vue';
|
import AssignmentCollection from '../AssignmentCollection.vue';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { STORES } from '@/constants';
|
||||||
|
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
||||||
|
|
||||||
const DEFAULT_SETUP = {
|
const DEFAULT_SETUP = {
|
||||||
pinia: createTestingPinia(),
|
pinia: createTestingPinia({
|
||||||
|
initialState: {
|
||||||
|
[STORES.SETTINGS]: SETTINGS_STORE_DEFAULT_STATE,
|
||||||
|
},
|
||||||
|
stubActions: false,
|
||||||
|
}),
|
||||||
props: {
|
props: {
|
||||||
path: 'parameters.fields',
|
path: 'parameters.fields',
|
||||||
node: {
|
node: {
|
||||||
|
@ -97,10 +103,7 @@ describe('AssignmentCollection.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add assignments by drag and drop (and infer type)', async () => {
|
it('can add assignments by drag and drop (and infer type)', async () => {
|
||||||
const pinia = createPinia();
|
const { getByTestId, findAllByTestId } = renderComponent();
|
||||||
setActivePinia(pinia);
|
|
||||||
|
|
||||||
const { getByTestId, findAllByTestId } = renderComponent({ pinia });
|
|
||||||
const dropArea = getByTestId('drop-area');
|
const dropArea = getByTestId('drop-area');
|
||||||
|
|
||||||
await dropAssignment({ key: 'boolKey', value: true, dropArea });
|
await dropAssignment({ key: 'boolKey', value: true, dropArea });
|
||||||
|
|
|
@ -780,7 +780,7 @@ async function saveCredential(): Promise<ICredentialsResponse | null> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing) &&
|
settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing] &&
|
||||||
credentialData.value.sharedWithProjects
|
credentialData.value.sharedWithProjects
|
||||||
) {
|
) {
|
||||||
credentialDetails.sharedWithProjects = credentialData.value
|
credentialDetails.sharedWithProjects = credentialData.value
|
||||||
|
@ -817,7 +817,7 @@ async function saveCredential(): Promise<ICredentialsResponse | null> {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) {
|
||||||
credentialDetails.sharedWithProjects = credentialData.value
|
credentialDetails.sharedWithProjects = credentialData.value
|
||||||
.sharedWithProjects as ProjectSharingData[];
|
.sharedWithProjects as ProjectSharingData[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,7 +156,7 @@ export default defineComponent({
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
isSharingEnabled(): boolean {
|
isSharingEnabled(): boolean {
|
||||||
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
|
return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing];
|
||||||
},
|
},
|
||||||
credentialOwnerName(): string {
|
credentialOwnerName(): string {
|
||||||
return this.credentialsStore.getCredentialOwnerNameById(`${this.credentialId}`);
|
return this.credentialsStore.getCredentialOwnerNameById(`${this.credentialId}`);
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default defineComponent({
|
||||||
...mapStores(useSettingsStore),
|
...mapStores(useSettingsStore),
|
||||||
canAccess(): boolean {
|
canAccess(): boolean {
|
||||||
return this.features.reduce((acc: boolean, feature) => {
|
return this.features.reduce((acc: boolean, feature) => {
|
||||||
return acc && !!this.settingsStore.isEnterpriseFeatureEnabled(feature);
|
return acc && !!this.settingsStore.isEnterpriseFeatureEnabled[feature];
|
||||||
}, true);
|
}, true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -190,9 +190,9 @@ export default defineComponent({
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
isAdvancedPermissionsEnabled(): boolean {
|
isAdvancedPermissionsEnabled(): boolean {
|
||||||
return this.settingsStore.isEnterpriseFeatureEnabled(
|
return this.settingsStore.isEnterpriseFeatureEnabled[
|
||||||
EnterpriseEditionFeature.AdvancedPermissions,
|
EnterpriseEditionFeature.AdvancedPermissions
|
||||||
);
|
];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -208,7 +208,7 @@ const workflowMenuItems = computed<ActionDropdownItem[]>(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isWorkflowHistoryFeatureEnabled = computed(() => {
|
const isWorkflowHistoryFeatureEnabled = computed(() => {
|
||||||
return settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.WorkflowHistory);
|
return settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.WorkflowHistory];
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflowHistoryRoute = computed<{ name: string; params: { workflowId: string } }>(() => {
|
const workflowHistoryRoute = computed<{ name: string; params: { workflowId: string } }>(() => {
|
||||||
|
|
|
@ -439,7 +439,7 @@ const foreignCredentials = computed(() => {
|
||||||
const usedCredentials = workflowsStore.usedCredentials;
|
const usedCredentials = workflowsStore.usedCredentials;
|
||||||
|
|
||||||
const foreignCredentialsArray: string[] = [];
|
const foreignCredentialsArray: string[] = [];
|
||||||
if (credentials && settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
if (credentials && settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) {
|
||||||
Object.values(credentials).forEach((credential) => {
|
Object.values(credentials).forEach((credential) => {
|
||||||
if (
|
if (
|
||||||
credential.id &&
|
credential.id &&
|
||||||
|
|
|
@ -50,8 +50,8 @@ const valueInputRef = ref<HTMLElement>();
|
||||||
|
|
||||||
const usage = ref(`$vars.${props.data.name}`);
|
const usage = ref(`$vars.${props.data.name}`);
|
||||||
|
|
||||||
const isFeatureEnabled = computed(() =>
|
const isFeatureEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables],
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -480,7 +480,7 @@ export default defineComponent({
|
||||||
return this.usersStore.currentUser;
|
return this.usersStore.currentUser;
|
||||||
},
|
},
|
||||||
isSharingEnabled(): boolean {
|
isSharingEnabled(): boolean {
|
||||||
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
|
return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing];
|
||||||
},
|
},
|
||||||
workflowOwnerName(): string {
|
workflowOwnerName(): string {
|
||||||
const fallback = this.$locale.baseText(
|
const fallback = this.$locale.baseText(
|
||||||
|
|
|
@ -197,7 +197,7 @@ export default defineComponent({
|
||||||
useRolesStore,
|
useRolesStore,
|
||||||
),
|
),
|
||||||
isSharingEnabled(): boolean {
|
isSharingEnabled(): boolean {
|
||||||
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
|
return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing];
|
||||||
},
|
},
|
||||||
modalTitle(): string {
|
modalTitle(): string {
|
||||||
if (this.isHomeTeamProject) {
|
if (this.isHomeTeamProject) {
|
||||||
|
|
|
@ -43,8 +43,8 @@ const debouncedEmit = debounce(emit, {
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCustomDataFilterTracked = ref(false);
|
const isCustomDataFilterTracked = ref(false);
|
||||||
const isAdvancedExecutionFilterEnabled = computed(() =>
|
const isAdvancedExecutionFilterEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.AdvancedExecutionFilters),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AdvancedExecutionFilters],
|
||||||
);
|
);
|
||||||
const showTags = computed(() => false);
|
const showTags = computed(() => false);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useRootStore } from '@/stores/root.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import type { WorkflowSettings } from 'n8n-workflow';
|
import type { WorkflowSettings } from 'n8n-workflow';
|
||||||
|
|
||||||
|
@ -38,60 +39,62 @@ type DebugInfo = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useDebugInfo() {
|
export function useDebugInfo() {
|
||||||
const store = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
|
||||||
const coreInfo = () => {
|
const coreInfo = () => {
|
||||||
return {
|
return {
|
||||||
n8nVersion: store.versionCli,
|
n8nVersion: rootStore.versionCli,
|
||||||
platform:
|
platform:
|
||||||
store.isDocker && store.deploymentType === 'cloud'
|
settingsStore.isDocker && settingsStore.deploymentType === 'cloud'
|
||||||
? 'docker (cloud)'
|
? 'docker (cloud)'
|
||||||
: store.isDocker
|
: settingsStore.isDocker
|
||||||
? 'docker (self-hosted)'
|
? 'docker (self-hosted)'
|
||||||
: 'npm',
|
: 'npm',
|
||||||
nodeJsVersion: store.nodeJsVersion,
|
nodeJsVersion: settingsStore.nodeJsVersion,
|
||||||
database:
|
database:
|
||||||
store.databaseType === 'postgresdb'
|
settingsStore.databaseType === 'postgresdb'
|
||||||
? 'postgres'
|
? 'postgres'
|
||||||
: store.databaseType === 'mysqldb'
|
: settingsStore.databaseType === 'mysqldb'
|
||||||
? 'mysql'
|
? 'mysql'
|
||||||
: store.databaseType,
|
: settingsStore.databaseType,
|
||||||
executionMode: store.isQueueModeEnabled ? 'scaling' : 'regular',
|
executionMode: settingsStore.isQueueModeEnabled ? 'scaling' : 'regular',
|
||||||
concurrency: store.settings.concurrency,
|
concurrency: settingsStore.settings.concurrency,
|
||||||
license: store.isCommunityPlan
|
license: settingsStore.isCommunityPlan
|
||||||
? 'community'
|
? 'community'
|
||||||
: store.settings.license.environment === 'production'
|
: settingsStore.settings.license.environment === 'production'
|
||||||
? 'enterprise (production)'
|
? 'enterprise (production)'
|
||||||
: 'enterprise (sandbox)',
|
: 'enterprise (sandbox)',
|
||||||
consumerId: store.consumerId,
|
consumerId: settingsStore.consumerId,
|
||||||
} as const;
|
} as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
const storageInfo = (): DebugInfo['storage'] => {
|
const storageInfo = (): DebugInfo['storage'] => {
|
||||||
return {
|
return {
|
||||||
success: store.saveDataSuccessExecution,
|
success: settingsStore.saveDataSuccessExecution,
|
||||||
error: store.saveDataErrorExecution,
|
error: settingsStore.saveDataErrorExecution,
|
||||||
progress: store.saveDataProgressExecution,
|
progress: settingsStore.saveDataProgressExecution,
|
||||||
manual: store.saveManualExecutions,
|
manual: settingsStore.saveManualExecutions,
|
||||||
binaryMode: store.binaryDataMode === 'default' ? 'memory' : store.binaryDataMode,
|
binaryMode:
|
||||||
|
settingsStore.binaryDataMode === 'default' ? 'memory' : settingsStore.binaryDataMode,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const pruningInfo = () => {
|
const pruningInfo = () => {
|
||||||
if (!store.pruning.isEnabled) return { enabled: false } as const;
|
if (!settingsStore.pruning.isEnabled) return { enabled: false } as const;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
maxAge: `${store.pruning.maxAge} hours`,
|
maxAge: `${settingsStore.pruning.maxAge} hours`,
|
||||||
maxCount: `${store.pruning.maxCount} executions`,
|
maxCount: `${settingsStore.pruning.maxCount} executions`,
|
||||||
} as const;
|
} as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
const securityInfo = () => {
|
const securityInfo = () => {
|
||||||
const info: DebugInfo['security'] = {};
|
const info: DebugInfo['security'] = {};
|
||||||
|
|
||||||
if (!store.security.blockFileAccessToN8nFiles) info.blockFileAccessToN8nFiles = false;
|
if (!settingsStore.security.blockFileAccessToN8nFiles) info.blockFileAccessToN8nFiles = false;
|
||||||
if (!store.security.secureCookie) info.secureCookie = false;
|
if (!settingsStore.security.secureCookie) info.secureCookie = false;
|
||||||
|
|
||||||
if (Object.keys(info).length === 0) return;
|
if (Object.keys(info).length === 0) return;
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ export const useExecutionDebugging = () => {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const uiStore = useUIStore();
|
const uiStore = useUIStore();
|
||||||
|
|
||||||
const isDebugEnabled = computed(() =>
|
const isDebugEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.DebugInEditor),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.DebugInEditor],
|
||||||
);
|
);
|
||||||
|
|
||||||
const applyExecutionData = async (executionId: string): Promise<void> => {
|
const applyExecutionData = async (executionId: string): Promise<void> => {
|
||||||
|
|
|
@ -44,7 +44,7 @@ function setCurrentUser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetStores() {
|
function resetStores() {
|
||||||
useSettingsStore().$reset();
|
useSettingsStore().reset();
|
||||||
useUsersStore().reset();
|
useUsersStore().reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ import { generateUpgradeLinkUrl, useUIStore } from '@/stores/ui.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { merge } from 'lodash-es';
|
import { merge } from 'lodash-es';
|
||||||
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
import * as cloudPlanApi from '@/api/cloudPlans';
|
import * as cloudPlanApi from '@/api/cloudPlans';
|
||||||
|
import { defaultSettings } from '../../__tests__/defaults';
|
||||||
import {
|
import {
|
||||||
getTrialExpiredUserResponse,
|
getTrialExpiredUserResponse,
|
||||||
getTrialingUserResponse,
|
getTrialingUserResponse,
|
||||||
|
@ -34,7 +34,7 @@ function setUser(role: IRole) {
|
||||||
function setupOwnerAndCloudDeployment() {
|
function setupOwnerAndCloudDeployment() {
|
||||||
setUser(ROLE.Owner);
|
setUser(ROLE.Owner);
|
||||||
settingsStore.setSettings(
|
settingsStore.setSettings(
|
||||||
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
|
merge({}, defaultSettings, {
|
||||||
n8nMetadata: {
|
n8nMetadata: {
|
||||||
userId: '1',
|
userId: '1',
|
||||||
},
|
},
|
||||||
|
@ -103,7 +103,7 @@ describe('UI store', () => {
|
||||||
setUser(role as IRole);
|
setUser(role as IRole);
|
||||||
|
|
||||||
settingsStore.setSettings(
|
settingsStore.setSettings(
|
||||||
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
|
merge({}, defaultSettings, {
|
||||||
deployment: {
|
deployment: {
|
||||||
type,
|
type,
|
||||||
},
|
},
|
||||||
|
@ -123,7 +123,7 @@ describe('UI store', () => {
|
||||||
|
|
||||||
it('should add non-production license banner to stack based on enterprise settings', () => {
|
it('should add non-production license banner to stack based on enterprise settings', () => {
|
||||||
settingsStore.setSettings(
|
settingsStore.setSettings(
|
||||||
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
|
merge({}, defaultSettings, {
|
||||||
enterprise: {
|
enterprise: {
|
||||||
showNonProdBanner: true,
|
showNonProdBanner: true,
|
||||||
},
|
},
|
||||||
|
@ -134,7 +134,7 @@ describe('UI store', () => {
|
||||||
|
|
||||||
it("should add V1 banner to stack if it's not dismissed", () => {
|
it("should add V1 banner to stack if it's not dismissed", () => {
|
||||||
settingsStore.setSettings(
|
settingsStore.setSettings(
|
||||||
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
|
merge({}, defaultSettings, {
|
||||||
versionCli: '1.0.0',
|
versionCli: '1.0.0',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -143,7 +143,7 @@ describe('UI store', () => {
|
||||||
|
|
||||||
it("should not add V1 banner to stack if it's dismissed", () => {
|
it("should not add V1 banner to stack if it's dismissed", () => {
|
||||||
settingsStore.setSettings(
|
settingsStore.setSettings(
|
||||||
merge({}, SETTINGS_STORE_DEFAULT_STATE.settings, {
|
merge({}, defaultSettings, {
|
||||||
versionCli: '1.0.0',
|
versionCli: '1.0.0',
|
||||||
banners: {
|
banners: {
|
||||||
dismissed: ['V1'],
|
dismissed: ['V1'],
|
||||||
|
|
|
@ -6,8 +6,8 @@ import { useSettingsStore } from '@/stores/settings.store';
|
||||||
export const useAuditLogsStore = defineStore('auditLogs', () => {
|
export const useAuditLogsStore = defineStore('auditLogs', () => {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const isEnterpriseAuditLogsFeatureEnabled = computed(() =>
|
const isEnterpriseAuditLogsFeatureEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.AuditLogs),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AuditLogs],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -314,7 +314,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
||||||
projectId,
|
projectId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) {
|
||||||
upsertCredential(credential);
|
upsertCredential(credential);
|
||||||
if (data.sharedWithProjects) {
|
if (data.sharedWithProjects) {
|
||||||
await setCredentialSharedWith({
|
await setCredentialSharedWith({
|
||||||
|
@ -389,7 +389,7 @@ export const useCredentialsStore = defineStore(STORES.CREDENTIALS, () => {
|
||||||
sharedWithProjects: ProjectSharingData[];
|
sharedWithProjects: ProjectSharingData[];
|
||||||
credentialId: string;
|
credentialId: string;
|
||||||
}): Promise<ICredentialsResponse> => {
|
}): Promise<ICredentialsResponse> => {
|
||||||
if (useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
if (useSettingsStore().isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) {
|
||||||
await credentialsEeApi.setCredentialSharedWith(
|
await credentialsEeApi.setCredentialSharedWith(
|
||||||
useRootStore().restApiContext,
|
useRootStore().restApiContext,
|
||||||
payload.credentialId,
|
payload.credentialId,
|
||||||
|
|
|
@ -19,8 +19,8 @@ export const useExternalSecretsStore = defineStore('externalSecrets', () => {
|
||||||
connectionState: {} as Record<string, ExternalSecretsProvider['state']>,
|
connectionState: {} as Record<string, ExternalSecretsProvider['state']>,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isEnterpriseExternalSecretsEnabled = computed(() =>
|
const isEnterpriseExternalSecretsEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets],
|
||||||
);
|
);
|
||||||
|
|
||||||
const secrets = computed(() => state.secrets);
|
const secrets = computed(() => state.secrets);
|
||||||
|
|
|
@ -1,27 +1,15 @@
|
||||||
import { createApiKey, deleteApiKey, getApiKey } from '@/api/api-keys';
|
import * as publicApiApi from '@/api/api-keys';
|
||||||
import {
|
import * as ldapApi from '@/api/ldap';
|
||||||
getLdapConfig,
|
import * as settingsApi from '@/api/settings';
|
||||||
getLdapSynchronizations,
|
|
||||||
runLdapSync,
|
|
||||||
testLdapConnection,
|
|
||||||
updateLdapConfig,
|
|
||||||
} from '@/api/ldap';
|
|
||||||
import { getSettings, submitContactInfo } from '@/api/settings';
|
|
||||||
import { testHealthEndpoint } from '@/api/templates';
|
import { testHealthEndpoint } from '@/api/templates';
|
||||||
import type {
|
import type { ILdapConfig } from '@/Interface';
|
||||||
EnterpriseEditionFeatureValue,
|
|
||||||
ILdapConfig,
|
|
||||||
IN8nPromptResponse,
|
|
||||||
ISettingsState,
|
|
||||||
} from '@/Interface';
|
|
||||||
import { STORES, INSECURE_CONNECTION_WARNING } from '@/constants';
|
import { STORES, INSECURE_CONNECTION_WARNING } from '@/constants';
|
||||||
import { UserManagementAuthenticationMethod } from '@/Interface';
|
import { UserManagementAuthenticationMethod } from '@/Interface';
|
||||||
import type {
|
import type {
|
||||||
IDataObject,
|
IDataObject,
|
||||||
LogLevel,
|
|
||||||
IN8nUISettings,
|
IN8nUISettings,
|
||||||
ITelemetrySettings,
|
|
||||||
WorkflowSettings,
|
WorkflowSettings,
|
||||||
|
IUserManagementSettings,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { ExpressionEvaluatorProxy } from 'n8n-workflow';
|
import { ExpressionEvaluatorProxy } from 'n8n-workflow';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
@ -33,382 +21,422 @@ import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||||
import { useTitleChange } from '@/composables/useTitleChange';
|
import { useTitleChange } from '@/composables/useTitleChange';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { i18n } from '@/plugins/i18n';
|
import { i18n } from '@/plugins/i18n';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
export const useSettingsStore = defineStore(STORES.SETTINGS, {
|
export const useSettingsStore = defineStore(STORES.SETTINGS, () => {
|
||||||
state: (): ISettingsState => ({
|
const initialized = ref(false);
|
||||||
initialized: false,
|
const settings = ref<IN8nUISettings>({} as IN8nUISettings);
|
||||||
settings: {} as IN8nUISettings,
|
const userManagement = ref<IUserManagementSettings>({
|
||||||
userManagement: {
|
quota: -1,
|
||||||
quota: -1,
|
showSetupOnFirstLoad: false,
|
||||||
showSetupOnFirstLoad: false,
|
smtpSetup: false,
|
||||||
smtpSetup: false,
|
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
||||||
authenticationMethod: UserManagementAuthenticationMethod.Email,
|
});
|
||||||
},
|
const templatesEndpointHealthy = ref(false);
|
||||||
templatesEndpointHealthy: false,
|
const api = ref({
|
||||||
api: {
|
enabled: false,
|
||||||
|
latestVersion: 0,
|
||||||
|
path: '/',
|
||||||
|
swaggerUi: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
latestVersion: 0,
|
},
|
||||||
path: '/',
|
});
|
||||||
swaggerUi: {
|
const ldap = ref({ loginLabel: '', loginEnabled: false });
|
||||||
|
const saml = ref({ loginLabel: '', loginEnabled: false });
|
||||||
|
const mfa = ref({ enabled: false });
|
||||||
|
const saveDataErrorExecution = ref<WorkflowSettings.SaveDataExecution>('all');
|
||||||
|
const saveDataSuccessExecution = ref<WorkflowSettings.SaveDataExecution>('all');
|
||||||
|
const saveManualExecutions = ref(false);
|
||||||
|
const saveDataProgressExecution = ref(false);
|
||||||
|
|
||||||
|
const isDocker = computed(() => settings.value?.isDocker ?? false);
|
||||||
|
|
||||||
|
const databaseType = computed(() => settings.value?.databaseType);
|
||||||
|
|
||||||
|
const planName = computed(() => settings.value?.license.planName ?? 'Community');
|
||||||
|
|
||||||
|
const consumerId = computed(() => settings.value?.license.consumerId);
|
||||||
|
|
||||||
|
const binaryDataMode = computed(() => settings.value?.binaryDataMode);
|
||||||
|
|
||||||
|
const pruning = computed(() => settings.value?.pruning);
|
||||||
|
|
||||||
|
const security = computed(() => ({
|
||||||
|
blockFileAccessToN8nFiles: settings.value.security.blockFileAccessToN8nFiles,
|
||||||
|
secureCookie: settings.value.authCookie.secure,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const isEnterpriseFeatureEnabled = computed(() => settings.value.enterprise);
|
||||||
|
|
||||||
|
const nodeJsVersion = computed(() => settings.value.nodeJsVersion);
|
||||||
|
|
||||||
|
const concurrency = computed(() => settings.value.concurrency);
|
||||||
|
|
||||||
|
const isPublicApiEnabled = computed(() => api.value.enabled);
|
||||||
|
|
||||||
|
const isSwaggerUIEnabled = computed(() => api.value.swaggerUi.enabled);
|
||||||
|
|
||||||
|
const isPreviewMode = computed(() => settings.value.previewMode);
|
||||||
|
|
||||||
|
const publicApiLatestVersion = computed(() => api.value.latestVersion);
|
||||||
|
|
||||||
|
const publicApiPath = computed(() => api.value.path);
|
||||||
|
|
||||||
|
const isLdapLoginEnabled = computed(() => ldap.value.loginEnabled);
|
||||||
|
|
||||||
|
const ldapLoginLabel = computed(() => ldap.value.loginLabel);
|
||||||
|
|
||||||
|
const isSamlLoginEnabled = computed(() => saml.value.loginEnabled);
|
||||||
|
|
||||||
|
const showSetupPage = computed(() => userManagement.value.showSetupOnFirstLoad);
|
||||||
|
|
||||||
|
const deploymentType = computed(() => settings.value.deployment?.type || 'default');
|
||||||
|
|
||||||
|
const isDesktopDeployment = computed(() =>
|
||||||
|
settings.value.deployment?.type.startsWith('desktop_'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isCloudDeployment = computed(() => settings.value.deployment?.type === 'cloud');
|
||||||
|
|
||||||
|
const isSmtpSetup = computed(() => userManagement.value.smtpSetup);
|
||||||
|
|
||||||
|
const isPersonalizationSurveyEnabled = computed(
|
||||||
|
() => settings.value.telemetry?.enabled && settings.value.personalizationSurveyEnabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
const telemetry = computed(() => settings.value.telemetry);
|
||||||
|
|
||||||
|
const logLevel = computed(() => settings.value.logLevel);
|
||||||
|
|
||||||
|
const isTelemetryEnabled = computed(
|
||||||
|
() => settings.value.telemetry && settings.value.telemetry.enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isMfaFeatureEnabled = computed(() => mfa.value.enabled);
|
||||||
|
|
||||||
|
const areTagsEnabled = computed(() =>
|
||||||
|
settings.value.workflowTagsDisabled !== undefined ? !settings.value.workflowTagsDisabled : true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isHiringBannerEnabled = computed(() => settings.value.hiringBannerEnabled);
|
||||||
|
|
||||||
|
const isTemplatesEnabled = computed(() =>
|
||||||
|
Boolean(settings.value.templates && settings.value.templates.enabled),
|
||||||
|
);
|
||||||
|
|
||||||
|
const isTemplatesEndpointReachable = computed(() => templatesEndpointHealthy.value);
|
||||||
|
|
||||||
|
const templatesHost = computed(() => settings.value.templates.host);
|
||||||
|
|
||||||
|
const pushBackend = computed(() => settings.value.pushBackend);
|
||||||
|
|
||||||
|
const isCommunityNodesFeatureEnabled = computed(() => settings.value.communityNodesEnabled);
|
||||||
|
|
||||||
|
const isNpmAvailable = computed(() => settings.value.isNpmAvailable);
|
||||||
|
|
||||||
|
const allowedModules = computed(() => settings.value.allowedModules);
|
||||||
|
|
||||||
|
const isQueueModeEnabled = computed(() => settings.value.executionMode === 'queue');
|
||||||
|
|
||||||
|
const isWorkerViewAvailable = computed(() => !!settings.value.enterprise?.workerView);
|
||||||
|
|
||||||
|
const workflowCallerPolicyDefaultOption = computed(
|
||||||
|
() => settings.value.workflowCallerPolicyDefaultOption,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isDefaultAuthenticationSaml = computed(
|
||||||
|
() => userManagement.value.authenticationMethod === UserManagementAuthenticationMethod.Saml,
|
||||||
|
);
|
||||||
|
|
||||||
|
const permanentlyDismissedBanners = computed(() => settings.value.banners?.dismissed ?? []);
|
||||||
|
|
||||||
|
const isBelowUserQuota = computed(
|
||||||
|
() =>
|
||||||
|
userManagement.value.quota === -1 ||
|
||||||
|
userManagement.value.quota > useUsersStore().allUsers.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isCommunityPlan = computed(() => planName.value.toLowerCase() === 'community');
|
||||||
|
|
||||||
|
const isDevRelease = computed(() => settings.value.releaseChannel === 'dev');
|
||||||
|
|
||||||
|
const setSettings = (newSettings: IN8nUISettings) => {
|
||||||
|
settings.value = newSettings;
|
||||||
|
userManagement.value = newSettings.userManagement;
|
||||||
|
if (userManagement.value) {
|
||||||
|
userManagement.value.showSetupOnFirstLoad =
|
||||||
|
!!settings.value.userManagement.showSetupOnFirstLoad;
|
||||||
|
}
|
||||||
|
api.value = settings.value.publicApi;
|
||||||
|
if (settings.value.sso?.ldap) {
|
||||||
|
ldap.value.loginEnabled = settings.value.sso.ldap.loginEnabled;
|
||||||
|
ldap.value.loginLabel = settings.value.sso.ldap.loginLabel;
|
||||||
|
}
|
||||||
|
if (settings.value.sso?.saml) {
|
||||||
|
saml.value.loginEnabled = settings.value.sso.saml.loginEnabled;
|
||||||
|
saml.value.loginLabel = settings.value.sso.saml.loginLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
mfa.value.enabled = settings.value.mfa?.enabled;
|
||||||
|
|
||||||
|
if (settings.value.enterprise?.showNonProdBanner) {
|
||||||
|
useUIStore().pushBannerToStack('NON_PRODUCTION_LICENSE');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.value.versionCli) {
|
||||||
|
useRootStore().setVersionCli(settings.value.versionCli);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
settings.value.authCookie.secure &&
|
||||||
|
location.protocol === 'http:' &&
|
||||||
|
!['localhost', '127.0.0.1'].includes(location.hostname)
|
||||||
|
) {
|
||||||
|
document.write(INSECURE_CONNECTION_WARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isV1BannerDismissedPermanently = (settings.value.banners?.dismissed || []).includes('V1');
|
||||||
|
if (!isV1BannerDismissedPermanently && settings.value.versionCli.startsWith('1.')) {
|
||||||
|
useUIStore().pushBannerToStack('V1');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAllowedModules = (allowedModules: IN8nUISettings['allowedModules']) => {
|
||||||
|
settings.value.allowedModules = allowedModules;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSaveDataErrorExecution = (newValue: WorkflowSettings.SaveDataExecution) => {
|
||||||
|
saveDataErrorExecution.value = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSaveDataSuccessExecution = (newValue: WorkflowSettings.SaveDataExecution) => {
|
||||||
|
saveDataSuccessExecution.value = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSaveManualExecutions = (newValue: boolean) => {
|
||||||
|
saveManualExecutions.value = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSaveDataProgressExecution = (newValue: boolean) => {
|
||||||
|
saveDataProgressExecution.value = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSettings = async () => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
const fetchedSettings = await settingsApi.getSettings(rootStore.restApiContext);
|
||||||
|
setSettings(fetchedSettings);
|
||||||
|
settings.value.communityNodesEnabled = fetchedSettings.communityNodesEnabled;
|
||||||
|
setAllowedModules(fetchedSettings.allowedModules);
|
||||||
|
setSaveDataErrorExecution(fetchedSettings.saveDataErrorExecution);
|
||||||
|
setSaveDataSuccessExecution(fetchedSettings.saveDataSuccessExecution);
|
||||||
|
setSaveDataProgressExecution(fetchedSettings.saveExecutionProgress);
|
||||||
|
setSaveManualExecutions(fetchedSettings.saveManualExecutions);
|
||||||
|
|
||||||
|
rootStore.setUrlBaseWebhook(fetchedSettings.urlBaseWebhook);
|
||||||
|
rootStore.setUrlBaseEditor(fetchedSettings.urlBaseEditor);
|
||||||
|
rootStore.setEndpointForm(fetchedSettings.endpointForm);
|
||||||
|
rootStore.setEndpointFormTest(fetchedSettings.endpointFormTest);
|
||||||
|
rootStore.setEndpointFormWaiting(fetchedSettings.endpointFormWaiting);
|
||||||
|
rootStore.setEndpointWebhook(fetchedSettings.endpointWebhook);
|
||||||
|
rootStore.setEndpointWebhookTest(fetchedSettings.endpointWebhookTest);
|
||||||
|
rootStore.setTimezone(fetchedSettings.timezone);
|
||||||
|
rootStore.setExecutionTimeout(fetchedSettings.executionTimeout);
|
||||||
|
rootStore.setMaxExecutionTimeout(fetchedSettings.maxExecutionTimeout);
|
||||||
|
rootStore.setInstanceId(fetchedSettings.instanceId);
|
||||||
|
rootStore.setOauthCallbackUrls(fetchedSettings.oauthCallbackUrls);
|
||||||
|
rootStore.setN8nMetadata(fetchedSettings.n8nMetadata || {});
|
||||||
|
rootStore.setDefaultLocale(fetchedSettings.defaultLocale);
|
||||||
|
rootStore.setIsNpmAvailable(fetchedSettings.isNpmAvailable);
|
||||||
|
rootStore.setBinaryDataMode(fetchedSettings.binaryDataMode);
|
||||||
|
useVersionsStore().setVersionNotificationSettings(fetchedSettings.versionNotifications);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialize = async () => {
|
||||||
|
if (initialized.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { showToast } = useToast();
|
||||||
|
try {
|
||||||
|
await getSettings();
|
||||||
|
|
||||||
|
ExpressionEvaluatorProxy.setEvaluator(settings.value.expressions.evaluator);
|
||||||
|
|
||||||
|
// Re-compute title since settings are now available
|
||||||
|
useTitleChange().titleReset();
|
||||||
|
|
||||||
|
initialized.value = true;
|
||||||
|
} catch (e) {
|
||||||
|
showToast({
|
||||||
|
title: i18n.baseText('startupError'),
|
||||||
|
message: i18n.baseText('startupError.message'),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopShowingSetupPage = () => {
|
||||||
|
userManagement.value.showSetupOnFirstLoad = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const disableTemplates = () => {
|
||||||
|
settings.value = {
|
||||||
|
...settings.value,
|
||||||
|
templates: {
|
||||||
|
...settings.value.templates,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
ldap: {
|
};
|
||||||
loginLabel: '',
|
|
||||||
loginEnabled: false,
|
|
||||||
},
|
|
||||||
saml: {
|
|
||||||
loginLabel: '',
|
|
||||||
loginEnabled: false,
|
|
||||||
},
|
|
||||||
mfa: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
saveDataErrorExecution: 'all',
|
|
||||||
saveDataSuccessExecution: 'all',
|
|
||||||
saveManualExecutions: false,
|
|
||||||
saveDataProgressExecution: false,
|
|
||||||
}),
|
|
||||||
getters: {
|
|
||||||
isDocker(): boolean {
|
|
||||||
return this.settings.isDocker;
|
|
||||||
},
|
|
||||||
databaseType(): 'sqlite' | 'mariadb' | 'mysqldb' | 'postgresdb' {
|
|
||||||
return this.settings.databaseType;
|
|
||||||
},
|
|
||||||
planName(): string {
|
|
||||||
return this.settings.license?.planName ?? 'Community';
|
|
||||||
},
|
|
||||||
isCommunityPlan(): boolean {
|
|
||||||
return this.planName.toLowerCase() === 'community';
|
|
||||||
},
|
|
||||||
consumerId(): string {
|
|
||||||
return this.settings.license?.consumerId ?? 'unknown';
|
|
||||||
},
|
|
||||||
binaryDataMode(): 'default' | 'filesystem' | 's3' {
|
|
||||||
return this.settings.binaryDataMode;
|
|
||||||
},
|
|
||||||
pruning(): { isEnabled: boolean; maxAge: number; maxCount: number } {
|
|
||||||
return this.settings.pruning;
|
|
||||||
},
|
|
||||||
security(): {
|
|
||||||
blockFileAccessToN8nFiles: boolean;
|
|
||||||
secureCookie: boolean;
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
blockFileAccessToN8nFiles: this.settings.security.blockFileAccessToN8nFiles,
|
|
||||||
secureCookie: this.settings.authCookie.secure,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
isEnterpriseFeatureEnabled() {
|
|
||||||
return (feature: EnterpriseEditionFeatureValue): boolean =>
|
|
||||||
Boolean(this.settings.enterprise?.[feature]);
|
|
||||||
},
|
|
||||||
|
|
||||||
versionCli(): string {
|
const submitContactInfo = async (email: string) => {
|
||||||
return this.settings.versionCli;
|
try {
|
||||||
},
|
const usersStore = useUsersStore();
|
||||||
nodeJsVersion(): string {
|
return await settingsApi.submitContactInfo(
|
||||||
return this.settings.nodeJsVersion;
|
settings.value.instanceId,
|
||||||
},
|
usersStore.currentUserId || '',
|
||||||
concurrency(): number {
|
email,
|
||||||
return this.settings.concurrency;
|
|
||||||
},
|
|
||||||
isPublicApiEnabled(): boolean {
|
|
||||||
return this.api.enabled;
|
|
||||||
},
|
|
||||||
isSwaggerUIEnabled(): boolean {
|
|
||||||
return this.api.swaggerUi.enabled;
|
|
||||||
},
|
|
||||||
isPreviewMode(): boolean {
|
|
||||||
return this.settings.previewMode;
|
|
||||||
},
|
|
||||||
publicApiLatestVersion(): number {
|
|
||||||
return this.api.latestVersion;
|
|
||||||
},
|
|
||||||
publicApiPath(): string {
|
|
||||||
return this.api.path;
|
|
||||||
},
|
|
||||||
isLdapLoginEnabled(): boolean {
|
|
||||||
return this.ldap.loginEnabled;
|
|
||||||
},
|
|
||||||
ldapLoginLabel(): string {
|
|
||||||
return this.ldap.loginLabel;
|
|
||||||
},
|
|
||||||
isSamlLoginEnabled(): boolean {
|
|
||||||
return this.saml.loginEnabled;
|
|
||||||
},
|
|
||||||
samlLoginLabel(): string {
|
|
||||||
return this.saml.loginLabel;
|
|
||||||
},
|
|
||||||
showSetupPage(): boolean {
|
|
||||||
return this.userManagement.showSetupOnFirstLoad === true;
|
|
||||||
},
|
|
||||||
deploymentType(): string {
|
|
||||||
return this.settings.deployment?.type || 'default';
|
|
||||||
},
|
|
||||||
isDesktopDeployment(): boolean {
|
|
||||||
if (!this.settings.deployment) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return this.settings.deployment?.type.startsWith('desktop_');
|
|
||||||
},
|
|
||||||
isCloudDeployment(): boolean {
|
|
||||||
return this.settings.deployment?.type === 'cloud';
|
|
||||||
},
|
|
||||||
isSmtpSetup(): boolean {
|
|
||||||
return this.userManagement.smtpSetup;
|
|
||||||
},
|
|
||||||
isPersonalizationSurveyEnabled(): boolean {
|
|
||||||
return (
|
|
||||||
this.settings.telemetry &&
|
|
||||||
this.settings.telemetry.enabled &&
|
|
||||||
this.settings.personalizationSurveyEnabled
|
|
||||||
);
|
);
|
||||||
},
|
} catch (error) {
|
||||||
telemetry(): ITelemetrySettings {
|
return;
|
||||||
return this.settings.telemetry;
|
}
|
||||||
},
|
};
|
||||||
logLevel(): LogLevel {
|
|
||||||
return this.settings.logLevel;
|
|
||||||
},
|
|
||||||
isTelemetryEnabled(): boolean {
|
|
||||||
return this.settings.telemetry && this.settings.telemetry.enabled;
|
|
||||||
},
|
|
||||||
isMfaFeatureEnabled(): boolean {
|
|
||||||
return this.settings?.mfa?.enabled;
|
|
||||||
},
|
|
||||||
areTagsEnabled(): boolean {
|
|
||||||
return this.settings.workflowTagsDisabled !== undefined
|
|
||||||
? !this.settings.workflowTagsDisabled
|
|
||||||
: true;
|
|
||||||
},
|
|
||||||
isHiringBannerEnabled(): boolean {
|
|
||||||
return this.settings.hiringBannerEnabled;
|
|
||||||
},
|
|
||||||
isTemplatesEnabled(): boolean {
|
|
||||||
return Boolean(this.settings.templates && this.settings.templates.enabled);
|
|
||||||
},
|
|
||||||
isTemplatesEndpointReachable(): boolean {
|
|
||||||
return this.templatesEndpointHealthy;
|
|
||||||
},
|
|
||||||
templatesHost(): string {
|
|
||||||
return this.settings.templates.host;
|
|
||||||
},
|
|
||||||
pushBackend(): IN8nUISettings['pushBackend'] {
|
|
||||||
return this.settings.pushBackend;
|
|
||||||
},
|
|
||||||
isCommunityNodesFeatureEnabled(): boolean {
|
|
||||||
return this.settings.communityNodesEnabled;
|
|
||||||
},
|
|
||||||
isNpmAvailable(): boolean {
|
|
||||||
return this.settings.isNpmAvailable;
|
|
||||||
},
|
|
||||||
allowedModules(): { builtIn?: string[]; external?: string[] } {
|
|
||||||
return this.settings.allowedModules;
|
|
||||||
},
|
|
||||||
isQueueModeEnabled(): boolean {
|
|
||||||
return this.settings.executionMode === 'queue';
|
|
||||||
},
|
|
||||||
isWorkerViewAvailable(): boolean {
|
|
||||||
return !!this.settings.enterprise?.workerView;
|
|
||||||
},
|
|
||||||
workflowCallerPolicyDefaultOption(): WorkflowSettings.CallerPolicy {
|
|
||||||
return this.settings.workflowCallerPolicyDefaultOption;
|
|
||||||
},
|
|
||||||
isDefaultAuthenticationSaml(): boolean {
|
|
||||||
return this.userManagement.authenticationMethod === UserManagementAuthenticationMethod.Saml;
|
|
||||||
},
|
|
||||||
permanentlyDismissedBanners(): string[] {
|
|
||||||
return this.settings.banners?.dismissed ?? [];
|
|
||||||
},
|
|
||||||
isBelowUserQuota(): boolean {
|
|
||||||
const userStore = useUsersStore();
|
|
||||||
return (
|
|
||||||
this.userManagement.quota === -1 || this.userManagement.quota > userStore.allUsers.length
|
|
||||||
);
|
|
||||||
},
|
|
||||||
isDevRelease(): boolean {
|
|
||||||
return this.settings.releaseChannel === 'dev';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
async initialize() {
|
|
||||||
if (this.initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { showToast } = useToast();
|
const testTemplatesEndpoint = async () => {
|
||||||
try {
|
const timeout = new Promise((_, reject) => setTimeout(() => reject(), 2000));
|
||||||
await this.getSettings();
|
await Promise.race([testHealthEndpoint(templatesHost.value), timeout]);
|
||||||
|
templatesEndpointHealthy.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
ExpressionEvaluatorProxy.setEvaluator(this.settings.expressions.evaluator);
|
const getApiKey = async () => {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
const { apiKey } = await publicApiApi.getApiKey(rootStore.restApiContext);
|
||||||
|
return apiKey;
|
||||||
|
};
|
||||||
|
|
||||||
// Re-compute title since settings are now available
|
const createApiKey = async () => {
|
||||||
useTitleChange().titleReset();
|
const rootStore = useRootStore();
|
||||||
|
const { apiKey } = await publicApiApi.createApiKey(rootStore.restApiContext);
|
||||||
|
return apiKey;
|
||||||
|
};
|
||||||
|
|
||||||
this.initialized = true;
|
const deleteApiKey = async () => {
|
||||||
} catch (e) {
|
const rootStore = useRootStore();
|
||||||
showToast({
|
await publicApiApi.deleteApiKey(rootStore.restApiContext);
|
||||||
title: i18n.baseText('startupError'),
|
};
|
||||||
message: i18n.baseText('startupError.message'),
|
|
||||||
type: 'error',
|
|
||||||
duration: 0,
|
|
||||||
dangerouslyUseHTMLString: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
throw e;
|
const getLdapConfig = async () => {
|
||||||
}
|
const rootStore = useRootStore();
|
||||||
},
|
return await ldapApi.getLdapConfig(rootStore.restApiContext);
|
||||||
setSettings(settings: IN8nUISettings): void {
|
};
|
||||||
this.settings = settings;
|
|
||||||
this.userManagement = settings.userManagement;
|
|
||||||
if (this.userManagement) {
|
|
||||||
this.userManagement.showSetupOnFirstLoad = !!settings.userManagement.showSetupOnFirstLoad;
|
|
||||||
}
|
|
||||||
this.api = settings.publicApi;
|
|
||||||
if (settings.sso?.ldap) {
|
|
||||||
this.ldap.loginEnabled = settings.sso.ldap.loginEnabled;
|
|
||||||
this.ldap.loginLabel = settings.sso.ldap.loginLabel;
|
|
||||||
}
|
|
||||||
if (settings.sso?.saml) {
|
|
||||||
this.saml.loginEnabled = settings.sso.saml.loginEnabled;
|
|
||||||
this.saml.loginLabel = settings.sso.saml.loginLabel;
|
|
||||||
}
|
|
||||||
if (settings.enterprise?.showNonProdBanner) {
|
|
||||||
useUIStore().pushBannerToStack('NON_PRODUCTION_LICENSE');
|
|
||||||
}
|
|
||||||
if (settings.versionCli) {
|
|
||||||
useRootStore().setVersionCli(settings.versionCli);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
const getLdapSynchronizations = async (pagination: { page: number }) => {
|
||||||
settings.authCookie.secure &&
|
const rootStore = useRootStore();
|
||||||
location.protocol === 'http:' &&
|
return await ldapApi.getLdapSynchronizations(rootStore.restApiContext, pagination);
|
||||||
!['localhost', '127.0.0.1'].includes(location.hostname)
|
};
|
||||||
) {
|
|
||||||
document.write(INSECURE_CONNECTION_WARNING);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isV1BannerDismissedPermanently = (settings.banners?.dismissed || []).includes('V1');
|
const testLdapConnection = async () => {
|
||||||
if (!isV1BannerDismissedPermanently && useRootStore().versionCli.startsWith('1.')) {
|
const rootStore = useRootStore();
|
||||||
useUIStore().pushBannerToStack('V1');
|
return await ldapApi.testLdapConnection(rootStore.restApiContext);
|
||||||
}
|
};
|
||||||
},
|
|
||||||
async getSettings(): Promise<void> {
|
|
||||||
const rootStore = useRootStore();
|
|
||||||
const settings = await getSettings(rootStore.restApiContext);
|
|
||||||
|
|
||||||
this.setSettings(settings);
|
const updateLdapConfig = async (ldapConfig: ILdapConfig) => {
|
||||||
this.settings.communityNodesEnabled = settings.communityNodesEnabled;
|
const rootStore = useRootStore();
|
||||||
this.setAllowedModules(settings.allowedModules);
|
return await ldapApi.updateLdapConfig(rootStore.restApiContext, ldapConfig);
|
||||||
this.setSaveDataErrorExecution(settings.saveDataErrorExecution);
|
};
|
||||||
this.setSaveDataSuccessExecution(settings.saveDataSuccessExecution);
|
|
||||||
this.setSaveDataProgressExecution(settings.saveExecutionProgress);
|
|
||||||
this.setSaveManualExecutions(settings.saveManualExecutions);
|
|
||||||
|
|
||||||
rootStore.setUrlBaseWebhook(settings.urlBaseWebhook);
|
const runLdapSync = async (data: IDataObject) => {
|
||||||
rootStore.setUrlBaseEditor(settings.urlBaseEditor);
|
const rootStore = useRootStore();
|
||||||
rootStore.setEndpointForm(settings.endpointForm);
|
return await ldapApi.runLdapSync(rootStore.restApiContext, data);
|
||||||
rootStore.setEndpointFormTest(settings.endpointFormTest);
|
};
|
||||||
rootStore.setEndpointFormWaiting(settings.endpointFormWaiting);
|
|
||||||
rootStore.setEndpointWebhook(settings.endpointWebhook);
|
|
||||||
rootStore.setEndpointWebhookTest(settings.endpointWebhookTest);
|
|
||||||
rootStore.setTimezone(settings.timezone);
|
|
||||||
rootStore.setExecutionTimeout(settings.executionTimeout);
|
|
||||||
rootStore.setMaxExecutionTimeout(settings.maxExecutionTimeout);
|
|
||||||
rootStore.setVersionCli(settings.versionCli);
|
|
||||||
rootStore.setInstanceId(settings.instanceId);
|
|
||||||
rootStore.setOauthCallbackUrls(settings.oauthCallbackUrls);
|
|
||||||
rootStore.setN8nMetadata(settings.n8nMetadata || {});
|
|
||||||
rootStore.setDefaultLocale(settings.defaultLocale);
|
|
||||||
rootStore.setIsNpmAvailable(settings.isNpmAvailable);
|
|
||||||
rootStore.setBinaryDataMode(settings.binaryDataMode);
|
|
||||||
|
|
||||||
useVersionsStore().setVersionNotificationSettings(settings.versionNotifications);
|
const getTimezones = async (): Promise<IDataObject> => {
|
||||||
},
|
const rootStore = useRootStore();
|
||||||
stopShowingSetupPage(): void {
|
return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones');
|
||||||
this.userManagement.showSetupOnFirstLoad = false;
|
};
|
||||||
},
|
|
||||||
disableTemplates(): void {
|
const reset = () => {
|
||||||
this.settings = {
|
settings.value = {} as IN8nUISettings;
|
||||||
...this.settings,
|
};
|
||||||
templates: {
|
|
||||||
...this.settings.templates,
|
return {
|
||||||
enabled: false,
|
settings,
|
||||||
},
|
userManagement,
|
||||||
};
|
templatesEndpointHealthy,
|
||||||
},
|
api,
|
||||||
setAllowedModules(allowedModules: { builtIn?: string[]; external?: string[] }): void {
|
ldap,
|
||||||
this.settings.allowedModules = allowedModules;
|
saml,
|
||||||
},
|
mfa,
|
||||||
async submitContactInfo(email: string): Promise<IN8nPromptResponse | undefined> {
|
isDocker,
|
||||||
try {
|
isDevRelease,
|
||||||
const usersStore = useUsersStore();
|
isEnterpriseFeatureEnabled,
|
||||||
return await submitContactInfo(
|
databaseType,
|
||||||
this.settings.instanceId,
|
planName,
|
||||||
usersStore.currentUserId || '',
|
consumerId,
|
||||||
email,
|
binaryDataMode,
|
||||||
);
|
pruning,
|
||||||
} catch (error) {
|
security,
|
||||||
return;
|
nodeJsVersion,
|
||||||
}
|
concurrency,
|
||||||
},
|
isPublicApiEnabled,
|
||||||
async testTemplatesEndpoint(): Promise<void> {
|
isSwaggerUIEnabled,
|
||||||
const timeout = new Promise((_, reject) => setTimeout(() => reject(), 2000));
|
isPreviewMode,
|
||||||
await Promise.race([testHealthEndpoint(this.templatesHost), timeout]);
|
publicApiLatestVersion,
|
||||||
this.templatesEndpointHealthy = true;
|
publicApiPath,
|
||||||
},
|
isLdapLoginEnabled,
|
||||||
async getApiKey(): Promise<string | null> {
|
ldapLoginLabel,
|
||||||
const rootStore = useRootStore();
|
isSamlLoginEnabled,
|
||||||
const { apiKey } = await getApiKey(rootStore.restApiContext);
|
showSetupPage,
|
||||||
return apiKey;
|
deploymentType,
|
||||||
},
|
isDesktopDeployment,
|
||||||
async createApiKey(): Promise<string | null> {
|
isCloudDeployment,
|
||||||
const rootStore = useRootStore();
|
isSmtpSetup,
|
||||||
const { apiKey } = await createApiKey(rootStore.restApiContext);
|
isPersonalizationSurveyEnabled,
|
||||||
return apiKey;
|
telemetry,
|
||||||
},
|
logLevel,
|
||||||
async deleteApiKey(): Promise<void> {
|
isTelemetryEnabled,
|
||||||
const rootStore = useRootStore();
|
isMfaFeatureEnabled,
|
||||||
await deleteApiKey(rootStore.restApiContext);
|
areTagsEnabled,
|
||||||
},
|
isHiringBannerEnabled,
|
||||||
async getLdapConfig() {
|
isTemplatesEnabled,
|
||||||
const rootStore = useRootStore();
|
isTemplatesEndpointReachable,
|
||||||
return await getLdapConfig(rootStore.restApiContext);
|
templatesHost,
|
||||||
},
|
pushBackend,
|
||||||
async getLdapSynchronizations(pagination: { page: number }) {
|
isCommunityNodesFeatureEnabled,
|
||||||
const rootStore = useRootStore();
|
isNpmAvailable,
|
||||||
return await getLdapSynchronizations(rootStore.restApiContext, pagination);
|
allowedModules,
|
||||||
},
|
isQueueModeEnabled,
|
||||||
async testLdapConnection() {
|
isWorkerViewAvailable,
|
||||||
const rootStore = useRootStore();
|
isDefaultAuthenticationSaml,
|
||||||
return await testLdapConnection(rootStore.restApiContext);
|
workflowCallerPolicyDefaultOption,
|
||||||
},
|
permanentlyDismissedBanners,
|
||||||
async updateLdapConfig(ldapConfig: ILdapConfig) {
|
isBelowUserQuota,
|
||||||
const rootStore = useRootStore();
|
saveDataErrorExecution,
|
||||||
return await updateLdapConfig(rootStore.restApiContext, ldapConfig);
|
saveDataSuccessExecution,
|
||||||
},
|
saveManualExecutions,
|
||||||
async runLdapSync(data: IDataObject) {
|
saveDataProgressExecution,
|
||||||
const rootStore = useRootStore();
|
isCommunityPlan,
|
||||||
return await runLdapSync(rootStore.restApiContext, data);
|
reset,
|
||||||
},
|
testLdapConnection,
|
||||||
setSaveDataErrorExecution(newValue: WorkflowSettings.SaveDataExecution) {
|
getLdapConfig,
|
||||||
this.saveDataErrorExecution = newValue;
|
getLdapSynchronizations,
|
||||||
},
|
updateLdapConfig,
|
||||||
setSaveDataSuccessExecution(newValue: WorkflowSettings.SaveDataExecution) {
|
runLdapSync,
|
||||||
this.saveDataSuccessExecution = newValue;
|
getTimezones,
|
||||||
},
|
createApiKey,
|
||||||
setSaveManualExecutions(saveManualExecutions: boolean) {
|
getApiKey,
|
||||||
this.saveManualExecutions = saveManualExecutions;
|
deleteApiKey,
|
||||||
},
|
testTemplatesEndpoint,
|
||||||
setSaveDataProgressExecution(newValue: boolean) {
|
submitContactInfo,
|
||||||
this.saveDataProgressExecution = newValue;
|
disableTemplates,
|
||||||
},
|
stopShowingSetupPage,
|
||||||
async getTimezones(): Promise<IDataObject> {
|
getSettings,
|
||||||
const rootStore = useRootStore();
|
setSettings,
|
||||||
return await makeRestApiRequest(rootStore.restApiContext, 'GET', '/options/timezones');
|
initialize,
|
||||||
},
|
};
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,8 +11,8 @@ export const useSourceControlStore = defineStore('sourceControl', () => {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const isEnterpriseSourceControlEnabled = computed(() =>
|
const isEnterpriseSourceControlEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.SourceControl),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.SourceControl],
|
||||||
);
|
);
|
||||||
|
|
||||||
const sshKeyTypes: SshKeyTypes = ['ed25519', 'rsa'];
|
const sshKeyTypes: SshKeyTypes = ['ed25519', 'rsa'];
|
||||||
|
|
|
@ -42,8 +42,8 @@ export const useSSOStore = defineStore('sso', () => {
|
||||||
void toggleLoginEnabled(value);
|
void toggleLoginEnabled(value);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const isEnterpriseSamlEnabled = computed(() =>
|
const isEnterpriseSamlEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Saml),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Saml],
|
||||||
);
|
);
|
||||||
const isDefaultAuthenticationSaml = computed(() => settingsStore.isDefaultAuthenticationSaml);
|
const isDefaultAuthenticationSaml = computed(() => settingsStore.isDefaultAuthenticationSaml);
|
||||||
const showSsoLoginButton = computed(
|
const showSsoLoginButton = computed(
|
||||||
|
|
|
@ -289,8 +289,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
},
|
},
|
||||||
async fetchTemplateById(templateId: string): Promise<ITemplatesWorkflowFull> {
|
async fetchTemplateById(templateId: string): Promise<ITemplatesWorkflowFull> {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = settingsStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getTemplateById(apiEndpoint, templateId, {
|
const response = await getTemplateById(apiEndpoint, templateId, {
|
||||||
'n8n-version': versionCli,
|
'n8n-version': versionCli,
|
||||||
});
|
});
|
||||||
|
@ -305,8 +306,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
},
|
},
|
||||||
async fetchCollectionById(collectionId: string): Promise<ITemplatesCollection | null> {
|
async fetchCollectionById(collectionId: string): Promise<ITemplatesCollection | null> {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = settingsStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getCollectionById(apiEndpoint, collectionId, {
|
const response = await getCollectionById(apiEndpoint, collectionId, {
|
||||||
'n8n-version': versionCli,
|
'n8n-version': versionCli,
|
||||||
});
|
});
|
||||||
|
@ -325,8 +327,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
return cachedCategories;
|
return cachedCategories;
|
||||||
}
|
}
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = settingsStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli });
|
const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli });
|
||||||
const categories = response.categories;
|
const categories = response.categories;
|
||||||
|
|
||||||
|
@ -340,8 +343,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = settingsStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli });
|
const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli });
|
||||||
const collections = response.collections;
|
const collections = response.collections;
|
||||||
|
|
||||||
|
@ -361,8 +365,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = settingsStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
|
|
||||||
const payload = await getWorkflows(
|
const payload = await getWorkflows(
|
||||||
apiEndpoint,
|
apiEndpoint,
|
||||||
|
@ -402,8 +407,9 @@ export const useTemplatesStore = defineStore(STORES.TEMPLATES, {
|
||||||
},
|
},
|
||||||
async getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
|
async getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
const rootStore = useRootStore();
|
||||||
const apiEndpoint: string = settingsStore.templatesHost;
|
const apiEndpoint: string = settingsStore.templatesHost;
|
||||||
const versionCli: string = settingsStore.versionCli;
|
const versionCli: string = rootStore.versionCli;
|
||||||
return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli });
|
return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ export const useWorkflowsEEStore = defineStore(STORES.WORKFLOWS_EE, {
|
||||||
const rootStore = useRootStore();
|
const rootStore = useRootStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]) {
|
||||||
await setWorkflowSharedWith(rootStore.restApiContext, payload.workflowId, {
|
await setWorkflowSharedWith(rootStore.restApiContext, payload.workflowId, {
|
||||||
shareWithIds: payload.sharedWithProjects.map((p) => p.id),
|
shareWithIds: payload.sharedWithProjects.map((p) => p.id),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { isEnterpriseFeatureEnabled } from '@/utils/rbac/checks/isEnterpriseFeatureEnabled';
|
import { isEnterpriseFeatureEnabled } from '@/utils/rbac/checks/isEnterpriseFeatureEnabled';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
import { EnterpriseEditionFeature } from '@/constants';
|
||||||
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
vi.mock('@/stores/settings.store', () => ({
|
import { defaultSettings } from '@/__tests__/defaults';
|
||||||
useSettingsStore: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Checks', () => {
|
describe('Checks', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
});
|
||||||
|
|
||||||
describe('isEnterpriseFeatureEnabled()', () => {
|
describe('isEnterpriseFeatureEnabled()', () => {
|
||||||
it('should return true if no feature is provided', () => {
|
it('should return true if no feature is provided', () => {
|
||||||
expect(isEnterpriseFeatureEnabled({})).toBe(true);
|
expect(isEnterpriseFeatureEnabled({})).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if feature is enabled', () => {
|
it('should return true if feature is enabled', () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: vi
|
...defaultSettings.enterprise,
|
||||||
.fn()
|
[EnterpriseEditionFeature.Saml]: true,
|
||||||
.mockImplementation((feature) => feature !== EnterpriseEditionFeature.Variables),
|
};
|
||||||
} as unknown as ReturnType<typeof useSettingsStore>);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
isEnterpriseFeatureEnabled({
|
isEnterpriseFeatureEnabled({
|
||||||
|
@ -27,11 +28,11 @@ describe('Checks', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if all features are enabled in allOf mode', () => {
|
it('should return true if all features are enabled in allOf mode', () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: vi
|
...defaultSettings.enterprise,
|
||||||
.fn()
|
[EnterpriseEditionFeature.Ldap]: true,
|
||||||
.mockImplementation((feature) => feature !== EnterpriseEditionFeature.Variables),
|
[EnterpriseEditionFeature.Saml]: true,
|
||||||
} as unknown as ReturnType<typeof useSettingsStore>);
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
isEnterpriseFeatureEnabled({
|
isEnterpriseFeatureEnabled({
|
||||||
|
@ -42,11 +43,11 @@ describe('Checks', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if any feature is not enabled in allOf mode', () => {
|
it('should return false if any feature is not enabled in allOf mode', () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: vi
|
...defaultSettings.enterprise,
|
||||||
.fn()
|
[EnterpriseEditionFeature.Ldap]: true,
|
||||||
.mockImplementation((feature) => feature !== EnterpriseEditionFeature.Saml),
|
[EnterpriseEditionFeature.Saml]: false,
|
||||||
} as unknown as ReturnType<typeof useSettingsStore>);
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
isEnterpriseFeatureEnabled({
|
isEnterpriseFeatureEnabled({
|
||||||
|
@ -57,11 +58,11 @@ describe('Checks', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if any feature is enabled in oneOf mode', () => {
|
it('should return true if any feature is enabled in oneOf mode', () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: vi
|
...defaultSettings.enterprise,
|
||||||
.fn()
|
[EnterpriseEditionFeature.Ldap]: true,
|
||||||
.mockImplementation((feature) => feature === EnterpriseEditionFeature.Ldap),
|
[EnterpriseEditionFeature.Saml]: false,
|
||||||
} as unknown as ReturnType<typeof useSettingsStore>);
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
isEnterpriseFeatureEnabled({
|
isEnterpriseFeatureEnabled({
|
||||||
|
@ -72,9 +73,11 @@ describe('Checks', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if no features are enabled in anyOf mode', () => {
|
it('should return false if no features are enabled in anyOf mode', () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: vi.fn().mockReturnValue(false),
|
...defaultSettings.enterprise,
|
||||||
} as unknown as ReturnType<typeof useSettingsStore>);
|
[EnterpriseEditionFeature.Ldap]: false,
|
||||||
|
[EnterpriseEditionFeature.Saml]: false,
|
||||||
|
};
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
isEnterpriseFeatureEnabled({
|
isEnterpriseFeatureEnabled({
|
||||||
|
|
|
@ -12,8 +12,8 @@ export const isEnterpriseFeatureEnabled: RBACPermissionCheck<EnterprisePermissio
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const mode = options.mode ?? 'allOf';
|
const mode = options.mode ?? 'allOf';
|
||||||
if (mode === 'allOf') {
|
if (mode === 'allOf') {
|
||||||
return features.every(settingsStore.isEnterpriseFeatureEnabled);
|
return features.every((feature) => settingsStore.isEnterpriseFeatureEnabled[feature]);
|
||||||
} else {
|
} else {
|
||||||
return features.some(settingsStore.isEnterpriseFeatureEnabled);
|
return features.some((feature) => settingsStore.isEnterpriseFeatureEnabled[feature]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,17 +3,21 @@ import { VIEWS, EnterpriseEditionFeature } from '@/constants';
|
||||||
import { enterpriseMiddleware } from '@/utils/rbac/middleware/enterprise';
|
import { enterpriseMiddleware } from '@/utils/rbac/middleware/enterprise';
|
||||||
import { type RouteLocationNormalized } from 'vue-router';
|
import { type RouteLocationNormalized } from 'vue-router';
|
||||||
import type { EnterprisePermissionOptions } from '@/types/rbac';
|
import type { EnterprisePermissionOptions } from '@/types/rbac';
|
||||||
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
vi.mock('@/stores/settings.store', () => ({
|
import { defaultSettings } from '@/__tests__/defaults';
|
||||||
useSettingsStore: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Middleware', () => {
|
describe('Middleware', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia());
|
||||||
|
});
|
||||||
|
|
||||||
describe('enterprise', () => {
|
describe('enterprise', () => {
|
||||||
it('should redirect to homepage if none of the required features are enabled in allOf mode', async () => {
|
it('should redirect to homepage if none of the required features are enabled in allOf mode', async () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: (_) => false,
|
...defaultSettings.enterprise,
|
||||||
} as ReturnType<typeof useSettingsStore>);
|
[EnterpriseEditionFeature.Ldap]: false,
|
||||||
|
[EnterpriseEditionFeature.Saml]: false,
|
||||||
|
};
|
||||||
|
|
||||||
const nextMock = vi.fn();
|
const nextMock = vi.fn();
|
||||||
const options: EnterprisePermissionOptions = {
|
const options: EnterprisePermissionOptions = {
|
||||||
|
@ -32,10 +36,11 @@ describe('Middleware', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow navigation if all of the required features are enabled in allOf mode', async () => {
|
it('should allow navigation if all of the required features are enabled in allOf mode', async () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: (feature) =>
|
...defaultSettings.enterprise,
|
||||||
[EnterpriseEditionFeature.Saml, EnterpriseEditionFeature.Ldap].includes(feature),
|
[EnterpriseEditionFeature.Ldap]: true,
|
||||||
} as ReturnType<typeof useSettingsStore>);
|
[EnterpriseEditionFeature.Saml]: true,
|
||||||
|
};
|
||||||
|
|
||||||
const nextMock = vi.fn();
|
const nextMock = vi.fn();
|
||||||
const options: EnterprisePermissionOptions = {
|
const options: EnterprisePermissionOptions = {
|
||||||
|
@ -54,9 +59,10 @@ describe('Middleware', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should redirect to homepage if none of the required features are enabled in oneOf mode', async () => {
|
it('should redirect to homepage if none of the required features are enabled in oneOf mode', async () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: (_) => false,
|
...defaultSettings.enterprise,
|
||||||
} as ReturnType<typeof useSettingsStore>);
|
[EnterpriseEditionFeature.Saml]: false,
|
||||||
|
};
|
||||||
|
|
||||||
const nextMock = vi.fn();
|
const nextMock = vi.fn();
|
||||||
const options: EnterprisePermissionOptions = {
|
const options: EnterprisePermissionOptions = {
|
||||||
|
@ -75,9 +81,11 @@ describe('Middleware', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow navigation if at least one of the required features is enabled in oneOf mode', async () => {
|
it('should allow navigation if at least one of the required features is enabled in oneOf mode', async () => {
|
||||||
vi.mocked(useSettingsStore).mockReturnValue({
|
useSettingsStore().settings.enterprise = {
|
||||||
isEnterpriseFeatureEnabled: (feature) => feature === EnterpriseEditionFeature.Saml,
|
...defaultSettings.enterprise,
|
||||||
} as ReturnType<typeof useSettingsStore>);
|
[EnterpriseEditionFeature.Ldap]: true,
|
||||||
|
[EnterpriseEditionFeature.Saml]: false,
|
||||||
|
};
|
||||||
|
|
||||||
const nextMock = vi.fn();
|
const nextMock = vi.fn();
|
||||||
const options: EnterprisePermissionOptions = {
|
const options: EnterprisePermissionOptions = {
|
||||||
|
|
|
@ -159,9 +159,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
const isVarsEnabled = useSettingsStore().isEnterpriseFeatureEnabled(
|
const isVarsEnabled =
|
||||||
EnterpriseEditionFeature.Variables,
|
useSettingsStore().isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables];
|
||||||
);
|
|
||||||
|
|
||||||
const loadPromises = [
|
const loadPromises = [
|
||||||
this.credentialsStore.fetchAllCredentials(
|
this.credentialsStore.fetchAllCredentials(
|
||||||
|
|
|
@ -210,11 +210,11 @@ async function initializeData() {
|
||||||
credentialsStore.fetchCredentialTypes(true),
|
credentialsStore.fetchCredentialTypes(true),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables)) {
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables]) {
|
||||||
promises.push(environmentsStore.fetchAllVariables());
|
promises.push(environmentsStore.fetchAllVariables());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets)) {
|
if (settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets]) {
|
||||||
promises.push(externalSecretsStore.fetchAllSecrets());
|
promises.push(externalSecretsStore.fetchAllSecrets());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -833,10 +833,10 @@ export default defineComponent({
|
||||||
const loadPromises = (() => {
|
const loadPromises = (() => {
|
||||||
if (this.settingsStore.isPreviewMode && this.isDemo) return [];
|
if (this.settingsStore.isPreviewMode && this.isDemo) return [];
|
||||||
const promises = [this.loadActiveWorkflows(), this.loadCredentialTypes()];
|
const promises = [this.loadActiveWorkflows(), this.loadCredentialTypes()];
|
||||||
if (this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables)) {
|
if (this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables]) {
|
||||||
promises.push(this.loadVariables());
|
promises.push(this.loadVariables());
|
||||||
}
|
}
|
||||||
if (this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.ExternalSecrets)) {
|
if (this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.ExternalSecrets]) {
|
||||||
promises.push(this.loadSecrets());
|
promises.push(this.loadSecrets());
|
||||||
}
|
}
|
||||||
return promises;
|
return promises;
|
||||||
|
@ -4208,7 +4208,7 @@ export default defineComponent({
|
||||||
|
|
||||||
if (
|
if (
|
||||||
nodeData.credentials &&
|
nodeData.credentials &&
|
||||||
this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)
|
this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing]
|
||||||
) {
|
) {
|
||||||
const usedCredentials = this.workflowsStore.usedCredentials;
|
const usedCredentials = this.workflowsStore.usedCredentials;
|
||||||
nodeData.credentials = Object.fromEntries(
|
nodeData.credentials = Object.fromEntries(
|
||||||
|
|
|
@ -153,7 +153,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
isLicensed(): boolean {
|
isLicensed(): boolean {
|
||||||
if (this.disableLicense) return false;
|
if (this.disableLicense) return false;
|
||||||
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.LogStreaming);
|
return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.LogStreaming];
|
||||||
},
|
},
|
||||||
canManageLogStreaming(): boolean {
|
canManageLogStreaming(): boolean {
|
||||||
return hasPermission(['rbac'], { rbac: { scope: 'logStreaming:manage' } });
|
return hasPermission(['rbac'], { rbac: { scope: 'logStreaming:manage' } });
|
||||||
|
|
|
@ -76,7 +76,7 @@ const usersListActions = computed((): IUserListAction[] => {
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
const isAdvancedPermissionsEnabled = computed((): boolean => {
|
const isAdvancedPermissionsEnabled = computed((): boolean => {
|
||||||
return settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.AdvancedPermissions);
|
return settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AdvancedPermissions];
|
||||||
});
|
});
|
||||||
|
|
||||||
const userRoles = computed((): Array<{ value: IRole; label: string; disabled?: boolean }> => {
|
const userRoles = computed((): Array<{ value: IRole; label: string; disabled?: boolean }> => {
|
||||||
|
|
|
@ -7,7 +7,9 @@ import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useRBACStore } from '@/stores/rbac.store';
|
import { useRBACStore } from '@/stores/rbac.store';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import { EnterpriseEditionFeature } from '@/constants';
|
import { EnterpriseEditionFeature, STORES } from '@/constants';
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
|
||||||
|
|
||||||
describe('VariablesView', () => {
|
describe('VariablesView', () => {
|
||||||
let server: ReturnType<typeof setupServer>;
|
let server: ReturnType<typeof setupServer>;
|
||||||
|
@ -16,7 +18,13 @@ describe('VariablesView', () => {
|
||||||
let usersStore: ReturnType<typeof useUsersStore>;
|
let usersStore: ReturnType<typeof useUsersStore>;
|
||||||
let rbacStore: ReturnType<typeof useRBACStore>;
|
let rbacStore: ReturnType<typeof useRBACStore>;
|
||||||
|
|
||||||
const renderComponent = createComponentRenderer(VariablesView);
|
const renderComponent = createComponentRenderer(VariablesView, {
|
||||||
|
pinia: createTestingPinia({
|
||||||
|
initialState: {
|
||||||
|
[STORES.SETTINGS]: SETTINGS_STORE_DEFAULT_STATE,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
server = setupServer();
|
server = setupServer();
|
||||||
|
@ -105,6 +113,7 @@ describe('VariablesView', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render variable entries', async () => {
|
it('should render variable entries', async () => {
|
||||||
|
settingsStore.settings.enterprise[EnterpriseEditionFeature.Variables] = true;
|
||||||
server.createList('variable', 3);
|
server.createList('variable', 3);
|
||||||
|
|
||||||
const wrapper = renderComponent({ pinia });
|
const wrapper = renderComponent({ pinia });
|
||||||
|
|
|
@ -41,8 +41,8 @@ const editMode = ref<Record<string, boolean>>({});
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const permissions = getVariablesPermissions(usersStore.currentUser);
|
const permissions = getVariablesPermissions(usersStore.currentUser);
|
||||||
|
|
||||||
const isFeatureEnabled = computed(() =>
|
const isFeatureEnabled = computed(
|
||||||
settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Variables),
|
() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Variables],
|
||||||
);
|
);
|
||||||
|
|
||||||
const variablesToResources = computed((): IResource[] =>
|
const variablesToResources = computed((): IResource[] =>
|
||||||
|
|
|
@ -217,7 +217,7 @@ const WorkflowsView = defineComponent({
|
||||||
return this.workflowsStore.allWorkflows;
|
return this.workflowsStore.allWorkflows;
|
||||||
},
|
},
|
||||||
isShareable(): boolean {
|
isShareable(): boolean {
|
||||||
return this.settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing);
|
return this.settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.Sharing];
|
||||||
},
|
},
|
||||||
statusFilterOptions(): Array<{ label: string; value: string | boolean }> {
|
statusFilterOptions(): Array<{ label: string; value: string | boolean }> {
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -11,8 +11,10 @@ import { useUsersStore } from '@/stores/users.store';
|
||||||
import { createUser } from '@/__tests__/data/users';
|
import { createUser } from '@/__tests__/data/users';
|
||||||
import { createProjectListItem } from '@/__tests__/data/projects';
|
import { createProjectListItem } from '@/__tests__/data/projects';
|
||||||
import { useRBACStore } from '@/stores/rbac.store';
|
import { useRBACStore } from '@/stores/rbac.store';
|
||||||
import { DELETE_USER_MODAL_KEY } from '@/constants';
|
import { DELETE_USER_MODAL_KEY, EnterpriseEditionFeature } from '@/constants';
|
||||||
import * as usersApi from '@/api/users';
|
import * as usersApi from '@/api/users';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { defaultSettings } from '@/__tests__/defaults';
|
||||||
|
|
||||||
const wrapperComponentWithModal = {
|
const wrapperComponentWithModal = {
|
||||||
components: { SettingsUsersView, ModalRoot, DeleteUserModal },
|
components: { SettingsUsersView, ModalRoot, DeleteUserModal },
|
||||||
|
@ -47,6 +49,11 @@ describe('SettingsUsersView', () => {
|
||||||
usersStore = useUsersStore();
|
usersStore = useUsersStore();
|
||||||
rbacStore = useRBACStore();
|
rbacStore = useRBACStore();
|
||||||
|
|
||||||
|
useSettingsStore().settings.enterprise = {
|
||||||
|
...defaultSettings.enterprise,
|
||||||
|
[EnterpriseEditionFeature.AdvancedExecutionFilters]: true,
|
||||||
|
};
|
||||||
|
|
||||||
vi.spyOn(rbacStore, 'hasScope').mockReturnValue(true);
|
vi.spyOn(rbacStore, 'hasScope').mockReturnValue(true);
|
||||||
vi.spyOn(usersApi, 'getUsers').mockResolvedValue(users);
|
vi.spyOn(usersApi, 'getUsers').mockResolvedValue(users);
|
||||||
vi.spyOn(usersStore, 'allUsers', 'get').mockReturnValue(users);
|
vi.spyOn(usersStore, 'allUsers', 'get').mockReturnValue(users);
|
||||||
|
|
|
@ -2596,7 +2596,7 @@ export type ExpressionEvaluatorType = 'tmpl' | 'tournament';
|
||||||
export type N8nAIProviderType = 'openai' | 'unknown';
|
export type N8nAIProviderType = 'openai' | 'unknown';
|
||||||
|
|
||||||
export interface IN8nUISettings {
|
export interface IN8nUISettings {
|
||||||
isDocker: boolean;
|
isDocker?: boolean;
|
||||||
databaseType: 'sqlite' | 'mariadb' | 'mysqldb' | 'postgresdb';
|
databaseType: 'sqlite' | 'mariadb' | 'mysqldb' | 'postgresdb';
|
||||||
endpointForm: string;
|
endpointForm: string;
|
||||||
endpointFormTest: string;
|
endpointFormTest: string;
|
||||||
|
|
Loading…
Reference in a new issue