mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 21:37:32 -08:00
feat(HTTP Request Node): Redesign and add the ability to import cURL commands (#3860)
* ⚡ Initial commit * 👕 Fix linting issue * ⚡ Add import button * ⚡ Remove ligh versioning * ⚡ Improvements * ⚡ Improvements * 🔥 Remove HttpRequest2 file used for testing * 🐛 Fix building issue * ⚡ Small improvement * 👕 Fix linting issue * 🔥 Remove HttpRequest2 from loader * ⚡ Update package-lock.json * ⚡ Improvements * ⚡ Small change * 🐛 Fix issue retrieving splitIntoItems * 🐛 Fix issue retrieving neverError parameter * 🐛 Fix issue with displayOptions * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Move cURL section to its own component * ⚡ Improvements * ⚡ Improvements * ⚡ Add fix for batching in all versions * ⚡ Add notice to cURL modal * 🔥 Remove comments * ⚡ Improvements * ⚡ Type curl-to-json endpoint * ⚡ Fix typo * 🔥 Remove console.logs * ⚡ Fix typo in curl-to-json endpoint * ⚡ Improvements * ⚡ Improvements * ⚡ Update package-lock.json * ⚡ Rename import modal constant * ⚡ Add return types to methods * ⚡ Add CSS modules to ImportParameter component * ⚡ Rename ImportParameter component to use kebab-case * ⚡ Improvements * ⚡ update package-lock.json * ⚡ Fix linting issues * Fix issue with css reference in ImportParameter component * ⚡ Small improvements * ⚡ Rename redirects to redirect * ⚡ Allow to set multiple parameters on valueChanged * 👕 Fix linting issue * 🐛 Add mistakenly removed openExistingCredentials * ⚡ Improve curl regex * ⚡ Keep headers as defined in the cURL command * ⚡ Account for all protocols supported by cURL * ⚡ Add tests * 🔥 Remove unnecessary lines * ⚡ Add more testing * ⚡ Add noDataExpression to dependent fields * 🐛 Fix bug not handling multipart-form data correctly * ⚡ Change error messages * 🐛 Fix response format string for empty values * Fix typo
This commit is contained in:
parent
5526057efc
commit
f37d6ba03b
297
package-lock.json
generated
297
package-lock.json
generated
|
@ -2485,6 +2485,57 @@
|
||||||
"node": ">=0.1.90"
|
"node": ">=0.1.90"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@curlconverter/yargs": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs/-/yargs-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@curlconverter/yargs-parser": "^0.0.1",
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs-parser": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs-parser/-/yargs-parser-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs/node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs/node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs/node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@dabh/diagnostics": {
|
"node_modules/@dabh/diagnostics": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
|
@ -9038,6 +9089,11 @@
|
||||||
"vite": "^2.1.5 || ^3.0.5"
|
"vite": "^2.1.5 || ^3.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/a-sync-waterfall": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
||||||
|
},
|
||||||
"node_modules/abab": {
|
"node_modules/abab": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||||
|
@ -9808,8 +9864,7 @@
|
||||||
"node_modules/asap": {
|
"node_modules/asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/asn1": {
|
"node_modules/asn1": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.6",
|
||||||
|
@ -13788,6 +13843,59 @@
|
||||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/curlconverter": {
|
||||||
|
"version": "3.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz",
|
||||||
|
"integrity": "sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@curlconverter/yargs": "^0.0.2",
|
||||||
|
"cookie": "^0.4.1",
|
||||||
|
"jsesc": "^3.0.2",
|
||||||
|
"nunjucks": "^3.2.3",
|
||||||
|
"query-string": "^7.0.1",
|
||||||
|
"string.prototype.startswith": "^1.0.0",
|
||||||
|
"yamljs": "^0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"curlconverter": "bin/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/curlconverter/node_modules/jsesc": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
|
||||||
|
"bin": {
|
||||||
|
"jsesc": "bin/jsesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/curlconverter/node_modules/query-string": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
|
||||||
|
"dependencies": {
|
||||||
|
"decode-uri-component": "^0.2.0",
|
||||||
|
"filter-obj": "^1.1.0",
|
||||||
|
"split-on-first": "^1.0.0",
|
||||||
|
"strict-uri-encode": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/curlconverter/node_modules/strict-uri-encode": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/currently-unhandled": {
|
"node_modules/currently-unhandled": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||||
|
@ -13958,7 +14066,6 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
|
@ -17037,6 +17144,14 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/filter-obj": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
@ -28003,6 +28118,38 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nunjucks": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"a-sync-waterfall": "^1.0.0",
|
||||||
|
"asap": "^2.0.3",
|
||||||
|
"commander": "^5.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nunjucks-precompile": "bin/precompile"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"chokidar": "^3.3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"chokidar": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nunjucks/node_modules/commander": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nwsapi": {
|
"node_modules/nwsapi": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
||||||
|
@ -33584,6 +33731,14 @@
|
||||||
"node": ">=4.5"
|
"node": ">=4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split-on-first": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/split-string": {
|
"node_modules/split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
|
@ -34265,6 +34420,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string.prototype.startswith": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==",
|
||||||
|
"dependencies": {
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string.prototype.trimend": {
|
"node_modules/string.prototype.trimend": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||||
|
@ -41556,6 +41720,7 @@
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
|
"curlconverter": "^3.0.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-openapi-validator": "^4.13.6",
|
"express-openapi-validator": "^4.13.6",
|
||||||
|
@ -44294,6 +44459,47 @@
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
|
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
|
||||||
},
|
},
|
||||||
|
"@curlconverter/yargs": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs/-/yargs-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==",
|
||||||
|
"requires": {
|
||||||
|
"@curlconverter/yargs-parser": "^0.0.1",
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
|
},
|
||||||
|
"y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@curlconverter/yargs-parser": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs-parser/-/yargs-parser-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA=="
|
||||||
|
},
|
||||||
"@dabh/diagnostics": {
|
"@dabh/diagnostics": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
|
@ -50563,6 +50769,11 @@
|
||||||
"integrity": "sha512-HlhVMhrOpBHlkkdSm5wQ6YN65ZqdF9YqP7HS1at6NjQgEOIhBiP7tRZ+dYM3ymztKAX0ovhyKCY9ZnQ/GMK0Qg==",
|
"integrity": "sha512-HlhVMhrOpBHlkkdSm5wQ6YN65ZqdF9YqP7HS1at6NjQgEOIhBiP7tRZ+dYM3ymztKAX0ovhyKCY9ZnQ/GMK0Qg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"a-sync-waterfall": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
||||||
|
},
|
||||||
"abab": {
|
"abab": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||||
|
@ -51132,8 +51343,7 @@
|
||||||
"asap": {
|
"asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.6",
|
||||||
|
@ -54331,6 +54541,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"curlconverter": {
|
||||||
|
"version": "3.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz",
|
||||||
|
"integrity": "sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==",
|
||||||
|
"requires": {
|
||||||
|
"@curlconverter/yargs": "^0.0.2",
|
||||||
|
"cookie": "^0.4.1",
|
||||||
|
"jsesc": "^3.0.2",
|
||||||
|
"nunjucks": "^3.2.3",
|
||||||
|
"query-string": "^7.0.1",
|
||||||
|
"string.prototype.startswith": "^1.0.0",
|
||||||
|
"yamljs": "^0.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jsesc": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="
|
||||||
|
},
|
||||||
|
"query-string": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
|
||||||
|
"requires": {
|
||||||
|
"decode-uri-component": "^0.2.0",
|
||||||
|
"filter-obj": "^1.1.0",
|
||||||
|
"split-on-first": "^1.0.0",
|
||||||
|
"strict-uri-encode": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strict-uri-encode": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"currently-unhandled": {
|
"currently-unhandled": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||||
|
@ -54463,8 +54710,7 @@
|
||||||
"decode-uri-component": {
|
"decode-uri-component": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"dedent": {
|
"dedent": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
|
@ -56777,6 +57023,11 @@
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"filter-obj": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
|
||||||
|
},
|
||||||
"finalhandler": {
|
"finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
@ -64791,6 +65042,7 @@
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
|
"curlconverter": "^3.0.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-openapi-validator": "^4.13.6",
|
"express-openapi-validator": "^4.13.6",
|
||||||
|
@ -66266,6 +66518,23 @@
|
||||||
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
|
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"nunjucks": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==",
|
||||||
|
"requires": {
|
||||||
|
"a-sync-waterfall": "^1.0.0",
|
||||||
|
"asap": "^2.0.3",
|
||||||
|
"commander": "^5.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"nwsapi": {
|
"nwsapi": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
||||||
|
@ -70672,6 +70941,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz",
|
||||||
"integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg=="
|
"integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg=="
|
||||||
},
|
},
|
||||||
|
"split-on-first": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
|
||||||
|
},
|
||||||
"split-string": {
|
"split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
|
@ -71205,6 +71479,15 @@
|
||||||
"es-abstract": "^1.19.1"
|
"es-abstract": "^1.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string.prototype.startswith": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==",
|
||||||
|
"requires": {
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"string.prototype.trimend": {
|
"string.prototype.trimend": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
|
"curlconverter": "^3.0.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-openapi-validator": "^4.13.6",
|
"express-openapi-validator": "^4.13.6",
|
||||||
|
|
5
packages/cli/src/CurlConverter.d.ts
vendored
Normal file
5
packages/cli/src/CurlConverter.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
/* eslint-disable import/no-default-export */
|
||||||
|
declare module 'curlconverter' {
|
||||||
|
export function toJsonString(data: string): string;
|
||||||
|
}
|
466
packages/cli/src/CurlConverterHelper.ts
Normal file
466
packages/cli/src/CurlConverterHelper.ts
Normal file
|
@ -0,0 +1,466 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import curlconverter from 'curlconverter';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
|
||||||
|
interface CurlJson {
|
||||||
|
url: string;
|
||||||
|
raw_url?: string;
|
||||||
|
method: string;
|
||||||
|
contentType?: string;
|
||||||
|
cookies?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
auth?: {
|
||||||
|
user: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
headers?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
files?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
queries: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
data?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Parameter {
|
||||||
|
parameterType?: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpNodeParameters {
|
||||||
|
url?: string;
|
||||||
|
method: string;
|
||||||
|
sendBody?: boolean;
|
||||||
|
authentication: string;
|
||||||
|
contentType?: 'form-urlencoded' | 'multipart-form-data' | 'json' | 'raw' | 'binaryData';
|
||||||
|
rawContentType?: string;
|
||||||
|
specifyBody?: 'json' | 'keypair';
|
||||||
|
bodyParameters?: {
|
||||||
|
parameters: Parameter[];
|
||||||
|
};
|
||||||
|
jsonBody?: object;
|
||||||
|
options: {
|
||||||
|
allowUnauthorizedCerts?: boolean;
|
||||||
|
proxy?: string;
|
||||||
|
timeout?: number;
|
||||||
|
redirect: {
|
||||||
|
redirect: {
|
||||||
|
followRedirects?: boolean;
|
||||||
|
maxRedirects?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
response: {
|
||||||
|
fullResponse?: boolean;
|
||||||
|
responseFormat?: string;
|
||||||
|
outputPropertyName?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
sendHeaders?: boolean;
|
||||||
|
headerParameters?: {
|
||||||
|
parameters: Parameter[];
|
||||||
|
};
|
||||||
|
sendQuery?: boolean;
|
||||||
|
queryParameters?: {
|
||||||
|
parameters: Parameter[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpNodeHeaders = Pick<HttpNodeParameters, 'sendHeaders' | 'headerParameters'>;
|
||||||
|
|
||||||
|
type HttpNodeQueries = Pick<HttpNodeParameters, 'sendQuery' | 'queryParameters'>;
|
||||||
|
|
||||||
|
enum ContentTypes {
|
||||||
|
applicationJson = 'application/json',
|
||||||
|
applicationFormUrlEncoded = 'application/x-www-form-urlencoded',
|
||||||
|
applicationMultipart = 'multipart/form-data',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_CONTENT_TYPES = [
|
||||||
|
ContentTypes.applicationJson,
|
||||||
|
ContentTypes.applicationFormUrlEncoded,
|
||||||
|
ContentTypes.applicationMultipart,
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONTENT_TYPE_KEY = 'content-type';
|
||||||
|
|
||||||
|
const FOLLOW_REDIRECT_FLAGS = ['--location', '-L'];
|
||||||
|
|
||||||
|
const MAX_REDIRECT_FLAG = '--max-redirs';
|
||||||
|
|
||||||
|
const PROXY_FLAGS = ['-x', '--proxy'];
|
||||||
|
|
||||||
|
const INCLUDE_HEADERS_IN_OUTPUT_FLAGS = ['-i', '--include'];
|
||||||
|
|
||||||
|
const REQUEST_FLAGS = ['-X', '--request'];
|
||||||
|
|
||||||
|
const TIMEOUT_FLAGS = ['--connect-timeout'];
|
||||||
|
|
||||||
|
const DOWNLOAD_FILE_FLAGS = ['-O', '-o'];
|
||||||
|
|
||||||
|
const IGNORE_SSL_ISSUES_FLAGS = ['-k', '--insecure'];
|
||||||
|
|
||||||
|
const curlToJson = (curlCommand: string): CurlJson => {
|
||||||
|
// eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse
|
||||||
|
return JSON.parse(curlconverter.toJsonString(curlCommand)) as CurlJson;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isContentType = (headers: CurlJson['headers'], contentType: ContentTypes): boolean => {
|
||||||
|
return get(headers, CONTENT_TYPE_KEY) === contentType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isJsonRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (isContentType(curlJson.headers, ContentTypes.applicationJson)) return true;
|
||||||
|
|
||||||
|
if (curlJson.data) {
|
||||||
|
const bodyKey = Object.keys(curlJson.data)[0];
|
||||||
|
try {
|
||||||
|
JSON.parse(bodyKey);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormUrlEncodedRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (isContentType(curlJson.headers, ContentTypes.applicationFormUrlEncoded)) return true;
|
||||||
|
if (curlJson.data && !curlJson.files) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMultipartRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (isContentType(curlJson.headers, ContentTypes.applicationMultipart)) return true;
|
||||||
|
|
||||||
|
// only multipart/form-data request include files
|
||||||
|
if (curlJson.files) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBinaryRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (curlJson?.headers?.[CONTENT_TYPE_KEY]) {
|
||||||
|
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY];
|
||||||
|
return ['image', 'video', 'audio'].some((d) => contentType.includes(d));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanatizeCurlCommand = (curlCommand: string) =>
|
||||||
|
curlCommand
|
||||||
|
.replace(/\r\n/g, ' ')
|
||||||
|
.replace(/\n/g, ' ')
|
||||||
|
.replace(/\\/g, ' ')
|
||||||
|
.replace(/[ ]{2,}/g, ' ');
|
||||||
|
|
||||||
|
const toKeyValueArray = ([key, value]: string[]) => ({ name: key, value });
|
||||||
|
|
||||||
|
const extractHeaders = (headers: CurlJson['headers'] = {}): HttpNodeHeaders => {
|
||||||
|
const emptyHeaders = !Object.keys(headers).length;
|
||||||
|
|
||||||
|
const onlyContentTypeHeaderDefined =
|
||||||
|
Object.keys(headers).length === 1 && headers[CONTENT_TYPE_KEY] !== undefined;
|
||||||
|
|
||||||
|
if (emptyHeaders || onlyContentTypeHeaderDefined) return { sendHeaders: false };
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendHeaders: true,
|
||||||
|
headerParameters: {
|
||||||
|
parameters: Object.entries(headers)
|
||||||
|
.map(toKeyValueArray)
|
||||||
|
.filter((parameter) => parameter.name !== CONTENT_TYPE_KEY),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractQueries = (queries: CurlJson['queries'] = {}): HttpNodeQueries => {
|
||||||
|
const emptyQueries = !Object.keys(queries).length;
|
||||||
|
|
||||||
|
if (emptyQueries) return { sendQuery: false };
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendQuery: true,
|
||||||
|
queryParameters: {
|
||||||
|
parameters: Object.entries(queries).map(toKeyValueArray),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractJson = (body: CurlJson['data']) =>
|
||||||
|
//@ts-ignore
|
||||||
|
// eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse
|
||||||
|
JSON.parse(Object.keys(body)[0]) as { [key: string]: string };
|
||||||
|
|
||||||
|
const jsonBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => {
|
||||||
|
const data = extractJson(body);
|
||||||
|
return Object.entries(data).map(toKeyValueArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
const multipartToNodeParameters = (
|
||||||
|
body: CurlJson['data'] = {},
|
||||||
|
files: CurlJson['files'] = {},
|
||||||
|
): Parameter[] | [] => {
|
||||||
|
return [
|
||||||
|
...Object.entries(body)
|
||||||
|
.map(toKeyValueArray)
|
||||||
|
.map((e) => ({ parameterType: 'formData', ...e })),
|
||||||
|
...Object.entries(files)
|
||||||
|
.map(toKeyValueArray)
|
||||||
|
.map((e) => ({ parameterType: 'formBinaryData', ...e })),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyValueBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => {
|
||||||
|
return Object.entries(body).map(toKeyValueArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
const lowerCaseContentTypeKey = (obj: { [x: string]: string }): void => {
|
||||||
|
const regex = new RegExp(CONTENT_TYPE_KEY, 'gi');
|
||||||
|
|
||||||
|
const contentTypeKey = Object.keys(obj).find((key) => {
|
||||||
|
const group = Array.from(key.matchAll(regex));
|
||||||
|
if (group.length) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!contentTypeKey) return;
|
||||||
|
|
||||||
|
const value = obj[contentTypeKey];
|
||||||
|
delete obj[contentTypeKey];
|
||||||
|
obj[CONTENT_TYPE_KEY] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeBasicAuthentication = (username: string, password: string) =>
|
||||||
|
Buffer.from(`${username}:${password}`).toString('base64');
|
||||||
|
|
||||||
|
const jsonHasNestedObjects = (json: { [key: string]: string | number | object }) =>
|
||||||
|
Object.values(json).some((e) => typeof e === 'object');
|
||||||
|
|
||||||
|
const extractGroup = (curlCommand: string, regex: RegExp) => curlCommand.matchAll(regex);
|
||||||
|
|
||||||
|
const mapCookies = (cookies: CurlJson['cookies']): { cookie: string } | {} => {
|
||||||
|
if (!cookies) return {};
|
||||||
|
|
||||||
|
const cookiesValues = Object.entries(cookies).reduce(
|
||||||
|
(accumulator: string, entry: [string, string]) => {
|
||||||
|
accumulator += `${entry[0]}=${entry[1]};`;
|
||||||
|
return accumulator;
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cookiesValues) return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
cookie: cookiesValues,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toHttpNodeParameters = (curlCommand: string): HttpNodeParameters => {
|
||||||
|
const curlJson = curlToJson(curlCommand);
|
||||||
|
|
||||||
|
if (!curlJson.headers) curlJson.headers = {};
|
||||||
|
|
||||||
|
lowerCaseContentTypeKey(curlJson.headers);
|
||||||
|
|
||||||
|
// set basic authentication
|
||||||
|
if (curlJson.auth) {
|
||||||
|
const { user, password: pass } = curlJson.auth;
|
||||||
|
Object.assign(curlJson.headers, {
|
||||||
|
authorization: `Basic ${encodeBasicAuthentication(user, pass)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpNodeParameters: HttpNodeParameters = {
|
||||||
|
url: curlJson.url,
|
||||||
|
authentication: 'none',
|
||||||
|
method: curlJson.method.toUpperCase(),
|
||||||
|
...extractHeaders({ ...curlJson.headers, ...mapCookies(curlJson.cookies) }),
|
||||||
|
...extractQueries(curlJson.queries),
|
||||||
|
options: {
|
||||||
|
redirect: {
|
||||||
|
redirect: {},
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//attempt to get the curl flags not supported by the library
|
||||||
|
const curl = sanatizeCurlCommand(curlCommand);
|
||||||
|
|
||||||
|
//check for follow redirect flags
|
||||||
|
if (FOLLOW_REDIRECT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
Object.assign(httpNodeParameters.options.redirect?.redirect, { followRedirects: true });
|
||||||
|
|
||||||
|
if (curl.includes(` ${MAX_REDIRECT_FLAG}`)) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${MAX_REDIRECT_FLAG} (\\d+)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, maxRedirects] = extractedValue[0];
|
||||||
|
if (maxRedirects) {
|
||||||
|
Object.assign(httpNodeParameters.options.redirect?.redirect, { maxRedirects });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//check for proxy flags
|
||||||
|
if (PROXY_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = PROXY_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${foundFlag} (\\S*)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, proxy] = extractedValue[0];
|
||||||
|
Object.assign(httpNodeParameters.options, { proxy });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for "include header in output" flag
|
||||||
|
if (INCLUDE_HEADERS_IN_OUTPUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
Object.assign(httpNodeParameters.options?.response?.response, {
|
||||||
|
fullResponse: true,
|
||||||
|
responseFormat: 'autodetect',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for request flag
|
||||||
|
if (REQUEST_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = REQUEST_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${foundFlag} (\\w+)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, request] = extractedValue[0];
|
||||||
|
httpNodeParameters.method = request.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for timeout flag
|
||||||
|
if (TIMEOUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = TIMEOUT_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${foundFlag} (\\d+)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, timeout] = extractedValue[0];
|
||||||
|
Object.assign(httpNodeParameters.options, {
|
||||||
|
timeout: parseInt(timeout, 10) * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for download flag
|
||||||
|
if (DOWNLOAD_FILE_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = DOWNLOAD_FILE_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
Object.assign(httpNodeParameters.options.response.response, {
|
||||||
|
responseFormat: 'file',
|
||||||
|
outputPropertyName: 'data',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IGNORE_SSL_ISSUES_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = IGNORE_SSL_ISSUES_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
Object.assign(httpNodeParameters.options, {
|
||||||
|
allowUnauthorizedCerts: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes;
|
||||||
|
|
||||||
|
if (isBinaryRequest(curlJson)) {
|
||||||
|
return Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'binaryData',
|
||||||
|
sendBody: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) {
|
||||||
|
return Object.assign(httpNodeParameters, {
|
||||||
|
sendBody: true,
|
||||||
|
contentType: 'raw',
|
||||||
|
rawContentType: contentType,
|
||||||
|
body: Object.keys(curlJson?.data ?? {})[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJsonRequest(curlJson)) {
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'json',
|
||||||
|
sendBody: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = extractJson(curlJson.data);
|
||||||
|
|
||||||
|
if (jsonHasNestedObjects(json)) {
|
||||||
|
// json body
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
specifyBody: 'json',
|
||||||
|
jsonBody: JSON.stringify(json),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// key-value body
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
specifyBody: 'keypair',
|
||||||
|
bodyParameters: {
|
||||||
|
parameters: jsonBodyToNodeParameters(curlJson.data),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (isFormUrlEncodedRequest(curlJson)) {
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'form-urlencoded',
|
||||||
|
sendBody: true,
|
||||||
|
specifyBody: 'keypair',
|
||||||
|
bodyParameters: {
|
||||||
|
parameters: keyValueBodyToNodeParameters(curlJson.data),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (isMultipartRequest(curlJson)) {
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'multipart-form-data',
|
||||||
|
sendBody: true,
|
||||||
|
bodyParameters: {
|
||||||
|
parameters: multipartToNodeParameters(curlJson.data, curlJson.files),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// could not figure the content type so do not set the body
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
sendBody: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) {
|
||||||
|
// @ts-ignore
|
||||||
|
delete httpNodeParameters.options.redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(httpNodeParameters.options.response.response).length) {
|
||||||
|
// @ts-ignore
|
||||||
|
delete httpNodeParameters.options.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpNodeParameters;
|
||||||
|
};
|
|
@ -221,3 +221,13 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb):
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const flattenObject = (obj: { [x: string]: any }, prefix = '') =>
|
||||||
|
Object.keys(obj).reduce((acc, k) => {
|
||||||
|
const pre = prefix.length ? prefix + '.' : '';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
|
||||||
|
//@ts-ignore
|
||||||
|
else acc[pre + k] = obj[k];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
|
@ -43,6 +43,7 @@ import { FindManyOptions, getConnectionManager, In } from 'typeorm';
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import clientOAuth1, { RequestOptions } from 'oauth-1.0a';
|
import clientOAuth1, { RequestOptions } from 'oauth-1.0a';
|
||||||
|
import curlconverter from 'curlconverter';
|
||||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
||||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||||
import { compare } from 'bcryptjs';
|
import { compare } from 'bcryptjs';
|
||||||
|
@ -96,6 +97,7 @@ import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants';
|
||||||
import { credentialsController } from './credentials/credentials.controller';
|
import { credentialsController } from './credentials/credentials.controller';
|
||||||
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
||||||
import type {
|
import type {
|
||||||
|
CurlHelper,
|
||||||
ExecutionRequest,
|
ExecutionRequest,
|
||||||
NodeListSearchRequest,
|
NodeListSearchRequest,
|
||||||
NodeParameterOptionsRequest,
|
NodeParameterOptionsRequest,
|
||||||
|
@ -151,6 +153,8 @@ import {
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import { ResponseError } from './ResponseHelper';
|
import { ResponseError } from './ResponseHelper';
|
||||||
|
|
||||||
|
import { toHttpNodeParameters } from './CurlConverterHelper';
|
||||||
|
|
||||||
require('body-parser-xml')(bodyParser);
|
require('body-parser-xml')(bodyParser);
|
||||||
|
|
||||||
const exec = promisify(callbackExec);
|
const exec = promisify(callbackExec);
|
||||||
|
@ -1061,6 +1065,27 @@ class App {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// curl-converter
|
||||||
|
// ----------------------------------------
|
||||||
|
this.app.post(
|
||||||
|
`/${this.restEndpoint}/curl-to-json`,
|
||||||
|
ResponseHelper.send(
|
||||||
|
async (
|
||||||
|
req: CurlHelper.ToJson,
|
||||||
|
res: express.Response,
|
||||||
|
): Promise<{ [key: string]: string }> => {
|
||||||
|
const curlCommand = req.body.curlCommand ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parameters = toHttpNodeParameters(curlCommand);
|
||||||
|
return ResponseHelper.flattenObject(parameters, 'parameters');
|
||||||
|
} catch (e) {
|
||||||
|
throw new ResponseHelper.ResponseError(`Invalid cURL command`, undefined, 400);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Credential-Types
|
// Credential-Types
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
8
packages/cli/src/requests.d.ts
vendored
8
packages/cli/src/requests.d.ts
vendored
|
@ -327,3 +327,11 @@ export declare namespace NodeRequest {
|
||||||
|
|
||||||
type Update = Post;
|
type Update = Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// /curl-to-json
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
export declare namespace CurlHelper {
|
||||||
|
type ToJson = AuthenticatedRequest<{}, {}, { curlCommand?: string }>;
|
||||||
|
}
|
||||||
|
|
275
packages/cli/test/unit/CurlConverterHelper.test.ts
Normal file
275
packages/cli/test/unit/CurlConverterHelper.test.ts
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
import { toHttpNodeParameters } from "../../src/CurlConverterHelper";
|
||||||
|
|
||||||
|
describe('CurlConverterHelper', () => {
|
||||||
|
|
||||||
|
test('Should parse form-urlencoded content type correctly', () => {
|
||||||
|
const curl = 'curl -X POST https://reqbin.com/echo/post/form -H "Content-Type: application/x-www-form-urlencoded" -d "param1=value1¶m2=value2"';
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/form');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('param1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('param2');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].value).toBe('value2');
|
||||||
|
expect(parameters.contentType).toBe('form-urlencoded');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse JSON content type correctly', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/json -H 'Content-Type: application/json' -d '{"login":"my_login","password":"my_password"}'`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('login');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('my_login');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('password');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].value).toBe('my_password');
|
||||||
|
expect(parameters.contentType).toBe('json');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse multipart-form-data content type correctly', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||||
|
expect(parameters.contentType).toBe('multipart-form-data');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse binary request correctly', () => {
|
||||||
|
const curl = `curl --location --request POST 'https://www.website.com' --header 'Content-Type: image/png' --data-binary '@/Users/image.png`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
console.log(JSON.stringify(parameters, undefined, 2));
|
||||||
|
expect(parameters.url).toBe('https://www.website.com');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.contentType).toBe('binaryData');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse unknown content type correctly', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/xml
|
||||||
|
-H "Content-Type: application/xml"
|
||||||
|
-H "Accept: application/xml"
|
||||||
|
-d "<Request><Login>my_login</Login><Password>my_password</Password></Request>"`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/xml');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.contentType).toBe('raw');
|
||||||
|
expect(parameters.rawContentType).toBe('application/xml');
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('Accept');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe('application/xml');
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse header properties and keep the original case', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename -H "ACCEPT: text/javascript" -H "content-type: multipart/form-data"`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||||
|
expect(parameters.contentType).toBe('multipart-form-data');
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('ACCEPT');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe('text/javascript');
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse querystring properties', () => {
|
||||||
|
const curl = `curl -G -d 'q=kitties' -d 'count=20' https://google.com/search`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://google.com/search');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(true);
|
||||||
|
expect(parameters.queryParameters?.parameters[0].name).toBe('q');
|
||||||
|
expect(parameters.queryParameters?.parameters[0].value).toBe('kitties');
|
||||||
|
expect(parameters.queryParameters?.parameters[1].name).toBe('count');
|
||||||
|
expect(parameters.queryParameters?.parameters[1].value).toBe('20');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse basic authentication property and keep the original case', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password"`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse location flag with --location', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" --location`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse location flag with --L', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -L`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse location and max redirects flags with --location and --max-redirs 10', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" --location --max-redirs 10`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||||
|
expect(parameters.options.redirect.redirect.maxRedirects).toBe("10");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse proxy flag -x', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.proxy).toBe('https://google.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse proxy flag --proxy', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.proxy).toBe('https://google.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include headers on output flag --include', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" --include -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include headers on output flag -i', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com -i`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include request flag -X', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include request flag --request', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include timeout flag --connect-timeout', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" --connect-timeout 20`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.timeout).toBe(20000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse download file flag -O', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -O`;
|
||||||
|
; const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||||
|
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should parse download file flag -o', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -o`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||||
|
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should parse ignore SSL flag -k', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -k`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should parse ignore SSL flag --insecure', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" --insecure`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
|
@ -127,7 +127,7 @@ export interface IEndpointOptions {
|
||||||
export interface IUpdateInformation {
|
export interface IUpdateInformation {
|
||||||
name: string;
|
name: string;
|
||||||
key: string;
|
key: string;
|
||||||
value: string | number; // with null makes problems in NodeSettings.vue
|
value: string | number | { [key: string]: string | number | boolean }; // with null makes problems in NodeSettings.vue
|
||||||
node?: string;
|
node?: string;
|
||||||
oldValue?: string | number;
|
oldValue?: string | number;
|
||||||
}
|
}
|
||||||
|
@ -924,6 +924,8 @@ export interface IModalState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
mode?: string | null;
|
mode?: string | null;
|
||||||
activeId?: string | null;
|
activeId?: string | null;
|
||||||
|
curlCommand?: string;
|
||||||
|
httpNodeParameters?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
|
export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
|
||||||
|
|
6
packages/editor-ui/src/api/curlHelper.ts
Normal file
6
packages/editor-ui/src/api/curlHelper.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import {IRestApiContext} from "@/Interface";
|
||||||
|
import {makeRestApiRequest} from "@/api/helpers";
|
||||||
|
|
||||||
|
export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<{ curlCommand: string | null }> {
|
||||||
|
return makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand });
|
||||||
|
}
|
196
packages/editor-ui/src/components/ImportCurlModal.vue
Normal file
196
packages/editor-ui/src/components/ImportCurlModal.vue
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
width="700px"
|
||||||
|
:title="$locale.baseText('importCurlModal.title')"
|
||||||
|
:eventBus="modalBus"
|
||||||
|
:name="IMPORT_CURL_MODAL_KEY"
|
||||||
|
:center="true"
|
||||||
|
>
|
||||||
|
<template slot="content">
|
||||||
|
<div :class="$style.container">
|
||||||
|
<n8n-input-label :label="$locale.baseText('importCurlModal.input.label')">
|
||||||
|
<n8n-input
|
||||||
|
:value="curlCommand"
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
:placeholder="$locale.baseText('importCurlModal.input.placeholder')"
|
||||||
|
@input="onInput"
|
||||||
|
@focus="$event.target.select()"
|
||||||
|
ref="input"
|
||||||
|
/>
|
||||||
|
</n8n-input-label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template slot="footer">
|
||||||
|
<div :class="$style.modalFooter">
|
||||||
|
<n8n-notice
|
||||||
|
:class="$style.notice"
|
||||||
|
:content="$locale.baseText('ImportCurlModal.notice.content')"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<n8n-button
|
||||||
|
@click="importCurlCommand"
|
||||||
|
float="right"
|
||||||
|
:label="$locale.baseText('importCurlModal.button.label')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Modal from './Modal.vue';
|
||||||
|
import {
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
|
CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS,
|
||||||
|
CURL_IMPORT_NODES_PROTOCOLS,
|
||||||
|
} from '../constants';
|
||||||
|
import { showMessage } from './mixins/showMessage';
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { INodeUi } from '@/Interface';
|
||||||
|
|
||||||
|
export default mixins(showMessage).extend({
|
||||||
|
name: 'ImportCurlModal',
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
|
curlCommand: '',
|
||||||
|
modalBus: new Vue(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
node(): INodeUi {
|
||||||
|
return this.$store.getters.activeNode;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeDialog(): void {
|
||||||
|
this.modalBus.$emit('close');
|
||||||
|
},
|
||||||
|
onInput(value: string): void {
|
||||||
|
this.curlCommand = value;
|
||||||
|
},
|
||||||
|
async importCurlCommand(): Promise<void> {
|
||||||
|
const curlCommand = this.curlCommand;
|
||||||
|
if (curlCommand === '') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parameters = await this.$store.dispatch('ui/getCurlToJson', curlCommand);
|
||||||
|
|
||||||
|
const url = parameters['parameters.url'];
|
||||||
|
|
||||||
|
const invalidProtocol = CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS.find((p) =>
|
||||||
|
url.includes(`${p}://`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!invalidProtocol) {
|
||||||
|
this.$store.dispatch('ui/setHttpNodeParameters', {
|
||||||
|
parameters: JSON.stringify(parameters),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
|
||||||
|
this.sendTelemetry();
|
||||||
|
|
||||||
|
return;
|
||||||
|
// if we have a node that supports the invalid protocol
|
||||||
|
// suggest that one
|
||||||
|
} else if (CURL_IMPORT_NODES_PROTOCOLS[invalidProtocol]) {
|
||||||
|
const useNode = CURL_IMPORT_NODES_PROTOCOLS[invalidProtocol];
|
||||||
|
|
||||||
|
this.showProtocolErrorWithSupportedNode(invalidProtocol, useNode);
|
||||||
|
// we do not have a node that supports the use protocol
|
||||||
|
} else {
|
||||||
|
this.showProtocolError(invalidProtocol);
|
||||||
|
}
|
||||||
|
this.sendTelemetry({ success: false, invalidProtocol: true, protocol: invalidProtocol });
|
||||||
|
} catch (e) {
|
||||||
|
this.showInvalidcURLCommandError();
|
||||||
|
|
||||||
|
this.sendTelemetry({ success: false, invalidProtocol: false });
|
||||||
|
} finally {
|
||||||
|
this.$store.dispatch('ui/setCurlCommand', { command: this.curlCommand });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showProtocolErrorWithSupportedNode(protocol: string, node: string): void {
|
||||||
|
this.$showToast({
|
||||||
|
title: this.$locale.baseText('importParameter.showError.invalidProtocol1.title', {
|
||||||
|
interpolate: {
|
||||||
|
node,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
message: this.$locale.baseText('importParameter.showError.invalidProtocol.message', {
|
||||||
|
interpolate: {
|
||||||
|
protocol: protocol.toUpperCase(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showProtocolError(protocol: string): void {
|
||||||
|
this.$showToast({
|
||||||
|
title: this.$locale.baseText('importParameter.showError.invalidProtocol2.title'),
|
||||||
|
message: this.$locale.baseText('importParameter.showError.invalidProtocol.message', {
|
||||||
|
interpolate: {
|
||||||
|
protocol,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showInvalidcURLCommandError(): void {
|
||||||
|
this.$showToast({
|
||||||
|
title: this.$locale.baseText('importParameter.showError.invalidCurlCommand.title'),
|
||||||
|
message: this.$locale.baseText('importParameter.showError.invalidCurlCommand.message'),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendTelemetry(
|
||||||
|
data: { success: boolean; invalidProtocol: boolean; protocol?: string } = {
|
||||||
|
success: true,
|
||||||
|
invalidProtocol: false,
|
||||||
|
protocol: '',
|
||||||
|
},
|
||||||
|
): void {
|
||||||
|
this.$telemetry.track('User imported curl command', {
|
||||||
|
success: data.success,
|
||||||
|
invalidProtocol: data.invalidProtocol,
|
||||||
|
protocol: data.protocol,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.curlCommand = this.$store.getters['ui/getCurlCommand'];
|
||||||
|
setTimeout(() => {
|
||||||
|
(this.$refs.input as HTMLTextAreaElement).focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.modalFooter {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container > * {
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
33
packages/editor-ui/src/components/ImportParameter.vue
Normal file
33
packages/editor-ui/src/components/ImportParameter.vue
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.importSection">
|
||||||
|
<n8n-button
|
||||||
|
type="secondary"
|
||||||
|
:label="$locale.baseText('importParameter.label')"
|
||||||
|
size="mini"
|
||||||
|
@click="onImportCurlClicked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { showMessage } from './mixins/showMessage';
|
||||||
|
|
||||||
|
export default mixins(showMessage).extend({
|
||||||
|
name: 'import-parameter',
|
||||||
|
methods: {
|
||||||
|
onImportCurlClicked() {
|
||||||
|
this.$store.dispatch('ui/openModal', IMPORT_CURL_MODAL_KEY);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.importSection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -93,6 +93,10 @@
|
||||||
<CommunityPackageInstallModal />
|
<CommunityPackageInstallModal />
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
|
|
||||||
|
<ModalRoot :name="IMPORT_CURL_MODAL_KEY">
|
||||||
|
<ImportCurlModal />
|
||||||
|
</ModalRoot>
|
||||||
|
|
||||||
<ModalRoot :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY">
|
<ModalRoot :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY">
|
||||||
<template v-slot="{ modalName, activeId, mode }">
|
<template v-slot="{ modalName, activeId, mode }">
|
||||||
<CommunityPackageManageConfirmModal
|
<CommunityPackageManageConfirmModal
|
||||||
|
@ -128,6 +132,7 @@ import {
|
||||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||||
WORKFLOW_OPEN_MODAL_KEY,
|
WORKFLOW_OPEN_MODAL_KEY,
|
||||||
WORKFLOW_SETTINGS_MODAL_KEY,
|
WORKFLOW_SETTINGS_MODAL_KEY,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
|
|
||||||
import AboutModal from './AboutModal.vue';
|
import AboutModal from './AboutModal.vue';
|
||||||
|
@ -150,6 +155,7 @@ import WorkflowOpen from "./WorkflowOpen.vue";
|
||||||
import DeleteUserModal from "./DeleteUserModal.vue";
|
import DeleteUserModal from "./DeleteUserModal.vue";
|
||||||
import ExecutionsList from "./ExecutionsList.vue";
|
import ExecutionsList from "./ExecutionsList.vue";
|
||||||
import ActivationModal from "./ActivationModal.vue";
|
import ActivationModal from "./ActivationModal.vue";
|
||||||
|
import ImportCurlModal from './ImportCurlModal.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: "Modals",
|
name: "Modals",
|
||||||
|
@ -174,6 +180,7 @@ export default Vue.extend({
|
||||||
ValueSurvey,
|
ValueSurvey,
|
||||||
WorkflowSettings,
|
WorkflowSettings,
|
||||||
WorkflowOpen,
|
WorkflowOpen,
|
||||||
|
ImportCurlModal,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY,
|
COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY,
|
||||||
|
@ -195,6 +202,7 @@ export default Vue.extend({
|
||||||
VALUE_SURVEY_MODAL_KEY,
|
VALUE_SURVEY_MODAL_KEY,
|
||||||
EXECUTIONS_MODAL_KEY,
|
EXECUTIONS_MODAL_KEY,
|
||||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
'node-settings': true, 'dragging': dragging }" @keydown.stop>
|
'node-settings': true, 'dragging': dragging }" @keydown.stop>
|
||||||
<div :class="$style.header">
|
<div :class="$style.header">
|
||||||
<div class="header-side-menu">
|
<div class="header-side-menu">
|
||||||
<NodeTitle class="node-name" :value="node && node.name" :nodeType="nodeType" @input="nameChanged" :readOnly="isReadOnly"></NodeTitle>
|
<NodeTitle
|
||||||
<div
|
class="node-name"
|
||||||
v-if="!isReadOnly"
|
:value="node && node.name"
|
||||||
>
|
:nodeType="nodeType"
|
||||||
|
@input="nameChanged"
|
||||||
|
:readOnly="isReadOnly"
|
||||||
|
></NodeTitle>
|
||||||
|
<div v-if="!isReadOnly">
|
||||||
<NodeExecuteButton
|
<NodeExecuteButton
|
||||||
:nodeName="node.name"
|
:nodeName="node.name"
|
||||||
:disabled="outputPanelEditMode.enabled"
|
:disabled="outputPanelEditMode.enabled"
|
||||||
|
@ -16,7 +20,12 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NodeSettingsTabs v-if="node && nodeValid" v-model="openPanel" :nodeType="nodeType" :sessionId="sessionId" />
|
<NodeSettingsTabs
|
||||||
|
v-if="node && nodeValid"
|
||||||
|
v-model="openPanel"
|
||||||
|
:nodeType="nodeType"
|
||||||
|
:sessionId="sessionId"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="node-is-not-valid" v-if="node && !nodeValid">
|
<div class="node-is-not-valid" v-if="node && !nodeValid">
|
||||||
<p :class="$style.warningIcon">
|
<p :class="$style.warningIcon">
|
||||||
|
@ -57,20 +66,18 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="node-parameters-wrapper" v-if="node && nodeValid">
|
<div class="node-parameters-wrapper" v-if="node && nodeValid">
|
||||||
<div v-show="openPanel === 'params'">
|
<div v-show="openPanel === 'params'">
|
||||||
<node-webhooks
|
<node-webhooks :node="node" :nodeType="nodeType" />
|
||||||
:node="node"
|
|
||||||
:nodeType="nodeType"
|
|
||||||
/>
|
|
||||||
<parameter-input-list
|
<parameter-input-list
|
||||||
:parameters="parametersNoneSetting"
|
:parameters="parametersNoneSetting"
|
||||||
:hideDelete="true"
|
:hideDelete="true"
|
||||||
:nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged"
|
:nodeValues="nodeValues"
|
||||||
|
path="parameters"
|
||||||
|
@valueChanged="valueChanged"
|
||||||
@activate="onWorkflowActivate"
|
@activate="onWorkflowActivate"
|
||||||
>
|
>
|
||||||
<node-credentials
|
|
||||||
:node="node"
|
<node-credentials :node="node" @credentialSelected="credentialSelected" />
|
||||||
@credentialSelected="credentialSelected"
|
|
||||||
/>
|
|
||||||
</parameter-input-list>
|
</parameter-input-list>
|
||||||
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
<div v-if="parametersNoneSetting.length === 0" class="no-parameters">
|
||||||
<n8n-text>
|
<n8n-text>
|
||||||
|
@ -80,17 +87,28 @@
|
||||||
|
|
||||||
<div v-if="isCustomApiCallSelected(nodeValues)" class="parameter-item parameter-notice">
|
<div v-if="isCustomApiCallSelected(nodeValues)" class="parameter-item parameter-notice">
|
||||||
<n8n-notice
|
<n8n-notice
|
||||||
:content="$locale.baseText(
|
:content="
|
||||||
'nodeSettings.useTheHttpRequestNode',
|
$locale.baseText('nodeSettings.useTheHttpRequestNode', {
|
||||||
{ interpolate: { nodeTypeDisplayName: nodeType.displayName } }
|
interpolate: { nodeTypeDisplayName: nodeType.displayName },
|
||||||
)"
|
})
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div v-show="openPanel === 'settings'">
|
<div v-show="openPanel === 'settings'">
|
||||||
<parameter-input-list :parameters="parametersSetting" :nodeValues="nodeValues" path="parameters" @valueChanged="valueChanged" />
|
<parameter-input-list
|
||||||
<parameter-input-list :parameters="nodeSettings" :hideDelete="true" :nodeValues="nodeValues" path="" @valueChanged="valueChanged" />
|
:parameters="parametersSetting"
|
||||||
|
:nodeValues="nodeValues"
|
||||||
|
path="parameters"
|
||||||
|
@valueChanged="valueChanged"
|
||||||
|
/>
|
||||||
|
<parameter-input-list
|
||||||
|
:parameters="nodeSettings"
|
||||||
|
:hideDelete="true"
|
||||||
|
:nodeValues="nodeValues"
|
||||||
|
path=""
|
||||||
|
@valueChanged="valueChanged"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -105,16 +123,13 @@ import {
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import {
|
import { INodeUi, INodeUpdatePropertiesInformation, IUpdateInformation } from '@/Interface';
|
||||||
INodeUi,
|
|
||||||
INodeUpdatePropertiesInformation,
|
|
||||||
IUpdateInformation,
|
|
||||||
} from '@/Interface';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
COMMUNITY_NODES_INSTALLATION_DOCS_URL,
|
||||||
CUSTOM_NODES_DOCS_URL,
|
CUSTOM_NODES_DOCS_URL,
|
||||||
MAIN_NODE_PANEL_WIDTH,
|
MAIN_NODE_PANEL_WIDTH,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
|
|
||||||
import NodeTitle from '@/components/NodeTitle.vue';
|
import NodeTitle from '@/components/NodeTitle.vue';
|
||||||
|
@ -133,12 +148,7 @@ import mixins from 'vue-typed-mixins';
|
||||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||||
import { isCommunityPackageName } from './helpers';
|
import { isCommunityPackageName } from './helpers';
|
||||||
|
|
||||||
export default mixins(
|
export default mixins(externalHooks, genericHelpers, nodeHelpers).extend({
|
||||||
externalHooks,
|
|
||||||
genericHelpers,
|
|
||||||
nodeHelpers,
|
|
||||||
)
|
|
||||||
.extend({
|
|
||||||
name: 'NodeSettings',
|
name: 'NodeSettings',
|
||||||
components: {
|
components: {
|
||||||
NodeTitle,
|
NodeTitle,
|
||||||
|
@ -150,6 +160,9 @@ export default mixins(
|
||||||
NodeExecuteButton,
|
NodeExecuteButton,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isCurlImportModalOpen() {
|
||||||
|
return this.$store.getters['ui/isModalOpen'](IMPORT_CURL_MODAL_KEY);
|
||||||
|
},
|
||||||
nodeTypeName(): string {
|
nodeTypeName(): string {
|
||||||
if (this.nodeType) {
|
if (this.nodeType) {
|
||||||
const shortNodeType = this.$locale.shortNodeType(this.nodeType.name);
|
const shortNodeType = this.$locale.shortNodeType(this.nodeType.name);
|
||||||
|
@ -162,7 +175,7 @@ export default mixins(
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
nodeTypeDescription (): string {
|
nodeTypeDescription(): string {
|
||||||
if (this.nodeType && this.nodeType.description) {
|
if (this.nodeType && this.nodeType.description) {
|
||||||
const shortNodeType = this.$locale.shortNodeType(this.nodeType.name);
|
const shortNodeType = this.$locale.shortNodeType(this.nodeType.name);
|
||||||
|
|
||||||
|
@ -174,7 +187,7 @@ export default mixins(
|
||||||
return this.$locale.baseText('nodeSettings.noDescriptionFound');
|
return this.$locale.baseText('nodeSettings.noDescriptionFound');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
headerStyle (): object {
|
headerStyle(): object {
|
||||||
if (!this.node) {
|
if (!this.node) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -183,27 +196,27 @@ export default mixins(
|
||||||
'background-color': this.node.color,
|
'background-color': this.node.color,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
node (): INodeUi {
|
node(): INodeUi {
|
||||||
return this.$store.getters.activeNode;
|
return this.$store.getters.activeNode;
|
||||||
},
|
},
|
||||||
parametersSetting (): INodeProperties[] {
|
parametersSetting(): INodeProperties[] {
|
||||||
return this.parameters.filter((item) => {
|
return this.parameters.filter((item) => {
|
||||||
return item.isNodeSetting;
|
return item.isNodeSetting;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
parametersNoneSetting (): INodeProperties[] {
|
parametersNoneSetting(): INodeProperties[] {
|
||||||
return this.parameters.filter((item) => {
|
return this.parameters.filter((item) => {
|
||||||
return !item.isNodeSetting;
|
return !item.isNodeSetting;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
parameters (): INodeProperties[] {
|
parameters(): INodeProperties[] {
|
||||||
if (this.nodeType === null) {
|
if (this.nodeType === null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.nodeType.properties;
|
return this.nodeType.properties;
|
||||||
},
|
},
|
||||||
outputPanelEditMode(): { enabled: boolean; value: string; } {
|
outputPanelEditMode(): { enabled: boolean; value: string } {
|
||||||
return this.$store.getters['ui/outputPanelEditMode'];
|
return this.$store.getters['ui/outputPanelEditMode'];
|
||||||
},
|
},
|
||||||
isCommunityNode(): boolean {
|
isCommunityNode(): boolean {
|
||||||
|
@ -211,8 +224,7 @@ export default mixins(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
eventBus: {
|
eventBus: {},
|
||||||
},
|
|
||||||
dragging: {
|
dragging: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
@ -223,7 +235,7 @@ export default mixins(
|
||||||
type: Object as PropType<INodeTypeDescription>,
|
type: Object as PropType<INodeTypeDescription>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
nodeValid: true,
|
nodeValid: true,
|
||||||
nodeColor: null,
|
nodeColor: null,
|
||||||
|
@ -277,9 +289,7 @@ export default mixins(
|
||||||
default: 3,
|
default: 3,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
retryOnFail: [
|
retryOnFail: [true],
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
|
@ -296,9 +306,7 @@ export default mixins(
|
||||||
default: 1000,
|
default: 1000,
|
||||||
displayOptions: {
|
displayOptions: {
|
||||||
show: {
|
show: {
|
||||||
retryOnFail: [
|
retryOnFail: [true],
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
noDataExpression: true,
|
noDataExpression: true,
|
||||||
|
@ -338,18 +346,40 @@ export default mixins(
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
node (newNode, oldNode) {
|
node(newNode, oldNode) {
|
||||||
this.setNodeValues();
|
this.setNodeValues();
|
||||||
},
|
},
|
||||||
|
isCurlImportModalOpen(newValue, oldValue) {
|
||||||
|
if (newValue === false) {
|
||||||
|
let parameters = this.$store.getters['ui/getHttpNodeParameters'];
|
||||||
|
|
||||||
|
if (!parameters) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
parameters = JSON.parse(parameters) as {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
this.valueChanged({
|
||||||
|
node: this.node.name,
|
||||||
|
name: 'parameters',
|
||||||
|
value: parameters,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$store.dispatch('ui/setHttpNodeParameters', { parameters: '' });
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onWorkflowActivate() {
|
onWorkflowActivate() {
|
||||||
this.$emit('activate');
|
this.$emit('activate');
|
||||||
},
|
},
|
||||||
onNodeExecute () {
|
onNodeExecute() {
|
||||||
this.$emit('execute');
|
this.$emit('execute');
|
||||||
},
|
},
|
||||||
setValue (name: string, value: NodeParameterValue) {
|
setValue(name: string, value: NodeParameterValue) {
|
||||||
const nameParts = name.split('.');
|
const nameParts = name.split('.');
|
||||||
let lastNamePart: string | undefined = nameParts.pop();
|
let lastNamePart: string | undefined = nameParts.pop();
|
||||||
|
|
||||||
|
@ -381,7 +411,9 @@ export default mixins(
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
// Property should be deleted
|
// Property should be deleted
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let tempValue = get(this.nodeValues, nameParts.join('.')) as INodeParameters | NodeParameters[];
|
let tempValue = get(this.nodeValues, nameParts.join('.')) as
|
||||||
|
| INodeParameters
|
||||||
|
| INodeParameters[];
|
||||||
Vue.delete(tempValue as object, lastNamePart as string);
|
Vue.delete(tempValue as object, lastNamePart as string);
|
||||||
|
|
||||||
if (isArray === true && (tempValue as INodeParameters[]).length === 0) {
|
if (isArray === true && (tempValue as INodeParameters[]).length === 0) {
|
||||||
|
@ -403,7 +435,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
credentialSelected (updateInformation: INodeUpdatePropertiesInformation) {
|
credentialSelected(updateInformation: INodeUpdatePropertiesInformation) {
|
||||||
// Update the values on the node
|
// Update the values on the node
|
||||||
this.$store.commit('updateNodeProperties', updateInformation);
|
this.$store.commit('updateNodeProperties', updateInformation);
|
||||||
|
|
||||||
|
@ -421,16 +453,16 @@ export default mixins(
|
||||||
name: 'name',
|
name: 'name',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
valueChanged (parameterData: IUpdateInformation) {
|
valueChanged(parameterData: IUpdateInformation) {
|
||||||
let newValue: NodeParameterValue;
|
let newValue: NodeParameterValue;
|
||||||
|
|
||||||
if (parameterData.hasOwnProperty('value')) {
|
if (parameterData.hasOwnProperty('value')) {
|
||||||
// New value is given
|
// New value is given
|
||||||
newValue = parameterData.value;
|
newValue = parameterData.value as string | number;
|
||||||
} else {
|
} else {
|
||||||
// Get new value from nodeData where it is set already
|
// Get new value from nodeData where it is set already
|
||||||
newValue = get(this.nodeValues, parameterData.name) as NodeParameterValue;
|
newValue = get(this.nodeValues, parameterData.name) as NodeParameterValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the node name before we commit the change because
|
// Save the node name before we commit the change because
|
||||||
// we need the old name to rename the node properly
|
// we need the old name to rename the node properly
|
||||||
const nodeNameBefore = parameterData.node || this.node.name;
|
const nodeNameBefore = parameterData.node || this.node.name;
|
||||||
|
@ -445,17 +477,116 @@ export default mixins(
|
||||||
name: parameterData.name,
|
name: parameterData.name,
|
||||||
};
|
};
|
||||||
this.$emit('valueChanged', sendData);
|
this.$emit('valueChanged', sendData);
|
||||||
|
} else if (parameterData.name === 'parameters') {
|
||||||
|
|
||||||
} else if (parameterData.name.startsWith('parameters.')) {
|
const nodeType = this.$store.getters['nodeTypes/getNodeType'](
|
||||||
// A node parameter changed
|
node.type,
|
||||||
|
node.typeVersion,
|
||||||
const nodeType = this.$store.getters['nodeTypes/getNodeType'](node.type, node.typeVersion) as INodeTypeDescription | null;
|
) as INodeTypeDescription | null;
|
||||||
if (!nodeType) {
|
if (!nodeType) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get only the parameters which are different to the defaults
|
// Get only the parameters which are different to the defaults
|
||||||
let nodeParameters = NodeHelpers.getNodeParameters(nodeType.properties, node.parameters, false, false, node);
|
let nodeParameters = NodeHelpers.getNodeParameters(
|
||||||
|
nodeType.properties,
|
||||||
|
node.parameters,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
|
||||||
|
const oldNodeParameters = Object.assign({}, nodeParameters);
|
||||||
|
|
||||||
|
// Copy the data because it is the data of vuex so make sure that
|
||||||
|
// we do not edit it directly
|
||||||
|
nodeParameters = JSON.parse(JSON.stringify(nodeParameters));
|
||||||
|
|
||||||
|
for (const parameterName of Object.keys(parameterData.value)) {
|
||||||
|
//@ts-ignore
|
||||||
|
newValue = parameterData.value[parameterName];
|
||||||
|
|
||||||
|
// Remove the 'parameters.' from the beginning to just have the
|
||||||
|
// actual parameter name
|
||||||
|
const parameterPath = parameterName.split('.').slice(1).join('.');
|
||||||
|
|
||||||
|
// Check if the path is supposed to change an array and if so get
|
||||||
|
// the needed data like path and index
|
||||||
|
const parameterPathArray = parameterPath.match(/(.*)\[(\d+)\]$/);
|
||||||
|
|
||||||
|
// Apply the new value
|
||||||
|
//@ts-ignore
|
||||||
|
if (parameterData[parameterName] === undefined && parameterPathArray !== null) {
|
||||||
|
// Delete array item
|
||||||
|
const path = parameterPathArray[1];
|
||||||
|
const index = parameterPathArray[2];
|
||||||
|
const data = get(nodeParameters, path);
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
data.splice(parseInt(index, 10), 1);
|
||||||
|
Vue.set(nodeParameters as object, path, data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (newValue === undefined) {
|
||||||
|
unset(nodeParameters as object, parameterPath);
|
||||||
|
} else {
|
||||||
|
set(nodeParameters as object, parameterPath, newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$externalHooks().run('nodeSettings.valueChanged', {
|
||||||
|
parameterPath,
|
||||||
|
newValue,
|
||||||
|
parameters: this.parameters,
|
||||||
|
oldNodeParameters,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parameters with the now new defaults according to the
|
||||||
|
// from the user actually defined parameters
|
||||||
|
nodeParameters = NodeHelpers.getNodeParameters(
|
||||||
|
nodeType.properties,
|
||||||
|
nodeParameters as INodeParameters,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const key of Object.keys(nodeParameters as object)) {
|
||||||
|
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
||||||
|
this.setValue(`parameters.${key}`, nodeParameters[key] as string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data in vuex
|
||||||
|
const updateInformation = {
|
||||||
|
name: node.name,
|
||||||
|
value: nodeParameters,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$store.commit('setNodeParameters', updateInformation);
|
||||||
|
|
||||||
|
this.updateNodeParameterIssues(node, nodeType);
|
||||||
|
this.updateNodeCredentialIssues(node);
|
||||||
|
} else if (parameterData.name.startsWith('parameters.')) {
|
||||||
|
// A node parameter changed
|
||||||
|
|
||||||
|
const nodeType = this.$store.getters['nodeTypes/getNodeType'](
|
||||||
|
node.type,
|
||||||
|
node.typeVersion,
|
||||||
|
) as INodeTypeDescription | null;
|
||||||
|
if (!nodeType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get only the parameters which are different to the defaults
|
||||||
|
let nodeParameters = NodeHelpers.getNodeParameters(
|
||||||
|
nodeType.properties,
|
||||||
|
node.parameters,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
node,
|
||||||
|
);
|
||||||
const oldNodeParameters = Object.assign({}, nodeParameters);
|
const oldNodeParameters = Object.assign({}, nodeParameters);
|
||||||
|
|
||||||
// Copy the data because it is the data of vuex so make sure that
|
// Copy the data because it is the data of vuex so make sure that
|
||||||
|
@ -491,7 +622,13 @@ export default mixins(
|
||||||
|
|
||||||
// Get the parameters with the now new defaults according to the
|
// Get the parameters with the now new defaults according to the
|
||||||
// from the user actually defined parameters
|
// from the user actually defined parameters
|
||||||
nodeParameters = NodeHelpers.getNodeParameters(nodeType.properties, nodeParameters as INodeParameters, true, false, node);
|
nodeParameters = NodeHelpers.getNodeParameters(
|
||||||
|
nodeType.properties,
|
||||||
|
nodeParameters as INodeParameters,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
|
||||||
for (const key of Object.keys(nodeParameters as object)) {
|
for (const key of Object.keys(nodeParameters as object)) {
|
||||||
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
||||||
|
@ -507,7 +644,12 @@ export default mixins(
|
||||||
|
|
||||||
this.$store.commit('setNodeParameters', updateInformation);
|
this.$store.commit('setNodeParameters', updateInformation);
|
||||||
|
|
||||||
this.$externalHooks().run('nodeSettings.valueChanged', { parameterPath, newValue, parameters: this.parameters, oldNodeParameters });
|
this.$externalHooks().run('nodeSettings.valueChanged', {
|
||||||
|
parameterPath,
|
||||||
|
newValue,
|
||||||
|
parameters: this.parameters,
|
||||||
|
oldNodeParameters,
|
||||||
|
});
|
||||||
|
|
||||||
this.updateNodeParameterIssues(node, nodeType);
|
this.updateNodeParameterIssues(node, nodeType);
|
||||||
this.updateNodeCredentialIssues(node);
|
this.updateNodeCredentialIssues(node);
|
||||||
|
@ -529,7 +671,7 @@ export default mixins(
|
||||||
/**
|
/**
|
||||||
* Sets the values of the active node in the internal settings variables
|
* Sets the values of the active node in the internal settings variables
|
||||||
*/
|
*/
|
||||||
setNodeValues () {
|
setNodeValues() {
|
||||||
if (!this.node) {
|
if (!this.node) {
|
||||||
// No node selected
|
// No node selected
|
||||||
return;
|
return;
|
||||||
|
@ -599,7 +741,9 @@ export default mixins(
|
||||||
},
|
},
|
||||||
onMissingNodeTextClick(event: MouseEvent) {
|
onMissingNodeTextClick(event: MouseEvent) {
|
||||||
if ((event.target as Element).localName === 'a') {
|
if ((event.target as Element).localName === 'a') {
|
||||||
this.$telemetry.track('user clicked cnr browse button', { source: 'cnr missing node modal' });
|
this.$telemetry.track('user clicked cnr browse button', {
|
||||||
|
source: 'cnr missing node modal',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onMissingNodeLearnMoreLinkClick() {
|
onMissingNodeLearnMoreLinkClick() {
|
||||||
|
@ -610,7 +754,7 @@ export default mixins(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
this.setNodeValues();
|
this.setNodeValues();
|
||||||
if (this.eventBus) {
|
if (this.eventBus) {
|
||||||
(this.eventBus as Vue).$on('openSettings', () => {
|
(this.eventBus as Vue).$on('openSettings', () => {
|
||||||
|
@ -618,7 +762,7 @@ export default mixins(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -638,6 +782,7 @@ export default mixins(
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
||||||
.node-settings {
|
.node-settings {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: var(--color-background-xlight);
|
background-color: var(--color-background-xlight);
|
||||||
|
@ -697,7 +842,7 @@ export default mixins(
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
display: table;
|
display: table;
|
||||||
content: " ";
|
content: ' ';
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
clear: both;
|
clear: both;
|
||||||
|
@ -710,7 +855,6 @@ export default mixins(
|
||||||
|
|
||||||
.color-reset-button-wrapper {
|
.color-reset-button-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
.color-reset-button {
|
.color-reset-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -725,7 +869,7 @@ export default mixins(
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing:border-box;
|
box-sizing: border-box;
|
||||||
background-color: #793300;
|
background-color: #793300;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,6 +302,7 @@ import {
|
||||||
|
|
||||||
import CodeEdit from '@/components/CodeEdit.vue';
|
import CodeEdit from '@/components/CodeEdit.vue';
|
||||||
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
||||||
|
import ImportParameter from '@/components/ImportParameter.vue';
|
||||||
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
||||||
import NodeCredentials from '@/components/NodeCredentials.vue';
|
import NodeCredentials from '@/components/NodeCredentials.vue';
|
||||||
import ScopesNotice from '@/components/ScopesNotice.vue';
|
import ScopesNotice from '@/components/ScopesNotice.vue';
|
||||||
|
@ -341,6 +342,7 @@ export default mixins(
|
||||||
ParameterIssues,
|
ParameterIssues,
|
||||||
ResourceLocator,
|
ResourceLocator,
|
||||||
TextEdit,
|
TextEdit,
|
||||||
|
ImportParameter,
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'inputSize',
|
'inputSize',
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<import-parameter
|
||||||
|
v-else-if="parameter.type === 'curlImport' && nodeTypeName === 'n8n-nodes-base.httpRequest' && nodeTypeVersion >= 3"
|
||||||
|
@valueChanged="valueChanged"
|
||||||
|
/>
|
||||||
|
|
||||||
<n8n-notice
|
<n8n-notice
|
||||||
v-else-if="parameter.type === 'notice'"
|
v-else-if="parameter.type === 'notice'"
|
||||||
class="parameter-item"
|
class="parameter-item"
|
||||||
|
@ -93,7 +98,6 @@
|
||||||
import {
|
import {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -104,6 +108,7 @@ import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
|
import ImportParameter from '@/components/ImportParameter.vue';
|
||||||
|
|
||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
|
|
||||||
|
@ -121,6 +126,7 @@ export default mixins(
|
||||||
ParameterInputFull,
|
ParameterInputFull,
|
||||||
FixedCollectionParameter: () => import('./FixedCollectionParameter.vue') as Promise<Component>,
|
FixedCollectionParameter: () => import('./FixedCollectionParameter.vue') as Promise<Component>,
|
||||||
CollectionParameter: () => import('./CollectionParameter.vue') as Promise<Component>,
|
CollectionParameter: () => import('./CollectionParameter.vue') as Promise<Component>,
|
||||||
|
ImportParameter,
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'nodeValues', // INodeParameters
|
'nodeValues', // INodeParameters
|
||||||
|
@ -130,6 +136,18 @@ export default mixins(
|
||||||
'indent',
|
'indent',
|
||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
|
nodeTypeVersion(): number | null {
|
||||||
|
if (this.node) {
|
||||||
|
return this.node.typeVersion;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
nodeTypeName (): string {
|
||||||
|
if (this.node) {
|
||||||
|
return this.node.type;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
filteredParameters (): INodeProperties[] {
|
filteredParameters (): INodeProperties[] {
|
||||||
return this.parameters.filter((parameter: INodeProperties) => this.displayNodeParameter(parameter));
|
return this.parameters.filter((parameter: INodeProperties) => this.displayNodeParameter(parameter));
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const WORKFLOW_ACTIVE_MODAL_KEY = 'activation';
|
||||||
export const ONBOARDING_CALL_SIGNUP_MODAL_KEY = 'onboardingCallSignup';
|
export const ONBOARDING_CALL_SIGNUP_MODAL_KEY = 'onboardingCallSignup';
|
||||||
export const COMMUNITY_PACKAGE_INSTALL_MODAL_KEY = 'communityPackageInstall';
|
export const COMMUNITY_PACKAGE_INSTALL_MODAL_KEY = 'communityPackageInstall';
|
||||||
export const COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY = 'communityPackageManageConfirm';
|
export const COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY = 'communityPackageManageConfirm';
|
||||||
|
export const IMPORT_CURL_MODAL_KEY = 'importCurl';
|
||||||
|
|
||||||
export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
|
export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
|
||||||
UNINSTALL: 'uninstall',
|
UNINSTALL: 'uninstall',
|
||||||
|
@ -350,3 +351,36 @@ export enum EnterpriseEditionFeature {
|
||||||
Sharing = 'sharing',
|
Sharing = 'sharing',
|
||||||
}
|
}
|
||||||
export const MAIN_NODE_PANEL_WIDTH = 360;
|
export const MAIN_NODE_PANEL_WIDTH = 360;
|
||||||
|
|
||||||
|
export const CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS = [
|
||||||
|
'ftp',
|
||||||
|
'ftps',
|
||||||
|
'dict',
|
||||||
|
'imap',
|
||||||
|
'imaps',
|
||||||
|
'ldap',
|
||||||
|
'ldaps',
|
||||||
|
'mqtt',
|
||||||
|
'pop',
|
||||||
|
'pop3s',
|
||||||
|
'rtmp',
|
||||||
|
'rtsp',
|
||||||
|
'scp',
|
||||||
|
'sftp',
|
||||||
|
'smb',
|
||||||
|
'smbs',
|
||||||
|
'smtp',
|
||||||
|
'smtps',
|
||||||
|
'telnet',
|
||||||
|
'tftp',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CURL_IMPORT_NODES_PROTOCOLS: { [key: string]: string } = {
|
||||||
|
'ftp': 'FTP',
|
||||||
|
'ftps': 'FTP',
|
||||||
|
'ldap': 'LDAP',
|
||||||
|
'ldaps': 'LDAP',
|
||||||
|
'mqtt': 'MQTT',
|
||||||
|
'imap': 'IMAP',
|
||||||
|
'imaps': 'IMAP',
|
||||||
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ import Vue from 'vue';
|
||||||
import {CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, VALUE_SURVEY_MODAL_KEY} from '@/constants';
|
import {CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, VALUE_SURVEY_MODAL_KEY} from '@/constants';
|
||||||
import { ITelemetrySettings } from 'n8n-workflow';
|
import { ITelemetrySettings } from 'n8n-workflow';
|
||||||
import { testHealthEndpoint } from '@/api/templates';
|
import { testHealthEndpoint } from '@/api/templates';
|
||||||
import {createApiKey, deleteApiKey, getApiKey} from "@/api/api-keys";
|
import {createApiKey, deleteApiKey, getApiKey } from "@/api/api-keys";
|
||||||
|
|
||||||
const module: Module<ISettingsState, IRootState> = {
|
const module: Module<ISettingsState, IRootState> = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
@ -183,6 +183,7 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async submitValueSurvey(context: ActionContext<ISettingsState, IRootState>, params: IN8nValueSurveyData) {
|
async submitValueSurvey(context: ActionContext<ISettingsState, IRootState>, params: IN8nValueSurveyData) {
|
||||||
try {
|
try {
|
||||||
const instanceId = context.state.settings.instanceId;
|
const instanceId = context.state.settings.instanceId;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getCurlToJson } from '@/api/curlHelper';
|
||||||
import { applyForOnboardingCall, fetchNextOnboardingPrompt, submitEmailOnSignup } from '@/api/workflow-webhooks';
|
import { applyForOnboardingCall, fetchNextOnboardingPrompt, submitEmailOnSignup } from '@/api/workflow-webhooks';
|
||||||
import {
|
import {
|
||||||
ABOUT_MODAL_KEY,
|
ABOUT_MODAL_KEY,
|
||||||
|
@ -22,6 +23,7 @@ import {
|
||||||
ONBOARDING_CALL_SIGNUP_MODAL_KEY,
|
ONBOARDING_CALL_SIGNUP_MODAL_KEY,
|
||||||
FAKE_DOOR_FEATURES,
|
FAKE_DOOR_FEATURES,
|
||||||
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
|
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { ActionContext, Module } from 'vuex';
|
import { ActionContext, Module } from 'vuex';
|
||||||
|
@ -100,6 +102,11 @@ const module: Module<IUiState, IRootState> = {
|
||||||
mode: '',
|
mode: '',
|
||||||
activeId: null,
|
activeId: null,
|
||||||
},
|
},
|
||||||
|
[IMPORT_CURL_MODAL_KEY]: {
|
||||||
|
open: false,
|
||||||
|
curlCommand: '',
|
||||||
|
httpNodeParameters: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
modalStack: [],
|
modalStack: [],
|
||||||
sidebarMenuCollapsed: true,
|
sidebarMenuCollapsed: true,
|
||||||
|
@ -167,6 +174,12 @@ const module: Module<IUiState, IRootState> = {
|
||||||
isVersionsOpen: (state: IUiState) => {
|
isVersionsOpen: (state: IUiState) => {
|
||||||
return state.modals[VERSIONS_MODAL_KEY].open;
|
return state.modals[VERSIONS_MODAL_KEY].open;
|
||||||
},
|
},
|
||||||
|
getCurlCommand: (state: IUiState) => {
|
||||||
|
return state.modals[IMPORT_CURL_MODAL_KEY].curlCommand;
|
||||||
|
},
|
||||||
|
getHttpNodeParameters: (state: IUiState) => {
|
||||||
|
return state.modals[IMPORT_CURL_MODAL_KEY].httpNodeParameters;
|
||||||
|
},
|
||||||
isModalOpen: (state: IUiState) => {
|
isModalOpen: (state: IUiState) => {
|
||||||
return (name: string) => state.modals[name].open;
|
return (name: string) => state.modals[name].open;
|
||||||
},
|
},
|
||||||
|
@ -231,6 +244,14 @@ const module: Module<IUiState, IRootState> = {
|
||||||
const { name, id } = params;
|
const { name, id } = params;
|
||||||
Vue.set(state.modals[name], 'activeId', id);
|
Vue.set(state.modals[name], 'activeId', id);
|
||||||
},
|
},
|
||||||
|
setCurlCommand: (state: IUiState, params: {name: string, command: string}) => {
|
||||||
|
const { name, command } = params;
|
||||||
|
Vue.set(state.modals[name], 'curlCommand', command);
|
||||||
|
},
|
||||||
|
setHttpNodeParameters: (state: IUiState, params: {name: string, parameters: string}) => {
|
||||||
|
const { name, parameters } = params;
|
||||||
|
Vue.set(state.modals[name], 'httpNodeParameters', parameters);
|
||||||
|
},
|
||||||
openModal: (state: IUiState, name: string) => {
|
openModal: (state: IUiState, name: string) => {
|
||||||
Vue.set(state.modals[name], 'open', true);
|
Vue.set(state.modals[name], 'open', true);
|
||||||
state.modalStack = [name].concat(state.modalStack);
|
state.modalStack = [name].concat(state.modalStack);
|
||||||
|
@ -323,6 +344,17 @@ const module: Module<IUiState, IRootState> = {
|
||||||
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
||||||
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
},
|
},
|
||||||
|
setCurlCommand: async (context: ActionContext<IUiState, IRootState>, { command }: {command: string}) => {
|
||||||
|
context.commit('setCurlCommand', { name: IMPORT_CURL_MODAL_KEY, command });
|
||||||
|
},
|
||||||
|
setHttpNodeParameters: async (context: ActionContext<IUiState, IRootState>, { parameters }) => {
|
||||||
|
context.commit('setHttpNodeParameters', { name: IMPORT_CURL_MODAL_KEY, parameters });
|
||||||
|
},
|
||||||
|
openExisitngCredential: async (context: ActionContext<IUiState, IRootState>, { id }: {id: string}) => {
|
||||||
|
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id });
|
||||||
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
||||||
|
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
|
},
|
||||||
openNewCredential: async (context: ActionContext<IUiState, IRootState>, { type }: {type: string}) => {
|
openNewCredential: async (context: ActionContext<IUiState, IRootState>, { type }: {type: string}) => {
|
||||||
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id: type });
|
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id: type });
|
||||||
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'new' });
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'new' });
|
||||||
|
@ -354,6 +386,9 @@ const module: Module<IUiState, IRootState> = {
|
||||||
context.commit('setMode', { name: COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, mode: COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE });
|
context.commit('setMode', { name: COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, mode: COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE });
|
||||||
context.commit('openModal', COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
context.commit('openModal', COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
||||||
},
|
},
|
||||||
|
async getCurlToJson(context: ActionContext<IUiState, IRootState>, curlCommand) {
|
||||||
|
return await getCurlToJson(context.rootGetters['getRestApiContext'], curlCommand);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1114,5 +1114,16 @@
|
||||||
"workflowSettings.showMessage.saveSettings.title": "Workflow settings saved",
|
"workflowSettings.showMessage.saveSettings.title": "Workflow settings saved",
|
||||||
"workflowSettings.timeoutAfter": "Timeout After",
|
"workflowSettings.timeoutAfter": "Timeout After",
|
||||||
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
|
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
|
||||||
"workflowSettings.timezone": "Timezone"
|
"workflowSettings.timezone": "Timezone",
|
||||||
|
"importCurlModal.title": "Import cURL command",
|
||||||
|
"importCurlModal.input.label": "cURL Command",
|
||||||
|
"importCurlModal.input.placeholder": "Paste the cURL command here",
|
||||||
|
"ImportCurlModal.notice.content": "This will overwrite any changes you have already made",
|
||||||
|
"importCurlModal.button.label": "Import",
|
||||||
|
"importParameter.label": "Import cURL",
|
||||||
|
"importParameter.showError.invalidCurlCommand.title": "Couldn’t import cURL command",
|
||||||
|
"importParameter.showError.invalidCurlCommand.message": "This command is in an unsupported format",
|
||||||
|
"importParameter.showError.invalidProtocol1.title": "Use the {node} node",
|
||||||
|
"importParameter.showError.invalidProtocol2.title": "Invalid Protocol",
|
||||||
|
"importParameter.showError.invalidProtocol.message": "The HTTP node doesn’t support {protocol} requests"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
1159
packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts
Normal file
1159
packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts
Normal file
File diff suppressed because it is too large
Load diff
1217
packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts
Normal file
1217
packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts
Normal file
File diff suppressed because it is too large
Load diff
1389
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
Normal file
1389
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -941,7 +941,8 @@ export type NodePropertyTypes =
|
||||||
| 'options'
|
| 'options'
|
||||||
| 'string'
|
| 'string'
|
||||||
| 'credentialsSelect'
|
| 'credentialsSelect'
|
||||||
| 'resourceLocator';
|
| 'resourceLocator'
|
||||||
|
| 'curlImport';
|
||||||
|
|
||||||
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue