Compare commits

...

181 Commits

Author SHA1 Message Date
Dot.L b1b9f49b06 Merge pull request #329 from APIParkLab/feature/liujian-1.8
Fix the issue of failed compilation of ARM images
2025-06-26 20:05:03 +08:00
Liujian 8f2857cf55 Fix the issue of failed compilation of ARM images 2025-06-26 20:03:48 +08:00
Dot.L f149fede71 Merge pull request #328 from APIParkLab/feature/liujian-1.8
Fix the issue of failed compilation of ARM images
2025-06-26 18:27:45 +08:00
Liujian 972f072346 Fix the issue of failed compilation of ARM images 2025-06-26 18:24:06 +08:00
Dot.L 3cab3c1828 Merge pull request #326 from APIParkLab/feature/liujian-1.8
Fix a series of bug
2025-06-26 17:07:59 +08:00
Liujian 88bf7d0244 1. Fix the issue of ineffective interception of routing settings
2. The problem of long loading times for optimizing service lists and API portals
2025-06-26 17:06:03 +08:00
Dot.L a7523c7b54 Merge pull request #322 from APIParkLab/feature/liujian-1.8
fix: The problem of slow retrieval of service lists and API portal se…
2025-06-19 17:02:14 +08:00
Liujian 590f328e07 fix: The problem of slow retrieval of service lists and API portal service lists 2025-06-19 17:00:51 +08:00
Dot.L ca2682fb22 Merge pull request #311 from APIParkLab/feature/liujian-1.8
update docker run.sh
2025-05-26 19:33:51 +08:00
Liujian 2bd1d4a423 update docker run.sh 2025-05-26 19:32:10 +08:00
Dot.L b2baa711c2 Merge pull request #309 from APIParkLab/feature/1.8-cx
chore: Add annotations
2025-05-23 18:31:33 +08:00
ningyv 3d51f96dda chore: Add annotations 2025-05-23 18:29:58 +08:00
Dot.L b55675e5a5 Merge pull request #308 from APIParkLab/feature/liujian-1.8
Feature/liujian 1.8
2025-05-23 18:24:32 +08:00
Liujian 9a33992a0b Merge remote-tracking branch 'github-pro/main' into feature/liujian-1.8
# Conflicts:
#	.gitlab-ci.yml
2025-05-23 18:23:52 +08:00
Liujian 07ae37eb5f update gitlab-ci.yml 2025-05-23 18:21:50 +08:00
Liujian c36726f25f update gitlab-ci.yml 2025-05-23 17:45:57 +08:00
Liujian e2e9abeb4c update gitlab-ci.yml 2025-05-23 16:19:28 +08:00
Liujian 0fcc2215f7 Merge remote-tracking branch 'origin/main' into main-github-pro 2025-05-23 15:42:51 +08:00
刘健 ce559c4643 Update .gitlab-ci.yml file 2025-05-23 15:30:54 +08:00
刘健 8d4b13f633 Update .gitlab-ci.yml file 2025-05-23 15:30:13 +08:00
Liujian 19a3378fa3 Merge branch 'main' into main-github-pro 2025-05-23 15:16:29 +08:00
Liujian cef1250199 Merge tag 'v1.8.0-beta' 2025-05-23 15:16:02 +08:00
刘健 3c85658931 Update .gitlab-ci.yml file 2025-05-23 15:05:39 +08:00
刘健 ba7022bc2d Update .gitlab-ci.yml file 2025-05-23 15:02:52 +08:00
刘健 fb24abc111 Update .gitlab-ci.yml file 2025-05-23 15:02:23 +08:00
刘健 0e3fb84e7c Update .gitlab-ci.yml file 2025-05-23 15:01:47 +08:00
刘健 5d6d949ca4 Update .gitlab-ci.yml file 2025-05-23 15:00:44 +08:00
刘健 3578182343 Update .gitlab-ci.yml file 2025-05-23 14:32:11 +08:00
刘健 28bad2d963 Update .gitlab-ci.yml file 2025-05-23 14:31:31 +08:00
刘健 384bd239fa Update .gitlab-ci.yml file 2025-05-23 14:21:58 +08:00
刘健 98710ad296 Update .gitlab-ci.yml file 2025-05-23 14:12:49 +08:00
刘健 4806e12907 Update .gitlab-ci.yml file 2025-05-23 14:12:09 +08:00
刘健 9097760a0f Update .gitlab-ci.yml file 2025-05-23 14:10:08 +08:00
刘健 a5639bff60 Update .gitlab-ci.yml file 2025-05-23 14:01:10 +08:00
刘健 1d66ed84f3 Update .gitlab-ci.yml file 2025-05-23 13:55:45 +08:00
刘健 249ac3ea1c Update .gitlab-ci.yml file 2025-05-23 13:55:22 +08:00
刘健 b02db8020d Update .gitlab-ci.yml file 2025-05-23 13:55:03 +08:00
刘健 4105540686 Update .gitlab-ci.yml file 2025-05-23 13:54:37 +08:00
刘健 36d10c5cfd Update .gitlab-ci.yml file 2025-05-23 13:54:17 +08:00
Dot.L f77bd76a14 Merge pull request #301 from APIParkLab/feature/liujian-1.8
Feature/liujian 1.8
2025-05-06 18:53:11 +08:00
ningyv cef548ce7d Merge pull request #299 from APIParkLab/feature/1.8-cx
Feature/1.8 cx
2025-05-06 18:52:12 +08:00
lichunxian 5efd19ef7c Merge branch 'feature/1.8-cx' into 'main'
feat: feature/1.8-Improve system observability

See merge request apipark/APIPark!377
2025-05-06 17:58:28 +08:00
ningyv a0f4ef4e19 feat: feature/1.8-Improve system observability 2025-05-06 17:57:56 +08:00
lichunxian 28cd4fd91c Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!376
2025-05-06 16:06:16 +08:00
ningyv 304239edd0 feature/1.8-Improve system observability 2025-05-06 16:06:00 +08:00
刘健 82f4089f42 Merge branch 'feature/liujian-1.8' into 'main'
update build.sh

See merge request apipark/APIPark!375
2025-05-06 16:02:17 +08:00
Liujian 00ef4d2cfc update build.sh 2025-05-06 16:01:51 +08:00
刘健 cd33448446 Merge branch 'feature/liujian-1.8' into 'main'
fix bug

See merge request apipark/APIPark!373
2025-05-06 15:31:23 +08:00
Liujian ddd70b0ff5 fix: log detail bug 2025-05-06 15:31:07 +08:00
lichunxian e5b50a7073 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!374
2025-05-06 14:41:00 +08:00
ningyv 83da8df554 feature/1.8-Improve system observability 2025-05-06 14:40:32 +08:00
Liujian 5a1baadf3b fix bug 2025-05-06 14:38:54 +08:00
刘健 10bd352bf4 Merge branch 'feature/liujian-1.8' into 'main'
update data

See merge request apipark/APIPark!372
2025-05-06 14:12:01 +08:00
Liujian cbea45e6e0 update data 2025-05-06 14:11:36 +08:00
刘健 e5c6e4fa82 Merge branch 'feature/liujian-1.8' into 'main'
Feature/liujian 1.8

See merge request apipark/APIPark!371
2025-05-06 12:04:47 +08:00
Liujian f05457fd2c update data 2025-05-06 12:04:11 +08:00
Liujian bc6875fe9f Merge remote-tracking branch 'origin/feature/1.8-cx' into feature/liujian-1.8 2025-05-06 11:18:15 +08:00
lichunxian 1572e03dd1 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!370
2025-05-06 11:09:42 +08:00
ningyv 131a1fae59 feature/1.8-Improve system observability 2025-05-06 11:09:18 +08:00
刘健 61025763ed Merge branch 'feature/liujian-1.8' into 'main'
Feature/liujian 1.8

See merge request apipark/APIPark!369
2025-05-06 10:49:33 +08:00
Liujian ef1c48e395 Modify the monitoring table to return field types 2025-05-06 10:43:09 +08:00
lichunxian 00905e4167 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!368
2025-04-30 19:09:44 +08:00
ningyv b91830b089 feature/1.8-Improve system observability 2025-04-30 19:09:14 +08:00
Liujian 9572c4157e Merge remote-tracking branch 'github-pro/feature/1.8-cx' into feature/liujian-1.8 2025-04-30 18:55:09 +08:00
Liujian fef49eb32c tmp commit 2025-04-30 18:55:01 +08:00
lichunxian a5a895e42d Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!367
2025-04-30 17:18:00 +08:00
ningyv c0dc5bcdb5 feature/1.8-Improve system observability 2025-04-30 17:17:00 +08:00
lichunxian ed1d19532b Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!366
2025-04-30 16:33:49 +08:00
ningyv d50fbcdf4f feature/1.8-Improve system observability 2025-04-30 16:33:22 +08:00
Liujian 9c1b19a1c7 Merge remote-tracking branch 'github-pro/feature/1.8-cx' into feature/liujian-1.8 2025-04-30 15:57:59 +08:00
刘健 bf990517dc Merge branch 'feature/liujian-1.8' into 'main'
update service logs

See merge request apipark/APIPark!365
2025-04-30 15:53:11 +08:00
Liujian 0cf7f952e2 update service logs 2025-04-30 15:52:48 +08:00
lichunxian 83873c8c92 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!364
2025-04-30 15:49:50 +08:00
ningyv 18a3516e04 feature/1.8-Improve system observability 2025-04-30 15:48:52 +08:00
刘健 a61e6ba67f Merge branch 'feature/liujian-1.8' into 'main'
Optimize chart data

See merge request apipark/APIPark!363
2025-04-30 15:06:24 +08:00
Liujian 94d881cc18 Optimize chart data 2025-04-30 15:05:47 +08:00
刘健 e081580786 Merge branch 'feature/liujian-1.8' into 'main'
fix:ai token monitor bug

See merge request apipark/APIPark!362
2025-04-30 14:00:22 +08:00
Liujian 8927211ea2 fix:ai token monitor bug 2025-04-30 13:59:50 +08:00
lichunxian 813905ca40 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!361
2025-04-30 10:25:41 +08:00
ningyv 12a8317dd8 feature/1.8-Improve system observability 2025-04-30 10:25:06 +08:00
lichunxian 66be761d18 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!360
2025-04-30 09:46:26 +08:00
ningyv f1e5b29908 feature/1.8-Improve system observability 2025-04-30 09:46:01 +08:00
lichunxian 6b6fa5bd40 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!359
2025-04-30 09:30:12 +08:00
ningyv 119e03fda8 feature/1.8-Improve system observability 2025-04-30 09:29:46 +08:00
lichunxian 943ef4f9b0 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!358
2025-04-30 09:20:32 +08:00
ningyv e98908a63a feature/1.8-Improve system observability 2025-04-30 09:20:04 +08:00
lichunxian 78d9a1c23c Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!357
2025-04-30 09:09:26 +08:00
ningyv 32bc7354ba feature/1.8-Improve system observability 2025-04-30 09:08:52 +08:00
刘健 342d022c43 Merge branch 'feature/liujian-1.8' into 'main'
Feature/liujian 1.8

See merge request apipark/APIPark!356
2025-04-30 00:24:33 +08:00
Liujian 1d36f4b821 fix monitor bug 2025-04-30 00:24:02 +08:00
Liujian 4e459168df Merge remote-tracking branch 'origin/feature/1.8-cx' into feature/liujian-1.8 2025-04-29 22:56:28 +08:00
lichunxian a8c14ee839 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!355
2025-04-29 19:40:30 +08:00
ningyv 5384807b85 feature/1.8-Improve system observability 2025-04-29 19:40:12 +08:00
lichunxian 23c40efe0d Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!354
2025-04-29 19:33:43 +08:00
ningyv 71e1ff778e feature/1.8-Improve system observability 2025-04-29 19:33:24 +08:00
lichunxian a76941ea17 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!353
2025-04-29 19:28:59 +08:00
ningyv 5e05b2117e feature/1.8-Improve system observability 2025-04-29 19:28:35 +08:00
刘健 0c392d2092 Merge branch 'feature/liujian-1.8' into 'main'
Log information returns the newly added Body

See merge request apipark/APIPark!352
2025-04-29 19:28:04 +08:00
Liujian e5f0423a90 Log information returns the newly added Body 2025-04-29 19:23:35 +08:00
刘健 46caf49f18 Merge branch 'feature/liujian-1.8' into 'main'
fix bug

See merge request apipark/APIPark!351
2025-04-29 19:16:54 +08:00
Liujian 7dc8d65235 fix bug 2025-04-29 19:16:21 +08:00
刘健 74c87ec308 Merge branch 'feature/liujian-1.8' into 'main'
finish service log module

See merge request apipark/APIPark!350
2025-04-29 19:08:43 +08:00
Liujian 604a8312ef finish service log module 2025-04-29 19:08:02 +08:00
lichunxian 8b318caa0b Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!349
2025-04-29 19:06:52 +08:00
ningyv 159bcc7aa6 feature/1.8-Improve system observability 2025-04-29 19:06:28 +08:00
lichunxian 6bad1c3c7c Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!348
2025-04-29 18:24:17 +08:00
ningyv c45cf6aaff feature/1.8-Improve system observability 2025-04-29 18:23:51 +08:00
刘健 1333d4ed02 Merge branch 'feature/liujian-1.8' into 'main'
finish log list

See merge request apipark/APIPark!347
2025-04-29 17:49:59 +08:00
Liujian a3bebde83c finish log list 2025-04-29 17:44:53 +08:00
lichunxian d3e91b04a2 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!346
2025-04-29 17:33:16 +08:00
ningyv 48f46ec4c3 feature/1.8-Improve system observability 2025-04-29 17:32:49 +08:00
lichunxian f4400c0130 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!345
2025-04-29 17:27:37 +08:00
ningyv d596f1af5a feature/1.8-Improve system observability 2025-04-29 17:27:06 +08:00
lichunxian cc5d677d67 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!344
2025-04-29 11:52:46 +08:00
ningyv 2f37be430b feature/1.8-Improve system observability 2025-04-29 11:52:24 +08:00
刘健 e4c3cbc99b Merge branch 'feature/liujian-1.8' into 'main'
update service overview

See merge request apipark/APIPark!343
2025-04-29 10:20:17 +08:00
Liujian 771c86229d update service overview 2025-04-29 10:18:02 +08:00
刘健 5c1db00d7e Merge branch 'feature/liujian-1.8' into 'main'
Feature/liujian 1.8

See merge request apipark/APIPark!342
2025-04-29 00:35:41 +08:00
Liujian cff536710e finish: monitor overview 2025-04-29 00:34:58 +08:00
lichunxian cd91f4bdb9 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!341
2025-04-27 17:44:31 +08:00
ningyv acfe5b988b feature/1.8-Improve system observability 2025-04-27 17:43:51 +08:00
lichunxian 79860bc665 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!340
2025-04-27 14:55:12 +08:00
ningyv 7d255f4be7 feature/1.8-Improve system observability 2025-04-27 14:54:51 +08:00
lichunxian 4623ba6fba Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!339
2025-04-27 14:50:59 +08:00
ningyv be658a1b31 feature/1.8-Improve system observability 2025-04-27 14:50:33 +08:00
lichunxian 1f4acdc99e Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!338
2025-04-27 14:31:19 +08:00
ningyv 2fe31055c8 feature/1.8-Improve system observability 2025-04-27 14:30:24 +08:00
lichunxian 0307282dbd Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!337
2025-04-27 14:15:29 +08:00
ningyv 85130ad882 feature/1.8-Improve system observability 2025-04-27 14:14:52 +08:00
lichunxian 7dbc2a1a78 Merge branch 'feature/1.8-cx' into 'main'
feature/1.8-Improve system observability

See merge request apipark/APIPark!336
2025-04-27 10:53:53 +08:00
ningyv 2e18e1f8a4 feature/1.8-Improve system observability 2025-04-27 10:53:27 +08:00
lichunxian 12d42c4247 Merge branch 'feature/1.8-cx' into 'main'
Feature/1.8 cx

See merge request apipark/APIPark!335
2025-04-27 10:45:08 +08:00
ningyv 3215912d0c feature/1.8-Improve system observability 2025-04-27 10:43:06 +08:00
ningyv 65ad7657ef feature/1.8-Improve system observability 2025-04-25 18:42:59 +08:00
Dot.L a22759136e Merge pull request #298 from APIParkLab/feature/1.7-liujian
update docker build script
2025-04-24 16:03:14 +08:00
Liujian b8ebbac2b8 update docker build script 2025-04-24 16:02:32 +08:00
Dot.L 9c4590db07 Merge pull request #297 from APIParkLab/feature/1.7-liujian
Fix: Apikey getting md5 when calling MCP Server at service level
2025-04-22 18:08:51 +08:00
Liujian 7ba8a57793 Fix: Apikey getting md5 when calling MCP Server at service level 2025-04-22 18:08:24 +08:00
lichunxian 4eb3368875 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP-analytics-table-optimize

See merge request apipark/APIPark!334
2025-04-16 15:47:13 +08:00
刘健 4478e6823a Merge branch 'feature/1.7-liujian' into 'main'
Fix: Dragging to modify the order of service categories will fail when there...

See merge request apipark/APIPark!333
2025-04-16 15:21:28 +08:00
lichunxian ab5bffea87 Merge branch 'feature/1.7-cxx' into 'main'
Feature/1.7 cxx

See merge request apipark/APIPark!332
2025-04-16 14:36:37 +08:00
刘健 8a48828a76 Merge branch 'feature/1.7-liujian' into 'main'
Fix: Issue of API duplicate publishing when publishing services

See merge request apipark/APIPark!331
2025-04-16 14:34:39 +08:00
刘健 c2b70e23e4 Merge branch 'feature/1.7-liujian' into 'main'
update

See merge request apipark/APIPark!330
2025-04-16 14:12:52 +08:00
lichunxian eb46a4365c Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!329
2025-04-16 14:07:50 +08:00
lichunxian 058a8f7974 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!328
2025-04-16 13:48:41 +08:00
lichunxian b5585f548a Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!327
2025-04-16 13:45:08 +08:00
lichunxian e4a3e1a1a2 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!326
2025-04-16 11:54:13 +08:00
刘健 674a15ef32 Merge branch 'feature/1.7-liujian' into 'main'
update api portal interface

See merge request apipark/APIPark!325
2025-04-16 11:11:14 +08:00
lichunxian d82d665280 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!324
2025-04-16 10:19:11 +08:00
lichunxian 752db42b3b Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!323
2025-04-16 09:53:24 +08:00
lichunxian 10aaf85a26 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!322
2025-04-16 09:47:57 +08:00
lichunxian 5c97ef9416 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!321
2025-04-16 09:18:16 +08:00
刘健 5fc84299f1 Merge branch 'feature/1.7-liujian' into 'main'
update path

See merge request apipark/APIPark!320
2025-04-16 00:01:29 +08:00
刘健 2eeeebf7c2 Merge branch 'feature/1.7-liujian' into 'main'
MCP Server supports multiple languages.

See merge request apipark/APIPark!319
2025-04-15 22:42:51 +08:00
lichunxian 155ad537a9 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!318
2025-04-15 18:58:14 +08:00
lichunxian 4a1430c62a Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP-Breadcrumb

See merge request apipark/APIPark!317
2025-04-14 17:45:58 +08:00
lichunxian 8cc0d038bd Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP-Breadcrumb

See merge request apipark/APIPark!316
2025-04-14 17:33:06 +08:00
lichunxian 165759398e Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!315
2025-04-14 14:09:47 +08:00
lichunxian 1091d4e086 Merge branch 'feature/1.7-cxx' into 'main'
Feature/1.7 cxx

See merge request apipark/APIPark!314
2025-04-14 13:37:01 +08:00
刘健 0523f13dfb Merge branch 'feature/1.7-liujian' into 'main'
Feature/1.7 liujian

See merge request apipark/APIPark!313
2025-04-14 11:57:40 +08:00
lichunxian 256c04f5bb Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!312
2025-04-14 11:33:52 +08:00
lichunxian a9dcc78db6 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!311
2025-04-14 11:12:24 +08:00
刘健 1f6c173e18 Merge branch 'feature/1.7-liujian' into 'main'
Fix: API forwarding header setting failure issue

See merge request apipark/APIPark!310
2025-04-14 10:20:17 +08:00
lichunxian b593e8b57b Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!309
2025-04-14 09:54:30 +08:00
lichunxian 1ec00de03c Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!308
2025-04-11 18:41:00 +08:00
lichunxian bad7fbadda Merge branch 'feature/1.7-cxx' into 'main'
Feature/1.7 cxx

See merge request apipark/APIPark!307
2025-04-11 18:31:30 +08:00
lichunxian 7a506fc15e Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!306
2025-04-11 17:28:46 +08:00
lichunxian dec2c3a23e Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!305
2025-04-11 16:49:06 +08:00
刘健 2093541c37 Merge branch 'feature/1.7-liujian' into 'main'
Add the openapiaddress field to the API portal to obtain detailed service information

See merge request apipark/APIPark!304
2025-04-11 14:43:07 +08:00
lichunxian 40c7ba4305 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!303
2025-04-11 14:18:13 +08:00
刘健 a95bca31e2 Merge branch 'feature/1.7-liujian' into 'main'
service detail add invoke_count

See merge request apipark/APIPark!302
2025-04-11 11:58:33 +08:00
刘健 a541e45a53 Merge branch 'feature/1.7-liujian' into 'main'
Feature/1.7 liujian

See merge request apipark/APIPark!301
2025-04-11 11:04:56 +08:00
lichunxian b20c66b311 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!300
2025-04-11 10:06:06 +08:00
lichunxian 729e1f105c Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!299
2025-04-10 17:48:12 +08:00
lichunxian 6aa96a2ae9 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!298
2025-04-10 16:29:48 +08:00
lichunxian 5093c98656 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!297
2025-04-10 16:22:52 +08:00
lichunxian f4b70d4e71 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!296
2025-04-10 16:11:01 +08:00
刘健 eeb36f43a4 Update .gitlab-ci.yml file 2025-04-10 15:52:44 +08:00
lichunxian 5a59a6d378 Merge branch 'feature/1.7-cxx' into 'main'
feature/1.7-MCP

See merge request apipark/APIPark!295
2025-04-10 15:27:13 +08:00
lichunxian c0045d17e2 Merge branch 'feature/1.7-cxx' into 'main'
Feature/1.7 cxx

See merge request apipark/APIPark!294
2025-04-10 10:47:54 +08:00
刘健 42963d3ee5 Merge branch 'feature/1.7-liujian' into 'main'
Revert "Auxiliary commit to revert individual files from da05525cbbf2510a2cbc37d7eed6bfb8248e448b"

