Files
n8n_stack/n8n/backup/workflows/Git_Pull_Workflows.json
T
V.Bolshakov 49b389ed53 first commit
2026-05-04 16:54:53 +07:00

854 lines
38 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"name": "Git Pull Workflows",
"nodes": [
{
"parameters": {
"content": "## Точка входа\n\nДва способа запустить workflow:\n\n**Форма** — основной способ. Пользователь заполняет данные репозитория, ветку, папку и переключатель **Активировать workflow** — разрешить ли активацию вообще.\n\n**Ручной запуск** — для отладки без открытия формы. Тестовые данные задаются в ноде «Тестовые данные»",
"height": 964,
"width": 588,
"color": 7
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
5136,
496
],
"id": "987b6189-d7c7-4b68-86cf-b568c1f92096",
"name": "Зона: Точка входа"
},
{
"parameters": {
"content": "## Подготовка и git операции\n\nПарсим данные формы и строим переменные:\n- **remoteUrl** — URL с кредами вида `http://user:pat@host/org/repo.git`. Используется только для git операций\n- **cleanRemoteUrl** — URL без кредов\n- **localFolder** — локальная папка репозитория\n\n**Git Clone / Pull:**\n- Папки нет или нет `.git` -> `git clone --branch`\n- Папка есть → обновляем remote URL, `git fetch`, `git checkout`, `git pull`\n\n⚠️ После операций сразу очищаем remote от кредов\n\n**Pull успешен?** — проверяем `exitCode === 0`. При ошибке показываем форму с текстом из stderr.",
"height": 964,
"width": 600,
"color": 5
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
5776,
496
],
"id": "523cc286-1ae6-4647-bbe3-f36e3164a3b1",
"name": "Зона: Подготовка и git pull"
},
{
"parameters": {
"content": "## Чтение файлов и определение статуса\n\nПолучаем список всех workflow из n8n через API, затем читаем все `.json` файлы из локальной папки репозитория.\n\nДля каждого файла определяем статус:\n- **NEW** — workflow с таким именем не найден в n8n\n- **CHANGED** — найден, но `updatedAt` отличается\n- **UNCHANGED** — найден и `updatedAt` совпадает — пропускаем\n\nДополнительно читаем поле **`active`** из JSON файла — оно определяет нужно ли активировать workflow после импорта (если разрешено глобально).\n\nФайлы которые не удалось распарсить пропускаются с выводом в console.log.",
"height": 964,
"width": 600,
"color": 6
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
6432,
496
],
"id": "6f3247a2-d433-4f75-ba92-832b33e5a584",
"name": "Зона: Чтение файлов"
},
{
"parameters": {
"content": "## Импорт через CLI\n\nДля каждого файла:\n\n**Switch — маршрутизация по статусу:**\n- `NEW` → импортируем через `n8n import:workflow`\n- `CHANGED` → импортируем через `n8n import:workflow`\n- `UNCHANGED` → пропускаем, сразу пишем результат\n\n**Разбор результата импорта** — парсим stdout, извлекаем статус успеха.\n\n**Нужно активировать?** — IF нода, все три условия должны выполняться:\n1. `activate = true` — пользователь разрешил активацию в форме\n2. `wfActive = true` — в JSON файле workflow был `\"active\": true`\n3. `success = true` — импорт прошёл без ошибок\n\n- Да → **Активировать workflow** (`n8n update:workflow --id=<id> --active=true`)\n- Нет → сразу **Записать результат**",
"height": 964,
"width": 1040,
"color": 4
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
7088,
496
],
"id": "e108e18e-20b7-4a4c-b6dd-c21c7ff13b52",
"name": "Зона: Поиск и импорт"
},
{
"parameters": {
"content": "## Итоговый отчёт\n\nПосле завершения цикла собираем статистику по всем файлам:\n- ✅ **Создано** — NEW + imported\n- 🔄 **Обновлено** — CHANGED + imported\n- **Пропущено** — UNCHANGED\n- ❌ **Ошибок** — не импортировались\n- ▶️ **Активировано** — workflow у которых `active: true` в файле и пользователь разрешил активацию\n\nВ детальном списке каждый активированный workflow помечается ▶️.\n\n⚠️ После импорта обязательно проверьте:\n1. Credentials в нодах — нужно перепривязать вручную\n2. Webhook URL — могли измениться",
"height": 964,
"width": 592,
"color": 3
},
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
8192,
496
],
"id": "8fc98c91-a6ab-42a3-af2f-b4f78b7b31af",
"name": "Зона: Результат"
},
{
"parameters": {
"formTitle": "Импорт Workflow из Git",
"formDescription": "Заполните данные для загрузки workflow из репозитория в n8n",
"formFields": {
"values": [
{
"fieldLabel": "Username",
"placeholder": "gitea_username",
"requiredField": true
},
{
"fieldLabel": "Personal Access Token",
"fieldType": "password",
"placeholder": "your_personal_access_token",
"requiredField": true
},
{
"fieldLabel": "Repository URL",
"placeholder": "https://<URL Repo>",
"defaultValue": "https://vcs.bbr.ru",
"requiredField": true
},
{
"fieldLabel": "Organization",
"placeholder": "Название организации",
"defaultValue": "event_forge",
"requiredField": true
},
{
"fieldLabel": "Repository Name",
"placeholder": "my-project",
"defaultValue": "workflows",
"requiredField": true
},
{
"fieldLabel": "Branch",
"fieldType": "dropdown",
"defaultValue": "main",
"fieldOptions": {
"values": [
{
"option": "main"
},
{
"option": "develop"
},
{
"option": "custom"
}
]
},
"requiredField": true
},
{
"fieldLabel": "Custom Branch Name",
"placeholder": "Полное имя ветки если выбран custom"
},
{
"fieldLabel": "Export Folder",
"placeholder": "/data/shared",
"defaultValue": "/data/shared",
"requiredField": true
},
{
"fieldLabel": "Активировать workflow",
"fieldType": "radio",
"defaultValue": "0",
"fieldOptions": {
"values": [
{
"option": "Да"
}
]
}
}
]
},
"responseMode": "lastNode",
"options": {
"path": "99ac2ec1-b958-4522-99f3-136a8f080ed1",
"customCss": ":root {\n --font-size-body: 13px;\n --font-size-label: 13px;\n --font-size-test-notice: 12px;\n --font-size-input: 13px;\n --font-size-header: 20px;\n --font-size-paragraph: 13px;\n --font-size-link: 12px;\n --font-size-error: 12px;\n --font-size-html-h1: 28px;\n --font-size-html-h2: 20px;\n --font-size-html-h3: 16px;\n --font-size-html-h4: 13px;\n --font-size-html-h5: 12px;\n --font-size-html-h6: 11px;\n --font-size-subheader: 13px;\n --color-background: #1b1f23;\n --color-test-notice-text: #d29922;\n --color-test-notice-bg: #272115;\n --color-test-notice-border: #5a4010;\n --color-card-bg: #22272e;\n --color-card-border: #373e47;\n --color-card-shadow: rgba(0,0,0,0.4);\n --color-link: #539bf5;\n --color-html-link: #539bf5;\n --color-header: #cdd9e5;\n --color-header-subtext: #768390;\n --color-label: #adbac7;\n --color-input-border: #444c56;\n --color-input-text: #cdd9e5;\n --color-input-bg: #1c2128;\n --color-focus-border: #539bf5;\n --color-submit-btn-bg: #347d39;\n --color-submit-btn-text: #cdd9e5;\n --color-error: #e5534b;\n --color-required: #e5534b;\n --color-clear-button-bg: #636e7b;\n --color-html-text: #adbac7;\n --border-radius-card: 6px;\n --border-radius-input: 6px;\n --border-radius-clear-btn: 50%;\n --card-border-radius: 6px;\n --padding-container-top: 24px;\n --padding-card: 24px;\n --padding-test-notice-vertical: 10px;\n --padding-test-notice-horizontal: 16px;\n --margin-bottom-card: 16px;\n --padding-form-input: 12px;\n --card-padding: 24px;\n --card-margin-bottom: 16px;\n --container-width: 448px;\n --submit-btn-height: 44px;\n --checkbox-size: 16px;\n --box-shadow-card: 0px 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.03);\n --opacity-placeholder: 0.35;\n}"
}
},
"id": "c6aa7731-f958-4895-93e0-99960dab73e9",
"name": "Форма запроса",
"type": "n8n-nodes-base.formTrigger",
"typeVersion": 2.2,
"position": [
5280,
912
],
"webhookId": "99ac2ec1-b958-4522-99f3-136a8f080ed1"
},
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
5280,
1136
],
"id": "0bb92efd-de10-4f1a-9372-9f41e9b74b8e",
"name": "Ручной запуск (отладка)"
},
{
"parameters": {
"mode": "raw",
"jsonOutput": "{\n \"Username\": \"gitea\",\n \"Personal Access Token\": \"0f276ee359b823464680037cb9b62f3558796f95\",\n \"Repository URL\": \"http://192.168.1.155:3001\",\n \"Repository Name\": \"workflows\",\n \"Organization\": \"event_forge\",\n \"Branch\": \"main\",\n \"Custom Branch Name\": \"\",\n \"Export Folder\": \"/data/shared\",\n \"Активировать workflow\": false\n}\n",
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
5456,
1136
],
"id": "1685976a-f037-4f4e-ad5b-6fed8c808fb8",
"name": "Тестовые данные"
},
{
"parameters": {
"jsCode": "const body = $input.first().json;\n\nconst username = (body['Username'] || '').trim();\nconst pat = (body['Personal Access Token'] || '').trim();\nconst repoUrl = (body['Repository URL'] || '').trim().replace(/\\/$/, '');\nconst repoName = (body['Repository Name'] || '').trim();\nconst org = (body['Organization'] || '').trim();\nconst exportFolder = (body['Export Folder'] || '/data/shared').trim().replace(/\\/$/, '');\n\n// Определяем ветку\nconst branchType = body['Branch'];\nconst customBranch = (body['Custom Branch Name'] || '').trim();\nlet branch;\nif (branchType === 'custom') {\n if (!customBranch) throw new Error('Custom Branch Name обязателен при выборе custom');\n branch = customBranch;\n} else {\n branch = branchType;\n}\n\nconst authUrl = repoUrl.replace(/^(https?:\\/\\/)/, `$1${encodeURIComponent(username)}:${encodeURIComponent(pat)}@`);\nconst remoteUrl = `${authUrl}/${org}/${repoName}.git`;\nconst cleanRemoteUrl = `${repoUrl}/${org}/${repoName}.git`;\nconst localFolder = `${exportFolder}/${org}/${repoName}`;\nconst activate = !!(body['Активировать workflow']);\n\nreturn [{\n json: {\n username,\n pat,\n repoUrl,\n repoName,\n org,\n branch,\n remoteUrl,\n cleanRemoteUrl,\n localFolder,\n activate,\n exportFolder,\n }\n}];"
},
"id": "74a303da-5491-4623-a884-298621a18122",
"name": "Подготовка переменных",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
5856,
912
]
},
{
"parameters": {
"command": "=LOCAL_FOLDER=\"{{ $json.localFolder }}\"\nREMOTE_URL=\"{{ $json.remoteUrl }}\"\nCLEAN_URL=\"{{ $json.cleanRemoteUrl }}\"\nBRANCH=\"{{ $json.branch }}\"\n\nif [ ! -d \"$LOCAL_FOLDER/.git\" ]; then\n # Папки нет или нет .git — клонируем заново\n rm -rf \"$LOCAL_FOLDER\"\n mkdir -p \"$(dirname $LOCAL_FOLDER)\"\n git clone --branch \"$BRANCH\" \"$REMOTE_URL\" \"$LOCAL_FOLDER\" 2>&1\n cd \"$LOCAL_FOLDER\"\nelse\n # Папка есть — обновляем remote и подтягиваем\n cd \"$LOCAL_FOLDER\"\n CURRENT_REMOTE=$(git remote get-url origin 2>/dev/null || echo \"\")\n if [ \"$CURRENT_REMOTE\" != \"$REMOTE_URL\" ]; then\n git remote set-url origin \"$REMOTE_URL\"\n fi\n git fetch origin 2>&1\n git checkout \"$BRANCH\" 2>&1 || git checkout -b \"$BRANCH\" \"origin/$BRANCH\" 2>&1\n git pull origin \"$BRANCH\" 2>&1\nfi\n\n# Очищаем remote от кредов сразу после операций\ngit remote set-url origin \"$CLEAN_URL\"\n\necho \"PULL_SUCCESS: $BRANCH | $LOCAL_FOLDER\""
},
"id": "5f20be07-92b1-4ba5-91b6-a95b0947e233",
"name": "Git Clone / Pull",
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
6032,
912
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "pull-success-check",
"leftValue": "={{ $json.exitCode }}",
"rightValue": 0,
"operator": {
"type": "number",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
6208,
912
],
"id": "010d52da-670b-40a0-9177-fb87cb9fa4bf",
"name": "Pull успешен?"
},
{
"parameters": {
"operation": "completion",
"completionTitle": "Ошибка git",
"completionMessage": "=Не удалось получить данные из репозитория:\n\n{{ $json.stderr }}",
"limitWaitTime": true,
"resumeUnit": "minutes",
"options": {
"customCss": ":root {\n --font-size-body: 13px;\n --font-size-label: 13px;\n --font-size-test-notice: 12px;\n --font-size-input: 13px;\n --font-size-header: 20px;\n --font-size-paragraph: 13px;\n --font-size-link: 12px;\n --font-size-error: 12px;\n --font-size-html-h1: 28px;\n --font-size-html-h2: 20px;\n --font-size-html-h3: 16px;\n --font-size-html-h4: 13px;\n --font-size-html-h5: 12px;\n --font-size-html-h6: 11px;\n --font-size-subheader: 13px;\n --color-background: #1b1f23;\n --color-test-notice-text: #d29922;\n --color-test-notice-bg: #272115;\n --color-test-notice-border: #5a4010;\n --color-card-bg: #22272e;\n --color-card-border: #373e47;\n --color-card-shadow: rgba(0,0,0,0.4);\n --color-link: #539bf5;\n --color-html-link: #539bf5;\n --color-header: #cdd9e5;\n --color-header-subtext: #768390;\n --color-label: #adbac7;\n --color-input-border: #444c56;\n --color-input-text: #cdd9e5;\n --color-input-bg: #1c2128;\n --color-focus-border: #539bf5;\n --color-submit-btn-bg: #347d39;\n --color-submit-btn-text: #cdd9e5;\n --color-error: #e5534b;\n --color-required: #e5534b;\n --color-clear-button-bg: #636e7b;\n --color-html-text: #adbac7;\n --border-radius-card: 6px;\n --border-radius-input: 6px;\n --border-radius-clear-btn: 50%;\n --card-border-radius: 6px;\n --padding-container-top: 24px;\n --padding-card: 24px;\n --padding-test-notice-vertical: 10px;\n --padding-test-notice-horizontal: 16px;\n --margin-bottom-card: 16px;\n --padding-form-input: 12px;\n --card-padding: 24px;\n --card-margin-bottom: 16px;\n --container-width: 448px;\n --submit-btn-height: 44px;\n --checkbox-size: 16px;\n --box-shadow-card: 0px 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.03);\n --opacity-placeholder: 0.35;\n}"
}
},
"type": "n8n-nodes-base.form",
"typeVersion": 2.5,
"position": [
6208,
1136
],
"id": "d458c7f0-f038-4bc1-8259-5581f6fe894b",
"name": "Форма: Ошибка git",
"webhookId": "b22c33d4-e55f-6677-b88c-99d00e11f222"
},
{
"parameters": {
"jsCode": "const fs = require('fs');\nconst path = require('path');\nconst localFolder = $('Подготовка переменных').first().json.localFolder;\n\nlet files;\ntry {\n files = fs.readdirSync(localFolder).filter(f => f.endsWith('.json'));\n} catch (e) {\n throw new Error(`Не удалось прочитать папку ${localFolder}: ${e.message}`);\n}\n\nif (files.length === 0) {\n throw new Error(`В папке ${localFolder} не найдено JSON файлов`);\n}\n\nconst allWorkflows = $input.all().map(item => item.json);\n\nconst results = [];\n\nfor (const fileName of files) {\n const filePath = path.join(localFolder, fileName);\n try {\n const raw = fs.readFileSync(filePath, 'utf8');\n const parsed = JSON.parse(raw);\n const wfData = Array.isArray(parsed) ? parsed[0] : parsed;\n\n if (!wfData || !wfData.name) {\n console.log(`SKIP: ${fileName} — нет поля name`);\n continue;\n }\n\n const existing = allWorkflows.find(wf => wf.name === wfData.name);\n\n let fileStatus;\n if (!existing) {\n fileStatus = 'NEW';\n } else {\n fileStatus = existing.updatedAt === wfData.updatedAt ? 'UNCHANGED' : 'CHANGED';\n }\n\n // Признак активации берём из файла\n const wfActive = !!(wfData.active);\n\n results.push({\n json: {\n fileName,\n filePath,\n fileStatus,\n workflowName: wfData.name,\n workflowData: wfData,\n existingId: existing ? existing.id : null,\n wfActive,\n }\n });\n\n console.log(`${fileStatus} [active=${wfActive}]: ${fileName}`);\n } catch (e) {\n console.log(`SKIP: ${fileName} — ошибка парсинга: ${e.message}`);\n }\n}\n\nif (results.length === 0) {\n throw new Error('Ни один файл не удалось распарсить');\n}\n\nconsole.log(`Прочитано файлов: ${results.length} из ${files.length}`);\nreturn results;"
},
"id": "4e566ce2-e54b-4c56-92ed-ff0eefbad2bc",
"name": "Прочитать файлы из папки",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
6800,
912
]
},
{
"parameters": {
"options": {}
},
"id": "d56efecf-5f77-4cd8-a149-4f3fefdd0faa",
"name": "Цикл по файлам",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
7152,
912
]
},
{
"parameters": {
"jsCode": "const results = $input.all().map(item => item.json);\n\nconst total = results.length;\nconst created = results.filter(r => r.fileStatus === 'NEW' && r.imported).length;\nconst updated = results.filter(r => r.fileStatus === 'CHANGED' && r.imported).length;\nconst skipped = results.filter(r => r.fileStatus === 'UNCHANGED').length;\nconst failed = results.filter(r => r.fileStatus !== 'UNCHANGED' && !r.imported).length;\nconst activated = results.filter(r => r.activated).length;\n\nconst details = results.map(r => {\n if (r.fileStatus === 'UNCHANGED') return ` Пропущен: ${r.workflowName}`;\n const act = r.activated ? ' ▶️' : '';\n if (r.imported && r.fileStatus === 'NEW') return `✅ Создан: ${r.workflowName}${act}`;\n if (r.imported && r.fileStatus === 'CHANGED') return `🔄 Обновлён: ${r.workflowName}${act}`;\n return `❌ Ошибка: ${r.workflowName}`;\n}).join('\\n');\n\nconst activatedLine = activated > 0 ? ` | ▶️ Активировано: ${activated}` : '';\n\nreturn [{\n json: {\n total,\n created,\n updated,\n skipped,\n failed,\n activated,\n summary: `Всего: ${total} | Создано: ${created} | Обновлено: ${updated} | Пропущено: ${skipped} | Ошибок: ${failed}${activatedLine}`,\n details,\n }\n}];"
},
"id": "2ef6e478-b1b5-401f-8d37-d0c252e4c651",
"name": "Сформировать сводку",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
8368,
912
]
},
{
"parameters": {
"operation": "completion",
"completionTitle": "Импорт завершён",
"completionMessage": "={{ $json.summary }}\n\n{{ $json.details }}\n\n⚠️ Не забудьте перепривязать credentials в импортированных workflow.",
"limitWaitTime": true,
"resumeUnit": "minutes",
"options": {
"customCss": ":root {\n --font-size-body: 13px;\n --font-size-label: 13px;\n --font-size-test-notice: 12px;\n --font-size-input: 13px;\n --font-size-header: 20px;\n --font-size-paragraph: 13px;\n --font-size-link: 12px;\n --font-size-error: 12px;\n --font-size-html-h1: 28px;\n --font-size-html-h2: 20px;\n --font-size-html-h3: 16px;\n --font-size-html-h4: 13px;\n --font-size-html-h5: 12px;\n --font-size-html-h6: 11px;\n --font-size-subheader: 13px;\n --color-background: #1b1f23;\n --color-test-notice-text: #d29922;\n --color-test-notice-bg: #272115;\n --color-test-notice-border: #5a4010;\n --color-card-bg: #22272e;\n --color-card-border: #373e47;\n --color-card-shadow: rgba(0,0,0,0.4);\n --color-link: #539bf5;\n --color-html-link: #539bf5;\n --color-header: #cdd9e5;\n --color-header-subtext: #768390;\n --color-label: #adbac7;\n --color-input-border: #444c56;\n --color-input-text: #cdd9e5;\n --color-input-bg: #1c2128;\n --color-focus-border: #539bf5;\n --color-submit-btn-bg: #347d39;\n --color-submit-btn-text: #cdd9e5;\n --color-error: #e5534b;\n --color-required: #e5534b;\n --color-clear-button-bg: #636e7b;\n --color-html-text: #adbac7;\n --border-radius-card: 6px;\n --border-radius-input: 6px;\n --border-radius-clear-btn: 50%;\n --card-border-radius: 6px;\n --padding-container-top: 24px;\n --padding-card: 24px;\n --padding-test-notice-vertical: 10px;\n --padding-test-notice-horizontal: 16px;\n --margin-bottom-card: 16px;\n --padding-form-input: 12px;\n --card-padding: 24px;\n --card-margin-bottom: 16px;\n --container-width: 448px;\n --submit-btn-height: 44px;\n --checkbox-size: 16px;\n --box-shadow-card: 0px 4px 16px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.03);\n --opacity-placeholder: 0.35;\n}"
}
},
"type": "n8n-nodes-base.form",
"typeVersion": 2.5,
"position": [
8544,
912
],
"id": "e615b2d1-d874-4288-9fac-ad4ba252674e",
"name": "Форма: Импорт завершён",
"webhookId": "c33d44e5-f66a-7788-c99d-00e11f2233g3"
},
{
"parameters": {
"command": "=n8n import:workflow --input=\"{{ $json.filePath }}\"\necho \"IMPORTED: {{ $json.workflowName }}\""
},
"type": "n8n-nodes-base.executeCommand",
"typeVersion": 1,
"position": [
7760,
976
],
"id": "87616442-e9c5-47fd-bc3a-0993b7f02408",
"name": "Импортировать workflow"
},
{
"parameters": {
"jsCode": "const item = $input.first().json;\nconst stdout = item.stdout || '';\n\n// Парсим строки лога — каждая строка это JSON\nconst lines = stdout.split('\\n').filter(Boolean);\nconst logMessages = [];\n\nfor (const line of lines) {\n try {\n const parsed = JSON.parse(line);\n logMessages.push(parsed.message);\n } catch (e) {\n // не JSON строка — обычный текст (IMPORTED: ...)\n }\n}\n\nconst success = item.exitCode === 0;\nconst workflowName = stdout.match(/IMPORTED: (.+)/)?.[1]?.trim() || 'неизвестно';\n\nreturn [{\n json: {\n workflowName,\n success,\n message: logMessages.join(' → '),\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
7184,
1200
],
"id": "35a93ed9-385d-4372-9c97-3b0cf0e56598",
"name": "Разбор результата импорта"
},
{
"parameters": {
"filters": {},
"requestOptions": {}
},
"type": "n8n-nodes-base.n8n",
"typeVersion": 1,
"position": [
6576,
912
],
"id": "3695a508-3511-485d-b641-b8e8202c39fc",
"name": "Получить все workflow n8n",
"credentials": {
"n8nApi": {
"id": "aLPzwPxLHLLpPJIw",
"name": "n8n local"
}
}
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"leftValue": "={{ $json.fileStatus }}",
"rightValue": "NEW",
"operator": {
"type": "string",
"operation": "equals"
},
"id": "e34cf093-cfc1-4ad6-98bd-6fa71911bd8c"
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "NEW"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "5fc2aed6-0795-4d31-a763-13269a9d889c",
"leftValue": "={{ $json.fileStatus }}",
"rightValue": "CHANGED",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "CHANGED"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "8b126f2d-d9bc-479a-b1ad-bdc7d429ca32",
"leftValue": "={{ $json.fileStatus }}",
"rightValue": "UNCHANGED",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "UNCHANGED"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
7360,
976
],
"id": "3d8141e0-fdb7-464e-8f9e-87cef96b0f37",
"name": "Маршрутизация по статусу"
},
{
"parameters": {
"jsCode": "const fromSwitch = $('Маршрутизация по статусу').item.json;\nconst fromImport = $('Разбор результата импорта').item.json;\n\n// Активация через REST API: успех = нода вернула объект с active=true\nlet activated = false;\nlet activatedId = null;\ntry {\n const out = $('Активировать workflow').item.json;\n activated = out && out.active === true;\n activatedId = out && out.id ? out.id : null;\n} catch (e) {\n // Ветка FALSE у IF — ноду активации не проходили\n}\n\nreturn [{\n json: {\n fileName: fromSwitch.fileName,\n workflowName: fromSwitch.workflowName,\n fileStatus: fromSwitch.fileStatus,\n existingId: fromSwitch.existingId,\n activatedId,\n imported: fromImport.success,\n activated,\n message: fromImport.message,\n importedAt: new Date().toISOString(),\n }\n}];\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
7968,
1200
],
"id": "08d1f524-a66f-4804-9d40-6dd98da463f8",
"name": "Записать результат"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "activate-global",
"leftValue": "={{ $('Подготовка переменных').first().json.activate }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
},
{
"id": "activate-wf-active",
"leftValue": "={{ $('Маршрутизация по статусу').item.json.wfActive }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
},
{
"id": "activate-success",
"leftValue": "={{ $json.success }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
7360,
1200
],
"id": "c98aa5ee-1df4-441d-9648-539f841a3794",
"name": "Нужно активировать?"
},
{
"parameters": {
"operation": "activate",
"workflowId": "={{ $json.id }}",
"additionalFields": {},
"requestOptions": {}
},
"type": "n8n-nodes-base.n8n",
"typeVersion": 1,
"position": [
7776,
1200
],
"id": "8c91c94d-c6af-4249-a226-d280cb7692c4",
"name": "Активировать workflow",
"credentials": {
"n8nApi": {
"id": "aLPzwPxLHLLpPJIw",
"name": "n8n local"
}
},
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const fromSwitch = $('Маршрутизация по статусу').item.json;\n\nreturn [{\n json: {\n fileName: fromSwitch.fileName,\n workflowName: fromSwitch.workflowName,\n fileStatus: 'UNCHANGED',\n existingId: fromSwitch.existingId,\n imported: false,\n activated: false,\n message: 'Пропущен — без изменений',\n importedAt: new Date().toISOString(),\n }\n}];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
7968,
1024
],
"id": "1410b0c4-1ee9-4b22-b48a-017c8f362549",
"name": "Записать результат (пропущен)"
},
{
"parameters": {
"filters": {
"name": "={{ $('Маршрутизация по статусу').item.json.workflowName }}"
},
"requestOptions": {}
},
"type": "n8n-nodes-base.n8n",
"typeVersion": 1,
"position": [
7568,
1200
],
"id": "4baec163-2102-4114-982b-eba38a38beca",
"name": "Найти id после импорта",
"credentials": {
"n8nApi": {
"id": "aLPzwPxLHLLpPJIw",
"name": "n8n local"
}
}
}
],
"pinData": {},
"connections": {
"Форма запроса": {
"main": [
[
{
"node": "Подготовка переменных",
"type": "main",
"index": 0
}
]
]
},
"Ручной запуск (отладка)": {
"main": [
[
{
"node": "Тестовые данные",
"type": "main",
"index": 0
}
]
]
},
"Тестовые данные": {
"main": [
[
{
"node": "Подготовка переменных",
"type": "main",
"index": 0
}
]
]
},
"Подготовка переменных": {
"main": [
[
{
"node": "Git Clone / Pull",
"type": "main",
"index": 0
}
]
]
},
"Git Clone / Pull": {
"main": [
[
{
"node": "Pull успешен?",
"type": "main",
"index": 0
}
]
]
},
"Pull успешен?": {
"main": [
[
{
"node": "Получить все workflow n8n",
"type": "main",
"index": 0
}
],
[
{
"node": "Форма: Ошибка git",
"type": "main",
"index": 0
}
]
]
},
"Прочитать файлы из папки": {
"main": [
[
{
"node": "Цикл по файлам",
"type": "main",
"index": 0
}
]
]
},
"Цикл по файлам": {
"main": [
[
{
"node": "Сформировать сводку",
"type": "main",
"index": 0
}
],
[
{
"node": "Маршрутизация по статусу",
"type": "main",
"index": 0
}
]
]
},
"Сформировать сводку": {
"main": [
[
{
"node": "Форма: Импорт завершён",
"type": "main",
"index": 0
}
]
]
},
"Импортировать workflow": {
"main": [
[
{
"node": "Разбор результата импорта",
"type": "main",
"index": 0
}
]
]
},
"Разбор результата импорта": {
"main": [
[
{
"node": "Нужно активировать?",
"type": "main",
"index": 0
}
]
]
},
"Получить все workflow n8n": {
"main": [
[
{
"node": "Прочитать файлы из папки",
"type": "main",
"index": 0
}
]
]
},
"Маршрутизация по статусу": {
"main": [
[
{
"node": "Импортировать workflow",
"type": "main",
"index": 0
}
],
[
{
"node": "Импортировать workflow",
"type": "main",
"index": 0
}
],
[
{
"node": "Записать результат (пропущен)",
"type": "main",
"index": 0
}
]
]
},
"Записать результат": {
"main": [
[
{
"node": "Цикл по файлам",
"type": "main",
"index": 0
}
]
]
},
"Нужно активировать?": {
"main": [
[
{
"node": "Найти id после импорта",
"type": "main",
"index": 0
}
],
[
{
"node": "Записать результат",
"type": "main",
"index": 0
}
]
]
},
"Активировать workflow": {
"main": [
[
{
"node": "Записать результат",
"type": "main",
"index": 0
}
]
]
},
"Записать результат (пропущен)": {
"main": [
[
{
"node": "Цикл по файлам",
"type": "main",
"index": 0
}
]
]
},
"Найти id после импорта": {
"main": [
[
{
"node": "Активировать workflow",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "cef6215b-db51-43c0-ac6c-65570a2c30dc",
"meta": {
"instanceId": "f720a996c27dff6663621d1fe2231dde9f00009fb064ea1db630bfc1dc798ddd"
},
"id": "0VqMxJC45kM28mgK",
"tags": []
}