mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
feat(cli): Enable community nodes based on npm availability (#3871)
* ⚡ Detect npm availability * 📘 Expand interfaces * ⚡ Adjust store * 🎨 Replace button with warning
This commit is contained in:
parent
620525ea85
commit
936264b3c6
|
@ -516,6 +516,7 @@ export interface IN8nUISettings {
|
||||||
missingPackages?: boolean;
|
missingPackages?: boolean;
|
||||||
executionMode: 'regular' | 'queue';
|
executionMode: 'regular' | 'queue';
|
||||||
communityNodesEnabled: boolean;
|
communityNodesEnabled: boolean;
|
||||||
|
isNpmAvailable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPersonalizationSurveyAnswers {
|
export interface IPersonalizationSurveyAnswers {
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { readFileSync, promises } from 'fs';
|
import { readFileSync, promises } from 'fs';
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
|
import { exec as callbackExec } from 'child_process';
|
||||||
import _, { cloneDeep } from 'lodash';
|
import _, { cloneDeep } from 'lodash';
|
||||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
||||||
import {
|
import {
|
||||||
|
@ -86,6 +87,7 @@ import jwks from 'jwks-rsa';
|
||||||
import timezones from 'google-timezones-json';
|
import timezones from 'google-timezones-json';
|
||||||
import parseUrl from 'parseurl';
|
import parseUrl from 'parseurl';
|
||||||
import promClient, { Registry } from 'prom-client';
|
import promClient, { Registry } from 'prom-client';
|
||||||
|
import { promisify } from 'util';
|
||||||
import * as Queue from './Queue';
|
import * as Queue from './Queue';
|
||||||
import {
|
import {
|
||||||
ActiveExecutions,
|
ActiveExecutions,
|
||||||
|
@ -167,6 +169,8 @@ import { loadPublicApiVersions } from './PublicApi';
|
||||||
|
|
||||||
require('body-parser-xml')(bodyParser);
|
require('body-parser-xml')(bodyParser);
|
||||||
|
|
||||||
|
const exec = promisify(callbackExec);
|
||||||
|
|
||||||
export const externalHooks: IExternalHooksClass = ExternalHooks();
|
export const externalHooks: IExternalHooksClass = ExternalHooks();
|
||||||
|
|
||||||
class App {
|
class App {
|
||||||
|
@ -330,6 +334,7 @@ class App {
|
||||||
onboardingCallPromptEnabled: config.getEnv('onboardingCallPrompt.enabled'),
|
onboardingCallPromptEnabled: config.getEnv('onboardingCallPrompt.enabled'),
|
||||||
executionMode: config.getEnv('executions.mode'),
|
executionMode: config.getEnv('executions.mode'),
|
||||||
communityNodesEnabled: config.getEnv('nodes.communityPackages.enabled'),
|
communityNodesEnabled: config.getEnv('nodes.communityPackages.enabled'),
|
||||||
|
isNpmAvailable: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,6 +379,10 @@ class App {
|
||||||
promClient.collectDefaultMetrics({ register });
|
promClient.collectDefaultMetrics({ register });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.frontendSettings.isNpmAvailable = await exec('npm --version')
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
|
||||||
this.versions = await GenericHelpers.getVersions();
|
this.versions = await GenericHelpers.getVersions();
|
||||||
this.frontendSettings.versionCli = this.versions.cli;
|
this.frontendSettings.versionCli = this.versions.cli;
|
||||||
|
|
||||||
|
|
|
@ -719,6 +719,7 @@ export interface IN8nUISettings {
|
||||||
};
|
};
|
||||||
executionMode: string;
|
executionMode: string;
|
||||||
communityNodesEnabled: boolean;
|
communityNodesEnabled: boolean;
|
||||||
|
isNpmAvailable: boolean;
|
||||||
publicApi: {
|
publicApi: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
latestVersion: number;
|
latestVersion: number;
|
||||||
|
@ -881,6 +882,7 @@ export interface IRootState {
|
||||||
sidebarMenuItems: IMenuItem[];
|
sidebarMenuItems: IMenuItem[];
|
||||||
instanceId: string;
|
instanceId: string;
|
||||||
nodeMetadata: {[nodeName: string]: INodeMetadata};
|
nodeMetadata: {[nodeName: string]: INodeMetadata};
|
||||||
|
isNpmAvailable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICommunityPackageMap {
|
export interface ICommunityPackageMap {
|
||||||
|
|
|
@ -63,6 +63,7 @@ export const NPM_PACKAGE_DOCS_BASE_URL = `https://www.npmjs.com/package/`;
|
||||||
export const NPM_KEYWORD_SEARCH_URL = `https://www.npmjs.com/search?q=keywords%3An8n-community-node-package`;
|
export const NPM_KEYWORD_SEARCH_URL = `https://www.npmjs.com/search?q=keywords%3An8n-community-node-package`;
|
||||||
export const N8N_QUEUE_MODE_DOCS_URL = `https://docs.n8n.io/hosting/scaling/queue-mode/`;
|
export const N8N_QUEUE_MODE_DOCS_URL = `https://docs.n8n.io/hosting/scaling/queue-mode/`;
|
||||||
export const COMMUNITY_NODES_INSTALLATION_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/installation/`;
|
export const COMMUNITY_NODES_INSTALLATION_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/installation/`;
|
||||||
|
export const COMMUNITY_NODES_NPM_INSTALLATION_URL = 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm';
|
||||||
export const COMMUNITY_NODES_RISKS_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/risks/`;
|
export const COMMUNITY_NODES_RISKS_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/risks/`;
|
||||||
export const COMMUNITY_NODES_BLOCKLIST_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/blocklist/`;
|
export const COMMUNITY_NODES_BLOCKLIST_DOCS_URL = `https://docs.n8n.io/integrations/community-nodes/blocklist/`;
|
||||||
export const CUSTOM_NODES_DOCS_URL = `https://docs.n8n.io/integrations/creating-nodes/code/create-n8n-nodes-module/`;
|
export const CUSTOM_NODES_DOCS_URL = `https://docs.n8n.io/integrations/creating-nodes/code/create-n8n-nodes-module/`;
|
||||||
|
|
|
@ -90,6 +90,9 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
isCommunityNodesFeatureEnabled: (state): boolean => {
|
isCommunityNodesFeatureEnabled: (state): boolean => {
|
||||||
return state.settings.communityNodesEnabled;
|
return state.settings.communityNodesEnabled;
|
||||||
},
|
},
|
||||||
|
isNpmAvailable: (state): boolean => {
|
||||||
|
return state.settings.isNpmAvailable;
|
||||||
|
},
|
||||||
isQueueModeEnabled: (state): boolean => {
|
isQueueModeEnabled: (state): boolean => {
|
||||||
return state.settings.executionMode === 'queue';
|
return state.settings.executionMode === 'queue';
|
||||||
},
|
},
|
||||||
|
@ -138,6 +141,7 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
context.commit('setOauthCallbackUrls', settings.oauthCallbackUrls, {root: true});
|
context.commit('setOauthCallbackUrls', settings.oauthCallbackUrls, {root: true});
|
||||||
context.commit('setN8nMetadata', settings.n8nMetadata || {}, {root: true});
|
context.commit('setN8nMetadata', settings.n8nMetadata || {}, {root: true});
|
||||||
context.commit('setDefaultLocale', settings.defaultLocale, {root: true});
|
context.commit('setDefaultLocale', settings.defaultLocale, {root: true});
|
||||||
|
context.commit('setIsNpmAvailable', settings.isNpmAvailable, {root: true});
|
||||||
context.commit('versions/setVersionNotificationSettings', settings.versionNotifications, {root: true});
|
context.commit('versions/setVersionNotificationSettings', settings.versionNotifications, {root: true});
|
||||||
context.commit('setCommunityNodesFeatureEnabled', settings.communityNodesEnabled === true);
|
context.commit('setCommunityNodesFeatureEnabled', settings.communityNodesEnabled === true);
|
||||||
},
|
},
|
||||||
|
|
|
@ -725,6 +725,7 @@
|
||||||
"settings.communityNodes.empty.description.no-packages": "Install node packages contributed by our community. <br /><a href=\"{docURL}\" target=\"_blank\" title=\"Read the n8n docs\">More info</a>",
|
"settings.communityNodes.empty.description.no-packages": "Install node packages contributed by our community. <br /><a href=\"{docURL}\" target=\"_blank\" title=\"Read the n8n docs\">More info</a>",
|
||||||
"settings.communityNodes.empty.installPackageLabel": "Install a community node",
|
"settings.communityNodes.empty.installPackageLabel": "Install a community node",
|
||||||
"settings.communityNodes.queueMode.warning": "You need to install community nodes manually because your instance is running in queue mode. <a href=\"{docURL}\" target=\"_blank\" title=\"Read the n8n docs\">More info</a>",
|
"settings.communityNodes.queueMode.warning": "You need to install community nodes manually because your instance is running in queue mode. <a href=\"{docURL}\" target=\"_blank\" title=\"Read the n8n docs\">More info</a>",
|
||||||
|
"settings.communityNodes.npmUnavailable.warning": "To use this feature, please <a href=\"{npmUrl}\" target=\"_blank\" title=\"How to install npm\">install npm</a> and restart n8n.",
|
||||||
"settings.communityNodes.packageNodes.label": "{count} node | {count} nodes",
|
"settings.communityNodes.packageNodes.label": "{count} node | {count} nodes",
|
||||||
"settings.communityNodes.updateAvailable.tooltip": "A newer version is available",
|
"settings.communityNodes.updateAvailable.tooltip": "A newer version is available",
|
||||||
"settings.communityNodes.viewDocsAction.label": "Documentation",
|
"settings.communityNodes.viewDocsAction.label": "Documentation",
|
||||||
|
|
|
@ -84,6 +84,7 @@ const state: IRootState = {
|
||||||
selectedNodes: [],
|
selectedNodes: [],
|
||||||
sessionId: Math.random().toString(36).substring(2, 15),
|
sessionId: Math.random().toString(36).substring(2, 15),
|
||||||
urlBaseWebhook: 'http://localhost:5678/',
|
urlBaseWebhook: 'http://localhost:5678/',
|
||||||
|
isNpmAvailable: false,
|
||||||
workflow: {
|
workflow: {
|
||||||
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
id: PLACEHOLDER_EMPTY_WORKFLOW_ID,
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -600,6 +601,9 @@ export const store = new Vuex.Store({
|
||||||
setDefaultLocale(state, locale: string) {
|
setDefaultLocale(state, locale: string) {
|
||||||
Vue.set(state, 'defaultLocale', locale);
|
Vue.set(state, 'defaultLocale', locale);
|
||||||
},
|
},
|
||||||
|
setIsNpmAvailable(state, isNpmAvailable: boolean) {
|
||||||
|
Vue.set(state, 'isNpmAvailable', isNpmAvailable);
|
||||||
|
},
|
||||||
setActiveNode(state, nodeName: string) {
|
setActiveNode(state, nodeName: string) {
|
||||||
state.activeNode = nodeName;
|
state.activeNode = nodeName;
|
||||||
},
|
},
|
||||||
|
|
|
@ -36,7 +36,11 @@
|
||||||
<n8n-action-box
|
<n8n-action-box
|
||||||
:heading="$locale.baseText('settings.communityNodes.empty.title')"
|
:heading="$locale.baseText('settings.communityNodes.empty.title')"
|
||||||
:description="getEmptyStateDescription"
|
:description="getEmptyStateDescription"
|
||||||
:buttonText="$locale.baseText('settings.communityNodes.empty.installPackageLabel')"
|
:buttonText="
|
||||||
|
isNpmAvailable
|
||||||
|
? $locale.baseText('settings.communityNodes.empty.installPackageLabel')
|
||||||
|
: ''
|
||||||
|
"
|
||||||
:calloutText="actionBoxConfig.calloutText"
|
:calloutText="actionBoxConfig.calloutText"
|
||||||
:calloutTheme="actionBoxConfig.calloutTheme"
|
:calloutTheme="actionBoxConfig.calloutTheme"
|
||||||
@click="openInstallModal"
|
@click="openInstallModal"
|
||||||
|
@ -63,7 +67,11 @@ import SettingsView from './SettingsView.vue';
|
||||||
import CommunityPackageCard from '../components/CommunityPackageCard.vue';
|
import CommunityPackageCard from '../components/CommunityPackageCard.vue';
|
||||||
import { showMessage } from '@/components/mixins/showMessage';
|
import { showMessage } from '@/components/mixins/showMessage';
|
||||||
import mixins from 'vue-typed-mixins';
|
import mixins from 'vue-typed-mixins';
|
||||||
import { COMMUNITY_PACKAGE_INSTALL_MODAL_KEY, COMMUNITY_NODES_INSTALLATION_DOCS_URL } from '../constants';
|
import {
|
||||||
|
COMMUNITY_PACKAGE_INSTALL_MODAL_KEY,
|
||||||
|
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||||
|
COMMUNITY_NODES_NPM_INSTALLATION_URL,
|
||||||
|
} from '../constants';
|
||||||
import { PublicInstalledPackage } from 'n8n-workflow';
|
import { PublicInstalledPackage } from 'n8n-workflow';
|
||||||
|
|
||||||
const PACKAGE_COUNT_THRESHOLD = 31;
|
const PACKAGE_COUNT_THRESHOLD = 31;
|
||||||
|
@ -123,7 +131,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('settings', ['isQueueModeEnabled']),
|
...mapGetters('settings', ['isNpmAvailable', 'isQueueModeEnabled']),
|
||||||
...mapGetters('communityNodes', ['getInstalledPackages']),
|
...mapGetters('communityNodes', ['getInstalledPackages']),
|
||||||
getEmptyStateDescription() {
|
getEmptyStateDescription() {
|
||||||
const packageCount = this.$store.getters['communityNodes/availablePackageCount'];
|
const packageCount = this.$store.getters['communityNodes/availablePackageCount'];
|
||||||
|
@ -141,13 +149,29 @@ export default mixins(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
actionBoxConfig() {
|
actionBoxConfig() {
|
||||||
return this.isQueueModeEnabled ? {
|
if (!this.isNpmAvailable) {
|
||||||
calloutText: this.$locale.baseText('settings.communityNodes.queueMode.warning', {
|
return {
|
||||||
interpolate: { docURL: COMMUNITY_NODES_INSTALLATION_DOCS_URL },
|
calloutText: this.$locale.baseText(
|
||||||
}),
|
'settings.communityNodes.npmUnavailable.warning',
|
||||||
calloutTheme: 'warning',
|
{ interpolate: { npmUrl: COMMUNITY_NODES_NPM_INSTALLATION_URL } },
|
||||||
hideButton: true,
|
),
|
||||||
} : {
|
calloutTheme: 'warning',
|
||||||
|
hideButton: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isQueueModeEnabled) {
|
||||||
|
return {
|
||||||
|
calloutText: this.$locale.baseText(
|
||||||
|
'settings.communityNodes.queueMode.warning',
|
||||||
|
{ interpolate: { docURL: COMMUNITY_NODES_INSTALLATION_DOCS_URL } },
|
||||||
|
),
|
||||||
|
calloutTheme: 'warning',
|
||||||
|
hideButton: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
calloutText: '',
|
calloutText: '',
|
||||||
calloutTheme: '',
|
calloutTheme: '',
|
||||||
hideButton: false,
|
hideButton: false,
|
||||||
|
|
Loading…
Reference in a new issue