See merge request apipark/APIPark!291
2025-04-10 10:00:30 +08:00
155 changed files with 72163 additions and 62401 deletions
+5
View File
@@ -75,6 +75,11 @@ jobs:
with:
name: frontend-package
path: frontend/dist
# 设置 QEMU 以支持多架构构建
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login Docker #登录docker
uses: docker/login-action@v1
with:
+2 -1
View File
@@ -7,4 +7,5 @@
/.vscode/
.air.toml
/tmp/
/work
/work
/cmd/
+88
View File
@@ -0,0 +1,88 @@
variables:
PATH: /opt/go-1.23/go/bin/:/opt/node-1.22/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
GOROOT: /opt/go-1.23/go
GOPROXY: https://goproxy.cn
VERSION: $CI_COMMIT_SHORT_SHA
APP: apipark
APP_PRE: ${APP}_${VERSION}
BUILD_DIR: ${APP}-build
DEPLOY_DESC: "DEV 环境"
VIEW_ADDR: http://172.18.166.219:8288
SAVE_DIR: /opt/${APP}
NODE_OPTIONS: --max_old_space_size=8192
stages:
# - notice
- build
- deploy
- webhook
#
#feishu-informer: # 飞书回调
# stage: notice
# variables:
# DIFF_URL: "$CI_MERGE_REQUEST_PROJECT_URL/-/merge_requests/$CI_MERGE_REQUEST_IID/diffs"
# rules:
# - if: $CI_PIPELINE_SOURCE=="merge_request_event" && $CI_COMMIT_BRANCH =~ "main-github-pro"
# script:
# - echo "merge request"
# - |
# curl -X POST -H "Content-Type: application/json" \
# -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"项目:${CI_PROJECT_NAME}\\n提交人:${GITLAB_USER_NAME}\\n提交信息:${CI_MERGE_REQUEST_TITLE}\\n合并分支信息:${CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} -> ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}\\n差异性地址:${DIFF_URL}\\n请及时review代码\"}}" \
# ${FEISHU_WEBHOOK}
builder:
stage: build
rules:
- if: $CI_COMMIT_BRANCH == "main-github-pro" || $CI_COMMIT_BRANCH == "main"
script:
- set -e
- |
if [ ! -d "../artifacts" ]; then
mkdir -p ../artifacts
fi
if [ -d "../artifacts/dist" ]; then
cp -r ../artifacts/dist frontend/dist
fi
- |
if [ -n "$(git diff --name-status HEAD~1 HEAD -- frontend)" ]; then
./scripts/build.sh $BUILD_DIR ${VERSION} all ""
else
./scripts/build.sh $BUILD_DIR ${VERSION}
fi
if [ -d "frontend/dist" ]; then
echo "copy frontend/dist to artifacts/dist"
rm -fr ../artifacts/dist
cp -r frontend/dist ../artifacts/dist
fi
cp $BUILD_DIR/${APP_PRE}_linux_amd64.tar.gz ${SAVE_DIR}
deployer:
stage: deploy
rules:
- if: $CI_COMMIT_BRANCH == "main-github-pro" || $CI_COMMIT_BRANCH == "main"
variables:
APIPARK_GUEST_MODE: allow
APIPARK_GUEST_ID: dklejrfbhjqwdh
script:
- cd ${SAVE_DIR};mkdir -p ${APP_PRE};tar -zxvf ${APP_PRE}_linux_amd64.tar.gz -C ${APP_PRE};cd ${APP_PRE};./install.sh ${SAVE_DIR};./run.sh restart;cd ${SAVE_DIR} && ./clean.sh ${APP_PRE}
when: on_success
success:
stage: webhook
rules:
- if: $CI_COMMIT_BRANCH == "main-github-pro" || $CI_COMMIT_BRANCH == "main"
script:
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署完成.\\n访问地址:${VIEW_ADDR}\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
${FEISHU_WEBHOOK}
when: on_success
failure:
stage: webhook
rules:
- if: $CI_COMMIT_BRANCH == "main-github-pro" || $CI_COMMIT_BRANCH == "main"
script:
- |
curl -X POST -H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"最近一次提交:${CI_COMMIT_TITLE}\\n提交人:${GITLAB_USER_NAME}\\n项目:${CI_PROJECT_NAME}\\n环境:${DEPLOY_DESC}\\n更新部署失败,请及时到gitlab上查看\\n工作流地址:${CI_PIPELINE_URL}\"}}" \
${FEISHU_WEBHOOK}
when: on_failure
+62748 -60558
View File
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
package common
import (
"fmt"
"strconv"
)
func FormatCountInt64(count int64) string {
switch {
case count < 1000:
return strconv.FormatInt(count, 10)
case count < 1000000:
return fmt.Sprintf("%.1fK", float64(count)/1000)
case count < 1000000000:
return fmt.Sprintf("%.1fM", float64(count)/1000000)
case count < 1000000000000:
return fmt.Sprintf("%.1fB", float64(count)/1000000000)
default:
return fmt.Sprintf("%.1fT", float64(count)/1000000000000)
}
}
func FormatCountFloat64(count float64) string {
switch {
case count < 1000:
return fmt.Sprintf("%.1f", count)
case count < 1000000:
return fmt.Sprintf("%.1fK", count/1000)
case count < 1000000000:
return fmt.Sprintf("%.1fM", count/1000000)
case count < 1000000000000:
return fmt.Sprintf("%.1fB", count/1000000000)
default:
return fmt.Sprintf("%.1fT", count/1000000000000)
}
}
func FormatTime(t int64) string {
if t < 1000 {
return strconv.FormatInt(t, 10) + "ms"
}
if t < 1000000 {
return fmt.Sprintf("%.1fs", float64(t)/1000)
}
if t < 1000000000 {
return fmt.Sprintf("%.1fmin", float64(t)/1000000)
}
if t < 1000000000000 {
return fmt.Sprintf("%.1fhour", float64(t)/1000000000)
}
return fmt.Sprintf("%.1D", float64(t)/1000000000000)
}
func FormatByte(b int64) string {
const (
KB = 1000
MB = KB * 1000
GB = MB * 1000
TB = GB * 1000
PB = TB * 1000
)
switch {
case b < KB:
return fmt.Sprintf("%dB", b)
case b < MB:
return fmt.Sprintf("%.1fKB", float64(b)/KB)
case b < GB:
return fmt.Sprintf("%.1fMB", float64(b)/MB)
case b < TB:
return fmt.Sprintf("%.1fGB", float64(b)/GB)
case b < PB:
return fmt.Sprintf("%.1fTB", float64(b)/TB)
default:
return fmt.Sprintf("%.1fPB", float64(b)/PB)
}
}
+2
View File
@@ -29,6 +29,8 @@ func FmtIntFromInterface(val interface{}) int64 {
return int64(ret)
case int:
return int64(ret)
case float64:
return int64(ret)
default:
return 0
}
+61
View File
@@ -2,6 +2,7 @@ package monitor
import (
"fmt"
"strconv"
"time"
"github.com/APIParkLab/APIPark/module/monitor"
@@ -17,6 +18,66 @@ type imlMonitorStatisticController struct {
module monitor.IMonitorStatisticModule `autowired:""`
}
func (i *imlMonitorStatisticController) ChartRestOverview(ctx *gin.Context, start string, end string) (*monitor_dto.ChartRestOverview, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, err
}
return i.module.RestChartOverview(ctx, "", s, e)
}
func (i *imlMonitorStatisticController) ChartAIOverview(ctx *gin.Context, start string, end string) (*monitor_dto.ChartAIOverview, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, err
}
return i.module.AIChartOverview(ctx, "", s, e)
}
func (i *imlMonitorStatisticController) AITopN(ctx *gin.Context, start string, end string, limit string) ([]*monitor_dto.TopN, []*monitor_dto.TopN, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, nil, err
}
l, err := strconv.Atoi(limit)
if err != nil {
if limit == "" {
l = 10
} else {
return nil, nil, fmt.Errorf("parse limit %s error: %w", limit, err)
}
}
return i.module.Top(ctx, "", s, e, l, "ai")
}
func formatTime(start string, end string) (int64, int64, error) {
s, err := strconv.ParseInt(start, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("parse start time %s error: %w", start, err)
}
e, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("parse end time %s error: %w", end, err)
}
return s, e, nil
}
func (i *imlMonitorStatisticController) RestTopN(ctx *gin.Context, start string, end string, limit string) ([]*monitor_dto.TopN, []*monitor_dto.TopN, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, nil, err
}
l, err := strconv.Atoi(limit)
if err != nil {
if limit == "" {
l = 10
} else {
return nil, nil, fmt.Errorf("parse limit %s error: %w", limit, err)
}
}
return i.module.Top(ctx, "", s, e, l, "rest")
}
func (i *imlMonitorStatisticController) Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) {
switch dataType {
case monitor_dto.DataTypeApi:
+5
View File
@@ -22,6 +22,11 @@ type IMonitorStatisticController interface {
InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error)
StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error)
ChartRestOverview(ctx *gin.Context, start string, end string) (*monitor_dto.ChartRestOverview, error)
ChartAIOverview(ctx *gin.Context, start string, end string) (*monitor_dto.ChartAIOverview, error)
AITopN(ctx *gin.Context, start string, end string, limit string) ([]*monitor_dto.TopN, []*monitor_dto.TopN, error)
RestTopN(ctx *gin.Context, start string, end string, limit string) ([]*monitor_dto.TopN, []*monitor_dto.TopN, error)
}
type IMonitorConfigController interface {
+208 -18
View File
@@ -5,9 +5,13 @@ import (
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/APIParkLab/APIPark/module/monitor"
monitor_dto "github.com/APIParkLab/APIPark/module/monitor/dto"
ai_provider_local "github.com/APIParkLab/APIPark/ai-provider/local"
subscribe_dto "github.com/APIParkLab/APIPark/module/subscribe/dto"
@@ -55,10 +59,6 @@ import (
"github.com/google/uuid"
)
//var (
// ollamaConfig = "{\n \"mirostat\": 0,\n \"mirostat_eta\": 0.1,\n \"mirostat_tau\": 5.0,\n \"num_ctx\": 4096,\n \"repeat_last_n\":64,\n \"repeat_penalty\": 1.1,\n \"temperature\": 0.7,\n \"seed\": 42,\n \"num_predict\": 42,\n \"top_k\": 40,\n \"top_p\": 0.9,\n \"min_p\": 0.5\n}\n"
//)
var (
_ IServiceController = (*imlServiceController)(nil)
@@ -66,20 +66,210 @@ var (
)
type imlServiceController struct {
module service.IServiceModule `autowired:""`
docModule service.IServiceDocModule `autowired:""`
subscribeModule subscribe.ISubscribeModule `autowired:""`
aiAPIModule ai_api.IAPIModule `autowired:""`
routerModule router.IRouterModule `autowired:""`
apiDocModule api_doc.IAPIDocModule `autowired:""`
providerModule ai.IProviderModule `autowired:""`
aiLocalModel ai_local.ILocalModelModule `autowired:""`
appModule service.IAppModule `autowired:""`
upstreamModule upstream.IUpstreamModule `autowired:""`
settingModule system.ISettingModule `autowired:""`
teamModule team.ITeamModule `autowired:""`
catalogueModule catalogue.ICatalogueModule `autowired:""`
transaction store.ITransaction `autowired:""`
module service.IServiceModule `autowired:""`
docModule service.IServiceDocModule `autowired:""`
subscribeModule subscribe.ISubscribeModule `autowired:""`
aiAPIModule ai_api.IAPIModule `autowired:""`
routerModule router.IRouterModule `autowired:""`
apiDocModule api_doc.IAPIDocModule `autowired:""`
providerModule ai.IProviderModule `autowired:""`
aiLocalModel ai_local.ILocalModelModule `autowired:""`
appModule service.IAppModule `autowired:""`
upstreamModule upstream.IUpstreamModule `autowired:""`
settingModule system.ISettingModule `autowired:""`
teamModule team.ITeamModule `autowired:""`
catalogueModule catalogue.ICatalogueModule `autowired:""`
monitorModule monitor.IMonitorStatisticModule `autowired:""`
monitorConfigModule monitor.IMonitorConfigModule `autowired:""`
transaction store.ITransaction `autowired:""`
}
func (i *imlServiceController) RestLogInfo(ctx *gin.Context, serviceId string, logId string) (*service_dto.RestLogInfo, error) {
return i.module.RestLogInfo(ctx, serviceId, logId)
}
func (i *imlServiceController) AILogInfo(ctx *gin.Context, serviceId string, logId string) (*service_dto.AILogInfo, error) {
return i.module.AILogInfo(ctx, serviceId, logId)
}
func (i *imlServiceController) AILogs(ctx *gin.Context, serviceId string, start string, end string, page string, size string) ([]*service_dto.AILogItem, int64, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, 0, err
}
if serviceId == "" {
return nil, 0, fmt.Errorf("service id is empty")
}
if page == "" {
page = "1"
}
if size == "" {
size = "20"
}
p, err := strconv.Atoi(page)
if err != nil {
return nil, 0, err
}
ps, err := strconv.Atoi(size)
if err != nil {
return nil, 0, err
}
return i.module.AILogs(ctx, serviceId, s, e, p, ps)
}
func (i *imlServiceController) RestLogs(ctx *gin.Context, serviceId string, start string, end string, page string, size string) ([]*service_dto.RestLogItem, int64, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, 0, err
}
if serviceId == "" {
return nil, 0, fmt.Errorf("service id is empty")
}
if page == "" {
page = "1"
}
if size == "" {
size = "20"
}
p, err := strconv.Atoi(page)
if err != nil {
return nil, 0, err
}
ps, err := strconv.Atoi(size)
if err != nil {
return nil, 0, err
}
return i.module.RestLogs(ctx, serviceId, s, e, p, ps)
}
func (i *imlServiceController) ServiceOverview(ctx *gin.Context, serviceId string) (*service_dto.Overview, error) {
o, err := i.module.ServiceOverview(ctx, serviceId)
if err != nil {
return nil, err
}
cfg, err := i.monitorConfigModule.GetMonitorConfig(ctx)
if err != nil {
return nil, err
}
if len(cfg.Config) < 1 {
return o, nil
}
statistics, err := i.monitorModule.ProviderStatistics(ctx, &monitor_dto.StatisticInput{
Services: []string{serviceId},
CommonInput: &monitor_dto.CommonInput{
Start: time.Now().Add(-24 * 30 * time.Hour).Unix(),
End: time.Now().Unix(),
},
})
if err != nil {
return nil, err
}
if len(statistics) < 1 {
return o, nil
}
o.InvokeNum = statistics[0].RequestTotal
return o, nil
}
func (i *imlServiceController) AIChartOverview(ctx *gin.Context, serviceId string, start string, end string) (*monitor_dto.ServiceChartAIOverview, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, err
}
if serviceId == "" {
return nil, fmt.Errorf("service is required")
}
so, err := i.module.ServiceOverview(ctx, serviceId)
if err != nil {
return nil, err
}
result := &monitor_dto.ServiceChartAIOverview{
EnableMCP: so.EnableMCP,
SubscriberNum: so.SubscriberNum,
APINum: so.APINum,
ServiceKind: so.ServiceKind,
}
cfg, err := i.monitorConfigModule.GetMonitorConfig(ctx)
if err != nil {
return nil, err
}
if len(cfg.Config) < 1 {
return result, nil
}
o, err := i.monitorModule.AIChartOverview(ctx, serviceId, s, e)
if err != nil {
return nil, err
}
result.AvailableMonitor = true
result.ChartAIOverview = o
return result, nil
}
func (i *imlServiceController) RestChartOverview(ctx *gin.Context, serviceId string, start string, end string) (*monitor_dto.ServiceChartRestOverview, error) {
s, e, err := formatTime(start, end)
if err != nil {
return nil, err
}
if serviceId == "" {
return nil, fmt.Errorf("service is required")
}
so, err := i.module.ServiceOverview(ctx, serviceId)
if err != nil {
return nil, err
}
result := &monitor_dto.ServiceChartRestOverview{
EnableMCP: so.EnableMCP,
SubscriberNum: so.SubscriberNum,
APINum: so.APINum,
ServiceKind: so.ServiceKind,
}
cfg, err := i.monitorConfigModule.GetMonitorConfig(ctx)
if err != nil {
return nil, err
}
if len(cfg.Config) < 1 {
return result, nil
}
o, err := i.monitorModule.RestChartOverview(ctx, serviceId, s, e)
if err != nil {
return nil, err
}
result.AvailableMonitor = true
result.ChartRestOverview = o
return result, nil
}
func formatTime(start string, end string) (int64, int64, error) {
s, err := strconv.ParseInt(start, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("parse start time %s error: %w", start, err)
}
e, err := strconv.ParseInt(end, 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("parse end time %s error: %w", end, err)
}
return s, e, nil
}
func (i *imlServiceController) Top10(ctx *gin.Context, serviceId string, start string, end string) ([]*monitor_dto.TopN, []*monitor_dto.TopN, error) {
if serviceId == "" {
return nil, nil, fmt.Errorf("serviceId is required")
}
info, err := i.module.Get(ctx, serviceId)
if err != nil {
return nil, nil, err
}
s, e, err := formatTime(start, end)
if err != nil {
return nil, nil, err
}
return i.monitorModule.Top(ctx, serviceId, s, e, 10, info.ServiceKind)
}
func (i *imlServiceController) QuickCreateAIService(ctx *gin.Context, input *service_dto.QuickCreateAIService) error {
+15
View File
@@ -3,6 +3,8 @@ package service
import (
"reflect"
monitor_dto "github.com/APIParkLab/APIPark/module/monitor/dto"
service_dto "github.com/APIParkLab/APIPark/module/service/dto"
"github.com/gin-gonic/gin"
@@ -32,6 +34,19 @@ type IServiceController interface {
Swagger(ctx *gin.Context)
ExportSwagger(ctx *gin.Context)
Top10(ctx *gin.Context, serviceId string, start string, end string) ([]*monitor_dto.TopN, []*monitor_dto.TopN, error)
AIChartOverview(ctx *gin.Context, serviceId string, start string, end string) (*monitor_dto.ServiceChartAIOverview, error)
RestChartOverview(ctx *gin.Context, serviceId string, start string, end string) (*monitor_dto.ServiceChartRestOverview, error)
ServiceOverview(ctx *gin.Context, serviceId string) (*service_dto.Overview, error)
AILogs(ctx *gin.Context, serviceId string, start string, end string, page string, size string) ([]*service_dto.AILogItem, int64, error)
RestLogs(ctx *gin.Context, serviceId string, start string, end string, page string, size string) ([]*service_dto.RestLogItem, int64, error)
RestLogInfo(ctx *gin.Context, serviceId string, logId string) (*service_dto.RestLogInfo, error)
AILogInfo(ctx *gin.Context, serviceId string, logId string) (*service_dto.AILogInfo, error)
}
type IAppController interface {
+2 -1
View File
@@ -50,7 +50,8 @@
"vite-tsconfig-paths": "^4.3.2",
"react-json-view": "^1.21.3",
"zod": "^3.23.8",
"@modelcontextprotocol/sdk": "^1.9.0"
"@modelcontextprotocol/sdk": "^1.9.0",
"echarts-for-react": "^3.0.2"
},
"devDependencies": {
"@ant-design/cssinjs": "^1.18.2",
@@ -36,7 +36,7 @@ function BasicLayout({ project = 'core' }: { project: string }) {
const { state, accessData, checkPermission, accessInit, dispatch, resetAccess, getGlobalAccessData, menuList } =
useGlobalContext()
const [pathname, setPathname] = useState(currentUrl)
const mainPage = project === 'core' ? '/service/list' : '/serviceHub/list'
const mainPage = project === 'core' ? '/service/list' : '/portal/list'
const [menuItems, setMenuItems] = useState<MenuProps['items']>()
const pluginSlotHub = usePluginSlotHub()
@@ -26,6 +26,7 @@ class InsidePageProps {
scrollInsidePage?: boolean = false
customPadding?: boolean
customBtn?: ReactNode
customBanner?: ReactNode
}
const InsidePage: FC<InsidePageProps> = ({
@@ -46,7 +47,8 @@ const InsidePage: FC<InsidePageProps> = ({
scrollPage = true,
scrollInsidePage = false,
customPadding = false,
customBtn
customBtn,
customBanner
}) => {
const navigate = useNavigate()
@@ -61,8 +63,13 @@ const InsidePage: FC<InsidePageProps> = ({
<div
className={`border-[0px] mr-PAGE_INSIDE_X ${showBorder ? 'border-solid border-b-[1px] border-BORDER' : ''} ${headerClassName}`}
>
{!pageTitle && !description && !backUrl && !customBtn ? (
{!pageTitle && !description && !backUrl && !customBtn && !customBanner ? (
<></>
) : customBanner ? (
<div className={customPadding ? '' : 'mb-[15px]'}>
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
{customBanner}
</div>
) : (
<div className={customPadding ? '' : 'mb-[30px]'}>
{backUrl && <TopBreadcrumb handleBackCallback={() => goBack()} />}
@@ -58,7 +58,7 @@ interface PageListProps<T> extends ProTableProps<T, unknown>, RefAttributes<Acti
delayLoading?: boolean
noScroll?: boolean
/* 前端分页的表格,需要传入该字段以支持后端搜索 */
manualReloadTable?: () => void,
manualReloadTable?: () => void
customEmptyRender?: () => React.ReactNode
}
@@ -109,6 +109,7 @@ const PageList = <T extends Record<string, unknown>>(
const [allowTableClick, setAllowTableClick] = useState<boolean>(false)
const { accessData, checkPermission, accessInit, state } = useGlobalContext()
const [minTableWidth, setMinTableWidth] = useState<number>(0)
const [enableVirtual, setEnableVirtual] = useState(false)
useImperativeHandle(ref, () => actionRef.current!)
@@ -301,7 +302,7 @@ const PageList = <T extends Record<string, unknown>>(
<ProTable<T>
actionRef={actionRef}
columns={newColumns}
virtual
virtual={enableVirtual}
scroll={noScroll ? undefined : { x: tableWidth, y: tableHeight }}
size="middle"
rowSelection={rowSelection}
@@ -328,6 +329,10 @@ const PageList = <T extends Record<string, unknown>>(
}
: false
}}
postData={(data: any) => {
setEnableVirtual(!!data?.length)
return data
}}
showSorterTooltip={false}
columnsState={{ persistenceType: 'localStorage', persistenceKey: id }}
pagination={
@@ -27,6 +27,7 @@ type TimeRangeSelectorProps = {
bindRef?: any
hideBtns?: TimeRangeButton[]
defaultTimeButton?: TimeRangeButton
customClassNames?: string
}
const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
const {
@@ -38,7 +39,8 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
labelSize = 'default',
bindRef,
hideBtns = [],
defaultTimeButton = 'hour'
defaultTimeButton = 'hour',
customClassNames = 'pt-btnybase'
} = props
const [timeButton, setTimeButton] = useState(initialTimeButton || '')
const [datePickerValue, setDatePickerValue] = useState<RangeValue>(initialDatePickerValue || [null, null])
@@ -110,8 +112,12 @@ const TimeRangeSelector = (props: TimeRangeSelectorProps) => {
return current && current.valueOf() > dayjs().startOf('day').valueOf()
}
useEffect(() => {
setTimeButton(initialTimeButton || '')
}, [initialTimeButton])
return (
<div className="flex flex-nowrap items-center pt-btnybase mr-btnybase">
<div className={`flex flex-nowrap items-center ${customClassNames} mr-btnybase`}>
{!hideTitle && <label className={`whitespace-nowrap `}>{$t('时间')}</label>}
<Radio.Group className="whitespace-nowrap" value={timeButton} onChange={handleRadioChange} buttonStyle="solid">
{hideBtns?.length && hideBtns.includes('hour') ? null : (
@@ -187,6 +187,9 @@ export const TranslateWord = () => {
{$t('调用地址')}
{$t('消费者 IP')}
{$t('鉴权名称')}
{$t('日志输出')}
{$t('响应时间')}
{$t('时间戳')}
</>
)
}
@@ -0,0 +1,293 @@
import { Avatar, Button, Card, Tag, Tooltip, App } from 'antd'
import { Icon } from '@iconify/react/dist/iconify.js'
import { $t } from '@common/locales/index.ts'
import { ApiOutlined } from '@ant-design/icons'
import { useEffect, useState } from 'react'
import { SERVICE_KIND_OPTIONS } from '@core/const/system/const'
import { IconButton } from '@common/components/postcat/api/IconButton'
import useCopyToClipboard from '@common/hooks/copy'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
export type ServiceBasicInfoType = {
id?: string
logo?: string
name: string
description: string
appNum: number
apiNum: number
serviceName: string
serviceDesc: string
invokeCount: number
catalogue: {
name: string
}
serviceKind: string
service_kind: string
enableMcp: boolean
enable_mcp: boolean
isReleased?: boolean
}
type ServiceInfoCardProps = {
actionSlot?: React.ReactNode
customClassName?: string
serviceId?: string
serviceBasicInfo?: ServiceBasicInfoType
teamId?: string
}
const ServiceInfoCard = ({
actionSlot,
customClassName,
serviceId,
serviceBasicInfo,
teamId
}: ServiceInfoCardProps) => {
/** 服务指标 */
const [serviceMetrics, setServiceMetrics] = useState<{ title: string; icon: React.ReactNode; value: string }[]>([])
/** 服务标签 */
const [serviceTags, setServiceTags] = useState<
{ color: string; textColor: string; title: string; content: React.ReactNode }[]
>([])
/** 剪切板 */
const { copyToClipboard } = useCopyToClipboard()
/** 弹窗组件 */
const { message } = App.useApp()
/** 获取服务信息 */
const { fetchData } = useFetch()
/** 服务信息 */
const [serviceOverview, setServiceOverview] = useState<ServiceBasicInfoType>()
/**
* 复制
* @param value
* @returns
*/
const handleCopy = async (value: string): Promise<void> => {
if (value) {
copyToClipboard(value)
message.success($t(RESPONSE_TIPS.copySuccess))
}
}
/** 获取服务信息 */
const getServiceOverview = () => {
fetchData<BasicResponse<{ overview: ServiceBasicInfoType }>>('service/overview/basic', {
method: 'GET',
eoParams: { service: serviceId, team: teamId },
eoTransformKeys: [
'api_num',
'enable_mcp',
'service_kind',
'subscriber_num',
'invoke_num',
'avaliable_monitor',
'is_released'
]
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const serviceOverview = {
...data.overview,
appNum: data.overview.subscriberNum,
invokeCount: data.overview.invokeNum,
serviceName: data.overview.name,
serviceDesc: data.overview.description
}
setServiceOverview(serviceOverview)
setServiceMetricsList(serviceOverview)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
/**
* 打开服务详情页面
*/
const openInPortal = () => {
window.open(`/portal/detail/${serviceOverview?.id}`, '_blank')
}
// 格式化调用次数,添加K和M单位
const formatInvokeCount = (count: number | null | undefined): string => {
if (count === null || count === undefined) return '-'
if (count >= 1000000) {
const value = Math.floor(count / 100000) / 10
return `${value}M`
}
if (count >= 1000) {
const value = Math.floor(count / 100) / 10
return `${value}K`
}
return count.toString()
}
const setServiceMetricsList = (serviceOverview: ServiceBasicInfoType) => {
// 设置服务指标数据
setServiceMetrics([
{
title: 'API 数量',
icon: <ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]" />,
value: serviceOverview.apiNum?.toString() || '0'
},
{
title: '接入消费者数量',
icon: <Icon icon="tabler:api-app" width="14" height="14" />,
value: serviceOverview.appNum?.toString() || '0'
},
{
title: '30天内调用次数',
icon: <Icon icon="iconoir:graph-up" width="14" height="14" />,
value: formatInvokeCount(serviceOverview.invokeCount ?? 0)
}
])
const serviceKind = serviceOverview?.serviceKind || serviceOverview?.service_kind
// 设置服务标签数据
const tags = [
{
color: '#7371fc1b',
textColor: 'text-theme',
title: serviceOverview?.catalogue?.name || '-',
content: serviceOverview?.catalogue?.name || '-'
},
{
color: `#${serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`,
textColor: 'text-[#000]',
title: serviceKind || '-',
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceKind)?.label || '-'
}
]
// 如果启用了MCP,添加MCP标签
if (serviceOverview?.enableMcp) {
tags.push({
color: '#FFF0C1',
textColor: 'text-[#000]',
title: 'MCP',
content: 'MCP'
})
}
setServiceTags(tags)
}
useEffect(() => {
if (!serviceId && serviceBasicInfo) {
setServiceMetricsList(serviceBasicInfo)
setServiceOverview(serviceBasicInfo)
return
}
getServiceOverview()
}, [serviceId, serviceBasicInfo])
return (
<>
<Card
style={{
borderRadius: '10px',
background: 'linear-gradient(35deg, rgb(246, 246, 260) 0%, rgb(255, 255, 255) 40%)'
}}
className={`w-full ${customClassName}`}
classNames={{
body: `p-[15px] ${actionSlot ? 'h-[180px]' : 'max-h-[130px]'}`
}}
>
{serviceOverview && (
<>
<div className="service-info">
<div className="flex items-center">
<div>
<Avatar
shape="square"
size={50}
className={`rounded-[12px] border-none rounded-[12px] ${serviceOverview.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
src={
serviceOverview.logo ? (
<img
src={serviceOverview.logo}
alt="Logo"
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
/>
) : undefined
}
icon={serviceOverview.logo ? '' : <Icon icon="tabler:api-app" />}
>
{' '}
</Avatar>
</div>
<div className="pl-[20px] w-[calc(100%-50px)] overflow-hidden">
<p
className={`text-[14px] h-[20px] leading-[20px] truncate font-bold w-full flex items-center gap-[4px]`}
>
{serviceOverview.serviceName}
</p>
<div className="mt-[5px] h-[20px] flex items-center font-normal">
{serviceTags.map((tag, index) => (
<Tag
key={index}
color={tag.color}
className={`${tag.textColor} font-normal border-0 mr-[12px] max-w-[150px] truncate`}
bordered={false}
title={tag.title}
>
{tag.content}
</Tag>
))}
{serviceMetrics.map((item, index) => (
<Tooltip key={index} title={$t(item.title)}>
<span className="mr-[12px] flex items-center">
<span className="h-[14px] mr-[4px] flex items-center">{item.icon}</span>
<span className="font-normal text-[14px]">{item.value}</span>
</span>
</Tooltip>
))}
</div>
</div>
{serviceOverview.id && (
<>
<div className="absolute top-[14px] right-[20px]">
<span className="bg-white relative py-[2px] pl-[10px] pr-[30px] inline-block border-solid border-[1px] border-BORDER rounded-lg">
{$t('服务 ID')}{serviceOverview.id || '-'}
<IconButton
name="copy"
onClick={() => handleCopy(serviceOverview.id || '')}
sx={{
position: 'absolute',
top: '0px',
right: '5px',
color: '#999',
transition: 'none',
'&.MuiButtonBase-root:hover': {
background: 'transparent',
color: '#3D46F2',
transition: 'none'
}
}}
></IconButton>
</span>
<Tooltip title={serviceOverview.isReleased ? '' : $t('服务尚未发布')}>
<Button
disabled={!serviceOverview.isReleased}
className="ml-[10px] !max-h-[28px] rounded-[13px]"
type="primary"
onClick={() => openInPortal()}
>
{$t('跳转至详情页')}
</Button>
</Tooltip>
</div>
</>
)}
</div>
<span className="line-clamp-2 mt-[15px] text-[12px] text-[#666]" title={serviceOverview.serviceDesc}>
{serviceOverview.serviceDesc || $t('暂无服务描述')}
</span>
</div>
</>
)}
<div className="absolute bottom-[15px]">{actionSlot}</div>
</Card>
</>
)
}
export default ServiceInfoCard
@@ -0,0 +1,431 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['exports', 'echarts'], factory);
} else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
// CommonJS
factory(exports, require('echarts'));
} else {
// Browser globals
factory({}, root.echarts);
}
}(this, function (exports, echarts) {
var log = function (msg) {
if (typeof console !== 'undefined') {
console && console.error && console.error(msg);
}
};
if (!echarts) {
log('ECharts is not Loaded');
return;
}
echarts.registerTheme('apipark chart palette', {
"color": [
"#4429e6",
"#fd6280",
"#28dbe2",
"#ffc404",
"#b92325",
"#1b9f17",
"#fe8705",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#333333"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#333333"
},
"subtextStyle": {
"color": "#999999"
}
},
"line": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "5",
"symbol": "circle",
"smooth": true
},
"radar": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "5",
"symbol": "circle",
"smooth": true
},
"bar": {
"itemStyle": {
"barBorderWidth": "2",
"barBorderColor": "rgba(255,255,255,0.3)"
}
},
"pie": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"scatter": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"parallel": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"sankey": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"funnel": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"gauge": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"candlestick": {
"itemStyle": {
"color": "#d87a80",
"color0": "#2ec7c9",
"borderColor": "#d87a80",
"borderColor0": "#2ec7c9",
"borderWidth": 1
}
},
"graph": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
},
"lineStyle": {
"width": 1,
"color": "#aaaaaa"
},
"symbolSize": "5",
"symbol": "circle",
"smooth": true,
"color": [
"#4429e6",
"#fd6280",
"#28dbe2",
"#ffc404",
"#b92325",
"#1b9f17",
"#fe8705",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#333333"
],
"label": {
"color": "#fefefe"
}
},
"map": {
"itemStyle": {
"areaColor": "#dddddd",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"label": {
"color": "#d87a80"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(254,153,78,1)",
"borderColor": "#444",
"borderWidth": 1
},
"label": {
"color": "rgb(100,0,0)"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#dddddd",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"label": {
"color": "#d87a80"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(254,153,78,1)",
"borderColor": "#444",
"borderWidth": 1
},
"label": {
"color": "rgb(100,0,0)"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333333"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": true,
"areaStyle": {
"color": [
"#ffffff",
"rgba(0,0,0,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": true,
"areaStyle": {
"color": [
"#ffffff",
"rgba(0,0,0,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#000000"
},
"emphasis": {
"iconStyle": {
"borderColor": "#000000"
}
}
},
"legend": {
"textStyle": {
"color": "#333333"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "rgba(0,0,0,0.3)",
"width": "1"
},
"crossStyle": {
"color": "rgba(0,0,0,0.3)",
"width": "1"
}
}
},
"timeline": {
"lineStyle": {
"color": "#008acd",
"width": 1
},
"itemStyle": {
"color": "#008acd",
"borderWidth": 1
},
"controlStyle": {
"color": "#008acd",
"borderColor": "#008acd",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#2ec7c9",
"borderColor": "#2ec7c9"
},
"label": {
"color": "#008acd"
},
"emphasis": {
"itemStyle": {
"color": "#a9334c"
},
"controlStyle": {
"color": "#008acd",
"borderColor": "#008acd",
"borderWidth": 0.5
},
"label": {
"color": "#008acd"
}
}
},
"visualMap": {
"color": [
"#ffffff",
"#4429e6"
]
},
"dataZoom": {
"backgroundColor": "rgba(47,69,84,0)",
"dataBackgroundColor": "#efefff",
"fillerColor": "rgba(182,162,222,0.2)",
"handleColor": "#008acd",
"handleSize": "100%",
"textStyle": {
"color": "#333333"
}
},
"markPoint": {
"label": {
"color": "#fefefe"
},
"emphasis": {
"label": {
"color": "#fefefe"
}
}
}
});
}));
@@ -0,0 +1,409 @@
{
"color": [
"#4429e6",
"#fd6280",
"#28dbe2",
"#ffc404",
"#b92325",
"#1b9f17",
"#fe8705",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#333333"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#333333"
},
"subtextStyle": {
"color": "#999999"
}
},
"line": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "5",
"symbol": "circle",
"smooth": true
},
"radar": {
"itemStyle": {
"borderWidth": "2"
},
"lineStyle": {
"width": "2"
},
"symbolSize": "5",
"symbol": "circle",
"smooth": true
},
"bar": {
"itemStyle": {
"barBorderWidth": "2",
"barBorderColor": "rgba(255,255,255,0.3)"
}
},
"pie": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"scatter": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"boxplot": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"parallel": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"sankey": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"funnel": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"gauge": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
}
},
"candlestick": {
"itemStyle": {
"color": "#d87a80",
"color0": "#2ec7c9",
"borderColor": "#d87a80",
"borderColor0": "#2ec7c9",
"borderWidth": 1
}
},
"graph": {
"itemStyle": {
"borderWidth": "2",
"borderColor": "rgba(255,255,255,0.3)"
},
"lineStyle": {
"width": 1,
"color": "#aaaaaa"
},
"symbolSize": "5",
"symbol": "circle",
"smooth": true,
"color": [
"#4429e6",
"#fd6280",
"#28dbe2",
"#ffc404",
"#b92325",
"#1b9f17",
"#fe8705",
"#97b552",
"#95706d",
"#dc69aa",
"#07a2a4",
"#9a7fd1",
"#588dd5",
"#f5994e",
"#333333"
],
"label": {
"color": "#fefefe"
}
},
"map": {
"itemStyle": {
"areaColor": "#dddddd",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"label": {
"color": "#d87a80"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(254,153,78,1)",
"borderColor": "#444",
"borderWidth": 1
},
"label": {
"color": "rgb(100,0,0)"
}
}
},
"geo": {
"itemStyle": {
"areaColor": "#dddddd",
"borderColor": "#eeeeee",
"borderWidth": 0.5
},
"label": {
"color": "#d87a80"
},
"emphasis": {
"itemStyle": {
"areaColor": "rgba(254,153,78,1)",
"borderColor": "#444",
"borderWidth": 1
},
"label": {
"color": "rgb(100,0,0)"
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333333"
},
"splitLine": {
"show": false,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": true,
"areaStyle": {
"color": [
"#ffffff",
"rgba(0,0,0,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": true,
"areaStyle": {
"color": [
"#ffffff",
"rgba(0,0,0,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisTick": {
"show": true,
"lineStyle": {
"color": "rgba(0,0,0,0.1)"
}
},
"axisLabel": {
"show": true,
"color": "#333"
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.3)",
"rgba(200,200,200,0.3)"
]
}
}
},
"toolbox": {
"iconStyle": {
"borderColor": "#000000"
},
"emphasis": {
"iconStyle": {
"borderColor": "#000000"
}
}
},
"legend": {
"textStyle": {
"color": "#333333"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "rgba(0,0,0,0.3)",
"width": "1"
},
"crossStyle": {
"color": "rgba(0,0,0,0.3)",
"width": "1"
}
}
},
"timeline": {
"lineStyle": {
"color": "#008acd",
"width": 1
},
"itemStyle": {
"color": "#008acd",
"borderWidth": 1
},
"controlStyle": {
"color": "#008acd",
"borderColor": "#008acd",
"borderWidth": 0.5
},
"checkpointStyle": {
"color": "#2ec7c9",
"borderColor": "#2ec7c9"
},
"label": {
"color": "#008acd"
},
"emphasis": {
"itemStyle": {
"color": "#a9334c"
},
"controlStyle": {
"color": "#008acd",
"borderColor": "#008acd",
"borderWidth": 0.5
},
"label": {
"color": "#008acd"
}
}
},
"visualMap": {
"color": [
"#ffffff",
"#4429e6"
]
},
"dataZoom": {
"backgroundColor": "rgba(47,69,84,0)",
"dataBackgroundColor": "#efefff",
"fillerColor": "rgba(182,162,222,0.2)",
"handleColor": "#008acd",
"handleSize": "100%",
"textStyle": {
"color": "#333333"
}
},
"markPoint": {
"label": {
"color": "#fefefe"
},
"emphasis": {
"label": {
"color": "#fefefe"
}
}
}
}
@@ -0,0 +1,12 @@
// 导入echarts核心模块
import * as echarts from 'echarts/core'
// 导入主题JSON
import themeJson from './apipark-chart-palette.json'
// 全局注册主题
export function registerApiparkTheme() {
echarts.registerTheme('apipark', themeJson)
}
// 导出主题名称,方便组件使用
export const THEME_NAME = 'apipark'
@@ -0,0 +1,11 @@
// 导入主题配置
import themeJson from './apipark-chart-palette.json'
// 导出主题配置
export const apiparkTheme = themeJson
// 导出颜色列表,方便单独使用
export const chartColors = themeJson.color
// 导出默认颜色
export const defaultColor = chartColors[0]
@@ -22,8 +22,10 @@ export const BreadcrumbProvider = ({ children }: unknown) => {
<BreadcrumbContext.Provider
value={{
setBreadcrumb: (newItems) => {
newItems.slice(0, newItems.length - 1).forEach((item) => {
item.title = <span className="cursor-pointer hover:text-theme">{item.title}</span>
newItems.forEach((item) => {
item.title = (
<span className={`${item.onClick ? 'cursor-pointer hover:text-theme' : ''}`}>{item.title}</span>
)
})
setBreadcrumb(newItems)
},
@@ -87,8 +87,8 @@ const mockData = [
},
{
name: 'API 市场',
key: 'serviceHub',
path: '/serviceHub',
key: 'portal',
path: '/portal',
icon: 'ic:baseline-hub',
access: 'system.api_portal.api_portal.view'
},
@@ -107,15 +107,15 @@ const mockData = [
},
{
name: '服务',
key: 'analyticsSubscriber',
path: '/analytics/subscriber/list',
key: 'analyticsService',
path: '/analytics/service/list',
icon: 'ic:baseline-blinds-closed',
access: 'system.analysis.run_view.view'
},
{
name: '消费者',
key: 'analyticsProvider',
path: '/analytics/provider/list',
key: 'analyticsConsumer',
path: '/analytics/consumer/list',
icon: 'ic:baseline-apps',
access: 'system.analysis.run_view.view'
},
@@ -253,7 +253,7 @@ const mockData = [
access: 'system.settings.ssl_certificate.view'
},
{
name: '日志',
name: '日志输出',
key: 'logsettings',
path: '/logsettings',
icon: 'ic:baseline-sticky-note-2',
@@ -112,10 +112,10 @@ const mockData = {
},
{
driver: 'apipark.builtIn.component',
name: 'serviceHub',
name: 'portal',
router: [
{
path: 'serviceHub',
path: 'portal',
type: 'normal'
}
]
@@ -54,6 +54,10 @@
"上游列表": "K54e44357",
"备注": "Kb8e8e6f5",
"上线情况": "K7e52ffa3",
"服务 ID": "K1e84ad04",
"服务尚未发布": "Ke1e649cb",
"跳转至详情页": "K2e683a7d",
"暂无服务描述": "Ka4b45550",
"申请原因": "K1ab0ae5b",
"审核意见": "K53c00c3c",
"暂无(0)权限,请联系管理员分配。": "Kfd50704d",
@@ -114,6 +118,7 @@
"无需审核:允许任何消费者调用该服务": "K1fc2cc28",
"人工审核:仅允许通过人工审核的消费者调用该服务": "K8dabb98e",
"开启:AI Agent 等产品能够通过 MCP 方式调用服务": "Ke959f135",
"总览": "Kaf9e8011",
"永久": "Kbfe02d7f",
"否": "K1e9c479e",
"是": "Kaddfcb6b",
@@ -241,6 +246,9 @@
"调用地址": "K2f5fdf5e",
"消费者 IP": "K1bc5e0a3",
"鉴权名称": "K6f39ea21",
"日志输出": "K3c722abd",
"响应时间": "K1be06929",
"时间戳": "K5e51f5d",
"暂无操作权限,请联系管理员分配。": "K23fda291",
"微信小程序": "K4618cb0a",
"获取文件,需填路径": "Ka854f511",
@@ -345,11 +353,10 @@
"重置": "K50d471b2",
"查询": "Kee8ae330",
"请输入 APIURL 搜索": "Kf8187c33",
"服务": "Kb58e0c3f",
"说明文档": "K6cd677b",
"最近一次更新者": "K617f34f1",
"最近一次更新时间": "K6ebca204",
"保存": "Kabfe9512",
"服务": "Kb58e0c3f",
"API 路由": "K51d1eb5d",
"API 文档": "Ka2b6d281",
"使用说明": "Kdefa9caa",
@@ -361,14 +368,10 @@
"管理": "K5974bf24",
"调用拓扑图": "K3fa5c4c3",
"设置": "Kb5c7b82d",
"服务 ID": "K1e84ad04",
"新增订阅方": "K39ab0358",
"手动添加": "K18307d56",
"订阅申请": "K705fe9f5",
"订阅方": "K3a67ea90",
"API": "K3ba29a85",
"编辑 API": "Ke93388fd",
"添加 API": "K84aabfd4",
"AI 路由设置": "Kefa2a4cf",
"路由名称": "K66060758",
"请求路径": "K5582ac8",
@@ -379,7 +382,6 @@
"拦截接口": "Kee4139c2",
"开启拦截后,网关会拦截所有该路径的请求。": "K3e38ea",
"模型配置": "K8a35059b",
"路由": "Kf9dcef3a",
"添加路由": "K6134bbe8",
"输入 URL 查找路由": "Kf85b83a0",
"线上模型": "K84b2cf2d",
@@ -554,11 +556,15 @@
"访客模式": "K192b3e38",
"您可通过访客模式查看所有页面和功能,但是无法编辑数据。访客模式仅用于了解产品功能,您可以在正式产品中关闭该功能。": "K91aa4801",
"Version (0)-(1)": "K480045ce",
"日志置": "Kadee8e49",
"日志输出设置": "K74a5fbc0",
"提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。": "K2724314b",
"日志配置": "Kadee8e49",
"MCP 配置": "K6e9c928f",
"Open API 文档": "Kb6d0eb39",
"AI 代理集成": "Ke6908f16",
"请先订阅该服务": "K71ed51fa",
"申请": "K4aa9ed2c",
"选择 API Key": "K1bec8cbe",
"新增 API Key": "Kb0e0aeda",
"API 密钥可用于调用系统级 Open API 和 MCP。": "K9d81999c",
"MCP 服务": "Kf106bc62",
@@ -619,7 +625,7 @@
"数据源": "K8fa58214",
"设置监控报表的数据来源,设置完成之后即可获得详细的API调用统计图表。": "Kdbafd6f9",
"统计图表": "K1358acf",
"数据日志": "K17dc3a62",
"请求日志": "Kc8bf447",
"地址(IP:端口)": "K62dabdf6",
"组织(Organization": "K2db12335",
"添加策略": "K34d0d409",
@@ -627,8 +633,6 @@
"处理日志": "Ke429194e",
"脱敏前": "K8c34c02f",
"脱敏后": "K8e3d388d",
"编辑服务策略": "Kf06f6737",
"添加服务策略": "K205971e1",
"编辑策略": "Kc82b8374",
"策略类型": "K4b34a5e5",
"匹配条件": "K57f0fee8",
@@ -660,6 +664,28 @@
"系统级别角色": "K138facd3",
"添加角色": "K6eac768d",
"团队级别角色": "Kb9c2cf02",
"API / Tools": "K9d526cac",
"消费者": "K7acfcfad",
"HTTP 状态": "Kc68ba0f4",
"IP": "Kb09b747",
"通过系统级别的 API Key 来调用": "K2eacb44f",
"日志详情": "K764bca7c",
"暂无数据": "Kf8525cf2",
"输入 Token": "K33bc1ad1",
"输出 Token": "Ke00ff18b",
"订阅数量": "Ke04bc00d",
"已开启": "K1b97ae0a",
"开启 MCP": "K19ec733b",
"API 使用排名": "Kbee2340",
"消费者使用排名": "Kf6af1f40",
"请求次数": "K9d3f2d9d",
"网络流量": "Ke2241377",
"平均响应时间": "K7c8d5c23",
"平均每消费者的请求次数": "K6c267c7b",
"平均每消费者的网络流量": "K133d4291",
"Token 消耗": "K37c5f1d0",
"平均 Token 消耗": "K10a8bee3",
"平均每消费者的 Token 消耗": "Kb98264d4",
"单位:ms,最小值:1": "K2a16c93b",
"API 路由设置": "Ka945cfb1",
"API 基础信息": "K2e050340",
@@ -742,7 +768,6 @@
"退出全屏": "Kaf70c3b",
"(0)调用详情": "Kd22841a4",
"消费者调用统计": "K61cca533",
"消费者": "K7acfcfad",
"请选择消费者": "Kdfff59d4",
"调用趋势": "K8c7f2d2e",
"(0)-(1)调用趋势": "K657c3452",
@@ -783,14 +808,13 @@
"配置集群信息": "Ke5ed9810",
"监控设置": "K1a132228",
"配置监控信息": "K6af08c3c",
"监控总览": "K4a1a14",
"服务被调用统计": "K69741ea7",
"API 调用统计": "K9c8d9933",
"加载数据失败,请重试": "K6c2d93b6",
"亿": "K145e4941",
"万": "Ke6a935d",
"搜索分类或标签": "Kd59290a2",
"暂无API数据": "K6b75bdbc",
"搜索或选择消费者": "Kb684c806",
"该消费者已订阅": "K5611e01e",
"申请理由": "K4b15d6f5",
"支持把当前服务对接主流的 AI Agent平台,实现在 Agent 平台上快速、安全和合规地使用企业开放的 API 能力。": "K2ec0fa56",
"可按以下步骤进行对接:": "K35f23b64",
@@ -850,8 +874,7 @@
"版本": "K81634069",
"更新时间": "Keefda53d",
"介绍": "K59cdbec3",
"暂无服务描述": "Ka4b45550",
"申请": "K4aa9ed2c",
"API": "K3ba29a85",
"无标签": "K96a2f1c8",
"分类": "Kb32f0afe",
"服务市场": "K370a3eb2",
@@ -4,7 +4,7 @@
"Kb58e0c3f": "Service",
"Kc9e489f5": "Team",
"K61c89f5f": "API Portal",
"K16d71239": "Analysis",
"K16d71239": "Analytics",
"K714c192d": "Call Statistics",
"Kd57dfe97": "Topology",
"K3fe97dcc": "System Settings",
@@ -186,7 +186,7 @@
"K617f34f1": "Updated By",
"K6ebca204": "Update Time",
"Kabfe9512": "Save",
"K51d1eb5d": "API",
"K51d1eb5d": "API Routes",
"Ka2b6d281": "API Docs",
"Kdefa9caa": "Usage Instructions",
"K36856e71": "Publish",
@@ -210,7 +210,7 @@
"K469e475a": "Max Retry Times",
"K8a35059b": "Model Settings",
"Kf9dcef3a": "API",
"K6134bbe8": "Add API",
"K6134bbe8": "Add API Route",
"Kf85b83a0": "Enter URL to Search",
"Kcf9f90b8": "Model Provider",
"Kfede1c7c": "Model",
@@ -927,5 +927,50 @@
"K71ed51fa": "Please subscribe to the service first",
"K1bec8cbe": "Select API Key",
"K5611e01e": "This consumer is already subscribed",
"Kaf9e8011": "Overview"
"Kaf9e8011": "Overview",
"Ke1e649cb": "Service not released",
"K2e683a7d": "Open in Portal",
"Ke04bc00d": "Subscribers",
"K1b97ae0a": "Enabled",
"K19ec733b": "Enable MCP",
"Kbee2340": "Top API",
"Kf6af1f40": "Top Consumer",
"K318a7519": "Requests",
"K9ef68e3f": "Token",
"Kfb14ccb0": "Models",
"K10a8bee3": "Avg Token per Second",
"K2727b76b": "Avg Requests per Subscriber",
"K4c7a6704": "Avg Token per Subscriber",
"K53eb7414": "Traffic",
"K7c8d5c23": "Avg Response Time",
"Kf9eb702": "QRS",
"K7f0aa740": "Avg Traffic per Subscriber",
"K9d526cac": "API / Tools",
"Kc68ba0f4": "HTTP Status",
"Kb09b747": "IP",
"K2eacb44f": "Request the API using a system-level API Key",
"K764bca7c": "Log Detail",
"K6c016898": "Avg Token per Second",
"K652843b0": "Avg Requests per Subscriber",
"Kdbf831a0": "Avg Token per Subscriber",
"K8158a6e4": "Avg Traffic per Subscriber",
"K6b882d4a": "Avg Token per Subscriber",
"K6c2d93b6": "Failed to load data, please try again",
"Kf5eeb9c5": "Avg Token per Subscriber",
"K1639a17a": "API Routes Docs",
"K33bc1ad1": "Input Token",
"Ke00ff18b": "Output Token",
"K81140e5b": "Total Token",
"K3c722abd": "Log Output",
"K74a5fbc0": "Log Output Settings",
"Kc8bf447": "Request Log",
"Kf8525cf2": "No Data",
"K1be06929": "Response Time",
"K5e51f5d": "Timestamp",
"K9d3f2d9d": "Requests",
"Ke2241377": "Traffic",
"K6c267c7b": "Avg Requests per Subscriber",
"K133d4291": "Avg Traffic per Subscriber",
"K37c5f1d0": "Token",
"Kb98264d4": "Avg Token per Subscriber"
}
@@ -189,7 +189,7 @@
"K617f34f1": "更新者",
"K6ebca204": "更新日時",
"Kabfe9512": "保存",
"K51d1eb5d": "API",
"K51d1eb5d": "APIルート",
"Ka2b6d281": "API ドキュメント",
"Kdefa9caa": "説明ドキュメント",
"K36856e71": "公開",
@@ -213,7 +213,7 @@
"K469e475a": "リトライ回数",
"K8a35059b": "モデル設定",
"Kf9dcef3a": "API",
"K6134bbe8": "API を追加",
"K6134bbe8": "APIルートを追加する",
"Kf85b83a0": "URL を入力して検索",
"Kcf9f90b8": "モデルプロバイダー",
"Kfede1c7c": "モデル",
@@ -949,5 +949,50 @@
"K71ed51fa": "このサービスに先にサブスクリプションしてください",
"K1bec8cbe": "APIキーを選択してください",
"K5611e01e": "この消費者はすでに購読しています",
"Kaf9e8011": "概要"
"Kaf9e8011": "概要",
"Ke1e649cb": "サービスはまだ公開されていません",
"K2e683a7d": "詳細ページへ移動",
"Ke04bc00d": "サブスクリプション数",
"K1b97ae0a": "有効",
"K19ec733b": "MCP を有効にする",
"Kbee2340": "API 使用ランキング",
"Kf6af1f40": "コンシューマー使用ランキング",
"K318a7519": "リクエスト数",
"K9ef68e3f": "トークン",
"Kfb14ccb0": "モデル使用量",
"K10a8bee3": "平均トークン消費量",
"K2727b76b": "ユーザーあたり平均リクエスト数",
"K4c7a6704": "ユーザーあたり平均トークン消費量",
"K53eb7414": "トラフィック",
"K7c8d5c23": "平均応答時間",
"Kf9eb702": "毎秒リクエスト数",
"K7f0aa740": "ユーザーあたり平均トラフィック",
"K9d526cac": "API / ツール",
"Kc68ba0f4": "HTTP ステータス",
"Kb09b747": "IP",
"K2eacb44f": "システムレベルの API Key で呼び出し",
"K764bca7c": "ログ詳細",
"K6c016898": "平均トークン/s 統計",
"K652843b0": "平均リクエスト数",
"Kdbf831a0": "平均トークン/加入者 統計",
"K8158a6e4": "平均トラフィック",
"K6b882d4a": "平均トークン/加入者",
"K6c2d93b6": "データの読み込みに失敗しました。もう一度お試しください",
"Kf5eeb9c5": "平均トークン/加入者 統計",
"K1639a17a": "APIルートのドキュメント",
"K33bc1ad1": "入力トークン",
"Ke00ff18b": "出力トークン",
"K81140e5b": "合計トークン",
"K3c722abd": "ログ出力",
"K74a5fbc0": "ログ出力設定",
"Kc8bf447": "リクエストログ",
"Kf8525cf2": "データがありません",
"K1be06929": "応答時間",
"K5e51f5d": "タイムスタンプ",
"K9d3f2d9d": "リクエスト数",
"Ke2241377": "ネットワークトラフィック",
"K6c267c7b": "消費者あたりの平均リクエスト数",
"K133d4291": "消費者あたりの平均ネットワークトラフィック",
"K37c5f1d0": "トークン消費量",
"Kb98264d4": "消費者あたりの平均トークン消費量"
}
@@ -1,69 +1 @@
{
"K630c9e6d": "APIPark",
"Ka3e9f580": "发布名称",
"Kb2480682": "策略列表",
"K76036e25": "HTTP 请求头",
"K44607e3f": "全等匹配",
"Kc287500a": "前缀匹配",
"Kfc0b1147": "后缀匹配",
"Ka4a92043": "子串匹配",
"K30b2e44f": "非等匹配",
"Kb1587991": "空值匹配",
"K1e97dbd8": "存在匹配",
"Kc8ee3e62": "不存在匹配",
"K87c5a801": "区分大小写的正则匹配",
"K95f062f1": "不区分大小写的正则匹配",
"Kfbd230a5": "任意匹配",
"Kd85208a3": "驳回",
"Kad6aa439": "已订阅",
"K9a68443b": "取消申请",
"Kaeba0229": "透传客户端请求 Host",
"K6d7e2fd0": "使用上游服务 Host",
"K31332633": "重写 Host",
"K2c2bc64f": "动态服务发现",
"K78b1ca25": "地址",
"K1644b775": "新增",
"Kec91f0db": "申请方消费者",
"K118d8d74": "数据格式",
"Kfe7c7d2d": "关键字",
"K2f57a694": "正则表达式",
"K8953e0a6": "手机号",
"K6f86a038": "身份证号",
"K7954e7c8": "银行卡号",
"K320fdb17": "金额",
"K7867acda": "日期",
"K7d327ae8": "局部显示",
"Kfbf38e3c": "局部遮蔽",
"Kd8c1fbb0": "截取",
"K89829921": "替换",
"K480a7165": "乱序",
"Kea0d69df": "随机字符串",
"Ke7c84d1d": "自定义字符串",
"K49731763": "请输入IP地址或CIDR范围,每条以换行分割",
"K3a34d49b": "待更新",
"Kd2850420": "待删除",
"K83237c89": "输入的IP或CIDR不符合格式",
"K5ae2c87a": "请正确输入路径,如/usr/*或*/usr/*",
"K67f4e9bb": "与外部平台集成时,获取 API 市场中文档信息的域名",
"Kc82b8374": "编辑策略",
"K4b34a5e5": "策略类型",
"K57f0fee8": "匹配条件",
"K10650c58": "数据脱敏规则",
"K1b34a9ab": "配置脱敏规则",
"K26d22405": "匹配值",
"K1546e1fe": "脱敏类型",
"K9b9b0629": "起始位置",
"K52c84fe1": "长度",
"Kde84409c": "替换类型",
"K338653b4": "替换值",
"Kbaeed3b7": "JSON Path",
"K4cd91d61": "脱敏规则",
"K8dcad979": "自定义字符串; 值:",
"K82e3f7b7": "起始位置:(0)位;长度:(1)位",
"K49dfc123": "已选择(0)项(1)数据",
"K8457ea34": "所有(0)",
"K7ca9a795": "属性名称",
"Kc4391744": "属性值",
"K678e13fc": "配置(0)",
"Kf5fd27ed": "输入名称查找用户"
}
{}
@@ -189,7 +189,7 @@
"K617f34f1": "更新者",
"K6ebca204": "更新时间",
"Kabfe9512": "保存",
"K51d1eb5d": "API",
"K51d1eb5d": "API 路由",
"Ka2b6d281": "API 文档",
"Kdefa9caa": "说明文档",
"K36856e71": "发布",
@@ -213,7 +213,7 @@
"K469e475a": "最大重试次数",
"K8a35059b": "模型设置",
"Kf9dcef3a": "API",
"K6134bbe8": "添加 API",
"K6134bbe8": "添加 API 路由",
"Kf85b83a0": "输入 URL 查找",
"Kcf9f90b8": "模型供应商",
"Kfede1c7c": "模型",
@@ -880,5 +880,48 @@
"K71ed51fa": "请先订阅该服务",
"K1bec8cbe": "选择 API Key",
"K5611e01e": "该消费者已订阅",
"Kaf9e8011": "总览"
"Kaf9e8011": "总览",
"Ke1e649cb": "服务尚未发布",
"K2e683a7d": "跳转至详情页",
"Ke04bc00d": "订阅方数量",
"K1b97ae0a": "已开启",
"K19ec733b": "开启 MCP",
"Kbee2340": "API 使用排名",
"Kf6af1f40": "消费者使用排名",
"K318a7519": "请求数",
"K9ef68e3f": "Token",
"Kfb14ccb0": "模型使用量",
"K10a8bee3": "平均 Token 消耗",
"K2727b76b": "人均请求数",
"K4c7a6704": "人均 Token 消耗",
"K53eb7414": "流量",
"K7c8d5c23": "平均响应时间",
"Kf9eb702": "每秒请求数量",
"K7f0aa740": "人均流量",
"K9d526cac": "API / Tools",
"Kc68ba0f4": "HTTP 状态",
"Kb09b747": "IP",
"K2eacb44f": "通过系统级别的 API Key 来调用",
"K764bca7c": "日志详情",
"K6c016898": "平均 Token/s 统计",
"K652843b0": "平均请求数",
"K8158a6e4": "平均流量",
"K6c2d93b6": "加载数据失败,请重试",
"Kf5eeb9c5": "平均 Token/订阅者统计",
"K1639a17a": "API 路由文档",
"K33bc1ad1": "输入 Token",
"Ke00ff18b": "输出 Token",
"K81140e5b": "总 Token",
"K3c722abd": "日志输出",
"K74a5fbc0": "日志输出设置",
"Kc8bf447": "请求日志",
"Kf8525cf2": "暂无数据",
"K1be06929": "响应时间",
"K5e51f5d": "时间",
"K9d3f2d9d": "请求次数",
"Ke2241377": "网络流量",
"K6c267c7b": "平均每消费者的请求次数",
"K133d4291": "平均每消费者的网络流量",
"K37c5f1d0": "Token 消耗",
"Kb98264d4": "平均每消费者的 Token 消耗"
}
@@ -189,7 +189,7 @@
"K617f34f1": "更新者",
"K6ebca204": "更新時間",
"Kabfe9512": "保存",
"K51d1eb5d": "API",
"K51d1eb5d": "API 路由",
"Ka2b6d281": "API 文檔",
"Kdefa9caa": "說明文檔",
"K36856e71": "發布",
@@ -213,7 +213,7 @@
"K469e475a": "最大重試次數",
"K8a35059b": "模型設置",
"Kf9dcef3a": "API",
"K6134bbe8": "添加 API",
"K6134bbe8": "添加 API 路由",
"Kf85b83a0": "輸入 URL 查找",
"Kcf9f90b8": "模型供應商",
"Kfede1c7c": "模型",
@@ -949,5 +949,50 @@
"K71ed51fa": "請先訂閱該服務",
"K1bec8cbe": "選擇 API Key",
"K5611e01e": "該消費者已訂閱",
"Kaf9e8011": "總覽"
"Kaf9e8011": "總覽",
"Ke1e649cb": "服務尚未發布",
"K2e683a7d": "跳轉至詳情頁",
"Ke04bc00d": "訂閱數量",
"K1b97ae0a": "已開啟",
"K19ec733b": "開啟 MCP",
"Kbee2340": "API 使用排名",
"Kf6af1f40": "消費者使用排名",
"K318a7519": "請求數",
"K9ef68e3f": "Token",
"Kfb14ccb0": "模型使用量",
"K10a8bee3": "平均 Token 消耗",
"K2727b76b": "人均請求數",
"K4c7a6704": "人均 Token 消耗",
"K53eb7414": "流量",
"K7c8d5c23": "平均回應時間",
"Kf9eb702": "每秒請求數量",
"K7f0aa740": "人均流量",
"K9d526cac": "API / 工具",
"Kc68ba0f4": "HTTP 狀態",
"Kb09b747": "IP",
"K2eacb44f": "透過系統級 API Key 調用",
"K764bca7c": "日誌詳情",
"K6c016898": "平均 Token/s 統計",
"K652843b0": "平均請求數",
"Kdbf831a0": "每位訂閱者平均 Token 統計",
"K8158a6e4": "平均流量",
"K6b882d4a": "每位訂閱者平均 Token",
"K6c2d93b6": "載入資料失敗,請重試",
"Kf5eeb9c5": "每位訂閱者平均 Token 統計",
"K1639a17a": "API 路由文件",
"K33bc1ad1": "輸入 Token",
"Ke00ff18b": "輸出 Token",
"K81140e5b": "總計 Token",
"K3c722abd": "日誌輸出",
"K74a5fbc0": "日誌輸出設定",
"Kc8bf447": "請求日誌",
"Kf8525cf2": "暫無資料",
"K1be06929": "回應時間",
"K5e51f5d": "時間",
"K9d3f2d9d": "請求次數",
"Ke2241377": "網路流量",
"K6c267c7b": "平均每位使用者的請求次數",
"K133d4291": "平均每位使用者的網路流量",
"K37c5f1d0": "Token 消耗",
"Kb98264d4": "平均每位使用者的 Token 消耗"
}
+4
View File
@@ -6,6 +6,7 @@ import { PluginEventHubProvider } from '@common/contexts/PluginEventHubContext'
import { PluginSlotHubProvider } from '@common/contexts/PluginSlotHubContext'
import useInitializeMonaco from '@common/hooks/useInitializeMonaco'
import { $t } from '@common/locales'
import { registerApiparkTheme } from '@common/const/charts/initChartTheme'
import RenderRoutes from '@core/components/aoplatform/RenderRoutes'
import { App as AppAntd, ConfigProvider } from 'antd'
import { useMemo } from 'react'
@@ -130,6 +131,9 @@ const antdComponentThemeToken = {
}
}
// 注册 ECharts 主题
registerApiparkTheme()
function App() {
const { locale } = useLocaleContext()
useInitializeMonaco()
@@ -6,17 +6,18 @@ import { AiServiceRouterTableListItem, VariableItems } from './type'
import { PageProColumns } from '@common/components/aoplatform/PageList'
export const AI_SERVICE_ROUTER_TABLE_COLUMNS: PageProColumns<AiServiceRouterTableListItem>[] = [
{
title: '名称',
dataIndex: 'name',
width: 200,
ellipsis: true
},
{
title: 'URL',
dataIndex: 'requestPath',
ellipsis: true,
width: 200
},
{
title: '名称',
dataIndex: 'name',
ellipsis: true
},
{
title: '模型',
dataIndex: ['model', 'name'],
@@ -15,7 +15,7 @@ export type AiServiceConfigFieldType = {
logoFile?:UploadFile;
tags?:Array<string>;
description?: string;
team?:string;
team?:EntityItem;
master?:string;
serviceType?:'public'|'inner';
catalogue?:string | string[];
+31 -3
View File
@@ -96,6 +96,20 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
key: 'restServiceInside',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@core/pages/system/SystemInsidePage.tsx')),
children: [
{
path: 'overview',
key: 'restServiceInsideOverview',
lazy: lazy(
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceOverview/RestServiceContainer')
)
},
{
path: 'logs',
key: 'restServiceInsideLogs',
lazy: lazy(
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceLogs/RestServiceLogsContainer')
)
},
{
path: 'api',
key: 'restServiceInsideApi',
@@ -268,6 +282,20 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
() => import(/* webpackChunkName: "[request]" */ '@core/pages/aiService/AiServiceInsidePage.tsx')
),
children: [
{
path: 'overview',
key: 'aiServiceInsideOverview',
lazy: lazy(
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceOverview/AiServiceContainer')
)
},
{
path: 'logs',
key: 'aiServiceInsideLogs',
lazy: lazy(
() => import(/* webpackChunkName: "[request]" */ '@core/pages/serviceLogs/AiServiceLogsContainer')
)
},
{
path: 'api',
key: 'aiServiceInsideApi',
@@ -507,7 +535,7 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
],
[
'serviceHub',
'portal',
{
type: 'module',
component: <Outlet />,
@@ -674,12 +702,12 @@ export const routerMap: Map<string, RouterMapConfig> = new Map([
children: [
{
path: 'total',
key: 'analytics2',
key: 'analyticsTotal',
lazy: lazy(() => import(/* webpackChunkName: "[request]" */ '@dashboard/pages/DashboardTotal.tsx'))
},
{
path: ':dashboardType',
key: 'analytics3',
key: 'analyticsOther',
component: <Outlet />,
children: [
{
@@ -123,6 +123,7 @@ export type PartitionDataLogHeaderListFieldType = {
export type PartitionDataLogConfigFieldType = {
headers: PartitionDataLogHeaderListFieldType[]
url: string
driver?: string
}
export const PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS: PageProColumns<PartitionDataLogConfigFieldType & { _id: string }>[] = [
@@ -13,6 +13,7 @@ import {
} from './type'
import { PageProColumns } from '@common/components/aoplatform/PageList'
import { LogItem } from '@core/pages/serviceLogs/ServiceLogs'
export enum SubscribeEnum {
Rejected = 0,
@@ -241,6 +242,12 @@ export const MATCH_CONFIG: ConfigField<MatchItem>[] = [
]
export const SYSTEM_API_TABLE_COLUMNS: PageProColumns<SystemApiTableListItem>[] = [
{
title: '名称',
dataIndex: 'name',
width: 200,
ellipsis: true
},
{
title: 'URL',
dataIndex: 'requestPath',
@@ -500,3 +507,133 @@ export const SYSTEM_PUBLISH_ONLINE_COLUMNS = [
}
}
]
/** AI 服务排行 */
export const AI_SERVICE_TOP_RANKING_LIST: PageProColumns<any>[] = [
{
title: '名称',
dataIndex: 'name',
ellipsis: true
},
{
title: '请求总数',
dataIndex: 'request',
ellipsis: true
},
{
title: 'Token',
dataIndex: 'token',
ellipsis: true
}
]
/** REST 服务排行 */
export const REST_SERVICE_TOP_RANKING_LIST: PageProColumns<any>[] = [
{
title: '名称',
dataIndex: 'name',
ellipsis: true
},
{
title: '请求总数',
dataIndex: 'request',
ellipsis: true
},
{
title: '流量',
dataIndex: 'traffic',
ellipsis: true
}
]
/** REST 服务日志 */
export const REST_SERVICE_LOG_LIST: PageProColumns<LogItem>[] = [
{
title: '时间戳',
dataIndex: 'logTime',
copyable: false,
width: 180,
ellipsis: true
},
{
title: 'API / Tools',
dataIndex: ['api', 'name'],
ellipsis: true
},
{
title: '消费者',
dataIndex: ['consumer', 'name'],
ellipsis: true
},
{
title: 'HTTP 状态',
dataIndex: 'status',
ellipsis: true
},
{
title: 'IP',
dataIndex: 'ip',
copyable: true,
width: 140,
ellipsis: true
},
{
title: '响应时间',
dataIndex: 'responseTime',
width: 130,
ellipsis: true
},
{
title: '流量',
dataIndex: 'traffic',
ellipsis: true
}
]
/** AI 服务日志 */
export const AI_SERVICE_LOG_LIST: PageProColumns<LogItem>[] = [
{
title: '时间戳',
dataIndex: 'logTime',
copyable: false,
width: 200,
ellipsis: true
},
{
title: 'API / Tools',
dataIndex: ['api', 'name'],
ellipsis: true
},
{
title: '消费者',
dataIndex: ['consumer', 'name'],
ellipsis: true
},
{
title: 'HTTP 状态',
dataIndex: 'status',
ellipsis: true
},
{
title: '模型',
dataIndex: 'model',
ellipsis: true
},
{
title: 'IP',
dataIndex: 'ip',
copyable: true,
width: 140,
ellipsis: true
},
{
title: 'Token/s',
dataIndex: 'tokenPerSecond',
ellipsis: true
},
{
title: 'Token',
dataIndex: 'token',
ellipsis: true
}
]
@@ -97,6 +97,7 @@ export type SystemApiProxyType = {
export type SystemApiProxyFieldType = {
protocols: string[];
id:string;
name:string
description?:string;
disable:boolean;
path:string;
+23
View File
@@ -1156,9 +1156,32 @@ p{
align-items: center;
}
.ranking-list .ant-pro-table{
overflow: hidden;
border-radius: 10px;
border: none !important;
}
.ranking-list .ant-table-tbody:not(tbody) .ant-table-cell{
padding: 10px 10px !important;
}
.ranking-list .ant-table-container .ant-table-thead th{
background-color: #fff !important;
padding: 10px 10px !important;
}
.ranking-list .ant-table-container .ant-table-thead th::before{
display: none;
}
.ant-alert-info{
background: #1784FC1A !important;
}
.service-log-tab .ant-tabs .ant-tabs-nav .ant-tabs-tab{
padding-left: 0px;
padding-right: 0px;
}
.service-log-tab .ant-tabs .ant-tabs-tab+.ant-tabs-tab {
margin-left: 15px;
}
.monaco-editor .find-widget .monaco-inputbox.synthetic-focus{
outline-color: var(--primary-color) !important;
@@ -8,9 +8,8 @@ import { App, Button } from 'antd'
import { EntityItem } from '@common/const/type.ts'
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
import { useNavigate, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { $t } from '@common/locales'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
const ServiceInsideDocument = () => {
const { message } = App.useApp()
const [updater, setUpdater] = useState<string>()
@@ -19,8 +18,6 @@ const ServiceInsideDocument = () => {
const [doc, setDoc] = useState<string>()
const { fetchData } = useFetch()
const { serviceId, teamId } = useParams<RouterParams>()
const { setBreadcrumb } = useBreadcrumb()
const navigator = useNavigate()
const save = () => {
fetchData<
@@ -80,15 +77,6 @@ const ServiceInsideDocument = () => {
}
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('使用说明')
}
])
getServiceDoc()
}, [])
@@ -9,11 +9,12 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
import { AiServiceConfigFieldType } from '@core/const/ai-service/type.ts'
import { App, Menu, MenuProps } from 'antd'
import { ItemType, MenuItemGroupType, MenuItemType } from 'antd/es/menu/interface'
import Paragraph from 'antd/es/typography/Paragraph'
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
import { cloneDeep } from 'lodash-es'
import { FC, useEffect, useMemo, useState } from 'react'
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
import { useAiServiceContext } from '../../contexts/AiServiceContext.tsx'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
const APP_MODE = import.meta.env.VITE_APP_MODE
const AiServiceInsidePage: FC = () => {
@@ -27,6 +28,7 @@ const AiServiceInsidePage: FC = () => {
const [activeMenu, setActiveMenu] = useState<string>()
const navigateTo = useNavigate()
const [showMenu, setShowMenu] = useState<boolean>(false)
const { setBreadcrumb } = useBreadcrumb()
const getAiServiceInfo = () => {
fetchData<BasicResponse<{ service: AiServiceConfigFieldType }>>('service/info', {
@@ -67,6 +69,7 @@ const AiServiceInsidePage: FC = () => {
'assets',
null,
[
getItem(<Link to="./overview">{$t('总览')}</Link>, 'overview', undefined, undefined, undefined, ''),
getItem(
<Link to="./route">{$t('API 路由')}</Link>,
'route',
@@ -149,7 +152,8 @@ const AiServiceInsidePage: FC = () => {
'project.myAiService.topology.view'
)
: null,
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, '')
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, ''),
getItem(<Link to="./logs">{$t('日志')}</Link>, 'logs', undefined, undefined, undefined, '')
],
'group'
)
@@ -202,7 +206,7 @@ const AiServiceInsidePage: FC = () => {
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
} else {
setActiveMenu('route')
setActiveMenu('overview')
}
}, [currentUrl])
@@ -213,10 +217,19 @@ const AiServiceInsidePage: FC = () => {
}, [accessData])
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigateTo('/service/list')
},
{
title: aiServiceInfo?.name || ''
}
])
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
navigateTo(`/service/${teamId}/aiInside/${serviceId}/${activeMenu}`)
}
}, [activeMenu])
}, [activeMenu, state.language, aiServiceInfo])
useEffect(() => {
serviceId && getAiServiceInfo()
@@ -231,17 +244,8 @@ const AiServiceInsidePage: FC = () => {
{showMenu ? (
<InsidePage
pageTitle={aiServiceInfo?.name || '-'}
tagList={[
...(aiServiceInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
{
label: (
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
{$t('服务 ID')}{serviceId || '-'}
</Paragraph>
)
}
]}
backUrl="/service/list"
customBanner={<ServiceInfoCard serviceId={serviceId} teamId={teamId} />}
>
<div className="flex flex-1 h-full">
<Menu
@@ -1,8 +1,7 @@
import {ActionType} from "@ant-design/pro-components";
import {FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
import {Link, useNavigate, useParams} from "react-router-dom";
import {useParams} from "react-router-dom";
import {App, Form,TreeSelect} from "antd";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {useFetch} from "@common/hooks/http.ts";
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
import {BasicResponse, COLUMNS_TITLE, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE} from "@common/const/const.tsx";
@@ -18,7 +17,6 @@ import { checkAccess } from "@common/utils/permission.ts";
import { $t } from "@common/locales/index.ts";
const AiServiceInsideSubscriber:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const { modal,message } = App.useApp()
const {fetchData} = useFetch()
const {serviceId, teamId} = useParams<RouterParams>()
@@ -26,7 +24,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
const pageListRef = useRef<ActionType>(null);
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
const {accessData,state} = useGlobalContext()
const navigator = useNavigate()
const getAiServiceSubscriber = ()=>{
return fetchData<BasicResponse<{subscribers:AiServiceSubscriberTableListItem[]}>>('service/subscribers',{method:'GET',eoParams:{service:serviceId,team:teamId},eoTransformKeys:['apply_time']}).then(response=>{
const {code,data,msg} = response
@@ -120,15 +117,6 @@ const AiServiceInsideSubscriber:FC = ()=>{
]
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title:$t('订阅方管理')
}
])
getMemberList()
manualReloadTable()
}, [serviceId]);
@@ -6,33 +6,21 @@ import { LoadingOutlined } from '@ant-design/icons'
import EmptySVG from '@common/assets/empty.svg'
import { $t } from '@common/locales/index.ts'
import ApiDocument from '@common/components/aoplatform/ApiDocument.tsx'
import { useNavigate, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
import {
AiServiceInsideApiDocumentHandle,
AiServiceInsideApiDocumentProps,
AiServiceApiDetail
} from '@core/const/ai-service/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
const AiServiceInsideApiDocument = forwardRef<AiServiceInsideApiDocumentHandle, AiServiceInsideApiDocumentProps>(() => {
const { serviceId, teamId } = useParams<RouterParams>()
const { fetchData } = useFetch()
const [apiDetail, setApiDetail] = useState<AiServiceApiDetail>()
const [loading, setLoading] = useState<boolean>(false)
const { setBreadcrumb } = useBreadcrumb()
const navigator = useNavigate()
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('API 文档')
}
])
getApiDetail()
}, [])
@@ -291,19 +291,6 @@ const AiServiceInsideRouterCreate = () => {
}
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title:$t('API'),
onClick: () => navigator(backUrl)
},
{
title: routeId ? $t('编辑 API') : $t('添加 API')
}
])
!routeId && aiServiceInfo?.provider && getDefaultModelConfig()
}, [aiServiceInfo])
@@ -3,7 +3,6 @@ import PageList, { PageProColumns } from '@common/components/aoplatform/PageList
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import { SimpleMemberItem } from '@common/const/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import { useFetch } from '@common/hooks/http.ts'
import { $t } from '@common/locales/index.ts'
@@ -17,7 +16,6 @@ import { Link, useNavigate, useParams } from 'react-router-dom'
const AiServiceInsideRouterList: FC = () => {
const [searchWord, setSearchWord] = useState<string>('')
const { setBreadcrumb } = useBreadcrumb()
const { modal, message } = App.useApp()
const [tableListDataSource, setTableListDataSource] = useState<AiServiceRouterTableListItem[]>([])
const [tableHttpReload, setTableHttpReload] = useState(true)
@@ -162,17 +160,6 @@ const AiServiceInsideRouterList: FC = () => {
getMemberList()
manualReloadTable()
}, [serviceId])
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('路由')
}
])
}, [state.language])
const columns = useMemo(() => {
return [...AI_SERVICE_ROUTER_TABLE_COLUMNS].map((x) => {
@@ -3,7 +3,6 @@ import {ActionType} from "@ant-design/pro-components";
import {FC, useEffect, useMemo, useRef, useState} from "react";
import {Link, useLocation, useNavigate, useParams} from "react-router-dom";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {App, Button} from "antd";
import {
SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN,
@@ -26,7 +25,6 @@ import { SubscribeApprovalInfoType } from "@common/const/approval/type.tsx";
import { $t } from "@common/locales";
const AiServiceInsideApprovalList:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const { modal,message } = App.useApp()
const {serviceId, teamId} = useParams<RouterParams>();
const [init, setInit] = useState<boolean>(true)
@@ -40,7 +38,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
const [approvalBtnLoading,setApprovalBtnLoading] = useState<boolean>(false)
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
const {accessData,state} = useGlobalContext()
const navigator = useNavigate()
const openModal = async (type:'approval'|'view',entity:SubscribeApprovalTableListItem)=>{
message.loading($t(RESPONSE_TIPS.loading))
@@ -142,15 +139,6 @@ const AiServiceInsideApprovalList:FC = ()=>{
}, [query]);
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title:$t('订阅审核')
}
])
getMemberList()
manualReloadTable()
}, [serviceId]);
@@ -2,13 +2,11 @@
import { Tabs } from "antd"
import { useState, useEffect, FC, useMemo } from "react"
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
import { SYSTEM_PUBLISH_TAB_ITEMS } from "../../../const/system/const"
import { $t } from "@common/locales"
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
const AiServiceInsidePublic:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const query =new URLSearchParams(useLocation().search)
const location = useLocation()
const currentUrl = location.pathname
@@ -25,18 +23,6 @@ const AiServiceInsidePublic:FC = ()=>{
setPageStatus(Number(query.get('status') ||0) as 0|1)
}, [currentUrl]);
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigateTo('/service/list')
},
{
title:$t('发布')
}
])
}, []);
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
return (
<>
@@ -9,7 +9,6 @@ import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_IN
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { SimpleMemberItem } from "@common/const/type.ts";
import { MemberTableListItem } from "../../../const/member/type";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
import { useFetch } from "@common/hooks/http";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { AiServicePublishReleaseItem } from "../../../const/system/type";
@@ -23,7 +22,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
import { $t } from "@common/locales";
const AiServiceInsidePublicList:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const { modal,message } = App.useApp()
const pageListRef = useRef<ActionType>(null);
const [tableHttpReload, setTableHttpReload] = useState(true);
@@ -45,7 +43,6 @@ const AiServiceInsidePublicList:FC = ()=>{
const [drawerData, setDrawerData] = useState<PublishTableListItem|PublishVersionTableListItem >({} as PublishTableListItem)
const [drawerOkTitle, setDrawerOkTitle] = useState<string>('确认')
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
const navigator = useNavigate()
const getAiServicePublishList = (params?: ParamsType & {
pageSize?: number | undefined;
current?: number | undefined;
@@ -351,15 +348,6 @@ const AiServiceInsidePublicList:FC = ()=>{
]
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('发布')
}
])
getMemberList()
manualReloadTable()
}, [serviceId]);
@@ -39,6 +39,7 @@ const ApiKeyContent: React.FC<ApiKeyContentProps> = forwardRef(({ provider, enti
const handleOk = async () => {
try {
// 表单校验
const values = await form.validateFields()
const { expire_time, ...restValues } = values
const expireTime = neverExpire ? 0 : Math.trunc(expire_time.valueOf() / 1000)
@@ -68,7 +68,7 @@ const LogSettings = () => {
<>
<Skeleton className="m-btnbase w-calc-100vw-minus-padding-r" active loading={loading}>
<InsidePage
pageTitle={$t('日志置')}
pageTitle={$t('日志输出设置')}
description={'APIPark ' + $t('提供详尽的 API 调用日志,帮助企业监控、分析和审计 API 的运行状况。')}
>
<div className="flex h-full">
@@ -1,11 +1,15 @@
import EditableTable from "@common/components/aoplatform/EditableTable"
import WithPermission from "@common/components/aoplatform/WithPermission"
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from "@common/const/const"
import { useFetch } from "@common/hooks/http"
import { $t } from "@common/locales"
import { PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS, PartitionDataLogConfigFieldType, PartitionDataLogHeaderListFieldType } from "@core/const/partitions/types"
import { Button, Form, Input, message } from "antd"
import { useEffect } from "react"
import EditableTable from '@common/components/aoplatform/EditableTable'
import WithPermission from '@common/components/aoplatform/WithPermission'
import { BasicResponse, PLACEHOLDER, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import {
PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS,
PartitionDataLogConfigFieldType,
PartitionDataLogHeaderListFieldType
} from '@core/const/partitions/types'
import { Button, Form, Input, message, Select } from 'antd'
import { useEffect } from 'react'
export type DashboardPageShowStatus = 'view' | 'edit'
export type DashboardSettingEditProps = {
@@ -15,7 +19,7 @@ export type DashboardSettingEditProps = {
}
const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
const { changeStatus, refreshData, data } = props
const [form] = Form.useForm();
const [form] = Form.useForm()
const { fetchData } = useFetch()
const onFinish = () => {
@@ -23,10 +27,16 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
const formData = {
config: {
url: value.url,
headers: value.headers.filter((item: PartitionDataLogHeaderListFieldType) => item.key).map((item: PartitionDataLogHeaderListFieldType) => ({key:item.key, value:item.value || ''}))
headers: value.headers
.filter((item: PartitionDataLogHeaderListFieldType) => item.key)
.map((item: PartitionDataLogHeaderListFieldType) => ({ key: item.key, value: item.value || '' }))
}
}
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', { method: 'POST', body: JSON.stringify(formData), eoParams: {} }).then(response => {
fetchData<BasicResponse<{ info: PartitionDataLogConfigFieldType }>>('log/loki', {
method: 'POST',
body: JSON.stringify(formData),
eoParams: {}
}).then((response) => {
const { code, msg } = response
if (code === STATUS_CODE.SUCCESS) {
message.success(msg || $t('操作成功,即将刷新页面'))
@@ -38,15 +48,26 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
})
}
useEffect(() => { form.setFieldsValue(data) }, [data])
useEffect(() => {
form.setFieldsValue({
...data,
headers: data?.headers?.length ? data.headers : [
{
key: '',
value: ''
}
],
driver: 'loki'
})
}, [data])
useEffect(() => {
return (form.setFieldsValue({}))
}, []);
return form.setFieldsValue({})
}, [])
return (
<>
<div className="overflow-auto h-full">
<WithPermission access={''} >
<WithPermission access={''}>
<Form
form={form}
className="mx-auto flex flex-col justify-between h-full"
@@ -55,23 +76,29 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
autoComplete="off"
>
<Form.Item<PartitionDataLogConfigFieldType>
label={$t("请求前缀")}
name="url"
label={$t('数据源类型')}
name="driver"
rules={[{ required: true }]}
>
<Select
showSearch
optionFilterProp="label"
className="w-INPUT_NORMAL"
placeholder={$t(PLACEHOLDER.select)}
options={[{ label: 'Loki', value: 'loki' }]}
></Select>
</Form.Item>
<Form.Item<PartitionDataLogConfigFieldType> label={$t('请求前缀')} name="url" rules={[{ required: true }]}>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item<PartitionDataLogConfigFieldType>
label={$t("HTTP 头部")}
name="headers"
>
<Form.Item<PartitionDataLogConfigFieldType> label={$t('HTTP 头部')} name="headers">
<EditableTable<PartitionDataLogConfigFieldType & { _id: string }>
configFields={PARTITION_DATA_LOG_CONFIG_TABLE_COLUMNS}
/>
</Form.Item>
<div className="flex gap-btnbase">
<WithPermission access='system.devops.data_source.edit'>
<WithPermission access="system.devops.data_source.edit">
<Button type="primary" htmlType="submit">
{$t('保存')}
</Button>
@@ -84,7 +111,7 @@ const DataLogSettingEdit = (props: DashboardSettingEditProps) => {
</WithPermission>
</div>
</>
);
)
}
export default DataLogSettingEdit;
export default DataLogSettingEdit
@@ -161,7 +161,7 @@ const PartitionInsideDashboardSetting: FC = () => {
className="overflow-hidden mt-[30px] w-full max-h-full flex flex-col justify-between"
title={
<div>
<span className="text-MAIN_TEXT my-btnybase mr-btnbase"> {$t('数据日志')}</span>
<span className="text-MAIN_TEXT my-btnybase mr-btnbase"> {$t('请求日志')}</span>
{!dataLogLoading && !dataLogData && <Tag color="#f50">{$t('未配置')}</Tag>}
</div>
}
@@ -220,6 +220,11 @@ export function DataLogConfigPreview(x: PartitionDataLogConfigFieldType) {
return (
<div className="flex flex-col gap-[4px] ">
<Row className="">
<Col className="font-bold text-right pr-[4px]">{$t('数据源')}</Col>
{/* 先写死,或许会有选择列表,但现在可以不用 */}
<Col>Loki</Col>
</Row>
<Row className="">
<Col className="font-bold text-right pr-[4px]">{$t('请求前缀')}</Col>
<Col>{x?.url}</Col>
@@ -1,25 +1,13 @@
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
import { useEffect } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import { $t } from '@common/locales'
export default function ServicePolicyLayout() {
const location = useLocation()
const pathName = location.pathname
const navigator = useNavigate()
const { setBreadcrumb } = useBreadcrumb()
useEffect(() => {
const tmpPath = pathName.split('/')
if (tmpPath[tmpPath.length - 1] === 'servicepolicy') {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('服务策略')
}
])
navigator('datamasking/list')
}
}, [pathName])
@@ -80,19 +80,6 @@ const DataMaskingConfig = forwardRef<DataMaskingConfigHandle>((_,ref) => {
};
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title:$t('服务策略'),
onClick: () => navigator(serviceId ? `/service/${teamId}/aiInside/${serviceId}/servicepolicy` : '')
},
{
title: policyId !== undefined ? $t('编辑服务策略') : $t('添加服务策略')
}
])
if (policyId !== undefined) {
setOnEdit(true);
getPolicyInfo();
@@ -0,0 +1,6 @@
import ServiceLogs from "./ServiceLogs"
const AiServiceLogsContainer = () => {
return <ServiceLogs serviceType="aiService" />
}
export default AiServiceLogsContainer
@@ -0,0 +1,89 @@
import { IconButton } from '@common/components/postcat/api/IconButton'
import useCopyToClipboard from '@common/hooks/copy'
import { RESPONSE_TIPS } from '@common/const/const'
import { $t } from '@common/locales/index.ts'
import { App } from 'antd'
import ReactJson from 'react-json-view'
const ApiNetWorkDataPreview = ({ configContent = {} }: { configContent?: { [key: string]: string | undefined } }) => {
/** 复制组件 */
const { copyToClipboard } = useCopyToClipboard()
/** 弹窗组件 */
const { message } = App.useApp()
/**
*
* @param value
* @returns
*/
const handleCopy = async (value: string): Promise<void> => {
if (value) {
copyToClipboard(value)
message.success($t(RESPONSE_TIPS.copySuccess))
}
}
/**
* JSON对象字符串
*/
const isJsonString = (str: string): boolean => {
try {
const parsed = JSON.parse(str)
return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
} catch (e) {
return false
}
}
return (
<>
{Object.keys(configContent).filter((item) => !!configContent[item]).map((item) => {
return (
<div className="overflow-auto mb-[15px]">
<div className="font-semibold text-[16px] mb-[10px]">{item}</div>
<div className="bg-[#0a0b21] text-white p-4 rounded-md my-2 font-mono text-sm overflow-auto relative">
{!configContent[item] ? (
<pre className="whitespace-pre-wrap break-words"></pre>
) : isJsonString(configContent[item] || '') ? (
// 如果是有效的JSON对象字符串,使用ReactJson渲染
<ReactJson
src={JSON.parse(configContent[item] || '')}
theme="monokai"
indentWidth={2}
displayDataTypes={false}
displayObjectSize={false}
name={false}
collapsed={false}
enableClipboard={false}
style={{
backgroundColor: 'transparent',
wordBreak: 'break-word',
whiteSpace: 'normal'
}}
/>
) : (
// 如果是普通字符串,直接用pre渲染
<pre className="whitespace-pre-wrap break-words my-[8px]">{configContent[item]}</pre>
)}
<IconButton
name="copy"
onClick={() => handleCopy(configContent[item] || '')}
sx={{
position: 'absolute',
top: '5px',
right: '5px',
color: '#999',
transition: 'none',
'&.MuiButtonBase-root:hover': {
background: 'transparent',
color: '#3D46F2',
transition: 'none'
}
}}
></IconButton>
</div>
</div>
)
})}
</>
)
}
export default ApiNetWorkDataPreview
@@ -0,0 +1,362 @@
import { Descriptions, DescriptionsProps, Spin, Tabs, Tooltip, message } from 'antd'
import { useEffect, useMemo, useState } from 'react'
import { $t } from '@common/locales/index.ts'
import React from 'react'
import { ExclamationCircleOutlined, LoadingOutlined } from '@ant-design/icons'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import ApiNetWorkDataPreview from './ApiNetWorkDataPreview'
import { LogItem } from './ServiceLogs'
import { useFetch } from '@common/hooks/http'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
// 定义状态码颜色映射枚举
export enum HttpStatusColor {
SUCCESS = '#7EC26A',
CLIENT_ERROR = '#F2CF59',
SERVER_ERROR = '#f80f34'
}
type LogDetailProps = {
selectedRow?: LogItem
serviceType: 'aiService' | 'restService'
serviceId?: string
teamId?: string
}
type AIServiceDetailType = {
id: string
api: {
id: string
name: string
}
logTime: string
consumer: {
id: string
name: string
}
isSystemConsumer: boolean
status: string
provider: {
id: string
name: string
}
model: string
ip: string
request: {
header: string
body: string
origin: string
token: number
}
response: {
header: string
body: string
origin: string
token: string
}
}
type RestServiceDetailType = {
id: string
api: {
id: string
name: string
}
logTime: string
consumer: {
id: string
name: string
}
isSystemConsumer: boolean
status: string
ip: string
request: {
header: string
origin: string
}
response: {
header: string
origin: string
}
}
const LogDetail = ({ selectedRow, serviceType, serviceId, teamId }: LogDetailProps) => {
/** 顶部描述 */
const [descriptionItems, setDescriptionItems] = useState<DescriptionsProps['items']>()
/** 全局状态 */
const { state } = useGlobalContext()
/** Request 标签页数据 */
const [requestInfoData, setRequestInfoData] = useState<{ [key: string]: string | undefined }>()
/** Response 标签页数据 */
const [responseInfoData, setResponseInfoData] = useState<{ [key: string]: string | undefined }>()
/** 面板 loading */
const [dashboardLoading, setDashboardLoading] = useState(true)
/**
*
*/
const { fetchData } = useFetch()
/**
*
* @param status
* @returns
*/
const renderStatusWithColor = (status: string) => {
// 获取状态码首位数字
const firstDigit = String(status).charAt(0)
let color = ''
switch (firstDigit) {
case '2':
color = HttpStatusColor.SUCCESS
break
case '4':
color = HttpStatusColor.CLIENT_ERROR
break
case '5':
color = HttpStatusColor.SERVER_ERROR
break
default:
break
}
return color ? <span style={{ color }}>{status}</span> : status
}
/**
*
*/
const tabItems = useMemo(
() => [
{
key: 'request',
label: 'Request',
children: <ApiNetWorkDataPreview configContent={requestInfoData} />
},
{
key: 'response',
label: 'Response',
children: <ApiNetWorkDataPreview configContent={responseInfoData} />
}
],
[state.language, requestInfoData, responseInfoData]
)
/**
* AI
*/
const getAIServiceDescriptionItemsList = ({
time,
api,
consumer,
status,
model,
ip
}: {
time: string
api: string
consumer: string
status: string
model: string
ip: string
}) => {
setDescriptionItems([
{
key: 'time',
label: $t('时间戳'),
children: time
},
{
key: 'api',
label: $t('API / Tools'),
children: api
},
{
key: 'consumer',
label: $t('消费者'),
children: consumer
},
{
key: 'httpStatus',
label: $t('HTTP 状态'),
children: renderStatusWithColor(status)
},
{
key: 'model',
label: $t('模型'),
children: model
},
{
key: 'ip',
label: $t('IP'),
children: ip
}
])
}
/**
* REST
*/
const getRestServiceDescriptionItemsList = ({
time,
api,
consumer,
isSystemConsumer,
status,
ip
}: {
time: string
api: string
consumer: string
isSystemConsumer?: boolean
status: string
ip: string
}) => {
setDescriptionItems([
{
key: 'time',
label: $t('时间戳'),
children: time
},
{
key: 'api',
label: $t('API / Tools'),
children: api
},
{
key: 'consumer',
label: $t('消费者'),
children: (
<>
<span className="mr-[50px]">{consumer}</span>
{isSystemConsumer && (
<span>
<span>System-level API Key</span>
<Tooltip title={$t('通过系统级别的 API Key 来调用')}>
<span className="ml-[12px] items-center">
<ExclamationCircleOutlined className="text-[14px] h-[14px] w-[14px]" />
</span>
</Tooltip>
</span>
)}
</>
)
},
{
key: 'httpStatus',
label: $t('HTTP 状态'),
children: renderStatusWithColor(status)
},
{
key: 'ip',
label: $t('IP'),
children: ip
}
])
}
/**
* AI
*/
const getAIServiceLogDetail = () => {
fetchData<BasicResponse<{ log: AIServiceDetailType }>>('service/log/ai', {
method: 'GET',
eoParams: { log: selectedRow?.id, service: serviceId, team: teamId },
eoTransformKeys: ['is_system_consumer', 'log_time']
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const result = data.log
getAIServiceDescriptionItemsList({
time: result.logTime,
api: result.api.name,
consumer: result.consumer.name,
status: result.status,
model: result.model,
ip: result.ip
})
setRequestInfoData({
Header: result.request.header,
Body: result.request.body
})
setResponseInfoData({
Header: result.response.header,
Body: result.response.body
})
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
setDashboardLoading(false)
})
}
/**
* REST
*/
const getRestServiceLogDetail = () => {
fetchData<BasicResponse<{ log: RestServiceDetailType }>>('service/log/rest', {
method: 'GET',
eoParams: { log: selectedRow?.id, service: serviceId, team: teamId },
eoTransformKeys: ['is_system_consumer', 'log_time']
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const result = data.log
getRestServiceDescriptionItemsList({
time: result.logTime,
api: result.api.name,
consumer: result.consumer.name,
status: result.status,
ip: result.ip,
isSystemConsumer: result.isSystemConsumer
})
setRequestInfoData({
Header: result.request.header,
Body: result.request.body
})
setResponseInfoData({
Header: result.response.header,
Body: result.response.body
})
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
setDashboardLoading(false)
})
}
useEffect(() => {
setDashboardLoading(true)
serviceType === 'aiService' ? getAIServiceLogDetail() : getRestServiceLogDetail()
}, [serviceType])
return (
<Spin
className="h-full pb-[20px]"
wrapperClassName="h-full min-h-[150px]"
indicator={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ transform: 'scale(1.5)' }}>
<LoadingOutlined style={{ fontSize: 30 }} spin />
</div>
</div>
}
spinning={dashboardLoading}
>
<Descriptions
column={1}
className="[&_.ant-descriptions-item]:p-0 [&_.ant-descriptions-item]:py-[5px]"
colon={false}
items={descriptionItems}
classNames={{
label: 'w-[250px] text-right pr-[12px]'
}}
contentStyle={{ fontWeight: '600' }}
/>
<div className="mt-[5px] service-log-tab">
<Tabs
className="overflow-hidden h-full [&>.ant-tabs-content-holder]:overflow-auto global-policy-tabs"
items={tabItems}
/>
</div>
</Spin>
)
}
export default LogDetail
@@ -0,0 +1,7 @@
import ServiceLogs from "./ServiceLogs"
const RestServiceLogsContainer = () => {
return <ServiceLogs serviceType="restService" />
}
export default RestServiceLogsContainer
@@ -0,0 +1,263 @@
import { LoadingOutlined } from '@ant-design/icons'
import { Drawer, Spin, message } from 'antd'
import { useEffect, useMemo, useRef, useState } from 'react'
import DateSelectFilter, { TimeOption } from '../serviceOverview/filter/DateSelectFilter'
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
import PageList from '@common/components/aoplatform/PageList'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { REST_SERVICE_LOG_LIST, AI_SERVICE_LOG_LIST } from '@core/const/system/const'
import { $t } from '@common/locales/index.ts'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { useFetch } from '@common/hooks/http'
import LogDetail, { HttpStatusColor } from './LogDetail'
import { useParams } from 'react-router-dom'
import { ActionType, ParamsType } from '@ant-design/pro-components'
import { getTime } from '@dashboard/utils/dashboard'
export type LogItem = {
id: string
api: {
id: string
name: string
}
status: number
logTime: string
responseTime: string
token?: number
model?: string
tokenPerSecond?: string
traffic?: string
consumers?: {
id: string
name: string
}
provider?: {
id: string
name: string
}
}
const ServiceLogs = ({ serviceType }: { serviceType: 'aiService' | 'restService' }) => {
/** 路由参数 */
const { serviceId, teamId } = useParams<{ serviceId: string; teamId: string }>()
/** 面板 loading */
const [dashboardLoading, setDashboardLoading] = useState(true)
/** 当前选中的时间范围 */
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
/** 默认时间 */
const [defaultTime] = useState<TimeOption>('day')
/** 全局状态 */
const { state } = useGlobalContext()
/**
*
*/
const { fetchData } = useFetch()
// 打开侧边弹窗
const [drawerOpen, setDrawerOpen] = useState<boolean>(false)
/** 选中的行 */
const [selectedRow, setSelectedRow] = useState<LogItem>()
/**
* ref
*/
const pageListRef = useRef<ActionType>(null)
/** 列 */
const columns = useMemo(() => {
return [...(serviceType === 'aiService' ? AI_SERVICE_LOG_LIST : REST_SERVICE_LOG_LIST)].map((x) => {
if (x.dataIndex === 'status') {
x.render = (text: any, record: any) => (
<>
<div className="w-full">{renderStatusWithColor(record.status)}</div>
</>
)
}
return {
...x,
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
}
})
}, [state.language])
/**
*
* @param status
* @returns
*/
const renderStatusWithColor = (status: string | number) => {
// 获取状态码首位数字
const firstDigit = status.toString().charAt(0)
let color = ''
switch (firstDigit) {
case '2':
color = HttpStatusColor.SUCCESS
break
case '4':
color = HttpStatusColor.CLIENT_ERROR
break
case '5':
color = HttpStatusColor.SERVER_ERROR
break
default:
break
}
return color ? <span style={{ color }}>{status}</span> : status
}
/**
* AI
* @param dataType
* @returns
*/
const getAiServiceLogList = (
params: ParamsType & {
pageSize?: number | undefined
current?: number | undefined
keyword?: string | undefined
}
) => {
return fetchData<BasicResponse<{ log: LogItem[] }>>(`service/logs/ai`, {
method: 'GET',
eoParams: {
service: serviceId,
team: teamId,
start: timeRange?.start,
end: timeRange?.end,
page: params?.current,
page_size:params?.pageSize
},
eoTransformKeys: ['log_time', 'response_time', 'token_per_second']
})
.then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 保存数据
return {
data: data.logs,
total: data.total,
success: true
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return { data: [], success: false }
}
})
.catch(() => {
return { data: [], success: false }
})
}
/**
* REST
* @param dataType
* @returns
*/
const getRestServiceLogList = (
params: ParamsType & {
pageSize?: number | undefined
current?: number | undefined
keyword?: string | undefined
}
) => {
console.log('params===', params)
return fetchData<BasicResponse<{ log: LogItem[] }>>(`service/logs/rest`, {
method: 'GET',
eoParams: {
service: serviceId,
team: teamId,
start: timeRange?.start,
end: timeRange?.end,
page: params?.current,
page_size:params?.pageSize
},
eoTransformKeys: ['log_time', 'response_time', 'token_per_second']
})
.then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 保存数据
return {
data: data.logs,
total: data.total,
success: true
}
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
return { data: [], success: false }
}
})
.catch(() => {
return { data: [], success: false }
})
}
useEffect(() => {
const { startTime, endTime } = getTime(defaultTime, [])
setTimeRange({
start: startTime,
end: endTime
})
}, [])
useEffect(() => {
if (timeRange) {
pageListRef.current?.reload()
}
}, [timeRange])
/** 行点击 */
const handleRowClick = (record: LogItem) => {
setSelectedRow(record)
setDrawerOpen(true)
}
/** 时间选择回调 */
const selectCallback = (date: TimeRange) => {
setTimeRange(date)
}
useEffect(() => {
setDashboardLoading(false)
}, [])
return (
<Spin
className="h-full pb-[20px]"
wrapperClassName="h-full min-h-[150px] overflow-hidden"
indicator={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ transform: 'scale(1.5)' }}>
<LoadingOutlined style={{ fontSize: 30 }} spin />
</div>
</div>
}
spinning={dashboardLoading}
>
<div className="h-full mr-PAGE_INSIDE_X">
<DateSelectFilter selectCallback={selectCallback} customClassNames={'pt-[0px]'} defaultTime={defaultTime} />
<div style={{ height: 'calc(100% - -3px)' }} className="mt-[20px] overflow-hidden">
<PageList
ref={pageListRef}
id={`${serviceType}_logs`}
columns={[...columns]}
request={async (
params: ParamsType & {
pageSize?: number | undefined
current?: number | undefined
keyword?: string | undefined
}
) => (serviceType === 'aiService' ? getAiServiceLogList(params) : getRestServiceLogList(params))}
onRowClick={(row: LogItem) => handleRowClick(row)}
/>
</div>
<Drawer
destroyOnClose={true}
maskClosable={false}
title={$t('日志详情')}
width={'40%'}
onClose={() => setDrawerOpen(false)}
open={drawerOpen}
>
<LogDetail selectedRow={selectedRow} serviceId={serviceId} teamId={teamId} serviceType={serviceType} />
</Drawer>
</div>
</Spin>
)
}
export default ServiceLogs
@@ -0,0 +1,7 @@
import ServiceOverview from "./serviceOverview"
const AiServiceContainer = () => {
return <ServiceOverview serviceType="aiService" />
}
export default AiServiceContainer
@@ -0,0 +1,12 @@
import { FC } from 'react'
import ServiceOverview from './serviceOverview'
const RestServiceContainer: FC = () => {
return (
<>
<ServiceOverview serviceType="restService" />
</>
)
}
export default RestServiceContainer
@@ -0,0 +1,278 @@
import { useEffect, useRef, useState } from 'react'
import ECharts, { EChartsOption } from 'echarts-for-react'
import { $t } from '@common/locales'
type AreaChartInfo = {
title: string
value: string
date: string[]
data: number[]
max: string
min: string
originValue?: number
showXAxis?: boolean
}
type ServiceAreaCharProps = {
customClassNames?: string
dataInfo?: AreaChartInfo
height?: number
showAvgLine?: boolean
customMarkLineValue?: number
}
const ServiceAreaChart = ({ customClassNames, dataInfo, height, showAvgLine, customMarkLineValue }: ServiceAreaCharProps) => {
const chartRef = useRef<ECharts>(null)
const [option, setOption] = useState<EChartsOption | undefined>({})
const [hasData, setHasData] = useState(true)
const setChartOption = (dataInfo: AreaChartInfo) => {
const dataExists = dataInfo.data && dataInfo.data.length > 0
// 更新hasData状态
setHasData(dataExists)
const option = {
tooltip: dataExists ? {
trigger: 'axis',
formatter: function (value: any) {
// 如果是数组,取第一个参数的name
const param = Array.isArray(value) ? value[0] : value
let tooltipContent = `<div style="min-width:140px;padding:8px;">`
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${param.color};"></span>`
tooltipContent += `<div style="margin-top: 8px; display: flex; justify-content: space-between; align-items: center;">
<div style="margin-right: 4px;">${marker}</div>${param.name} <div style="font-weight:bold; margin-left: 20px;">${param.value}</div>
</div>`
tooltipContent += '</div>'
return tooltipContent
}
} : {
show: false // 没有数据时不显示tooltip
},
title: [
{
text: '{titleStyle|' + $t(dataInfo.title) + '}\n\n{valueStyle|' + dataInfo.value + '}',
left: '2%',
top: '0',
textStyle: {
rich: {
titleStyle: {
fontSize: 14,
color: '#999999',
fontWeight: 'normal',
lineHeight: 20
},
valueStyle: {
fontSize: 32,
color: '#101010',
fontWeight: 500,
lineHeight: 40
}
}
}
}
],
toolbox: {
show: false
},
grid: {
left: '3%',
right: '3%',
bottom: '0%',
top: '110px',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: dataInfo.date,
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#ccc'
}
},
show: false
},
yAxis: {
type: 'value',
boundaryGap: [0, '5%'],
show: dataExists, // 没有数据时不显示Y轴
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: false
}
},
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
// dataZoom: [
// {
// type: 'inside', // 内置的数据区域缩放组件(使用鼠标滚轮缩放)
// xAxisIndex: 0, // 设置缩放作用在第一个x轴
// filterMode: 'filter',
// start: 0,
// end: 100
// },
// {
// type: 'slider', // 滑动条型数据区域缩放组件
// xAxisIndex: 0,
// filterMode: 'filter',
// height: 20,
// bottom: 0,
// start: 0,
// end: 100,
// handleIcon:
// 'path://M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
// handleSize: '80%',
// handleStyle: {
// color: '#fff',
// shadowBlur: 3,
// shadowColor: 'rgba(0, 0, 0, 0.6)',
// shadowOffsetX: 2,
// shadowOffsetY: 2
// },
// show: false // 默认隐藏底部的滑动条,可以改为 true 显示
// }
// ],
// 添加空状态提示
silent: !dataExists,
graphic: !dataExists
? [
{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: $t('暂无数据'),
fontSize: 14,
fill: '#999'
}
}
]
: [],
series: [
{
name: dataInfo.title,
type: 'line',
symbol: 'none',
sampling: 'lttb',
itemStyle: {
color: 'rgb(255, 70, 131)'
},
markLine: showAvgLine ? {
silent: false,
symbol: 'none',
lineStyle: {
width: 1,
type: 'dashed'
},
label: {
show: false,
position: 'insideEndTop',
formatter: '{c}',
color: '#000',
fontSize: 10,
backgroundColor: 'transparent',
padding: [10, 4],
borderRadius: 2,
distance: -5
},
emphasis: {
lineStyle: {
width: 1 // 保持线条宽度不变,禁用默认的悬停加粗
},
label: {
show: false // 悬停时不显示标签
}
},
data: dataInfo?.originValue !== undefined ?
[{ yAxis: dataInfo?.originValue, name: '自定义值' }] :
[{ type: 'average', name: 'Avg' }]
} : undefined,
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: 'rgb(255, 158, 68)'
},
{
offset: 1,
color: 'rgb(255, 70, 131)'
}
]
}
},
data: dataInfo.data
}
]
}
const echartsInstance = chartRef.current?.getEchartsInstance()
if (echartsInstance) {
echartsInstance.setOption(option)
}
}
// 使用深度监听来确保图表数据更新
useEffect(() => {
if (!dataInfo) return
// 直接获取 ECharts 实例并设置选项
const echartsInstance = chartRef.current?.getEchartsInstance()
if (echartsInstance) {
// 清除已有的图表
echartsInstance.clear()
// 重新设置选项
setChartOption(dataInfo)
}
}, [dataInfo, JSON.stringify(dataInfo)])
// 添加窗口大小变化监听,实现自适应
useEffect(() => {
// 定义resize处理函数
const handleResize = () => {
const echartsInstance = chartRef.current?.getEchartsInstance()
if (echartsInstance) {
echartsInstance.resize()
}
}
// 添加监听
window.addEventListener('resize', handleResize)
// 组件卸载时移除监听
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return (
<div className={`w-full ${customClassNames}`}>
<div className="absolute top-[10px] left-[10px] w-full">
<div className="relative top-[5px]">
<div className="absolute top-[23px] right-[5%] grid grid-cols-[auto_auto] justify-items-end">
<div className="flex justify-center items-center">
<span className="text-[#FE564D] text-[9px]"></span>
</div>
<span className="ml-1 text-right">{dataInfo?.max}</span>
<div className="flex justify-center items-center">
<span className="text-[#27B148] text-[9px]"></span>
</div>
<span className="ml-1 text-right">{dataInfo?.min}</span>
</div>
</div>
</div>
<div style={!hasData ? { cursor: 'default', pointerEvents: 'none' } : {}}>
<ECharts ref={chartRef} option={option} theme="apipark" style={{ height: height || 400 }} opts={{ renderer: 'svg' }} />
</div>
</div>
)
}
export default ServiceAreaChart
@@ -0,0 +1,395 @@
import ECharts, { EChartsOption } from 'echarts-for-react'
import { useEffect, useRef, useState } from 'react'
import { $t } from '@common/locales/index.ts'
import { chartColors, defaultColor } from '@common/const/charts/theme'
export type BarChartInfo = {
title: string
value: string
date: string[]
data: {
name: string
color: string
value: number[]
}[]
showXAxis?: boolean
inputTokenTotal?: string
outputTokenTotal?: string
request2xxTotal?: string
request4xxTotal?: string
request5xxTotal?: string
traffic2xxTotal?: string
traffic4xxTotal?: string
traffic5xxTotal?: string
max?: string | number
min?: string | number
}
type ServiceBarCharProps = {
customClassNames?: string
dataInfo?: BarChartInfo
height?: number
showAvgLine?: boolean
showLegendIndicator?: boolean
hideIndicatorValue?: boolean
}
const ServiceBarChar = ({
customClassNames,
dataInfo,
height,
showAvgLine,
showLegendIndicator,
hideIndicatorValue
}: ServiceBarCharProps) => {
const chartRef = useRef<ECharts>(null)
const [option, setOption] = useState<EChartsOption | undefined>({})
// 使用从主题配置中导入的默认颜色,而不是硬编码的颜色值
const [detaultColor] = useState(defaultColor)
const [hasData, setHasData] = useState(true)
const tokenMap = {
inputToken: $t('输入 Token'),
outputToken: $t('输出 Token')
}
const setChartOption = (dataInfo: BarChartInfo) => {
const isNumberArray = typeof dataInfo.data[0] !== 'object'
const legendData = isNumberArray ? [dataInfo.title] : dataInfo.data.map((item) => item.name)
const dataExists = dataInfo.data && dataInfo.data.length > 0
// 更新hasData状态
setHasData(dataExists)
const tooltipFormatter = (params: { name: string; color: string; seriesIndex?: number }) => {
let tooltipContent = `<div style="min-width:140px;padding:8px;">
<div>${isNumberArray ? '' : params.name}</div>`
const data = isNumberArray
? [
{
name: params.name,
color: detaultColor,
value: dataInfo.data
}
]
: dataInfo.data
// 为每个数据系列添加一行
data.forEach((item, index) => {
// 使用与柱状图相同的颜色策略,确保颜色一致性
const color = item.color ? item.color : index < chartColors.length ? chartColors[index] : detaultColor
const name = tokenMap[item.name as keyof typeof tokenMap] || item.name
const value = item.value[dataInfo.date.indexOf(params.name)] || 0
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${color};"></span>`
tooltipContent += `<div style="margin-top: ${index === 0 ? 8 : 4}px; display: flex; justify-content: space-between; align-items: center;">
<div>${marker} ${name}</div> <div style="font-weight:bold; margin-left: 20px;">${value}</div>
</div>`
})
tooltipContent += '</div>'
return tooltipContent
}
const option: EChartsOption = {
title: [
{
text:
'{titleStyle|' +
$t(dataInfo.title) +
`}${hideIndicatorValue ? '' : '\n\n{valueStyle|' + dataInfo.value + '}'}`,
left: '2%',
top: '0',
textStyle: {
rich: {
titleStyle: {
fontSize: 14,
color: '#999999',
fontWeight: 'normal',
lineHeight: 20
},
valueStyle: {
fontSize: 32,
color: '#101010',
fontWeight: 500,
lineHeight: 40
}
}
}
}
],
grid: {
left: '3%',
right: '3%',
bottom: '0%',
top: '110px',
containLabel: true
},
tooltip: dataExists
? {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
formatter: function (params: any) {
// 如果是数组,取第一个参数的name
const param = Array.isArray(params) ? params[0] : params
return tooltipFormatter(param)
}
}
: {
show: false // 没有数据时不显示tooltip
},
legend: {
show: !isNumberArray,
data: legendData,
right: '10px',
top: hideIndicatorValue ? '10px' : '60px',
itemWidth: 10,
itemHeight: 10,
textStyle: {
color: '#333'
},
icon: 'rect',
formatter: function (name: string): string {
// 这里可以映射或自定义图例文本
const customNames: Record<string, string> = {
inputToken: `${$t('输入 Token')} ${showLegendIndicator ? `(${dataInfo.inputTokenTotal})` : ''}`,
outputToken: `${$t('输出 Token')} ${showLegendIndicator ? `(${dataInfo.outputTokenTotal})` : ''}`,
'2xx': `${'2xx'} ${showLegendIndicator ? `(${dataInfo.request2xxTotal || dataInfo.traffic2xxTotal})` : ''}`,
'4xx': `${'4xx'} ${showLegendIndicator ? `(${dataInfo.request4xxTotal || dataInfo.traffic4xxTotal})` : ''}`,
'5xx': `${'5xx'} ${showLegendIndicator ? `(${dataInfo.request5xxTotal || dataInfo.traffic5xxTotal})` : ''}`
}
return customNames[name] || name
}
},
xAxis: {
type: 'category',
data: dataInfo.date,
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#ccc'
}
},
show: false
},
yAxis: {
type: 'value',
name: '',
min: 0,
...(showAvgLine ? {} : { minInterval: 1 }),
show: dataExists, // 没有数据时不显示Y轴
splitLine: {
show: dataExists, // 没有数据时不显示网格线
lineStyle: {
type: 'dashed',
color: '#eee'
}
},
axisLabel: {
formatter: '{value}'
}
},
// 添加数据缩放组件,实现鼠标放大缩小,后续可能需要
// dataZoom: [
// {
// type: 'inside', // 内置的数据区域缩放组件(使用鼠标滚轮缩放)
// xAxisIndex: 0, // 设置缩放作用在第一个x轴
// filterMode: 'filter',
// start: 0,
// end: 100
// },
// {
// type: 'slider', // 滑动条型数据区域缩放组件
// xAxisIndex: 0,
// filterMode: 'filter',
// height: 20,
// bottom: 0,
// start: 0,
// end: 100,
// handleIcon: 'path://M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4v1.3h1.3v-1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7V23h6.6V24.4z M13.3,19.6H6.7v-1.4h6.6V19.6z',
// handleSize: '80%',
// handleStyle: {
// color: '#fff',
// shadowBlur: 3,
// shadowColor: 'rgba(0, 0, 0, 0.6)',
// shadowOffsetX: 2,
// shadowOffsetY: 2
// },
// show: false // 默认隐藏底部的滑动条,可以改为 true 显示
// }
// ],
// 添加空状态提示
silent: !dataExists,
graphic: !dataExists
? [
{
type: 'text',
left: 'center',
top: 'middle',
style: {
text: $t('暂无数据'),
fontSize: 14,
fill: '#999'
}
}
]
: [],
series: isNumberArray
? [
{
name: dataInfo.title,
type: 'bar',
stack: '总量',
emphasis: {
focus: 'series'
},
itemStyle: {
color: detaultColor
},
markLine: showAvgLine
? {
silent: false,
symbol: 'none',
lineStyle: {
width: 1,
type: 'dashed'
},
label: {
show: false,
position: 'insideEndTop',
formatter: '{c}',
color: '#000',
fontSize: 10,
backgroundColor: 'transparent',
padding: [10, 4],
borderRadius: 2,
distance: -5
},
emphasis: {
lineStyle: {
width: 1 // 保持线条宽度不变,禁用默认的悬停加粗
},
label: {
show: false // 悬停时不显示标签
}
},
data: [{ type: 'average', name: 'Avg' }]
}
: undefined,
data: dataInfo.data
}
]
: dataInfo.data.map((item, index) => ({
name: item.name,
type: 'bar',
stack: '总量',
markLine: showAvgLine
? {
silent: false,
symbol: 'none',
lineStyle: {
width: 1,
type: 'dashed'
},
label: {
show: false,
position: 'insideEndTop',
formatter: '{c}',
color: '#000',
fontSize: 10,
backgroundColor: 'transparent',
padding: [10, 4],
borderRadius: 2,
distance: -5
},
emphasis: {
lineStyle: {
width: 1 // 保持线条宽度不变,禁用默认的悬停加粗
},
label: {
show: false // 悬停时不显示标签
}
},
data: [{ type: 'average', name: 'Avg' }]
}
: undefined,
emphasis: {
focus: 'series'
},
itemStyle: {
// 使用主题中的颜色列表,如果索引超出范围则使用项目自带的颜色
color: item.color ? item.color : index < chartColors.length ? chartColors[index] : detaultColor
},
data: item.value
}))
}
const echartsInstance = chartRef.current?.getEchartsInstance()
if (echartsInstance) {
echartsInstance.setOption(option)
}
}
// 使用深度监听来确保图表数据更新
useEffect(() => {
if (!dataInfo) return
// 直接获取 ECharts 实例并设置选项
const echartsInstance = chartRef.current?.getEchartsInstance()
if (echartsInstance) {
// 清除已有的图表
echartsInstance.clear()
// 重新设置选项
setChartOption(dataInfo)
}
}, [dataInfo, JSON.stringify(dataInfo)])
// 添加窗口大小变化监听,实现自适应
useEffect(() => {
// 定义resize处理函数
const handleResize = () => {
const echartsInstance = chartRef.current?.getEchartsInstance()
if (echartsInstance) {
echartsInstance.resize()
}
}
// 添加监听
window.addEventListener('resize', handleResize)
// 组件卸载时移除监听
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return (
<div className={`w-full ${customClassNames}`}>
{
hideIndicatorValue && (
<div className="absolute top-[26px] left-[10px] w-full">
<div className="relative top-[5px]">
<div className="absolute top-[23px] right-[5%] grid grid-cols-[auto_auto] justify-items-end">
<div className="flex justify-center items-center">
<span className="text-[#FE564D] text-[9px]"></span>
</div>
<span className="ml-1 text-right">{dataInfo?.max}</span>
<div className="flex justify-center items-center">
<span className="text-[#27B148] text-[9px]"></span>
</div>
<span className="ml-1 text-right">{dataInfo?.min}</span>
</div>
</div>
</div>
)
}
<div style={!hasData ? { cursor: 'default', pointerEvents: 'none' } : {}}>
<ECharts
ref={chartRef}
option={option}
style={{ height: height || 400 }}
opts={{ renderer: 'svg' }}
theme="apipark" // 这里应用主题名称,需要先在应用入口注册
/>
</div>
</div>
)
}
export default ServiceBarChar
@@ -0,0 +1,37 @@
import TimeRangeSelector, { RangeValue, TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
import { useState } from 'react'
export type TimeOption = '' | 'hour' | 'day' | 'threeDays' | 'sevenDays'
const DateSelectFilter = ({
selectCallback,
defaultTime,
customClassNames
}: {
selectCallback: (timeRange: TimeRange) => void
defaultTime: TimeOption
customClassNames?: string
}) => {
/** 默认时间 */
const [timeButton, setTimeButton] = useState<TimeOption>(defaultTime || 'hour')
/** 日期选择 */
const [datePickerValue, setDatePickerValue] = useState<RangeValue>()
/** 时间范围变化 */
const handleTimeRangeChange = (timeRange: TimeRange) => {
selectCallback(timeRange)
}
return (
<div>
<TimeRangeSelector
labelSize="small"
customClassNames={customClassNames}
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
initialDatePickerValue={datePickerValue}
onTimeRangeChange={handleTimeRangeChange}
/>
</div>
)
}
export default DateSelectFilter
@@ -0,0 +1,88 @@
import { Button, Card } from 'antd'
import { useEffect, useState } from 'react'
import { $t } from '@common/locales'
import { useNavigate } from 'react-router-dom'
import { Icon } from '@iconify/react/dist/iconify.js'
/** 服务指标 */
type IndicatorType = {
title: string
link?: string
content: string | React.ReactNode
}
const Indicator = ({ indicatorInfo }: { indicatorInfo: any }) => {
/** 服务指标 */
const [indicatorList, setIndicator] = useState<IndicatorType[]>([])
/** 路由跳转 */
const navigateTo = useNavigate()
/** 设置服务指标 */
const setIndicatorList = () => {
const side = indicatorInfo?.serviceKind === 'ai' ? 'aiInside' : 'inside'
setIndicator([
{
title: indicatorInfo?.enableMcp ? 'APIs / Tools' : 'APIs',
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/route`,
content: indicatorInfo?.apiNum ?? 0
},
{
title: $t('订阅数量'),
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/subscriber`,
content: indicatorInfo?.subscriberNum ?? 0
},
{
title: 'MCP',
link: `/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`,
content: (
<>
<Button
color={indicatorInfo?.enableMcp ? 'green' : 'primary'}
className="w-full rounded-[10px]"
variant="outlined"
onClick={(e) => {
e.stopPropagation()
navigateTo(`/service/${indicatorInfo?.teamId}/${side}/${indicatorInfo?.serviceId}/setting`)
}}
>
{indicatorInfo?.enableMcp ? $t('已开启') : $t('开启 MCP')}
</Button>
</>
)
}
])
}
useEffect(() => {
if (!indicatorInfo) return
setIndicatorList()
}, [indicatorInfo])
return (
<div className="flex">
{indicatorList.map((item, index) => (
<Card
key={index}
className={`flex-1 cursor-pointer rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
classNames={{
body: 'py-[20px] px-[18px]'
}}
onClick={() => {
if (item.link) {
navigateTo(item.link)
}
}}
>
<div className="text-[14px] text-[#999999] mb-[10px]" style={{ fontFamily: 'Microsoft YaHei' }}>
{item.title}
{item.link && <Icon icon="uiw:right" width="16" height="16" className="absolute top-[14px] right-[14px]" />}
</div>
<div className={`${index < 2 ? 'text-[32px] font-medium text-[#101010]' : 'block mt-[30px]'}`} style={{ fontFamily: 'Microsoft YaHei' }}>
{item.content}
</div>
</Card>
))}
</div>
)
}
export default Indicator
@@ -0,0 +1,96 @@
import { useMemo, useRef, useEffect } from 'react'
import PageList from '@common/components/aoplatform/PageList'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
import { $t } from '@common/locales/index.ts'
import { Card } from 'antd'
import { AI_SERVICE_TOP_RANKING_LIST, REST_SERVICE_TOP_RANKING_LIST } from '@core/const/system/const'
interface RankingListData {
[key: string]: Array<{
id: string;
name: string;
request: number;
token?: number;
traffic?: number;
}>;
}
interface PageListRef {
reload: () => void;
[key: string]: any;
}
/**
*
* @returns
*/
const RankingList = ({ topRankingList, serviceType }: { topRankingList: RankingListData; serviceType: 'aiService' | 'restService' }) => {
/** 全局状态 */
const { state } = useGlobalContext()
/** 表格 ref */
const tableRefs = useRef<{ [key: string]: PageListRef | null }>({});
/** 列 */
const columns = useMemo(() => {
return [...(serviceType === 'aiService' ? AI_SERVICE_TOP_RANKING_LIST : REST_SERVICE_TOP_RANKING_LIST)].map((x) => {
return {
...x,
title: typeof x.title === 'string' ? $t(x.title as string) : x.title
}
})
}, [serviceType, state.language])
/** 监听 serviceType 变化,刷新所有表格 */
useEffect(() => {
// 重新加载所有表格数据
if (Object.keys(tableRefs.current).length > 0) {
Object.values(tableRefs.current).forEach(ref => {
// 如果组件实例存在并且有reload方法
if (ref && typeof ref.reload === 'function') {
ref.reload();
}
});
}
}, [serviceType, topRankingList])
/**
*
* @param item
* @returns
*/
const getTableData = (item: string) => {
return new Promise((resolve, reject) => {
resolve({ data: topRankingList[item], success: true })
})
}
return (
<div className="flex w-full pb-[10px]">
{Object.keys(topRankingList)?.map((item: any, index: number) => (
<Card
key={index}
className={`flex-1 min-w-[430px] h-fit rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
classNames={{
body: 'p-[15px]'
}}
>
<div className="mb-[10px]">
<span className="text-[14px] text-[#999999]" style={{ fontFamily: 'Microsoft YaHei' }}>{item === 'TOP API' ? $t('API 使用排名') : $t('消费者使用排名')}</span>
</div>
<PageList
id={item}
columns={[...columns]}
minVirtualHeight={430}
noScroll
request={() => getTableData(item)}
showPagination={false}
tableClass="ranking-list"
ref={ref => {
if (ref) tableRefs.current[item] = ref;
}}
/>
</Card>
))}
</div>
)
}
export default RankingList
@@ -0,0 +1,428 @@
import { Card, Spin } from 'antd'
import { useEffect, useState } from 'react'
import { useParams } from 'react-router-dom'
import { $t } from '@common/locales/index.ts'
import Indicator from './indicator/Indicator'
import { LoadingOutlined } from '@ant-design/icons'
import DateSelectFilter, { TimeOption } from './filter/DateSelectFilter'
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
import ServiceBarChar, { BarChartInfo } from './charts/ServiceBarChar'
import { useFetch } from '@common/hooks/http'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { App } from 'antd'
import ServiceAreaChart from './charts/ServiceAreaChart'
import RankingList from './rankingList/RankingList'
import { abbreviateFloat, formatBytes, formatDuration, formatNumberWithUnit, getTime } from '@dashboard/utils/dashboard'
import { setBarChartInfoData } from './utils'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
const ServiceOverview = ({ serviceType }: { serviceType: 'aiService' | 'restService' }) => {
/** 路由参数 */
const { serviceId, teamId } = useParams<{ serviceId: string; teamId: string }>()
/** 面板 loading */
const [dashboardLoading, setDashboardLoading] = useState(true)
/** 默认时间 */
const [defaultTime] = useState<TimeOption>('day')
/** 当前选中的时间范围 */
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
/** 总数数据 */
const [barChartInfo, setBarChartInfo] = useState<any>()
/** 平均值数据 */
const [perBarChartInfo, setPerBarChartInfo] = useState<any>()
/** 指标数据 */
const [indicatorInfo, setIndicatorInfo] = useState<any>([])
/** 排名表格数据 */
const [topRankingList, setTopRankingList] = useState<any>([])
/** 获取服务信息 */
const { fetchData } = useFetch()
/** 弹窗组件 */
const { message } = App.useApp()
/** 全局状态 */
const { state } = useGlobalContext()
/** AI 服务数据 */
const [aiServiceOverview, setAiServiceOverview] = useState<any>()
/** REST 服务数据 */
const [restServiceOverview, setRestServiceOverview] = useState<any>()
/** 时间选择回调 */
const selectCallback = (date: TimeRange) => {
setTimeRange(date)
}
/** 获取 AI 服务信息 */
const getAIServiceOverview = () => {
fetchData<BasicResponse<{ overview: any }>>('service/overview/monitor/ai', {
method: 'GET',
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end },
eoTransformKeys: [
'enable_mcp',
'subscriber_num',
'api_num',
'service_kind',
'avaliable_monitor',
'request_overview',
'token_overview',
'avg_token_overview',
'avg_request_per_subscriber_overview',
'avg_token_per_subscriber_overview',
'request_total',
'token_total',
'avg_token',
'max_token',
'min_token',
'avg_request_per_subscriber',
'avg_token_per_subscriber',
'input_token',
'output_token',
'total_token',
'request_2xx_total',
'request_4xx_total',
'request_5xx_total',
'input_token_total',
'output_token_total',
'max_token_per_subscriber',
'min_token_per_subscriber',
'max_request_per_subscriber',
'min_request_per_subscriber'
]
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 存储 AI 服务数据
setAiServiceOverview(data.overview)
// 设置 AI 报表数据
setAiChartInfoData(data.overview)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
setDashboardLoading(false)
})
}
/**
* REST
* */
const setRestChartInfoData = (serviceOverview: any) => {
// 设置指标数据
setIndicatorInfo({
apiNum: serviceOverview.apiNum,
subscriberNum: serviceOverview.subscriberNum,
teamId: teamId,
enableMcp: serviceOverview.enableMcp,
serviceKind: serviceOverview.serviceKind,
serviceId: serviceId
})
// 设置总数数据
setBarChartInfo([
// 服务请求次数
{
...setBarChartInfoData({
title: $t('请求次数'),
data: serviceOverview.requestOverview,
value: formatNumberWithUnit(serviceOverview.requestTotal),
date: serviceOverview.date
}),
request2xxTotal: formatNumberWithUnit(serviceOverview.request2xxTotal),
request4xxTotal: formatNumberWithUnit(serviceOverview.request4xxTotal),
request5xxTotal: formatNumberWithUnit(serviceOverview.request5xxTotal)
},
// 流量消耗总数
{
...setBarChartInfoData({
title: $t('网络流量'),
data: serviceOverview.trafficOverview,
value: formatBytes(serviceOverview.trafficTotal),
date: serviceOverview.date
}),
traffic2xxTotal: formatBytes(serviceOverview.traffic2xxTotal),
traffic4xxTotal: formatBytes(serviceOverview.traffic4xxTotal),
traffic5xxTotal: formatBytes(serviceOverview.traffic5xxTotal)
}
])
// 设置平均值数据
setPerBarChartInfo([
// 各个模型使用量
{
title: $t('平均响应时间'),
data: serviceOverview.avgResponseTimeOverview,
value: formatDuration(serviceOverview.avgResponseTime),
originValue: serviceOverview.avgResponseTime,
date: serviceOverview.date,
max: formatDuration(serviceOverview.maxResponseTime),
min: formatDuration(serviceOverview.minResponseTime),
type: 'area',
showXAxis: false
},
// 平均请求
{
...setBarChartInfoData({
title: $t('平均每消费者的请求次数'),
data: serviceOverview.avgRequestPerSubscriberOverview,
date: serviceOverview.date,
showXAxis: false
}),
max: abbreviateFloat(serviceOverview.maxRequestPerSubscriber),
min: abbreviateFloat(serviceOverview.minRequestPerSubscriber)
},
// 平均流量消耗
{
...setBarChartInfoData({
title: $t('平均每消费者的网络流量'),
data: serviceOverview.avgTrafficPerSubscriberOverview,
date: serviceOverview.date,
showXAxis: false
}),
max: formatBytes(serviceOverview.maxTrafficPerSubscriber),
min: formatBytes(serviceOverview.minTrafficPerSubscriber)
}
])
}
/**
* AI
* */
const setAiChartInfoData = (serviceOverview: any) => {
// 设置指标数据
setIndicatorInfo({
apiNum: serviceOverview.apiNum,
subscriberNum: serviceOverview.subscriberNum,
teamId: teamId,
enableMcp: serviceOverview.enableMcp,
serviceKind: serviceOverview.serviceKind,
serviceId: serviceId
})
// 设置总数数据
setBarChartInfo([
// 服务请求次数
{
...setBarChartInfoData({
title: $t('请求次数'),
data: serviceOverview.requestOverview,
value: formatNumberWithUnit(serviceOverview.requestTotal),
date: serviceOverview.date
}),
request2xxTotal: formatNumberWithUnit(serviceOverview.request2xxTotal),
request4xxTotal: formatNumberWithUnit(serviceOverview.request4xxTotal),
request5xxTotal: formatNumberWithUnit(serviceOverview.request5xxTotal)
},
// token 消耗总数
{
...setBarChartInfoData({
title: $t('Token 消耗'),
data: serviceOverview.tokenOverview.map((item: { inputToken: number; outputToken: number }) => ({
inputToken: item.inputToken,
outputToken: item.outputToken
})),
value: formatNumberWithUnit(serviceOverview.tokenTotal),
date: serviceOverview.date
}),
inputTokenTotal: formatNumberWithUnit(serviceOverview.inputTokenTotal),
outputTokenTotal: formatNumberWithUnit(serviceOverview.outputTokenTotal)
}
])
// 设置平均值数据
setPerBarChartInfo([
// 平均 token 消耗
{
title: $t('平均 Token 消耗'),
data: serviceOverview.avgTokenOverview,
value: formatNumberWithUnit(serviceOverview.avgToken) + ' Token/s',
originValue: serviceOverview.avgToken,
date: serviceOverview.date,
min: formatNumberWithUnit(serviceOverview.minToken) + ' Token/s',
max: formatNumberWithUnit(serviceOverview.maxToken) + ' Token/s',
type: 'area'
},
{
// 平均请求
...setBarChartInfoData({
title: $t('平均每消费者的请求次数'),
data: serviceOverview.avgRequestPerSubscriberOverview,
date: serviceOverview.date
}),
max: abbreviateFloat(serviceOverview.maxRequestPerSubscriber),
min: abbreviateFloat(serviceOverview.minRequestPerSubscriber)
},
// 评价 token 消耗
{
...setBarChartInfoData({
title: $t('平均每消费者的 Token 消耗'),
data: serviceOverview.avgTokenPerSubscriberOverview.map(
(item: { inputToken: number; outputToken: number }) => ({
inputToken: item.inputToken,
outputToken: item.outputToken
})
),
date: serviceOverview.date
}),
max: abbreviateFloat(serviceOverview.maxTokenPerSubscriber),
min: abbreviateFloat(serviceOverview.minTokenPerSubscriber)
}
])
}
/** 获取 REST 服务信息 */
const getRestServiceOverview = () => {
fetchData<BasicResponse<{ overview: any }>>('service/overview/monitor/rest', {
method: 'GET',
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end },
eoTransformKeys: [
'enable_mcp',
'subscriber_num',
'api_num',
'service_kind',
'avaliable_monitor',
'request_overview',
'traffic_overview',
'avg_request_per_subscriber_overview',
'avg_response_time_overview',
'avg_traffic_per_subscriber_overview',
'request_total',
'traffic_total',
'max_response_time',
'min_response_time',
'avg_response_time',
'avg_request_per_subscriber',
'avg_traffic_per_subscriber',
'request_2xx_total',
'request_4xx_total',
'request_5xx_total',
'traffic_2xx_total',
'traffic_4xx_total',
'traffic_5xx_total',
'max_request_per_subscriber',
'min_request_per_subscriber',
'max_traffic_per_subscriber',
'min_traffic_per_subscriber'
]
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 存储 REST 服务数据
setRestServiceOverview(data.overview)
// 设置 REST 报表数据
setRestChartInfoData(data.overview)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
setDashboardLoading(false)
})
}
/** 获取排名列表 */
const getTopRankingList = () => {
fetchData<BasicResponse<{ overview: any }>>('service/monitor/top10', {
method: 'GET',
eoParams: { service: serviceId, team: teamId, start: timeRange?.start, end: timeRange?.end }
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 设置排名表格数据
setTopRankingList({
'TOP API': data.apis,
'TOP Consumer': data.consumers
})
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
setDashboardLoading(false)
})
}
useEffect(() => {
const { startTime, endTime } = getTime(defaultTime, [])
setTimeRange({
start: startTime,
end: endTime
})
}, [])
useEffect(() => {
if (timeRange) {
serviceType === 'aiService' ? getAIServiceOverview() : getRestServiceOverview()
getTopRankingList()
}
}, [timeRange])
useEffect(() => {
if (serviceType === 'aiService') {
aiServiceOverview && setAiChartInfoData(aiServiceOverview)
} else {
restServiceOverview && setRestChartInfoData(restServiceOverview)
}
}, [state.language])
return (
<Spin
className="h-full pb-[20px]"
wrapperClassName="h-full min-h-[150px]"
indicator={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ transform: 'scale(1.5)' }}>
<LoadingOutlined style={{ fontSize: 30 }} spin />
</div>
</div>
}
spinning={dashboardLoading}
>
<div className="mr-[30px]">
<Indicator indicatorInfo={indicatorInfo} />
<div className="mt-[20px]">
<DateSelectFilter selectCallback={selectCallback} defaultTime={defaultTime} />
</div>
<div className="mt-[20px] flex mb-[10px]">
{barChartInfo?.map((item: BarChartInfo, index: number) => (
<Card
key={index}
className={`flex-1 min-w-[430px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
classNames={{
body: 'py-[15px] px-[0px]'
}}
>
<ServiceBarChar
showLegendIndicator={true}
key={index}
height={400}
dataInfo={item}
customClassNames="flex-1"
></ServiceBarChar>
</Card>
))}
</div>
<div className="flex mb-[10px]">
{perBarChartInfo?.map((item: any, index: number) => (
<Card
key={index}
className={`flex-1 rounded-[10px] min-w-[284px] ${index > 0 ? 'ml-[10px]' : ''}`}
classNames={{
body: 'py-[15px] px-[0px]'
}}
>
{item.type === 'area' ? (
<>
<ServiceAreaChart
key={index}
height={270}
dataInfo={item}
showAvgLine={true}
customClassNames="flex-1 relative"
></ServiceAreaChart>
</>
) : (
<ServiceBarChar
key={index}
height={270}
dataInfo={item}
hideIndicatorValue={true}
customClassNames="flex-1"
></ServiceBarChar>
)}
</Card>
))}
</div>
<RankingList topRankingList={topRankingList} serviceType={serviceType} />
</div>
</Spin>
)
}
export default ServiceOverview
@@ -0,0 +1,55 @@
export type BarData = {
title: string
value?: string
date: string[]
data: any[]
showXAxis?: boolean
}
export const setBarChartInfoData = ({ title, value, data, date, showXAxis }: BarData) => {
// 首先获取所有的键名(假设所有对象的键名都一样)
if (data.length === 0) {
return {
title,
value,
date,
data: [],
showXAxis: !!showXAxis
}
}
if (typeof data[0] !== 'object') {
return {
title,
value,
date,
data,
showXAxis: !!showXAxis
}
}
// 从第一个对象中获取所有键名
const keys = Object.keys(data[0])
// 定义颜色映射
const colorMap: Record<string, string> = {
'2xx': '#3ba272',
'4xx': '#ffc404',
'5xx': '#b92325'
}
// 为每个键创建一个数据集
const transformedData = keys.map((key) => {
// 为没有映射颜色的键生成随机颜色
const color = colorMap[key]
return {
name: key,
color: color,
value: data.map((item) => item[key])
}
})
return {
title,
value,
date,
data: transformedData,
showXAxis: !!showXAxis
}
}
@@ -2,7 +2,6 @@ import { LoadingOutlined } from '@ant-design/icons'
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { BasicResponse, DELETE_TIPS, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import { EntityItem, MemberItem, SimpleTeamItem } from '@common/const/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import { useFetch } from '@common/hooks/http.ts'
import { $t } from '@common/locales/index.ts'
@@ -49,7 +48,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
const { fetchData } = useFetch()
const [teamOptionList, setTeamOptionList] = useState<DefaultOptionType[]>()
const navigate = useNavigate()
const { setBreadcrumb } = useBreadcrumb()
const { setSystemInfo } = useSystemContext()
const [showClassify, setShowClassify] = useState<boolean>(true)
const [showAI, setShowAI] = useState<boolean>(false)
@@ -355,15 +353,6 @@ const SystemConfig = forwardRef<SystemConfigHandle>((_, ref) => {
if (serviceId !== undefined) {
setOnEdit(true)
getSystemInfo()
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigate('/service/list')
},
{
title: $t('设置')
}
])
} else {
getProviderOptionList()
setOnEdit(false)
@@ -1,7 +1,6 @@
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import { EntityItem } from '@common/const/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
import { useFetch } from '@common/hooks/http.ts'
import { $t } from '@common/locales'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
@@ -10,7 +9,7 @@ import { App, Button } from 'antd'
import hljs from 'highlight.js'
import 'highlight.js/styles/default.css'
import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
const ServiceInsideDocument = () => {
const { message } = App.useApp()
const [updater, setUpdater] = useState<string>()
@@ -19,8 +18,6 @@ const ServiceInsideDocument = () => {
const [doc, setDoc] = useState<string>()
const { fetchData } = useFetch()
const { serviceId, teamId } = useParams<RouterParams>()
const { setBreadcrumb } = useBreadcrumb()
const navigator = useNavigate()
const save = () => {
fetchData<
BasicResponse<{
@@ -88,15 +85,6 @@ const ServiceInsideDocument = () => {
}
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('使用说明')
}
])
getServiceDoc()
}, [])
@@ -14,6 +14,8 @@ import { FC, useEffect, useMemo, useState } from 'react'
import { Link, Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'
import { SystemConfigFieldType } from '../../const/system/type.ts'
import { useSystemContext } from '../../contexts/SystemContext.tsx'
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
const SystemInsidePage: FC = () => {
const { message } = App.useApp()
@@ -26,12 +28,13 @@ const SystemInsidePage: FC = () => {
const [activeMenu, setActiveMenu] = useState<string>()
const navigateTo = useNavigate()
const [showMenu, setShowMenu] = useState<boolean>(false)
const { setBreadcrumb } = useBreadcrumb()
const getSystemInfo = () => {
fetchData<BasicResponse<{ service: SystemConfigFieldType }>>('service/info', {
method: 'GET',
eoParams: { team: teamId, service: serviceId }
}).then(response => {
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setSystemInfo(data.service)
@@ -47,7 +50,7 @@ const SystemInsidePage: FC = () => {
fetchData<BasicResponse<{ prefix: string; force: boolean }>>('service/router/define', {
method: 'GET',
eoParams: { service: serviceId, team: teamId }
}).then(response => {
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
setApiPrefix(data.prefix)
@@ -65,6 +68,7 @@ const SystemInsidePage: FC = () => {
'assets',
null,
[
getItem(<Link to="./overview">{$t('总览')}</Link>, 'overview', undefined, undefined, undefined, ''),
getItem(
<Link to="./route">{$t('API 路由')}</Link>,
'route',
@@ -146,9 +150,10 @@ const SystemInsidePage: FC = () => {
null,
[
// APP_MODE === 'pro' ? getItem(<Link to="./topology">{$t('调用拓扑图')}</Link>, 'topology',undefined,undefined,undefined,'project.mySystem.topology.view'):null,
getItem(<Link to="./setting">{$t('设置')}</Link>, 'setting', undefined, undefined, undefined, ''),
getItem(
<Link to="./setting">{$t('设置')}</Link>,
'setting',
<Link to="./logs">{$t('日志')}</Link>,
'logs',
undefined,
undefined,
undefined,
@@ -166,12 +171,11 @@ const SystemInsidePage: FC = () => {
const newMenu = cloneDeep(menu)
return newMenu!.filter((m: MenuItemGroupType) => {
if (m && m.children && m.children.length > 0) {
m.children = m.children.filter(c => {
m.children = m.children.filter((c) => {
if (!c) return false
return (c as MenuItemType & { access: string }).access
? checkPermission(
(c as MenuItemType & { access: string })
.access as keyof (typeof PERMISSION_DEFINITION)[0]
(c as MenuItemType & { access: string }).access as keyof (typeof PERMISSION_DEFINITION)[0]
)
: true
})
@@ -180,12 +184,8 @@ const SystemInsidePage: FC = () => {
})
}
const filteredMenu = filterMenu(SYSTEM_PAGE_MENU_ITEMS as MenuItemGroupType<MenuItemType>[])
const menu =
(activeMenu ?? filteredMenu[0]?.children)
? filteredMenu[0]?.children?.[0]?.key
: filteredMenu[0]?.key
if (menu && currentUrl.split('/')[-1] !== menu)
navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`)
const menu = (activeMenu ?? filteredMenu[0]?.children) ? filteredMenu[0]?.children?.[0]?.key : filteredMenu[0]?.key
if (menu && currentUrl.split('/')[-1] !== menu) navigateTo(`/service/${teamId}/inside/${serviceId}/${menu}`)
return filteredMenu || []
}, [accessData, accessInit, SYSTEM_PAGE_MENU_ITEMS])
@@ -208,7 +208,7 @@ const SystemInsidePage: FC = () => {
} else if (serviceId !== currentUrl.split('/')[currentUrl.split('/').length - 1]) {
setActiveMenu(currentUrl.split('/')[currentUrl.split('/').length - 1])
} else {
setActiveMenu('route')
setActiveMenu('overview')
}
}, [currentUrl])
@@ -219,10 +219,19 @@ const SystemInsidePage: FC = () => {
}, [accessData])
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigateTo('/service/list')
},
{
title: systemInfo?.name || ''
}
])
if (activeMenu && serviceId === currentUrl.split('/')[currentUrl.split('/').length - 1]) {
navigateTo(`/service/${teamId}/inside/${serviceId}/${activeMenu}`)
}
}, [activeMenu])
}, [activeMenu, systemInfo, state.language])
useEffect(() => {
serviceId && getSystemInfo()
@@ -233,16 +242,7 @@ const SystemInsidePage: FC = () => {
{showMenu ? (
<InsidePage
pageTitle={systemInfo?.name || '-'}
tagList={[
...(systemInfo?.enable_mcp ? [{ label: 'MCP', color: '#FFF0C1', className: 'text-[#000]' }] : []),
{
label: (
<Paragraph className="mb-0" copyable={serviceId ? { text: serviceId } : false}>
{$t('服务 ID')}{serviceId || '-'}
</Paragraph>
)
}
]}
customBanner={<ServiceInfoCard serviceId={serviceId} teamId={teamId} />}
backUrl="/service/list"
>
<div className="flex flex-1 h-full">
@@ -11,7 +11,6 @@ import {
STATUS_CODE
} from '@common/const/const.tsx'
import { SimpleMemberItem } from '@common/const/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import { useFetch } from '@common/hooks/http.ts'
import { $t } from '@common/locales/index.ts'
@@ -20,7 +19,7 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
import { App, Form, TreeSelect } from 'antd'
import { DefaultOptionType } from 'antd/es/cascader'
import { FC, forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { SYSTEM_SUBSCRIBER_TABLE_COLUMNS } from '../../const/system/const.tsx'
import {
SimpleSystemItem,
@@ -31,7 +30,6 @@ import {
} from '../../const/system/type.ts'
const SystemInsideSubscriber: FC = () => {
const { setBreadcrumb } = useBreadcrumb()
const { modal, message } = App.useApp()
const { fetchData } = useFetch()
const { serviceId, teamId } = useParams<RouterParams>()
@@ -39,7 +37,6 @@ const SystemInsideSubscriber: FC = () => {
const pageListRef = useRef<ActionType>(null)
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
const { accessData, state } = useGlobalContext()
const navigator = useNavigate()
const getSystemSubscriber = () => {
return fetchData<BasicResponse<{ subscribers: SystemSubscriberTableListItem[] }>>(
'service/subscribers',
@@ -162,15 +159,6 @@ const SystemInsideSubscriber: FC = () => {
]
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('订阅方管理')
}
])
getMemberList()
manualReloadTable()
}, [serviceId])
@@ -2,7 +2,6 @@ import { ZoomInOutlined, ZoomOutOutlined } from '@ant-design/icons'
import G6, { EdgeConfig, Graph, NodeConfig } from '@antv/g6'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { EntityItem } from '@common/const/type'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext'
import { useFetch } from '@common/hooks/http'
import { $t } from '@common/locales'
import { getNodeSpacing } from '@common/utils/systemRunning'
@@ -10,7 +9,7 @@ import { RouterParams } from '@core/components/aoplatform/RenderRoutes'
import { App, Button } from 'antd'
import { debounce } from 'lodash-es'
import { useCallback, useEffect, useRef, useState } from 'react'
import { Link, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { RELATIVE_PICTURE_NODE_FONTSIZE } from '../../const/system-running/const'
import { GraphData } from '../../const/system-running/type'
import { SYSTEM_TOPOLOGY_NODE_TYPE_COLOR_MAP } from '../../const/system/const'
@@ -26,9 +25,7 @@ export default function SystemTopology() {
const [graph, setGraph] = useState<Graph | null>(null)
const { fetchData } = useFetch()
const { systemInfo } = useSystemContext()
const { setBreadcrumb } = useBreadcrumb()
const [zoomNum, setZoomNum] = useState<number>(1)
const navigate = useNavigate()
const getNodeData = () => {
@@ -105,15 +102,6 @@ export default function SystemTopology() {
useEffect(() => {
getNodeData()
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigate('/service/list')
},
{
title: $t('调用拓扑图')
}
])
}, [serviceId])
useEffect(() => {
@@ -9,13 +9,12 @@ import { $t } from '@common/locales/index.ts'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
import { Button, Empty, Spin, Upload, message } from 'antd'
import { forwardRef, useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import {
SystemApiDetail,
SystemInsideApiDocumentHandle,
SystemInsideApiDocumentProps
} from '../../../const/system/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
const SystemInsideApiDocument = forwardRef<
SystemInsideApiDocumentHandle,
@@ -26,18 +25,7 @@ const SystemInsideApiDocument = forwardRef<
const [apiDetail, setApiDetail] = useState<SystemApiDetail>()
const [loading, setLoading] = useState<boolean>(false)
const [showEditor, setShowEditor] = useState<boolean>(false)
const { setBreadcrumb } = useBreadcrumb()
const navigator = useNavigate()
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('API 文档')
}
])
getApiDetail()
}, [])
@@ -26,7 +26,6 @@ import {
SystemInsideRouterCreateHandle,
SystemInsideRouterCreateProps
} from '../../../const/system/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, SystemInsideRouterCreateProps>(
(props, ref) => {
@@ -39,14 +38,12 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
const { state } = useGlobalContext()
const { apiPrefix, prefixForce } = useSystemContext()
const navigator = useNavigate()
const { setBreadcrumb } = useBreadcrumb()
const onFinish = () => {
return Promise.all([proxyRef.current?.validate?.(), form.validateFields()]).then(([, formValue]) => {
const body = {
...formValue,
path: `${prefixForce ? apiPrefix + '/' : ''}${formValue.path.trim()}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
path: `${prefixForce ? apiPrefix + (!formValue.path?.trim() ? '': '/') : ''}${(formValue.path?.trim() || '')}${formValue.pathMatch === 'prefix' ? '/*' : ''}`,
proxy: {
...formValue.proxy,
path: formValue.proxy.path
@@ -118,7 +115,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
.then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
const { disable, protocols, path, methods, description, match, proxy } = data.router
const { disable, protocols, path, name, methods, description, match, proxy } = data.router
let newPath = path
let pathMatch = 'full'
if (prefixForce && path?.startsWith(apiPrefix + '/')) {
@@ -131,6 +128,7 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
form.setFieldsValue({
disable,
protocols,
name,
path: newPath,
pathMatch,
methods,
@@ -147,19 +145,6 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
}
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title:$t('API'),
onClick: () => navigator(`/service/${teamId}/inside/${serviceId}/route`)
},
{
title: routeId ? $t('编辑 API') : $t('添加 API')
}
])
if (routeId) {
getRouterConfig()
} else {
@@ -253,6 +238,14 @@ const SystemInsideRouterCreate = forwardRef<SystemInsideRouterCreateHandle, Syst
>
<Switch />
</Form.Item>
<Form.Item<SystemApiProxyFieldType>
className="flex-1"
label={$t('路由名称')}
name="name"
rules={[{ required: true, whitespace: true }]}
>
<Input className="w-INPUT_NORMAL" placeholder={$t(PLACEHOLDER.input)} />
</Form.Item>
<Form.Item<SystemApiProxyFieldType> label={$t('请求协议')} name="protocols" rules={[{ required: true }]}>
<Select
@@ -3,7 +3,6 @@ import PageList, { PageProColumns } from '@common/components/aoplatform/PageList
import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission.tsx'
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import { SimpleMemberItem } from '@common/const/type.ts'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import { useFetch } from '@common/hooks/http.ts'
import { $t } from '@common/locales/index.ts'
@@ -11,13 +10,12 @@ import { checkAccess } from '@common/utils/permission.ts'
import { RouterParams } from '@core/components/aoplatform/RenderRoutes.tsx'
import { App, Divider, Typography } from 'antd'
import { FC, useEffect, useMemo, useRef, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { useNavigate, useParams } from 'react-router-dom'
import { SYSTEM_API_TABLE_COLUMNS } from '../../../const/system/const.tsx'
import { SystemApiTableListItem } from '../../../const/system/type.ts'
const SystemInsideRouterList: FC = () => {
const [searchWord, setSearchWord] = useState<string>('')
const { setBreadcrumb } = useBreadcrumb()
const { modal, message } = App.useApp()
const [tableListDataSource, setTableListDataSource] = useState<SystemApiTableListItem[]>([])
const [tableHttpReload, setTableHttpReload] = useState(true)
@@ -162,17 +160,6 @@ const SystemInsideRouterList: FC = () => {
getMemberList()
manualReloadTable()
}, [serviceId])
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title: $t('路由')
}
])
}, [state.language])
const columns = useMemo(() => {
return [...SYSTEM_API_TABLE_COLUMNS].map((x) => {
@@ -1,9 +1,8 @@
import {ActionType} from "@ant-design/pro-components";
import {FC, useEffect, useMemo, useRef, useState} from "react";
import {Link, useLocation, useNavigate, useParams} from "react-router-dom";
import { useLocation, useParams} from "react-router-dom";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList.tsx";
import {useBreadcrumb} from "@common/contexts/BreadcrumbContext.tsx";
import {App, Button} from "antd";
import {
SUBSCRIBE_APPROVAL_INNER_DONE_TABLE_COLUMN,
@@ -26,7 +25,6 @@ import { SubscribeApprovalInfoType } from "@common/const/approval/type.tsx";
import { $t } from "@common/locales";
const SystemInsideApprovalList:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const { modal,message } = App.useApp()
const {serviceId, teamId} = useParams<RouterParams>();
const [init, setInit] = useState<boolean>(true)
@@ -40,7 +38,6 @@ const SystemInsideApprovalList:FC = ()=>{
const [approvalBtnLoading,setApprovalBtnLoading] = useState<boolean>(false)
const [memberValueEnum, setMemberValueEnum] = useState<SimpleMemberItem[]>([])
const {accessData,state} = useGlobalContext()
const navigator = useNavigate()
const openModal = async (type:'approval'|'view',entity:SubscribeApprovalTableListItem)=>{
message.loading($t(RESPONSE_TIPS.loading))
@@ -142,15 +139,6 @@ const SystemInsideApprovalList:FC = ()=>{
}, [query]);
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigator('/service/list')
},
{
title:$t('订阅审核')
}
])
getMemberList()
manualReloadTable()
}, [serviceId]);
@@ -1,14 +1,12 @@
import { Tabs } from "antd"
import { useState, useEffect, FC, useMemo } from "react"
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom"
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext"
import { Outlet, useLocation, useNavigate } from "react-router-dom"
import { SYSTEM_PUBLISH_TAB_ITEMS } from "../../../const/system/const"
import { $t } from "@common/locales"
import { useGlobalContext } from "@common/contexts/GlobalStateContext"
const SystemInsidePublic:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const query =new URLSearchParams(useLocation().search)
const location = useLocation()
const currentUrl = location.pathname
@@ -25,18 +23,6 @@ const SystemInsidePublic:FC = ()=>{
setPageStatus(Number(query.get('status') ||0) as 0|1)
}, [currentUrl]);
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigateTo('/service/list')
},
{
title:$t('发布')
}
])
}, []);
const tabItems = useMemo(()=>SYSTEM_PUBLISH_TAB_ITEMS?.map((x)=>({...x, label:$t(x.label as string) })),[state.language])
return (
<>
@@ -1,14 +1,13 @@
import { ActionType, ParamsType } from "@ant-design/pro-components";
import { App, Button, Divider } from "antd";
import { useState, useRef, useEffect, useMemo, FC } from "react";
import { useParams, Link, useLocation, useNavigate } from "react-router-dom";
import { useParams, useLocation } from "react-router-dom";
import PageList, { PageProColumns } from "@common/components/aoplatform/PageList";
import { PublishApprovalModalContent } from "@common/components/aoplatform/PublishApprovalModalContent";
import { PUBLISH_APPROVAL_RECORD_INNER_TABLE_COLUMN, PUBLISH_APPROVAL_VERSION_INNER_TABLE_COLUMN, PublishApplyStatusEnum, PublishStatusEnum, PublishTableStatusColorClass } from "@common/const/approval/const";
import { BasicResponse, COLUMNS_TITLE, DELETE_TIPS, RESPONSE_TIPS, STATUS_CODE } from "@common/const/const";
import { RouterParams, SimpleMemberItem } from "@common/const/type.ts";
import { MemberTableListItem } from "../../../const/member/type";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext";
import { useFetch } from "@common/hooks/http";
import WithPermission from "@common/components/aoplatform/WithPermission";
import { SystemPublishReleaseItem } from "../../../const/system/type";
@@ -22,7 +21,6 @@ import { DrawerWithFooter } from "@common/components/aoplatform/DrawerWithFooter
import { $t } from "@common/locales";
const SystemInsidePublicList:FC = ()=>{
const { setBreadcrumb } = useBreadcrumb()
const { modal,message } = App.useApp()
const pageListRef = useRef<ActionType>(null);
const [tableHttpReload, setTableHttpReload] = useState(true);
@@ -44,7 +42,6 @@ const SystemInsidePublicList:FC = ()=>{
const [drawerData, setDrawerData] = useState<PublishTableListItem|PublishVersionTableListItem >({} as PublishTableListItem)
const [drawerOkTitle, setDrawerOkTitle] = useState<string>('确认')
const [isOkToPublish, setIsOkToPublish] = useState<boolean>(false)
const navigateTo = useNavigate()
const getSystemPublishList = (params?: ParamsType & {
pageSize?: number | undefined;
current?: number | undefined;
@@ -350,15 +347,6 @@ const SystemInsidePublicList:FC = ()=>{
]
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigateTo('/service/list')
},
{
title:$t('发布')
}
])
getMemberList()
manualReloadTable()
}, [serviceId]);
@@ -8,11 +8,10 @@ import EditableTable from "@common/components/aoplatform/EditableTable.tsx";
import EditableTableWithModal from "@common/components/aoplatform/EditableTableWithModal.tsx";
import WithPermission from "@common/components/aoplatform/WithPermission.tsx";
import { UPSTREAM_TYPE_OPTIONS, SYSTEM_UPSTREAM_GLOBAL_CONFIG_TABLE_COLUMNS, schemeOptions, UPSTREAM_BALANCE_OPTIONS, UPSTREAM_PASS_HOST_OPTIONS, PROXY_HEADER_CONFIG, UPSTREAM_PROXY_HEADER_TYPE_OPTIONS } from "../../../const/system/const.tsx";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { RouterParams } from "@core/components/aoplatform/RenderRoutes.tsx";
import { BasicResponse, PLACEHOLDER, RESPONSE_TIPS, STATUS_CODE, VALIDATE_MESSAGE } from "@common/const/const.tsx";
import { useFetch } from "@common/hooks/http.ts";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
import { $t } from "@common/locales/index.ts";
import { useGlobalContext } from "@common/contexts/GlobalStateContext.tsx";
@@ -33,10 +32,8 @@ const SystemInsideUpstreamContent= forwardRef<SystemInsideUpstreamContentHandle>
const {fetchData} = useFetch()
const [, forceUpdate] = useState<unknown>(null);
const [formShowHost, setFormShowHost] = useState<boolean>(false);
const { setBreadcrumb } = useBreadcrumb()
const [form] = Form.useForm();
const {state} = useGlobalContext()
const navigate = useNavigate()
useImperativeHandle(ref, () => ({
save:()=>formRef.current?.save()
@@ -109,16 +106,7 @@ const globalConfigNodesRule: FormItemProps['rules'] = [
];
useEffect(() => {
setBreadcrumb([
{
title: $t('服务'),
onClick: () => navigate('/service/list')
},
{
title: $t('上游')
}])
getUpstreamInfo();
getUpstreamInfo();
}, [serviceId]);
const typeOptions = useMemo(()=>UPSTREAM_TYPE_OPTIONS.map(x=>({...x, label:$t(x.label)})),[state.language])
@@ -9,7 +9,6 @@ import { useFetch } from '@common/hooks/http.ts'
import { DefaultOptionType } from 'antd/es/cascader'
import { TeamConfigFieldType } from '../../const/team/type.ts'
import WithPermission from '@common/components/aoplatform/WithPermission.tsx'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import { useTeamContext } from '../../contexts/TeamContext.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import { $t } from '@common/locales/index.ts'
@@ -32,7 +31,6 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
const currentUrl = location.pathname
const { fetchData } = useFetch()
const [managerOption, setManagerOption] = useState<DefaultOptionType[]>([])
const { setBreadcrumb } = useBreadcrumb()
const { setTeamInfo } = useTeamContext()
const { checkPermission, accessInit, state } = useGlobalContext()
const pageType = useMemo(() => {
@@ -133,15 +131,6 @@ const TeamConfig = forwardRef<TeamConfigHandle, TeamConfigProps>((props, ref) =>
.catch((errorInfo) => reject(errorInfo))
})
}
useEffect(() => {
setBreadcrumb([
{
title: $t('团队'),
onClick: () => navigateTo('/team/list')
},
{ title: $t('设置') }
])
}, [state.language])
useEffect(() => {
getManagerList()
@@ -1,8 +1,7 @@
import PageList, { PageProColumns } from '@common/components/aoplatform/PageList.tsx'
import { ActionType } from '@ant-design/pro-components'
import { FC, useEffect, useMemo, useRef, useState } from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import { useBreadcrumb } from '@common/contexts/BreadcrumbContext.tsx'
import { useParams } from 'react-router-dom'
import { App, Button, Modal, Select } from 'antd'
import { BasicResponse, COLUMNS_TITLE, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const.tsx'
import { useFetch } from '@common/hooks/http.ts'
@@ -59,7 +58,6 @@ export const addMemberToDepartment = (
const TeamInsideMember: FC = () => {
const [searchWord, setSearchWord] = useState<string>('')
const { setBreadcrumb } = useBreadcrumb()
const { modal, message } = App.useApp()
const { fetchData } = useFetch()
const { teamId } = useParams<RouterParams>()
@@ -73,7 +71,6 @@ const TeamInsideMember: FC = () => {
const [addMemberBtnDisabled, setAddMemberBtnDisabled] = useState<boolean>(true)
const [allMemberSelectedDepartIds, setAllMemberSelectedDepartIds] = useState<string[]>([])
const [roleList, setRoleList] = useState<EntityItem[]>([])
const navigator = useNavigate()
const operation: PageProColumns<TeamMemberTableListItem>[] = [
{
@@ -355,13 +352,6 @@ const TeamInsideMember: FC = () => {
}, [teamId])
useEffect(() => {
setBreadcrumb([
{
title: $t('团队'),
onClick: () => navigator('/team/list')
},
{ title: $t('成员') }
])
getRoleList()
}, [state.language])
@@ -16,6 +16,7 @@ import { PERMISSION_DEFINITION } from "@common/const/permissions.ts";
import { TeamConfigType } from "@core/const/team/type.ts";
import { $t } from "@common/locales/index.ts";
import { getItem } from "@common/utils/navigation.tsx";
import { useBreadcrumb } from "@common/contexts/BreadcrumbContext.tsx";
const TeamInsidePage:FC = ()=> {
const { message } = App.useApp()
@@ -26,6 +27,7 @@ const TeamInsidePage:FC = ()=> {
const {getTeamAccessData,cleanTeamAccessData,accessData,checkPermission,teamDataFlushed,accessInit,state} = useGlobalContext()
const navigateTo = useNavigate()
const [activeMenu, setActiveMenu] = useState<string>()
const { setBreadcrumb } = useBreadcrumb()
const onMenuClick: MenuProps['onClick'] = ({key}) => {
setActiveMenu(key)
@@ -88,6 +90,16 @@ const TeamInsidePage:FC = ()=> {
}
},[activeMenu])
useEffect(()=>{
setBreadcrumb([
{
title: $t('团队'),
onClick: () => navigateTo('/team/list')
},
{ title: teamInfo?.name || '-' }
])
},[state.language, teamInfo])
useEffect(()=>{
getTeamInfo()
teamId && getTeamAccessData(teamId)
@@ -62,6 +62,17 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
getProjectList()
}, [])
/**
*
*/
let resetTimeRange = () => {}
/**
*
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
}
const getApiList = (projectIds?: string[]) => {
return fetchData<{ apis: EntityItem[] }>('simple/service/apis', {
method: 'POST',
@@ -146,6 +157,7 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
}
const clearSearch = () => {
resetTimeRange()
setTimeButton('hour')
setDatePickerValue(null)
setQueryData(undefined)
@@ -186,11 +198,17 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
setDrawerOpen(true)
}
useEffect(() => {
setQueryBtnLoading(true)
getApiTableList()
}, [queryData])
return (
<div className="overflow-hidden h-full pr-PAGE_INSIDE_X">
<ScrollableSection>
<div className="pl-btnbase pr-btnrbase pb-btnbase content-before">
<TimeRangeSelector
bindRef={bindRef}
labelSize="small"
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
@@ -235,12 +253,8 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
<div className="w-[346px] inline-block">
{/* <SearchInputGroup eoSingle={false} eoInputVal={queryData.path} eoClick={() => setQueryData({ ...queryData, path: '' })} /> */}
<Input
value={queryData?.path}
onChange={(e) =>
debounce((e) => {
setQueryData((prevData) => ({ ...(prevData || {}), path: e.target.value }))
}, 100)(e)
}
value={queryData?.path || ''}
onChange={(e) => setQueryData((prevData) => ({ ...(prevData || {}), path: e.target.value }))}
allowClear
placeholder={$t('请输入请求路径进行搜索')}
prefix={<SearchOutlined className="cursor-pointer" />}
@@ -249,17 +263,6 @@ export default function MonitorApiPage(props: MonitorApiPageProps) {
<Button className="ml-btnybase" onClick={clearSearch}>
{$t('重置')}
</Button>
<Button
type="primary"
loading={queryBtnLoading}
className="ml-btnybase"
onClick={() => {
setQueryBtnLoading(true)
getApiTableList()
}}
>
{$t('查询')}
</Button>
<Button className="ml-btnybase" loading={exportLoading} onClick={exportData}>
{$t('导出')}
</Button>
@@ -58,7 +58,17 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
getMonitorData()
getAppList()
}, [])
/**
*
*/
let resetTimeRange = () => {}
/**
*
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
}
const getMonitorData = () => {
let query = queryData
if (!queryData || queryData.start === undefined) {
@@ -86,6 +96,7 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
}
const clearSearch = () => {
resetTimeRange()
setTimeButton('hour')
setDatePickerValue(null)
setQueryData({ type: 'subscriber' })
@@ -163,10 +174,16 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
setDrawerOpen(true)
}
useEffect(() => {
setQueryBtnLoading(true)
getAppTableList()
}, [queryData])
return (
<div className="h-full overflow-hidden pr-PAGE_INSIDE_X">
<div className="pl-btnbase pr-btnrbase pb-btnybase">
<TimeRangeSelector
bindRef={bindRef}
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
initialDatePickerValue={datePickerValue}
@@ -194,17 +211,6 @@ export default function MonitorAppPage(props: MonitorAppPageProps) {
<Button className="ml-btnybase" onClick={clearSearch}>
{$t('重置')}
</Button>
<Button
type="primary"
loading={queryBtnLoading}
className="ml-btnybase"
onClick={() => {
setQueryBtnLoading(true)
getAppTableList()
}}
>
{$t('查询')}
</Button>
<Button className="ml-btnybase" loading={exportLoading} onClick={exportData}>
{$t('导出')}
</Button>
@@ -78,7 +78,17 @@ export default function MonitorDetailPage(props: MonitorDetailPageProps) {
const monitorTableRef = useRef<MonitorTableHandler>(null)
const [modalTitle, setModalTitle] = useState<string>($t('调用趋势'))
const [queryBtnLoading, setQueryBtnLoading] = useState<boolean>(false)
/**
*
*/
let resetTimeRange = () => {}
/**
*
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
}
useEffect(() => {
// 初始化数据
getMonitorData()
@@ -144,6 +154,7 @@ export default function MonitorDetailPage(props: MonitorDetailPageProps) {
}
const clearSearch = () => {
resetTimeRange()
setTimeButton('hour')
setDatePickerValue(null)
setQueryData(null)
@@ -178,11 +189,17 @@ export default function MonitorDetailPage(props: MonitorDetailPageProps) {
setQueryData((pre) => ({ ...pre, ...timeRange }) as SearchBody)
}
useEffect(() => {
setQueryBtnLoading(true)
getMonitorData()
}, [queryData])
return (
<div className="pb-[20px] h-full box-border flex flex-col">
<div className="pl-btnbase pr-btnrbase pb-btnybase sticky top-[0] bg-[#fff] z-[10] shadow-SCROLL_TOP ">
<div className="flex flex-nowrap items-center mr-btnybase">
<TimeRangeSelector
bindRef={bindRef}
initialTimeButton={timeButton}
initialDatePickerValue={datePickerValue}
onTimeButtonChange={setTimeButton}
@@ -192,16 +209,6 @@ export default function MonitorDetailPage(props: MonitorDetailPageProps) {
<Button className="ml-btnybase mt-btnybase" onClick={clearSearch}>
{$t('重置')}
</Button>
<Button
className="ant-btn-primary ml-btnybase mt-btnybase"
loading={queryBtnLoading}
onClick={() => {
setQueryBtnLoading(true)
getMonitorData()
}}
>
{$t('查询')}
</Button>
</div>
</div>
<div className={`flex overflow-y-hidden flex-col flex-1`}>
@@ -63,7 +63,17 @@ export default function MonitorSubPage(props: MonitorSubPageProps) {
getMonitorData()
getProjectList()
}, [])
/**
*
*/
let resetTimeRange = () => {}
/**
*
* @param instance
*/
const bindRef = (instance: any) => {
resetTimeRange = instance.reset
}
const getMonitorData = () => {
let query = queryData
if (!queryData || queryData.start === undefined) {
@@ -91,6 +101,7 @@ export default function MonitorSubPage(props: MonitorSubPageProps) {
}
const clearSearch = () => {
resetTimeRange()
setTimeButton('hour')
setDatePickerValue(null)
setQueryData({ type: 'provider' })
@@ -168,10 +179,16 @@ export default function MonitorSubPage(props: MonitorSubPageProps) {
setDrawerOpen(true)
}
useEffect(() => {
setQueryBtnLoading(true)
getAppTableList()
}, [queryData])
return (
<div className="overflow-hidden h-full pr-PAGE_INSIDE_X">
<div className="pl-btnbase pr-btnrbase pb-btnybase">
<TimeRangeSelector
bindRef={bindRef}
initialTimeButton={timeButton}
onTimeButtonChange={setTimeButton}
initialDatePickerValue={datePickerValue}
@@ -198,17 +215,6 @@ export default function MonitorSubPage(props: MonitorSubPageProps) {
<Button className="ml-btnybase" onClick={clearSearch}>
{$t('重置')}
</Button>
<Button
type="primary"
loading={queryBtnLoading}
className="ml-btnybase"
onClick={() => {
setQueryBtnLoading(true)
getAppTableList()
}}
>
{$t('查询')}
</Button>
<Button className="ml-btnybase" loading={exportLoading} onClick={exportData}>
{$t('导出')}
</Button>
@@ -16,7 +16,7 @@ export default function Dashboard() {
const { message } = App.useApp()
const [clusters, setClusters] = useState<Array<{ id: string; name: string; enable: boolean }>>([])
const [enabledClusters, setEnabledClusters] = useState<Array<EntityItem>>([])
const [loading, setLoading] = useState(false)
const [loading, setLoading] = useState(true)
const getClusters = () => {
setLoading(true)
fetchData<BasicResponse<{ clusters: Array<{ id: string; name: string; enable: boolean }> }>>(
@@ -50,11 +50,7 @@ export default function Dashboard() {
return (
<>
<Spin
wrapperClassName="h-full w-full pb-PAGE_INSIDE_B "
indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}
spinning={loading}
>
<Spin wrapperClassName="h-full w-full pb-PAGE_INSIDE_B " indicator={<></>} spinning={loading}>
{!loading && (
<>
{enabledClusters.length > 0 ? (
@@ -10,8 +10,8 @@ export default function DashboardList() {
return (
<>
{dashboardType === 'api' && <DashboardApiList />}
{dashboardType === 'subscriber' && <DashboardProjectList />}
{dashboardType === 'provider' && <DashboardApplicationList />}
{dashboardType === 'service' && <DashboardProjectList />}
{dashboardType === 'consumer' && <DashboardApplicationList />}
</>
)
}
@@ -1,89 +1,436 @@
import { useNavigate } from 'react-router-dom'
import MonitorTotalPage from '@dashboard/component/MonitorTotalPage'
import { BasicResponse } from '@common/const/const'
import {
InvokeData,
MessageData,
MonitorApiData,
MonitorSubscriberData,
PieData,
SearchBody
} from '@dashboard/const/type'
import { useFetch } from '@common/hooks/http'
import { objectToSearchParameters } from '@common/utils/router'
import ScrollableSection from '@common/components/aoplatform/ScrollableSection'
import { TimeRange } from '@common/components/aoplatform/TimeRangeSelector'
import { useEffect, useState } from 'react'
import DateSelectFilter, { TimeOption } from '@core/pages/serviceOverview/filter/DateSelectFilter'
import { abbreviateFloat, formatBytes, formatDuration, formatNumberWithUnit, getTime } from '@dashboard/utils/dashboard'
import { $t } from '@common/locales/index.ts'
import { LoadingOutlined } from '@ant-design/icons'
import { Card, Spin } from 'antd'
import ServiceBarChar, { BarChartInfo } from '@core/pages/serviceOverview/charts/ServiceBarChar'
import ServiceAreaChart from '@core/pages/serviceOverview/charts/ServiceAreaChart'
import RankingList from '@core/pages/serviceOverview/rankingList/RankingList'
import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const'
import { setBarChartInfoData } from '@core/pages/serviceOverview/utils'
import { App } from 'antd'
import { useGlobalContext } from '@common/contexts/GlobalStateContext'
export default function DashboardTotal() {
/** 获取数据 */
const { fetchData } = useFetch()
const navigateTo = useNavigate()
const fetchPieData: (body: SearchBody) => Promise<BasicResponse<PieData>> = (body: SearchBody) =>
fetchData<BasicResponse<PieData>>('monitor/overview/summary', {
method: 'POST',
eoBody: body,
eoTransformKeys: ['request_summary', 'proxy_summary']
})
const fetchInvokeData: (body: SearchBody) => Promise<BasicResponse<InvokeData>> = (body: SearchBody) =>
fetchData<BasicResponse<InvokeData>>('monitor/overview/invoke', {
method: 'POST',
eoBody: body,
eoTransformKeys: ['request_total', 'request_rate', 'proxy_total', 'proxy_rate', 'time_interval']
})
const fetchMessageData: (body: SearchBody) => Promise<BasicResponse<MessageData>> = (body: SearchBody) =>
fetchData<BasicResponse<MessageData>>('monitor/overview/message', {
method: 'POST',
eoBody: body,
eoTransformKeys: ['time_interval', 'request_message', 'response_message']
})
const fetchTableData: (
body: SearchBody,
type: 'api' | 'subscribers' | 'providers'
) => Promise<BasicResponse<{ top10: MonitorApiData[] | MonitorSubscriberData[] }>> = (
body: SearchBody,
type: 'api' | 'subscribers' | 'providers'
) =>
fetchData<BasicResponse<{ api: MonitorApiData[]; subscribers: MonitorSubscriberData }>>('monitor/overview/top10', {
method: 'POST',
eoBody: { ...body, dataType: type },
eoTransformKeys: [
'dataType',
'request_total',
'request_success',
'request_rate',
'proxy_total',
'proxy_success',
'proxy_rate',
'status_fail',
'avg_resp',
'max_resp',
'min_resp',
'avg_traffic',
'max_traffic',
'min_traffic',
'min_traffic',
'is_red'
]
})
const goToDetail: (body: SearchBody, val: MonitorApiData | MonitorSubscriberData, type: string) => void = (
body: SearchBody,
val: MonitorApiData | MonitorSubscriberData,
type: string
) => {
// ...跳转到详情页...
const { start: startTime, end: endTime, clusters } = body
navigateTo(
`/analytics/${type}/list?${objectToSearchParameters({ id: val.id, clusters: clusters || undefined, start: startTime?.toString(), end: endTime?.toString(), name: val.name }).toString()}`
)
/** 默认时间 */
const [defaultTime] = useState<TimeOption>('day')
/** 当前选中的时间范围 */
const [timeRange, setTimeRange] = useState<TimeRange | undefined>()
/** 当前激活的标签 */
const [activeTab, setActiveTab] = useState('REST')
/** 面板 loading */
const [dashboardLoading, setDashboardLoading] = useState(false)
/** 总数数据 */
const [barChartInfo, setBarChartInfo] = useState<any>()
/** 平均值数据 */
const [perBarChartInfo, setPerBarChartInfo] = useState<any>()
/** 排名表格数据 */
const [topRankingList, setTopRankingList] = useState<any>([])
/** 弹窗组件 */
const { message } = App.useApp()
/** 全局状态 */
const { state } = useGlobalContext()
/** AI 服务数据 */
const [aiServiceOverview, setAiServiceOverview] = useState<any>()
/** REST 服务数据 */
const [restServiceOverview, setRestServiceOverview] = useState<any>()
/** 时间选择回调 */
const selectCallback = (date: TimeRange) => {
setTimeRange(date)
}
/** 获取 AI 服务信息 */
const getAIServiceOverview = () => {
return fetchData<BasicResponse<{ overview: any }>>('monitor/overview/chart/ai', {
method: 'GET',
eoParams: { start: timeRange?.start, end: timeRange?.end },
eoTransformKeys: [
'request_overview',
'token_overview',
'avg_token_overview',
'avg_request_per_subscriber_overview',
'avg_token_per_subscriber_overview',
'request_total',
'token_total',
'avg_token',
'min_token',
'max_token',
'avg_request_per_subscriber',
'avg_token_per_subscriber',
'input_token',
'output_token',
'total_token',
'request_2xx_total',
'request_4xx_total',
'request_5xx_total',
'input_token_total',
'output_token_total',
'max_request_per_subscriber',
'min_request_per_subscriber',
'max_token_per_subscriber',
'min_token_per_subscriber'
]
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 存储 AI 服务数据
setAiServiceOverview(data?.overview)
// 设置 AI 报表数据
setAiChartInfoData(data?.overview)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
/** 获取 REST 服务信息 */
const getRestServiceOverview = () => {
return fetchData<BasicResponse<{ overview: any }>>('monitor/overview/chart/rest', {
method: 'GET',
eoParams: { start: timeRange?.start, end: timeRange?.end },
eoTransformKeys: [
'request_overview',
'traffic_overview',
'avg_request_per_subscriber_overview',
'avg_response_time_overview',
'avg_traffic_per_subscriber_overview',
'request_total',
'traffic_total',
'avg_response_time',
'max_response_time',
'min_response_time',
'avg_request_per_subscriber',
'avg_traffic_per_subscriber',
'request_2xx_total',
'request_4xx_total',
'request_5xx_total',
'traffic_2xx_total',
'traffic_4xx_total',
'traffic_5xx_total',
'max_request_per_subscriber',
'min_request_per_subscriber',
'max_traffic_per_subscriber',
'min_traffic_per_subscriber'
]
}).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 存储 REST 服务数据
setRestServiceOverview(data?.overview)
// 设置 REST 报表数据
setRestChartInfoData(data?.overview)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
/**
* REST
* */
const setRestChartInfoData = (serviceOverview: any) => {
// 设置总数数据
setBarChartInfo([
// 服务请求次数
{
...setBarChartInfoData({
title: $t('请求次数'),
data: serviceOverview.requestOverview,
value: formatNumberWithUnit(serviceOverview.requestTotal),
date: serviceOverview.date
}),
request2xxTotal: formatNumberWithUnit(serviceOverview.request2xxTotal),
request4xxTotal: formatNumberWithUnit(serviceOverview.request4xxTotal),
request5xxTotal: formatNumberWithUnit(serviceOverview.request5xxTotal)
},
// 流量消耗总数
{
...setBarChartInfoData({
title: $t('网络流量'),
data: serviceOverview.trafficOverview,
value: formatBytes(serviceOverview.trafficTotal),
date: serviceOverview.date
}),
traffic2xxTotal: formatBytes(serviceOverview.traffic2xxTotal),
traffic4xxTotal: formatBytes(serviceOverview.traffic4xxTotal),
traffic5xxTotal: formatBytes(serviceOverview.traffic5xxTotal)
}
])
// 设置平均值数据
setPerBarChartInfo([
// 各个模型使用量
{
title: $t('平均响应时间'),
data: serviceOverview.avgResponseTimeOverview,
value: formatDuration(serviceOverview.avgResponseTime),
originValue: serviceOverview.avgResponseTime,
date: serviceOverview.date,
min: formatDuration(serviceOverview.minResponseTime),
max: formatDuration(serviceOverview.maxResponseTime),
type: 'area'
},
// 平均请求
{
...setBarChartInfoData({
title: $t('平均每消费者的请求次数'),
data: serviceOverview.avgRequestPerSubscriberOverview,
date: serviceOverview.date
}),
max: abbreviateFloat(serviceOverview.maxRequestPerSubscriber),
min: abbreviateFloat(serviceOverview.minRequestPerSubscriber)
},
// 平均流量消耗
{
...setBarChartInfoData({
title: $t('平均每消费者的网络流量'),
data: serviceOverview.avgTrafficPerSubscriberOverview,
date: serviceOverview.date
}),
max: formatBytes(serviceOverview.maxTrafficPerSubscriber),
min: formatBytes(serviceOverview.minTrafficPerSubscriber)
}
])
}
/**
* AI
* */
const setAiChartInfoData = (serviceOverview: any) => {
// 设置总数数据
setBarChartInfo([
// 服务请求次数
{
...setBarChartInfoData({
title: $t('请求次数'),
data: serviceOverview.requestOverview,
value: formatNumberWithUnit(serviceOverview.requestTotal),
date: serviceOverview.date
}),
request2xxTotal: formatNumberWithUnit(serviceOverview.request2xxTotal),
request4xxTotal: formatNumberWithUnit(serviceOverview.request4xxTotal),
request5xxTotal: formatNumberWithUnit(serviceOverview.request5xxTotal)
},
// token 消耗总数
{
...setBarChartInfoData({
title: $t('Token 消耗'),
data: serviceOverview.tokenOverview.map((item: { inputToken: number; outputToken: number }) => ({
inputToken: item.inputToken,
outputToken: item.outputToken
})),
value: formatNumberWithUnit(serviceOverview.tokenTotal),
date: serviceOverview.date
}),
inputTokenTotal: formatNumberWithUnit(serviceOverview.inputTokenTotal),
outputTokenTotal: formatNumberWithUnit(serviceOverview.outputTokenTotal)
}
])
// 设置平均值数据
setPerBarChartInfo([
// 平均 token 消耗
{
title: $t('平均 Token 消耗'),
data: serviceOverview.avgTokenOverview,
value: formatNumberWithUnit(serviceOverview.avgToken) + ' Token/s',
originValue: serviceOverview.avgToken,
date: serviceOverview.date,
min: formatNumberWithUnit(serviceOverview.minToken) + ' Token/s',
max: formatNumberWithUnit(serviceOverview.maxToken) + ' Token/s',
type: 'area'
},
// 平均请求
{
...setBarChartInfoData({
title: $t('平均每消费者的请求次数'),
data: serviceOverview.avgRequestPerSubscriberOverview,
date: serviceOverview.date
}),
max: abbreviateFloat(serviceOverview.maxRequestPerSubscriber),
min: abbreviateFloat(serviceOverview.minRequestPerSubscriber)
},
// 平均 token 消耗
{
...setBarChartInfoData({
title: $t('平均每消费者的 Token 消耗'),
data: serviceOverview.avgTokenPerSubscriberOverview.map(
(item: { inputToken: number; outputToken: number }) => ({
inputToken: item.inputToken,
outputToken: item.outputToken
})
),
date: serviceOverview.date
}),
max: abbreviateFloat(serviceOverview.maxTokenPerSubscriber),
min: abbreviateFloat(serviceOverview.minTokenPerSubscriber)
}
])
}
/** 获取排名列表 */
const getTopRankingList = () => {
return fetchData<BasicResponse<{ apis: any; consumers: any }>>(
`monitor/overview/top10/${activeTab === 'AI' ? 'ai' : 'rest'}`,
{
method: 'GET',
eoParams: { start: timeRange?.start, end: timeRange?.end }
}
).then((response) => {
const { code, data, msg } = response
if (code === STATUS_CODE.SUCCESS) {
// 设置排名表格数据
setTopRankingList({
'TOP API': data.apis,
'TOP Consumer': data.consumers
})
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
}
})
}
useEffect(() => {
const { startTime, endTime } = getTime(defaultTime, [])
setTimeRange({
start: startTime,
end: endTime
})
}, [])
useEffect(() => {
const fetchData = async () => {
setDashboardLoading(true)
try {
const requests = []
// 根据activeTab添加相应的请求
if (activeTab === 'AI') {
requests.push(getAIServiceOverview())
} else {
requests.push(getRestServiceOverview())
}
// 添加排名列表请求
requests.push(getTopRankingList())
// 等待所有请求完成
await Promise.all(requests)
} catch (error) {
console.error('加载数据出错:', error)
message.error($t('加载数据失败,请重试'))
} finally {
// 无论成功失败,最后都设置loading为false
setDashboardLoading(false)
}
}
if (timeRange) {
fetchData()
}
}, [timeRange, activeTab])
useEffect(() => {
if (activeTab === 'AI') {
aiServiceOverview && setAiChartInfoData(aiServiceOverview)
} else {
restServiceOverview && setRestChartInfoData(restServiceOverview)
}
}, [state.language])
return (
<MonitorTotalPage
fetchPieData={fetchPieData}
fetchInvokeData={fetchInvokeData}
fetchMessageData={fetchMessageData}
fetchTableData={fetchTableData}
goToDetail={goToDetail}
/>
<div className={`h-full overflow-hidden pb-btnybase flex flex-col bg-[#fff] `}>
<ScrollableSection>
<div className="flex items-center flex-wrap content-before bg-MAIN_BG pr-PAGE_INSIDE_X ">
<div className="pt-btnybase mr-[10px]">{$t('服务')}</div>
<div className="mt-3 tab-nav flex rounded-md overflow-hidden border border-solid border-[#3D46F2] w-[150px] mr-[30px]">
<div
className={`tab-item text-center px-5 py-1.5 cursor-pointer w-[50%] text-sm transition-colors ${activeTab === 'REST' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
onClick={() => setActiveTab('REST')}
>
REST
</div>
<div
className={`tab-item text-center px-5 py-1.5 cursor-pointer w-[50%] text-sm transition-colors ${activeTab === 'AI' ? 'bg-[#3D46F2] text-white' : 'bg-white text-[#3D46F2]'}`}
onClick={() => setActiveTab('AI')}
>
AI
</div>
</div>
<DateSelectFilter selectCallback={selectCallback} customClassNames="pt-[12px]" defaultTime={defaultTime} />
</div>
<Spin
className="h-full pb-[20px]"
wrapperClassName={`flex-1 overflow-auto`}
indicator={
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ transform: 'scale(1.5)' }}>
<LoadingOutlined style={{ fontSize: 30 }} spin />
</div>
</div>
}
spinning={dashboardLoading}
>
<div className="mr-[40px]">
<div className="mt-[20px] flex mb-[10px]">
{barChartInfo?.map((item: BarChartInfo, index: number) => (
<Card
key={index}
className={`flex-1 min-w-[430px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
classNames={{
body: 'py-[15px] px-[0px]'
}}
>
<ServiceBarChar
showLegendIndicator={true}
key={index}
height={400}
dataInfo={item}
customClassNames="flex-1"
></ServiceBarChar>
</Card>
))}
</div>
<div className="flex mb-[10px]">
{perBarChartInfo?.map((item: any, index: number) => (
<Card
key={index}
className={`flex-1 min-w-[284px] rounded-[10px] ${index > 0 ? 'ml-[10px]' : ''}`}
classNames={{
body: 'py-[15px] px-[0px]'
}}
>
{item.type === 'area' ? (
<>
<ServiceAreaChart
key={index}
height={270}
showAvgLine={true}
dataInfo={item}
customClassNames="flex-1 relative"
></ServiceAreaChart>
</>
) : (
<ServiceBarChar
key={index}
height={270}
hideIndicatorValue={true}
dataInfo={item}
customClassNames="flex-1"
></ServiceBarChar>
)}
</Card>
))}
</div>
<RankingList
topRankingList={topRankingList}
serviceType={activeTab === 'AI' ? 'aiService' : 'restService'}
/>
</div>
</Spin>
</ScrollableSection>
</div>
)
}
@@ -89,3 +89,114 @@ export function yUnitFormatter(value: number): string {
}
return res
}
/**
*
*
* - 1000
* - (K): 1,000 - 999,999
* - (M): 1,000,000 - 999,999,999
* - 亿(B): 1,000,000,000 - 999,999,999,999
* - 亿(T): 1,000,000,000,000
* @param count
* @returns
*/
export function formatNumberWithUnit(count: number) {
if (count < 1000) {
return count.toString() // 小于1000直接返回
} else if (count < 1_000_000) {
return (count / 1000).toFixed(1) + 'K' // 千级别,如1.5K
} else if (count < 1_000_000_000) {
return (count / 1_000_000).toFixed(1) + 'M' // 百万级别,如2.3M
} else if (count < 1_000_000_000_000) {
return (count / 1_000_000_000).toFixed(1) + 'B' // 十亿级别,如4.5B
} else {
return (count / 1_000_000_000_000).toFixed(1) + 'T' // 万亿级别,如7.8T
}
}
/**
*
* formatNumberWithUnit 10001
* - 10001
* - (K): 1,000 - 999,999
* - (M): 1,000,000 - 999,999,999
* - 亿(B): 1,000,000,000 - 999,999,999,999
* - 亿(T): 1,000,000,000,000
* @param count
* @returns
*/
export function abbreviateFloat(count: number) {
if (count < 1000) {
return count.toFixed(1) // 小于1000的数字保留1位小数,如5.0
} else if (count < 1_000_000) {
return (count / 1000).toFixed(1) + 'K' // 千级别,如1.5K
} else if (count < 1_000_000_000) {
return (count / 1_000_000).toFixed(1) + 'M' // 百万级别,如2.3M
} else if (count < 1_000_000_000_000) {
return (count / 1_000_000_000).toFixed(1) + 'B' // 十亿级别,如4.5B
} else {
return (count / 1_000_000_000_000).toFixed(1) + 'T' // 万亿级别,如7.8T
}
}
/**
*
*
* - (ms): < 1000
* - (s): 1,000 - 999,999
* - (min): 1,000,000 - 999,999,999
* - (hour): 1,000,000,000 - 999,999,999,999
* - (day): 1,000,000,000,000
* @param durationNano
* @returns
*/
export function formatDuration(durationNano: number) {
if (durationNano < 1000) {
return `${durationNano}ms` // 小于1000,返回毫秒
}
if (durationNano < 1_000_000) {
return (durationNano / 1000).toFixed(1) + 's' // 转换为秒
}
if (durationNano < 1_000_000_000) {
return (durationNano / 1_000_000).toFixed(1) + 'min' // 转换为分钟
}
if (durationNano < 1_000_000_000_000) {
return (durationNano / 1_000_000_000).toFixed(1) + 'hour' // 转换为小时
}
return (durationNano / 1_000_000_000_000).toFixed(1) + 'day' // 转换为天
}
/**
*
*
* - (B): < 1000
* - (KB): 1,000 - 999,999
* - (MB): 1,000,000 - 999,999,999
* - (GB): 1,000,000,000 - 999,999,999,999
* - (TB): 1,000,000,000,000 - 999,999,999,999,999
* - (PB): 1,000,000,000,000,000
* @param bytes
* @returns
*/
export function formatBytes(bytes: number) {
const KB = 1000 // 千字节
const MB = KB * 1000 // 兆字节
const GB = MB * 1000 // 吉字节
const TB = GB * 1000 // 太字节
const PB = TB * 1000 // 拉字节
if (bytes < KB) {
return `${bytes}B` // 直接显示字节
} else if (bytes < MB) {
return (bytes / KB).toFixed(1) + 'KB' // 转换为千字节
} else if (bytes < GB) {
return (bytes / MB).toFixed(1) + 'MB' // 转换为兆字节
} else if (bytes < TB) {
return (bytes / GB).toFixed(1) + 'GB' // 转换为吉字节
} else if (bytes < PB) {
return (bytes / TB).toFixed(1) + 'TB' // 转换为太字节
} else {
return (bytes / PB).toFixed(1) + 'PB' // 转换为拉字节
}
}
@@ -53,7 +53,7 @@ const PUBLIC_ROUTES: RouteConfig[] = [
key: uuidv4(),
children: [
{
path: 'serviceHub',
path: 'portal',
component: <Outlet />,
key: uuidv4(),
children: [
@@ -151,7 +151,7 @@ const PUBLIC_ROUTES: RouteConfig[] = [
},
{
path: '*',
component: <Navigate to={'/serviceHub/list'} replace />,
component: <Navigate to={'/portal/list'} replace />,
key: uuidv4()
}
]
@@ -20,6 +20,7 @@ import { Tool } from '@modelcontextprotocol/sdk/types.js'
import McpToolsContainer from '@core/pages/mcpService/McpToolsContainer.tsx'
import { useGlobalContext } from '@common/contexts/GlobalStateContext.tsx'
import TopBreadcrumb from '@common/components/aoplatform/Breadcrumb.tsx'
import ServiceInfoCard from '@common/components/aoplatform/serviceInfoCard.tsx'
type TabItemType = {
key: string
@@ -32,18 +33,12 @@ const ServiceHubDetail = () => {
const { serviceId } = useParams<RouterParams>()
const { setBreadcrumb } = useBreadcrumb()
const [serviceBasicInfo, setServiceBasicInfo] = useState<ServiceBasicInfoType>()
const [serviceName, setServiceName] = useState<string>()
const [serviceDesc, setServiceDesc] = useState<string>()
const [serviceDoc, setServiceDoc] = useState<string>()
const { fetchData } = useFetch()
const applyRef = useRef<ApplyServiceHandle>(null)
const { modal, message } = App.useApp()
const [mySystemOptionList, setMySystemOptionList] = useState<DefaultOptionType[]>()
const [service, setService] = useState<ServiceDetailType>()
const [serviceMetrics, setServiceMetrics] = useState<{ title: string; icon: React.ReactNode; value: string }[]>([])
const [serviceTags, setServiceTags] = useState<
{ color: string; textColor: string; title: string; content: React.ReactNode }[]
>([])
const [tools, setTools] = useState<Tool[]>([])
const [tabItem, setTabItem] = useState<TabItemType[]>([])
const [currentTab, setCurrentTab] = useState('')
@@ -149,10 +144,7 @@ servers:
apiDoc: modifyApiDoc(data.service.apiDoc, data.service.basic?.invokeAddress)
})
setServiceBasicInfo(data.service.basic)
setServiceName(data.service.name)
setServiceDesc(data.service.description)
setServiceDoc(DOMPurify.sanitize(data.service.document))
setServiceMetricsList(data.service.basic)
setTabItemList(data.service.basic)
} else {
message.error(msg || $t(RESPONSE_TIPS.error))
@@ -164,54 +156,6 @@ servers:
setCurrentTab(value)
}
const setServiceMetricsList = (serviceBasicInfo: ServiceBasicInfoType) => {
// 设置服务指标数据
setServiceMetrics([
{
title: 'API 数量',
icon: <ApiOutlined className="mr-[1px] text-[14px] h-[14px] w-[14px]" />,
value: serviceBasicInfo.apiNum.toString()
},
{
title: '接入消费者数量',
icon: <Icon icon="tabler:api-app" width="14" height="14" />,
value: serviceBasicInfo.appNum.toString()
},
{
title: '30天内调用次数',
icon: <Icon icon="iconoir:graph-up" width="14" height="14" />,
value: formatInvokeCount(serviceBasicInfo.invokeCount ?? 0)
}
])
// 设置服务标签数据
const tags = [
{
color: '#7371fc1b',
textColor: 'text-theme',
title: serviceBasicInfo?.catalogue?.name || '-',
content: serviceBasicInfo?.catalogue?.name || '-'
},
{
color: `#${serviceBasicInfo?.serviceKind === 'ai' ? 'EADEFF' : 'DEFFE7'}`,
textColor: 'text-[#000]',
title: serviceBasicInfo?.serviceKind || '-',
content: SERVICE_KIND_OPTIONS.find((x) => x.value === serviceBasicInfo?.serviceKind)?.label || '-'
}
]
// 如果启用了MCP,添加MCP标签
if (serviceBasicInfo?.enableMcp) {
tags.push({
color: '#FFF0C1',
textColor: 'text-[#000]',
title: 'MCP',
content: 'MCP'
})
}
setServiceTags(tags)
}
useEffect(() => {
if (!serviceId) {
console.warn('缺少serviceId')
@@ -227,11 +171,12 @@ servers:
setBreadcrumb([
{
title: $t('API 门户'),
onClick: () => navigate(`/serviceHub/list`)
onClick: () => navigate(`/portal/list`)
},
{ title: service?.name || '-' },
{ title: $t('服务详情') }
])
}, [state.language])
}, [state.language, service])
const getMySelectList = () => {
setMySystemOptionList([])
@@ -270,7 +215,7 @@ servers:
content: (
<ApplyServiceModal
ref={applyRef}
entity={{ ...serviceBasicInfo!, name: serviceName!, id: serviceId! }}
entity={{ ...serviceBasicInfo!, name: service?.name || '', id: serviceId! }}
mySystemOptionList={mySystemOptionList!}
/>
),
@@ -293,19 +238,6 @@ servers:
const handleToolsChange = (value: Tool[]) => {
setTools(value)
}
// 格式化调用次数,添加K和M单位
const formatInvokeCount = (count: number | null | undefined): string => {
if (count === null || count === undefined) return '-'
if (count >= 1000000) {
const value = Math.floor(count / 100000) / 10
return `${value}M`
}
if (count >= 1000) {
const value = Math.floor(count / 100) / 10
return `${value}K`
}
return count.toString()
}
/**
* serviceBasicInfo或tools变化时调用
@@ -429,76 +361,23 @@ servers:
return (
<div className="pr-[40px]">
<header>
<TopBreadcrumb handleBackCallback={() => navigate(`/serviceHub/list`)} />
<TopBreadcrumb handleBackCallback={() => navigate(`/portal/list`)} />
</header>
<Card
style={{
borderRadius: '10px',
background: 'linear-gradient(35deg, rgb(246, 246, 260) 0%, rgb(255, 255, 255) 40%)'
<ServiceInfoCard
serviceBasicInfo={{
...serviceBasicInfo,
serviceName: service?.name || '',
serviceDesc: service?.description || ''
}}
className={`w-full mt-[20px]`}
classNames={{
body: 'p-[15px] h-[180px]'
}}
>
<div className="service-info">
<div className="flex items-center">
<div>
<Avatar
shape="square"
size={50}
className={`rounded-[12px] border-none rounded-[12px] ${serviceBasicInfo?.logo ? 'bg-[linear-gradient(135deg,white,#f0f0f0)]' : 'bg-theme'}`}
src={
serviceBasicInfo?.logo ? (
<img
src={serviceBasicInfo?.logo}
alt="Logo"
style={{ maxWidth: '200px', width: '45px', height: '45px', objectFit: 'unset' }}
/>
) : undefined
}
icon={serviceBasicInfo?.logo ? '' : <Icon icon="tabler:api-app" />}
>
{' '}
</Avatar>
</div>
<div className="pl-[20px] w-[calc(100%-50px)] overflow-hidden">
<p className="text-[14px] h-[20px] leading-[20px] truncate font-bold w-full flex items-center gap-[4px]">
{serviceName}
</p>
<div className="mt-[5px] h-[20px] flex items-center font-normal">
{serviceTags.map((tag, index) => (
<Tag
key={index}
color={tag.color}
className={`${tag.textColor} font-normal border-0 mr-[12px] max-w-[150px] truncate`}
bordered={false}
title={tag.title}
>
{tag.content}
</Tag>
))}
{serviceMetrics.map((item, index) => (
<Tooltip key={index} title={$t(item.title)}>
<span className="mr-[12px] flex items-center">
<span className="h-[14px] mr-[4px] flex items-center">{item.icon}</span>
<span className="font-normal text-[14px]">{item.value}</span>
</span>
</Tooltip>
))}
</div>
</div>
</div>
<span className="line-clamp-2 mt-[15px] text-[12px] text-[#666]" title={serviceDesc}>
{serviceDesc || $t('暂无服务描述')}
</span>
</div>
<div className="absolute bottom-[15px]">
<Button type="primary" onClick={() => openModal('apply')}>
{$t('申请')}
</Button>
</div>
</Card>
customClassName="mt-[20px]"
actionSlot={
<>
<Button type="primary" onClick={() => openModal('apply')}>
{$t('申请')}
</Button>
</>
}
/>
<div className="flex">
<Tabs
className="p-btnbase pr-0 overflow-hidden [&>.ant-tabs-content-holder]:overflow-auto w-full flex-1 mr-[10px]"
@@ -254,7 +254,7 @@ export default function ManagementInsideService() {
<Button
type="text"
className="bg-[#7371fc20] hover:bg-[#7371fc19] text-theme"
onClick={() => window.open(`/serviceHub/detail/${item.service.id}`, '_blank')}
onClick={() => window.open(`/portal/detail/${item.service.id}`, '_blank')}
>
{$t('API 文档')}
</Button>
+1 -1
View File
@@ -7,7 +7,7 @@ toolchain go1.23.6
require (
github.com/eolinker/ap-account v1.0.15
github.com/eolinker/eosc v0.18.3
github.com/eolinker/go-common v1.1.6
github.com/eolinker/go-common v1.1.7
github.com/gabriel-vasile/mimetype v1.4.4
github.com/getkin/kin-openapi v0.127.0
github.com/gin-contrib/gzip v1.0.1
+2 -2
View File
@@ -32,8 +32,8 @@ github.com/eolinker/ap-account v1.0.15 h1:n6DJeL6RHZ8eLlZUcY2U3H4d/GPaA5oelAx3R0
github.com/eolinker/ap-account v1.0.15/go.mod h1:zm/Ivs6waJ/M/nEszhpPmM6g50y/MKO+5eABFAdeD0g=
github.com/eolinker/eosc v0.18.3 h1:3IK5HkAPnJRfLbQ0FR7kWsZr6Y/OiqqGazvN1q2BL5A=
github.com/eolinker/eosc v0.18.3/go.mod h1:O9PQQXFCpB6fjHf+oFt/LN6EOAv779ItbMixMKCfTfk=
github.com/eolinker/go-common v1.1.6 h1:s+NaQL0InjX/MwWY53+8y8qzAgsULIUc4U6nWXWQ2Nw=
github.com/eolinker/go-common v1.1.6/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
github.com/eolinker/go-common v1.1.7 h1:bi7wDmlCYQGjS3k8Bz/o+Mo9aMJAzmPsBLXWurxPfwk=
github.com/eolinker/go-common v1.1.7/go.mod h1:Kb/jENMN1mApnodvRgV4YwO9FJby1Jkt2EUjrBjvSX4=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
+2 -1
View File
@@ -9,7 +9,8 @@ import (
type ILogDriver interface {
LogInfo(clusterId string, id string) (*LogInfo, error)
LogCount(clusterId string, conditions map[string]string, spendHour int64, group string) (map[string]int64, error)
Logs(clusterId string, conditions map[string]string, start time.Time, end time.Time, limit int64, offset int64) ([]*Log, int64, error)
Logs(clusterId string, conditions map[string]string, start time.Time, end time.Time, limit int64, offset int64) ([]*LogItem, int64, error)
LogRecords(clusterId string, start time.Time, end time.Time) ([]*LogItem, error)
}
var (
+14 -2
View File
@@ -4,22 +4,34 @@ import (
"time"
)
type Log struct {
type LogItem struct {
ID string
Strategy string
Service string
API string
Method string
Url string
RemoteIP string
Consumer string
Authorization string
InputToken int64
OutputToken int64
TotalToken int64
AIProvider string
AIModel string
StatusCode int64
ResponseTime int64
Traffic int64
RecordTime time.Time
}
type LogInfo struct {
ID string
*LogItem
ContentType string
RequestBody string
ProxyBody string
ProxyResponseBody string
ResponseBody string
RequestHeader string
ResponseHeader string
}
+34 -19
View File
@@ -49,29 +49,44 @@ type LogCount struct {
}
type LogInfo struct {
Stream *LogDetail `json:"stream"`
Stream *LogDetail `json:"stream"`
Values []interface{} `json:"values"`
}
type LogDetail struct {
Api string `json:"api"`
Application string `json:"application"`
Strategy string `json:"strategy"`
ContentType string `json:"content_type"`
Cluster string `json:"cluster"`
Msec string `json:"msec"`
Node string `json:"node"`
RequestId string `json:"request_id"`
RequestMethod string `json:"request_method"`
RequestScheme string `json:"request_scheme"`
RequestTime string `json:"request_time"`
RequestUri string `json:"request_uri"`
type LogBodyDetail struct {
RequestBody string `json:"request_body"`
ProxyBody string `json:"proxy_body"`
ResponseBody string `json:"response_body"`
ProxyResponseBody string `json:"proxy_response_body"`
Service string `json:"service"`
Provider string `json:"provider"`
Authorization string `json:"authorization"`
SrcIp string `json:"src_ip"`
Status string `json:"status"`
}
type LogDetail struct {
Api string `json:"api"`
Application string `json:"application"`
Strategy string `json:"strategy"`
ContentType string `json:"content_type"`
Cluster string `json:"cluster"`
Msec string `json:"msec"`
Node string `json:"node"`
RequestId string `json:"request_id"`
RequestMethod string `json:"request_method"`
RequestScheme string `json:"request_scheme"`
RequestHeader string `json:"request_header"`
RequestTime string `json:"request_time"`
RequestUri string `json:"request_uri"`
RequestBody string `json:"request_body"`
ProxyBody string `json:"proxy_body"`
ResponseBody string `json:"response_body"`
ResponseHeader string `json:"response_header"`
ProxyResponseBody string `json:"proxy_response_body"`
Service string `json:"service"`
Provider string `json:"provider"`
Authorization string `json:"authorization"`
SrcIp string `json:"src_ip"`
Status string `json:"status"`
AIProvider string `json:"ai_provider"`
AIModel string `json:"ai_model"`
AIModelInputToken interface{} `json:"ai_model_input_token"`
AIModelOutputToken interface{} `json:"ai_model_output_token"`
AIModelTotalToken interface{} `json:"ai_model_total_token"`
}
+97 -21
View File
@@ -81,13 +81,39 @@ func (d *Driver) LogInfo(clusterId string, id string) (*log_driver.LogInfo, erro
return nil, fmt.Errorf("no log found")
}
stream := list[0].Stream
requestBody := stream.RequestBody
proxyRequestBody := stream.ProxyBody
proxyResponseBody := stream.ProxyResponseBody
responseBody := stream.ResponseBody
if len(list[0].Values) > 0 {
switch t := list[0].Values[0].(type) {
case []interface{}:
if len(t) > 1 {
v, ok := t[1].(string)
if !ok {
break
}
var tmp LogBodyDetail
err = json.Unmarshal([]byte(v), &tmp)
if err == nil {
requestBody = tmp.RequestBody
proxyRequestBody = tmp.ProxyBody
responseBody = tmp.ResponseBody
proxyResponseBody = tmp.ProxyBody
}
}
}
}
msec, _ := strconv.ParseInt(stream.Msec, 10, 64)
return &log_driver.LogInfo{
ID: stream.RequestId,
LogItem: ToLogItem(stream, msec),
ContentType: stream.ContentType,
RequestBody: stream.RequestBody,
ProxyBody: stream.ProxyBody,
ProxyResponseBody: stream.ProxyResponseBody,
ResponseBody: stream.ResponseBody,
RequestBody: requestBody,
ProxyBody: proxyRequestBody,
ProxyResponseBody: proxyResponseBody,
ResponseBody: responseBody,
RequestHeader: stream.RequestHeader,
ResponseHeader: stream.ResponseHeader,
}, nil
}
@@ -132,7 +158,25 @@ func (d *Driver) LogCount(clusterId string, conditions map[string]string, spendH
return result, nil
}
func (d *Driver) Logs(clusterId string, conditions map[string]string, start time.Time, end time.Time, limit int64, offset int64) ([]*log_driver.Log, int64, error) {
func (d *Driver) LogRecords(clusterId string, start time.Time, end time.Time) ([]*log_driver.LogItem, error) {
if start.After(end) {
return nil, fmt.Errorf("start time is greater than end time")
}
queries := url.Values{}
queries.Set("query", fmt.Sprintf("{cluster=\"%s\"} | json", clusterId))
queries.Set("direction", "backward")
queries.Set("start", strconv.FormatInt(start.UnixNano(), 10))
queries.Set("end", strconv.FormatInt(end.UnixNano(), 10))
log.Debug("query is ", queries.Get("query"))
logs, err := d.recuseLogs(queries, end, 1)
if err != nil {
return nil, err
}
return logs, nil
}
func (d *Driver) Logs(clusterId string, conditions map[string]string, start time.Time, end time.Time, limit int64, offset int64) ([]*log_driver.LogItem, int64, error) {
if start.After(end) {
return nil, 0, fmt.Errorf("start time is greater than end time")
}
@@ -177,7 +221,30 @@ func (d *Driver) Logs(clusterId string, conditions map[string]string, start time
return logs, count, nil
}
func (d *Driver) recuseLogs(queries url.Values, end time.Time, offset int64) ([]*log_driver.Log, error) {
func ToLogItem(detail *LogDetail, msec int64) *log_driver.LogItem {
return &log_driver.LogItem{
ID: detail.RequestId,
Strategy: detail.Strategy,
Service: detail.Provider,
API: detail.Api,
Method: detail.RequestMethod,
Url: detail.RequestUri,
RemoteIP: detail.SrcIp,
Consumer: detail.Application,
Authorization: detail.Authorization,
InputToken: parseToInt64(detail.AIModelInputToken),
OutputToken: parseToInt64(detail.AIModelOutputToken),
TotalToken: parseToInt64(detail.AIModelTotalToken),
AIProvider: detail.AIProvider,
AIModel: detail.AIModel,
StatusCode: parseToInt64(detail.Status),
ResponseTime: parseToInt64(detail.RequestTime),
Traffic: int64(len(detail.ResponseBody) + len(detail.RequestBody)),
RecordTime: time.UnixMilli(msec),
}
}
func (d *Driver) recuseLogs(queries url.Values, end time.Time, offset int64) ([]*log_driver.LogItem, error) {
queries.Set("end", strconv.FormatInt(end.UnixNano(), 10))
list, err := send[LogInfo](http.MethodGet, fmt.Sprintf("%s/loki/api/v1/query_range", d.url), d.headers, queries, "")
if err != nil {
@@ -198,24 +265,13 @@ func (d *Driver) recuseLogs(queries url.Values, end time.Time, offset int64) ([]
}
return d.recuseLogs(queries, time.UnixMilli(msec), offset-1)
}
logs := make([]*log_driver.Log, 0, len(list))
logs := make([]*log_driver.LogItem, 0, len(list))
for _, l := range list {
if l.Stream == nil {
continue
}
detail := l.Stream
msec, _ := strconv.ParseInt(detail.Msec, 10, 64)
logs = append(logs, &log_driver.Log{
ID: detail.RequestId,
Service: detail.Provider,
Method: detail.RequestMethod,
Url: detail.RequestUri,
RemoteIP: detail.SrcIp,
Consumer: detail.Application,
Authorization: detail.Authorization,
RecordTime: time.UnixMilli(msec),
})
msec, _ := strconv.ParseInt(l.Stream.Msec, 10, 64)
logs = append(logs, ToLogItem(l.Stream, msec))
}
sort.Slice(logs, func(i, j int) bool {
return logs[i].RecordTime.After(logs[j].RecordTime)
@@ -223,6 +279,26 @@ func (d *Driver) recuseLogs(queries url.Values, end time.Time, offset int64) ([]
return logs, nil
}
func parseToInt64(v interface{}) int64 {
switch t := v.(type) {
case int:
return int64(t)
case int64:
return t
case string:
if v == "" {
return 0
}
i, err := strconv.ParseInt(t, 10, 64)
if err != nil {
return 0
}
return i
default:
return 0
}
}
func (d *Driver) logCount(clusterId string, conditions map[string]string, start time.Time, end time.Time) (int64, error) {
// 先查在这段时间内符合条件的日志数量
queries := url.Values{}
+5 -5
View File
@@ -44,12 +44,12 @@ func TestLoki(t *testing.T) {
// if err != nil {
// t.Fatalf("failed to send request: %v", err)
// }
// t.Log(time.Now().Sub(a))
// t.LogItem(time.Now().Sub(a))
// data, err := json.Marshal(result)
// if err != nil {
// t.Fatalf("failed to marshal data: %v", err)
// }
// t.Log(string(data))
// t.LogItem(string(data))
//}
//
//func TestLokiLogCount(t *testing.T) {
@@ -67,7 +67,7 @@ func TestLoki(t *testing.T) {
// if err != nil {
// t.Fatalf("failed to marshal data: %v", err)
// }
// t.Log(string(data))
// t.LogItem(string(data))
//}
//
//func TestLokiLogs(t *testing.T) {
@@ -83,7 +83,7 @@ func TestLoki(t *testing.T) {
// queries.Set("limit", "1")
// now = time.Now()
// result, err := send[map[string]interface{}](http.MethodGet, "http://localhost:3100/loki/api/v1/query_range", headers, queries, "")
// t.Log(time.Now().Sub(now))
// t.LogItem(time.Now().Sub(now))
// if err != nil {
// t.Fatalf("failed to send request: %v", err)
// }
@@ -91,5 +91,5 @@ func TestLoki(t *testing.T) {
// if err != nil {
// t.Fatalf("failed to marshal data: %v", err)
// }
// t.Log(string(data))
// t.LogItem(string(data))
//}

Some files were not shown because too many files have changed in this diff Show More