This is an automated email from the ASF dual-hosted git repository.
lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git
The following commit(s) were added to refs/heads/rocketmq-studio by this push:
new 7a27c63 fix: align frontend API success response handling (#429)
7a27c63 is described below
commit 7a27c6376f017923441f2b5e14dedce85f1612aa
Author: btlqql <[email protected]>
AuthorDate: Tue Jul 14 21:11:55 2026 +0800
fix: align frontend API success response handling (#429)
---
web/package-lock.json | 491 ++++++++++++++++++++++++++++++++++++++++++++-
web/package.json | 5 +-
web/src/api/client.test.ts | 109 ++++++++++
web/src/api/client.ts | 29 ++-
4 files changed, 628 insertions(+), 6 deletions(-)
diff --git a/web/package-lock.json b/web/package-lock.json
index 647e869..ae22fb1 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -25,6 +25,7 @@
"@typescript-eslint/parser": "^8.62.1",
"@vitejs/plugin-react": "^4.3.0",
"autoprefixer": "^10.5.2",
+ "axios-mock-adapter": "^2.1.0",
"eslint": "^10.6.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
@@ -36,7 +37,8 @@
"tailwindcss": "^3.4.19",
"typescript": "^5.6.0",
"typescript-eslint": "^8.62.1",
- "vite": "^6.0.0"
+ "vite": "^6.0.0",
+ "vitest": "^3.2.4"
}
},
"node_modules/@alloc/quick-lru": {
@@ -1725,6 +1727,24 @@
"@babel/types": "^7.28.2"
}
},
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity":
"sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved":
"https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity":
"sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/esrecurse": {
"version": "4.3.1",
"resolved":
"https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
@@ -2041,6 +2061,121 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
}
},
+ "node_modules/@vitest/expect": {
+ "version": "3.2.7",
+ "resolved":
"https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
+ "integrity":
"sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "3.2.7",
+ "resolved":
"https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz",
+ "integrity":
"sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "3.2.7",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.17"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "3.2.7",
+ "resolved":
"https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",
+ "integrity":
"sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "3.2.7",
+ "resolved":
"https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",
+ "integrity":
"sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "3.2.7",
+ "pathe": "^2.0.3",
+ "strip-literal": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "3.2.7",
+ "resolved":
"https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",
+ "integrity":
"sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",
+ "integrity":
"sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^4.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",
+ "integrity":
"sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "3.2.7",
+ "loupe": "^3.1.4",
+ "tinyrainbow": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
"node_modules/acorn": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
@@ -2241,6 +2376,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved":
"https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity":
"sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2296,6 +2441,20 @@
"proxy-from-env": "^2.1.0"
}
},
+ "node_modules/axios-mock-adapter": {
+ "version": "2.1.0",
+ "resolved":
"https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz",
+ "integrity":
"sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "is-buffer": "^2.0.5"
+ },
+ "peerDependencies": {
+ "axios": ">= 0.17.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved":
"https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -2392,6 +2551,16 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity":
"sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved":
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -2436,6 +2605,33 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity":
"sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved":
"https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity":
"sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -2634,6 +2830,16 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity":
"sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2723,6 +2929,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved":
"https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity":
"sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved":
"https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
@@ -3042,6 +3255,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved":
"https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity":
"sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3059,6 +3282,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved":
"https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity":
"sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved":
"https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3479,6 +3712,30 @@
"node": ">=8"
}
},
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity":
"sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.16.2",
"resolved":
"https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
@@ -3784,6 +4041,13 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity":
"sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3794,6 +4058,16 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved":
"https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity":
"sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved":
"https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -4067,6 +4341,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity":
"sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity":
"sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved":
"https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -5246,6 +5537,13 @@
"node": ">=8"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity":
"sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved":
"https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -5286,6 +5584,20 @@
"node": ">=0.10.0"
}
},
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity":
"sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity":
"sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/string-argv": {
"version": "0.3.2",
"resolved":
"https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -5335,6 +5647,26 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/strip-literal": {
+ "version": "3.1.0",
+ "resolved":
"https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz",
+ "integrity":
"sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/strip-literal/node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity":
"sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/stylis": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
@@ -5447,6 +5779,13 @@
"node": ">=12.22"
}
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity":
"sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinyexec": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
@@ -5474,6 +5813,36 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity":
"sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "2.0.0",
+ "resolved":
"https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz",
+ "integrity":
"sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz",
+ "integrity":
"sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved":
"https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5687,6 +6056,109 @@
}
}
},
+ "node_modules/vite-node": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
+ "integrity":
"sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.4.1",
+ "es-module-lexer": "^1.7.0",
+ "pathe": "^2.0.3",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz",
+ "integrity":
"sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/chai": "^5.2.2",
+ "@vitest/expect": "3.2.7",
+ "@vitest/mocker": "3.2.7",
+ "@vitest/pretty-format": "^3.2.7",
+ "@vitest/runner": "3.2.7",
+ "@vitest/snapshot": "3.2.7",
+ "@vitest/spy": "3.2.7",
+ "@vitest/utils": "3.2.7",
+ "chai": "^5.2.0",
+ "debug": "^4.4.1",
+ "expect-type": "^1.2.1",
+ "magic-string": "^0.30.17",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.2",
+ "std-env": "^3.9.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.2",
+ "tinyglobby": "^0.2.14",
+ "tinypool": "^1.1.1",
+ "tinyrainbow": "^2.0.0",
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
+ "vite-node": "3.2.4",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/debug": "^4.1.12",
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "@vitest/browser": "3.2.7",
+ "@vitest/ui": "3.2.7",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/debug": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity":
"sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -5703,6 +6175,23 @@
"node": ">= 8"
}
},
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved":
"https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity":
"sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
diff --git a/web/package.json b/web/package.json
index 3c79ec2..1e7248b 100644
--- a/web/package.json
+++ b/web/package.json
@@ -6,6 +6,7 @@
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
+ "test": "vitest run",
"preview": "vite preview",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
@@ -38,6 +39,7 @@
"@typescript-eslint/eslint-plugin": "^8.62.1",
"@typescript-eslint/parser": "^8.62.1",
"@vitejs/plugin-react": "^4.3.0",
+ "axios-mock-adapter": "^2.1.0",
"autoprefixer": "^10.5.2",
"eslint": "^10.6.0",
"eslint-config-prettier": "^10.1.8",
@@ -50,6 +52,7 @@
"tailwindcss": "^3.4.19",
"typescript": "^5.6.0",
"typescript-eslint": "^8.62.1",
- "vite": "^6.0.0"
+ "vite": "^6.0.0",
+ "vitest": "^3.2.4"
}
}
diff --git a/web/src/api/client.test.ts b/web/src/api/client.test.ts
new file mode 100644
index 0000000..77a504f
--- /dev/null
+++ b/web/src/api/client.test.ts
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import MockAdapter from 'axios-mock-adapter';
+import { message } from 'antd';
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import client from './client';
+
+vi.mock('antd', () => ({
+ message: {
+ error: vi.fn(),
+ },
+}));
+
+const mock = new MockAdapter(client);
+const storage = new Map<string, string>();
+
+vi.stubGlobal('localStorage', {
+ getItem: (key: string) => storage.get(key) ?? null,
+ setItem: (key: string, value: string) => storage.set(key, value),
+ removeItem: (key: string) => storage.delete(key),
+ clear: () => storage.clear(),
+ key: (index: number) => [...storage.keys()][index] ?? null,
+ get length() {
+ return storage.size;
+ },
+});
+
+describe('API client response contract', () => {
+ beforeEach(() => {
+ mock.reset();
+ localStorage.clear();
+ vi.mocked(message.error).mockClear();
+ });
+
+ afterEach(() => {
+ mock.reset();
+ });
+
+ it('accepts the backend Result.ok response code', async () => {
+ const payload = { code: 200, message: 'success', data: { name: 'cluster-a'
} };
+ mock.onGet('/clusters').reply(200, payload);
+
+ const response = await client.get('/clusters');
+
+ expect(response.data).toEqual(payload);
+ expect(message.error).not.toHaveBeenCalled();
+ });
+
+ it('keeps compatibility with legacy zero success codes', async () => {
+ const payload = { code: 0, message: 'success', data: ['topic-a'] };
+ mock.onGet('/topics').reply(200, payload);
+
+ await expect(client.get('/topics')).resolves.toMatchObject({ data: payload
});
+ expect(message.error).not.toHaveBeenCalled();
+ });
+
+ it('passes through responses without a business envelope', async () => {
+ const payload = [{ id: 'broker-a' }];
+ mock.onGet('/brokers').reply(200, payload);
+
+ await expect(client.get('/brokers')).resolves.toMatchObject({ data:
payload });
+ expect(message.error).not.toHaveBeenCalled();
+ });
+
+ it('rejects non-success business codes with the backend message', async ()
=> {
+ mock.onPost('/topics/create').reply(200, {
+ code: 400,
+ message: 'Topic already exists',
+ data: null,
+ });
+
+ await expect(client.post('/topics/create', { name: 'orders'
})).rejects.toThrow(
+ 'Topic already exists',
+ );
+ expect(message.error).toHaveBeenCalledWith('Topic already exists');
+ });
+
+ it('uses a stable fallback for malformed error envelopes', async () => {
+ mock.onGet('/clusters').reply(200, { code: '500', data: null });
+
+ await expect(client.get('/clusters')).rejects.toThrow('请求失败');
+ expect(message.error).toHaveBeenCalledWith('请求失败');
+ });
+
+ it('attaches the stored bearer token to outgoing requests', async () => {
+ localStorage.setItem('token', 'test-token');
+ mock.onGet('/clusters').reply((config) => {
+ expect(config.headers?.Authorization).toBe('Bearer test-token');
+ return [200, { code: 200, message: 'success', data: [] }];
+ });
+
+ await client.get('/clusters');
+ });
+});
diff --git a/web/src/api/client.ts b/web/src/api/client.ts
index bb341ad..a87d762 100644
--- a/web/src/api/client.ts
+++ b/web/src/api/client.ts
@@ -18,6 +18,27 @@
import axios from 'axios';
import { message } from 'antd';
+const SUCCESS_BUSINESS_CODES = new Set([0, 200]);
+
+interface BusinessResponse {
+ code?: unknown;
+ message?: unknown;
+}
+
+function isBusinessResponse(data: unknown): data is BusinessResponse {
+ return typeof data === 'object' && data !== null;
+}
+
+function getBusinessError(data: unknown): string | null {
+ if (!isBusinessResponse(data) || data.code === undefined) {
+ return null;
+ }
+ if (typeof data.code === 'number' && SUCCESS_BUSINESS_CODES.has(data.code)) {
+ return null;
+ }
+ return typeof data.message === 'string' && data.message.trim() ?
data.message : '请求失败';
+}
+
const client = axios.create({
baseURL: '/api',
timeout: 30000,
@@ -38,10 +59,10 @@ client.interceptors.request.use(
// Response interceptor: check business code and handle 401
client.interceptors.response.use(
(response) => {
- const data = response.data;
- if (data.code !== undefined && data.code !== 0) {
- message.error(data.message || '请求失败');
- return Promise.reject(new Error(data.message || '请求失败'));
+ const errorMessage = getBusinessError(response.data);
+ if (errorMessage) {
+ message.error(errorMessage);
+ return Promise.reject(new Error(errorMessage));
}
return response;
},