UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

970 lines (969 loc) 39.2 kB
import { C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, G as nextTick, Gt as unref, I as guardReactiveProps, It as ref, M as createVNode, P as defineComponent, T as createBlock, U as mergeModels, Z as onMounted, _t as watch, bt as withCtx, et as openBlock, ft as useModel, h as withModifiers, j as createTextVNode, q as onBeforeMount, vn as normalizeClass, w as createBaseVNode, yn as normalizeProps } from "./vue.runtime.esm-bundler-tP5dCd7J.js"; import { _t as I18nT, gt as useI18n, rt as useDebounceFn } from "./_MapCache-B159LOu5.js"; import { Hn as N8nButton_default, V as N8nFormInput_default, Vn as N8nText_default, Wn as N8nIcon_default, X as N8nInputLabel_default, d as N8nUserSelect_default, et as N8nActionToggle_default, l as N8nIconPicker_default, nt as N8nActionDropdown_default, p as N8nUserInfo_default, s as N8nDataTableServer_default, yt as N8nInput_default } from "./src-BW7zuOU1.js"; import "./en-b3uD8cvU.js"; import "./preload-helper-CR0ecmWK.js"; import { t as __plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BwBpWJRZ.js"; import { y as useRouter } from "./truncate-Dc5upPom.js"; import "./icon-MZ_3fB_h.js"; import "./overlay-CftFqN-S.js"; import "./empty-4-N75rxI.js"; import { t as ElRadio } from "./radio-BiGj7Bdg.js"; import { t as ElDialog } from "./dialog-DlumiPST.js"; import { En as useProjectsStore, Tn as useDocumentTitle, lr as isProjectRole, nr as useUsersStore, t as useTelemetry } from "./useTelemetry-C797TaYa.js"; import { t as useToast } from "./useToast-ZaUhbG9w.js"; import "./sanitize-html-CgpOSgtK.js"; import "./CalendarDate-DxkU3CHY.js"; import { t as Alert_default } from "./Alert-B0QporwB.js"; import "./path-browserify-B5h_dP_u.js"; import { Da as deepCopy, Io as VIEWS } from "./constants-CX1ozgFz.js"; import "./merge-BArJwbQo.js"; import "./_baseOrderBy-DL6Sy5xK.js"; import "./dateformat-CMLc6OKJ.js"; import "./useDebounce-RgPaX171.js"; import "./useExternalHooks-CYMS6JpD.js"; import "./useStyles-DhM0cpS7.js"; import { t as useCloudPlanStore } from "./cloudPlan.store-nppjm48S.js"; import "./folders.store-zKqujIOj.js"; import "./versions.store-BYh7N-zv.js"; import { t as usePageRedirectionHelper } from "./usePageRedirectionHelper-DY-ECTME.js"; import { n as isIconOrEmoji } from "./ProjectIcon-C-YEsNSL.js"; import "./orderBy-Fs5qTlOh.js"; import { t as ProjectSharing_default } from "./ProjectSharing-DT7-6UH6.js"; import { t as useRolesStore } from "./roles.store-BDtQtMoj.js"; import "./readyToRun.store-jtzPXHZP.js"; import { t as ProjectHeader_default } from "./ProjectHeader-CUMujzbL.js"; import { t as useUserRoleProvisioningStore } from "./userRoleProvisioning.store-CG5CAkga.js"; var _hoisted_1$4 = { key: 1 }; var _hoisted_2$1 = { class: "pt-l" }; var ProjectDeleteDialog_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ProjectDeleteDialog", props: /* @__PURE__ */ mergeModels({ currentProject: {}, projects: {}, resourceCounts: {} }, { "modelValue": { type: Boolean }, "modelModifiers": {} }), emits: /* @__PURE__ */ mergeModels(["confirmDelete"], ["update:modelValue"]), setup(__props, { emit: __emit }) { const props = __props; const visible = useModel(__props, "modelValue"); const emit = __emit; const locale = useI18n(); const selectedProject = ref(null); const operation$1 = ref(null); const wipeConfirmText = ref(""); const hasMovableResources = computed(() => props.resourceCounts.credentials + props.resourceCounts.workflows + props.resourceCounts.dataTables > 0); const isValid = computed(() => { const expectedWipeConfirmation = locale.baseText("projects.settings.delete.question.wipe.placeholder"); return !hasMovableResources.value || operation$1.value === "transfer" && !!selectedProject.value || operation$1.value === "wipe" && wipeConfirmText.value === expectedWipeConfirmation; }); const onDelete = () => { if (!isValid.value) return; if (operation$1.value === "wipe") selectedProject.value = null; emit("confirmDelete", selectedProject.value?.id); }; return (_ctx, _cache) => { return openBlock(), createBlock(unref(ElDialog), { modelValue: visible.value, "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => visible.value = $event), title: unref(locale).baseText("projects.settings.delete.title", { interpolate: { projectName: props.currentProject?.name ?? "" } }), width: "650" }, { footer: withCtx(() => [createVNode(unref(N8nButton_default), { type: "danger", "native-type": "button", disabled: !isValid.value, "data-test-id": "project-settings-delete-confirm-button", onClick: withModifiers(onDelete, ["stop", "prevent"]) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.settings.danger.deleteProject")), 1)]), _: 1 }, 8, ["disabled"])]), default: withCtx(() => [!hasMovableResources.value ? (openBlock(), createBlock(unref(N8nText_default), { key: 0, color: "text-base" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.settings.delete.message.empty")), 1)]), _: 1 })) : hasMovableResources.value ? (openBlock(), createElementBlock("div", _hoisted_1$4, [createVNode(unref(N8nText_default), { color: "text-base" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.settings.delete.message")), 1)]), _: 1 }), createBaseVNode("div", _hoisted_2$1, [ createVNode(unref(ElRadio), { "model-value": operation$1.value ?? "", label: "transfer", class: "mb-s", "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => operation$1.value = "transfer") }, { default: withCtx(() => [createVNode(unref(N8nText_default), { color: "text-dark" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.settings.delete.question.transfer.label")), 1)]), _: 1 })]), _: 1 }, 8, ["model-value"]), operation$1.value === "transfer" ? (openBlock(), createElementBlock("div", { key: 0, class: normalizeClass(_ctx.$style.operation) }, [createVNode(unref(N8nText_default), { color: "text-dark" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.settings.delete.question.transfer.title")), 1)]), _: 1 }), createVNode(ProjectSharing_default, { modelValue: selectedProject.value, "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => selectedProject.value = $event), class: "pt-2xs", projects: props.projects, "empty-options-text": unref(locale).baseText("projects.sharing.noMatchingProjects") }, null, 8, [ "modelValue", "projects", "empty-options-text" ])], 2)) : createCommentVNode("", true), createVNode(unref(ElRadio), { "model-value": operation$1.value ?? "", label: "wipe", class: "mb-s", "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => operation$1.value = "wipe") }, { default: withCtx(() => [createVNode(unref(N8nText_default), { color: "text-dark" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.settings.delete.question.wipe.label")), 1)]), _: 1 })]), _: 1 }, 8, ["model-value"]), operation$1.value === "wipe" ? (openBlock(), createElementBlock("div", { key: 1, class: normalizeClass(_ctx.$style.operation) }, [createVNode(unref(N8nInputLabel_default), { label: unref(locale).baseText("projects.settings.delete.question.wipe.title") }, { default: withCtx(() => [createVNode(unref(N8nInput_default), { modelValue: wipeConfirmText.value, "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => wipeConfirmText.value = $event), "data-test-id": "project-delete-confirm-input", placeholder: unref(locale).baseText("projects.settings.delete.question.wipe.placeholder") }, null, 8, ["modelValue", "placeholder"])]), _: 1 }, 8, ["label"])], 2)) : createCommentVNode("", true) ])])) : createCommentVNode("", true)]), _: 1 }, 8, ["modelValue", "title"]); }; } }); var ProjectDeleteDialog_vue_vue_type_style_index_0_lang_module_default = { operation: "_operation_12mti_123" }; var ProjectDeleteDialog_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ProjectDeleteDialog_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ProjectDeleteDialog_vue_vue_type_style_index_0_lang_module_default }]]); var _hoisted_1$3 = { class: "pt-l" }; var ProjectRoleUpgradeDialog_default = /* @__PURE__ */ defineComponent({ __name: "ProjectRoleUpgradeDialog", props: /* @__PURE__ */ mergeModels({ limit: {}, planName: {} }, { "modelValue": { type: Boolean }, "modelModifiers": {} }), emits: ["update:modelValue"], setup(__props) { const props = __props; const visible = useModel(__props, "modelValue"); const pageRedirectionHelper = usePageRedirectionHelper(); const locale = useI18n(); const goToUpgrade = async () => { await pageRedirectionHelper.goToUpgrade("rbac", "upgrade-rbac"); visible.value = false; }; return (_ctx, _cache) => { return openBlock(), createBlock(unref(ElDialog), { modelValue: visible.value, "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => visible.value = $event), title: unref(locale).baseText("projects.settings.role.upgrade.title"), width: "500" }, { footer: withCtx(() => [createVNode(unref(N8nButton_default), { type: "secondary", "native-type": "button", onClick: _cache[0] || (_cache[0] = ($event) => visible.value = false) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("generic.cancel")), 1)]), _: 1 }), createVNode(unref(N8nButton_default), { type: "primary", "native-type": "button", onClick: goToUpgrade }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.create.limitReached.link")), 1)]), _: 1 })]), default: withCtx(() => [createBaseVNode("div", _hoisted_1$3, [createVNode(unref(I18nT), { keypath: "projects.settings.role.upgrade.message", scope: "global" }, { planName: withCtx(() => [createTextVNode(toDisplayString(props.planName), 1)]), limit: withCtx(() => [createTextVNode(toDisplayString(unref(locale).baseText("projects.create.limit", { adjustToNumber: props.limit, interpolate: { count: String(props.limit) } })), 1)]), _: 1 })])]), _: 1 }, 8, ["modelValue", "title"]); }; } }); var ProjectMembersActionsCell_default = /* @__PURE__ */ defineComponent({ __name: "ProjectMembersActionsCell", props: { data: {}, actions: {} }, emits: ["action"], setup(__props, { emit: __emit }) { const props = __props; const emit = __emit; const onAction = (action) => { emit("action", { action, userId: props.data.id }); }; return (_ctx, _cache) => { return props.actions.length > 0 ? (openBlock(), createBlock(unref(N8nActionToggle_default), { key: 0, placement: "bottom", actions: props.actions, theme: "dark", onAction }, null, 8, ["actions"])) : createCommentVNode("", true); }; } }); var _hoisted_1$2 = { key: 1 }; var ProjectMembersRoleCell_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ProjectMembersRoleCell", props: { data: {}, roles: {}, actions: {} }, emits: ["update:role", "badge-click"], setup(__props, { emit: __emit }) { const props = __props; const emit = __emit; const selectedRole = computed(() => props.roles.find((role) => role.slug === props.data.role)); const isEditable = computed(() => props.data.role !== "project:personalOwner"); const onActionSelect = (role) => { emit("update:role", { role, userId: props.data.id }); }; return (_ctx, _cache) => { return isEditable.value ? (openBlock(), createBlock(unref(N8nActionDropdown_default), { key: 0, placement: "bottom-start", items: props.actions, "max-height": 280, "data-test-id": "project-member-role-dropdown", onSelect: onActionSelect, onBadgeClick: _cache[0] || (_cache[0] = ($event) => emit("badge-click", $event)) }, { activator: withCtx(() => [createBaseVNode("button", { class: normalizeClass(_ctx.$style.roleLabel), type: "button" }, [createVNode(unref(N8nText_default), { color: "text-dark" }, { default: withCtx(() => [createTextVNode(toDisplayString(selectedRole.value?.displayName), 1)]), _: 1 }), createVNode(unref(N8nIcon_default), { color: "text-dark", icon: "chevron-down", size: "large" })], 2)]), menuItem: withCtx((item) => [createVNode(unref(ElRadio), { "model-value": selectedRole.value?.slug, label: item.id, disabled: item.disabled }, { default: withCtx(() => [createBaseVNode("span", { class: normalizeClass(_ctx.$style.radioLabel) }, [createVNode(unref(N8nText_default), { color: "text-dark", class: "pb-3xs" }, { default: withCtx(() => [createTextVNode(toDisplayString(item.label), 1)]), _: 2 }, 1024), createVNode(unref(N8nText_default), { color: "text-dark", size: "small" }, { default: withCtx(() => [createTextVNode(toDisplayString(item.description), 1)]), _: 2 }, 1024)], 2)]), _: 2 }, 1032, [ "model-value", "label", "disabled" ])]), _: 1 }, 8, ["items"])) : (openBlock(), createElementBlock("span", _hoisted_1$2, toDisplayString(selectedRole.value?.displayName), 1)); }; } }); var ProjectMembersRoleCell_vue_vue_type_style_index_0_lang_module_default = { roleLabel: "_roleLabel_3ta9s_123", radioLabel: "_radioLabel_3ta9s_133" }; var ProjectMembersRoleCell_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ProjectMembersRoleCell_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ProjectMembersRoleCell_vue_vue_type_style_index_0_lang_module_default }]]); var _hoisted_1$1 = { class: "pt-xs pb-xs" }; var ProjectMembersTable_default = /* @__PURE__ */ defineComponent({ __name: "ProjectMembersTable", props: /* @__PURE__ */ mergeModels({ data: {}, loading: { type: Boolean }, currentUserId: {}, projectRoles: {}, actions: {}, canEditRole: { type: Boolean } }, { "tableOptions": { default: () => ({ page: 0, itemsPerPage: 10, sortBy: [] }) }, "tableOptionsModifiers": {} }), emits: /* @__PURE__ */ mergeModels([ "update:options", "update:role", "action", "show-upgrade-dialog" ], ["update:tableOptions"]), setup(__props, { emit: __emit }) { const i18n = useI18n(); const props = __props; const emit = __emit; const tableOptions = useModel(__props, "tableOptions"); const rows = computed(() => props.data.items); const headers = ref([ { title: i18n.baseText("projects.settings.table.header.user"), key: "name", width: 400, disableSort: true, value: (row) => row }, { title: i18n.baseText("projects.settings.table.header.role"), key: "role", disableSort: true }, { title: "", key: "actions", align: "end", width: 46, disableSort: true, value() {} } ]); const roleActions = computed(() => props.projectRoles.map((role) => ({ id: role.slug, label: role.displayName, disabled: !role.licensed, description: role.description ?? void 0, badge: !role.licensed ? i18n.baseText("generic.upgrade") : void 0, badgeProps: !role.licensed ? { theme: "warning", bold: true } : void 0 }))); const canUpdateRole = (member) => member.id !== props.currentUserId && props.canEditRole; const onRoleChange = ({ role, userId }) => { emit("update:role", { role, userId }); }; const filterActions = (member) => { if (member.id === props.currentUserId || member.role === "project:personalOwner") return []; return (props.actions ?? []).filter((action) => action.guard?.(member) ?? true); }; return (_ctx, _cache) => { return openBlock(), createElementBlock("div", null, [createVNode(unref(N8nDataTableServer_default), { "sort-by": tableOptions.value.sortBy, "onUpdate:sortBy": _cache[2] || (_cache[2] = ($event) => tableOptions.value.sortBy = $event), page: tableOptions.value.page, "onUpdate:page": _cache[3] || (_cache[3] = ($event) => tableOptions.value.page = $event), "items-per-page": _ctx.data.count, headers: headers.value, items: rows.value, "items-length": _ctx.data.count, loading: _ctx.loading, "page-sizes": [_ctx.data.count + 1], "onUpdate:options": _cache[4] || (_cache[4] = ($event) => emit("update:options", $event)) }, { [`item.name`]: withCtx(({ value }) => [createBaseVNode("div", _hoisted_1$1, [createVNode(unref(N8nUserInfo_default), normalizeProps(guardReactiveProps(value)), null, 16)])]), [`item.role`]: withCtx(({ item }) => [canUpdateRole(item) ? (openBlock(), createBlock(ProjectMembersRoleCell_default, { key: 0, data: item, roles: props.projectRoles, actions: roleActions.value, "onUpdate:role": onRoleChange, onBadgeClick: _cache[0] || (_cache[0] = ($event) => emit("show-upgrade-dialog")) }, null, 8, [ "data", "roles", "actions" ])) : (openBlock(), createBlock(unref(N8nText_default), { key: 1, color: "text-dark" }, { default: withCtx(() => [createTextVNode(toDisplayString(props.projectRoles.find((role) => role.slug === item.role)?.displayName ?? item.role), 1)]), _: 2 }, 1024))]), [`item.actions`]: withCtx(({ item }) => [createVNode(ProjectMembersActionsCell_default, { data: item, actions: filterActions(item), onAction: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("action", $event)) }, null, 8, ["data", "actions"])]), _: 2 }, 1032, [ "sort-by", "page", "items-per-page", "headers", "items", "items-length", "loading", "page-sizes" ])]); }; } }); var _hoisted_1 = { for: "projectName" }; var _hoisted_2 = { for: "projectDescription" }; var _hoisted_3 = { for: "projectMembers" }; var _hoisted_4 = { key: 0, class: "mb-m" }; var _hoisted_5 = { class: "mb-m" }; var SEARCH_THRESHOLD = 10; var ProjectSettings_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ProjectSettings", setup(__props) { const usersStore = useUsersStore(); const i18n = useI18n(); const projectsStore = useProjectsStore(); const rolesStore = useRolesStore(); const cloudPlanStore = useCloudPlanStore(); const userRoleProvisioningStore = useUserRoleProvisioningStore(); const toast = useToast(); const router = useRouter(); const telemetry = useTelemetry(); const documentTitle = useDocumentTitle(); const showSaveError = (error) => { toast.showError(error, i18n.baseText("projects.settings.save.error.title")); }; const dialogVisible = ref(false); const upgradeDialogVisible = ref(false); const isDirty = ref(false); const isValid = ref(false); const resourceCounts = ref({ credentials: -1, dataTables: -1, workflows: -1 }); const formData = ref({ name: "", description: "", relations: [] }); const suppressNextSync = ref(false); const nameInput = ref(null); const projectIcon = ref({ type: "icon", value: "layers" }); const search$1 = ref(""); const membersTableState = ref({ page: 0, itemsPerPage: 10, sortBy: [ { id: "firstName", desc: false }, { id: "lastName", desc: false }, { id: "email", desc: false } ] }); const usersList = computed(() => usersStore.allUsers.filter((user) => { return !(formData.value.relations || []).find((r) => r.id === user.id); })); const projects = computed(() => projectsStore.availableProjects.filter((project) => project.id !== projectsStore.currentProjectId)); const firstLicensedRole = computed(() => rolesStore.processedProjectRoles.find((role) => role.licensed)?.slug); const projectMembersActions = computed(() => [{ label: i18n.baseText("projects.settings.table.row.removeUser"), value: "remove", guard: (member) => member.id !== usersStore.currentUser?.id && member.role !== "project:personalOwner" }]); const onAddMember = async (userId) => { if (!projectsStore.currentProject) return; if (!usersStore.usersById[userId]) return; const role = firstLicensedRole.value; if (!role) return; if (!formData.value.relations.find((r) => r.id === userId)) formData.value.relations.push({ id: userId, role }); try { suppressNextSync.value = true; await projectsStore.addMember(projectsStore.currentProject.id, { userId, role }); toast.showMessage({ type: "success", title: i18n.baseText("projects.settings.member.added.title") }); telemetry.track("User added member to project", { project_id: projectsStore.currentProject.id, target_user_id: userId, role }); } catch (error) { formData.value.relations = formData.value.relations.filter((r) => r.id !== userId); showSaveError(error); } }; const onUpdateMemberRole = async ({ userId, role }) => { if (!projectsStore.currentProject) return; const memberIndex = formData.value.relations.findIndex((r) => r.id === userId); if (memberIndex === -1) return; const originalRole = formData.value.relations[memberIndex].role; formData.value.relations[memberIndex].role = role; try { suppressNextSync.value = true; await projectsStore.updateMemberRole(projectsStore.currentProject.id, userId, role); rolesStore.fetchRoles(); toast.showMessage({ type: "success", title: i18n.baseText("projects.settings.memberRole.updated.title") }); telemetry.track("User changed member role on project", { project_id: projectsStore.currentProject.id, target_user_id: userId, role }); } catch (error) { formData.value.relations[memberIndex].role = originalRole; toast.showError(error, i18n.baseText("projects.settings.memberRole.update.error.title")); } }; const onTextInput = () => { isDirty.value = true; }; async function onRemoveMember(userId) { const current = projectsStore.currentProject; if (!current) return; const idx = formData.value.relations.findIndex((r) => r.id === userId); if (idx === -1) return; const removed = formData.value.relations.splice(idx, 1)[0]; if (!current.relations.some((r) => r.id === userId)) return; try { suppressNextSync.value = true; await projectsStore.removeMember(current.id, userId); toast.showMessage({ type: "success", title: i18n.baseText("projects.settings.member.removed.title") }); telemetry.track("User removed member from project", { project_id: current.id, target_user_id: userId }); } catch (error) { formData.value.relations.splice(idx, 0, removed); showSaveError(error); } } const onMembersListAction = async ({ action, userId }) => { switch (action) { case "remove": await onRemoveMember(userId); break; default: break; } }; const resetFormData = () => { formData.value.relations = projectsStore.currentProject?.relations ? deepCopy(projectsStore.currentProject.relations) : []; formData.value.name = projectsStore.currentProject?.name ?? ""; formData.value.description = projectsStore.currentProject?.description ?? ""; }; const onCancel = () => { resetFormData(); isDirty.value = false; }; const makeFormDataDiff = () => { const diff = {}; if (!projectsStore.currentProject) return diff; if (formData.value.name !== projectsStore.currentProject.name) diff.name = formData.value.name ?? ""; if (formData.value.description !== projectsStore.currentProject.description) diff.description = formData.value.description ?? ""; if (formData.value.relations.length !== projectsStore.currentProject.relations.length) { diff.memberAdded = formData.value.relations.filter((r) => !projectsStore.currentProject?.relations.find((cr) => cr.id === r.id)); diff.memberRemoved = projectsStore.currentProject.relations.filter((cr) => !formData.value.relations.find((r) => r.id === cr.id)); } diff.role = formData.value.relations.filter((r) => { return (projectsStore.currentProject?.relations.find((cr) => cr.id === r.id))?.role !== r.role && !diff.memberAdded?.find((ar) => ar.id === r.id); }); return diff; }; const sendTelemetry = (diff) => { const projectId = projectsStore.currentProject?.id; if (diff.name) telemetry.track("User changed project name", { project_id: projectId, name: diff.name }); diff.memberAdded?.forEach((r) => { telemetry.track("User added member to project", { project_id: projectId, target_user_id: r.id, role: r.role }); }); diff.memberRemoved?.forEach((r) => { telemetry.track("User removed member from project", { project_id: projectId, target_user_id: r.id }); }); diff.role?.forEach((r) => { telemetry.track("User changed member role on project", { project_id: projectId, target_user_id: r.id, role: r.role }); }); }; const updateProject = async () => { if (!projectsStore.currentProject) return; try { await projectsStore.updateProject(projectsStore.currentProject.id, { name: formData.value.name ?? "", description: formData.value.description ?? "" }); isDirty.value = false; } catch (error) { showSaveError(error); throw error; } }; const onSubmit = async () => { if (!isDirty.value) return; try { await updateProject(); sendTelemetry(makeFormDataDiff()); toast.showMessage({ title: i18n.baseText("projects.settings.save.successful.title", { interpolate: { projectName: formData.value.name ?? "" } }), type: "success" }); } catch (error) {} }; const onDelete = async () => { await projectsStore.getAvailableProjects(); if (projectsStore.currentProjectId) resourceCounts.value = await projectsStore.getResourceCounts(projectsStore.currentProjectId); dialogVisible.value = true; }; const onConfirmDelete = async (transferId) => { try { if (projectsStore.currentProject) { const projectName$1 = projectsStore.currentProject?.name ?? ""; await projectsStore.deleteProject(projectsStore.currentProject.id, transferId); await router.push({ name: VIEWS.HOMEPAGE }); toast.showMessage({ title: i18n.baseText("projects.settings.delete.successful.title", { interpolate: { projectName: projectName$1 } }), type: "success" }); dialogVisible.value = true; } } catch (error) { toast.showError(error, i18n.baseText("projects.settings.delete.error.title")); } }; const selectProjectNameIfMatchesDefault = () => { if (formData.value.name === i18n.baseText("projects.settings.newProjectName")) { nameInput.value?.inputRef?.focus(); nameInput.value?.inputRef?.select(); } }; const onIconUpdated = async () => { if (!projectsStore.currentProject) return; try { await projectsStore.updateProject(projectsStore.currentProject.id, { icon: projectIcon.value }); toast.showMessage({ title: i18n.baseText("projects.settings.icon.update.successful.title"), type: "success" }); } catch (error) { showSaveError(error); } }; watch(() => projectsStore.currentProject, async () => { if (suppressNextSync.value) { suppressNextSync.value = false; return; } resetFormData(); await nextTick(); selectProjectNameIfMatchesDefault(); if (projectsStore.currentProject?.icon && isIconOrEmoji(projectsStore.currentProject.icon)) projectIcon.value = projectsStore.currentProject.icon; }, { immediate: true }); const relationUsers = computed(() => formData.value.relations.map((relation) => { const user = usersStore.usersById[relation.id]; const safeRole = isProjectRole(relation.role) ? relation.role : "project:viewer"; return { ...user, ...relation, role: safeRole, firstName: user?.firstName ?? null, lastName: user?.lastName ?? null, email: user?.email ?? null }; })); const membersTableData = computed(() => ({ items: relationUsers.value, count: relationUsers.value.length })); const filteredMembersData = computed(() => { if (!search$1.value.trim()) return membersTableData.value; const searchTerm = search$1.value.toLowerCase(); const filtered = relationUsers.value.filter((member) => { const fullName = `${member.firstName ?? ""} ${member.lastName ?? ""}`.toLowerCase(); const email = (member.email ?? "").toLowerCase(); return fullName.includes(searchTerm) || email.includes(searchTerm); }); return { items: filtered, count: filtered.length }; }); const shouldShowSearch = computed(() => relationUsers.value.length >= SEARCH_THRESHOLD); watch(shouldShowSearch, (show) => { if (!show && search$1.value) search$1.value = ""; }); const debouncedSearch = useDebounceFn(() => { membersTableState.value.page = 0; }, 300); const onSearch = (value) => { search$1.value = value; debouncedSearch(); }; const onUpdateMembersTableOptions = (options) => { membersTableState.value = options; }; onBeforeMount(async () => { await usersStore.fetchUsers(); }); const isProjectRoleProvisioningEnabled = computed(() => userRoleProvisioningStore.provisioningConfig?.scopesProvisionProjectRoles || false); onMounted(async () => { documentTitle.set(i18n.baseText("projects.settings")); selectProjectNameIfMatchesDefault(); await userRoleProvisioningStore.getProvisioningConfig(); }); return (_ctx, _cache) => { return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.$style.projectSettings), "data-test-id": "project-settings-container" }, [ createBaseVNode("div", { class: normalizeClass(_ctx.$style.header) }, [createVNode(ProjectHeader_default), createBaseVNode("div", { class: normalizeClass(_ctx.$style.headerRow) }, [createVNode(unref(N8nText_default), { tag: "h1", size: "xlarge", class: "pt-xs pb-m" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projects.settings.info")), 1)]), _: 1 }), createBaseVNode("div", { class: normalizeClass(_ctx.$style.headerButtons) }, [createVNode(unref(N8nButton_default), { type: "secondary", "native-type": "button", disabled: !isDirty.value, class: "mr-2xs", "data-test-id": "project-settings-cancel-button", onClick: withModifiers(onCancel, ["stop", "prevent"]) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projects.settings.button.cancel")), 1)]), _: 1 }, 8, ["disabled"]), createVNode(unref(N8nButton_default), { disabled: !isValid.value || !isDirty.value, type: "primary", "data-test-id": "project-settings-save-button", onClick: withModifiers(onSubmit, ["stop", "prevent"]) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projects.settings.button.save")), 1)]), _: 1 }, 8, ["disabled"])], 2)], 2)], 2), createBaseVNode("form", { onSubmit: withModifiers(onSubmit, ["prevent"]) }, [ createBaseVNode("fieldset", null, [createBaseVNode("label", _hoisted_1, toDisplayString(unref(i18n).baseText("projects.settings.name")), 1), createBaseVNode("div", { class: normalizeClass(_ctx.$style.projectName) }, [createVNode(unref(N8nIconPicker_default), { modelValue: projectIcon.value, "onUpdate:modelValue": [_cache[0] || (_cache[0] = ($event) => projectIcon.value = $event), onIconUpdated], "button-tooltip": unref(i18n).baseText("projects.settings.iconPicker.button.tooltip") }, null, 8, ["modelValue", "button-tooltip"]), createVNode(unref(N8nFormInput_default), { id: "projectName", ref_key: "nameInput", ref: nameInput, modelValue: formData.value.name, "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => formData.value.name = $event), label: "", type: "text", name: "name", required: "", "data-test-id": "project-settings-name-input", class: normalizeClass(_ctx.$style.projectNameInput), onEnter: onSubmit, onInput: onTextInput, onValidate: _cache[2] || (_cache[2] = ($event) => isValid.value = $event) }, null, 8, ["modelValue", "class"])], 2)]), createBaseVNode("fieldset", null, [createBaseVNode("label", _hoisted_2, toDisplayString(unref(i18n).baseText("projects.settings.description")), 1), createVNode(unref(N8nFormInput_default), { id: "projectDescription", modelValue: formData.value.description, "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => formData.value.description = $event), label: "", name: "description", type: "textarea", maxlength: 512, autosize: true, "data-test-id": "project-settings-description-input", class: normalizeClass(_ctx.$style.projectDescriptionInput), onEnter: onSubmit, onInput: onTextInput, onValidate: _cache[4] || (_cache[4] = ($event) => isValid.value = $event) }, null, 8, ["modelValue", "class"])]), createBaseVNode("fieldset", null, [ createBaseVNode("h3", null, [createBaseVNode("label", _hoisted_3, toDisplayString(unref(i18n).baseText("projects.settings.projectMembers")), 1)]), createBaseVNode("div", { class: normalizeClass([_ctx.$style.membersInputRow, "mb-s"]) }, [createVNode(unref(N8nUserSelect_default), { id: "projectMembers", class: normalizeClass(_ctx.$style.userSelect), size: "large", users: usersList.value, "current-user-id": unref(usersStore).currentUser?.id, placeholder: unref(i18n).baseText("workflows.shareModal.select.placeholder"), "data-test-id": "project-members-select", "onUpdate:modelValue": onAddMember, disabled: isProjectRoleProvisioningEnabled.value }, { prefix: withCtx(() => [createVNode(unref(N8nIcon_default), { icon: "search" })]), _: 1 }, 8, [ "class", "users", "current-user-id", "placeholder", "disabled" ]), shouldShowSearch.value ? (openBlock(), createBlock(unref(N8nInput_default), { key: 0, class: normalizeClass(_ctx.$style.search), "model-value": search$1.value, placeholder: unref(i18n).baseText("projects.settings.members.search.placeholder"), clearable: "", "data-test-id": "project-members-search", "onUpdate:modelValue": onSearch }, { prefix: withCtx(() => [createVNode(unref(N8nIcon_default), { icon: "search" })]), _: 1 }, 8, [ "class", "model-value", "placeholder" ])) : createCommentVNode("", true)], 2), isProjectRoleProvisioningEnabled.value ? (openBlock(), createElementBlock("div", _hoisted_4, [createVNode(unref(Alert_default), { type: "info", title: unref(i18n).baseText("settings.provisioningProjectRolesHandledBySsoProvider.description") }, null, 8, ["title"])])) : createCommentVNode("", true), relationUsers.value.length > 0 ? (openBlock(), createElementBlock("div", { key: 1, class: normalizeClass(_ctx.$style.membersTableContainer) }, [createVNode(ProjectMembersTable_default, { "table-options": membersTableState.value, "onUpdate:tableOptions": _cache[5] || (_cache[5] = ($event) => membersTableState.value = $event), "data-test-id": "project-members-table", data: filteredMembersData.value, "current-user-id": unref(usersStore).currentUser?.id, "project-roles": unref(rolesStore).processedProjectRoles, actions: projectMembersActions.value, "can-edit-role": !isProjectRoleProvisioningEnabled.value, "onUpdate:options": onUpdateMembersTableOptions, "onUpdate:role": onUpdateMemberRole, onAction: onMembersListAction, onShowUpgradeDialog: _cache[6] || (_cache[6] = ($event) => upgradeDialogVisible.value = true) }, null, 8, [ "table-options", "data", "current-user-id", "project-roles", "actions", "can-edit-role" ])], 2)) : createCommentVNode("", true) ]), createBaseVNode("fieldset", null, [ createBaseVNode("h3", _hoisted_5, toDisplayString(unref(i18n).baseText("projects.settings.danger.title")), 1), createBaseVNode("small", { class: normalizeClass(_ctx.$style.danger) }, toDisplayString(unref(i18n).baseText("projects.settings.danger.message")), 3), createVNode(unref(N8nButton_default), { type: "tertiary", size: "large", "native-type": "button", "data-test-id": "project-settings-delete-button", onClick: withModifiers(onDelete, ["stop", "prevent"]) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projects.settings.danger.deleteProject")), 1)]), _: 1 }) ]) ], 32), createVNode(ProjectDeleteDialog_default, { modelValue: dialogVisible.value, "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => dialogVisible.value = $event), "current-project": unref(projectsStore).currentProject, "resource-counts": resourceCounts.value, projects: projects.value, onConfirmDelete }, null, 8, [ "modelValue", "current-project", "resource-counts", "projects" ]), createVNode(ProjectRoleUpgradeDialog_default, { modelValue: upgradeDialogVisible.value, "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => upgradeDialogVisible.value = $event), limit: unref(projectsStore).teamProjectsLimit, "plan-name": unref(cloudPlanStore).currentPlanData?.displayName }, null, 8, [ "modelValue", "limit", "plan-name" ]) ], 2); }; } }); var ProjectSettings_vue_vue_type_style_index_0_lang_module_default = { projectSettings: "_projectSettings_142pd_123", header: "_header_142pd_147", headerRow: "_headerRow_142pd_153", headerButtons: "_headerButtons_142pd_160", upgrade: "_upgrade_142pd_166", membersInputRow: "_membersInputRow_142pd_170", membersTableContainer: "_membersTableContainer_142pd_178", search: "_search_142pd_182", projectName: "_projectName_142pd_187", projectNameInput: "_projectNameInput_142pd_192", projectDescriptionInput: "_projectDescriptionInput_142pd_196", userSelect: "_userSelect_142pd_197", danger: "_danger_142pd_207" }; var ProjectSettings_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ProjectSettings_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ProjectSettings_vue_vue_type_style_index_0_lang_module_default }]]); export { ProjectSettings_default as default };