UNPKG

n8n-editor-ui

Version:

Workflow Editor UI for n8n

439 lines (438 loc) 17.3 kB
import { o as __toESM } from "./chunk-6z4oVpB-.js"; import { Bt as toRaw, C as computed, Cn as toDisplayString, D as createElementBlock, E as createCommentVNode, Gt as unref, It as ref, M as createVNode, P as defineComponent, T as createBlock, _ as Fragment, bt as withCtx, et as openBlock, it as renderList, j as createTextVNode, vn as normalizeClass, w as createBaseVNode } from "./vue.runtime.esm-bundler-tP5dCd7J.js"; import { gt as useI18n, x as useAsyncState } from "./_MapCache-B159LOu5.js"; import { Hn as N8nButton_default, V as N8nFormInput_default, Vn as N8nText_default, mt as N8nTooltip_default, tt as N8nLoading_default, zn as N8nHeading_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 useMessage } from "./useMessage-C1bozAQq.js"; import { Lt as require_sortBy, 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 "./path-browserify-B5h_dP_u.js"; import { Io as VIEWS, _o as require_isEqual, ts as MODAL_CONFIRM } 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 useRolesStore } from "./roles.store-BDtQtMoj.js"; var import_isEqual = /* @__PURE__ */ __toESM(require_isEqual()); var import_sortBy = /* @__PURE__ */ __toESM(require_sortBy()); var _hoisted_1 = { key: 0 }; var _hoisted_2 = { style: { "flex": "1" } }; var _hoisted_3 = { key: 0, class: "mt-xl" }; var ProjectRoleView_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({ __name: "ProjectRoleView", props: { roleSlug: {} }, setup(__props) { const rolesStore = useRolesStore(); const router = useRouter(); const { showError, showMessage } = useToast(); const i18n = useI18n(); const message = useMessage(); const telemetry = useTelemetry(); const props = __props; const defaultForm = () => ({ displayName: "", description: "", scopes: structuredClone(toRaw(rolesStore.processedProjectRoles.find((role) => role.slug === "project:viewer")?.scopes || [])) }); const initialState = ref(); const { state: form, isLoading } = useAsyncState(async () => { if (!props.roleSlug) return defaultForm(); try { const role = await rolesStore.fetchRoleBySlug({ slug: props.roleSlug }); initialState.value = structuredClone(role); return { displayName: role.displayName, description: role.description, scopes: role.scopes }; } catch (error) { showError(error, "Error fetching role"); return defaultForm(); } }, defaultForm(), { shallow: false }); const hasUnsavedChanges = computed(() => { if (!initialState.value) return false; if (!(0, import_isEqual.default)(initialState.value.displayName, form.value.displayName)) return true; if (!(0, import_isEqual.default)(initialState.value.description ?? null, form.value.description || null)) return true; if (!(0, import_isEqual.default)((0, import_sortBy.default)(initialState.value.scopes), (0, import_sortBy.default)(form.value.scopes))) return true; return false; }); function resetForm(payload) { form.value = payload ? { displayName: payload.displayName, description: payload.description, scopes: payload.scopes } : defaultForm(); } const project = [ "read", "update", "delete" ].map((action) => `project:${action}`); const folder = [ "read", "update", "create", "move", "delete" ].map((action) => `folder:${action}`); const workflow = [ "read", "update", "create", "move", "delete" ].map((action) => `workflow:${action}`); const credential = [ "read", "update", "create", "share", "move", "delete" ].map((action) => `credential:${action}`); const sourceControl = ["push"].map((action) => `sourceControl:${action}`); const dataTable = [ "read", "readRow", "update", "writeRow", "create", "delete" ].map((action) => `dataTable:${action}`); const projectVariable = [ "read", "update", "create", "delete" ].map((action) => `projectVariable:${action}`); const scopeTypes = [ "project", "folder", "workflow", "credential", "dataTable", "projectVariable", "sourceControl" ]; const scopes = { project, folder, workflow, credential, sourceControl, dataTable, projectVariable }; function toggleScope(scope) { const index = form.value.scopes.indexOf(scope); if (index !== -1) form.value.scopes.splice(index, 1); else form.value.scopes.push(scope); if (scope.startsWith("dataTable:") && scope.endsWith(":read")) { toggleScope(scope.replace(":read", ":listProject")); return; } if (scope.endsWith(":read")) toggleScope(scope.replace(":read", ":list")); if (scope === "workflow:update") toggleScope("workflow:execute"); } async function createProjectRole() { try { const role = await rolesStore.createProjectRole({ ...form.value, description: form.value.description ?? void 0, roleType: "project" }); rolesStore.fetchRoles(); telemetry.track("User successfully created new role", { role_id: role.slug, role_name: role.displayName, permissions: role.scopes }); router.replace({ name: VIEWS.PROJECT_ROLE_SETTINGS, params: { roleSlug: role.slug } }); showMessage({ type: "success", message: i18n.baseText("projectRoles.action.create.success") }); initialState.value = structuredClone(role); return role; } catch (error) { showError(error, i18n.baseText("projectRoles.action.create.error")); return; } } async function confirmRoleUpdate(usedByUsers) { if (!usedByUsers) return true; return await message.confirm(i18n.baseText("projectRoles.action.update.text", { interpolate: { count: usedByUsers } }), i18n.baseText("projectRoles.action.update.title", { interpolate: { count: usedByUsers } }), { type: "warning", confirmButtonText: i18n.baseText("projectRoles.action.update"), cancelButtonText: i18n.baseText("projectRoles.action.cancel") }) === MODAL_CONFIRM; } async function updateProjectRole(slug) { if (!await confirmRoleUpdate(initialState?.value?.usedByUsers)) return; try { const role = await rolesStore.updateProjectRole(slug, { ...form.value, description: form.value.description ?? void 0 }); rolesStore.fetchRoles(); telemetry.track("User updated role", { role_id: role.slug, role_name: role.displayName, permissions_from: initialState.value?.scopes, permissions_to: role.scopes }); initialState.value = structuredClone(role); showMessage({ type: "success", message: i18n.baseText("projectRoles.action.update.success") }); return role; } catch (error) { showError(error, i18n.baseText("projectRoles.action.update.error")); return; } } async function handleSubmit() { if (props.roleSlug) await updateProjectRole(props.roleSlug); else await createProjectRole(); } function setPreset(slug) { const preset = rolesStore.processedProjectRoles.find((role) => role.slug === slug); if (!preset) return; form.value.scopes = structuredClone(toRaw(preset.scopes)); } async function deleteRole() { if (!initialState?.value) return; if (await message.confirm(i18n.baseText("projectRoles.action.delete.text", { interpolate: { roleName: initialState.value.displayName } }), i18n.baseText("projectRoles.action.delete.title", { interpolate: { roleName: initialState.value.displayName } }), { type: "warning", confirmButtonText: i18n.baseText("projectRoles.action.delete"), cancelButtonText: i18n.baseText("projectRoles.action.cancel") }) !== "confirm") return; telemetry.track("User clicked delete role", { role_id: initialState.value.slug, role_name: initialState.value.displayName, permissions: initialState.value?.scopes }); try { await rolesStore.deleteProjectRole(initialState.value.slug); const index = rolesStore.roles.project.findIndex((role) => role.slug === initialState.value?.slug); if (index !== -1) rolesStore.roles.project.splice(index, 1); showMessage({ title: i18n.baseText("projectRoles.action.delete.success"), type: "success" }); telemetry.track("User successfully deleted role", { role_id: initialState.value.slug, role_name: initialState.value.displayName, permissions: initialState.value?.scopes }); router.back(); } catch (error) { showError(error, i18n.baseText("projectRoles.action.delete.error")); return; } } const displayNameValidationRules = [{ name: "REQUIRED" }, { name: "MIN_LENGTH", config: { minimum: 2 } }]; return (_ctx, _cache) => { return openBlock(), createElementBlock("div", { class: normalizeClass(["pb-xl", _ctx.$style.container]) }, [ createVNode(unref(N8nButton_default), { icon: "arrow-left", type: "secondary", class: normalizeClass(_ctx.$style.backButton), text: "", onClick: _cache[0] || (_cache[0] = () => unref(router).back()) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.backToRoleList")), 1)]), _: 1 }, 8, ["class"]), createBaseVNode("div", { class: normalizeClass(["mb-xl", _ctx.$style.headerContainer]) }, [createVNode(unref(N8nHeading_default), { tag: "h1", size: "2xlarge" }, { default: withCtx(() => [createTextVNode(toDisplayString(_ctx.roleSlug ? `Role "${unref(form).displayName}"` : unref(i18n).baseText("projectRoles.newRole")), 1)]), _: 1 }), initialState.value ? (openBlock(), createElementBlock("div", _hoisted_1, [createVNode(unref(N8nButton_default), { type: "secondary", disabled: !hasUnsavedChanges.value, class: "mr-xs", onClick: _cache[1] || (_cache[1] = ($event) => resetForm(initialState.value)) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.discardChanges")), 1)]), _: 1 }, 8, ["disabled"]), createVNode(unref(N8nButton_default), { disabled: !hasUnsavedChanges.value, onClick: handleSubmit }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.save")), 1)]), _: 1 }, 8, ["disabled"])])) : (openBlock(), createBlock(unref(N8nButton_default), { key: 1, onClick: handleSubmit }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.create")), 1)]), _: 1 }))], 2), createBaseVNode("div", { class: normalizeClass(["mb-l", _ctx.$style.formContainer]) }, [createVNode(unref(N8nFormInput_default), { modelValue: unref(form).displayName, "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => unref(form).displayName = $event), label: unref(i18n).baseText("projectRoles.roleName"), "validate-on-blur": "", "validation-rules": displayNameValidationRules, class: "mb-s", "show-required-asterisk": "", required: "", maxlength: 100 }, null, 8, ["modelValue", "label"]), createVNode(unref(N8nFormInput_default), { modelValue: unref(form).description, "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => unref(form).description = $event), label: unref(i18n).baseText("projectRoles.description"), placeholder: unref(i18n).baseText("projectRoles.optional"), type: "textarea", maxlength: 500, autosize: { minRows: 2, maxRows: 4 } }, null, 8, [ "modelValue", "label", "placeholder" ])], 2), createVNode(unref(N8nHeading_default), { tag: "h2", size: "xlarge", class: "mb-s" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.permissions")), 1)]), _: 1 }), createVNode(unref(N8nText_default), { color: "text-light", class: "mb-2xs", tag: "p" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.preset")), 1)]), _: 1 }), createBaseVNode("div", { class: normalizeClass(["mb-s", _ctx.$style.presetsContainer]) }, [ createVNode(unref(N8nButton_default), { type: "secondary", onClick: _cache[4] || (_cache[4] = ($event) => setPreset("project:admin")) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.admin")), 1)]), _: 1 }), createVNode(unref(N8nButton_default), { type: "secondary", onClick: _cache[5] || (_cache[5] = ($event) => setPreset("project:editor")) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.editor")), 1)]), _: 1 }), createVNode(unref(N8nButton_default), { type: "secondary", onClick: _cache[6] || (_cache[6] = ($event) => setPreset("project:viewer")) }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.viewer")), 1)]), _: 1 }) ], 2), createBaseVNode("div", { class: normalizeClass(_ctx.$style.cardContainer) }, [(openBlock(), createElementBlock(Fragment, null, renderList(scopeTypes, (type) => { return createBaseVNode("div", { key: type, class: normalizeClass(["mb-s mt-s", _ctx.$style.card]) }, [createBaseVNode("div", { class: normalizeClass(_ctx.$style.cardTitle) }, toDisplayString(unref(i18n).baseText(`projectRoles.type.${type}`)), 3), createBaseVNode("div", _hoisted_2, [unref(isLoading) ? (openBlock(), createBlock(unref(N8nLoading_default), { key: 0, rows: scopes[type].length, "shrink-last": false }, null, 8, ["rows"])) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(scopes[type], (scope) => { return openBlock(), createElementBlock("div", { key: scope, class: "mb-2xs" }, [createVNode(unref(N8nTooltip_default), { content: unref(i18n).baseText(`projectRoles.${scope}.tooltip`), placement: "right", enterable: false, "show-after": 250 }, { default: withCtx(() => [createVNode(unref(N8nFormInput_default), { "data-test-id": `scope-checkbox-${scope}`, "model-value": unref(form).scopes.includes(scope), label: unref(i18n).baseText(`projectRoles.${scope}`), "validate-on-blur": "", type: "checkbox", class: normalizeClass(_ctx.$style.checkbox), "onUpdate:modelValue": () => toggleScope(scope) }, null, 8, [ "data-test-id", "model-value", "label", "class", "onUpdate:modelValue" ])]), _: 2 }, 1032, ["content"])]); }), 128))])], 2); }), 64))], 2), _ctx.roleSlug && !initialState.value?.systemRole ? (openBlock(), createElementBlock("div", _hoisted_3, [ createVNode(unref(N8nHeading_default), { tag: "h2", class: "mb-2xs", size: "large" }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.dangerZone")), 1)]), _: 1 }), createVNode(unref(N8nText_default), { tag: "p", class: "mb-s" }, { default: withCtx(() => [initialState.value?.usedByUsers ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.action.delete.useWarning", { interpolate: { count: initialState.value.usedByUsers } })), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.action.delete.warning")), 1)], 64))]), _: 1 }), createVNode(unref(N8nButton_default), { type: "danger", disabled: Boolean(initialState.value?.usedByUsers), onClick: deleteRole }, { default: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).baseText("projectRoles.action.delete.button")), 1)]), _: 1 }, 8, ["disabled"]) ])) : createCommentVNode("", true) ], 2); }; } }); var ProjectRoleView_vue_vue_type_style_index_0_lang_module_default = { container: "_container_14r2u_2", cardContainer: "_cardContainer_14r2u_9", card: "_card_14r2u_9", cardTitle: "_cardTitle_14r2u_28", backButton: "_backButton_14r2u_32", headerContainer: "_headerContainer_14r2u_38", formContainer: "_formContainer_14r2u_44", presetsContainer: "_presetsContainer_14r2u_48", checkbox: "_checkbox_14r2u_53" }; var ProjectRoleView_default = /* @__PURE__ */ __plugin_vue_export_helper_default(ProjectRoleView_vue_vue_type_script_setup_true_lang_default, [["__cssModules", { "$style": ProjectRoleView_vue_vue_type_style_index_0_lang_module_default }]]); export { ProjectRoleView_default as default };