diff --git a/packages/cli/src/config/schema.ts b/packages/cli/src/config/schema.ts index fbca7d0983..7dfcb76742 100644 --- a/packages/cli/src/config/schema.ts +++ b/packages/cli/src/config/schema.ts @@ -892,6 +892,7 @@ export const schema = { sharing: { format: Boolean, default: false, + env: 'N8N_SHARING_ENABLED', }, }, // This is a temporary flag (acting as feature toggle) @@ -899,6 +900,7 @@ export const schema = { workflowSharingEnabled: { format: Boolean, default: false, + env: 'N8N_WORKFLOW_SHARING_ENABLED', }, }, diff --git a/packages/design-system/src/components/N8nUsersList/UsersList.vue b/packages/design-system/src/components/N8nUsersList/UsersList.vue index 5cecb62200..605e3e3382 100644 --- a/packages/design-system/src/components/N8nUsersList/UsersList.vue +++ b/packages/design-system/src/components/N8nUsersList/UsersList.vue @@ -15,8 +15,13 @@ > {{ t('nds.auth.roles.owner') }} + t('nds.usersList.reinviteUser'), }, + actions: { + type: Array as PropType, + default: () => ['delete', 'reinvite'], + }, }, computed: { sortedUsers(): IUser[] { @@ -113,6 +123,7 @@ export default mixins(Locale).extend({ }, methods: { getActions(user: IUser): IUserListAction[] { + const actions = []; const DELETE: IUserListAction = { label: this.deleteLabel as string, value: 'delete', @@ -127,16 +138,17 @@ export default mixins(Locale).extend({ return []; } - if (user.firstName) { - return [ - DELETE, - ]; - } else { - return [ - REINVITE, - DELETE, - ]; + if (!user.firstName) { + if (this.actions.includes('reinvite')) { + actions.push(REINVITE); + } } + + if (this.actions.includes('delete')) { + actions.push(DELETE); + } + + return actions; }, onUserAction(user: IUser, action: string): void { if (action === 'delete' || action === 'reinvite') { diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index 32881943f6..989e1e7586 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -37,6 +37,7 @@ import { NodeParameterValueType, } from 'n8n-workflow'; import { FAKE_DOOR_FEATURES } from './constants'; +import {ICredentialsDb} from "n8n"; export * from 'n8n-design-system/src/types'; @@ -320,6 +321,7 @@ export interface IWorkflowDb { sharedWith?: Array>; ownedBy?: Partial; hash: string; + usedCredentials?: Array>; } // Identical to cli.Interfaces.ts @@ -332,6 +334,14 @@ export interface IWorkflowShortResponse { tags: ITag[]; } +export interface IWorkflowsShareResponse { + id: string; + createdAt: number | string; + updatedAt: number | string; + sharedWith?: Array>; + ownedBy?: Partial; +} + // Identical or almost identical to cli.Interfaces.ts @@ -346,12 +356,17 @@ export interface IShareCredentialsPayload { shareWithIds: string[]; } +export interface IShareWorkflowsPayload { + shareWithIds: string[]; +} + export interface ICredentialsResponse extends ICredentialsEncrypted { id: string; createdAt: number | string; updatedAt: number | string; sharedWith?: Array>; ownedBy?: Partial; + currentUserHasAccess?: boolean; } export interface ICredentialsBase { @@ -987,7 +1002,6 @@ export interface ICredentialMap { export interface ICredentialsState { credentialTypes: ICredentialTypeMap; credentials: ICredentialMap; - foreignCredentials?: ICredentialMap; } export interface ITagsState { @@ -1112,7 +1126,7 @@ export type IFakeDoor = { uiLocations: IFakeDoorLocation[], }; -export type IFakeDoorLocation = 'settings' | 'credentialsModal'; +export type IFakeDoorLocation = 'settings' | 'credentialsModal' | 'workflowShareModal'; export type INodeFilterType = "Regular" | "Trigger" | "All"; diff --git a/packages/editor-ui/src/api/credentials.ts b/packages/editor-ui/src/api/credentials.ts index 9ff6df1c64..60a81e061e 100644 --- a/packages/editor-ui/src/api/credentials.ts +++ b/packages/editor-ui/src/api/credentials.ts @@ -51,9 +51,3 @@ export async function oAuth2CredentialAuthorize(context: IRestApiContext, data: export async function testCredential(context: IRestApiContext, data: INodeCredentialTestRequest): Promise { return makeRestApiRequest(context, 'POST', '/credentials/test', data as unknown as IDataObject); } - -export async function getForeignCredentials(context: IRestApiContext): Promise { - // TODO: Get foreign credentials - //return await makeRestApiRequest(context, 'GET', '/foreign-credentials'); - return []; -} diff --git a/packages/editor-ui/src/api/workflows.ee.ts b/packages/editor-ui/src/api/workflows.ee.ts new file mode 100644 index 0000000000..34ef341b61 --- /dev/null +++ b/packages/editor-ui/src/api/workflows.ee.ts @@ -0,0 +1,13 @@ +import { + IRestApiContext, + IShareWorkflowsPayload, + IWorkflowsShareResponse, +} from '@/Interface'; +import { makeRestApiRequest } from './helpers'; +import { + IDataObject, +} from 'n8n-workflow'; + +export async function setWorkflowSharedWith(context: IRestApiContext, id: string, data: IShareWorkflowsPayload): Promise { + return makeRestApiRequest(context, 'PUT', `/workflows/${id}/share`, data as unknown as IDataObject); +} diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue index 1cd94be649..123c67a00b 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialSharing.ee.vue @@ -73,7 +73,7 @@ export default mixins( }, methods: { async onAddSharee(userId: string) { - const sharee = this.usersStore.getUserById(userId); + const sharee = { ...this.usersStore.getUserById(userId), isOwner: false }; this.$emit('change', (this.credentialData.sharedWith || []).concat(sharee)); }, async onRemoveSharee(userId: string) { diff --git a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue index 38b89bc6b9..7979532acf 100644 --- a/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue +++ b/packages/editor-ui/src/components/MainHeader/WorkflowDetails.vue @@ -67,6 +67,15 @@ + + + {{ $locale.baseText('workflowDetails.share') }} + + + + + + @@ -128,6 +132,7 @@ import { VERSIONS_MODAL_KEY, WORKFLOW_ACTIVE_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, + WORKFLOW_SHARE_MODAL_KEY, IMPORT_CURL_MODAL_KEY, } from '@/constants'; @@ -151,6 +156,7 @@ import DeleteUserModal from "./DeleteUserModal.vue"; import ExecutionsList from "./ExecutionsList.vue"; import ActivationModal from "./ActivationModal.vue"; import ImportCurlModal from './ImportCurlModal.vue'; +import WorkflowShareModal from './WorkflowShareModal.ee.vue'; export default Vue.extend({ name: "Modals", @@ -174,6 +180,7 @@ export default Vue.extend({ UpdatesPanel, ValueSurvey, WorkflowSettings, + WorkflowShareModal, ImportCurlModal, }, data: () => ({ @@ -192,6 +199,7 @@ export default Vue.extend({ TAGS_MANAGER_MODAL_KEY, VERSIONS_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, + WORKFLOW_SHARE_MODAL_KEY, VALUE_SURVEY_MODAL_KEY, EXECUTIONS_MODAL_KEY, WORKFLOW_ACTIVE_MODAL_KEY, diff --git a/packages/editor-ui/src/components/NodeCredentials.vue b/packages/editor-ui/src/components/NodeCredentials.vue index ef4d9bc3c5..b188ef3b56 100644 --- a/packages/editor-ui/src/components/NodeCredentials.vue +++ b/packages/editor-ui/src/components/NodeCredentials.vue @@ -15,7 +15,7 @@ size="small" color="text-dark" > -
+
{ + if (credential.id && foreignCredentials[credential.id] && !foreignCredentials[credential.id].currentUserHasAccess) { + hasForeignCredential = true; + } + }); + } + + return hasForeignCredential; + }, }, watch: { activeNode(node: INodeUi | null) { @@ -384,8 +402,6 @@ export default mixins( nodeSubtitle: this.getNodeSubtitle(node, this.activeNodeType, this.getCurrentWorkflow()), }); - this.checkForeignCredentials(); - setTimeout(() => { if (this.activeNode) { const outgoingConnections = this.workflowsStore.outgoingConnectionsByNodeName( @@ -631,12 +647,6 @@ export default mixins( input_node_type: this.inputNode ? this.inputNode.type : '', }); }, - checkForeignCredentials() { - if(this.activeNode){ - const issues = this.getNodeCredentialIssues(this.activeNode); - this.hasForeignCredential = !!issues?.credentials?.foreign; - } - }, onStopExecution(){ this.$emit('stopExecution'); }, diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index 4d0fa649e9..ae762d2794 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -10,7 +10,7 @@ :isReadOnly="isReadOnly" @input="nameChanged" > -
+
- +
@@ -264,6 +264,10 @@ export default mixins(externalHooks, nodeHelpers).extend({ type: Boolean, default: false, }, + executable: { + type: Boolean, + default: true, + }, }, data() { return { diff --git a/packages/editor-ui/src/components/WorkflowCard.vue b/packages/editor-ui/src/components/WorkflowCard.vue index 82de96c28a..8b1e185efa 100644 --- a/packages/editor-ui/src/components/WorkflowCard.vue +++ b/packages/editor-ui/src/components/WorkflowCard.vue @@ -26,9 +26,9 @@