From 1d64cab3993fb36bf924e8aca8dd32cb0abba1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=98=84=EC=84=B1=ED=95=84?= Date: Mon, 19 Feb 2024 12:59:05 +0900 Subject: [PATCH] load tester beta --- ApiTestManager/package-lock.json | 800 ++++++- ApiTestManager/package.json | 12 +- ApiTestManager/src/APIClient.js | 331 ++- ApiTestManager/src/APIScenario.js | 642 ++--- ApiTestManager/src/APIScenarioExecutor.js | 409 ++-- ApiTestManager/src/APITester.js | 2126 +++++++++-------- ApiTestManager/src/LoadTestManager.js | 261 ++ .../src/components/EditableInput.js | 25 +- .../src/components/LoadTestResult.js | 117 + .../src/components/LoadTestResultDetail.js | 117 + .../src/components/LoadTestResultList.js | 102 + ApiTestManager/src/components/LoadTester.js | 179 ++ .../src/components/PerformanceTable.js | 10 + .../src/components/PropertyTable.js | 22 +- .../src/components/table/Columns.js | 61 + ApiTestManager/src/components/table/Table.js | 91 + ApiTestManager/src/index.js | 69 +- ApiTestManager/webpack.config.js | 8 + .../testmaster/client/dto/ApiRequestDTO.java | 15 +- .../client/dto/ApiRequestKeyValueDTO.java | 13 +- .../dto/internal/ScenarioConnection.java | 32 + .../dto/internal/ScenarioConnections.java | 17 + .../client/dto/internal/ScenarioNode.java | 109 + .../client/mapper/ApiRequestMapper.java | 22 +- .../client/mapper/ApiScenarioHelper.java | 20 + .../client/mapper/ApiScenarioMapper.java | 31 +- .../client/service/HttpHandler.java | 4 +- .../client/service/NettyApiClient.java | 8 +- .../common/mapper/CommonMapper.java | 33 +- .../testmaster/config/ServletConfig.java | 27 +- .../config/WebSocketStompConfig.java | 48 + .../controller/LoadTestController.java | 31 + .../controller/LoadTestMgmtController.java | 62 + .../controller/LoadTestPageController.java | 15 + .../loadtest/dto/ApiRequestEvent.java | 22 + .../testmaster/loadtest/dto/LoadTestDTO.java | 111 + .../dto/LoadTestRequestAndResponse.java | 44 + .../loadtest/entity/APILoadTestResult.java | 26 + .../testmaster/loadtest/entity/LoadTest.java | 129 + .../loadtest/entity/LoadTestResult.java | 74 + .../loadtest/mapper/LoadTestMapper.java | 27 + .../repository/LoadTestRepository.java | 9 + .../repository/LoadTestResultRepository.java | 5 + .../service/ApiPerformanceAggregation.java | 33 + .../service/ApiPerformanceSummary.java | 28 + .../service/CallableLoadTestService.java | 87 + .../loadtest/service/CallableVirtualUser.java | 36 + .../testmaster/loadtest/service/Console.java | 13 + .../service/LoadTestGraphBuilder.java | 179 ++ .../loadtest/service/LoadTestMgmtService.java | 52 + .../loadtest/service/LoadTestMonitorMap.java | 65 + .../loadtest/service/LoadTestService.java | 229 ++ .../loadtest/service/VirtualUser.java | 246 ++ .../controller/ServerRestController.java | 43 + .../server/mapper/ServerMapper.java | 24 +- .../resources/templates/fragment/head.html | 2 +- .../resources/templates/fragment/header.html | 3 + .../templates/layout/api_tester_layout.html | 4 +- .../templates/layout/base_layout.html | 2 +- src/main/resources/templates/page/login.html | 2 +- .../templates/page/tester/apiScenario.html | 1 - .../templates/page/tester/apiTester.html | 79 +- .../templates/page/tester/loadTester.html | 100 + 63 files changed, 5600 insertions(+), 1944 deletions(-) create mode 100644 ApiTestManager/src/LoadTestManager.js create mode 100644 ApiTestManager/src/components/LoadTestResult.js create mode 100644 ApiTestManager/src/components/LoadTestResultDetail.js create mode 100644 ApiTestManager/src/components/LoadTestResultList.js create mode 100644 ApiTestManager/src/components/LoadTester.js create mode 100644 ApiTestManager/src/components/PerformanceTable.js create mode 100644 ApiTestManager/src/components/table/Columns.js create mode 100644 ApiTestManager/src/components/table/Table.js create mode 100644 src/main/java/com/eactive/testmaster/client/dto/internal/ScenarioConnection.java create mode 100644 src/main/java/com/eactive/testmaster/client/dto/internal/ScenarioConnections.java create mode 100644 src/main/java/com/eactive/testmaster/client/dto/internal/ScenarioNode.java create mode 100644 src/main/java/com/eactive/testmaster/client/mapper/ApiScenarioHelper.java create mode 100644 src/main/java/com/eactive/testmaster/config/WebSocketStompConfig.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/controller/LoadTestController.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/controller/LoadTestMgmtController.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/controller/LoadTestPageController.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/dto/ApiRequestEvent.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/dto/LoadTestDTO.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/dto/LoadTestRequestAndResponse.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/entity/APILoadTestResult.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/entity/LoadTest.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/entity/LoadTestResult.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/mapper/LoadTestMapper.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/repository/LoadTestRepository.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/repository/LoadTestResultRepository.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/ApiPerformanceAggregation.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/ApiPerformanceSummary.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/CallableLoadTestService.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/CallableVirtualUser.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/Console.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/LoadTestGraphBuilder.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/LoadTestMgmtService.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/LoadTestMonitorMap.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/LoadTestService.java create mode 100644 src/main/java/com/eactive/testmaster/loadtest/service/VirtualUser.java create mode 100644 src/main/java/com/eactive/testmaster/server/controller/ServerRestController.java create mode 100644 src/main/resources/templates/page/tester/loadTester.html diff --git a/ApiTestManager/package-lock.json b/ApiTestManager/package-lock.json index ead3678..2053476 100644 --- a/ApiTestManager/package-lock.json +++ b/ApiTestManager/package-lock.json @@ -10,18 +10,26 @@ "license": "ISC", "dependencies": { "@popperjs/core": "^2.11.8", + "@stomp/stompjs": "^7.0.0", "drawflow": "^0.0.59", + "dropzone": "^6.0.0-beta.2", + "golden-layout": "^2.6.0", "jqueryui": "^1.11.1", "monaco-editor": "^0.45.0", - "monaco-editor-workers": "^0.45.0" + "monaco-editor-workers": "^0.45.0", + "sockjs-client": "^1.6.1", + "split-grid": "^1.0.11", + "x-data-spreadsheet": "^1.1.9" }, "devDependencies": { "@babel/core": "^7.23.7", "@babel/preset-env": "^7.23.8", "babel-loader": "^9.1.3", - "css-loader": "^6.9.0", + "css-loader": "^6.10.0", "expose-loader": "^4.1.0", "file-loader": "^6.2.0", + "less": "^4.2.0", + "less-loader": "^12.2.0", "monaco-editor-webpack-plugin": "^7.1.0", "rimraf": "^5.0.5", "style-loader": "^3.3.4", @@ -1806,6 +1814,16 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@stomp/stompjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@stomp/stompjs/-/stompjs-7.0.0.tgz", + "integrity": "sha512-fGdq4wPDnSV/KyOsjq4P+zLc8MFWC3lMmP5FBgLWKPJTYcuCbAIrnRGjB7q2jHZdYCOD5vxLuFoKIYLy5/u8Pw==" + }, + "node_modules/@swc/helpers": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.2.14.tgz", + "integrity": "sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA==" + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -2286,6 +2304,14 @@ "ajv": "^8.8.2" } }, + "node_modules/ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -2397,6 +2423,35 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-polyfill": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz", + "integrity": "sha512-0l7mVU+LrQ2X/ZTUq63T5i3VyR2aTgcRTFmBcD6djQ/Fek6q1A9t5u0F4jZVYHzp78jwWAzGfLpAY1b4/I3lfg==", + "dependencies": { + "babel-runtime": "^6.22.0", + "core-js": "^2.4.0", + "regenerator-runtime": "^0.10.0" + } + }, + "node_modules/babel-polyfill/node_modules/regenerator-runtime": { + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha512-02YopEIhAgiBHWeoTiA8aitHDt8z6w+rQqNuIftlM+ZtvSl/brTouaU7DW6GO/cHtvxJvS4Hwv2ibKdxIRi24w==" + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/babel-runtime/node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2601,6 +2656,11 @@ "node": ">=4" } }, + "node_modules/chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha512-j/Toj7f1z98Hh2cYo2BVr85EpIRWqUi7rtRSGxh/cqUjqrnJe9l9UE7IUGd2vQ2p+kSHLkSzObQPZPLUC6TQwg==" + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2637,6 +2697,22 @@ "node": ">=6.0" } }, + "node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", @@ -2792,6 +2868,25 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, "node_modules/core-js-compat": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", @@ -2826,16 +2921,16 @@ } }, "node_modules/css-loader": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.0.tgz", - "integrity": "sha512-3I5Nu4ytWlHvOP6zItjiHlefBNtrH+oehq8tnQa2kO305qpVyx9XNIT1CXIj5bgCJs7qICBCkgCYxQLKPANoLA==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz", + "integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.31", + "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.3", - "postcss-modules-scope": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.4", + "postcss-modules-scope": "^3.1.1", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" @@ -2848,7 +2943,16 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { + "@rspack/core": "0.x || 1.x", "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/css-loader/node_modules/lru-cache": { @@ -2990,6 +3094,15 @@ "resolved": "https://registry.npmjs.org/drawflow/-/drawflow-0.0.59.tgz", "integrity": "sha512-HJM/8trYzignViTMzUSpskRclDhE62jM6oxjgqEJQBbkX9QvDG7JJwRlvdjqnjfL192Pdb0aIysrZMUqG8/vyg==" }, + "node_modules/dropzone": { + "version": "6.0.0-beta.2", + "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-6.0.0-beta.2.tgz", + "integrity": "sha512-k44yLuFFhRk53M8zP71FaaNzJYIzr99SKmpbO/oZKNslDjNXQsBTdfLs+iONd0U0L94zzlFzRnFdqbLcs7h9fQ==", + "dependencies": { + "@swc/helpers": "^0.2.13", + "just-extend": "^5.0.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -3032,6 +3145,25 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -3057,6 +3189,19 @@ "node": ">=4" } }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, "node_modules/es-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", @@ -3082,7 +3227,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -3163,6 +3307,14 @@ "node": ">=0.8.x" } }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3259,6 +3411,19 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "dependencies": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3284,7 +3449,6 @@ "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, "dependencies": { "websocket-driver": ">=0.5.1" }, @@ -3292,6 +3456,17 @@ "node": ">=0.8.0" } }, + "node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -3633,6 +3808,11 @@ "node": ">=4" } }, + "node_modules/golden-layout": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-2.6.0.tgz", + "integrity": "sha512-sIVQCiRWOymHbVD1Aw/T9/ijbPYAVGBlgGYd1N9MRKfcyBNSpjr87Vg9nSHm+RCT8ELrvK8IJYJV0QRJuVUkCQ==" + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3657,6 +3837,25 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3797,8 +3996,7 @@ "node_modules/http-parser-js": { "version": "0.5.8", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", - "dev": true + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" }, "node_modules/http-proxy": { "version": "1.18.1", @@ -3851,7 +4049,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -3871,6 +4068,19 @@ "postcss": "^8.1.0" } }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -3976,8 +4186,116 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inquirer": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.0.6.tgz", + "integrity": "sha512-thluxTGBXUGb8DuQcvH9/CM/CrcGyB5xUpWc9x6Slqcq1z/hRr2a6KxUpX4ddRfmbe0hg3E4jTvo5833aWz3BA==", + "dependencies": { + "ansi-escapes": "^1.1.0", + "chalk": "^1.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.1", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx": "^4.1.0", + "string-width": "^2.0.0", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "node_modules/inquirer/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inquirer/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } }, "node_modules/interpret": { "version": "3.1.1", @@ -4111,6 +4429,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -4247,6 +4571,11 @@ "node": ">=6" } }, + "node_modules/just-extend": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-5.1.1.tgz", + "integrity": "sha512-b+z6yF1d4EOyDgylzQo5IminlUmzSeqR1hs/bzjBNjuGras4FXq/6TrzjxfN0j+TmI0ltJzTNlqXUMCniciwKQ==" + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -4266,6 +4595,58 @@ "shell-quote": "^1.8.1" } }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -4304,6 +4685,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -4319,6 +4705,30 @@ "yallist": "^3.0.2" } }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4437,6 +4847,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==" + }, "node_modules/minipass": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", @@ -4478,8 +4893,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multicast-dns": { "version": "7.2.5", @@ -4494,6 +4908,11 @@ "multicast-dns": "cli.js" } }, + "node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -4512,6 +4931,36 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -4527,6 +4976,23 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/node-fetch": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz", + "integrity": "sha512-BDxbhLHXFFFvilHjh9xihcDyPkXQ+kjblxnl82zAX41xUYSNvuRpFRznmldR9+OKu+p+ULZ7hNoyunlLB5ecUA==", + "dependencies": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "node_modules/node-fetch/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -4563,6 +5029,14 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -4640,6 +5114,101 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opencollective": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/opencollective/-/opencollective-1.0.3.tgz", + "integrity": "sha512-YBRI0Qa8+Ui0/STV1qYuPrJm889PT3oCPHMVoL+8Y3nwCffj7PSrB2NlGgrhgBKDujxTjxknHWJ/FiqOsYcIDw==", + "dependencies": { + "babel-polyfill": "6.23.0", + "chalk": "1.1.3", + "inquirer": "3.0.6", + "minimist": "1.2.0", + "node-fetch": "1.6.3", + "opn": "4.0.2" + }, + "bin": { + "oc": "dist/bin/opencollective.js", + "opencollective": "dist/bin/opencollective.js" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/opencollective/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/opencollective/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/opencollective/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/opencollective/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/opencollective/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/opn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/opn/-/opn-4.0.2.tgz", + "integrity": "sha512-iPBWbPP4OEOzR1xfhpGLDh+ypKBOygunZhM9jBtA7FS5sKjEiMZw0EFb82hnDOmTZX90ZWLoZKUza4cVt8MexA==", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -4692,6 +5261,15 @@ "node": ">=6" } }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4783,6 +5361,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pkg-dir": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", @@ -4839,9 +5446,9 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", - "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz", + "integrity": "sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q==", "dev": true, "dependencies": { "icss-utils": "^5.0.0", @@ -4856,9 +5463,9 @@ } }, "node_modules/postcss-modules-scope": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.0.tgz", - "integrity": "sha512-SaIbK8XW+MZbd0xHPf7kdfA/3eOt7vxJ72IRecn3EzuZVLr1r0orzf0MX/pN8m+NMDoo6X/SQd8oeKqGZd8PXg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz", + "integrity": "sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA==", "dev": true, "dependencies": { "postcss-selector-parser": "^6.0.4" @@ -4932,6 +5539,13 @@ "node": ">= 0.10" } }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4956,6 +5570,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -5119,8 +5738,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.8", @@ -5160,6 +5778,37 @@ "node": ">=8" } }, + "node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -5187,11 +5836,23 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, "funding": [ { "type": "github", @@ -5210,8 +5871,14 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "dev": true, + "optional": true }, "node_modules/schema-utils": { "version": "4.2.0", @@ -5488,8 +6155,7 @@ "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sockjs": { "version": "0.3.24", @@ -5502,6 +6168,32 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "dependencies": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://tidelift.com/funding/github/npm/sockjs-client" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5560,6 +6252,11 @@ "wbuf": "^1.7.3" } }, + "node_modules/split-grid": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/split-grid/-/split-grid-1.0.11.tgz", + "integrity": "sha512-ELtFtxc3r5we5GZfe6Fi0BFFxIi2M6BY1YEntBscKRDD3zx4JVHqx2VnTRSQu1BixCYSTH3MTjKd4esI2R7EgQ==" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -5833,12 +6530,28 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5869,6 +6582,12 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5976,6 +6695,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6347,7 +7075,6 @@ "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", @@ -6361,7 +7088,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -6535,6 +7261,16 @@ } } }, + "node_modules/x-data-spreadsheet": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/x-data-spreadsheet/-/x-data-spreadsheet-1.1.9.tgz", + "integrity": "sha512-wk7knDBYdHjtWiHUVQryZMy00dsGNCF+6wMb5ykwEFcAtBYkYZakJCOCHpEo8onC0Lb/q2gIynWpbQxA4qakyg==", + "hasInstallScript": true, + "dependencies": { + "opencollective": "^1.0.3", + "opencollective-postinstall": "^2.0.2" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/ApiTestManager/package.json b/ApiTestManager/package.json index a43b56f..44d7f78 100644 --- a/ApiTestManager/package.json +++ b/ApiTestManager/package.json @@ -16,9 +16,11 @@ "@babel/core": "^7.23.7", "@babel/preset-env": "^7.23.8", "babel-loader": "^9.1.3", - "css-loader": "^6.9.0", + "css-loader": "^6.10.0", "expose-loader": "^4.1.0", "file-loader": "^6.2.0", + "less": "^4.2.0", + "less-loader": "^12.2.0", "monaco-editor-webpack-plugin": "^7.1.0", "rimraf": "^5.0.5", "style-loader": "^3.3.4", @@ -28,9 +30,15 @@ }, "dependencies": { "@popperjs/core": "^2.11.8", + "@stomp/stompjs": "^7.0.0", "drawflow": "^0.0.59", + "dropzone": "^6.0.0-beta.2", + "golden-layout": "^2.6.0", "jqueryui": "^1.11.1", "monaco-editor": "^0.45.0", - "monaco-editor-workers": "^0.45.0" + "monaco-editor-workers": "^0.45.0", + "sockjs-client": "^1.6.1", + "split-grid": "^1.0.11", + "x-data-spreadsheet": "^1.1.9" } } diff --git a/ApiTestManager/src/APIClient.js b/ApiTestManager/src/APIClient.js index 84d5846..c18a00b 100644 --- a/ApiTestManager/src/APIClient.js +++ b/ApiTestManager/src/APIClient.js @@ -8,159 +8,156 @@ class APIClient { - CLIENT_URL = 'mgmt/api/test.do'; + CLIENT_URL = 'mgmt/api/test.do'; - constructor() { - window.globals = {}; - this.variables = {}; - this.abortController = null; - this.contextPath = document.querySelector('meta[name="context-path"]').getAttribute('content'); + constructor() { + window.globals = {}; + this.variables = {}; + this.abortController = null; + this.contextPath = document.querySelector('meta[name="context-path"]').getAttribute('content'); - if (!this.contextPath) { - this.contextPath = '/'; + if (!this.contextPath) { + this.contextPath = '/'; + } + + if (!this.contextPath.endsWith('/')) { + this.contextPath += '/'; + } + } + + getClientUrl() { + return this.contextPath + this.CLIENT_URL; + } + + async sendAPIRequest(model, preProcessCallback, consoleOutput) { + try { + const updatedModel = await this.executePreRequestScript(model, consoleOutput); + let processedRequest = this.replacePlaceholdersWithVariables(updatedModel); + + if (preProcessCallback) { + preProcessCallback(processedRequest); + } + const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput); + const responseData = await response.json(); // Parse the JSON response + + if (!response.ok) { + const errorMessage = responseData.error || 'Unknown error'; + throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`); + } + + const finalResponse = this.formatResponse(responseData); + return this.executePostRequestScript(model.postRequestScript, finalResponse, consoleOutput).catch(error => { + $('.toast-body').text(error); + $('.toast').toast('show'); + return finalResponse; // Return the original response even if there's an error in the post-request script + }); + + } catch (error) { + throw error; + } + } + + formatResponse(data) { + // Format response as needed before post-request script + let response = {}; + response.body = data.body; + response.status = data.status; + response.headers = {}; + response.size = data.size; + _.forEach(data.headers, (header) => { + response.headers[header.key] = header.value; + }); + return response; + } + + abort() { + if (this.abortController) { + this.abortController.abort(); // abort the fetch + this.abortController = null; // reset the controller + } + } + + makeAjaxRequest(processedRequest, model) { + this.abortController = new AbortController(); + const {signal} = this.abortController; + + try { + return fetch(this.getClientUrl(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content') + }, + body: JSON.stringify(processedRequest), + signal: signal // pass the signal to the fetch + }); + } catch (error) { + if (error.name !== 'AbortError') { + console.error('Network request failed:', error); + } + throw error; + } + } + + replacePlaceholdersWithVariables(original) { + let request = JSON.parse(JSON.stringify(original)); + + let mergedVariables = {}; + _.forEach(window.globals, (value, key) => { + mergedVariables[key] = value; + }); + + if (this.variables) { + _.forEach(this.variables, (value, key) => { + mergedVariables[key] = value; + }); + } + + _.forEach(mergedVariables, (value, key) => { + const placeholder = `{{${key}}}`; + // Replace in path + request.path = request.path.replace(new RegExp(placeholder, 'g'), value); + + // Replace in requestBody + if (request.requestBody) { + request.requestBody = request.requestBody.replace(new RegExp(placeholder, 'g'), value); + } + + // Replace in headers + if (request.headers) { + let replaced = JSON.stringify(request.headers).replace(new RegExp(placeholder, 'g'), value); + request.headers = JSON.parse(replaced); + } + + // Replace in queryParams + if (request.queryParams) { + let replaced = JSON.stringify(request.queryParams).replace(new RegExp(placeholder, 'g'), value); + request.queryParams = JSON.parse(replaced); + } + }); + + return request; + } + + executePreRequestScript(model, consoleOutput) { + return new Promise((resolve, reject) => { + let script = model.preRequestScript; + let resultFrame = document.getElementById('preRequest'); + + this.variables = {}; + + window.preRequestComplete = (updatedModel, error) => { + if (error) { + reject(error); + } else { + resolve(updatedModel); } + }; - if (!this.contextPath.endsWith('/')) { - this.contextPath += '/'; - } - } - - getClientUrl() { - return this.contextPath + this.CLIENT_URL; - } - - async sendAPIRequest(model, preProcessCallback, consoleOutput) { - try { - const updatedModel = await this.executePreRequestScript(model, consoleOutput); - let processedRequest = this.replacePlaceholdersWithVariables(updatedModel); - - if (preProcessCallback) { - preProcessCallback(processedRequest); - } - const response = await this.makeAjaxRequest(processedRequest, model, consoleOutput); - const responseData = await response.json(); // Parse the JSON response - - if (!response.ok) { - const errorMessage = responseData.error || 'Unknown error'; - throw new Error(`API 호출 중 오류가 발생했습니다. 사유[${errorMessage}]`); - } - - const finalResponse = this.formatResponse(responseData); - return this.executePostRequestScript(model.postRequestScript, finalResponse, consoleOutput) - .catch(error => { - $('.toast-body').text(error); - $('.toast').toast('show'); - return finalResponse; // Return the original response even if there's an error in the post-request script - }); - - } catch (error) { - throw error; - } - } - - formatResponse(data) { - // Format response as needed before post-request script - let response = {}; - response.body = data.body; - response.status = data.status; - response.headers = {}; - response.size = data.size; - _.forEach(data.headers, (header) => { - response.headers[header.key] = header.value; - }); - return response; - } - - abort() { - if (this.abortController) { - this.abortController.abort(); // abort the fetch - this.abortController = null; // reset the controller - } - } - - makeAjaxRequest(processedRequest, model) { - this.abortController = new AbortController(); - const {signal} = this.abortController; - - try { - return fetch(this.getClientUrl(), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-XSRF-TOKEN': $('meta[name="_csrf"]').attr('content') - }, - body: JSON.stringify(processedRequest), - signal: signal // pass the signal to the fetch - }); - } catch (error) { - if (error.name !== 'AbortError') { - console.error('Network request failed:', error); - } - throw error; - } - } - - - replacePlaceholdersWithVariables(original) { - let request = JSON.parse(JSON.stringify(original)); - - let mergedVariables = {}; - _.forEach(window.globals, (value, key) => { - mergedVariables[key] = value; - }); - - if (this.variables) { - _.forEach(this.variables, (value, key) => { - mergedVariables[key] = value; - }); - } - - _.forEach(mergedVariables, (value, key) => { - const placeholder = `{{${key}}}`; - // Replace in path - request.path = request.path.replace(new RegExp(placeholder, 'g'), value); - - // Replace in requestBody - if (request.requestBody) { - request.requestBody = request.requestBody.replace(new RegExp(placeholder, 'g'), value); - } - - // Replace in headers - if (request.headers) { - let replaced = JSON.stringify(request.headers).replace(new RegExp(placeholder, 'g'), value); - request.headers = JSON.parse(replaced); - } - - // Replace in queryParams - if (request.queryParams) { - let replaced = JSON.stringify(request.queryParams).replace(new RegExp(placeholder, 'g'), value); - request.queryParams = JSON.parse(replaced); - } - }); - - return request; - } - - - executePreRequestScript(model, consoleOutput) { - return new Promise((resolve, reject) => { - let script = model.preRequestScript; - let resultFrame = document.getElementById('preRequest'); - - this.variables = {}; - - window.preRequestComplete = (updatedModel, error) => { - if (error) { - reject(error); - } else { - resolve(updatedModel); - } - }; - - window.temp_variable = this.variables; - window.currentRequest = model; - window.consoleOutput = consoleOutput; - resultFrame.srcdoc = ``; - }); - } + }); + } - executePostRequestScript(script, response, consoleOutput) { - return new Promise((resolve, reject) => { - let resultFrame = document.getElementById('postRequest'); - window.response = response; - window.temp_variable = this.variables; - window.postRequestComplete = (response, error) => { - if (error) { - reject(error); - } else { - resolve(response); - } - }; - window.consoleOutput = consoleOutput; - resultFrame.srcdoc = ``; - }); - } + }); + } } export default APIClient; diff --git a/ApiTestManager/src/APIScenario.js b/ApiTestManager/src/APIScenario.js index a820493..7080b44 100644 --- a/ApiTestManager/src/APIScenario.js +++ b/ApiTestManager/src/APIScenario.js @@ -1,368 +1,367 @@ -// require.config({paths: {'vs': '/plugins/vs'}}); import * as monaco from 'monaco-editor'; -import APIScenarioExecutor from "./APIScenarioExecutor"; -import Drawflow from "drawflow"; +import APIScenarioExecutor from './APIScenarioExecutor'; +import Drawflow from 'drawflow'; class APIScenario { - constructor(apiTester, contextPath) { - this.scenarioList = []; - this.currentScenario = null; - this.apiTester = apiTester; - this.contextPath = contextPath || '/'; - } + constructor(apiTester, contextPath) { + this.scenarioList = []; + this.currentScenario = null; + this.apiTester = apiTester; + this.contextPath = contextPath || '/'; + } - getAPIScenarioListURL() { - return this.contextPath + 'mgmt/api_scenario/list.do'; - } + getAPIScenarioListURL() { + return this.contextPath + 'mgmt/api_scenario/list.do'; + } - getAPIScenarioCreateURL() { - return this.contextPath + 'mgmt/api_scenario/create.do'; - } + getAPIScenarioCreateURL() { + return this.contextPath + 'mgmt/api_scenario/create.do'; + } - getAPIScenarioUpdateURL() { - return this.contextPath + 'mgmt/api_scenario/update.do'; - } + getAPIScenarioUpdateURL() { + return this.contextPath + 'mgmt/api_scenario/update.do'; + } - getAPIScenarioDeleteURL() { - return this.contextPath + 'mgmt/api_scenario/delete.do'; - } + getAPIScenarioDeleteURL() { + return this.contextPath + 'mgmt/api_scenario/delete.do'; + } - init() { - this.loadScenarioList(); - this.outputEditor = monaco.editor.create(document.getElementById('output'), { - value: '', - automaticLayout: true - }); - this.bindEvents(); + init() { + this.loadScenarioList(); + this.outputEditor = monaco.editor.create(document.getElementById('output'), { + value: '', + automaticLayout: true + }); + this.bindEvents(); - let id = document.getElementById('drawflow'); - this.editor = new Drawflow(id); - this.editor.reroute = true; - this.editor.reroute_fix_curvature = true; - this.editor.force_first_input = true; - this.editor.start(); - // this.editor.on('contextmenu', (event) => { - // let node = this.editor.getNodeFromId(event.target.id); - // }) + let id = document.getElementById('drawflow'); + this.editor = new Drawflow(id); + this.editor.reroute = true; + this.editor.reroute_fix_curvature = true; + this.editor.force_first_input = true; + this.editor.start(); + // this.editor.on('contextmenu', (event) => { + // let node = this.editor.getNodeFromId(event.target.id); + // }) - this.showOutput = true; - } + this.showOutput = true; + } - loadScenarioList(callback) { - const me = this; - this.currentAjaxRequest = $.ajax({ - url: this.getAPIScenarioListURL(), - type: 'GET', - contentType: 'application/json', - success: function (data) { - me.scenarioList = data; - let drawflowData = {drawflow: {Home: {data: {}}}}; - data.forEach((scenario) => { - let scenarioData = null; - try { - scenarioData = JSON.parse(scenario.scenario) - } catch (e) { - scenarioData = {}; - } - drawflowData.drawflow[scenario.id] = {data: scenarioData}; - }); - - me.editor.import(drawflowData); - me.editor.changeModule('Home'); - }, - error: function (response, textStatus, errorThrown) { - me.hideLoadingOverlay(); - $('.toast-body').text(response.responseJSON.error); - $('.toast').toast('show'); - }, - complete: function () { - me.renderScenarioList(); - if (callback) { - callback(); - } - me.hideLoadingOverlay(); - } - }); - } - - getScenarioList() { - return this.scenarioList; - } - - openScenario(event) { - event.preventDefault(); - let scenarioId = $(event.target).closest('li').data('id'); - this.openScenarioById(scenarioId); - } - - openScenarioById(scenarioId) { - document.querySelectorAll(".api-scenario-list li").forEach((node) => { - node.classList.remove('selected'); + loadScenarioList(callback) { + const me = this; + this.currentAjaxRequest = $.ajax({ + url: this.getAPIScenarioListURL(), + type: 'GET', + contentType: 'application/json', + success: function(data) { + me.scenarioList = data; + let drawflowData = {drawflow: {Home: {data: {}}}}; + data.forEach((scenario) => { + let scenarioData = null; + try { + scenarioData = JSON.parse(scenario.scenario); + } catch (e) { + scenarioData = {}; + } + drawflowData.drawflow[scenario.id] = {data: scenarioData}; }); - //find li by data-id and add class selected - document.querySelector(`.api-scenario-list li[data-id="${scenarioId}"]`).classList.add('selected'); + me.editor.import(drawflowData); + me.editor.changeModule('Home'); + }, + error: function(response, textStatus, errorThrown) { + me.hideLoadingOverlay(); + $('.toast-body').text(response.responseJSON.error); + $('.toast').toast('show'); + }, + complete: function() { + me.renderScenarioList(); + if (callback) { + callback(); + } + me.hideLoadingOverlay(); + } + }); + } - this.editor.changeModule(scenarioId); - this.currentScenario = scenarioId; - } + getScenarioList() { + return this.scenarioList; + } - getScenario(scenarioId) { - return this.getScenarioList().find((scenario) => scenario.id === scenarioId); - } + openScenario(event) { + event.preventDefault(); + let scenarioId = $(event.target).closest('li').data('id'); + this.openScenarioById(scenarioId); + } - showLoadingOverlay() { - $("#loading-overlay").show(); - } + openScenarioById(scenarioId) { + document.querySelectorAll('.api-scenario-list li').forEach((node) => { + node.classList.remove('selected'); + }); - hideLoadingOverlay() { - $("#loading-overlay").hide(); - } + //find li by data-id and add class selected + document.querySelector(`.api-scenario-list li[data-id="${scenarioId}"]`).classList.add('selected'); - showNewScenarioModal() { - $('#new_scenario_modal').find('input').val(''); - $('#new_scenario_modal').modal('show'); - } + this.editor.changeModule(scenarioId); + this.currentScenario = scenarioId; + } - createScenario() { - const name = $('#new_scenario_name').val(); - const me = this; - me.showLoadingOverlay(); - this.currentAjaxRequest = $.ajax({ - url: this.getAPIScenarioCreateURL(), - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({name: name, scenario: "{}", description: ""}), - success: function (data) { - me.loadScenarioList(() => { - me.openScenarioById(data.id); - let start = ` + getScenario(scenarioId) { + return this.getScenarioList().find((scenario) => scenario.id === scenarioId); + } + + showLoadingOverlay() { + $('#loading-overlay').show(); + } + + hideLoadingOverlay() { + $('#loading-overlay').hide(); + } + + showNewScenarioModal() { + $('#new_scenario_modal').find('input').val(''); + $('#new_scenario_modal').modal('show'); + } + + createScenario() { + const name = $('#new_scenario_name').val(); + const me = this; + me.showLoadingOverlay(); + this.currentAjaxRequest = $.ajax({ + url: this.getAPIScenarioCreateURL(), + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({name: name, scenario: '{}', description: ''}), + success: function(data) { + me.loadScenarioList(() => { + me.openScenarioById(data.id); + let start = `
Start
`; - me.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start); - me.saveScenario(); - }); - }, - error: function (response, textStatus, errorThrown) { - me.hideLoadingOverlay(); - $('.toast-body').text(response.responseJSON.error); - $('.toast').toast('show'); - }, - complete: function () { - me.hideLoadingOverlay(); - $('#new_scenario_modal').modal('hide'); - } + me.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start); + me.saveScenario(); }); - } + }, + error: function(response, textStatus, errorThrown) { + me.hideLoadingOverlay(); + $('.toast-body').text(response.responseJSON.error); + $('.toast').toast('show'); + }, + complete: function() { + me.hideLoadingOverlay(); + $('#new_scenario_modal').modal('hide'); + } + }); + } - saveScenario() { - if (this.currentScenario !== null) { - let data = this.editor.export(); - console.log(data.drawflow[this.currentScenario].data); - let updatedScenario = this.getScenario(this.currentScenario); - updatedScenario.scenario = JSON.stringify(data.drawflow[this.currentScenario].data); + saveScenario() { + if (this.currentScenario !== null) { + let data = this.editor.export(); + console.log(data.drawflow[this.currentScenario].data); + let updatedScenario = this.getScenario(this.currentScenario); + updatedScenario.scenario = JSON.stringify(data.drawflow[this.currentScenario].data); - const me = this; - me.showLoadingOverlay(); - this.currentAjaxRequest = $.ajax({ - url: this.getAPIScenarioUpdateURL(), - type: 'POST', - contentType: 'application/json', - data: JSON.stringify(updatedScenario), - error: function (response, textStatus, errorThrown) { - me.hideLoadingOverlay(); - $('.toast-body').text(response.responseJSON.error); - $('.toast').toast('show'); - }, - complete: function () { - me.hideLoadingOverlay(); - $('.toast-body').text('저장되었습니다.'); - $('.toast').toast('show'); - } - }); + const me = this; + me.showLoadingOverlay(); + this.currentAjaxRequest = $.ajax({ + url: this.getAPIScenarioUpdateURL(), + type: 'POST', + contentType: 'application/json', + data: JSON.stringify(updatedScenario), + error: function(response, textStatus, errorThrown) { + me.hideLoadingOverlay(); + $('.toast-body').text(response.responseJSON.error); + $('.toast').toast('show'); + }, + complete: function() { + me.hideLoadingOverlay(); + $('.toast-body').text('저장되었습니다.'); + $('.toast').toast('show'); } + }); } + } - showDeleteModal(event) { - const id = $(event.currentTarget).closest('li').data('id'); - $('#confirm_delete_scenario_modal').find('.confirm_delete_scenario').data('id', id); - $('#confirm_delete_scenario_modal').modal('show'); - } + showDeleteModal(event) { + const id = $(event.currentTarget).closest('li').data('id'); + $('#confirm_delete_scenario_modal').find('.confirm_delete_scenario').data('id', id); + $('#confirm_delete_scenario_modal').modal('show'); + } - removeScenario(event) { - const id = $(event.currentTarget).data('id'); - const me = this; - me.showLoadingOverlay(); - this.currentAjaxRequest = $.ajax({ - url: this.getAPIScenarioDeleteURL(), - type: 'POST', - contentType: 'application/json', - data: JSON.stringify({id: id}), - error: function (response, textStatus, errorThrown) { - me.hideLoadingOverlay(); - $('.toast-body').text(response.responseJSON.error); - $('.toast').toast('show'); - }, - complete: function () { - me.hideLoadingOverlay(); - $('#confirm_delete_scenario_modal').modal('hide'); - $('.toast-body').text('삭제되었습니다.'); - $('.toast').toast('show'); - me.editor.changeModule('Home'); - me.loadScenarioList(() => { - if (me.getScenarioList().length > 0) - me.openScenarioById(me.getScenarioList()[0].id); + removeScenario(event) { + const id = $(event.currentTarget).data('id'); + const me = this; + me.showLoadingOverlay(); + this.currentAjaxRequest = $.ajax({ + url: this.getAPIScenarioDeleteURL(), + type: 'POST', + contentType: 'application/json', + data: JSON.stringify({id: id}), + error: function(response, textStatus, errorThrown) { + me.hideLoadingOverlay(); + $('.toast-body').text(response.responseJSON.error); + $('.toast').toast('show'); + }, + complete: function() { + me.hideLoadingOverlay(); + $('#confirm_delete_scenario_modal').modal('hide'); + $('.toast-body').text('삭제되었습니다.'); + $('.toast').toast('show'); + me.editor.changeModule('Home'); + me.loadScenarioList(() => { + if (me.getScenarioList().length > 0) { + me.openScenarioById(me.getScenarioList()[0].id); + } - }); - } }); + } + }); + } + + toggleOutput() { + // Define your selectors as constants for easy changes and better readability + const scenarioContentSelector = '.api-scenario-content'; + const scenarioOutputSelector = '.api-scenario-output'; + const scenarioOutputBodySelector = '.api-scenario-output-body'; + const drawflowSelector = '#drawflow'; + const scenarioEditorSelector = '.scenario_editor'; + + // Ensure parentHeight is a number + let parentHeight = parseInt($(scenarioContentSelector).css('height'), 10); + + // Toggle the showOutput state + this.showOutput = !this.showOutput; + + if (!this.showOutput) { + $(scenarioOutputSelector).css('flex', '0'); + $(scenarioOutputBodySelector).hide(); + + let editorHeight = $(scenarioEditorSelector).css('height'); + $(drawflowSelector).css('height', editorHeight); + $(scenarioOutputSelector).removeClass('halfHeight'); + $(drawflowSelector).removeClass('halfHeight'); + } else { + $(scenarioOutputSelector).css('flex', '1'); + $(scenarioOutputSelector).addClass('halfHeight'); + $(scenarioOutputBodySelector).show(); + $(drawflowSelector).addClass('halfHeight'); + $(drawflowSelector).css('height', parentHeight / 2); } - toggleOutput() { - // Define your selectors as constants for easy changes and better readability - const scenarioContentSelector = '.api-scenario-content'; - const scenarioOutputSelector = '.api-scenario-output'; - const scenarioOutputBodySelector = '.api-scenario-output-body'; - const drawflowSelector = '#drawflow'; - const scenarioEditorSelector = '.scenario_editor'; + // Adjustments for when parentHeight isn't a valid number + if (isNaN(parentHeight)) { + console.error('parentHeight is not a number. Check the height of the api-scenario-content element.'); + // Fallback or additional error handling can be placed here + } + } - // Ensure parentHeight is a number - let parentHeight = parseInt($(scenarioContentSelector).css('height'), 10); + resizeEditor() { + //FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출 + this.outputEditor.layout({ + width: 0 + }); + this.outputEditor.layout({}); - // Toggle the showOutput state - this.showOutput = !this.showOutput; + } - if (!this.showOutput) { - $(scenarioOutputSelector).css('flex', '0'); - $(scenarioOutputBodySelector).hide(); + runScript() { + console.log(this.editor.export().drawflow); + let graph = APIScenarioExecutor.extractGraphFromStart(this.editor.export().drawflow[this.currentScenario].data); + this.executor = new APIScenarioExecutor(graph, this.apiTester, this.outputEditor); + } - let editorHeight = $(scenarioEditorSelector).css('height'); - $(drawflowSelector).css('height', editorHeight); - $(scenarioOutputSelector).removeClass('halfHeight'); - $(drawflowSelector).removeClass('halfHeight'); - } else { - $(scenarioOutputSelector).css('flex', '1'); - $(scenarioOutputSelector).addClass('halfHeight'); - $(scenarioOutputBodySelector).show(); - $(drawflowSelector).addClass('halfHeight'); - $(drawflowSelector).css('height', parentHeight / 2); - } + runScenarioAll() { + if (!this.executor) { + this.runScript(); + } + if (!this.executor.isDone) { + this.executor.executeAll(); + } else { + this.executor.reset(); + } + } - // Adjustments for when parentHeight isn't a valid number - if (isNaN(parentHeight)) { - console.error('parentHeight is not a number. Check the height of the api-scenario-content element.'); - // Fallback or additional error handling can be placed here - } + runScenarioStep() { + if (!this.executor) { + this.runScript(); + } + if (!this.executor.isDone) { + this.executor.executeCurrentNode(); + this.executor.moveToNextNode(); + } else { + this.executor.reset(); } + } - resizeEditor() { - //FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출 - this.outputEditor.layout({ - width: 0 - }); - this.outputEditor.layout({}); + resetScenario() { + } + + pauseScenario() { + + } + + stopScenario() { + if (this.executor) { + this.executor.reset(); } + this.outputEditor.setValue(''); + } - runScript() { - console.log(this.editor.export().drawflow); - let graph = APIScenarioExecutor.extractGraphFromStart(this.editor.export().drawflow[this.currentScenario].data); - this.executor = new APIScenarioExecutor(graph, this.apiTester, this.outputEditor); - } - - runScenarioAll() { - if (!this.executor) { - this.runScript(); - } - if (!this.executor.isDone) { - this.executor.executeAll(); - } else { - this.executor.reset(); - } - } - - - runScenarioStep() { - if (!this.executor) { - this.runScript(); - } - if (!this.executor.isDone) { - this.executor.executeCurrentNode(); - this.executor.moveToNextNode(); - } else { - this.executor.reset(); - } - - } - - resetScenario() { - - } - - pauseScenario() { - - } - - stopScenario() { - if (this.executor) - this.executor.reset(); - this.outputEditor.setValue(''); - } - - addStart() { - let start = ` + addStart() { + let start = `
Start
`; - this.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start); + this.editor.addNode('Start', 0, 1, 100, 100, 'start', {}, start); + } + + bindEvents() { + const events = [ + {type: 'click', selector: '.open_scenario', action: this.openScenario.bind(this)}, + {type: 'click', selector: '.export', action: this.runScript.bind(this)}, + {type: 'click', selector: '.toggle_output', action: this.toggleOutput.bind(this)}, + {type: 'click', selector: '.run_scenario_step', action: this.runScenarioStep.bind(this)}, + {type: 'click', selector: '.run_scenario_all', action: this.runScenarioAll.bind(this)}, + {type: 'click', selector: '.reset_scenario', action: this.resetScenario.bind(this)}, + {type: 'click', selector: '.pause_scenario', action: this.pauseScenario.bind(this)}, + {type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)}, + {type: 'click', selector: '#new_scenario', action: this.showNewScenarioModal.bind(this)}, + {type: 'click', selector: '.create_scenario', action: this.createScenario.bind(this)}, + {type: 'click', selector: '.save_scenario', action: this.saveScenario.bind(this)}, + // {type: 'click', selector: '.add_start', action: this.addStart.bind(this)}, + {type: 'click', selector: '.delete_scenario', action: this.showDeleteModal.bind(this)}, + {type: 'click', selector: '.confirm_delete_scenario', action: this.removeScenario.bind(this)}, + {type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)} + ]; + + for (let event of events) { + $(document).on(event.type, event.selector, event.action); } - bindEvents() { - const events = [ - {type: 'click', selector: '.open_scenario', action: this.openScenario.bind(this)}, - {type: 'click', selector: '.export', action: this.runScript.bind(this)}, - {type: 'click', selector: '.toggle_output', action: this.toggleOutput.bind(this)}, - {type: 'click', selector: '.run_scenario_step', action: this.runScenarioStep.bind(this)}, - {type: 'click', selector: '.run_scenario_all', action: this.runScenarioAll.bind(this)}, - {type: 'click', selector: '.reset_scenario', action: this.resetScenario.bind(this)}, - {type: 'click', selector: '.pause_scenario', action: this.pauseScenario.bind(this)}, - {type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)}, - {type: 'click', selector: '#new_scenario', action: this.showNewScenarioModal.bind(this)}, - {type: 'click', selector: '.create_scenario', action: this.createScenario.bind(this)}, - {type: 'click', selector: '.save_scenario', action: this.saveScenario.bind(this)}, - // {type: 'click', selector: '.add_start', action: this.addStart.bind(this)}, - {type: 'click', selector: '.delete_scenario', action: this.showDeleteModal.bind(this)}, - {type: 'click', selector: '.confirm_delete_scenario', action: this.removeScenario.bind(this)}, - {type: 'click', selector: '.stop_scenario', action: this.stopScenario.bind(this)} - ]; + window.addEventListener('resize', this.resizeEditor.bind(this)); + var me = this; - for (let event of events) { - $(document).on(event.type, event.selector, event.action); - } + $('#drawflow').droppable({ + drop: function(event, ui) { + const x = ui.offset.left - $(this).offset().left; + const y = ui.offset.top - $(this).offset().top; - window.addEventListener('resize', this.resizeEditor.bind(this)); - var me = this; + const dataset = ui.draggable[0].dataset; + var node = `
API
${dataset.name}
`; - $('#drawflow').droppable({ - drop: function (event, ui) { - const x = ui.offset.left - $(this).offset().left; - const y = ui.offset.top - $(this).offset().top; + me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node); + } + }); + } - const dataset = ui.draggable[0].dataset; - var node = `
API
${dataset.name}
`; - - me.editor.addNode(dataset.name, 1, 1, x, y, dataset.nodeType, dataset, node); - } - }); - } - - scenarioListTemplate(scenario) { - return ` + scenarioListTemplate(scenario) { + return `
  • ${scenario.name}
    @@ -371,19 +370,20 @@ class APIScenario {
  • `; - } + } - renderScenarioList() { - let scenarioList = this.getScenarioList(); - let scenarioListHtml = scenarioList.map((scenario) => this.scenarioListTemplate(scenario)).join(''); - $('.api-scenario-list').html(scenarioListHtml); - let nodes = document.querySelectorAll(".api-scenario-list li"); - nodes.forEach((node) => { - if ($(node).data('id') === this.currentScenario) { - node.classList.add('selected'); - } - }); - } + renderScenarioList() { + let scenarioList = this.getScenarioList(); + let scenarioListHtml = scenarioList.map((scenario) => this.scenarioListTemplate(scenario)).join(''); + $('.api-scenario-list').html(scenarioListHtml); + + let nodes = document.querySelectorAll('.api-scenario-list li'); + nodes.forEach((node) => { + if ($(node).data('id') === this.currentScenario) { + node.classList.add('selected'); + } + }); + } } export default APIScenario; diff --git a/ApiTestManager/src/APIScenarioExecutor.js b/ApiTestManager/src/APIScenarioExecutor.js index dd23f8c..711ac70 100644 --- a/ApiTestManager/src/APIScenarioExecutor.js +++ b/ApiTestManager/src/APIScenarioExecutor.js @@ -2,232 +2,227 @@ import * as monaco from 'monaco-editor'; import APIClient from './APIClient'; class APIScenarioExecutor { - constructor(graph, apiTester, outputEditor) { - this.graph = graph; // The graph data - this.apiClient = new APIClient(); - this.apiTester = apiTester; - this.outputEditor = outputEditor; - this.reset(); + constructor(graph, apiTester, outputEditor) { + this.graph = graph; // The graph data + this.apiClient = new APIClient(); + this.apiTester = apiTester; + this.outputEditor = outputEditor; + this.reset(); + } + + // Helper method to find the start node ID + findStartNodeId() { + for (let nodeId in this.graph) { + if (this.graph[nodeId].class === 'start') { + return nodeId; + } } + return null; // Return null if no start node is found + } - // Helper method to find the start node ID - findStartNodeId() { - for (let nodeId in this.graph) { - if (this.graph[nodeId].class === 'start') { - return nodeId; - } - } - return null; // Return null if no start node is found - } + reset() { + this.currentNodeId = this.findStartNodeId(); // Initialize at the 'start' node + this.visitedNodes = new Set(); // Keep track of visited nodes + this.isRunning = false; + this.isDone = false; - reset() { - this.currentNodeId = this.findStartNodeId(); // Initialize at the 'start' node - this.visitedNodes = new Set(); // Keep track of visited nodes - this.isRunning = false; - this.isDone = false; + let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node'); + allNodes.forEach(node => { + node.classList.remove('current-execution'); + node.classList.remove('result-success'); + node.classList.remove('result-error'); + node.classList.remove('result-abort'); + }); + } - let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node'); - allNodes.forEach(node => { - node.classList.remove('current-execution'); - node.classList.remove('result-success'); - node.classList.remove('result-error'); - node.classList.remove('result-abort'); - }); - } + executeCurrentNode() { + return new Promise((resolve, reject) => { + console.log(this.currentNodeId); - executeCurrentNode() { - return new Promise((resolve, reject) => { - console.log(this.currentNodeId) + let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node'); + allNodes.forEach(node => { + node.classList.remove('current-execution'); + }); - let allNodes = document.querySelector('#drawflow').querySelectorAll('.drawflow-node'); - allNodes.forEach(node => { - node.classList.remove('current-execution'); - }); + if (!this.currentNodeId) { + console.log('No current node to execute.'); + return; + } - if (!this.currentNodeId) { - console.log('No current node to execute.'); - return; - } + const currentNode = this.graph[this.currentNodeId]; - const currentNode = this.graph[this.currentNodeId]; + if (currentNode) { + this.isRunning = true; + this.visitedNodes.add(this.currentNodeId); // Mark this node as visited - if (currentNode) { - this.isRunning = true; - this.visitedNodes.add(this.currentNodeId); // Mark this node as visited - - let nodeElement = document.querySelector('#drawflow').querySelector(`.drawflow-node[id="node-${this.currentNodeId}"]`); - if (nodeElement) { - nodeElement.classList.add('current-execution'); - } else { - console.error('Node with ID ' + this.currentNodeId + ' not found'); - } - - if (currentNode.class === 'api') { - let model = JSON.parse(JSON.stringify(this.apiTester.getApiRequestModel(currentNode.data.id))); - model.responseModel = {}; - this.appendLog(`===============================================================\nExecuting API [${model.name}]`); - this.apiClient.sendAPIRequest(model, this.logHttpRequest.bind(this)) - .then(response => { - this.isRunning = false; - this.logHttpResponse(response); - if (response.status === 200 || response.status === 201) { - nodeElement.classList.add('result-success'); - } else { - nodeElement.classList.add('result-error'); - } - resolve(); - }) - .catch(error => { - nodeElement.classList.add('result-abort'); - $('.toast-body').text(error.message); - $('.toast').toast('show'); - this.appendLog(error.message); - reject(error); - }); - console.log(`===============================================================\nExecuting API request for node: ${currentNode.id} - ${currentNode.name}`); - } else if (currentNode.class === 'start') { - this.appendLog('Starting API scenario\n'); - nodeElement.classList.add('result-success'); - resolve(); - } else { - console.log(`Executing script for node: ${currentNode.id} - ${currentNode.name}`); - resolve(); - } - - } - }); - - } - - appendLog(text) { - var range = new monaco.Range(this.outputEditor.getModel().getLineCount(), 1, this.outputEditor.getModel().getLineCount(), 1); - var id = {major: 1, minor: 1}; - var op = {identifier: id, range: range, text: text + '\n\n', forceMoveMarkers: true}; - this.outputEditor.executeEdits("my-source", [op]); - } - - logHttpRequest(model) { - console.log(model); - let log = `[API Request]\n${model.method} ${model.path} HTTP/1.1\n[Request Headers]\n${this.parseRequestHeaders(model.headers)}\n[Request Body]\n${model.requestBody}`; - this.appendLog(log); - } - - logHttpResponse(response) { - let log = `[Response]\nHTTP/1.1 ${response.status}\n[Response Headers]\n${this.parseHeaders(response.headers)}\n[Response Body]\n${response.body}\n`; - this.appendLog(log); - } - - parseRequestHeaders(headers) { - let parsedHeaders = ''; - headers.forEach(header => { - if (header.enabled) { - parsedHeaders += `${header.key}: ${header.value}\n`; - } - }); - - - return parsedHeaders; - } - - parseHeaders(headers) { - //convert object to string with \n separation - //sort header by key - - let parsedHeaders = ''; - const sortedKeys = Object.keys(headers).sort(); // Sorting the keys alphabetically - sortedKeys.forEach(key => { - parsedHeaders += `${key}: ${headers[key]}\n`; - }); - return parsedHeaders; - } - - - // Method to move to the next node - moveToNextNode() { - const currentNode = this.graph[this.currentNodeId]; - console.log(currentNode); - - if (currentNode && currentNode.connections.length > 0) { - // Find the next unvisited node - const nextNode = currentNode.connections.find(nodeId => !this.visitedNodes.has(nodeId)); - if (nextNode) { - this.currentNodeId = nextNode; - } else { - console.log('No more unvisited connections to move to.'); - this.currentNodeId = null; // No more nodes to visit - } + let nodeElement = document.querySelector('#drawflow').querySelector(`.drawflow-node[id="node-${this.currentNodeId}"]`); + if (nodeElement) { + nodeElement.classList.add('current-execution'); } else { - console.log('Current node has no connections or does not exist.'); - this.currentNodeId = null; // No more nodes to visit - this.isDone = true; + console.error('Node with ID ' + this.currentNodeId + ' not found'); } - } - async executeAll() { - while (this.currentNodeId) { - console.log(this.currentNodeId); - try { - await this.executeCurrentNode(); - } catch (error) { - console.error('Error executing node: ', error); - // Handle error or break loop if necessary + if (currentNode.class === 'api') { + let model = JSON.parse(JSON.stringify(this.apiTester.getApiRequestModel(currentNode.data.id))); + model.responseModel = {}; + this.appendLog(`===============================================================\nExecuting API [${model.name}]`); + this.apiClient.sendAPIRequest(model, this.logHttpRequest.bind(this)).then(response => { + this.isRunning = false; + this.logHttpResponse(response); + if (response.status === 200 || response.status === 201) { + nodeElement.classList.add('result-success'); + } else { + nodeElement.classList.add('result-error'); } - await this.moveToNextNodeWithDelay(250); + resolve(); + }).catch(error => { + nodeElement.classList.add('result-abort'); + $('.toast-body').text(error.message); + $('.toast').toast('show'); + this.appendLog(error.message); + reject(error); + }); + console.log(`===============================================================\nExecuting API request for node: ${currentNode.id} - ${currentNode.name}`); + } else if (currentNode.class === 'start') { + this.appendLog('Starting API scenario\n'); + nodeElement.classList.add('result-success'); + resolve(); + } else { + console.log(`Executing script for node: ${currentNode.id} - ${currentNode.name}`); + resolve(); } - console.log('Completed execution of all API nodes.'); - } + } + }); - moveToNextNodeWithDelay(delay) { - return new Promise(resolve => { - setTimeout(() => { - this.moveToNextNode(); - resolve(); - }, delay); + } + + appendLog(text) { + var range = new monaco.Range(this.outputEditor.getModel().getLineCount(), 1, this.outputEditor.getModel().getLineCount(), 1); + var id = {major: 1, minor: 1}; + var op = {identifier: id, range: range, text: text + '\n\n', forceMoveMarkers: true}; + this.outputEditor.executeEdits('my-source', [op]); + } + + logHttpRequest(model) { + console.log(model); + let log = `[API Request]\n${model.method} ${model.path} HTTP/1.1\n[Request Headers]\n${this.parseRequestHeaders(model.headers)}\n[Request Body]\n${model.requestBody}`; + this.appendLog(log); + } + + logHttpResponse(response) { + let log = `[Response]\nHTTP/1.1 ${response.status}\n[Response Headers]\n${this.parseHeaders(response.headers)}\n[Response Body]\n${response.body}\n`; + this.appendLog(log); + } + + parseRequestHeaders(headers) { + let parsedHeaders = ''; + headers.forEach(header => { + if (header.enabled) { + parsedHeaders += `${header.key}: ${header.value}\n`; + } + }); + + return parsedHeaders; + } + + parseHeaders(headers) { + //convert object to string with \n separation + //sort header by key + + let parsedHeaders = ''; + const sortedKeys = Object.keys(headers).sort(); // Sorting the keys alphabetically + sortedKeys.forEach(key => { + parsedHeaders += `${key}: ${headers[key]}\n`; + }); + return parsedHeaders; + } + + // Method to move to the next node + moveToNextNode() { + const currentNode = this.graph[this.currentNodeId]; + console.log(currentNode); + + if (currentNode && currentNode.connections.length > 0) { + // Find the next unvisited node + const nextNode = currentNode.connections.find(nodeId => !this.visitedNodes.has(nodeId)); + if (nextNode) { + this.currentNodeId = nextNode; + } else { + console.log('No more unvisited connections to move to.'); + this.currentNodeId = null; // No more nodes to visit + } + } else { + console.log('Current node has no connections or does not exist.'); + this.currentNodeId = null; // No more nodes to visit + this.isDone = true; + } + } + + async executeAll() { + while (this.currentNodeId) { + console.log(this.currentNodeId); + try { + await this.executeCurrentNode(); + } catch (error) { + console.error('Error executing node: ', error); + // Handle error or break loop if necessary + } + await this.moveToNextNodeWithDelay(250); + } + console.log('Completed execution of all API nodes.'); + } + + moveToNextNodeWithDelay(delay) { + return new Promise(resolve => { + setTimeout(() => { + this.moveToNextNode(); + resolve(); + }, delay); + }); + } + + static extractGraphFromStart(graphData) { + console.log(graphData); + let graph = {}; + + // Helper function to recursively build the graph + function buildGraph(nodeId) { + const node = graphData[nodeId]; + if (!node || graph[nodeId]) { + // If the node doesn't exist or has already been visited, stop the recursion + return; + } + + // Initialize the node in the graph + graph[nodeId] = { + id: node.id, + name: node.name, + class: node.class, + data: node.data, + connections: [] + }; + + // If the node has outputs, recursively add connected nodes + if (node.outputs) { + Object.values(node.outputs).forEach(output => { + output.connections.forEach(connection => { + graph[nodeId].connections.push(connection.node); + buildGraph(connection.node); // Recurse + }); }); + } } - static extractGraphFromStart(graphData) { - console.log(graphData) - let graph = {}; - - // Helper function to recursively build the graph - function buildGraph(nodeId) { - const node = graphData[nodeId]; - if (!node || graph[nodeId]) { - // If the node doesn't exist or has already been visited, stop the recursion - return; - } - - // Initialize the node in the graph - graph[nodeId] = { - id: node.id, - name: node.name, - class: node.class, - data: node.data, - connections: [] - }; - - // If the node has outputs, recursively add connected nodes - if (node.outputs) { - Object.values(node.outputs).forEach(output => { - output.connections.forEach(connection => { - graph[nodeId].connections.push(connection.node); - buildGraph(connection.node); // Recurse - }); - }); - } - } - - // Find the start node - for (let nodeId in graphData) { - if (graphData[nodeId].class === 'start') { - buildGraph(nodeId); // Start building the graph from the 'start' node - break; // Assuming there's only one start node - } - } - - return graph; + // Find the start node + for (let nodeId in graphData) { + if (graphData[nodeId].class === 'start') { + buildGraph(nodeId); // Start building the graph from the 'start' node + break; // Assuming there's only one start node + } } + + return graph; + } } export default APIScenarioExecutor; diff --git a/ApiTestManager/src/APITester.js b/ApiTestManager/src/APITester.js index 64bf661..95117c9 100644 --- a/ApiTestManager/src/APITester.js +++ b/ApiTestManager/src/APITester.js @@ -1,80 +1,74 @@ import * as monaco from 'monaco-editor'; import APIClient from './APIClient'; import EditableInput from './components/EditableInput'; -import {createPopper} from "@popperjs/core/lib/popper-lite"; -import PropertyTable from "./components/PropertyTable"; -import VariableSnippet from "./components/VariableSnippet"; - - +import {createPopper} from '@popperjs/core/lib/popper-lite'; +import PropertyTable from './components/PropertyTable'; +import VariableSnippet from './components/VariableSnippet'; class APITester { - constructor(contextPath) { - this.servers = []; - this.apiTabTitleTarget = '#apiRequestTabTitle'; - this.apiTabContentTarget = '#apiRequestTabContent'; - this.collectionTarget = '#side_menubar'; - this.currentIndex = 0; - this.apiRequests = []; //탭 열린 API - this.collections = []; //API 컬렉션 - this.requestEditors = []; //API request body editor - this.responseEditors = []; //API response body editor - this.consoleEditors = []; //API console editor - this.preRequestEditors = []; - this.postRequestEditors = []; + constructor(contextPath) { + this.servers = []; + this.apiTabTitleTarget = '#apiRequestTabTitle'; + this.apiTabContentTarget = '#apiRequestTabContent'; + this.collectionTarget = '#side_menubar'; + this.currentIndex = 0; + this.apiRequests = []; //탭 열린 API + this.collections = []; //API 컬렉션 + this.requestEditors = []; //API request body editor + this.responseEditors = []; //API response body editor + this.consoleEditors = []; //API console editor + this.preRequestEditors = []; + this.postRequestEditors = []; - this.apiClient = new APIClient(); - this.popperContent = document.getElementById('popperContent'); - this.popperInstance = null; - this.contextPath = contextPath || '/'; - } + this.apiClient = new APIClient(); + this.popperContent = document.getElementById('popperContent'); + this.popperInstance = null; + this.contextPath = contextPath || '/'; + } - getCollectionListURL(){ - return this.contextPath + 'mgmt/collections/list.do'; - } + getCollectionListURL() { + return this.contextPath + 'mgmt/collections/list.do'; + } - getCollectionCreateURL(){ - return this.contextPath + 'mgmt/collections/create.do'; - } + getCollectionCreateURL() { + return this.contextPath + 'mgmt/collections/create.do'; + } - getCollectionUpdateURL(){ - return this.contextPath + 'mgmt/collections/update.do'; - } + getCollectionUpdateURL() { + return this.contextPath + 'mgmt/collections/update.do'; + } - getCollectionDeleteURL(){ - return this.contextPath + 'mgmt/collections/delete.do'; - } + getCollectionDeleteURL() { + return this.contextPath + 'mgmt/collections/delete.do'; + } - getAPISaveURL(collectionId){ - return this.contextPath + `mgmt/collections/${collectionId}/apis/save.do`; - } + getAPISaveURL(collectionId) { + return this.contextPath + `mgmt/collections/${collectionId}/apis/save.do`; + } - getAPIDeleteURL(collectionId){ - return this.contextPath + `mgmt/collections/${collectionId}/apis/delete.do`; - } + getAPIDeleteURL(collectionId) { + return this.contextPath + `mgmt/collections/${collectionId}/apis/delete.do`; + } + renderServers(selectedServer) { + const optionsHtml = this.servers.map(server => ``).join(''); - - renderServers(selectedServer) { - const optionsHtml = this.servers.map(server => ` - - `).join(''); - - return ` ${optionsHtml}`; - } + } - basePath(serverId) { - if (serverId === undefined || serverId === null || serverId === '') { - return ''; - } - return this.servers.filter(server => server.id == serverId)[0].basePath; // == : 타입 비교 없이 값만 비교 + basePath(serverId) { + if (serverId === undefined || serverId === null || serverId === '') { + return ''; } + return this.servers.filter(server => server.id == serverId)[0].basePath; // == : 타입 비교 없이 값만 비교 + } - apiRequestTemplate(apiRequest, index) { - return ` -
    + apiRequestTemplate(apiRequest, index) { + return ` +
    ${apiRequest.collectionName} > ${apiRequest.name}
    @@ -105,128 +99,136 @@ class APITester {
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    -
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    +
    + +
    +
    + Response + + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + +
    이름
    +
    +
    +
    +
    -
    - Response - - - -
    - -
    -
    -
    -
    -
    - - - - - - - - - -
    이름
    -
    -
    -
    -
    + `; - } + } - - headerTemplate(header, index) { - return ` + headerTemplate(header, index) { + return `
    @@ -244,10 +246,10 @@ class APITester { `; - } + } - responseHeaderTemplate(key, value) { - return ` + responseHeaderTemplate(key, value) { + return ` @@ -257,10 +259,10 @@ class APITester { `; - } + } - collectionTemplate(collection, index) { - return ` + collectionTemplate(collection, index) { + return `
  • `; - } + } - collectionApiTemplate(api, apiIndex, collectionId) { - return ` + collectionApiTemplate(api, apiIndex, collectionId) { + return `
  • ${api.name}
    @@ -291,952 +293,954 @@ class APITester {
  • `; + } + + init(servers) { + if (servers) { + this.servers = servers; + if (this.servers.length === 0) { + $('.toast-body').text('서버관리에서 서버를 등록해주세요.'); + $('.toast').toast('show'); + } + } + this.bindEvents(); + this.loadCollections(); + } + + bindEvents() { + const events = [ + {selector: '.btn_add_header', action: this.addHeader.bind(this)}, + {selector: '.btn_remove_header', action: this.removeHeader.bind(this)}, + {selector: '.send', action: this.handleSendAPIRequest.bind(this)}, + {selector: '.save', action: this.saveAPIRequest.bind(this)}, + {selector: '.save_collection', action: this.addCollection.bind(this)}, + {selector: '#cancel-ajax', action: this.cancelAjaxRequest.bind(this)}, + {selector: '#new_collection', action: this.showNewCollectionModal.bind(this)}, + {selector: '#new_request', action: this.showNewApiRequestModal.bind(this)}, + {selector: '.edit_collection', action: this.showCollectionEditModal.bind(this)}, + {selector: '.delete_collection', action: this.showCollectionDeleteModal.bind(this)}, + {selector: '.confirm_delete', action: this.removeCollection.bind(this)}, + {selector: '.add_new_request', action: this.addAPIRequest.bind(this)}, + {selector: '.edit_api_request', action: this.showEditNameModal.bind(this)}, + {selector: '.delete_api_request', action: this.showDeleteApiRequestModal.bind(this)}, + {selector: '.open_api_request', action: this.openApiRequest.bind(this)}, + {selector: '.confirm_delete_api', action: this.deleteApiRequest.bind(this)}, + {selector: '.close_tab', action: this.closeTab.bind(this)} + ]; + + for (let event of events) { + $(document).on('click', event.selector, event.action); } - init(servers) { - if (servers) { - this.servers = servers; - if (this.servers.length === 0) { - $('.toast-body').text('서버관리에서 서버를 등록해주세요.'); - $('.toast').toast('show'); - } + $(document).on('change', '.path', this.parseParam.bind(this)); + $(document).on('change', '.request_headers input', this.handleUIUpdate.bind(this)); + $(document).on('change', '.api_request_info select, .api_request_info input', this.handleUIUpdate.bind(this)); + $(document).on('change', '.api_request_info select', this.handleUpdateServer.bind(this)); + + window.addEventListener('resize', this.resizeEditor.bind(this)); + + } + + resizeEditor() { + //FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출 + + for (let i = 0; i < this.requestEditors.length; i++) { + this.requestEditors[i].layout({ + width: 0 + }); + } + for (let i = 0; i < this.responseEditors.length; i++) { + this.responseEditors[i].layout({ + width: 0 + }); + } + for (let i = 0; i < this.preRequestEditors.length; i++) { + this.preRequestEditors[i].layout({ + width: 0 + }); + } + for (let i = 0; i < this.postRequestEditors.length; i++) { + this.postRequestEditors[i].layout({ + width: 0 + }); + } + + for (let i = 0; i < this.requestEditors.length; i++) { + this.requestEditors[i].layout({}); + } + for (let i = 0; i < this.responseEditors.length; i++) { + this.responseEditors[i].layout({}); + } + for (let i = 0; i < this.preRequestEditors.length; i++) { + this.preRequestEditors[i].layout({}); + } + for (let i = 0; i < this.postRequestEditors.length; i++) { + this.postRequestEditors[i].layout({}); + } + + } + + showCollectionDeleteModal(event) { + const collectionId = $(event.currentTarget).closest('button').data('collection'); + $('#confirm_delete_modal').find('.confirm_delete').data('id', collectionId); + $('#confirm_delete_modal').modal('show'); + } + + getApiRequestModel(apiId) { + let foundApi = null; + this.collections.forEach(function(collection) { + collection.apis.forEach(function(api) { + if (api.id === apiId) { + foundApi = api; } - this.bindEvents(); - this.loadCollections(); + }); + }); + + return foundApi; + } + + showCollectionEditModal(event) { + const me = this; + const collectionId = $(event.currentTarget).closest('button').data('collection'); + const collection = this.collections.filter(function(collection) { + return collection.id === collectionId; + })[0]; + + $('#edit_name_modal').find('input').val(''); + $('#edit_name_modal').modal('show'); + + let saveBtn = $('#edit_name_modal').find('button.change_name'); + saveBtn.off('click'); + + saveBtn.on('click', function() { + collection.name = $('#edit_name_modal').find('input').val(); + me.saveCollection(collection.id, collection.name); + me.renderCollections(); + $('#edit_name_modal').modal('hide'); + }); + } + + showDeleteApiRequestModal(event) { + let collectionId = $(event.currentTarget).closest('li').data('collection'); + let apiId = $(event.currentTarget).closest('li').data('id'); + $('#confirm_delete_api_modal').find('.confirm_delete_api').data('id', apiId); + $('#confirm_delete_api_modal').find('.confirm_delete_api').data('collection', collectionId); + $('#confirm_delete_api_modal').modal('show'); + } + + showNewApiRequestModal(event) { + if (this.collections.length == 0) { + $('.toast-body').text('새로운 컬렉션을 추가해 주세요.'); + $('.toast').toast('show'); + return; } + $('#new_api_request_modal').modal('show'); + } - bindEvents() { - const events = [ - {selector: '.btn_add_header', action: this.addHeader.bind(this)}, - {selector: '.btn_remove_header', action: this.removeHeader.bind(this)}, - {selector: '.send', action: this.handleSendAPIRequest.bind(this)}, - {selector: '.save', action: this.saveAPIRequest.bind(this)}, - {selector: '.save_collection', action: this.addCollection.bind(this)}, - {selector: '#cancel-ajax', action: this.cancelAjaxRequest.bind(this)}, - {selector: '#new_collection', action: this.showNewCollectionModal.bind(this)}, - {selector: '#new_request', action: this.showNewApiRequestModal.bind(this)}, - {selector: '.edit_collection', action: this.showCollectionEditModal.bind(this)}, - {selector: '.delete_collection', action: this.showCollectionDeleteModal.bind(this)}, - {selector: '.confirm_delete', action: this.removeCollection.bind(this)}, - {selector: '.add_new_request', action: this.addAPIRequest.bind(this)}, - {selector: '.edit_api_request', action: this.showEditNameModal.bind(this)}, - {selector: '.delete_api_request', action: this.showDeleteApiRequestModal.bind(this)}, - {selector: '.open_api_request', action: this.openApiRequest.bind(this)}, - {selector: '.confirm_delete_api', action: this.deleteApiRequest.bind(this)}, - {selector: '.close_tab', action: this.closeTab.bind(this)}, - ]; + showNewCollectionModal(event) { + $('#new_collection_modal').find('input').val(''); + $('#new_collection_modal').modal('show'); + } - for (let event of events) { - $(document).on('click', event.selector, event.action); - } + showEditNameModal(event) { + const me = this; + const apiId = $(event.currentTarget).closest('li').data('id'); + const collectionId = $(event.currentTarget).closest('li').data('collection'); - $(document).on('change', '.path', this.parseParam.bind(this)); - $(document).on('change', '.request_headers input', this.handleUIUpdate.bind(this)); - $(document).on('change', '.api_request_info select, .api_request_info input', this.handleUIUpdate.bind(this)); - $(document).on('change', '.api_request_info select', this.handleUpdateServer.bind(this)); + const collection = this.collections.filter(function(collection) { + return collection.id === collectionId; + })[0]; - window.addEventListener('resize', this.resizeEditor.bind(this)); + let apiRequest = collection.apis.filter(function(api) { + return api.id === apiId; + })[0]; + apiRequest.collectionId = collectionId; + let saveBtn = $('#edit_name_modal').find('button.change_name'); + saveBtn.off('click'); + saveBtn.on('click', function() { + apiRequest.name = $('#edit_name_modal').find('input').val(); + me.changeAPIName(apiRequest); + me.renderCollections(); + $('#edit_name_modal').modal('hide'); + + //탭이 열려 있으면, 닫았다가 다시 열어야 함. + me.renderTabHeader(); + me.renderTabContent(apiRequest.index); + + }); + $('#edit_name_modal').find('input').val(''); + $('#edit_name_modal').modal('show'); + } + + getFieldValue(element) { + if ($(element).is('select') || $(element).is('input[type="text"]')) { + return $(element).val(); } - - resizeEditor() { - //FIXME: 창 크기 변경시 자동으로 editor 크기가 조정되지 않아서, 0으로 강제 변경후 다시 layout() 호출 - - for (let i = 0; i < this.requestEditors.length; i++) { - this.requestEditors[i].layout({ - width: 0 - }); - } - for (let i = 0; i < this.responseEditors.length; i++) { - this.responseEditors[i].layout({ - width: 0 - }); - } - for (let i = 0; i < this.preRequestEditors.length; i++) { - this.preRequestEditors[i].layout({ - width: 0 - }); - } - for (let i = 0; i < this.postRequestEditors.length; i++) { - this.postRequestEditors[i].layout({ - width: 0 - }); - } - - for (let i = 0; i < this.requestEditors.length; i++) { - this.requestEditors[i].layout({}); - } - for (let i = 0; i < this.responseEditors.length; i++) { - this.responseEditors[i].layout({}); - } - for (let i = 0; i < this.preRequestEditors.length; i++) { - this.preRequestEditors[i].layout({}); - } - for (let i = 0; i < this.postRequestEditors.length; i++) { - this.postRequestEditors[i].layout({}); - } - + if ($(element).is('input[type="checkbox"]')) { + return $(element).is(':checked'); } + } - showCollectionDeleteModal(event) { - const collectionId = $(event.currentTarget).closest('button').data('collection'); - $('#confirm_delete_modal').find('.confirm_delete').data('id', collectionId); - $('#confirm_delete_modal').modal('show'); + handleUIUpdate(event) { + if (event) { + event.preventDefault(); + const element = event.currentTarget; + const fieldName = $(element).attr('name'); + const newValue = this.getFieldValue(element); + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); + let model = this.apiRequests[tabIndex]; + + if (fieldName !== '') { + _.set(model, fieldName, newValue); + } } + } - getApiRequestModel(apiId) { - let foundApi = null; - this.collections.forEach(function (collection) { - collection.apis.forEach(function (api) { - if (api.id === apiId) { - foundApi = api; - } - }); - }); + handleUpdateServer(event) { + const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); + let model = this.apiRequests[tabIndex]; + let target = $('#api_request_' + model.id); + target.find('input[name="basePath"]').val(this.basePath(model.server)); + } - return foundApi; - } + renderHeaders(headers) { + return headers.map((header, index) => this.headerTemplate(header, index)).join(''); + } - showCollectionEditModal(event) { - const me = this; - const collectionId = $(event.currentTarget).closest('button').data('collection'); - const collection = this.collections.filter(function (collection) { - return collection.id === collectionId; - })[0]; + renderTabHeader() { + let apiRequestTabTitle = $(this.apiTabTitleTarget); + apiRequestTabTitle.empty(); - $('#edit_name_modal').find('input').val(''); - $('#edit_name_modal').modal('show'); - - let saveBtn = $('#edit_name_modal').find('button.change_name'); - saveBtn.off('click'); - - saveBtn.on('click', function () { - collection.name = $('#edit_name_modal').find('input').val(); - me.saveCollection(collection.id, collection.name); - me.renderCollections(); - $('#edit_name_modal').modal('hide'); - }); - } - - showDeleteApiRequestModal(event) { - let collectionId = $(event.currentTarget).closest('li').data('collection'); - let apiId = $(event.currentTarget).closest('li').data('id'); - $('#confirm_delete_api_modal').find('.confirm_delete_api').data('id', apiId); - $('#confirm_delete_api_modal').find('.confirm_delete_api').data('collection', collectionId); - $('#confirm_delete_api_modal').modal('show'); - } - - showNewApiRequestModal(event) { - if (this.collections.length == 0) { - $('.toast-body').text('새로운 컬렉션을 추가해 주세요.'); - $('.toast').toast('show'); - return; - } - $('#new_api_request_modal').modal('show'); - } - - showNewCollectionModal(event) { - $('#new_collection_modal').find('input').val(''); - $('#new_collection_modal').modal('show'); - } - - showEditNameModal(event) { - const me = this; - const apiId = $(event.currentTarget).closest('li').data('id'); - const collectionId = $(event.currentTarget).closest('li').data('collection'); - - const collection = this.collections.filter(function (collection) { - return collection.id === collectionId; - })[0]; - - let apiRequest = collection.apis.filter(function (api) { - return api.id === apiId; - })[0]; - - apiRequest.collectionId = collectionId; - - let saveBtn = $('#edit_name_modal').find('button.change_name'); - saveBtn.off('click'); - saveBtn.on('click', function () { - apiRequest.name = $('#edit_name_modal').find('input').val(); - me.changeAPIName(apiRequest); - me.renderCollections(); - $('#edit_name_modal').modal('hide'); - - //탭이 열려 있으면, 닫았다가 다시 열어야 함. - me.renderTabHeader(); - me.renderTabContent(apiRequest.index); - - }); - $('#edit_name_modal').find('input').val(''); - $('#edit_name_modal').modal('show'); - } - - getFieldValue(element) { - if ($(element).is('select') || $(element).is('input[type="text"]')) { - return $(element).val(); - } - if ($(element).is('input[type="checkbox"]')) { - return $(element).is(':checked'); - } - } - - handleUIUpdate(event) { - if (event) { - event.preventDefault(); - const element = event.currentTarget; - const fieldName = $(element).attr('name'); - const newValue = this.getFieldValue(element); - const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); - let model = this.apiRequests[tabIndex]; - - if (fieldName !== '') { - _.set(model, fieldName, newValue); - } - } - } - - handleUpdateServer(event) { - const tabIndex = $('#apiRequestTabContent').children().index($(event.currentTarget).closest('.api_request')); - let model = this.apiRequests[tabIndex]; - let target = $('#api_request_' + model.id); - target.find('input[name="basePath"]').val(this.basePath(model.server)); - } - - - renderHeaders(headers) { - return headers.map((header, index) => this.headerTemplate(header, index)).join(''); - } - - renderTabHeader() { - let apiRequestTabTitle = $(this.apiTabTitleTarget); - apiRequestTabTitle.empty(); - - for (let idx = 0; idx < this.apiRequests.length; idx++) { - let li = ``; - apiRequestTabTitle.append(li); + apiRequestTabTitle.append(li); + } + } + + showPopper(element, text) { + const variable = text.replace(/{{|}}/g, ''); + this.popperContent.style.display = 'block'; + + const innerDiv = this.popperContent.querySelector('.variable_popper'); + innerDiv.textContent = '{{' + variable + '}} = ' + (window.globals[variable] === undefined ? '' : window.globals[variable]); + + this.popperInstance = createPopper(element, this.popperContent, { + placement: 'bottom', // Popper below the element + modifiers: [ + { + name: 'offset', + options: { + offset: [0, 8] // Adjust the position if needed + } } + ] + }); + } + + hidePopper(element) { + if (this.popperInstance) { + this.popperInstance.destroy(); + this.popperInstance = null; } + this.popperContent.style.display = 'none'; + } - showPopper(element, text) { - const variable = text.replace(/{{|}}/g, ''); - this.popperContent.style.display = 'block'; + renderTabContent(index) { + var me = this; + let apiRequestTabContent = $(this.apiTabContentTarget); + let model = this.apiRequests[index]; - const innerDiv = this.popperContent.querySelector('.variable_popper'); - innerDiv.textContent = '{{' + variable + '}} = ' + (window.globals[variable] === undefined ? '' : window.globals[variable]); + apiRequestTabContent.append(this.apiRequestTemplate(model, index)); - this.popperInstance = createPopper(element, this.popperContent, { - placement: 'bottom', // Popper below the element - modifiers: [ - { - name: 'offset', - options: { - offset: [0, 8], // Adjust the position if needed - }, - }, - ], + let editableInput = new EditableInput({ + name: 'path', + value: model.path + }); + + let queryParamTable = new PropertyTable({ + propertyName: 'queryParams', + properties: model.queryParams + }); + + let headerTable = new PropertyTable({ + propertyName: 'headers', + properties: model.headers + }); + + queryParamTable.init(apiRequestTabContent.find(`#api_request_${model.id}`).find('.query_params')[0], (properties) => { + model.queryParams = properties; + console.log(model.queryParams); + this.buildPath(model); + editableInput.setValue(model.path); + $('span.variable').off('mouseover'); + $('span.variable').off('mouseout'); + document.querySelectorAll('span.variable').forEach(element => { + let text = element.innerText; + element.addEventListener('mouseover', () => me.showPopper(element, text)); + element.addEventListener('mouseout', () => me.hidePopper(element)); + }); + }); + + headerTable.init(apiRequestTabContent.find(`#api_request_${model.id}`).find('.request_headers')[0], (properties) => { + model.headers = properties; + $('span.variable').off('mouseover'); + $('span.variable').off('mouseout'); + document.querySelectorAll('span.variable').forEach(element => { + let text = element.innerText; + element.addEventListener('mouseover', () => me.showPopper(element, text)); + element.addEventListener('mouseout', () => me.hidePopper(element)); + }); + }); + + apiRequestTabContent.find(`#api_request_${model.id}`).find('.api_request_info').find('.path').each(function() { + editableInput.init(this, (value) => { + model.path = value; + model.queryParams = me.parseParam(model.path); + queryParamTable.setProperties(model.queryParams); + $('span.variable').off('mouseover'); + $('span.variable').off('mouseout'); + document.querySelectorAll('span.variable').forEach(element => { + let text = element.innerText; + element.addEventListener('mouseover', () => me.showPopper(element, text)); + element.addEventListener('mouseout', () => me.hidePopper(element)); }); - } + }); - hidePopper(element) { - if (this.popperInstance) { - this.popperInstance.destroy(); - this.popperInstance = null; + $(this).append(``); + + $('span.variable').off('mouseover'); + $('span.variable').off('mouseout'); + }); + + document.querySelectorAll('.variable').forEach(element => { + + let text = element.innerText; + element.addEventListener('mouseover', () => me.showPopper(element, text)); + element.addEventListener('mouseout', () => me.hidePopper(element)); + }); + + this.openTab(model.id); + let target = $('#api_request_' + model.id); + + let requestBodyEditor = monaco.editor.create(target.find('.request_body')[0], { + value: model.requestBody, + language: 'json', + automaticLayout: true + }); + + requestBodyEditor.onMouseMove((e) => { + let position = e.target.position; + if (position) { + let model = requestBodyEditor.getModel(); + let word = model.getWordAtPosition(position); + let lineContent = model.getLineContent(position.lineNumber); + if (word) { + if (lineContent.indexOf('{{' + word.word + '}}') > -1) { + me.showPopper(e.target.element, word.word); + } + } else { + me.hidePopper(e.target.element); } - this.popperContent.style.display = 'none'; + } + }); + + me.requestEditors.push(requestBodyEditor); + target.find('.request_body')[0].addEventListener('shown.bs.tab', () => { + me.requestEditors[index].layout(); + }); + + me.requestEditors[index].onDidChangeModelContent((e) => { + model.requestBody = requestBodyEditor.getValue(); + }); + + target.find('.request_body_type').on('change', function() { + let model = requestBodyEditor.getModel(); + monaco.editor.setModelLanguage(model, $(this).val()); + }); + + let responseBodyEditor = monaco.editor.create(target.find('.response_body')[0], { + value: '', + language: 'json', + automaticLayout: true, + quickSuggestions: false + }); + + me.responseEditors.push(responseBodyEditor); + + let consoleEditor = monaco.editor.create(target.find('.console')[0], { + value: '', + automaticLayout: true, + quickSuggestions: false + }); + + me.consoleEditors.push(consoleEditor); + + let preRequestEditor = monaco.editor.create(target.find('.pre-request')[0], { + value: model.preRequestScript, + language: 'javascript', + automaticLayout: true, + quickSuggestions: false + }); + + const snippet1 = new VariableSnippet(); + snippet1.init(target.find('.pre-request-variable')[0], preRequestEditor); + + me.preRequestEditors.push(preRequestEditor); + target.find('.pre-request')[0].addEventListener('shown.bs.tab', () => { + me.preRequestEditors[index].layout(); + }); + + me.preRequestEditors[index].onDidChangeModelContent((e) => { + model.preRequestScript = preRequestEditor.getValue(); + }); + + let postRequestEditor = monaco.editor.create(target.find('.post-request')[0], { + value: model.postRequestScript, + language: 'javascript', + automaticLayout: true, + quickSuggestions: false + }); + + const snippet2 = new VariableSnippet(); + snippet2.init(target.find('.post-request-variable')[0], postRequestEditor); + + me.postRequestEditors.push(postRequestEditor); + target.find('.post-request')[0].addEventListener('shown.bs.tab', () => { + me.postRequestEditors[index].layout(); + }); + + me.postRequestEditors[index].onDidChangeModelContent((e) => { + model.postRequestScript = postRequestEditor.getValue(); + }); + + // apiRequestTabContent.find('.resizer').each(function(index, ele) { + // resizable(ele, () => {this.resizeEditor();}); + // }); + } + + renderResponse(tabIndex) { + let model = this.apiRequests[tabIndex]; + let target = $('#api_request_' + model.id); + target.find('.response_headers').empty(); + target.find('.response_status').empty(); + + try { + const parsedJson = JSON.parse(model.responseModel.body); + const formattedJson = JSON.stringify(parsedJson, null, 2); + this.responseEditors[tabIndex].setValue(formattedJson); + } catch (e) { + this.responseEditors[tabIndex].setValue(model.responseModel.body); + } + if (model.responseModel && model.responseModel.headers) { + const headers = model.responseModel.headers; + Object.entries(headers).forEach(([key, value]) => { + target.find('.response_headers').append(this.responseHeaderTemplate(key, value)); + }); } - renderTabContent(index) { - console.log('renderTabContent'); - var me = this; - let apiRequestTabContent = $(this.apiTabContentTarget); - let model = this.apiRequests[index]; + target.find('.response_status').text('status: ' + model.responseModel.status); + target.find('.response_time').text('time: ' + model.responseModel.time + 'ms'); + target.find('.response_size').text('size: ' + model.responseModel.size + 'bytes'); - apiRequestTabContent.append(this.apiRequestTemplate(model, index)); + } - let editableInput = new EditableInput({ - name: 'path', - value: model.path, - }); - - let queryParamTable = new PropertyTable({ - propertyName: 'queryParams', - properties: model.queryParams - }); - - let headerTable = new PropertyTable({ - propertyName: 'headers', - properties: model.headers - }); - - queryParamTable.init(apiRequestTabContent.find(`#api_request_${model.id}`).find('.query_params')[0], (properties) => { - model.queryParams = properties; - console.log(model.queryParams); - this.buildPath(model); - editableInput.setValue(model.path); - $('span.variable').off('mouseover'); - $('span.variable').off('mouseout'); - document.querySelectorAll('span.variable').forEach(element => { - let text = element.innerText; - element.addEventListener('mouseover', () => me.showPopper(element, text)); - element.addEventListener('mouseout', () => me.hidePopper(element)); - }); - }); - - headerTable.init(apiRequestTabContent.find(`#api_request_${model.id}`).find('.request_headers')[0], (properties) => { - model.headers = properties; - $('span.variable').off('mouseover'); - $('span.variable').off('mouseout'); - document.querySelectorAll('span.variable').forEach(element => { - let text = element.innerText; - element.addEventListener('mouseover', () => me.showPopper(element, text)); - element.addEventListener('mouseout', () => me.hidePopper(element)); - }); - }); - - apiRequestTabContent.find(`#api_request_${model.id}`).find('.api_request_info').find('.path').each(function () { - editableInput.init(this, (value) => { - model.path = value; - model.queryParams = me.parseParam(model.path); - queryParamTable.setProperties(model.queryParams); - $('span.variable').off('mouseover'); - $('span.variable').off('mouseout'); - document.querySelectorAll('span.variable').forEach(element => { - let text = element.innerText; - element.addEventListener('mouseover', () => me.showPopper(element, text)); - element.addEventListener('mouseout', () => me.hidePopper(element)); - }); - }); - - $(this).append(``); - - $('span.variable').off('mouseover'); - $('span.variable').off('mouseout'); - }); - - document.querySelectorAll('.variable').forEach(element => { - - let text = element.innerText; - element.addEventListener('mouseover', () => me.showPopper(element, text)); - element.addEventListener('mouseout', () => me.hidePopper(element)); - }); - - this.openTab(model.id); - let target = $('#api_request_' + model.id); - - let requestBodyEditor = monaco.editor.create(target.find(".request_body")[0], { - value: model.requestBody, - language: 'json', - automaticLayout: true - }); - - requestBodyEditor.onMouseMove((e) => { - let position = e.target.position; - if (position) { - let model = requestBodyEditor.getModel(); - let word = model.getWordAtPosition(position); - let lineContent = model.getLineContent(position.lineNumber); - if (word) { - if (lineContent.indexOf('{{' + word.word + '}}') > -1) { - me.showPopper(e.target.element, word.word); - } - } else { - me.hidePopper(e.target.element); - } - } - }); - - me.requestEditors.push(requestBodyEditor); - target.find(".request_body")[0].addEventListener('shown.bs.tab', () => { - me.requestEditors[index].layout(); - }); - - me.requestEditors[index].onDidChangeModelContent((e) => { - model.requestBody = requestBodyEditor.getValue(); - }); - - target.find('.request_body_type').on('change', function () { - let model = requestBodyEditor.getModel(); - monaco.editor.setModelLanguage(model, $(this).val()); - }); - - let responseBodyEditor = monaco.editor.create(target.find(".response_body")[0], { - value: '', - language: 'json', - automaticLayout: true, - quickSuggestions: false - }); - - me.responseEditors.push(responseBodyEditor); - - let consoleEditor = monaco.editor.create(target.find(".console")[0], { - value: '', - automaticLayout: true, - quickSuggestions: false - }); - - me.consoleEditors.push(consoleEditor); - - let preRequestEditor = monaco.editor.create(target.find(".pre-request")[0], { - value: model.preRequestScript, - language: 'javascript', - automaticLayout: true, - quickSuggestions: false - }); - - const snippet1 = new VariableSnippet(); - snippet1.init(target.find(".pre-request-variable")[0], preRequestEditor); - - me.preRequestEditors.push(preRequestEditor); - target.find(".pre-request")[0].addEventListener('shown.bs.tab', () => { - me.preRequestEditors[index].layout(); - }); - - me.preRequestEditors[index].onDidChangeModelContent((e) => { - model.preRequestScript = preRequestEditor.getValue(); - }); - - let postRequestEditor = monaco.editor.create(target.find(".post-request")[0], { - value: model.postRequestScript, - language: 'javascript', - automaticLayout: true, - quickSuggestions: false - }); - - const snippet2 = new VariableSnippet(); - snippet2.init(target.find(".post-request-variable")[0], postRequestEditor); - - me.postRequestEditors.push(postRequestEditor); - target.find(".post-request")[0].addEventListener('shown.bs.tab', () => { - me.postRequestEditors[index].layout(); - }); - - me.postRequestEditors[index].onDidChangeModelContent((e) => { - model.postRequestScript = postRequestEditor.getValue(); - }); - - } - - renderResponse(tabIndex) { - let model = this.apiRequests[tabIndex]; - let target = $('#api_request_' + model.id); - target.find('.response_headers').empty(); - target.find(".response_status").empty(); - - try { - const parsedJson = JSON.parse(model.responseModel.body); - const formattedJson = JSON.stringify(parsedJson, null, 2); - this.responseEditors[tabIndex].setValue(formattedJson); - } catch (e) { - this.responseEditors[tabIndex].setValue(model.responseModel.body); - } - if (model.responseModel && model.responseModel.headers) { - const headers = model.responseModel.headers; - Object.entries(headers).forEach(([key, value]) => { - target.find('.response_headers').append(this.responseHeaderTemplate(key, value)); - }); - } - - target.find(".response_status").text("status: " + model.responseModel.status); - target.find(".response_time").text("time: " + model.responseModel.time + "ms"); - target.find(".response_size").text("size: " + model.responseModel.size + "bytes"); - - } - - customHelper(event) { - // Create and return the custom helper element - let name = $(event.target).closest('.api_request_sortable').find('.open_api_request').text().trim(); - let apiId = $(event.target).closest('.api_request_sortable').data('id'); - return `
    + customHelper(event) { + // Create and return the custom helper element + let name = $(event.target).closest('.api_request_sortable').find('.open_api_request').text().trim(); + let apiId = $(event.target).closest('.api_request_sortable').data('id'); + return `
    ${name}
    `; + } + + renderCollections() { + let select = $('#modal_collection'); + let sidemenu = $(this.collectionTarget); + select.empty(); + sidemenu.empty(); + + for (let i = 0; i < this.collections.length; i++) { + sidemenu.append(this.renderCollection(this.collections[i], i)); + let option = $('