mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-26 20:02:26 -08:00
feat(core, editor): introduce workflow caller policy (#4368)
* ✨ Create env `N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION` * 👕 Adjust BE settings interface * 👕 Adjust FE settings interface * ⚡ Send policy along with settings * ⚡ Enforce policy * ✨ Create `SubworkflowOperationError` * ⚡ Add policy to Vuex store * ⚡ Add setting to FE * ⚡ Trim caller IDs on BE * ⚡ Hide new UI behind `isWorkflowSharingEnabled` * ✏️ Copy updates * 👕 Fix lint
This commit is contained in:
parent
dd3c59677b
commit
e8935de3b2
|
@ -185,6 +185,12 @@ export const schema = {
|
||||||
default: false,
|
default: false,
|
||||||
env: 'N8N_ONBOARDING_FLOW_DISABLED',
|
env: 'N8N_ONBOARDING_FLOW_DISABLED',
|
||||||
},
|
},
|
||||||
|
callerPolicyDefaultOption: {
|
||||||
|
doc: 'Default option for which workflows may call the current workflow',
|
||||||
|
format: ['any', 'none', 'workflowsFromAList'] as const,
|
||||||
|
default: 'any',
|
||||||
|
env: 'N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
executions: {
|
executions: {
|
||||||
|
|
|
@ -485,6 +485,7 @@ export interface IN8nUISettings {
|
||||||
saveManualExecutions: boolean;
|
saveManualExecutions: boolean;
|
||||||
executionTimeout: number;
|
executionTimeout: number;
|
||||||
maxExecutionTimeout: number;
|
maxExecutionTimeout: number;
|
||||||
|
workflowCallerPolicyDefaultOption: 'any' | 'none' | 'workflowsFromAList';
|
||||||
oauthCallbackUrls: {
|
oauthCallbackUrls: {
|
||||||
oauth1: string;
|
oauth1: string;
|
||||||
oauth2: string;
|
oauth2: string;
|
||||||
|
|
|
@ -284,6 +284,7 @@ class App {
|
||||||
saveManualExecutions: this.saveManualExecutions,
|
saveManualExecutions: this.saveManualExecutions,
|
||||||
executionTimeout: this.executionTimeout,
|
executionTimeout: this.executionTimeout,
|
||||||
maxExecutionTimeout: this.maxExecutionTimeout,
|
maxExecutionTimeout: this.maxExecutionTimeout,
|
||||||
|
workflowCallerPolicyDefaultOption: config.getEnv('workflows.callerPolicyDefaultOption'),
|
||||||
timezone: this.timezone,
|
timezone: this.timezone,
|
||||||
urlBaseWebhook,
|
urlBaseWebhook,
|
||||||
urlBaseEditor: instanceBaseUrl,
|
urlBaseEditor: instanceBaseUrl,
|
||||||
|
|
|
@ -747,9 +747,38 @@ export async function getRunData(
|
||||||
workflowData: IWorkflowBase,
|
workflowData: IWorkflowBase,
|
||||||
userId: string,
|
userId: string,
|
||||||
inputData?: INodeExecutionData[],
|
inputData?: INodeExecutionData[],
|
||||||
|
parentWorkflowId?: string,
|
||||||
): Promise<IWorkflowExecutionDataProcess> {
|
): Promise<IWorkflowExecutionDataProcess> {
|
||||||
const mode = 'integrated';
|
const mode = 'integrated';
|
||||||
|
|
||||||
|
const policy =
|
||||||
|
workflowData.settings?.callerPolicy ?? config.getEnv('workflows.callerPolicyDefaultOption');
|
||||||
|
|
||||||
|
if (policy === 'none') {
|
||||||
|
throw new SubworkflowOperationError(
|
||||||
|
`Target workflow ID ${workflowData.id} may not be called by other workflows.`,
|
||||||
|
'Please update the settings of the target workflow or ask its owner to do so.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
policy === 'workflowsFromAList' &&
|
||||||
|
typeof workflowData.settings?.callerIds === 'string' &&
|
||||||
|
parentWorkflowId !== undefined
|
||||||
|
) {
|
||||||
|
const allowedCallerIds = workflowData.settings.callerIds
|
||||||
|
.split(',')
|
||||||
|
.map((id) => id.trim())
|
||||||
|
.filter((id) => id !== '');
|
||||||
|
|
||||||
|
if (!allowedCallerIds.includes(parentWorkflowId)) {
|
||||||
|
throw new SubworkflowOperationError(
|
||||||
|
`Target workflow ID ${workflowData.id} may only be called by a list of workflows, which does not include current workflow ID ${parentWorkflowId}.`,
|
||||||
|
'Please update the settings of the target workflow or ask its owner to do so.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const startingNode = findSubworkflowStart(workflowData.nodes);
|
const startingNode = findSubworkflowStart(workflowData.nodes);
|
||||||
|
|
||||||
// Always start with empty data if no inputData got supplied
|
// Always start with empty data if no inputData got supplied
|
||||||
|
|
|
@ -291,6 +291,7 @@ export class WorkflowRunnerProcess {
|
||||||
workflowData,
|
workflowData,
|
||||||
additionalData.userId,
|
additionalData.userId,
|
||||||
options?.inputData,
|
options?.inputData,
|
||||||
|
options?.parentWorkflowId,
|
||||||
);
|
);
|
||||||
await sendToParentProcess('startExecution', { runData });
|
await sendToParentProcess('startExecution', { runData });
|
||||||
const executionId: string = await new Promise((resolve) => {
|
const executionId: string = await new Promise((resolve) => {
|
||||||
|
|
|
@ -719,12 +719,15 @@ export interface ITemplatesCategory {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkflowCallerPolicyDefaultOption = 'any' | 'none' | 'workflowsFromAList';
|
||||||
|
|
||||||
export interface IN8nUISettings {
|
export interface IN8nUISettings {
|
||||||
endpointWebhook: string;
|
endpointWebhook: string;
|
||||||
endpointWebhookTest: string;
|
endpointWebhookTest: string;
|
||||||
saveDataErrorExecution: string;
|
saveDataErrorExecution: string;
|
||||||
saveDataSuccessExecution: string;
|
saveDataSuccessExecution: string;
|
||||||
saveManualExecutions: boolean;
|
saveManualExecutions: boolean;
|
||||||
|
workflowCallerPolicyDefaultOption: WorkflowCallerPolicyDefaultOption;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
executionTimeout: number;
|
executionTimeout: number;
|
||||||
maxExecutionTimeout: number;
|
maxExecutionTimeout: number;
|
||||||
|
@ -768,6 +771,7 @@ export interface IN8nUISettings {
|
||||||
deployment?: {
|
deployment?: {
|
||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
|
isWorkflowSharingEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||||
|
@ -777,6 +781,8 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
|
||||||
saveManualExecutions?: boolean;
|
saveManualExecutions?: boolean;
|
||||||
timezone?: string;
|
timezone?: string;
|
||||||
executionTimeout?: number;
|
executionTimeout?: number;
|
||||||
|
callerIds?: string;
|
||||||
|
callerPolicy?: WorkflowCallerPolicyDefaultOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITimeoutHMS {
|
export interface ITimeoutHMS {
|
||||||
|
|
|
@ -123,6 +123,45 @@
|
||||||
</n8n-select>
|
</n8n-select>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
<div v-if="isWorkflowSharingEnabled">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="10" class="setting-name">
|
||||||
|
{{ $locale.baseText('workflowSettings.callerPolicy') + ":" }}
|
||||||
|
<n8n-tooltip class="setting-info" placement="top" >
|
||||||
|
<div slot="content" v-text="helpTexts.workflowCallerPolicy"></div>
|
||||||
|
<font-awesome-icon icon="question-circle" />
|
||||||
|
</n8n-tooltip>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<el-col :span="14" class="ignore-key-press">
|
||||||
|
<n8n-select v-model="workflowSettings.callerPolicy" :placeholder="$locale.baseText('workflowSettings.selectOption')" size="medium" filterable :limit-popper-width="true">
|
||||||
|
<n8n-option
|
||||||
|
v-for="option of workflowCallerPolicyOptions"
|
||||||
|
:key="option.key"
|
||||||
|
:label="option.value"
|
||||||
|
:value="option.key">
|
||||||
|
</n8n-option>
|
||||||
|
</n8n-select>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row v-if="workflowSettings.callerPolicy === 'workflowsFromAList'">
|
||||||
|
<el-col :span="10" class="setting-name">
|
||||||
|
{{ $locale.baseText('workflowSettings.callerIds') + ":" }}
|
||||||
|
<n8n-tooltip class="setting-info" placement="top" >
|
||||||
|
<div slot="content" v-text="helpTexts.workflowCallerIds"></div>
|
||||||
|
<font-awesome-icon icon="question-circle" />
|
||||||
|
</n8n-tooltip>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="14">
|
||||||
|
<n8n-input
|
||||||
|
type="text"
|
||||||
|
size="medium"
|
||||||
|
v-model="workflowSettings.callerIds"
|
||||||
|
@input="onCallerIdsInput"
|
||||||
|
/>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="10" class="setting-name">
|
<el-col :span="10" class="setting-name">
|
||||||
{{ $locale.baseText('workflowSettings.timeoutWorkflow') + ":" }}
|
{{ $locale.baseText('workflowSettings.timeoutWorkflow') + ":" }}
|
||||||
|
@ -216,6 +255,8 @@ export default mixins(
|
||||||
saveManualExecutions: this.$locale.baseText('workflowSettings.helpTexts.saveManualExecutions'),
|
saveManualExecutions: this.$locale.baseText('workflowSettings.helpTexts.saveManualExecutions'),
|
||||||
executionTimeoutToggle: this.$locale.baseText('workflowSettings.helpTexts.executionTimeoutToggle'),
|
executionTimeoutToggle: this.$locale.baseText('workflowSettings.helpTexts.executionTimeoutToggle'),
|
||||||
executionTimeout: this.$locale.baseText('workflowSettings.helpTexts.executionTimeout'),
|
executionTimeout: this.$locale.baseText('workflowSettings.helpTexts.executionTimeout'),
|
||||||
|
workflowCallerPolicy: this.$locale.baseText('workflowSettings.helpTexts.workflowCallerPolicy'),
|
||||||
|
workflowCallerIds: this.$locale.baseText('workflowSettings.helpTexts.workflowCallerIds'),
|
||||||
},
|
},
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
timezone: 'America/New_York',
|
timezone: 'America/New_York',
|
||||||
|
@ -223,7 +264,9 @@ export default mixins(
|
||||||
saveDataSuccessExecution: 'all',
|
saveDataSuccessExecution: 'all',
|
||||||
saveExecutionProgress: false,
|
saveExecutionProgress: false,
|
||||||
saveManualExecutions: false,
|
saveManualExecutions: false,
|
||||||
|
workflowCallerPolicy: '',
|
||||||
},
|
},
|
||||||
|
workflowCallerPolicyOptions: [] as Array<{ key: string, value: string }>,
|
||||||
saveDataErrorExecutionOptions: [] as Array<{ key: string, value: string }>,
|
saveDataErrorExecutionOptions: [] as Array<{ key: string, value: string }>,
|
||||||
saveDataSuccessExecutionOptions: [] as Array<{ key: string, value: string }>,
|
saveDataSuccessExecutionOptions: [] as Array<{ key: string, value: string }>,
|
||||||
saveExecutionProgressOptions: [] as Array<{ key: string | boolean, value: string }>,
|
saveExecutionProgressOptions: [] as Array<{ key: string | boolean, value: string }>,
|
||||||
|
@ -241,6 +284,9 @@ export default mixins(
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['workflowName', 'workflowId']),
|
...mapGetters(['workflowName', 'workflowId']),
|
||||||
|
isWorkflowSharingEnabled(): boolean {
|
||||||
|
return this.$store.getters['settings/isWorkflowSharingEnabled'];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted () {
|
async mounted () {
|
||||||
|
@ -259,6 +305,7 @@ export default mixins(
|
||||||
this.defaultValues.saveDataSuccessExecution = this.$store.getters.saveDataSuccessExecution;
|
this.defaultValues.saveDataSuccessExecution = this.$store.getters.saveDataSuccessExecution;
|
||||||
this.defaultValues.saveManualExecutions = this.$store.getters.saveManualExecutions;
|
this.defaultValues.saveManualExecutions = this.$store.getters.saveManualExecutions;
|
||||||
this.defaultValues.timezone = this.$store.getters.timezone;
|
this.defaultValues.timezone = this.$store.getters.timezone;
|
||||||
|
this.defaultValues.workflowCallerPolicy = this.$store.getters['settings/workflowCallerPolicyDefaultOption'];
|
||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
@ -268,6 +315,7 @@ export default mixins(
|
||||||
promises.push(this.loadSaveExecutionProgressOptions());
|
promises.push(this.loadSaveExecutionProgressOptions());
|
||||||
promises.push(this.loadSaveManualOptions());
|
promises.push(this.loadSaveManualOptions());
|
||||||
promises.push(this.loadTimezones());
|
promises.push(this.loadTimezones());
|
||||||
|
promises.push(this.loadWorkflowCallerPolicyOptions());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -292,6 +340,9 @@ export default mixins(
|
||||||
if (workflowSettings.saveManualExecutions === undefined) {
|
if (workflowSettings.saveManualExecutions === undefined) {
|
||||||
workflowSettings.saveManualExecutions = 'DEFAULT';
|
workflowSettings.saveManualExecutions = 'DEFAULT';
|
||||||
}
|
}
|
||||||
|
if (workflowSettings.callerPolicy === undefined) {
|
||||||
|
workflowSettings.callerPolicy = this.defaultValues.workflowCallerPolicy;
|
||||||
|
}
|
||||||
if (workflowSettings.executionTimeout === undefined) {
|
if (workflowSettings.executionTimeout === undefined) {
|
||||||
workflowSettings.executionTimeout = this.$store.getters.executionTimeout;
|
workflowSettings.executionTimeout = this.$store.getters.executionTimeout;
|
||||||
}
|
}
|
||||||
|
@ -307,6 +358,11 @@ export default mixins(
|
||||||
this.$telemetry.track('User opened workflow settings', { workflow_id: this.$store.getters.workflowId });
|
this.$telemetry.track('User opened workflow settings', { workflow_id: this.$store.getters.workflowId });
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onCallerIdsInput(str: string) {
|
||||||
|
this.workflowSettings.callerIds = /^[0-9,\s]+$/.test(str)
|
||||||
|
? str
|
||||||
|
: str.replace(/[^0-9,\s]/g, '');
|
||||||
|
},
|
||||||
closeDialog () {
|
closeDialog () {
|
||||||
this.modalBus.$emit('close');
|
this.modalBus.$emit('close');
|
||||||
this.$externalHooks().run('workflowSettings.dialogVisibleChanged', { dialogVisible: false });
|
this.$externalHooks().run('workflowSettings.dialogVisibleChanged', { dialogVisible: false });
|
||||||
|
@ -319,6 +375,22 @@ export default mixins(
|
||||||
[key]: time,
|
[key]: time,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
async loadWorkflowCallerPolicyOptions () {
|
||||||
|
this.workflowCallerPolicyOptions = [
|
||||||
|
{
|
||||||
|
key: 'any',
|
||||||
|
value: this.$locale.baseText('workflowSettings.callerPolicy.options.any'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'none',
|
||||||
|
value: this.$locale.baseText('workflowSettings.callerPolicy.options.none'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'workflowsFromAList',
|
||||||
|
value: this.$locale.baseText('workflowSettings.callerPolicy.options.workflowsFromAList'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
async loadSaveDataErrorExecutionOptions () {
|
async loadSaveDataErrorExecutionOptions () {
|
||||||
this.saveDataErrorExecutionOptions.length = 0;
|
this.saveDataErrorExecutionOptions.length = 0;
|
||||||
this.saveDataErrorExecutionOptions.push.apply( // eslint-disable-line no-useless-call
|
this.saveDataErrorExecutionOptions.push.apply( // eslint-disable-line no-useless-call
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
IN8nValueSurveyData,
|
IN8nValueSurveyData,
|
||||||
IRootState,
|
IRootState,
|
||||||
ISettingsState,
|
ISettingsState,
|
||||||
|
WorkflowCallerPolicyDefaultOption,
|
||||||
} from '../Interface';
|
} from '../Interface';
|
||||||
import { getPromptsData, submitValueSurvey, submitContactInfo, getSettings } from '../api/settings';
|
import { getPromptsData, submitValueSurvey, submitContactInfo, getSettings } from '../api/settings';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
@ -108,6 +109,12 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
isQueueModeEnabled: (state): boolean => {
|
isQueueModeEnabled: (state): boolean => {
|
||||||
return state.settings.executionMode === 'queue';
|
return state.settings.executionMode === 'queue';
|
||||||
},
|
},
|
||||||
|
workflowCallerPolicyDefaultOption: (state): WorkflowCallerPolicyDefaultOption => {
|
||||||
|
return state.settings.workflowCallerPolicyDefaultOption;
|
||||||
|
},
|
||||||
|
isWorkflowSharingEnabled: (state): boolean => {
|
||||||
|
return state.settings.isWorkflowSharingEnabled;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setSettings(state: ISettingsState, settings: IN8nUISettings) {
|
setSettings(state: ISettingsState, settings: IN8nUISettings) {
|
||||||
|
@ -132,6 +139,12 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
setCommunityNodesFeatureEnabled(state: ISettingsState, isEnabled: boolean) {
|
setCommunityNodesFeatureEnabled(state: ISettingsState, isEnabled: boolean) {
|
||||||
state.settings.communityNodesEnabled = isEnabled;
|
state.settings.communityNodesEnabled = isEnabled;
|
||||||
},
|
},
|
||||||
|
setIsWorkflowSharingEnabled(state: ISettingsState, enabled: boolean) {
|
||||||
|
state.settings.isWorkflowSharingEnabled = enabled;
|
||||||
|
},
|
||||||
|
setWorkflowCallerPolicyDefaultOption(state: ISettingsState, defaultOption: WorkflowCallerPolicyDefaultOption) {
|
||||||
|
state.settings.workflowCallerPolicyDefaultOption = defaultOption;
|
||||||
|
},
|
||||||
setAllowedModules(state, allowedModules: { builtIn?: string, external?: string }) {
|
setAllowedModules(state, allowedModules: { builtIn?: string, external?: string }) {
|
||||||
state.settings.allowedModules = {
|
state.settings.allowedModules = {
|
||||||
...(allowedModules.builtIn && { builtIn: allowedModules.builtIn.split(',') }),
|
...(allowedModules.builtIn && { builtIn: allowedModules.builtIn.split(',') }),
|
||||||
|
@ -164,6 +177,8 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
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);
|
||||||
context.commit('settings/setAllowedModules', settings.allowedModules, {root: true});
|
context.commit('settings/setAllowedModules', settings.allowedModules, {root: true});
|
||||||
|
context.commit('settings/setWorkflowCallerPolicyDefaultOption', settings.workflowCallerPolicyDefaultOption, {root: true});
|
||||||
|
context.commit('settings/setIsWorkflowSharingEnabled', settings.enterprise.workflowSharing, {root: true});
|
||||||
},
|
},
|
||||||
async fetchPromptsData(context: ActionContext<ISettingsState, IRootState>) {
|
async fetchPromptsData(context: ActionContext<ISettingsState, IRootState>) {
|
||||||
if (!context.getters.isTelemetryEnabled) {
|
if (!context.getters.isTelemetryEnabled) {
|
||||||
|
|
|
@ -1267,6 +1267,11 @@
|
||||||
"workflowRun.showError.title": "Problem running workflow",
|
"workflowRun.showError.title": "Problem running workflow",
|
||||||
"workflowRun.showMessage.message": "Please fix them before executing",
|
"workflowRun.showMessage.message": "Please fix them before executing",
|
||||||
"workflowRun.showMessage.title": "Workflow has issues",
|
"workflowRun.showMessage.title": "Workflow has issues",
|
||||||
|
"workflowSettings.callerIds": "Caller IDs",
|
||||||
|
"workflowSettings.callerPolicy": "This workflow can be called by",
|
||||||
|
"workflowSettings.callerPolicy.options.any": "Any workflow",
|
||||||
|
"workflowSettings.callerPolicy.options.workflowsFromAList": "List of specific workflow IDs",
|
||||||
|
"workflowSettings.callerPolicy.options.none": "No workflows can call one",
|
||||||
"workflowSettings.defaultTimezone": "Default - {defaultTimezoneValue}",
|
"workflowSettings.defaultTimezone": "Default - {defaultTimezoneValue}",
|
||||||
"workflowSettings.defaultTimezoneNotValid": "Default Timezone not valid",
|
"workflowSettings.defaultTimezoneNotValid": "Default Timezone not valid",
|
||||||
"workflowSettings.errorWorkflow": "Error Workflow",
|
"workflowSettings.errorWorkflow": "Error Workflow",
|
||||||
|
@ -1278,6 +1283,8 @@
|
||||||
"workflowSettings.helpTexts.saveExecutionProgress": "Whether to save data after each node execution. This allows you to resume from where execution stopped if there is an error, but may increase latency.",
|
"workflowSettings.helpTexts.saveExecutionProgress": "Whether to save data after each node execution. This allows you to resume from where execution stopped if there is an error, but may increase latency.",
|
||||||
"workflowSettings.helpTexts.saveManualExecutions": "Whether to save data of executions that are started manually from the editor",
|
"workflowSettings.helpTexts.saveManualExecutions": "Whether to save data of executions that are started manually from the editor",
|
||||||
"workflowSettings.helpTexts.timezone": "The timezone in which the workflow should run. Used by 'cron' node, for example.",
|
"workflowSettings.helpTexts.timezone": "The timezone in which the workflow should run. Used by 'cron' node, for example.",
|
||||||
|
"workflowSettings.helpTexts.workflowCallerIds": "Comma-delimited list of IDs of workflows that are allowed to call this workflow",
|
||||||
|
"workflowSettings.helpTexts.workflowCallerPolicy": "Workflows that are allowed to call this workflow using the Execute Workflow node",
|
||||||
"workflowSettings.hours": "hours",
|
"workflowSettings.hours": "hours",
|
||||||
"workflowSettings.minutes": "minutes",
|
"workflowSettings.minutes": "minutes",
|
||||||
"workflowSettings.noWorkflow": "- No Workflow -",
|
"workflowSettings.noWorkflow": "- No Workflow -",
|
||||||
|
|
Loading…
Reference in a new issue